@khanacademy/perseus 49.2.2 → 50.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 (146) hide show
  1. package/dist/article-renderer.d.ts +2 -6
  2. package/dist/components/graph.d.ts +1 -1
  3. package/dist/components/graphie.d.ts +1 -1
  4. package/dist/components/highlighting/ui/highlight-set-renderer.d.ts +1 -4
  5. package/dist/components/highlighting/ui/highlighting-ui.d.ts +1 -4
  6. package/dist/components/i18n-context.d.ts +1 -1
  7. package/dist/components/input-with-examples.d.ts +1 -4
  8. package/dist/components/math-input.d.ts +2 -6
  9. package/dist/components/number-input.d.ts +2 -5
  10. package/dist/components/svg-image.d.ts +1 -1
  11. package/dist/es/index.js +529 -1346
  12. package/dist/es/index.js.map +1 -1
  13. package/dist/es/strings.js +19 -1
  14. package/dist/es/strings.js.map +1 -1
  15. package/dist/hint-renderer.d.ts +1 -4
  16. package/dist/hints-renderer.d.ts +2 -5
  17. package/dist/index.d.ts +5 -7
  18. package/dist/index.js +588 -1407
  19. package/dist/index.js.map +1 -1
  20. package/dist/renderer-util.d.ts +1 -1
  21. package/dist/renderer.d.ts +2 -5
  22. package/dist/server-item-renderer.d.ts +3 -7
  23. package/dist/strings.d.ts +8 -0
  24. package/dist/strings.js +19 -0
  25. package/dist/strings.js.map +1 -1
  26. package/dist/types.d.ts +1 -2
  27. package/dist/util/extract-perseus-data.d.ts +1 -1
  28. package/dist/util/parse-perseus-json/index.d.ts +33 -4
  29. package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +1 -1
  30. package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +1 -1
  31. package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +1 -1
  32. package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +1 -1
  33. package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +1 -1
  34. package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +1 -1
  35. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +1 -1
  36. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +3 -3
  37. package/dist/util/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +1 -1
  38. package/dist/util/parse-perseus-json/perseus-parsers/group-widget.d.ts +1 -1
  39. package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +1 -1
  40. package/dist/util/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +1 -1
  41. package/dist/util/parse-perseus-json/perseus-parsers/image-widget.d.ts +1 -1
  42. package/dist/util/parse-perseus-json/perseus-parsers/images-map.d.ts +1 -1
  43. package/dist/util/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +1 -1
  44. package/dist/util/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +1 -1
  45. package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +1 -1
  46. package/dist/util/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +1 -1
  47. package/dist/util/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +1 -1
  48. package/dist/util/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +1 -1
  49. package/dist/util/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +1 -1
  50. package/dist/util/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +1 -1
  51. package/dist/util/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +1 -1
  52. package/dist/util/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +1 -1
  53. package/dist/util/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +1 -1
  54. package/dist/util/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +1 -1
  55. package/dist/util/parse-perseus-json/perseus-parsers/passage-widget.d.ts +1 -1
  56. package/dist/util/parse-perseus-json/perseus-parsers/perseus-article.d.ts +3 -0
  57. package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +1 -1
  58. package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +1 -1
  59. package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +1 -1
  60. package/dist/util/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +1 -1
  61. package/dist/util/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +1 -1
  62. package/dist/util/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +1 -1
  63. package/dist/util/parse-perseus-json/perseus-parsers/radio-widget.d.ts +1 -1
  64. package/dist/util/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +1 -1
  65. package/dist/util/parse-perseus-json/perseus-parsers/table-widget.d.ts +1 -1
  66. package/dist/util/parse-perseus-json/perseus-parsers/versioned-widget-options.d.ts +1 -1
  67. package/dist/util/parse-perseus-json/perseus-parsers/video-widget.d.ts +1 -1
  68. package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +1 -1
  69. package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +1 -1
  70. package/dist/util/test-utils.d.ts +1 -1
  71. package/dist/util.d.ts +1 -1
  72. package/dist/validation.types.d.ts +9 -3
  73. package/dist/widget-ai-utils/grapher/grapher-ai-utils.d.ts +1 -1
  74. package/dist/widget-ai-utils/interactive-graph/interactive-graph-ai-utils.d.ts +1 -1
  75. package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +13 -0
  76. package/dist/widget-ai-utils/prompt-types.d.ts +2 -1
  77. package/dist/widget-container.d.ts +1 -1
  78. package/dist/widget-type-utils.d.ts +1 -1
  79. package/dist/widgets/categorizer/categorizer.d.ts +2 -5
  80. package/dist/widgets/cs-program/cs-program.d.ts +1 -1
  81. package/dist/widgets/definition/definition.d.ts +2 -5
  82. package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +1 -4
  83. package/dist/widgets/dropdown/dropdown.d.ts +2 -5
  84. package/dist/widgets/explanation/explanation.d.ts +2 -5
  85. package/dist/widgets/expression/expression.d.ts +5 -8
  86. package/dist/widgets/graded-group/graded-group-answer-bar.d.ts +1 -4
  87. package/dist/widgets/graded-group/graded-group.d.ts +2 -5
  88. package/dist/widgets/graded-group-set/graded-group-set.d.ts +1 -1
  89. package/dist/widgets/grapher/grapher.d.ts +2 -2
  90. package/dist/widgets/group/group.d.ts +2 -5
  91. package/dist/widgets/iframe/iframe.d.ts +1 -1
  92. package/dist/widgets/image/image.d.ts +2 -5
  93. package/dist/widgets/input-number/input-number.d.ts +2 -6
  94. package/dist/widgets/interaction/interaction.d.ts +1 -1
  95. package/dist/widgets/interactive-graph.d.ts +4 -6
  96. package/dist/widgets/interactive-graphs/backgrounds/grid.d.ts +2 -2
  97. package/dist/widgets/interactive-graphs/backgrounds/legacy-grid.d.ts +1 -1
  98. package/dist/widgets/interactive-graphs/graph-locked-labels-layer.d.ts +1 -1
  99. package/dist/widgets/interactive-graphs/graph-locked-layer.d.ts +1 -1
  100. package/dist/widgets/interactive-graphs/graphs/circle.d.ts +2 -0
  101. package/dist/widgets/interactive-graphs/graphs/components/angle-indicators.d.ts +1 -1
  102. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +2 -2
  103. package/dist/widgets/interactive-graphs/locked-figures/locked-ellipse.d.ts +1 -1
  104. package/dist/widgets/interactive-graphs/locked-figures/locked-function.d.ts +1 -1
  105. package/dist/widgets/interactive-graphs/locked-figures/locked-label.d.ts +1 -1
  106. package/dist/widgets/interactive-graphs/locked-figures/locked-line.d.ts +1 -1
  107. package/dist/widgets/interactive-graphs/locked-figures/locked-point.d.ts +1 -1
  108. package/dist/widgets/interactive-graphs/locked-figures/locked-polygon.d.ts +1 -1
  109. package/dist/widgets/interactive-graphs/locked-figures/locked-vector.d.ts +1 -1
  110. package/dist/widgets/interactive-graphs/mafs-state-to-interactive-graph.d.ts +1 -1
  111. package/dist/widgets/interactive-graphs/reducer/initialize-graph-state.d.ts +1 -1
  112. package/dist/widgets/interactive-graphs/reducer/interactive-graph-state.d.ts +1 -1
  113. package/dist/widgets/interactive-graphs/reducer/use-graph-config.d.ts +2 -1
  114. package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +1 -1
  115. package/dist/widgets/interactive-graphs/types.d.ts +1 -1
  116. package/dist/widgets/interactive-graphs/utils.d.ts +1 -1
  117. package/dist/widgets/label-image/label-image.d.ts +2 -5
  118. package/dist/widgets/label-image/marker.d.ts +1 -4
  119. package/dist/widgets/matcher/matcher.d.ts +2 -5
  120. package/dist/widgets/matrix/matrix.d.ts +2 -5
  121. package/dist/widgets/measurer/measurer.d.ts +1 -1
  122. package/dist/widgets/mock-widgets/index.d.ts +2 -0
  123. package/dist/widgets/mock-widgets/mock-asset-loading-widget.d.ts +17 -0
  124. package/dist/widgets/mock-widgets/mock-widget.d.ts +47 -0
  125. package/dist/widgets/mock-widgets/score-mock-widget.d.ts +5 -0
  126. package/dist/widgets/molecule/molecule.d.ts +1 -1
  127. package/dist/widgets/number-line/number-line.d.ts +1 -4
  128. package/dist/widgets/numeric-input/numeric-input.d.ts +2 -6
  129. package/dist/widgets/orderer/orderer.d.ts +1 -1
  130. package/dist/widgets/passage/passage.d.ts +2 -5
  131. package/dist/widgets/passage-ref/passage-ref.d.ts +2 -5
  132. package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +2 -5
  133. package/dist/widgets/phet-simulation/phet-simulation.d.ts +2 -5
  134. package/dist/widgets/plotter/plotter.d.ts +2 -5
  135. package/dist/widgets/radio/base-radio.d.ts +1 -1
  136. package/dist/widgets/radio/radio-component.d.ts +2 -5
  137. package/dist/widgets/radio/radio.d.ts +1 -1
  138. package/dist/widgets/sorter/sorter.d.ts +1 -1
  139. package/dist/widgets/table/table.d.ts +2 -5
  140. package/dist/widgets/video/video.d.ts +2 -5
  141. package/dist/widgets.d.ts +1 -1
  142. package/package.json +44 -43
  143. package/dist/interactive2/objective_.d.ts +0 -34
  144. package/dist/perseus-types.d.ts +0 -980
  145. package/dist/util/answer-types.d.ts +0 -69
  146. package/dist/util/math.d.ts +0 -29
package/dist/es/index.js CHANGED
@@ -12,16 +12,17 @@ import React__default, { useContext, useState, useEffect, useRef, createContext,
12
12
  import * as ReactDOM from 'react-dom';
13
13
  import ReactDOM__default from 'react-dom';
14
14
  import _, { debounce as debounce$1 } from 'underscore';
15
+ import { ErrorCodes, KhanAnswerTypes } from '@khanacademy/perseus-score';
15
16
  import Clickable from '@khanacademy/wonder-blocks-clickable';
16
17
  import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
17
18
  import { color, spacing, font } from '@khanacademy/wonder-blocks-tokens';
18
19
  import $ from 'jquery';
19
20
  import { v4 } from 'uuid';
20
- import { PerseusError, Errors, addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
21
+ import { PerseusError, Errors, pluck, mapObject, lockedFigureColors, lockedFigureFillStyles, addLibraryVersionToPerseusDebug, lockedFigureColorNames, plotterPlotTypes, ItemExtras } from '@khanacademy/perseus-core';
21
22
  import PropTypes from 'prop-types';
22
- import { number as number$1, point, vector as vector$1, line } from '@khanacademy/kmath';
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
26
  import SimpleMarkdown from '@khanacademy/simple-markdown';
26
27
  import { pureMarkdownRules } from '@khanacademy/pure-markdown';
27
28
  import { TextField } from '@khanacademy/wonder-blocks-form';
@@ -57,6 +58,183 @@ function _extends() {
57
58
  * The translated strings that are used to render Perseus.
58
59
  */
59
60
 
61
+ /**
62
+ * Untranslated strings used in Perseus. To be used by an external
63
+ * translator to produce translated strings, passed in as `PerseusStrings`.
64
+ * !! Note: Ensure that all escape sequences are double-escaped. (e.g. `\\text` -> `\\\\text`)
65
+ */
66
+ const strings = {
67
+ closeKeypad: "close math keypad",
68
+ openKeypad: "open math keypad",
69
+ mathInputBox: "Math input box",
70
+ removeHighlight: "Remove highlight",
71
+ addHighlight: "Add highlight",
72
+ hintPos: "Hint #%(pos)s",
73
+ errorRendering: "Error rendering: %(error)s",
74
+ APPROXIMATED_PI_ERROR: "Your answer is close, but you may " + "have approximated pi. Enter your " + "answer as a multiple of pi, like " + "12 pi or " + "2/3 pi",
75
+ EXTRA_SYMBOLS_ERROR: "We could not understand your " + "answer. Please check your answer for extra " + "text or symbols.",
76
+ NEEDS_TO_BE_SIMPLFIED_ERROR: "Your answer is almost correct, " + "but it needs to be simplified.",
77
+ MISSING_PERCENT_ERROR: "Your answer is almost correct, " + "but it is missing a " + "<code>\\\\%</code> at the end.",
78
+ MULTIPLICATION_SIGN_ERROR: "I'm a computer. I only understand " + "multiplication if you use an asterisk " + "(*) as the multiplication sign.",
79
+ WRONG_CASE_ERROR: "Your answer includes use of a variable with the wrong case.",
80
+ WRONG_LETTER_ERROR: "Your answer includes a wrong variable letter.",
81
+ invalidSelection: "Make sure you select something for every row.",
82
+ ERROR_TITLE: "Oops!",
83
+ ERROR_MESSAGE: "Sorry, I don't understand that!",
84
+ hints: "Hints",
85
+ getAnotherHint: "Get another hint",
86
+ deprecatedStandin: "Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!",
87
+ keepTrying: "Keep trying",
88
+ tryAgain: "Try again",
89
+ check: "Check",
90
+ correctExcited: "Correct!",
91
+ nextQuestion: "Next question",
92
+ skipToTitle: "Skip to %(title)s",
93
+ current: "Current",
94
+ correct: "Correct",
95
+ correctSelected: "Correct (selected)",
96
+ correctCrossedOut: "Correct (but you crossed it out)",
97
+ incorrect: "Incorrect",
98
+ incorrectSelected: "Incorrect (selected)",
99
+ hideExplanation: "Hide explanation",
100
+ explain: "Explain",
101
+ INVALID_MESSAGE_PREFIX: "We couldn't grade your answer.",
102
+ DEFAULT_INVALID_MESSAGE_1: "It looks like you left something blank or ",
103
+ DEFAULT_INVALID_MESSAGE_2: "entered in an invalid answer.",
104
+ integerExample: "an integer, like $6$",
105
+ properExample: "a *proper* fraction, like $1/2$ or $6/10$",
106
+ simplifiedProperExample: "a *simplified proper* fraction, like $3/5$",
107
+ improperExample: "an *improper* fraction, like $10/7$ or $14/8$",
108
+ simplifiedImproperExample: "a *simplified improper* fraction, like $7/4$",
109
+ mixedExample: "a mixed number, like $1\\\\ 3/4$",
110
+ decimalExample: "an *exact* decimal, like $0.75$",
111
+ percentExample: "a percent, like $12.34\\\\%$",
112
+ piExample: "a multiple of pi, like $12$ pi or $2/3$ pi",
113
+ yourAnswer: "**Your answer should be** ",
114
+ yourAnswerLabel: "Your answer:",
115
+ addPoints: "Click to add points",
116
+ addVertices: "Click to add vertices",
117
+ tapMultiple: "Tap each dot on the image to select all answers that apply.",
118
+ tapSingle: "Tap each dot on the image to select an answer.",
119
+ clickMultiple: "Click each dot on the image to select all answers that apply.",
120
+ clickSingle: "Click each dot on the image to select an answer.",
121
+ choices: "Choices:",
122
+ answers: {
123
+ one: "%(num)s answer",
124
+ other: "%(num)s answers"
125
+ },
126
+ hideAnswersToggleLabel: "Hide answer choices",
127
+ moves: {
128
+ one: "Moves: %(num)s",
129
+ other: "Moves: %(num)s"
130
+ },
131
+ clickTiles: "Click on the tiles to change the lights.",
132
+ turnOffLights: "You must turn on all of the lights to continue.",
133
+ fillAllCells: "Make sure you fill in all cells in the matrix.",
134
+ molecularDrawing: "A molecular structure drawing. SMILES notation: %(content)s",
135
+ switchDirection: "Switch direction",
136
+ circleOpen: "Make circle open",
137
+ circleFilled: "Make circle filled",
138
+ numDivisions: "Number of divisions:",
139
+ divisions: "Please make sure the number of divisions is in the range %(divRangeString)s.",
140
+ lineRange: "lines %(lineRange)s",
141
+ lineNumber: "line %(lineNumber)s",
142
+ symbolPassage: "The symbol %(questionSymbol)s indicates that question %(questionNumber)s references this portion of the passage.",
143
+ symbolQuestion: " The symbol %(sentenceSymbol)s indicates that the following sentence is referenced in a question.",
144
+ lineLabel: {
145
+ context: "a label next to a reading passage to denote the line number",
146
+ message: "Line"
147
+ },
148
+ beginningPassage: "Beginning of reading passage.",
149
+ beginningFootnotes: "Beginning of reading passage footnotes.",
150
+ endPassage: "End of reading passage.",
151
+ questionMarker: "[Marker for question %(number)s]",
152
+ circleMarker: "[Circle marker %(number)s]",
153
+ sentenceMarker: "[Sentence %(number)s]",
154
+ dragHandles: "Drag handles to make graph",
155
+ tapAddPoints: "Tap to add points",
156
+ false: "False",
157
+ true: "True",
158
+ no: "No",
159
+ yes: "Yes",
160
+ chooseCorrectNum: "Please choose the correct number of answers.",
161
+ notNoneOfTheAbove: "'None of the above' may not be selected when other answers are selected.",
162
+ noneOfTheAbove: "None of the above",
163
+ chooseNumAnswers: "Choose %(numCorrect)s answers:",
164
+ chooseAllAnswers: "Choose all answers that apply:",
165
+ chooseOneAnswer: "Choose 1 answer:",
166
+ choiceCheckedCorrect: "(Choice %(letter)s, Checked, Correct)",
167
+ choiceCrossedOutCorrect: "(Choice %(letter)s, Crossed out, Correct)",
168
+ choiceCorrect: "(Choice %(letter)s, Correct)",
169
+ choiceCheckedIncorrect: "(Choice %(letter)s, Checked, Incorrect)",
170
+ choiceCrossedOutIncorrect: "(Choice %(letter)s, Crossed out, Incorrect)",
171
+ choiceIncorrect: "(Choice %(letter)s, Incorrect)",
172
+ choiceChecked: "(Choice %(letter)s, Checked)",
173
+ choiceCrossedOut: "(Choice %(letter)s, Crossed out)",
174
+ choice: "(Choice %(letter)s)",
175
+ crossOut: "Cross out",
176
+ crossOutOption: "Cross out option",
177
+ crossOutChoice: "Cross out Choice %(letter)s",
178
+ bringBack: "Bring back",
179
+ openMenuForChoice: "Open menu for Choice %(letter)s",
180
+ letters: {
181
+ context: "This is a list of single-character labels that will appear in front of multiple-choice options. For instance, a multiple-choice question with three options would display (A) first option (B) second option (C) third option. There must be spaces between each of the different characters. The characters will show up next to options in the order that they are listed here. Most multiple choice questions have 5 or fewer options.",
182
+ message: "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
183
+ },
184
+ rightArrow: "Reaction arrow pointing to the right.",
185
+ dontUnderstandUnits: "I couldn't understand those units.",
186
+ checkSigFigs: "Check your significant figures.",
187
+ answerNumericallyIncorrect: "That answer is numerically incorrect.",
188
+ checkUnits: "Check your units.",
189
+ dontUnderstand: "I don't understand that",
190
+ loading: "Loading...",
191
+ videoTranscript: "See video transcript",
192
+ somethingWrong: "Something went wrong.",
193
+ videoWrapper: "Khan Academy video wrapper",
194
+ mathInputTitle: "mathematics keyboard",
195
+ mathInputDescription: "Use keyboard/mouse to interact with math-based input fields",
196
+ sin: "sin",
197
+ cos: "cos",
198
+ tan: "tan",
199
+ simulationLoadFail: "Sorry, this simulation cannot load.",
200
+ simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
201
+ selectAnAnswer: "Select an answer",
202
+ // The following strings are used for interactive graph SR descriptions.
203
+ addPoint: "Add Point",
204
+ removePoint: "Remove Point",
205
+ graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
206
+ closePolygon: "Close shape",
207
+ openPolygon: "Re-open shape",
208
+ srInteractiveElements: "Interactive elements: %(elements)s",
209
+ srNoInteractiveElements: "No interactive elements",
210
+ // TODO(LEMS-2660): The following strings are ones that will need
211
+ // translation tickets after all interactive graph SR strings have
212
+ // been finalized. Remove this comment after the tickets have been
213
+ // created.
214
+ srPointAtCoordinates: "Point %(num)s at %(x)s comma %(y)s",
215
+ srCircleGraph: "A circle on a coordinate plane.",
216
+ srCircleShape: "Circle. The center point is at %(centerX)s comma %(centerY)s.",
217
+ srCircleRadiusPoint: "Radius point at %(radiusPointX)s comma %(radiusPointY)s.",
218
+ srCircleRadius: "Circle radius is %(radius)s.",
219
+ srCircleOuterPoints: "Points on the circle at %(point1X)s comma %(point1Y)s, %(point2X)s comma %(point2Y)s, %(point3X)s comma %(point3Y)s, %(point4X)s comma %(point4Y)s.",
220
+ srLinearGraph: "A line on a coordinate plane.",
221
+ srLinearGraphPoints: "The line has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
222
+ srLinearGraphSlopeIncreasing: "Its slope increases from left to right.",
223
+ srLinearGraphSlopeDecreasing: "Its slope decreases from left to right.",
224
+ srLinearGraphSlopeHorizontal: "Its slope is zero.",
225
+ srLinearGraphSlopeVertical: "Its slope is undefined.",
226
+ srLinearGraphXOnlyIntercept: "The line crosses the X-axis at %(xIntercept)s comma 0.",
227
+ srLinearGraphYOnlyIntercept: "The line crosses the Y-axis at 0 comma %(yIntercept)s.",
228
+ srLinearGraphBothIntercepts: "The line crosses the X-axis at %(xIntercept)s comma 0 and the Y-axis at 0 comma %(yIntercept)s.",
229
+ srLinearGraphOriginIntercept: "The line crosses the x and y axes at the graph's origin.",
230
+ srLinearGrabHandle: "Line from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.",
231
+ srAngleSideAtCoordinates: "Point %(point)s, %(side)s at %(x)s comma %(y)s",
232
+ srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
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"
235
+ // The above strings are used for interactive graph SR descriptions.
236
+ };
237
+
60
238
  /**
61
239
  * Mock strings for the Perseus package, to be used for tests and Storybook.
62
240
  */
@@ -326,6 +504,16 @@ const mockStrings = {
326
504
  // The above strings are used for interactive graph SR descriptions.
327
505
  };
328
506
 
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
+ });
516
+
329
517
  /**
330
518
  * PerseusI18nContext provides a way to set the strings and locale that are
331
519
  * used inside the Perseus package.
@@ -1304,917 +1492,6 @@ const Log = {
1304
1492
  }
1305
1493
  };
1306
1494
 
1307
- const KhanMath = {
1308
- // Simplify formulas before display
1309
- cleanMath: function (expr) {
1310
- return typeof expr === "string" ? expr.replace(/\+\s*-/g, "- ").replace(/-\s*-/g, "+ ").replace(/\^1/g, "") : expr;
1311
- },
1312
- // Bound a number by 1e-6 and 1e20 to avoid exponents after toString
1313
- bound: function (num) {
1314
- if (num === 0) {
1315
- return num;
1316
- }
1317
- if (num < 0) {
1318
- return -KhanMath.bound(-num);
1319
- }
1320
- return Math.max(1e-6, Math.min(num, 1e20));
1321
- },
1322
- factorial: function (x) {
1323
- if (x <= 1) {
1324
- return x;
1325
- }
1326
- return x * KhanMath.factorial(x - 1);
1327
- },
1328
- getGCD: function (a, b) {
1329
- if (arguments.length > 2) {
1330
- // TODO(kevinb): rewrite using rest args instead of arguments
1331
- // eslint-disable-next-line prefer-rest-params
1332
- const rest = [].slice.call(arguments, 1);
1333
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
1334
- return KhanMath.getGCD(a, KhanMath.getGCD(...rest));
1335
- }
1336
- let mod;
1337
- a = Math.abs(a);
1338
- b = Math.abs(b);
1339
- while (b) {
1340
- mod = a % b;
1341
- a = b;
1342
- b = mod;
1343
- }
1344
- return a;
1345
- },
1346
- getLCM: function (a, b) {
1347
- if (arguments.length > 2) {
1348
- // TODO(kevinb): rewrite using rest args instead of arguments
1349
- // eslint-disable-next-line prefer-rest-params
1350
- const rest = [].slice.call(arguments, 1);
1351
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
1352
- return KhanMath.getLCM(a, KhanMath.getLCM(...rest));
1353
- }
1354
- return Math.abs(a * b) / KhanMath.getGCD(a, b);
1355
- },
1356
- primes: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97],
1357
- isPrime: function (n) {
1358
- if (n <= 1) {
1359
- return false;
1360
- }
1361
- if (n < 101) {
1362
- return !!$.grep(KhanMath.primes, function (p, i) {
1363
- return Math.abs(p - n) <= 0.5;
1364
- }).length;
1365
- }
1366
- if (n <= 1 || n > 2 && n % 2 === 0) {
1367
- return false;
1368
- }
1369
- for (let i = 3, sqrt = Math.sqrt(n); i <= sqrt; i += 2) {
1370
- if (n % i === 0) {
1371
- return false;
1372
- }
1373
- }
1374
- return true;
1375
- },
1376
- // @ts-expect-error - TS2366 - Function lacks ending return statement and return type does not include 'undefined'.
1377
- getPrimeFactorization: function (number) {
1378
- if (number === 1) {
1379
- return [];
1380
- }
1381
- if (KhanMath.isPrime(number)) {
1382
- return [number];
1383
- }
1384
- const maxf = Math.sqrt(number);
1385
- for (let f = 2; f <= maxf; f++) {
1386
- if (number % f === 0) {
1387
- return $.merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
1388
- }
1389
- }
1390
- },
1391
- // Round a number to the nearest increment
1392
- // E.g., if increment = 30 and num = 40, return 30. if increment = 30 and
1393
- // num = 45, return 60.
1394
- roundToNearest: function (increment, num) {
1395
- return Math.round(num / increment) * increment;
1396
- },
1397
- // Round a number to a certain number of decimal places
1398
- roundTo: function (precision, num) {
1399
- const factor = Math.pow(10, precision).toFixed(5);
1400
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'number'. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
1401
- return Math.round((num * factor).toFixed(5)) / factor;
1402
- },
1403
- /**
1404
- * Return a string of num rounded to a fixed precision decimal places,
1405
- * with an approx symbol if num had to be rounded, and trailing 0s
1406
- */
1407
- toFixedApprox: function (num, precision) {
1408
- // TODO(aria): Make this locale-dependent
1409
- const fixedStr = num.toFixed(precision);
1410
- if (number$1.equal(+fixedStr, num)) {
1411
- return fixedStr;
1412
- }
1413
- return "\\approx " + fixedStr;
1414
- },
1415
- /**
1416
- * Return a string of num rounded to precision decimal places, with an
1417
- * approx symbol if num had to be rounded, but no trailing 0s if it was
1418
- * not rounded.
1419
- */
1420
- roundToApprox: function (num, precision) {
1421
- const fixed = KhanMath.roundTo(precision, num);
1422
- if (number$1.equal(fixed, num)) {
1423
- return String(fixed);
1424
- }
1425
- return KhanMath.toFixedApprox(num, precision);
1426
- },
1427
- // toFraction(4/8) => [1, 2]
1428
- // toFraction(0.666) => [333, 500]
1429
- // toFraction(0.666, 0.001) => [2, 3]
1430
- //
1431
- // tolerance can't be bigger than 1, sorry
1432
- toFraction: function (decimal, tolerance) {
1433
- if (tolerance == null) {
1434
- tolerance = Math.pow(2, -46);
1435
- }
1436
- if (decimal < 0 || decimal > 1) {
1437
- let fract = decimal % 1;
1438
- fract += fract < 0 ? 1 : 0;
1439
- const nd = KhanMath.toFraction(fract, tolerance);
1440
- nd[0] += Math.round(decimal - fract) * nd[1];
1441
- return nd;
1442
- }
1443
- if (Math.abs(Math.round(Number(decimal)) - decimal) <= tolerance) {
1444
- return [Math.round(decimal), 1];
1445
- }
1446
- let loN = 0;
1447
- let loD = 1;
1448
- let hiN = 1;
1449
- let hiD = 1;
1450
- let midN = 1;
1451
- let midD = 2;
1452
-
1453
- // eslint-disable-next-line no-constant-condition
1454
- while (true) {
1455
- if (Math.abs(Number(midN / midD) - decimal) <= tolerance) {
1456
- return [midN, midD];
1457
- }
1458
- if (midN / midD < decimal) {
1459
- loN = midN;
1460
- loD = midD;
1461
- } else {
1462
- hiN = midN;
1463
- hiD = midD;
1464
- }
1465
- midN = loN + hiN;
1466
- midD = loD + hiD;
1467
- }
1468
- },
1469
- // Returns the format (string) of a given numeric string
1470
- // Note: purposively more inclusive than answer-types' predicate.forms
1471
- // That is, it is not necessarily true that interpreted input are numeric
1472
- getNumericFormat: function (text) {
1473
- text = $.trim(text);
1474
- text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
1475
- if (text.match(/^[+-]?\d+$/)) {
1476
- return "integer";
1477
- }
1478
- if (text.match(/^[+-]?\d+\s+\d+\s*\/\s*\d+$/)) {
1479
- return "mixed";
1480
- }
1481
- const fraction = text.match(/^[+-]?(\d+)\s*\/\s*(\d+)$/);
1482
- if (fraction) {
1483
- return parseFloat(fraction[1]) > parseFloat(fraction[2]) ? "improper" : "proper";
1484
- }
1485
- if (text.replace(/[,. ]/g, "").match(/^\d+$/)) {
1486
- return "decimal";
1487
- }
1488
- if (text.match(/(pi?|\u03c0|t(?:au)?|\u03c4|pau)/)) {
1489
- return "pi";
1490
- }
1491
- return null;
1492
- },
1493
- // Returns a string of the number in a specified format
1494
- toNumericString: function (number, format) {
1495
- if (number == null) {
1496
- return "";
1497
- }
1498
- if (number === 0) {
1499
- return "0"; // otherwise it might end up as 0% or 0pi
1500
- }
1501
-
1502
- if (format === "percent") {
1503
- return number * 100 + "%";
1504
- }
1505
- if (format === "pi") {
1506
- const fraction = number$1.toFraction(number / Math.PI);
1507
- const numerator = Math.abs(fraction[0]);
1508
- const denominator = fraction[1];
1509
- if (number$1.isInteger(numerator)) {
1510
- const sign = number < 0 ? "-" : "";
1511
- const pi = "\u03C0";
1512
- return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
1513
- }
1514
- }
1515
- if (_(["proper", "improper", "mixed", "fraction"]).contains(format)) {
1516
- const fraction = number$1.toFraction(number);
1517
- const numerator = Math.abs(fraction[0]);
1518
- const denominator = fraction[1];
1519
- const sign = number < 0 ? "-" : "";
1520
- if (denominator === 1) {
1521
- return sign + numerator; // for integers, irrational, d > 1000
1522
- }
1523
-
1524
- if (format === "mixed") {
1525
- const modulus = numerator % denominator;
1526
- const integer = (numerator - modulus) / denominator;
1527
- return sign + (integer ? integer + " " : "") + modulus + "/" + denominator;
1528
- } // otherwise proper, improper, or fraction
1529
- return sign + numerator + "/" + denominator;
1530
- }
1531
-
1532
- // otherwise (decimal, float, long long)
1533
- return String(number);
1534
- }
1535
- };
1536
- function sum$1(array) {
1537
- return array.reduce(add$3, 0);
1538
- }
1539
- function add$3(a, b) {
1540
- return a + b;
1541
- }
1542
-
1543
- /* eslint-disable no-useless-escape */
1544
- const MAXERROR_EPSILON = Math.pow(2, -42);
1545
-
1546
- // TOOD(kevinb): Figure out how this relates to KEScore in
1547
- // perseus-all-package/types.js and see if there's a way to
1548
- // unify these types.
1549
- /**
1550
- * Answer types
1551
- *
1552
- * Utility for creating answerable questions displayed in exercises
1553
- *
1554
- * Different answer types produce different kinds of input displays, and do
1555
- * different kinds of checking on the solutions.
1556
- *
1557
- * Each of the objects contain two functions, setup and createValidator.
1558
- *
1559
- * The setup function takes a solutionarea and solution, and performs setup
1560
- * within the solutionarea, and then returns an object which contains:
1561
- *
1562
- * answer: a function which, when called, will retrieve the current answer from
1563
- * the solutionarea, which can then be validated using the validator
1564
- * function
1565
- * validator: a function returned from the createValidator function (defined
1566
- * below)
1567
- * solution: the correct answer to the problem
1568
- * showGuess: a function which, when given a guess, shows the guess within the
1569
- * provided solutionarea
1570
- * showGuessCustom: a function which displays parts of a guess that are not
1571
- * within the solutionarea; currently only used for custom
1572
- * answers
1573
- *
1574
- * The createValidator function only takes a solution, and it returns a
1575
- * function which can be used to validate an answer.
1576
- *
1577
- * The resulting validator function returns:
1578
- * - true: if the answer is fully correct
1579
- * - false: if the answer is incorrect
1580
- * - "" (the empty string): if no answer has been provided (e.g. the answer box
1581
- * is left unfilled)
1582
- * - a string: if there is some slight error
1583
- *
1584
- * In most cases, setup and createValidator don't really need the solution DOM
1585
- * element so we have setupFunctional and createValidatorFunctional for them
1586
- * which take only $solution.text() and $solution.data(). This makes it easier
1587
- * to reuse specific answer types.
1588
- *
1589
- * TODO(alpert): Think of a less-absurd name for createValidatorFunctional.
1590
- *
1591
- */
1592
- const KhanAnswerTypes = {
1593
- /*
1594
- * predicate answer type
1595
- *
1596
- * performs simple predicate-based checking of a numeric solution, with
1597
- * different kinds of number formats
1598
- *
1599
- * Uses the data-forms option on the solution to choose which number formats
1600
- * are acceptable. Available data-forms:
1601
- *
1602
- * - integer: 3
1603
- * - proper: 3/5
1604
- * - improper: 5/3
1605
- * - pi: 3 pi
1606
- * - log: log(5)
1607
- * - percent: 15%
1608
- * - mixed: 1 1/3
1609
- * - decimal: 1.7
1610
- *
1611
- * The solution should be a predicate of the form:
1612
- *
1613
- * function(guess, maxError) {
1614
- * return abs(guess - 3) < maxError;
1615
- * }
1616
- *
1617
- */
1618
- predicate: {
1619
- defaultForms: "integer, proper, improper, mixed, decimal",
1620
- createValidatorFunctional: function (predicate, options, strings) {
1621
- // Extract the options from the given solution object
1622
- options = _.extend({
1623
- simplify: "required",
1624
- ratio: false,
1625
- forms: KhanAnswerTypes.predicate.defaultForms
1626
- }, options);
1627
- let acceptableForms;
1628
- // this is maintaining backwards compatibility
1629
- // TODO(merlob) fix all places that depend on this, then delete
1630
- if (!_.isArray(options.forms)) {
1631
- acceptableForms = options.forms.split(/\s*,\s*/);
1632
- } else {
1633
- acceptableForms = options.forms;
1634
- }
1635
-
1636
- // TODO(jack): remove options.inexact in favor of options.maxError
1637
- if (options.inexact === undefined) {
1638
- // If we aren't allowing inexact, ensure that we don't have a
1639
- // large maxError as well.
1640
- options.maxError = 0;
1641
- }
1642
- // Allow a small tolerance on maxError, to avoid numerical
1643
- // representation issues (2.3 should be correct for a solution of
1644
- // 2.45 with maxError=0.15).
1645
- options.maxError = +options.maxError + MAXERROR_EPSILON;
1646
-
1647
- // If percent is an acceptable form, make sure it's the last one
1648
- // in the list so we don't prematurely complain about not having
1649
- // a percent sign when the user entered the correct answer in a
1650
- // different form (such as a decimal or fraction)
1651
- if (_.contains(acceptableForms, "percent")) {
1652
- acceptableForms = _.without(acceptableForms, "percent");
1653
- acceptableForms.push("percent");
1654
- }
1655
-
1656
- // Take text looking like a fraction, and turn it into a number
1657
- const fractionTransformer = function fractionTransformer(text) {
1658
- text = text
1659
- // Replace unicode minus sign with hyphen
1660
- .replace(/\u2212/, "-")
1661
- // Remove space after +, -
1662
- .replace(/([+-])\s+/g, "$1")
1663
- // Remove leading/trailing whitespace
1664
- .replace(/(^\s*)|(\s*$)/gi, "");
1665
-
1666
- // Extract numerator and denominator
1667
- const match = text.match(/^([+-]?\d+)\s*\/\s*([+-]?\d+)$/);
1668
- // Fractions are represented as "-\frac{numerator}{denominator}"
1669
- // in Mobile device input instead of "numerator/denominator" as
1670
- // in web-browser.
1671
- const mobileDeviceMatch = text.match(/^([+-]?)\\frac\{([+-]?\d+)\}\{([+-]?\d+)\}$/);
1672
- const parsedInt = parseInt(text, 10);
1673
- if (match || mobileDeviceMatch) {
1674
- let num;
1675
- let denom;
1676
- let simplified = true;
1677
- if (match) {
1678
- num = parseFloat(match[1]);
1679
- denom = parseFloat(match[2]);
1680
- } else {
1681
- num = parseFloat(mobileDeviceMatch[2]);
1682
- if (mobileDeviceMatch[1] === "-") {
1683
- if (num < 0) {
1684
- simplified = false;
1685
- }
1686
- num = -num;
1687
- }
1688
- denom = parseFloat(mobileDeviceMatch[3]);
1689
- }
1690
- simplified = simplified && denom > 0 && (options.ratio || denom !== 1) && KhanMath.getGCD(num, denom) === 1;
1691
- return [{
1692
- value: num / denom,
1693
- exact: simplified
1694
- }];
1695
- }
1696
- if (!isNaN(parsedInt) && "" + parsedInt === text) {
1697
- return [{
1698
- value: parsedInt,
1699
- exact: true
1700
- }];
1701
- }
1702
- return [];
1703
- };
1704
-
1705
- /*
1706
- * Different forms of numbers
1707
- *
1708
- * Each function returns a list of objects of the form:
1709
- *
1710
- * {
1711
- * value: numerical value,
1712
- * exact: true/false
1713
- * }
1714
- */
1715
- const forms = {
1716
- // integer, which is encompassed by decimal
1717
- integer: function (text) {
1718
- // Compare the decimal form to the decimal form rounded to
1719
- // an integer. Only accept if the user actually entered an
1720
- // integer.
1721
- const decimal = forms.decimal(text);
1722
- const rounded = forms.decimal(text, 1);
1723
- if (decimal[0].value != null && decimal[0].value === rounded[0].value || decimal[1].value != null && decimal[1].value === rounded[1].value) {
1724
- return decimal;
1725
- }
1726
- return [];
1727
- },
1728
- // A proper fraction
1729
- proper: function (text) {
1730
- return $.map(fractionTransformer(text), function (o) {
1731
- // All fractions that are less than 1
1732
- if (Math.abs(o.value) < 1) {
1733
- return [o];
1734
- }
1735
- return [];
1736
- });
1737
- },
1738
- // an improper fraction
1739
- improper: function (text) {
1740
- return $.map(fractionTransformer(text), function (o) {
1741
- // All fractions that are greater than 1
1742
- if (Math.abs(o.value) >= 1) {
1743
- return [o];
1744
- }
1745
- return [];
1746
- });
1747
- },
1748
- // pi-like numbers
1749
- pi: function (text) {
1750
- let match;
1751
- let possibilities = [];
1752
-
1753
- // Replace unicode minus sign with hyphen
1754
- text = text.replace(/\u2212/, "-");
1755
-
1756
- // - pi
1757
- // (Note: we also support \pi (for TeX), p, tau (and \tau,
1758
- // and t), pau.)
1759
- if (match = text.match(/^([+-]?)\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
1760
- possibilities = [{
1761
- value: parseFloat(match[1] + "1"),
1762
- exact: true
1763
- }];
1764
-
1765
- // 5 / 6 pi
1766
- } else if (match = text.match(/^([+-]?\s*\d+\s*(?:\/\s*[+-]?\s*\d+)?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
1767
- possibilities = fractionTransformer(match[1]);
1768
-
1769
- // 4 5 / 6 pi
1770
- } else if (match = text.match(/^([+-]?)\s*(\d+)\s*([+-]?\d+)\s*\/\s*([+-]?\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
1771
- const sign = parseFloat(match[1] + "1");
1772
- const integ = parseFloat(match[2]);
1773
- const num = parseFloat(match[3]);
1774
- const denom = parseFloat(match[4]);
1775
- const simplified = num < denom && KhanMath.getGCD(num, denom) === 1;
1776
- possibilities = [{
1777
- value: sign * (integ + num / denom),
1778
- exact: simplified
1779
- }];
1780
-
1781
- // 5 pi / 6
1782
- } else if (match = text.match(/^([+-]?\s*\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\s*\d+))?$/i)) {
1783
- possibilities = fractionTransformer(match[1] + "/" + match[3]);
1784
-
1785
- // - pi / 4
1786
- } else if (match = text.match(/^([+-]?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\d+))?$/i)) {
1787
- possibilities = fractionTransformer(match[1] + "1/" + match[3]);
1788
-
1789
- // 0
1790
- } else if (text === "0") {
1791
- possibilities = [{
1792
- value: 0,
1793
- exact: true
1794
- }];
1795
-
1796
- // 0.5 pi (fallback)
1797
- } else if (match = text.match(/^(.+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
1798
- possibilities = forms.decimal(match[1]);
1799
- } else {
1800
- possibilities = _.reduce(KhanAnswerTypes.predicate.defaultForms.split(/\s*,\s*/), function (memo, form) {
1801
- return memo.concat(forms[form](text));
1802
- }, []);
1803
-
1804
- // If the answer is a floating point number that's
1805
- // near a multiple of pi, mark is as being possibly
1806
- // an approximation of pi. We actually check if
1807
- // it's a plausible approximation of pi/12, since
1808
- // sometimes the correct answer is like pi/3 or pi/4.
1809
- // We also say it's a pi-approximation if it involves
1810
- // x/7 (since 22/7 is an approximation of pi.)
1811
- // Never mark an integer as being an approximation
1812
- // of pi.
1813
- let approximatesPi = false;
1814
- const number = parseFloat(text);
1815
- if (!isNaN(number) && number !== parseInt(text)) {
1816
- const piMult = Math.PI / 12;
1817
- const roundedNumber = piMult * Math.round(number / piMult);
1818
- if (Math.abs(number - roundedNumber) < 0.01) {
1819
- approximatesPi = true;
1820
- }
1821
- } else if (text.match(/\/\s*7/)) {
1822
- approximatesPi = true;
1823
- }
1824
- if (approximatesPi) {
1825
- _.each(possibilities, function (possibility) {
1826
- possibility.piApprox = true;
1827
- });
1828
- }
1829
- return possibilities;
1830
- }
1831
- let multiplier = Math.PI;
1832
- if (text.match(/\\?tau|t|\u03c4/)) {
1833
- multiplier = Math.PI * 2;
1834
- }
1835
-
1836
- // We're taking an early stand along side xkcd in the
1837
- // inevitable ti vs. pau debate... http://xkcd.com/1292
1838
- if (text.match(/pau/)) {
1839
- multiplier = Math.PI * 1.5;
1840
- }
1841
- $.each(possibilities, function (ix, possibility) {
1842
- possibility.value *= multiplier;
1843
- });
1844
- return possibilities;
1845
- },
1846
- // Converts '' to 1 and '-' to -1 so you can write "[___] x"
1847
- // and accept sane things
1848
- coefficient: function (text) {
1849
- let possibilities = [];
1850
-
1851
- // Replace unicode minus sign with hyphen
1852
- text = text.replace(/\u2212/, "-");
1853
- if (text === "") {
1854
- possibilities = [{
1855
- value: 1,
1856
- exact: true
1857
- }];
1858
- } else if (text === "-") {
1859
- possibilities = [{
1860
- value: -1,
1861
- exact: true
1862
- }];
1863
- }
1864
- return possibilities;
1865
- },
1866
- // simple log(c) form
1867
- log: function (text) {
1868
- let match;
1869
- let possibilities = [];
1870
-
1871
- // Replace unicode minus sign with hyphen
1872
- text = text.replace(/\u2212/, "-");
1873
- text = text.replace(/[ \(\)]/g, "");
1874
- if (match = text.match(/^log\s*(\S+)\s*$/i)) {
1875
- // @ts-expect-error - TS2322 - Type '{ value: number | undefined; exact: boolean; }[]' is not assignable to type 'never[]'.
1876
- possibilities = forms.decimal(match[1]);
1877
- } else if (text === "0") {
1878
- // @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'. | TS2322 - Type 'boolean' is not assignable to type 'never'.
1879
- possibilities = [{
1880
- value: 0,
1881
- exact: true
1882
- }];
1883
- }
1884
- return possibilities;
1885
- },
1886
- // Numbers with percent signs
1887
- percent: function (text) {
1888
- text = $.trim(text);
1889
- // store whether or not there is a percent sign
1890
- let hasPercentSign = false;
1891
- if (text.indexOf("%") === text.length - 1) {
1892
- text = $.trim(text.substring(0, text.length - 1));
1893
- hasPercentSign = true;
1894
- }
1895
- const transformed = forms.decimal(text);
1896
- $.each(transformed, function (ix, t) {
1897
- t.exact = hasPercentSign;
1898
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
1899
- t.value = t.value / 100;
1900
- });
1901
- return transformed;
1902
- },
1903
- // Mixed numbers, like 1 3/4
1904
- mixed: function (text) {
1905
- const match = text
1906
- // Replace unicode minus sign with hyphen
1907
- .replace(/\u2212/, "-")
1908
- // Remove space after +, -
1909
- .replace(/([+-])\s+/g, "$1")
1910
- // Extract integer, numerator and denominator
1911
- .match(/^([+-]?)(\d+)\s+(\d+)\s*\/\s*(\d+)$/);
1912
- if (match) {
1913
- const sign = parseFloat(match[1] + "1");
1914
- const integ = parseFloat(match[2]);
1915
- const num = parseFloat(match[3]);
1916
- const denom = parseFloat(match[4]);
1917
- const simplified = num < denom && KhanMath.getGCD(num, denom) === 1;
1918
- return [{
1919
- value: sign * (integ + num / denom),
1920
- exact: simplified
1921
- }];
1922
- }
1923
- return [];
1924
- },
1925
- // Decimal numbers -- compare entered text rounded to
1926
- // 'precision' Reciprical of the precision against the correct
1927
- // answer. We round to 1/1e10 by default, which is healthily
1928
- // less than machine epsilon but should be more than any real
1929
- // decimal answer would use. (The 'integer' answer type uses
1930
- // precision == 1.)
1931
- decimal: function (text, precision = 1e10) {
1932
- const normal = function normal(text) {
1933
- text = $.trim(text);
1934
- const match = text
1935
- // Replace unicode minus sign with hyphen
1936
- .replace(/\u2212/, "-")
1937
- // Remove space after +, -
1938
- .replace(/([+-])\s+/g, "$1")
1939
- // Extract integer, numerator and denominator. If
1940
- // commas or spaces are used, they must be in the
1941
- // "correct" places
1942
- .match(/^([+-]?(?:\d{1,3}(?:[, ]?\d{3})*\.?|\d{0,3}(?:[, ]?\d{3})*\.(?:\d{3}[, ]?)*\d{1,3}))$/);
1943
-
1944
- // You can't start a number with `0,`, to prevent us
1945
- // interpeting '0.342' as correct for '342'
1946
- const badLeadingZero = text.match(/^0[0,]*,/);
1947
- if (match && !badLeadingZero) {
1948
- let x = parseFloat(match[1].replace(/[, ]/g, ""));
1949
- if (options.inexact === undefined) {
1950
- x = Math.round(x * precision) / precision;
1951
- }
1952
- return x;
1953
- }
1954
- };
1955
- const commas = function commas(text) {
1956
- text = text.replace(/([\.,])/g, function (_, c) {
1957
- return c === "." ? "," : ".";
1958
- });
1959
- return normal(text);
1960
- };
1961
- return [{
1962
- value: normal(text),
1963
- exact: true
1964
- }, {
1965
- value: commas(text),
1966
- exact: true
1967
- }];
1968
- }
1969
- };
1970
-
1971
- // validator function
1972
- return function (guess) {
1973
- // The fallback variable is used in place of the answer, if no
1974
- // answer is provided (i.e. the field is left blank)
1975
- const fallback = options.fallback != null ? "" + options.fallback : "";
1976
- guess = $.trim(guess) || fallback;
1977
- const score = {
1978
- empty: guess === "",
1979
- correct: false,
1980
- message: null,
1981
- guess: guess
1982
- };
1983
-
1984
- // iterate over all the acceptable forms, and if one of the
1985
- // answers is correct, return true
1986
- $.each(acceptableForms, function (i, form) {
1987
- const transformed = forms[form](guess);
1988
- for (let j = 0, l = transformed.length; j < l; j++) {
1989
- const val = transformed[j].value;
1990
- const exact = transformed[j].exact;
1991
- const piApprox = transformed[j].piApprox;
1992
- // If a string was returned, and it exactly matches,
1993
- // return true
1994
- if (predicate(val, options.maxError)) {
1995
- // If the exact correct number was returned,
1996
- // return true
1997
- if (exact || options.simplify === "optional") {
1998
- score.correct = true;
1999
- score.message = options.message || null;
2000
- // If the answer is correct, don't say it's
2001
- // empty. This happens, for example, with the
2002
- // coefficient type where guess === "" but is
2003
- // interpreted as "1" which is correct.
2004
- score.empty = false;
2005
- } else if (form === "percent") {
2006
- // Otherwise, an error was returned
2007
- score.empty = true;
2008
- score.message = strings.MISSING_PERCENT_ERROR;
2009
- } else {
2010
- if (options.simplify !== "enforced") {
2011
- score.empty = true;
2012
- }
2013
- score.message = strings.NEEDS_TO_BE_SIMPLFIED_ERROR;
2014
- }
2015
- // The return false below stops the looping of the
2016
- // callback since predicate check succeeded.
2017
- // No more forms to look to verify the user guess.
2018
- return false;
2019
- }
2020
- if (piApprox && predicate(val, Math.abs(val * 0.001))) {
2021
- score.empty = true;
2022
- score.message = strings.APPROXIMATED_PI_ERROR;
2023
- }
2024
- }
2025
- });
2026
- if (score.correct === false) {
2027
- let interpretedGuess = false;
2028
- _.each(forms, function (form) {
2029
- const anyAreNaN = _.any(form(guess), function (t) {
2030
- return t.value != null && !_.isNaN(t.value);
2031
- });
2032
- if (anyAreNaN) {
2033
- interpretedGuess = true;
2034
- }
2035
- });
2036
- if (!interpretedGuess) {
2037
- score.empty = true;
2038
- score.message = strings.EXTRA_SYMBOLS_ERROR;
2039
- return score;
2040
- }
2041
- }
2042
- return score;
2043
- };
2044
- }
2045
- },
2046
- /*
2047
- * number answer type
2048
- *
2049
- * wraps the predicate answer type to performs simple number-based checking
2050
- * of a solution
2051
- */
2052
- number: {
2053
- convertToPredicate: function (correctAnswer, options) {
2054
- const correctFloat = parseFloat(correctAnswer);
2055
- return [function (guess, maxError) {
2056
- return Math.abs(guess - correctFloat) < maxError;
2057
- }, $.extend({}, options, {
2058
- type: "predicate"
2059
- })];
2060
- },
2061
- createValidatorFunctional: function (correctAnswer, options, strings) {
2062
- return KhanAnswerTypes.predicate.createValidatorFunctional(...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
2063
- }
2064
- },
2065
- /*
2066
- * The expression answer type parses a given expression or equation
2067
- * and semantically compares it to the solution. In addition, instant
2068
- * feedback is provided by rendering the last answer that fully parsed.
2069
- *
2070
- * Parsing options:
2071
- * functions (e.g. data-functions="f g h")
2072
- * A space or comma separated list of single-letter variables that
2073
- * should be interpreted as functions. Case sensitive. "e" and "i"
2074
- * are reserved.
2075
- *
2076
- * no functions specified: f(x+y) == fx + fy
2077
- * with "f" as a function: f(x+y) != fx + fy
2078
- *
2079
- * Comparison options:
2080
- * same-form (e.g. data-same-form)
2081
- * If present, the answer must match the solution's structure in
2082
- * addition to evaluating the same. Commutativity and excess negation
2083
- * are ignored, but all other changes will trigger a rejection. Useful
2084
- * for requiring a particular form of an equation, or if the answer
2085
- * must be factored.
2086
- *
2087
- * example question: Factor x^2 + x - 2
2088
- * example solution: (x-1)(x+2)
2089
- * accepted answers: (x-1)(x+2), (x+2)(x-1), ---(-x-2)(-1+x), etc.
2090
- * rejected answers: x^2+x-2, x*x+x-2, x(x+1)-2, (x-1)(x+2)^1, etc.
2091
- * rejection message: Your answer is not in the correct form
2092
- *
2093
- * simplify (e.g. data-simplify)
2094
- * If present, the answer must be fully expanded and simplified. Use
2095
- * carefully - simplification is hard and there may be bugs, or you
2096
- * might not agree on the definition of "simplified" used. You will
2097
- * get an error if the provided solution is not itself fully expanded
2098
- * and simplified.
2099
- *
2100
- * example question: Simplify ((n*x^5)^5) / (n^(-2)*x^2)^-3
2101
- * example solution: x^31 / n
2102
- * accepted answers: x^31 / n, x^31 / n^1, x^31 * n^(-1), etc.
2103
- * rejected answers: (x^25 * n^5) / (x^(-6) * n^6), etc.
2104
- * rejection message: Your answer is not fully expanded and simplified
2105
- *
2106
- * Rendering options:
2107
- * times (e.g. data-times)
2108
- * If present, explicit multiplication (such as between numbers) will
2109
- * be rendered with a cross/x symbol (TeX: \times) instead of the usual
2110
- * center dot (TeX: \cdot).
2111
- *
2112
- * normal rendering: 2 * 3^x -> 2 \cdot 3^{x}
2113
- * but with "times": 2 * 3^x -> 2 \times 3^{x}
2114
- */
2115
- expression: {
2116
- parseSolution: function (solutionString, options) {
2117
- let solution = KAS.parse(solutionString, options);
2118
- if (!solution.parsed) {
2119
- throw new PerseusError("The provided solution (" + solutionString + ") didn't parse.", Errors.InvalidInput);
2120
- } else if (options.simplified && !solution.expr.isSimplified()) {
2121
- throw new PerseusError("The provided solution (" + solutionString + ") isn't fully expanded and simplified.", Errors.InvalidInput);
2122
- } else {
2123
- solution = solution.expr;
2124
- }
2125
- return solution;
2126
- },
2127
- createValidatorFunctional: function (solution, options, strings) {
2128
- return function (guess) {
2129
- const score = {
2130
- empty: false,
2131
- correct: false,
2132
- message: null,
2133
- guess: guess,
2134
- // Setting `ungraded` to true indicates that if the
2135
- // guess doesn't match any of the solutions, the guess
2136
- // shouldn't be marked as incorrect; instead, `message`
2137
- // should be shown to the user. This is different from
2138
- // setting `empty` to true, since the behavior of `empty`
2139
- // is that `message` only will be shown if the guess is
2140
- // graded as empty for every solution.
2141
- ungraded: false
2142
- };
2143
-
2144
- // Don't bother parsing an empty input
2145
- if (!guess) {
2146
- // @ts-expect-error - TS2540 - Cannot assign to 'empty' because it is a read-only property.
2147
- score.empty = true;
2148
- return score;
2149
- }
2150
- const answer = KAS.parse(guess, options);
2151
-
2152
- // An unsuccessful parse doesn't count as wrong
2153
- if (!answer.parsed) {
2154
- // @ts-expect-error - TS2540 - Cannot assign to 'empty' because it is a read-only property.
2155
- score.empty = true;
2156
- return score;
2157
- }
2158
-
2159
- // Solution will need to be parsed again if we're creating
2160
- // this from a multiple question type
2161
- if (typeof solution === "string") {
2162
- solution = KhanAnswerTypes.expression.parseSolution(solution, options);
2163
- }
2164
- const result = KAS.compare(answer.expr, solution, options);
2165
- if (result.equal) {
2166
- // Correct answer
2167
- // @ts-expect-error - TS2540 - Cannot assign to 'correct' because it is a read-only property.
2168
- score.correct = true;
2169
- } else if (result.wrongVariableNames || result.wrongVariableCase) {
2170
- // We don't want to give people an error for getting the
2171
- // variable names or the variable case wrong.
2172
- // TODO(aasmund): This should ideally have been handled
2173
- // under the `result.message` condition, but the
2174
- // KAS messages currently aren't translatable.
2175
- // @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
2176
- score.ungraded = true;
2177
- // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
2178
- score.message = result.wrongVariableCase ? strings.WRONG_CASE_ERROR : strings.WRONG_LETTER_ERROR;
2179
- // Don't tell the use they're "almost there" in this case, that may not be true and isn't helpful.
2180
- // @ts-expect-error - TS2339 - Property 'suppressAlmostThere' does not exist on type '{ readonly empty: false; readonly correct: false; readonly message: string | null | undefined; readonly guess: any; readonly ungraded: false; }'.
2181
- score.suppressAlmostThere = true;
2182
- } else if (result.message) {
2183
- // Nearly correct answer
2184
- // TODO(aasmund): This message also isn't translatable;
2185
- // need to fix that in KAS
2186
- // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
2187
- score.message = result.message;
2188
- } else {
2189
- // Replace x with * and see if it would have been correct
2190
- // TODO(aasmund): I think this branch is effectively dead,
2191
- // because the replacement will only work in situations
2192
- // where the variables are wrong (except if the variable
2193
- // is x, in which case the replacement won't work either),
2194
- // which is handled by another branch. When we implement a
2195
- // more sophisticated variable check, revive this or
2196
- // remove it completely if it will never come into play.
2197
- const answerX = KAS.parse(guess.replace(/[xX]/g, "*"), options);
2198
- if (answerX.parsed) {
2199
- const resultX = KAS.compare(answerX.expr, solution, options);
2200
- if (resultX.equal) {
2201
- // @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
2202
- score.ungraded = true;
2203
- // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
2204
- score.message = strings.MULTIPLICATION_SIGN_ERROR;
2205
- } else if (resultX.message) {
2206
- // TODO(aasmund): I18nize `score.message`
2207
- // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
2208
- score.message = resultX.message + " Also, I'm a computer. I only understand " + "multiplication if you use an " + "asterisk (*) as the multiplication " + "sign.";
2209
- }
2210
- }
2211
- }
2212
- return score;
2213
- };
2214
- }
2215
- }
2216
- };
2217
-
2218
1495
  /**
2219
1496
  * Checks user input from the expression widget to see if it is scorable.
2220
1497
  *
@@ -2281,7 +1558,7 @@ function scoreExpression(userInput, rubric, strings, locale) {
2281
1558
  return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, _({}).extend(options, {
2282
1559
  simplify: answer.simplify,
2283
1560
  form: answer.form
2284
- }), strings);
1561
+ }));
2285
1562
  };
2286
1563
 
2287
1564
  // Find the first answer form that matches the user's input and that
@@ -2956,7 +2233,7 @@ function firstNumericalParse$1(text, strings) {
2956
2233
  simplify: "optional",
2957
2234
  inexact: true,
2958
2235
  forms: "integer, proper, improper, pi, log, mixed, decimal"
2959
- }, strings);
2236
+ });
2960
2237
  val(text);
2961
2238
  return first;
2962
2239
  }
@@ -14684,54 +13961,6 @@ var MovableLineOptions = {
14684
13961
  }
14685
13962
  };
14686
13963
 
14687
- /**
14688
- * A work-in-progress of _ methods for objects.
14689
- * That is, they take an object as a parameter,
14690
- * and return an object instead of an array.
14691
- *
14692
- * TODO(aria): Move this out of interactive2
14693
- */
14694
-
14695
- /**
14696
- * Does a pluck on keys inside objects in an object
14697
- *
14698
- * Ex:
14699
- * tools = {
14700
- * translation: {
14701
- * enabled: true
14702
- * },
14703
- * rotation: {
14704
- * enabled: false
14705
- * }
14706
- * };
14707
- * pluckObject(tools, "enabled") returns {
14708
- * translation: true
14709
- * rotation: false
14710
- * }
14711
- */
14712
- const pluck = function pluck(table, subKey) {
14713
- return _.object(_.map(table, function (value, key) {
14714
- return [key, value[subKey]];
14715
- }));
14716
- };
14717
-
14718
- /**
14719
- * Maps an object to an object
14720
- *
14721
- * > mapObject({a: '1', b: '2'}, (value, key) => {
14722
- * return value + 1;
14723
- * });
14724
- * {a: 2, b: 3}
14725
- */
14726
- const mapObject = function mapObject(obj, lambda) {
14727
- const result = {};
14728
- Object.keys(obj).forEach(key => {
14729
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
14730
- result[key] = lambda(obj[key], key);
14731
- });
14732
- return result;
14733
- };
14734
-
14735
13964
  /**
14736
13965
  * MovableLine
14737
13966
  */
@@ -18547,7 +17776,7 @@ const inlineNodeTypes = {
18547
17776
 
18548
17777
  // @ts-expect-error - TS2345 - Argument of type '{ readonly columns: { readonly react: (node: any, output: any, state: any) => Element; readonly order: -2; readonly match: any; readonly parse: (capture: any, parse: any, state: any) => any; }; ... 35 more ...; readonly text: TextInOutRule; }' is not assignable to parameter of type 'ParserRules'.
18549
17778
  const builtParser$1 = SimpleMarkdown.parserFor(rules$1);
18550
- const parse$3 = (source, state) => {
17779
+ const parse$4 = (source, state) => {
18551
17780
  const paragraphedSource = source + "\n\n";
18552
17781
  return builtParser$1(paragraphedSource, _extends({}, state, {
18553
17782
  inline: false
@@ -18630,14 +17859,14 @@ const getContent = ast => {
18630
17859
  */
18631
17860
  const characterCount = source => {
18632
17861
  // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
18633
- const ast = parse$3(source);
17862
+ const ast = parse$4(source);
18634
17863
  const content = getContent(ast).join("");
18635
17864
  return content.length;
18636
17865
  };
18637
17866
  var PerseusMarkdown = {
18638
17867
  characterCount: characterCount,
18639
17868
  traverseContent: traverseContent,
18640
- parse: parse$3,
17869
+ parse: parse$4,
18641
17870
  parseInline: inlineParser,
18642
17871
  reactFor: SimpleMarkdown.reactFor,
18643
17872
  ruleOutput: SimpleMarkdown.ruleOutput(rules$1, "react"),
@@ -21866,7 +21095,7 @@ function scoreInputNumber(userInput, rubric, strings) {
21866
21095
  inexact: rubric.inexact || undefined,
21867
21096
  maxError: rubric.maxError,
21868
21097
  forms: answerTypes[rubric.answerType].forms
21869
- }, strings);
21098
+ });
21870
21099
 
21871
21100
  // We may have received TeX; try to parse it before grading.
21872
21101
  // If `currentValue` is not TeX, this should be a no-op.
@@ -22204,7 +21433,7 @@ function scoreNumericInput(userInput, rubric, strings) {
22204
21433
  // TODO(merlob) backfill / delete
22205
21434
  maxError: answer.maxError,
22206
21435
  forms: validatorForms
22207
- }, strings);
21436
+ });
22208
21437
  };
22209
21438
 
22210
21439
  // We may have received TeX; try to parse it before grading.
@@ -24309,7 +23538,7 @@ const CIRCLE_LABEL_STYLE = {
24309
23538
  // TODO(LEMS-2656): remove TS suppression
24310
23539
  // @ts-expect-error: State is not assignable to ParseState
24311
23540
  const builtParser = SimpleMarkdown.parserFor(rules);
24312
- const parse$2 = (source, state) => {
23541
+ const parse$3 = (source, state) => {
24313
23542
  // @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
24314
23543
  state = state || {};
24315
23544
  const paragraphedSource = source + "\n\n";
@@ -24319,7 +23548,7 @@ const parse$2 = (source, state) => {
24319
23548
  // @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
24320
23549
  const output = SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules, "react"));
24321
23550
  var PassageMarkdown = {
24322
- parse: parse$2,
23551
+ parse: parse$3,
24323
23552
  output,
24324
23553
  START_REF_PREFIX,
24325
23554
  END_REF_PREFIX,
@@ -28956,6 +28185,9 @@ const typeToButton = type => {
28956
28185
  };
28957
28186
 
28958
28187
  function getCoefficientsByType(data) {
28188
+ if (data.coords == null) {
28189
+ return undefined;
28190
+ }
28959
28191
  if (data.type === "exponential" || data.type === "logarithm") {
28960
28192
  const grader = functionForType(data.type);
28961
28193
  return grader.getCoefficients(data.coords, data.asymptote);
@@ -31710,9 +30942,10 @@ const AxisTicks = () => {
31710
30942
  * @param {number} axisIndex which axis we're getting options for
31711
30943
  */
31712
30944
  const axisOptions = (props, axisIndex) => {
30945
+ const lines = props.markings === "axes" ? false : props.gridStep[axisIndex];
31713
30946
  return {
31714
- axis: props.markings === "graph",
31715
- lines: props.gridStep[axisIndex],
30947
+ axis: props.markings === "graph" || props.markings === "axes",
30948
+ lines: lines,
31716
30949
  labels: false
31717
30950
  };
31718
30951
  };
@@ -32294,231 +31527,6 @@ const LegacyGrid = ({
32294
31527
  return null;
32295
31528
  };
32296
31529
 
32297
- /**
32298
- * The Perseus "data schema" file.
32299
- *
32300
- * This file, and the types in it, represents the "data schema" that Perseus
32301
- * uses. The @khanacademy/perseus-editor package edits and produces objects
32302
- * that conform to the types in this file. Similarly, the top-level renderers
32303
- * in @khanacademy/perseus, consume objects that conform to these types.
32304
- *
32305
- * WARNING: This file should not import any types from elsewhere so that it is
32306
- * easy to reason about changes that alter the Perseus schema. This helps
32307
- * ensure that it is not changed accidentally when upgrading a dependant
32308
- * package or other part of Perseus code. Note that TypeScript does type
32309
- * checking via something called "structural typing". This means that as long
32310
- * as the shape of a type matches, the name it goes by doesn't matter. As a
32311
- * result, a `Coord` type that looks like this `[x: number, y: number]` is
32312
- * _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
32313
- * number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
32314
- * number, y: number]` is compatible with `[min: number, max: number]`. The
32315
- * labels are for humans, not TypeScript. :)
32316
- *
32317
- * If you make changes to types in this file, be very sure that:
32318
- *
32319
- * a) the changes are backwards compatible. If they are not, old data from
32320
- * previous versions of the "schema" could become unrenderable, or worse,
32321
- * introduce hard-to-diagnose bugs.
32322
- * b) the parsing code (`util/parse-perseus-json/`) is updated to handle
32323
- * the new format _as well as_ the old format.
32324
- */
32325
-
32326
- // TODO(FEI-4010): Remove `Perseus` prefix for all types here
32327
-
32328
- // Same name as Mafs
32329
-
32330
- /**
32331
- * Our core set of Perseus widgets.
32332
- *
32333
- * This interface is the basis for "registering" all Perseus widget types.
32334
- * There should be one key/value pair for each supported widget. If you create
32335
- * a new widget, an entry should be added to this interface. Note that this
32336
- * only registers the widget options type, you'll also need to register the
32337
- * widget so that it's available at runtime (@see
32338
- * {@link file://./widgets.ts#registerWidget}).
32339
- *
32340
- * Importantly, the key should be the name that is used in widget IDs. For most
32341
- * widgets that is the same as the widget option's `type` field. In cases where
32342
- * a widget has been deprecated and replaced with the deprecated-standin
32343
- * widget, it should be the original widget type!
32344
- *
32345
- * If you define the widget outside of this package, you can still add the new
32346
- * widget to this interface by writing the following in that package that
32347
- * contains the widget. TypeScript will merge that definition of the
32348
- * `PerseusWidgets` with the one defined below.
32349
- *
32350
- * ```typescript
32351
- * declare module "@khanacademy/perseus" {
32352
- * interface PerseusWidgetTypes {
32353
- * // A new widget
32354
- * "new-awesomeness": MyAwesomeNewWidget;
32355
- *
32356
- * // A deprecated widget
32357
- * "super-old-widget": DeprecatedStandinWidget;
32358
- * }
32359
- * }
32360
- *
32361
- * // The new widget's options definition
32362
- * type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
32363
- *
32364
- * // The deprecated widget's options definition
32365
- * type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
32366
- * ```
32367
- *
32368
- * This interface can be extended through the magic of TypeScript "Declaration
32369
- * merging". Specifically, we augment this module and extend this interface.
32370
- *
32371
- * @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
32372
- */
32373
-
32374
- /**
32375
- * A map of widget IDs to widget options. This is most often used as the type
32376
- * for a set of widgets defined in a `PerseusItem` but can also be useful to
32377
- * represent a function parameter where only `widgets` from a `PerseusItem` are
32378
- * needed. Today Widget IDs are made up of the widget type and an incrementing
32379
- * integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
32380
- * reading/parsing the widget id to derive any information from it, except in
32381
- * the case of this map.
32382
- *
32383
- * @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
32384
- * by augmenting the PerseusWidgetTypes with new widget types!
32385
- */
32386
-
32387
- /**
32388
- * A "PerseusItem" is a classic Perseus item. It is rendered by the
32389
- * `ServerItemRenderer` and the layout is pre-set.
32390
- *
32391
- * To render more complex Perseus items, see the `Item` type in the multi item
32392
- * area.
32393
- */
32394
-
32395
- /**
32396
- * A "PerseusArticle" is an item that is meant to be rendered as an article.
32397
- * This item is never scored and is rendered by the `ArticleRenderer`.
32398
- */
32399
-
32400
- const ItemExtras = [
32401
- // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
32402
- "calculator",
32403
- // The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
32404
- "chi2Table",
32405
- // The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
32406
- "financialCalculatorMonthlyPayment",
32407
- // The user might benefit from a total amount calculator. Provided on Khan Academy when true
32408
- "financialCalculatorTotalAmount",
32409
- // The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
32410
- "financialCalculatorTimeToPayOff",
32411
- // The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
32412
- "periodicTable",
32413
- // The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
32414
- "periodicTableWithKey",
32415
- // The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
32416
- "tTable",
32417
- // The user might benefit from using a statistics Z Table like https://www.ztable.net/
32418
- "zTable"];
32419
-
32420
- /**
32421
- * The type representing the common structure of all widget's options. The
32422
- * `Options` generic type represents the widget-specific option data.
32423
- */
32424
-
32425
- // prettier-ignore
32426
-
32427
- // prettier-ignore
32428
-
32429
- // prettier-ignore
32430
-
32431
- // prettier-ignore
32432
-
32433
- // prettier-ignore
32434
-
32435
- // prettier-ignore
32436
-
32437
- // prettier-ignore
32438
-
32439
- // prettier-ignore
32440
-
32441
- // prettier-ignore
32442
-
32443
- // prettier-ignore
32444
-
32445
- // prettier-ignore
32446
-
32447
- // prettier-ignore
32448
-
32449
- // prettier-ignore
32450
-
32451
- // prettier-ignore
32452
-
32453
- // prettier-ignore
32454
-
32455
- // prettier-ignore
32456
-
32457
- // prettier-ignore
32458
-
32459
- // prettier-ignore
32460
-
32461
- // prettier-ignore
32462
-
32463
- // prettier-ignore
32464
-
32465
- // prettier-ignore
32466
-
32467
- // prettier-ignore
32468
-
32469
- // prettier-ignore
32470
-
32471
- // prettier-ignore
32472
-
32473
- // prettier-ignore
32474
-
32475
- // prettier-ignore
32476
-
32477
- // prettier-ignore
32478
-
32479
- // prettier-ignore
32480
-
32481
- // prettier-ignore
32482
-
32483
- // prettier-ignore
32484
-
32485
- // prettier-ignore
32486
-
32487
- // prettier-ignore
32488
-
32489
- // prettier-ignore
32490
-
32491
- //prettier-ignore
32492
-
32493
- /**
32494
- * A background image applied to various widgets.
32495
- */
32496
-
32497
- const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
32498
-
32499
- // 2D range: xMin, xMax, yMin, yMax
32500
-
32501
- const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
32502
- const lockedFigureColors = {
32503
- blue: "#3D7586",
32504
- green: "#447A53",
32505
- grayH: "#3B3D45",
32506
- purple: "#594094",
32507
- pink: "#B25071",
32508
- red: "#D92916",
32509
- orange: "#946700"
32510
- };
32511
- const lockedFigureFillStyles = {
32512
- none: 0,
32513
- white: 1,
32514
- translucent: 0.4,
32515
- solid: 1
32516
- };
32517
-
32518
- // Not associated with a specific figure
32519
-
32520
- const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
32521
-
32522
31530
  function LockedLabel(props) {
32523
31531
  const {
32524
31532
  coord,
@@ -34289,7 +33297,9 @@ function renderCircleGraph(state, dispatch) {
34289
33297
  graphState: state,
34290
33298
  dispatch: dispatch
34291
33299
  }),
34292
- interactiveElementsDescription: null
33300
+ interactiveElementsDescription: /*#__PURE__*/React.createElement(CircleGraphDescription, {
33301
+ state: state
33302
+ })
34293
33303
  };
34294
33304
  }
34295
33305
  function CircleGraph(props) {
@@ -34313,38 +33323,26 @@ function CircleGraph(props) {
34313
33323
  const outerPointsId = id + "-outer-points";
34314
33324
 
34315
33325
  // Aria label strings
34316
- const circleGraphAriaLabel = strings.srCircleGraph;
34317
- const circleShapeAriaLabel = strings.srCircleShape({
34318
- centerX: srFormatNumber(center[0], locale),
34319
- centerY: srFormatNumber(center[1], locale)
34320
- });
34321
- const circleRadiusPointAriaLabel = strings.srCircleRadiusPoint({
34322
- radiusPointX: srFormatNumber(radiusPoint[0], locale),
34323
- radiusPointY: srFormatNumber(radiusPoint[1], locale)
34324
- });
34325
- const circleRadiusDescription = strings.srCircleRadius({
34326
- radius
34327
- });
34328
- const circleOuterPointsDescription = strings.srCircleOuterPoints({
34329
- point1X: srFormatNumber(center[0] + radius, locale),
34330
- point1Y: srFormatNumber(center[1], locale),
34331
- point2X: srFormatNumber(center[0], locale),
34332
- point2Y: srFormatNumber(center[1] + radius, locale),
34333
- point3X: srFormatNumber(center[0] - radius, locale),
34334
- point3Y: srFormatNumber(center[1], locale),
34335
- point4X: srFormatNumber(center[0], locale),
34336
- point4Y: srFormatNumber(center[1] - radius, locale)
33326
+ const {
33327
+ srCircleGraph,
33328
+ srCircleShape,
33329
+ srCircleRadiusPoint,
33330
+ srCircleRadius,
33331
+ srCircleOuterPoints
33332
+ } = describeCircleGraph(graphState, {
33333
+ strings,
33334
+ locale
34337
33335
  });
34338
33336
  return /*#__PURE__*/React.createElement("g", {
34339
33337
  // Outer circle minimal description
34340
- "aria-label": circleGraphAriaLabel,
33338
+ "aria-label": srCircleGraph,
34341
33339
  "aria-describedby": `${circleId} ${radiusId} ${outerPointsId}`
34342
33340
  }, /*#__PURE__*/React.createElement(MovableCircle, {
34343
33341
  id: circleId
34344
33342
  // Focusable circle aria label reads with every update
34345
33343
  // because of the aria-live property in the circle <g>.
34346
33344
  ,
34347
- ariaLabel: circleShapeAriaLabel
33345
+ ariaLabel: srCircleShape
34348
33346
  // Aria-describedby describes additional info on focus.
34349
33347
  ,
34350
33348
  ariaDescribedBy: `${radiusId} ${outerPointsId}`,
@@ -34357,7 +33355,7 @@ function CircleGraph(props) {
34357
33355
  }), /*#__PURE__*/React.createElement(MovablePoint$1
34358
33356
  // Radius point aria label reads with every update.
34359
33357
  , {
34360
- ariaLabel: `${circleRadiusPointAriaLabel} ${circleRadiusDescription}`
33358
+ ariaLabel: `${srCircleRadiusPoint} ${srCircleRadius}`
34361
33359
  // Aria-describedby describes additional info on focus.
34362
33360
  ,
34363
33361
  ariaDescribedBy: `${outerPointsId}`
@@ -34380,12 +33378,12 @@ function CircleGraph(props) {
34380
33378
  style: {
34381
33379
  display: "hidden"
34382
33380
  }
34383
- }, circleRadiusDescription), /*#__PURE__*/React.createElement("g", {
33381
+ }, srCircleRadius), /*#__PURE__*/React.createElement("g", {
34384
33382
  id: outerPointsId,
34385
33383
  style: {
34386
33384
  display: "hidden"
34387
33385
  }
34388
- }, circleOuterPointsDescription));
33386
+ }, srCircleOuterPoints));
34389
33387
  }
34390
33388
  function MovableCircle(props) {
34391
33389
  const {
@@ -34472,6 +33470,64 @@ function crossProduct(as, bs) {
34472
33470
  }
34473
33471
  return result;
34474
33472
  }
33473
+ function CircleGraphDescription({
33474
+ state
33475
+ }) {
33476
+ // The reason that CircleGraphDescription is a component (rather than a
33477
+ // function that returns a string) is because it needs to use a
33478
+ // hook: `usePerseusI18n`.
33479
+ const i18n = usePerseusI18n();
33480
+ const strings = describeCircleGraph(state, i18n);
33481
+ return strings.srCircleInteractiveElement;
33482
+ }
33483
+
33484
+ // Exported for testing
33485
+ function describeCircleGraph(state, i18n) {
33486
+ const {
33487
+ strings,
33488
+ locale
33489
+ } = i18n;
33490
+ const {
33491
+ center,
33492
+ radiusPoint
33493
+ } = state;
33494
+ const radius = getRadius(state);
33495
+
33496
+ // Aria label strings
33497
+ const srCircleGraph = strings.srCircleGraph;
33498
+ const srCircleShape = strings.srCircleShape({
33499
+ centerX: srFormatNumber(center[0], locale),
33500
+ centerY: srFormatNumber(center[1], locale)
33501
+ });
33502
+ const srCircleRadiusPoint = strings.srCircleRadiusPoint({
33503
+ radiusPointX: srFormatNumber(radiusPoint[0], locale),
33504
+ radiusPointY: srFormatNumber(radiusPoint[1], locale)
33505
+ });
33506
+ const srCircleRadius = strings.srCircleRadius({
33507
+ radius
33508
+ });
33509
+ const srCircleOuterPoints = strings.srCircleOuterPoints({
33510
+ point1X: srFormatNumber(center[0] + radius, locale),
33511
+ point1Y: srFormatNumber(center[1], locale),
33512
+ point2X: srFormatNumber(center[0], locale),
33513
+ point2Y: srFormatNumber(center[1] + radius, locale),
33514
+ point3X: srFormatNumber(center[0] - radius, locale),
33515
+ point3Y: srFormatNumber(center[1], locale),
33516
+ point4X: srFormatNumber(center[0], locale),
33517
+ point4Y: srFormatNumber(center[1] - radius, locale)
33518
+ });
33519
+ const srCircleInteractiveElement = strings.srInteractiveElements({
33520
+ elements: [srCircleShape, srCircleRadius].join(" ")
33521
+ });
33522
+ return {
33523
+ srCircleGraph,
33524
+ srCircleShape,
33525
+ srCircleRadiusPoint,
33526
+ srCircleRadius,
33527
+ srCircleOuterPoints,
33528
+ srCircleInteractiveElement
33529
+ };
33530
+ }
34475
33531
 
34476
33532
  function renderLinearGraph(state, dispatch) {
34477
33533
  return {
@@ -35462,7 +34518,9 @@ const MafsGraph = props => {
35462
34518
  labels,
35463
34519
  disableKeyboardInteraction: readOnly || !!props.static
35464
34520
  }
35465
- }, /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(View, {
34521
+ }, /*#__PURE__*/React.createElement(View, {
34522
+ className: "mafs-graph-container"
34523
+ }, /*#__PURE__*/React.createElement(View, {
35466
34524
  className: "mafs-graph",
35467
34525
  style: {
35468
34526
  position: "relative",
@@ -35505,7 +34563,7 @@ const MafsGraph = props => {
35505
34563
  bottom: 0,
35506
34564
  left: 0
35507
34565
  }
35508
- }, props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(Mafs, {
34566
+ }, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(Mafs, {
35509
34567
  preserveAspectRatio: false,
35510
34568
  viewBox: {
35511
34569
  x: state.range[X],
@@ -35525,7 +34583,7 @@ const MafsGraph = props => {
35525
34583
  height: height
35526
34584
  })),
35527
34585
  // Only render the axis ticks and arrows if the markings are set to a full "graph"
35528
- props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
34586
+ (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
35529
34587
  lockedFigures: props.lockedFigures,
35530
34588
  range: state.range
35531
34589
  }))), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
@@ -41025,7 +40083,7 @@ function scoreMatrix(userInput, rubric, strings) {
41025
40083
  // @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
41026
40084
  solution[row][col], {
41027
40085
  simplify: true
41028
- }, strings);
40086
+ });
41029
40087
  const result = validator(supplied[row][col]);
41030
40088
  if (result.message) {
41031
40089
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
@@ -42178,10 +41236,10 @@ function parseBondModifier(smiles, ctx) {
42178
41236
  const firstChar = smiles[0];
42179
41237
  const rest = smiles.slice(1);
42180
41238
  if (firstChar === "=") {
42181
- return parse$1(rest, _mset(ctx, ["bond", "bondType"], "double"));
41239
+ return parse$2(rest, _mset(ctx, ["bond", "bondType"], "double"));
42182
41240
  }
42183
41241
  if (firstChar === "#") {
42184
- return parse$1(rest, _mset(ctx, ["bond", "bondType"], "triple"));
41242
+ return parse$2(rest, _mset(ctx, ["bond", "bondType"], "triple"));
42185
41243
  }
42186
41244
  throw new ParseError$1("Invalid character: " + firstChar);
42187
41245
  }
@@ -42240,8 +41298,8 @@ function parseParenthesizedExpression(smiles, ctx) {
42240
41298
  idx: newCtx.idx.concat([[inBranchIdx, 0]]),
42241
41299
  parens: newCtx.parens.concat("(")
42242
41300
  });
42243
- const parenExpr = parse$1(rest, parenCtx);
42244
- const remainder = parse$1(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
41301
+ const parenExpr = parse$2(rest, parenCtx);
41302
+ const remainder = parse$2(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
42245
41303
  return [parenExpr].concat(remainder);
42246
41304
  }
42247
41305
  if (firstChar === ")") {
@@ -42305,7 +41363,7 @@ function parseAtom(smiles, ctx) {
42305
41363
 
42306
41364
  // increment the atom counter and reset the branch counter
42307
41365
  const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
42308
- let restOfMolecule = parse$1(rest, _mset(newCtx, ["bond", "bondType"], "single"));
41366
+ let restOfMolecule = parse$2(rest, _mset(newCtx, ["bond", "bondType"], "single"));
42309
41367
  if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
42310
41368
  //TODO(colin): fix this awkwardness.
42311
41369
  restOfMolecule = [restOfMolecule];
@@ -42344,7 +41402,7 @@ function isModifierChar(s) {
42344
41402
  * ParseError: if the input is not valid SMILES or contains features not
42345
41403
  * yet implemented.
42346
41404
  */
42347
- function parse$1(smiles, ctx) {
41405
+ function parse$2(smiles, ctx) {
42348
41406
  if (!validate(smiles)) {
42349
41407
  throw new ParseError$1("Invalid input.");
42350
41408
  }
@@ -42369,7 +41427,7 @@ function parse$1(smiles, ctx) {
42369
41427
  return parseParenthesizedExpression(smiles, ctx);
42370
41428
  }
42371
41429
  var SmilesParser = {
42372
- parse: parse$1,
41430
+ parse: parse$2,
42373
41431
  ParseError: ParseError$1
42374
41432
  };
42375
41433
 
@@ -42377,7 +41435,7 @@ var SmilesParser = {
42377
41435
  const {
42378
41436
  layout
42379
41437
  } = MoleculeLayout;
42380
- const parse = SmilesParser.parse;
41438
+ const parse$1 = SmilesParser.parse;
42381
41439
  const ParseError = SmilesParser.ParseError;
42382
41440
  const borderSize = 30;
42383
41441
  class Molecule extends React.Component {
@@ -42391,7 +41449,7 @@ class Molecule extends React.Component {
42391
41449
  try {
42392
41450
  this.setState({
42393
41451
  // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
42394
- parsedSmiles: parse(smiles),
41452
+ parsedSmiles: parse$1(smiles),
42395
41453
  error: null
42396
41454
  });
42397
41455
  } catch (e) {
@@ -45077,7 +44135,7 @@ function scoreTable(userInput, rubric, strings) {
45077
44135
  const cellSolution = rowSolution[i];
45078
44136
  const validator = createValidator(cellSolution, {
45079
44137
  simplify: true
45080
- }, strings);
44138
+ });
45081
44139
  const result = validator(cellSupplied);
45082
44140
  if (result.message) {
45083
44141
  message = result.message;
@@ -45501,7 +44559,7 @@ const init = function init() {
45501
44559
 
45502
44560
  // This file is processed by a Rollup plugin (replace) to inject the production
45503
44561
  const libName = "@khanacademy/perseus";
45504
- const libVersion = "49.2.2";
44562
+ const libVersion = "50.0.0";
45505
44563
  addLibraryVersionToPerseusDebug(libName, libVersion);
45506
44564
 
45507
44565
  const apiVersion = {
@@ -47582,6 +46640,69 @@ function all(results, combineFailureDetails = a => a) {
47582
46640
  return success(values);
47583
46641
  }
47584
46642
 
46643
+ class ErrorTrackingParseContext {
46644
+ constructor(path) {
46645
+ this.path = path;
46646
+ }
46647
+ failure(expected, badValue) {
46648
+ return failure([{
46649
+ expected: wrapInArray(expected),
46650
+ badValue,
46651
+ path: this.path
46652
+ }]);
46653
+ }
46654
+ forSubtree(key) {
46655
+ return new ErrorTrackingParseContext([...this.path, key]);
46656
+ }
46657
+ success(value) {
46658
+ return success(value);
46659
+ }
46660
+ }
46661
+ function wrapInArray(a) {
46662
+ return Array.isArray(a) ? a : [a];
46663
+ }
46664
+
46665
+ function formatPath(path) {
46666
+ return "(root)" + path.map(formatPathSegment).join("");
46667
+ }
46668
+ function formatPathSegment(segment) {
46669
+ if (typeof segment === "string") {
46670
+ return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
46671
+ }
46672
+ return `[${segment.toString()}]`;
46673
+ }
46674
+ const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
46675
+
46676
+ function message(failure) {
46677
+ const expected = conjoin(failure.expected);
46678
+ const path = formatPath(failure.path);
46679
+ const badValue = JSON.stringify(failure.badValue);
46680
+ return `At ${path} -- expected ${expected}, but got ${badValue}`;
46681
+ }
46682
+ function conjoin(items) {
46683
+ switch (items.length) {
46684
+ // TODO(benchristel): handle 0 if this is reused elsewhere.
46685
+ case 1:
46686
+ return items[0];
46687
+ case 2:
46688
+ return items.join(" or ");
46689
+ default:
46690
+ {
46691
+ const allButLast = items.slice(0, items.length - 1);
46692
+ const last = items[items.length - 1];
46693
+ return allButLast.join(", ") + ", or " + last;
46694
+ }
46695
+ }
46696
+ }
46697
+
46698
+ function parse(value, parser) {
46699
+ const result = parser(value, new ErrorTrackingParseContext([]));
46700
+ if (isFailure(result)) {
46701
+ return failure(result.detail.map(message).join("; "));
46702
+ }
46703
+ return result;
46704
+ }
46705
+
47585
46706
  const any = (rawValue, ctx) => ctx.success(rawValue);
47586
46707
 
47587
46708
  function array(elementParser) {
@@ -47862,7 +46983,6 @@ const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
47862
46983
  })),
47863
46984
  showEditor: boolean,
47864
46985
  showButtons: boolean,
47865
- width: number,
47866
46986
  height: number,
47867
46987
  static: defaulted(boolean, () => false)
47868
46988
  }));
@@ -48049,25 +47169,6 @@ function migrateV0ToV1(widget) {
48049
47169
  }
48050
47170
  const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
48051
47171
 
48052
- const parsePerseusRenderer = defaulted(object({
48053
- // TODO(benchristel): content is also defaulted to empty string in
48054
- // renderer.tsx. See if we can remove one default or the other.
48055
- content: defaulted(string, () => ""),
48056
- // This module has an import cycle with parseWidgetsMap, because the
48057
- // `group` widget can contain another renderer.
48058
- // The anonymous function below ensures that we don't try to access
48059
- // parseWidgetsMap before it's defined.
48060
- widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
48061
- metadata: optional(array(string)),
48062
- images: parseImages
48063
- }),
48064
- // Default value
48065
- () => ({
48066
- content: "",
48067
- widgets: {},
48068
- images: {}
48069
- }));
48070
-
48071
47172
  const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
48072
47173
  const parseGradedGroupWidgetOptions = object({
48073
47174
  title: defaulted(string, () => ""),
@@ -48138,27 +47239,27 @@ const parseGrapherWidget = parseWidget(constant("grapher"), object({
48138
47239
  availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
48139
47240
  correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
48140
47241
  type: constant("absolute_value"),
48141
- coords: pairOfPoints
47242
+ coords: nullable(pairOfPoints)
48142
47243
  })).withBranch("exponential", object({
48143
47244
  type: constant("exponential"),
48144
47245
  asymptote: pairOfPoints,
48145
- coords: pairOfPoints
47246
+ coords: nullable(pairOfPoints)
48146
47247
  })).withBranch("linear", object({
48147
47248
  type: constant("linear"),
48148
- coords: defaulted(pairOfPoints, () => [[-5, 5], [5, 5]])
47249
+ coords: nullable(pairOfPoints)
48149
47250
  })).withBranch("logarithm", object({
48150
47251
  type: constant("logarithm"),
48151
47252
  asymptote: pairOfPoints,
48152
- coords: pairOfPoints
47253
+ coords: nullable(pairOfPoints)
48153
47254
  })).withBranch("quadratic", object({
48154
47255
  type: constant("quadratic"),
48155
- coords: pairOfPoints
47256
+ coords: nullable(pairOfPoints)
48156
47257
  })).withBranch("sinusoid", object({
48157
47258
  type: constant("sinusoid"),
48158
- coords: pairOfPoints
47259
+ coords: nullable(pairOfPoints)
48159
47260
  })).withBranch("tangent", object({
48160
47261
  type: constant("tangent"),
48161
- coords: pairOfPoints
47262
+ coords: nullable(pairOfPoints)
48162
47263
  })).parser,
48163
47264
  graph: object({
48164
47265
  backgroundImage: object({
@@ -48194,13 +47295,13 @@ const parseGroupWidget = parseWidget(constant("group"),
48194
47295
 
48195
47296
  const parseIframeWidget = parseWidget(constant("iframe"), object({
48196
47297
  url: string,
48197
- settings: array(object({
47298
+ settings: optional(array(object({
48198
47299
  name: string,
48199
47300
  value: string
48200
- })),
47301
+ }))),
48201
47302
  width: union(number).or(string).parser,
48202
47303
  height: union(number).or(string).parser,
48203
- allowFullScreen: boolean,
47304
+ allowFullScreen: defaulted(boolean, () => false),
48204
47305
  allowTopNavigation: optional(boolean),
48205
47306
  static: defaulted(boolean, () => false)
48206
47307
  }));
@@ -48273,10 +47374,10 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
48273
47374
 
48274
47375
  const pairOfNumbers$1 = pair(number, number);
48275
47376
  const stringOrEmpty = defaulted(string, () => "");
48276
- const parseFunctionType = constant("function");
47377
+ const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
48277
47378
  const parseFunctionElement = object({
48278
- type: parseFunctionType,
48279
- key: string,
47379
+ type: constant("function"),
47380
+ key: parseKey,
48280
47381
  options: object({
48281
47382
  value: string,
48282
47383
  funcName: string,
@@ -48287,10 +47388,9 @@ const parseFunctionElement = object({
48287
47388
  strokeWidth: number
48288
47389
  })
48289
47390
  });
48290
- const parseLabelType = constant("label");
48291
47391
  const parseLabelElement = object({
48292
- type: parseLabelType,
48293
- key: string,
47392
+ type: constant("label"),
47393
+ key: parseKey,
48294
47394
  options: object({
48295
47395
  label: string,
48296
47396
  color: string,
@@ -48298,10 +47398,9 @@ const parseLabelElement = object({
48298
47398
  coordY: string
48299
47399
  })
48300
47400
  });
48301
- const parseLineType = constant("line");
48302
47401
  const parseLineElement = object({
48303
- type: parseLineType,
48304
- key: string,
47402
+ type: constant("line"),
47403
+ key: parseKey,
48305
47404
  options: object({
48306
47405
  color: string,
48307
47406
  startX: string,
@@ -48313,10 +47412,9 @@ const parseLineElement = object({
48313
47412
  arrows: string
48314
47413
  })
48315
47414
  });
48316
- const parseMovableLineType = constant("movable-line");
48317
47415
  const parseMovableLineElement = object({
48318
- type: parseMovableLineType,
48319
- key: string,
47416
+ type: constant("movable-line"),
47417
+ key: parseKey,
48320
47418
  options: object({
48321
47419
  startX: string,
48322
47420
  startY: string,
@@ -48333,10 +47431,9 @@ const parseMovableLineElement = object({
48333
47431
  constraintYMax: string
48334
47432
  })
48335
47433
  });
48336
- const parseMovablePointType = constant("movable-point");
48337
47434
  const parseMovablePointElement = object({
48338
- type: parseMovablePointType,
48339
- key: string,
47435
+ type: constant("movable-point"),
47436
+ key: parseKey,
48340
47437
  options: object({
48341
47438
  startX: string,
48342
47439
  startY: string,
@@ -48350,10 +47447,9 @@ const parseMovablePointElement = object({
48350
47447
  constraintYMax: stringOrEmpty
48351
47448
  })
48352
47449
  });
48353
- const parseParametricType = constant("parametric");
48354
47450
  const parseParametricElement = object({
48355
- type: parseParametricType,
48356
- key: string,
47451
+ type: constant("parametric"),
47452
+ key: parseKey,
48357
47453
  options: object({
48358
47454
  x: string,
48359
47455
  y: string,
@@ -48364,20 +47460,18 @@ const parseParametricElement = object({
48364
47460
  strokeWidth: number
48365
47461
  })
48366
47462
  });
48367
- const parsePointType = constant("point");
48368
47463
  const parsePointElement = object({
48369
- type: parsePointType,
48370
- key: string,
47464
+ type: constant("point"),
47465
+ key: parseKey,
48371
47466
  options: object({
48372
47467
  color: string,
48373
47468
  coordX: string,
48374
47469
  coordY: string
48375
47470
  })
48376
47471
  });
48377
- const parseRectangleType = constant("rectangle");
48378
47472
  const parseRectangleElement = object({
48379
- type: parseRectangleType,
48380
- key: string,
47473
+ type: constant("rectangle"),
47474
+ key: parseKey,
48381
47475
  options: object({
48382
47476
  color: string,
48383
47477
  coordX: string,
@@ -48523,8 +47617,8 @@ const parseLockedLineType = object({
48523
47617
  points: pair(parseLockedPointType, parseLockedPointType),
48524
47618
  color: parseLockedFigureColor,
48525
47619
  lineStyle: parseLockedLineStyle,
48526
- showPoint1: boolean,
48527
- showPoint2: boolean,
47620
+ showPoint1: defaulted(boolean, () => false),
47621
+ showPoint2: defaulted(boolean, () => false),
48528
47622
  // TODO(benchristel): default labels to empty array?
48529
47623
  labels: optional(array(parseLockedLabelType)),
48530
47624
  ariaLabel: optional(string)
@@ -48571,7 +47665,7 @@ const parseLockedFunctionType = object({
48571
47665
  labels: optional(array(parseLockedLabelType)),
48572
47666
  ariaLabel: optional(string)
48573
47667
  });
48574
- const parseLockedFigure = union(parseLockedPointType).or(parseLockedLineType).or(parseLockedVectorType).or(parseLockedEllipseType).or(parseLockedPolygonType).or(parseLockedFunctionType).or(parseLockedLabelType).parser;
47668
+ const parseLockedFigure = discriminatedUnionOn("type").withBranch("point", parseLockedPointType).withBranch("line", parseLockedLineType).withBranch("vector", parseLockedVectorType).withBranch("ellipse", parseLockedEllipseType).withBranch("polygon", parseLockedPolygonType).withBranch("function", parseLockedFunctionType).withBranch("label", parseLockedLabelType).parser;
48575
47669
  const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
48576
47670
  step: pairOfNumbers,
48577
47671
  // TODO(benchristel): rather than making gridStep and snapStep
@@ -48639,7 +47733,14 @@ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matri
48639
47733
  }));
48640
47734
 
48641
47735
  const parseMeasurerWidget = parseWidget(constant("measurer"), object({
48642
- image: parsePerseusImageBackground,
47736
+ // The default value for image comes from measurer.tsx.
47737
+ // See parse-perseus-json/README.md for why we want to duplicate the
47738
+ // defaults here.
47739
+ image: defaulted(parsePerseusImageBackground, () => ({
47740
+ url: null,
47741
+ top: 0,
47742
+ left: 0
47743
+ })),
48643
47744
  showProtractor: boolean,
48644
47745
  showRuler: boolean,
48645
47746
  rulerLabel: string,
@@ -48750,9 +47851,15 @@ const parsePlotterWidget = parseWidget(constant("plotter"), object({
48750
47851
  categories: array(string),
48751
47852
  type: enumeration(...plotterPlotTypes),
48752
47853
  maxY: number,
48753
- scaleY: number,
47854
+ // The default value for scaleY comes from plotter.tsx.
47855
+ // See parse-perseus-json/README.md for why we want to duplicate the
47856
+ // defaults here.
47857
+ scaleY: defaulted(number, () => 1),
48754
47858
  labelInterval: optional(nullable(number)),
48755
- snapsPerLine: number,
47859
+ // The default value for snapsPerLine comes from plotter.tsx.
47860
+ // See parse-perseus-json/README.md for why we want to duplicate the
47861
+ // defaults here.
47862
+ snapsPerLine: defaulted(number, () => 2),
48756
47863
  starting: array(number),
48757
47864
  correct: array(number),
48758
47865
  picUrl: optional(nullable(string)),
@@ -48829,12 +47936,12 @@ const parseWidgetsMap = (rawValue, ctx) => {
48829
47936
  }
48830
47937
  return ctx.success(widgetsMap);
48831
47938
  };
48832
- const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
48833
- const keyComponentsResult = parseWidgetMapKeyComponents(key.split(" "), ctx);
48834
- if (isFailure(keyComponentsResult)) {
48835
- return keyComponentsResult;
47939
+ const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
47940
+ const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
47941
+ if (isFailure(idComponentsResult)) {
47942
+ return idComponentsResult;
48836
47943
  }
48837
- const [type, id] = keyComponentsResult.value;
47944
+ const [type, n] = idComponentsResult.value;
48838
47945
  function parseAndAssign(key, parse) {
48839
47946
  const widgetResult = parse(widget, ctx);
48840
47947
  if (isFailure(widgetResult)) {
@@ -48845,88 +47952,88 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
48845
47952
  }
48846
47953
  switch (type) {
48847
47954
  case "categorizer":
48848
- return parseAndAssign(`categorizer ${id}`, parseCategorizerWidget);
47955
+ return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
48849
47956
  case "cs-program":
48850
- return parseAndAssign(`cs-program ${id}`, parseCSProgramWidget);
47957
+ return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
48851
47958
  case "definition":
48852
- return parseAndAssign(`definition ${id}`, parseDefinitionWidget);
47959
+ return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
48853
47960
  case "dropdown":
48854
- return parseAndAssign(`dropdown ${id}`, parseDropdownWidget);
47961
+ return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
48855
47962
  case "explanation":
48856
- return parseAndAssign(`explanation ${id}`, parseExplanationWidget);
47963
+ return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
48857
47964
  case "expression":
48858
- return parseAndAssign(`expression ${id}`, parseExpressionWidget);
47965
+ return parseAndAssign(`expression ${n}`, parseExpressionWidget);
48859
47966
  case "grapher":
48860
- return parseAndAssign(`grapher ${id}`, parseGrapherWidget);
47967
+ return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
48861
47968
  case "group":
48862
- return parseAndAssign(`group ${id}`, parseGroupWidget);
47969
+ return parseAndAssign(`group ${n}`, parseGroupWidget);
48863
47970
  case "graded-group":
48864
- return parseAndAssign(`graded-group ${id}`, parseGradedGroupWidget);
47971
+ return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
48865
47972
  case "graded-group-set":
48866
- return parseAndAssign(`graded-group-set ${id}`, parseGradedGroupSetWidget);
47973
+ return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
48867
47974
  case "iframe":
48868
- return parseAndAssign(`iframe ${id}`, parseIframeWidget);
47975
+ return parseAndAssign(`iframe ${n}`, parseIframeWidget);
48869
47976
  case "image":
48870
- return parseAndAssign(`image ${id}`, parseImageWidget);
47977
+ return parseAndAssign(`image ${n}`, parseImageWidget);
48871
47978
  case "input-number":
48872
- return parseAndAssign(`input-number ${id}`, parseInputNumberWidget);
47979
+ return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
48873
47980
  case "interaction":
48874
- return parseAndAssign(`interaction ${id}`, parseInteractionWidget);
47981
+ return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
48875
47982
  case "interactive-graph":
48876
- return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
47983
+ return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
48877
47984
  case "label-image":
48878
- return parseAndAssign(`label-image ${id}`, parseLabelImageWidget);
47985
+ return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
48879
47986
  case "matcher":
48880
- return parseAndAssign(`matcher ${id}`, parseMatcherWidget);
47987
+ return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
48881
47988
  case "matrix":
48882
- return parseAndAssign(`matrix ${id}`, parseMatrixWidget);
47989
+ return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
48883
47990
  case "measurer":
48884
- return parseAndAssign(`measurer ${id}`, parseMeasurerWidget);
47991
+ return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
48885
47992
  case "molecule-renderer":
48886
- return parseAndAssign(`molecule-renderer ${id}`, parseMoleculeRendererWidget);
47993
+ return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
48887
47994
  case "number-line":
48888
- return parseAndAssign(`number-line ${id}`, parseNumberLineWidget);
47995
+ return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
48889
47996
  case "numeric-input":
48890
- return parseAndAssign(`numeric-input ${id}`, parseNumericInputWidget);
47997
+ return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
48891
47998
  case "orderer":
48892
- return parseAndAssign(`orderer ${id}`, parseOrdererWidget);
47999
+ return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
48893
48000
  case "passage":
48894
- return parseAndAssign(`passage ${id}`, parsePassageWidget);
48001
+ return parseAndAssign(`passage ${n}`, parsePassageWidget);
48895
48002
  case "passage-ref":
48896
- return parseAndAssign(`passage-ref ${id}`, parsePassageRefWidget);
48003
+ return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
48897
48004
  case "passage-ref-target":
48898
48005
  // NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
48899
48006
  // used in test content. See:
48900
48007
  // https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
48901
- return parseAndAssign(`passage-ref-target ${id}`, any);
48008
+ return parseAndAssign(`passage-ref-target ${n}`, any);
48902
48009
  case "phet-simulation":
48903
- return parseAndAssign(`phet-simulation ${id}`, parsePhetSimulationWidget);
48010
+ return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
48904
48011
  case "plotter":
48905
- return parseAndAssign(`plotter ${id}`, parsePlotterWidget);
48012
+ return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
48906
48013
  case "python-program":
48907
- return parseAndAssign(`python-program ${id}`, parsePythonProgramWidget);
48014
+ return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
48908
48015
  case "radio":
48909
- return parseAndAssign(`radio ${id}`, parseRadioWidget);
48016
+ return parseAndAssign(`radio ${n}`, parseRadioWidget);
48910
48017
  case "sorter":
48911
- return parseAndAssign(`sorter ${id}`, parseSorterWidget);
48018
+ return parseAndAssign(`sorter ${n}`, parseSorterWidget);
48912
48019
  case "table":
48913
- return parseAndAssign(`table ${id}`, parseTableWidget);
48020
+ return parseAndAssign(`table ${n}`, parseTableWidget);
48914
48021
  case "video":
48915
- return parseAndAssign(`video ${id}`, parseVideoWidget);
48022
+ return parseAndAssign(`video ${n}`, parseVideoWidget);
48916
48023
  case "sequence":
48917
48024
  // sequence is a deprecated widget type, and the corresponding
48918
48025
  // widget component no longer exists.
48919
- return parseAndAssign(`sequence ${id}`, parseDeprecatedWidget);
48026
+ return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
48920
48027
  case "lights-puzzle":
48921
- return parseAndAssign(`lights-puzzle ${id}`, parseDeprecatedWidget);
48028
+ return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
48922
48029
  case "simulator":
48923
- return parseAndAssign(`simulator ${id}`, parseDeprecatedWidget);
48030
+ return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
48924
48031
  case "transformer":
48925
- return parseAndAssign(`transformer ${id}`, parseDeprecatedWidget);
48032
+ return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
48926
48033
  default:
48927
48034
  if (getWidget(type)) {
48928
48035
  // @ts-expect-error - 'type' is not a valid widget type
48929
- return parseAndAssign(`${type} ${id}`, any);
48036
+ return parseAndAssign(`${type} ${n}`, any);
48930
48037
  }
48931
48038
  return ctx.failure("a valid widget type", type);
48932
48039
  }
@@ -48938,11 +48045,32 @@ const parseDeprecatedWidget = parseWidget(
48938
48045
  object({}));
48939
48046
  const parseStringToPositiveInt = (rawValue, ctx) => {
48940
48047
  if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
48941
- return ctx.failure("numeric string", rawValue);
48048
+ return ctx.failure("a string representing a positive integer", rawValue);
48942
48049
  }
48943
48050
  return ctx.success(+rawValue);
48944
48051
  };
48945
- const parseWidgetMapKeyComponents = pair(string, parseStringToPositiveInt);
48052
+ const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
48053
+
48054
+ const parsePerseusRenderer = defaulted(object({
48055
+ // TODO(benchristel): content is also defaulted to empty string in
48056
+ // renderer.tsx. See if we can remove one default or the other.
48057
+ content: defaulted(string, () => ""),
48058
+ // This module has an import cycle with parseWidgetsMap, because the
48059
+ // `group` widget can contain another renderer.
48060
+ // The anonymous function below ensures that we don't try to access
48061
+ // parseWidgetsMap before it's defined.
48062
+ widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
48063
+ metadata: optional(array(string)),
48064
+ images: parseImages
48065
+ }),
48066
+ // Default value
48067
+ () => ({
48068
+ content: "",
48069
+ widgets: {},
48070
+ images: {}
48071
+ }));
48072
+
48073
+ const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
48946
48074
 
48947
48075
  const parseHint = object({
48948
48076
  replace: optional(boolean),
@@ -48953,7 +48081,7 @@ const parseHint = object({
48953
48081
  });
48954
48082
 
48955
48083
  const _excluded = ["type", "options"];
48956
- object({
48084
+ const parsePerseusItem$1 = object({
48957
48085
  question: parsePerseusRenderer,
48958
48086
  hints: defaulted(array(parseHint), () => []),
48959
48087
  answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
@@ -48988,6 +48116,7 @@ function migrateAnswerArea(rawValue, ctx) {
48988
48116
  * Why not just use JSON.parse? We want:
48989
48117
  * - To make sure types are correct
48990
48118
  * - To give us a central place to validate/transform output if needed
48119
+ * @deprecated - use parseAndMigratePerseusItem instead
48991
48120
  * @param {string} json - the stringified PerseusItem JSON
48992
48121
  * @returns {PerseusItem} the parsed PerseusItem object
48993
48122
  */
@@ -48999,6 +48128,60 @@ function parsePerseusItem(json) {
48999
48128
  }
49000
48129
  throw new Error("Something went wrong.");
49001
48130
  }
48131
+ /**
48132
+ * Parses a PerseusItem from a JSON string, migrates old formats to the latest
48133
+ * schema, and runtime-typechecks the result. Use this to parse assessmentItem
48134
+ * data.
48135
+ *
48136
+ * @returns a {@link Result} of the parsed PerseusItem. If the result is a
48137
+ * failure, it will contain an error message describing where in the tree
48138
+ * parsing failed.
48139
+ * @throws SyntaxError if the argument is not well-formed JSON.
48140
+ */
48141
+ function parseAndMigratePerseusItem(json) {
48142
+ throwErrorIfCheatingDetected();
48143
+ const object = JSON.parse(json);
48144
+ const result = parse(object, parsePerseusItem$1);
48145
+ if (isFailure(result)) {
48146
+ return failure({
48147
+ message: result.detail,
48148
+ invalidObject: object
48149
+ });
48150
+ }
48151
+ return result;
48152
+ }
48153
+
48154
+ /**
48155
+ * Parses a PerseusArticle from a JSON string, migrates old formats to the
48156
+ * latest schema, and runtime-typechecks the result.
48157
+ *
48158
+ * @returns a {@link Result} of the parsed PerseusArticle. If the result is a
48159
+ * failure, it will contain an error message describing where in the tree
48160
+ * parsing failed.
48161
+ * @throws SyntaxError if the argument is not well-formed JSON.
48162
+ */
48163
+ function parseAndMigratePerseusArticle(json) {
48164
+ throwErrorIfCheatingDetected();
48165
+ const object = JSON.parse(json);
48166
+ const result = parse(object, parsePerseusArticle);
48167
+ if (isFailure(result)) {
48168
+ return failure({
48169
+ message: result.detail,
48170
+ invalidObject: object
48171
+ });
48172
+ }
48173
+ return result;
48174
+ }
48175
+
48176
+ /**
48177
+ * Tries to block a cheating vector that relies on monkey-patching JSON.parse.
48178
+ */
48179
+ // TODO(LEMS-2331): delete this function once server-side scoring is done.
48180
+ function throwErrorIfCheatingDetected() {
48181
+ if (!isRealJSONParse(JSON.parse)) {
48182
+ throw new Error("Something went wrong.");
48183
+ }
48184
+ }
49002
48185
 
49003
48186
  const genericPerseusItemData = {
49004
48187
  question: {
@@ -49218,5 +48401,5 @@ const ScoringUtil = {
49218
48401
  * Types
49219
48402
  */
49220
48403
 
49221
- 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, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, 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, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mathOnlyParser, mediaQueries, parseDataFromJSONP, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
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 };
49222
48405
  //# sourceMappingURL=index.js.map