@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/index.js CHANGED
@@ -13,6 +13,7 @@ var classNames$1 = require('classnames');
13
13
  var React = require('react');
14
14
  var ReactDOM = require('react-dom');
15
15
  var _ = require('underscore');
16
+ var perseusScore = require('@khanacademy/perseus-score');
16
17
  var Clickable = require('@khanacademy/wonder-blocks-clickable');
17
18
  var wonderBlocksPopover = require('@khanacademy/wonder-blocks-popover');
18
19
  var wonderBlocksTokens = require('@khanacademy/wonder-blocks-tokens');
@@ -20,9 +21,9 @@ var $ = require('jquery');
20
21
  var uuid = require('uuid');
21
22
  var perseusCore = require('@khanacademy/perseus-core');
22
23
  var PropTypes = require('prop-types');
23
- var kmath = require('@khanacademy/kmath');
24
24
  var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
25
25
  var wonderBlocksProgressSpinner = require('@khanacademy/wonder-blocks-progress-spinner');
26
+ var kmath = require('@khanacademy/kmath');
26
27
  var SimpleMarkdown = require('@khanacademy/simple-markdown');
27
28
  var pureMarkdown = require('@khanacademy/pure-markdown');
28
29
  var wonderBlocksForm = require('@khanacademy/wonder-blocks-form');
@@ -108,6 +109,183 @@ function _extends() {
108
109
  * The translated strings that are used to render Perseus.
109
110
  */
110
111
 
112
+ /**
113
+ * Untranslated strings used in Perseus. To be used by an external
114
+ * translator to produce translated strings, passed in as `PerseusStrings`.
115
+ * !! Note: Ensure that all escape sequences are double-escaped. (e.g. `\\text` -> `\\\\text`)
116
+ */
117
+ const strings = {
118
+ closeKeypad: "close math keypad",
119
+ openKeypad: "open math keypad",
120
+ mathInputBox: "Math input box",
121
+ removeHighlight: "Remove highlight",
122
+ addHighlight: "Add highlight",
123
+ hintPos: "Hint #%(pos)s",
124
+ errorRendering: "Error rendering: %(error)s",
125
+ 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",
126
+ EXTRA_SYMBOLS_ERROR: "We could not understand your " + "answer. Please check your answer for extra " + "text or symbols.",
127
+ NEEDS_TO_BE_SIMPLFIED_ERROR: "Your answer is almost correct, " + "but it needs to be simplified.",
128
+ MISSING_PERCENT_ERROR: "Your answer is almost correct, " + "but it is missing a " + "<code>\\\\%</code> at the end.",
129
+ MULTIPLICATION_SIGN_ERROR: "I'm a computer. I only understand " + "multiplication if you use an asterisk " + "(*) as the multiplication sign.",
130
+ WRONG_CASE_ERROR: "Your answer includes use of a variable with the wrong case.",
131
+ WRONG_LETTER_ERROR: "Your answer includes a wrong variable letter.",
132
+ invalidSelection: "Make sure you select something for every row.",
133
+ ERROR_TITLE: "Oops!",
134
+ ERROR_MESSAGE: "Sorry, I don't understand that!",
135
+ hints: "Hints",
136
+ getAnotherHint: "Get another hint",
137
+ deprecatedStandin: "Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!",
138
+ keepTrying: "Keep trying",
139
+ tryAgain: "Try again",
140
+ check: "Check",
141
+ correctExcited: "Correct!",
142
+ nextQuestion: "Next question",
143
+ skipToTitle: "Skip to %(title)s",
144
+ current: "Current",
145
+ correct: "Correct",
146
+ correctSelected: "Correct (selected)",
147
+ correctCrossedOut: "Correct (but you crossed it out)",
148
+ incorrect: "Incorrect",
149
+ incorrectSelected: "Incorrect (selected)",
150
+ hideExplanation: "Hide explanation",
151
+ explain: "Explain",
152
+ INVALID_MESSAGE_PREFIX: "We couldn't grade your answer.",
153
+ DEFAULT_INVALID_MESSAGE_1: "It looks like you left something blank or ",
154
+ DEFAULT_INVALID_MESSAGE_2: "entered in an invalid answer.",
155
+ integerExample: "an integer, like $6$",
156
+ properExample: "a *proper* fraction, like $1/2$ or $6/10$",
157
+ simplifiedProperExample: "a *simplified proper* fraction, like $3/5$",
158
+ improperExample: "an *improper* fraction, like $10/7$ or $14/8$",
159
+ simplifiedImproperExample: "a *simplified improper* fraction, like $7/4$",
160
+ mixedExample: "a mixed number, like $1\\\\ 3/4$",
161
+ decimalExample: "an *exact* decimal, like $0.75$",
162
+ percentExample: "a percent, like $12.34\\\\%$",
163
+ piExample: "a multiple of pi, like $12$ pi or $2/3$ pi",
164
+ yourAnswer: "**Your answer should be** ",
165
+ yourAnswerLabel: "Your answer:",
166
+ addPoints: "Click to add points",
167
+ addVertices: "Click to add vertices",
168
+ tapMultiple: "Tap each dot on the image to select all answers that apply.",
169
+ tapSingle: "Tap each dot on the image to select an answer.",
170
+ clickMultiple: "Click each dot on the image to select all answers that apply.",
171
+ clickSingle: "Click each dot on the image to select an answer.",
172
+ choices: "Choices:",
173
+ answers: {
174
+ one: "%(num)s answer",
175
+ other: "%(num)s answers"
176
+ },
177
+ hideAnswersToggleLabel: "Hide answer choices",
178
+ moves: {
179
+ one: "Moves: %(num)s",
180
+ other: "Moves: %(num)s"
181
+ },
182
+ clickTiles: "Click on the tiles to change the lights.",
183
+ turnOffLights: "You must turn on all of the lights to continue.",
184
+ fillAllCells: "Make sure you fill in all cells in the matrix.",
185
+ molecularDrawing: "A molecular structure drawing. SMILES notation: %(content)s",
186
+ switchDirection: "Switch direction",
187
+ circleOpen: "Make circle open",
188
+ circleFilled: "Make circle filled",
189
+ numDivisions: "Number of divisions:",
190
+ divisions: "Please make sure the number of divisions is in the range %(divRangeString)s.",
191
+ lineRange: "lines %(lineRange)s",
192
+ lineNumber: "line %(lineNumber)s",
193
+ symbolPassage: "The symbol %(questionSymbol)s indicates that question %(questionNumber)s references this portion of the passage.",
194
+ symbolQuestion: " The symbol %(sentenceSymbol)s indicates that the following sentence is referenced in a question.",
195
+ lineLabel: {
196
+ context: "a label next to a reading passage to denote the line number",
197
+ message: "Line"
198
+ },
199
+ beginningPassage: "Beginning of reading passage.",
200
+ beginningFootnotes: "Beginning of reading passage footnotes.",
201
+ endPassage: "End of reading passage.",
202
+ questionMarker: "[Marker for question %(number)s]",
203
+ circleMarker: "[Circle marker %(number)s]",
204
+ sentenceMarker: "[Sentence %(number)s]",
205
+ dragHandles: "Drag handles to make graph",
206
+ tapAddPoints: "Tap to add points",
207
+ false: "False",
208
+ true: "True",
209
+ no: "No",
210
+ yes: "Yes",
211
+ chooseCorrectNum: "Please choose the correct number of answers.",
212
+ notNoneOfTheAbove: "'None of the above' may not be selected when other answers are selected.",
213
+ noneOfTheAbove: "None of the above",
214
+ chooseNumAnswers: "Choose %(numCorrect)s answers:",
215
+ chooseAllAnswers: "Choose all answers that apply:",
216
+ chooseOneAnswer: "Choose 1 answer:",
217
+ choiceCheckedCorrect: "(Choice %(letter)s, Checked, Correct)",
218
+ choiceCrossedOutCorrect: "(Choice %(letter)s, Crossed out, Correct)",
219
+ choiceCorrect: "(Choice %(letter)s, Correct)",
220
+ choiceCheckedIncorrect: "(Choice %(letter)s, Checked, Incorrect)",
221
+ choiceCrossedOutIncorrect: "(Choice %(letter)s, Crossed out, Incorrect)",
222
+ choiceIncorrect: "(Choice %(letter)s, Incorrect)",
223
+ choiceChecked: "(Choice %(letter)s, Checked)",
224
+ choiceCrossedOut: "(Choice %(letter)s, Crossed out)",
225
+ choice: "(Choice %(letter)s)",
226
+ crossOut: "Cross out",
227
+ crossOutOption: "Cross out option",
228
+ crossOutChoice: "Cross out Choice %(letter)s",
229
+ bringBack: "Bring back",
230
+ openMenuForChoice: "Open menu for Choice %(letter)s",
231
+ letters: {
232
+ 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.",
233
+ 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"
234
+ },
235
+ rightArrow: "Reaction arrow pointing to the right.",
236
+ dontUnderstandUnits: "I couldn't understand those units.",
237
+ checkSigFigs: "Check your significant figures.",
238
+ answerNumericallyIncorrect: "That answer is numerically incorrect.",
239
+ checkUnits: "Check your units.",
240
+ dontUnderstand: "I don't understand that",
241
+ loading: "Loading...",
242
+ videoTranscript: "See video transcript",
243
+ somethingWrong: "Something went wrong.",
244
+ videoWrapper: "Khan Academy video wrapper",
245
+ mathInputTitle: "mathematics keyboard",
246
+ mathInputDescription: "Use keyboard/mouse to interact with math-based input fields",
247
+ sin: "sin",
248
+ cos: "cos",
249
+ tan: "tan",
250
+ simulationLoadFail: "Sorry, this simulation cannot load.",
251
+ simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
252
+ selectAnAnswer: "Select an answer",
253
+ // The following strings are used for interactive graph SR descriptions.
254
+ addPoint: "Add Point",
255
+ removePoint: "Remove Point",
256
+ graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
257
+ closePolygon: "Close shape",
258
+ openPolygon: "Re-open shape",
259
+ srInteractiveElements: "Interactive elements: %(elements)s",
260
+ srNoInteractiveElements: "No interactive elements",
261
+ // TODO(LEMS-2660): The following strings are ones that will need
262
+ // translation tickets after all interactive graph SR strings have
263
+ // been finalized. Remove this comment after the tickets have been
264
+ // created.
265
+ srPointAtCoordinates: "Point %(num)s at %(x)s comma %(y)s",
266
+ srCircleGraph: "A circle on a coordinate plane.",
267
+ srCircleShape: "Circle. The center point is at %(centerX)s comma %(centerY)s.",
268
+ srCircleRadiusPoint: "Radius point at %(radiusPointX)s comma %(radiusPointY)s.",
269
+ srCircleRadius: "Circle radius is %(radius)s.",
270
+ 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.",
271
+ srLinearGraph: "A line on a coordinate plane.",
272
+ srLinearGraphPoints: "The line has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
273
+ srLinearGraphSlopeIncreasing: "Its slope increases from left to right.",
274
+ srLinearGraphSlopeDecreasing: "Its slope decreases from left to right.",
275
+ srLinearGraphSlopeHorizontal: "Its slope is zero.",
276
+ srLinearGraphSlopeVertical: "Its slope is undefined.",
277
+ srLinearGraphXOnlyIntercept: "The line crosses the X-axis at %(xIntercept)s comma 0.",
278
+ srLinearGraphYOnlyIntercept: "The line crosses the Y-axis at 0 comma %(yIntercept)s.",
279
+ srLinearGraphBothIntercepts: "The line crosses the X-axis at %(xIntercept)s comma 0 and the Y-axis at 0 comma %(yIntercept)s.",
280
+ srLinearGraphOriginIntercept: "The line crosses the x and y axes at the graph's origin.",
281
+ srLinearGrabHandle: "Line from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.",
282
+ srAngleSideAtCoordinates: "Point %(point)s, %(side)s at %(x)s comma %(y)s",
283
+ srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
284
+ srAngleGraphAriaLabel: "An angle on a coordinate plane.",
285
+ 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"
286
+ // The above strings are used for interactive graph SR descriptions.
287
+ };
288
+
111
289
  /**
112
290
  * Mock strings for the Perseus package, to be used for tests and Storybook.
113
291
  */
@@ -497,6 +675,16 @@ const mockStrings = {
497
675
  // The above strings are used for interactive graph SR descriptions.
498
676
  };
499
677
 
678
+ ({
679
+ [perseusScore.ErrorCodes.MISSING_PERCENT_ERROR]: strings.MISSING_PERCENT_ERROR,
680
+ [perseusScore.ErrorCodes.NEEDS_TO_BE_SIMPLIFIED_ERROR]: strings.NEEDS_TO_BE_SIMPLFIED_ERROR,
681
+ [perseusScore.ErrorCodes.APPROXIMATED_PI_ERROR]: strings.APPROXIMATED_PI_ERROR,
682
+ [perseusScore.ErrorCodes.EXTRA_SYMBOLS_ERROR]: strings.EXTRA_SYMBOLS_ERROR,
683
+ [perseusScore.ErrorCodes.WRONG_CASE_ERROR]: strings.WRONG_CASE_ERROR,
684
+ [perseusScore.ErrorCodes.WRONG_LETTER_ERROR]: strings.WRONG_LETTER_ERROR,
685
+ [perseusScore.ErrorCodes.MULTIPLICATION_SIGN_ERROR]: strings.MULTIPLICATION_SIGN_ERROR
686
+ });
687
+
500
688
  /**
501
689
  * PerseusI18nContext provides a way to set the strings and locale that are
502
690
  * used inside the Perseus package.
@@ -1464,918 +1652,6 @@ const Log = {
1464
1652
  }
1465
1653
  };
1466
1654
 
1467
- const KhanMath = {
1468
- // Simplify formulas before display
1469
- cleanMath: function (expr) {
1470
- return typeof expr === "string" ? expr.replace(/\+\s*-/g, "- ").replace(/-\s*-/g, "+ ").replace(/\^1/g, "") : expr;
1471
- },
1472
- // Bound a number by 1e-6 and 1e20 to avoid exponents after toString
1473
- bound: function (num) {
1474
- if (num === 0) {
1475
- return num;
1476
- }
1477
- if (num < 0) {
1478
- return -KhanMath.bound(-num);
1479
- }
1480
- return Math.max(1e-6, Math.min(num, 1e20));
1481
- },
1482
- factorial: function (x) {
1483
- if (x <= 1) {
1484
- return x;
1485
- }
1486
- return x * KhanMath.factorial(x - 1);
1487
- },
1488
- getGCD: function (a, b) {
1489
- if (arguments.length > 2) {
1490
- // TODO(kevinb): rewrite using rest args instead of arguments
1491
- // eslint-disable-next-line prefer-rest-params
1492
- const rest = [].slice.call(arguments, 1);
1493
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
1494
- return KhanMath.getGCD(a, KhanMath.getGCD(...rest));
1495
- }
1496
- let mod;
1497
- a = Math.abs(a);
1498
- b = Math.abs(b);
1499
- while (b) {
1500
- mod = a % b;
1501
- a = b;
1502
- b = mod;
1503
- }
1504
- return a;
1505
- },
1506
- getLCM: function (a, b) {
1507
- if (arguments.length > 2) {
1508
- // TODO(kevinb): rewrite using rest args instead of arguments
1509
- // eslint-disable-next-line prefer-rest-params
1510
- const rest = [].slice.call(arguments, 1);
1511
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
1512
- return KhanMath.getLCM(a, KhanMath.getLCM(...rest));
1513
- }
1514
- return Math.abs(a * b) / KhanMath.getGCD(a, b);
1515
- },
1516
- 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],
1517
- isPrime: function (n) {
1518
- if (n <= 1) {
1519
- return false;
1520
- }
1521
- if (n < 101) {
1522
- return !!$__default["default"].grep(KhanMath.primes, function (p, i) {
1523
- return Math.abs(p - n) <= 0.5;
1524
- }).length;
1525
- }
1526
- if (n <= 1 || n > 2 && n % 2 === 0) {
1527
- return false;
1528
- }
1529
- for (let i = 3, sqrt = Math.sqrt(n); i <= sqrt; i += 2) {
1530
- if (n % i === 0) {
1531
- return false;
1532
- }
1533
- }
1534
- return true;
1535
- },
1536
- // @ts-expect-error - TS2366 - Function lacks ending return statement and return type does not include 'undefined'.
1537
- getPrimeFactorization: function (number) {
1538
- if (number === 1) {
1539
- return [];
1540
- }
1541
- if (KhanMath.isPrime(number)) {
1542
- return [number];
1543
- }
1544
- const maxf = Math.sqrt(number);
1545
- for (let f = 2; f <= maxf; f++) {
1546
- if (number % f === 0) {
1547
- return $__default["default"].merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
1548
- }
1549
- }
1550
- },
1551
- // Round a number to the nearest increment
1552
- // E.g., if increment = 30 and num = 40, return 30. if increment = 30 and
1553
- // num = 45, return 60.
1554
- roundToNearest: function (increment, num) {
1555
- return Math.round(num / increment) * increment;
1556
- },
1557
- // Round a number to a certain number of decimal places
1558
- roundTo: function (precision, num) {
1559
- const factor = Math.pow(10, precision).toFixed(5);
1560
- // @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.
1561
- return Math.round((num * factor).toFixed(5)) / factor;
1562
- },
1563
- /**
1564
- * Return a string of num rounded to a fixed precision decimal places,
1565
- * with an approx symbol if num had to be rounded, and trailing 0s
1566
- */
1567
- toFixedApprox: function (num, precision) {
1568
- // TODO(aria): Make this locale-dependent
1569
- const fixedStr = num.toFixed(precision);
1570
- if (kmath.number.equal(+fixedStr, num)) {
1571
- return fixedStr;
1572
- }
1573
- return "\\approx " + fixedStr;
1574
- },
1575
- /**
1576
- * Return a string of num rounded to precision decimal places, with an
1577
- * approx symbol if num had to be rounded, but no trailing 0s if it was
1578
- * not rounded.
1579
- */
1580
- roundToApprox: function (num, precision) {
1581
- const fixed = KhanMath.roundTo(precision, num);
1582
- if (kmath.number.equal(fixed, num)) {
1583
- return String(fixed);
1584
- }
1585
- return KhanMath.toFixedApprox(num, precision);
1586
- },
1587
- // toFraction(4/8) => [1, 2]
1588
- // toFraction(0.666) => [333, 500]
1589
- // toFraction(0.666, 0.001) => [2, 3]
1590
- //
1591
- // tolerance can't be bigger than 1, sorry
1592
- toFraction: function (decimal, tolerance) {
1593
- if (tolerance == null) {
1594
- tolerance = Math.pow(2, -46);
1595
- }
1596
- if (decimal < 0 || decimal > 1) {
1597
- let fract = decimal % 1;
1598
- fract += fract < 0 ? 1 : 0;
1599
- const nd = KhanMath.toFraction(fract, tolerance);
1600
- nd[0] += Math.round(decimal - fract) * nd[1];
1601
- return nd;
1602
- }
1603
- if (Math.abs(Math.round(Number(decimal)) - decimal) <= tolerance) {
1604
- return [Math.round(decimal), 1];
1605
- }
1606
- let loN = 0;
1607
- let loD = 1;
1608
- let hiN = 1;
1609
- let hiD = 1;
1610
- let midN = 1;
1611
- let midD = 2;
1612
-
1613
- // eslint-disable-next-line no-constant-condition
1614
- while (true) {
1615
- if (Math.abs(Number(midN / midD) - decimal) <= tolerance) {
1616
- return [midN, midD];
1617
- }
1618
- if (midN / midD < decimal) {
1619
- loN = midN;
1620
- loD = midD;
1621
- } else {
1622
- hiN = midN;
1623
- hiD = midD;
1624
- }
1625
- midN = loN + hiN;
1626
- midD = loD + hiD;
1627
- }
1628
- },
1629
- // Returns the format (string) of a given numeric string
1630
- // Note: purposively more inclusive than answer-types' predicate.forms
1631
- // That is, it is not necessarily true that interpreted input are numeric
1632
- getNumericFormat: function (text) {
1633
- text = $__default["default"].trim(text);
1634
- text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
1635
- if (text.match(/^[+-]?\d+$/)) {
1636
- return "integer";
1637
- }
1638
- if (text.match(/^[+-]?\d+\s+\d+\s*\/\s*\d+$/)) {
1639
- return "mixed";
1640
- }
1641
- const fraction = text.match(/^[+-]?(\d+)\s*\/\s*(\d+)$/);
1642
- if (fraction) {
1643
- return parseFloat(fraction[1]) > parseFloat(fraction[2]) ? "improper" : "proper";
1644
- }
1645
- if (text.replace(/[,. ]/g, "").match(/^\d+$/)) {
1646
- return "decimal";
1647
- }
1648
- if (text.match(/(pi?|\u03c0|t(?:au)?|\u03c4|pau)/)) {
1649
- return "pi";
1650
- }
1651
- return null;
1652
- },
1653
- // Returns a string of the number in a specified format
1654
- toNumericString: function (number, format) {
1655
- if (number == null) {
1656
- return "";
1657
- }
1658
- if (number === 0) {
1659
- return "0"; // otherwise it might end up as 0% or 0pi
1660
- }
1661
-
1662
- if (format === "percent") {
1663
- return number * 100 + "%";
1664
- }
1665
- if (format === "pi") {
1666
- const fraction = kmath.number.toFraction(number / Math.PI);
1667
- const numerator = Math.abs(fraction[0]);
1668
- const denominator = fraction[1];
1669
- if (kmath.number.isInteger(numerator)) {
1670
- const sign = number < 0 ? "-" : "";
1671
- const pi = "\u03C0";
1672
- return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
1673
- }
1674
- }
1675
- if (___default["default"](["proper", "improper", "mixed", "fraction"]).contains(format)) {
1676
- const fraction = kmath.number.toFraction(number);
1677
- const numerator = Math.abs(fraction[0]);
1678
- const denominator = fraction[1];
1679
- const sign = number < 0 ? "-" : "";
1680
- if (denominator === 1) {
1681
- return sign + numerator; // for integers, irrational, d > 1000
1682
- }
1683
-
1684
- if (format === "mixed") {
1685
- const modulus = numerator % denominator;
1686
- const integer = (numerator - modulus) / denominator;
1687
- return sign + (integer ? integer + " " : "") + modulus + "/" + denominator;
1688
- } // otherwise proper, improper, or fraction
1689
- return sign + numerator + "/" + denominator;
1690
- }
1691
-
1692
- // otherwise (decimal, float, long long)
1693
- return String(number);
1694
- }
1695
- };
1696
- function sum$1(array) {
1697
- return array.reduce(add$3, 0);
1698
- }
1699
- function add$3(a, b) {
1700
- return a + b;
1701
- }
1702
-
1703
- /* eslint-disable no-useless-escape */
1704
- const MAXERROR_EPSILON = Math.pow(2, -42);
1705
-
1706
- // TOOD(kevinb): Figure out how this relates to KEScore in
1707
- // perseus-all-package/types.js and see if there's a way to
1708
- // unify these types.
1709
- /**
1710
- * Answer types
1711
- *
1712
- * Utility for creating answerable questions displayed in exercises
1713
- *
1714
- * Different answer types produce different kinds of input displays, and do
1715
- * different kinds of checking on the solutions.
1716
- *
1717
- * Each of the objects contain two functions, setup and createValidator.
1718
- *
1719
- * The setup function takes a solutionarea and solution, and performs setup
1720
- * within the solutionarea, and then returns an object which contains:
1721
- *
1722
- * answer: a function which, when called, will retrieve the current answer from
1723
- * the solutionarea, which can then be validated using the validator
1724
- * function
1725
- * validator: a function returned from the createValidator function (defined
1726
- * below)
1727
- * solution: the correct answer to the problem
1728
- * showGuess: a function which, when given a guess, shows the guess within the
1729
- * provided solutionarea
1730
- * showGuessCustom: a function which displays parts of a guess that are not
1731
- * within the solutionarea; currently only used for custom
1732
- * answers
1733
- *
1734
- * The createValidator function only takes a solution, and it returns a
1735
- * function which can be used to validate an answer.
1736
- *
1737
- * The resulting validator function returns:
1738
- * - true: if the answer is fully correct
1739
- * - false: if the answer is incorrect
1740
- * - "" (the empty string): if no answer has been provided (e.g. the answer box
1741
- * is left unfilled)
1742
- * - a string: if there is some slight error
1743
- *
1744
- * In most cases, setup and createValidator don't really need the solution DOM
1745
- * element so we have setupFunctional and createValidatorFunctional for them
1746
- * which take only $solution.text() and $solution.data(). This makes it easier
1747
- * to reuse specific answer types.
1748
- *
1749
- * TODO(alpert): Think of a less-absurd name for createValidatorFunctional.
1750
- *
1751
- */
1752
- const KhanAnswerTypes = {
1753
- /*
1754
- * predicate answer type
1755
- *
1756
- * performs simple predicate-based checking of a numeric solution, with
1757
- * different kinds of number formats
1758
- *
1759
- * Uses the data-forms option on the solution to choose which number formats
1760
- * are acceptable. Available data-forms:
1761
- *
1762
- * - integer: 3
1763
- * - proper: 3/5
1764
- * - improper: 5/3
1765
- * - pi: 3 pi
1766
- * - log: log(5)
1767
- * - percent: 15%
1768
- * - mixed: 1 1/3
1769
- * - decimal: 1.7
1770
- *
1771
- * The solution should be a predicate of the form:
1772
- *
1773
- * function(guess, maxError) {
1774
- * return abs(guess - 3) < maxError;
1775
- * }
1776
- *
1777
- */
1778
- predicate: {
1779
- defaultForms: "integer, proper, improper, mixed, decimal",
1780
- createValidatorFunctional: function (predicate, options, strings) {
1781
- // Extract the options from the given solution object
1782
- options = ___default["default"].extend({
1783
- simplify: "required",
1784
- ratio: false,
1785
- forms: KhanAnswerTypes.predicate.defaultForms
1786
- }, options);
1787
- let acceptableForms;
1788
- // this is maintaining backwards compatibility
1789
- // TODO(merlob) fix all places that depend on this, then delete
1790
- if (!___default["default"].isArray(options.forms)) {
1791
- acceptableForms = options.forms.split(/\s*,\s*/);
1792
- } else {
1793
- acceptableForms = options.forms;
1794
- }
1795
-
1796
- // TODO(jack): remove options.inexact in favor of options.maxError
1797
- if (options.inexact === undefined) {
1798
- // If we aren't allowing inexact, ensure that we don't have a
1799
- // large maxError as well.
1800
- options.maxError = 0;
1801
- }
1802
- // Allow a small tolerance on maxError, to avoid numerical
1803
- // representation issues (2.3 should be correct for a solution of
1804
- // 2.45 with maxError=0.15).
1805
- options.maxError = +options.maxError + MAXERROR_EPSILON;
1806
-
1807
- // If percent is an acceptable form, make sure it's the last one
1808
- // in the list so we don't prematurely complain about not having
1809
- // a percent sign when the user entered the correct answer in a
1810
- // different form (such as a decimal or fraction)
1811
- if (___default["default"].contains(acceptableForms, "percent")) {
1812
- acceptableForms = ___default["default"].without(acceptableForms, "percent");
1813
- acceptableForms.push("percent");
1814
- }
1815
-
1816
- // Take text looking like a fraction, and turn it into a number
1817
- const fractionTransformer = function (text) {
1818
- text = text
1819
- // Replace unicode minus sign with hyphen
1820
- .replace(/\u2212/, "-")
1821
- // Remove space after +, -
1822
- .replace(/([+-])\s+/g, "$1")
1823
- // Remove leading/trailing whitespace
1824
- .replace(/(^\s*)|(\s*$)/gi, "");
1825
-
1826
- // Extract numerator and denominator
1827
- const match = text.match(/^([+-]?\d+)\s*\/\s*([+-]?\d+)$/);
1828
- // Fractions are represented as "-\frac{numerator}{denominator}"
1829
- // in Mobile device input instead of "numerator/denominator" as
1830
- // in web-browser.
1831
- const mobileDeviceMatch = text.match(/^([+-]?)\\frac\{([+-]?\d+)\}\{([+-]?\d+)\}$/);
1832
- const parsedInt = parseInt(text, 10);
1833
- if (match || mobileDeviceMatch) {
1834
- let num;
1835
- let denom;
1836
- let simplified = true;
1837
- if (match) {
1838
- num = parseFloat(match[1]);
1839
- denom = parseFloat(match[2]);
1840
- } else {
1841
- num = parseFloat(mobileDeviceMatch[2]);
1842
- if (mobileDeviceMatch[1] === "-") {
1843
- if (num < 0) {
1844
- simplified = false;
1845
- }
1846
- num = -num;
1847
- }
1848
- denom = parseFloat(mobileDeviceMatch[3]);
1849
- }
1850
- simplified = simplified && denom > 0 && (options.ratio || denom !== 1) && KhanMath.getGCD(num, denom) === 1;
1851
- return [{
1852
- value: num / denom,
1853
- exact: simplified
1854
- }];
1855
- }
1856
- if (!isNaN(parsedInt) && "" + parsedInt === text) {
1857
- return [{
1858
- value: parsedInt,
1859
- exact: true
1860
- }];
1861
- }
1862
- return [];
1863
- };
1864
-
1865
- /*
1866
- * Different forms of numbers
1867
- *
1868
- * Each function returns a list of objects of the form:
1869
- *
1870
- * {
1871
- * value: numerical value,
1872
- * exact: true/false
1873
- * }
1874
- */
1875
- const forms = {
1876
- // integer, which is encompassed by decimal
1877
- integer: function (text) {
1878
- // Compare the decimal form to the decimal form rounded to
1879
- // an integer. Only accept if the user actually entered an
1880
- // integer.
1881
- const decimal = forms.decimal(text);
1882
- const rounded = forms.decimal(text, 1);
1883
- if (decimal[0].value != null && decimal[0].value === rounded[0].value || decimal[1].value != null && decimal[1].value === rounded[1].value) {
1884
- return decimal;
1885
- }
1886
- return [];
1887
- },
1888
- // A proper fraction
1889
- proper: function (text) {
1890
- return $__default["default"].map(fractionTransformer(text), function (o) {
1891
- // All fractions that are less than 1
1892
- if (Math.abs(o.value) < 1) {
1893
- return [o];
1894
- }
1895
- return [];
1896
- });
1897
- },
1898
- // an improper fraction
1899
- improper: function (text) {
1900
- return $__default["default"].map(fractionTransformer(text), function (o) {
1901
- // All fractions that are greater than 1
1902
- if (Math.abs(o.value) >= 1) {
1903
- return [o];
1904
- }
1905
- return [];
1906
- });
1907
- },
1908
- // pi-like numbers
1909
- pi: function (text) {
1910
- let match;
1911
- let possibilities = [];
1912
-
1913
- // Replace unicode minus sign with hyphen
1914
- text = text.replace(/\u2212/, "-");
1915
-
1916
- // - pi
1917
- // (Note: we also support \pi (for TeX), p, tau (and \tau,
1918
- // and t), pau.)
1919
- if (match = text.match(/^([+-]?)\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
1920
- possibilities = [{
1921
- value: parseFloat(match[1] + "1"),
1922
- exact: true
1923
- }];
1924
-
1925
- // 5 / 6 pi
1926
- } else if (match = text.match(/^([+-]?\s*\d+\s*(?:\/\s*[+-]?\s*\d+)?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
1927
- possibilities = fractionTransformer(match[1]);
1928
-
1929
- // 4 5 / 6 pi
1930
- } else if (match = text.match(/^([+-]?)\s*(\d+)\s*([+-]?\d+)\s*\/\s*([+-]?\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
1931
- const sign = parseFloat(match[1] + "1");
1932
- const integ = parseFloat(match[2]);
1933
- const num = parseFloat(match[3]);
1934
- const denom = parseFloat(match[4]);
1935
- const simplified = num < denom && KhanMath.getGCD(num, denom) === 1;
1936
- possibilities = [{
1937
- value: sign * (integ + num / denom),
1938
- exact: simplified
1939
- }];
1940
-
1941
- // 5 pi / 6
1942
- } else if (match = text.match(/^([+-]?\s*\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\s*\d+))?$/i)) {
1943
- possibilities = fractionTransformer(match[1] + "/" + match[3]);
1944
-
1945
- // - pi / 4
1946
- } else if (match = text.match(/^([+-]?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\d+))?$/i)) {
1947
- possibilities = fractionTransformer(match[1] + "1/" + match[3]);
1948
-
1949
- // 0
1950
- } else if (text === "0") {
1951
- possibilities = [{
1952
- value: 0,
1953
- exact: true
1954
- }];
1955
-
1956
- // 0.5 pi (fallback)
1957
- } else if (match = text.match(/^(.+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
1958
- possibilities = forms.decimal(match[1]);
1959
- } else {
1960
- possibilities = ___default["default"].reduce(KhanAnswerTypes.predicate.defaultForms.split(/\s*,\s*/), function (memo, form) {
1961
- return memo.concat(forms[form](text));
1962
- }, []);
1963
-
1964
- // If the answer is a floating point number that's
1965
- // near a multiple of pi, mark is as being possibly
1966
- // an approximation of pi. We actually check if
1967
- // it's a plausible approximation of pi/12, since
1968
- // sometimes the correct answer is like pi/3 or pi/4.
1969
- // We also say it's a pi-approximation if it involves
1970
- // x/7 (since 22/7 is an approximation of pi.)
1971
- // Never mark an integer as being an approximation
1972
- // of pi.
1973
- let approximatesPi = false;
1974
- const number = parseFloat(text);
1975
- if (!isNaN(number) && number !== parseInt(text)) {
1976
- const piMult = Math.PI / 12;
1977
- const roundedNumber = piMult * Math.round(number / piMult);
1978
- if (Math.abs(number - roundedNumber) < 0.01) {
1979
- approximatesPi = true;
1980
- }
1981
- } else if (text.match(/\/\s*7/)) {
1982
- approximatesPi = true;
1983
- }
1984
- if (approximatesPi) {
1985
- ___default["default"].each(possibilities, function (possibility) {
1986
- possibility.piApprox = true;
1987
- });
1988
- }
1989
- return possibilities;
1990
- }
1991
- let multiplier = Math.PI;
1992
- if (text.match(/\\?tau|t|\u03c4/)) {
1993
- multiplier = Math.PI * 2;
1994
- }
1995
-
1996
- // We're taking an early stand along side xkcd in the
1997
- // inevitable ti vs. pau debate... http://xkcd.com/1292
1998
- if (text.match(/pau/)) {
1999
- multiplier = Math.PI * 1.5;
2000
- }
2001
- $__default["default"].each(possibilities, function (ix, possibility) {
2002
- possibility.value *= multiplier;
2003
- });
2004
- return possibilities;
2005
- },
2006
- // Converts '' to 1 and '-' to -1 so you can write "[___] x"
2007
- // and accept sane things
2008
- coefficient: function (text) {
2009
- let possibilities = [];
2010
-
2011
- // Replace unicode minus sign with hyphen
2012
- text = text.replace(/\u2212/, "-");
2013
- if (text === "") {
2014
- possibilities = [{
2015
- value: 1,
2016
- exact: true
2017
- }];
2018
- } else if (text === "-") {
2019
- possibilities = [{
2020
- value: -1,
2021
- exact: true
2022
- }];
2023
- }
2024
- return possibilities;
2025
- },
2026
- // simple log(c) form
2027
- log: function (text) {
2028
- let match;
2029
- let possibilities = [];
2030
-
2031
- // Replace unicode minus sign with hyphen
2032
- text = text.replace(/\u2212/, "-");
2033
- text = text.replace(/[ \(\)]/g, "");
2034
- if (match = text.match(/^log\s*(\S+)\s*$/i)) {
2035
- // @ts-expect-error - TS2322 - Type '{ value: number | undefined; exact: boolean; }[]' is not assignable to type 'never[]'.
2036
- possibilities = forms.decimal(match[1]);
2037
- } else if (text === "0") {
2038
- // @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'. | TS2322 - Type 'boolean' is not assignable to type 'never'.
2039
- possibilities = [{
2040
- value: 0,
2041
- exact: true
2042
- }];
2043
- }
2044
- return possibilities;
2045
- },
2046
- // Numbers with percent signs
2047
- percent: function (text) {
2048
- text = $__default["default"].trim(text);
2049
- // store whether or not there is a percent sign
2050
- let hasPercentSign = false;
2051
- if (text.indexOf("%") === text.length - 1) {
2052
- text = $__default["default"].trim(text.substring(0, text.length - 1));
2053
- hasPercentSign = true;
2054
- }
2055
- const transformed = forms.decimal(text);
2056
- $__default["default"].each(transformed, function (ix, t) {
2057
- t.exact = hasPercentSign;
2058
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
2059
- t.value = t.value / 100;
2060
- });
2061
- return transformed;
2062
- },
2063
- // Mixed numbers, like 1 3/4
2064
- mixed: function (text) {
2065
- const match = text
2066
- // Replace unicode minus sign with hyphen
2067
- .replace(/\u2212/, "-")
2068
- // Remove space after +, -
2069
- .replace(/([+-])\s+/g, "$1")
2070
- // Extract integer, numerator and denominator
2071
- .match(/^([+-]?)(\d+)\s+(\d+)\s*\/\s*(\d+)$/);
2072
- if (match) {
2073
- const sign = parseFloat(match[1] + "1");
2074
- const integ = parseFloat(match[2]);
2075
- const num = parseFloat(match[3]);
2076
- const denom = parseFloat(match[4]);
2077
- const simplified = num < denom && KhanMath.getGCD(num, denom) === 1;
2078
- return [{
2079
- value: sign * (integ + num / denom),
2080
- exact: simplified
2081
- }];
2082
- }
2083
- return [];
2084
- },
2085
- // Decimal numbers -- compare entered text rounded to
2086
- // 'precision' Reciprical of the precision against the correct
2087
- // answer. We round to 1/1e10 by default, which is healthily
2088
- // less than machine epsilon but should be more than any real
2089
- // decimal answer would use. (The 'integer' answer type uses
2090
- // precision == 1.)
2091
- decimal: function (text) {
2092
- let precision = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1e10;
2093
- const normal = function (text) {
2094
- text = $__default["default"].trim(text);
2095
- const match = text
2096
- // Replace unicode minus sign with hyphen
2097
- .replace(/\u2212/, "-")
2098
- // Remove space after +, -
2099
- .replace(/([+-])\s+/g, "$1")
2100
- // Extract integer, numerator and denominator. If
2101
- // commas or spaces are used, they must be in the
2102
- // "correct" places
2103
- .match(/^([+-]?(?:\d{1,3}(?:[, ]?\d{3})*\.?|\d{0,3}(?:[, ]?\d{3})*\.(?:\d{3}[, ]?)*\d{1,3}))$/);
2104
-
2105
- // You can't start a number with `0,`, to prevent us
2106
- // interpeting '0.342' as correct for '342'
2107
- const badLeadingZero = text.match(/^0[0,]*,/);
2108
- if (match && !badLeadingZero) {
2109
- let x = parseFloat(match[1].replace(/[, ]/g, ""));
2110
- if (options.inexact === undefined) {
2111
- x = Math.round(x * precision) / precision;
2112
- }
2113
- return x;
2114
- }
2115
- };
2116
- const commas = function (text) {
2117
- text = text.replace(/([\.,])/g, function (_, c) {
2118
- return c === "." ? "," : ".";
2119
- });
2120
- return normal(text);
2121
- };
2122
- return [{
2123
- value: normal(text),
2124
- exact: true
2125
- }, {
2126
- value: commas(text),
2127
- exact: true
2128
- }];
2129
- }
2130
- };
2131
-
2132
- // validator function
2133
- return function (guess) {
2134
- // The fallback variable is used in place of the answer, if no
2135
- // answer is provided (i.e. the field is left blank)
2136
- const fallback = options.fallback != null ? "" + options.fallback : "";
2137
- guess = $__default["default"].trim(guess) || fallback;
2138
- const score = {
2139
- empty: guess === "",
2140
- correct: false,
2141
- message: null,
2142
- guess: guess
2143
- };
2144
-
2145
- // iterate over all the acceptable forms, and if one of the
2146
- // answers is correct, return true
2147
- $__default["default"].each(acceptableForms, function (i, form) {
2148
- const transformed = forms[form](guess);
2149
- for (let j = 0, l = transformed.length; j < l; j++) {
2150
- const val = transformed[j].value;
2151
- const exact = transformed[j].exact;
2152
- const piApprox = transformed[j].piApprox;
2153
- // If a string was returned, and it exactly matches,
2154
- // return true
2155
- if (predicate(val, options.maxError)) {
2156
- // If the exact correct number was returned,
2157
- // return true
2158
- if (exact || options.simplify === "optional") {
2159
- score.correct = true;
2160
- score.message = options.message || null;
2161
- // If the answer is correct, don't say it's
2162
- // empty. This happens, for example, with the
2163
- // coefficient type where guess === "" but is
2164
- // interpreted as "1" which is correct.
2165
- score.empty = false;
2166
- } else if (form === "percent") {
2167
- // Otherwise, an error was returned
2168
- score.empty = true;
2169
- score.message = strings.MISSING_PERCENT_ERROR;
2170
- } else {
2171
- if (options.simplify !== "enforced") {
2172
- score.empty = true;
2173
- }
2174
- score.message = strings.NEEDS_TO_BE_SIMPLFIED_ERROR;
2175
- }
2176
- // The return false below stops the looping of the
2177
- // callback since predicate check succeeded.
2178
- // No more forms to look to verify the user guess.
2179
- return false;
2180
- }
2181
- if (piApprox && predicate(val, Math.abs(val * 0.001))) {
2182
- score.empty = true;
2183
- score.message = strings.APPROXIMATED_PI_ERROR;
2184
- }
2185
- }
2186
- });
2187
- if (score.correct === false) {
2188
- let interpretedGuess = false;
2189
- ___default["default"].each(forms, function (form) {
2190
- const anyAreNaN = ___default["default"].any(form(guess), function (t) {
2191
- return t.value != null && !___default["default"].isNaN(t.value);
2192
- });
2193
- if (anyAreNaN) {
2194
- interpretedGuess = true;
2195
- }
2196
- });
2197
- if (!interpretedGuess) {
2198
- score.empty = true;
2199
- score.message = strings.EXTRA_SYMBOLS_ERROR;
2200
- return score;
2201
- }
2202
- }
2203
- return score;
2204
- };
2205
- }
2206
- },
2207
- /*
2208
- * number answer type
2209
- *
2210
- * wraps the predicate answer type to performs simple number-based checking
2211
- * of a solution
2212
- */
2213
- number: {
2214
- convertToPredicate: function (correctAnswer, options) {
2215
- const correctFloat = parseFloat(correctAnswer);
2216
- return [function (guess, maxError) {
2217
- return Math.abs(guess - correctFloat) < maxError;
2218
- }, $__default["default"].extend({}, options, {
2219
- type: "predicate"
2220
- })];
2221
- },
2222
- createValidatorFunctional: function (correctAnswer, options, strings) {
2223
- return KhanAnswerTypes.predicate.createValidatorFunctional(...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
2224
- }
2225
- },
2226
- /*
2227
- * The expression answer type parses a given expression or equation
2228
- * and semantically compares it to the solution. In addition, instant
2229
- * feedback is provided by rendering the last answer that fully parsed.
2230
- *
2231
- * Parsing options:
2232
- * functions (e.g. data-functions="f g h")
2233
- * A space or comma separated list of single-letter variables that
2234
- * should be interpreted as functions. Case sensitive. "e" and "i"
2235
- * are reserved.
2236
- *
2237
- * no functions specified: f(x+y) == fx + fy
2238
- * with "f" as a function: f(x+y) != fx + fy
2239
- *
2240
- * Comparison options:
2241
- * same-form (e.g. data-same-form)
2242
- * If present, the answer must match the solution's structure in
2243
- * addition to evaluating the same. Commutativity and excess negation
2244
- * are ignored, but all other changes will trigger a rejection. Useful
2245
- * for requiring a particular form of an equation, or if the answer
2246
- * must be factored.
2247
- *
2248
- * example question: Factor x^2 + x - 2
2249
- * example solution: (x-1)(x+2)
2250
- * accepted answers: (x-1)(x+2), (x+2)(x-1), ---(-x-2)(-1+x), etc.
2251
- * rejected answers: x^2+x-2, x*x+x-2, x(x+1)-2, (x-1)(x+2)^1, etc.
2252
- * rejection message: Your answer is not in the correct form
2253
- *
2254
- * simplify (e.g. data-simplify)
2255
- * If present, the answer must be fully expanded and simplified. Use
2256
- * carefully - simplification is hard and there may be bugs, or you
2257
- * might not agree on the definition of "simplified" used. You will
2258
- * get an error if the provided solution is not itself fully expanded
2259
- * and simplified.
2260
- *
2261
- * example question: Simplify ((n*x^5)^5) / (n^(-2)*x^2)^-3
2262
- * example solution: x^31 / n
2263
- * accepted answers: x^31 / n, x^31 / n^1, x^31 * n^(-1), etc.
2264
- * rejected answers: (x^25 * n^5) / (x^(-6) * n^6), etc.
2265
- * rejection message: Your answer is not fully expanded and simplified
2266
- *
2267
- * Rendering options:
2268
- * times (e.g. data-times)
2269
- * If present, explicit multiplication (such as between numbers) will
2270
- * be rendered with a cross/x symbol (TeX: \times) instead of the usual
2271
- * center dot (TeX: \cdot).
2272
- *
2273
- * normal rendering: 2 * 3^x -> 2 \cdot 3^{x}
2274
- * but with "times": 2 * 3^x -> 2 \times 3^{x}
2275
- */
2276
- expression: {
2277
- parseSolution: function (solutionString, options) {
2278
- let solution = KAS__namespace.parse(solutionString, options);
2279
- if (!solution.parsed) {
2280
- throw new perseusCore.PerseusError("The provided solution (" + solutionString + ") didn't parse.", perseusCore.Errors.InvalidInput);
2281
- } else if (options.simplified && !solution.expr.isSimplified()) {
2282
- throw new perseusCore.PerseusError("The provided solution (" + solutionString + ") isn't fully expanded and simplified.", perseusCore.Errors.InvalidInput);
2283
- } else {
2284
- solution = solution.expr;
2285
- }
2286
- return solution;
2287
- },
2288
- createValidatorFunctional: function (solution, options, strings) {
2289
- return function (guess) {
2290
- const score = {
2291
- empty: false,
2292
- correct: false,
2293
- message: null,
2294
- guess: guess,
2295
- // Setting `ungraded` to true indicates that if the
2296
- // guess doesn't match any of the solutions, the guess
2297
- // shouldn't be marked as incorrect; instead, `message`
2298
- // should be shown to the user. This is different from
2299
- // setting `empty` to true, since the behavior of `empty`
2300
- // is that `message` only will be shown if the guess is
2301
- // graded as empty for every solution.
2302
- ungraded: false
2303
- };
2304
-
2305
- // Don't bother parsing an empty input
2306
- if (!guess) {
2307
- // @ts-expect-error - TS2540 - Cannot assign to 'empty' because it is a read-only property.
2308
- score.empty = true;
2309
- return score;
2310
- }
2311
- const answer = KAS__namespace.parse(guess, options);
2312
-
2313
- // An unsuccessful parse doesn't count as wrong
2314
- if (!answer.parsed) {
2315
- // @ts-expect-error - TS2540 - Cannot assign to 'empty' because it is a read-only property.
2316
- score.empty = true;
2317
- return score;
2318
- }
2319
-
2320
- // Solution will need to be parsed again if we're creating
2321
- // this from a multiple question type
2322
- if (typeof solution === "string") {
2323
- solution = KhanAnswerTypes.expression.parseSolution(solution, options);
2324
- }
2325
- const result = KAS__namespace.compare(answer.expr, solution, options);
2326
- if (result.equal) {
2327
- // Correct answer
2328
- // @ts-expect-error - TS2540 - Cannot assign to 'correct' because it is a read-only property.
2329
- score.correct = true;
2330
- } else if (result.wrongVariableNames || result.wrongVariableCase) {
2331
- // We don't want to give people an error for getting the
2332
- // variable names or the variable case wrong.
2333
- // TODO(aasmund): This should ideally have been handled
2334
- // under the `result.message` condition, but the
2335
- // KAS messages currently aren't translatable.
2336
- // @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
2337
- score.ungraded = true;
2338
- // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
2339
- score.message = result.wrongVariableCase ? strings.WRONG_CASE_ERROR : strings.WRONG_LETTER_ERROR;
2340
- // Don't tell the use they're "almost there" in this case, that may not be true and isn't helpful.
2341
- // @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; }'.
2342
- score.suppressAlmostThere = true;
2343
- } else if (result.message) {
2344
- // Nearly correct answer
2345
- // TODO(aasmund): This message also isn't translatable;
2346
- // need to fix that in KAS
2347
- // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
2348
- score.message = result.message;
2349
- } else {
2350
- // Replace x with * and see if it would have been correct
2351
- // TODO(aasmund): I think this branch is effectively dead,
2352
- // because the replacement will only work in situations
2353
- // where the variables are wrong (except if the variable
2354
- // is x, in which case the replacement won't work either),
2355
- // which is handled by another branch. When we implement a
2356
- // more sophisticated variable check, revive this or
2357
- // remove it completely if it will never come into play.
2358
- const answerX = KAS__namespace.parse(guess.replace(/[xX]/g, "*"), options);
2359
- if (answerX.parsed) {
2360
- const resultX = KAS__namespace.compare(answerX.expr, solution, options);
2361
- if (resultX.equal) {
2362
- // @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
2363
- score.ungraded = true;
2364
- // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
2365
- score.message = strings.MULTIPLICATION_SIGN_ERROR;
2366
- } else if (resultX.message) {
2367
- // TODO(aasmund): I18nize `score.message`
2368
- // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
2369
- score.message = resultX.message + " Also, I'm a computer. I only understand " + "multiplication if you use an " + "asterisk (*) as the multiplication " + "sign.";
2370
- }
2371
- }
2372
- }
2373
- return score;
2374
- };
2375
- }
2376
- }
2377
- };
2378
-
2379
1655
  /**
2380
1656
  * Checks user input from the expression widget to see if it is scorable.
2381
1657
  *
@@ -2439,10 +1715,10 @@ function scoreExpression(userInput, rubric, strings, locale) {
2439
1715
  });
2440
1716
  return null;
2441
1717
  }
2442
- return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
1718
+ return perseusScore.KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
2443
1719
  simplify: answer.simplify,
2444
1720
  form: answer.form
2445
- }), strings);
1721
+ }));
2446
1722
  };
2447
1723
 
2448
1724
  // Find the first answer form that matches the user's input and that
@@ -3108,14 +2384,14 @@ const split = "x".split(/(.)/g).length ? function (str, r) {
3108
2384
  function firstNumericalParse$1(text, strings) {
3109
2385
  // TODO(alpert): This is sort of hacky...
3110
2386
  let first;
3111
- const val = KhanAnswerTypes.predicate.createValidatorFunctional(function (ans) {
2387
+ const val = perseusScore.KhanAnswerTypes.predicate.createValidatorFunctional(function (ans) {
3112
2388
  first = ans;
3113
2389
  return true; /* break */
3114
2390
  }, {
3115
2391
  simplify: "optional",
3116
2392
  inexact: true,
3117
2393
  forms: "integer, proper, improper, pi, log, mixed, decimal"
3118
- }, strings);
2394
+ });
3119
2395
  val(text);
3120
2396
  return first;
3121
2397
  }
@@ -9128,7 +8404,7 @@ var Tex$1 = {
9128
8404
  text = text != null ? text + "" : "";
9129
8405
 
9130
8406
  // Attempt to clean up some of the math
9131
- text = KhanMath.cleanMath(text);
8407
+ text = kmath.KhanMath.cleanMath(text);
9132
8408
 
9133
8409
  // Store the formula that we're using
9134
8410
  $elem.attr("data-math-formula", text);
@@ -9815,7 +9091,7 @@ class Graphie$1 {
9815
9091
  return "z";
9816
9092
  }
9817
9093
  const scaled = alreadyScaled ? point : this.scalePoint(point);
9818
- return (i === 0 ? "M" : "L") + KhanMath.bound(scaled[0]) + " " + KhanMath.bound(scaled[1]);
9094
+ return (i === 0 ? "M" : "L") + kmath.KhanMath.bound(scaled[0]) + " " + kmath.KhanMath.bound(scaled[1]);
9819
9095
  }).join("");
9820
9096
  };
9821
9097
  svgParabolaPath = (a, b, c) => {
@@ -9851,7 +9127,7 @@ class Graphie$1 {
9851
9127
 
9852
9128
  // Scale and bound
9853
9129
  const points = [left, control, right].map(this.scalePoint);
9854
- const values = points.flat().map(KhanMath.bound);
9130
+ const values = points.flat().map(kmath.KhanMath.bound);
9855
9131
  return "M" + values[0] + "," + values[1] + " Q" + values[2] + "," + values[3] + " " + values[4] + "," + values[5];
9856
9132
  };
9857
9133
  svgSinusoidPath = (a, b, c, d) => {
@@ -10901,7 +10177,7 @@ class Arrowhead$1 extends WrappedPath {
10901
10177
  }
10902
10178
  toCoordAtAngle(coord, angle) {
10903
10179
  const clipPoint = this.graphie.scalePoint(getClipPoint(this.graphie, coord, angle));
10904
- this.transform("translateX(" + (clipPoint[0] + Arrowhead$1.scale * this.center[0]) + "px) " + "translateY(" + (clipPoint[1] + Arrowhead$1.scale * this.center[1]) + "px) " + "translateZ(0) " + "rotate(" + (360 - KhanMath.bound(angle)) + "deg)");
10180
+ this.transform("translateX(" + (clipPoint[0] + Arrowhead$1.scale * this.center[0]) + "px) " + "translateY(" + (clipPoint[1] + Arrowhead$1.scale * this.center[1]) + "px) " + "translateZ(0) " + "rotate(" + (360 - kmath.KhanMath.bound(angle)) + "deg)");
10905
10181
  }
10906
10182
  }
10907
10183
 
@@ -11025,9 +10301,9 @@ class WrappedLine extends WrappedDrawing {
11025
10301
  // Compute transformation parameters
11026
10302
  const polarDiff = kmath.vector.polarDegFromCart(kmath.vector.subtract(scaledEnd, scaledStart));
11027
10303
  const lineLength = polarDiff[0];
11028
- const angle = KhanMath.bound(polarDiff[1]);
10304
+ const angle = kmath.KhanMath.bound(polarDiff[1]);
11029
10305
  const delta = kmath.vector.subtract(scaledStart, this.initialPoint);
11030
- const scale = KhanMath.bound(lineLength / this.initialLength);
10306
+ const scale = kmath.KhanMath.bound(lineLength / this.initialLength);
11031
10307
 
11032
10308
  // Construct and apply transformation string
11033
10309
  const do3dTransform = InteractiveUtil.getCanUse3dTransform();
@@ -11133,7 +10409,7 @@ function clockwise$1(points) {
11133
10409
  const p2 = segment[1];
11134
10410
  return (p2[0] - p1[0]) * (p2[1] + p1[1]);
11135
10411
  });
11136
- return sum$1(areas) > 0;
10412
+ return kmath.sum(areas) > 0;
11137
10413
  }
11138
10414
  function magnitude(v) {
11139
10415
  return Math.sqrt(___default["default"].reduce(v, function (memo, el) {
@@ -11380,7 +10656,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11380
10656
  const match = text.match(/\$deg(\d)?/);
11381
10657
  if (match) {
11382
10658
  const precision = match[1] || 1;
11383
- text = text.replace(match[0], KhanMath.toFixedApprox(angle, precision) + "^{\\circ}");
10659
+ text = text.replace(match[0], kmath.KhanMath.toFixedApprox(angle, precision) + "^{\\circ}");
11384
10660
  }
11385
10661
  const sOffset = scaledPolarDeg(sRadius + 15, halfAngle);
11386
10662
  const sPosition = kmath.vector.add(sVertex, sOffset);
@@ -11473,7 +10749,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11473
10749
  if (match) {
11474
10750
  const distance = GraphUtils.getDistance(p1, p2);
11475
10751
  const precision = match[1] || 1;
11476
- text = text.replace(match[0], KhanMath.toFixedApprox(distance, precision));
10752
+ text = text.replace(match[0], kmath.KhanMath.toFixedApprox(distance, precision));
11477
10753
  }
11478
10754
  const sOffset = 20;
11479
10755
  const sOffsetVector = scaledPolarRad(sOffset, perpendicularAngle);
@@ -11696,8 +10972,8 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11696
10972
  // convert back to coordinates relative to graphie canvas
11697
10973
  mouseX = mouseXrel + centerX;
11698
10974
  mouseY = -mouseYrel + centerY;
11699
- coordX = KhanMath.roundTo(5, mouseX / graph.scale[0] + graph.range[0][0]);
11700
- coordY = KhanMath.roundTo(5, graph.range[1][1] - mouseY / graph.scale[1]);
10975
+ coordX = kmath.KhanMath.roundTo(5, mouseX / graph.scale[0] + graph.range[0][0]);
10976
+ coordY = kmath.KhanMath.roundTo(5, graph.range[1][1] - mouseY / graph.scale[1]);
11701
10977
  }
11702
10978
  const result = movablePoint.applyConstraint([coordX, coordY]);
11703
10979
  return result;
@@ -14869,54 +14145,6 @@ var MovableLineOptions = {
14869
14145
  }
14870
14146
  };
14871
14147
 
14872
- /**
14873
- * A work-in-progress of _ methods for objects.
14874
- * That is, they take an object as a parameter,
14875
- * and return an object instead of an array.
14876
- *
14877
- * TODO(aria): Move this out of interactive2
14878
- */
14879
-
14880
- /**
14881
- * Does a pluck on keys inside objects in an object
14882
- *
14883
- * Ex:
14884
- * tools = {
14885
- * translation: {
14886
- * enabled: true
14887
- * },
14888
- * rotation: {
14889
- * enabled: false
14890
- * }
14891
- * };
14892
- * pluckObject(tools, "enabled") returns {
14893
- * translation: true
14894
- * rotation: false
14895
- * }
14896
- */
14897
- const pluck = function (table, subKey) {
14898
- return ___default["default"].object(___default["default"].map(table, function (value, key) {
14899
- return [key, value[subKey]];
14900
- }));
14901
- };
14902
-
14903
- /**
14904
- * Maps an object to an object
14905
- *
14906
- * > mapObject({a: '1', b: '2'}, (value, key) => {
14907
- * return value + 1;
14908
- * });
14909
- * {a: 2, b: 3}
14910
- */
14911
- const mapObject = function (obj, lambda) {
14912
- const result = {};
14913
- Object.keys(obj).forEach(key => {
14914
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
14915
- result[key] = lambda(obj[key], key);
14916
- });
14917
- return result;
14918
- };
14919
-
14920
14148
  /**
14921
14149
  * MovableLine
14922
14150
  */
@@ -14981,7 +14209,7 @@ ___default["default"].extend(MovableLine$4.prototype, {
14981
14209
  // - are objects, not primitives (and need a deeper copy)
14982
14210
  // - they don't need getters created for them
14983
14211
  // TODO(jack): Consider "default" once we es3ify perseus
14984
- pluck(MovableLineOptions, "standard")), DEFAULT_PROPS$2);
14212
+ perseusCore.pluck(MovableLineOptions, "standard")), DEFAULT_PROPS$2);
14985
14213
  },
14986
14214
  /**
14987
14215
  * Resets the object to its state as if it were constructed with
@@ -15488,7 +14716,7 @@ class MovablePoint$5 {
15488
14716
  // - are objects, not primitives (and need a deeper copy)
15489
14717
  // - they don't need getters created for them
15490
14718
  // TODO(jack): Consider "default" once we es3ify perseus
15491
- pluck(MovablePointOptions, "standard")
14719
+ perseusCore.pluck(MovablePointOptions, "standard")
15492
14720
  // We only update props here, because we want things on state to
15493
14721
  // be persistent, and updated appropriately in modify()
15494
14722
  ),
@@ -16389,7 +15617,7 @@ ___default["default"].extend(MovablePolygon.prototype, {
16389
15617
  // - are objects, not primitives (and need a deeper copy)
16390
15618
  // - they don't need getters created for them
16391
15619
  // TODO(jack): Consider "default" once we es3ify perseus
16392
- pluck(MovablePolygonOptions, "standard")
15620
+ perseusCore.pluck(MovablePolygonOptions, "standard")
16393
15621
 
16394
15622
  // We only update props here, because we want things on state to
16395
15623
  // be persistent, and updated appropriately in modify()
@@ -18774,7 +18002,7 @@ const inlineNodeTypes = {
18774
18002
 
18775
18003
  // @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'.
18776
18004
  const builtParser$1 = SimpleMarkdown__default["default"].parserFor(rules$1);
18777
- const parse$3 = (source, state) => {
18005
+ const parse$4 = (source, state) => {
18778
18006
  const paragraphedSource = source + "\n\n";
18779
18007
  return builtParser$1(paragraphedSource, {
18780
18008
  ...state,
@@ -18859,14 +18087,14 @@ const getContent = ast => {
18859
18087
  */
18860
18088
  const characterCount = source => {
18861
18089
  // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
18862
- const ast = parse$3(source);
18090
+ const ast = parse$4(source);
18863
18091
  const content = getContent(ast).join("");
18864
18092
  return content.length;
18865
18093
  };
18866
18094
  var PerseusMarkdown = {
18867
18095
  characterCount: characterCount,
18868
18096
  traverseContent: traverseContent,
18869
- parse: parse$3,
18097
+ parse: parse$4,
18870
18098
  parseInline: inlineParser,
18871
18099
  reactFor: SimpleMarkdown__default["default"].reactFor,
18872
18100
  ruleOutput: SimpleMarkdown__default["default"].ruleOutput(rules$1, "react"),
@@ -19569,7 +18797,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19569
18797
 
19570
18798
  function getUpgradedWidgetOptions(oldWidgetOptions) {
19571
18799
  // @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
19572
- return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
18800
+ return perseusCore.mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
19573
18801
  if (!widgetInfo.type || !widgetInfo.alignment) {
19574
18802
  const newValues = {};
19575
18803
  if (!widgetInfo.type) {
@@ -20295,7 +19523,7 @@ class Renderer extends React__namespace.Component {
20295
19523
  * state from that instead of the current widget props.
20296
19524
  */
20297
19525
  getSerializedState = widgetProps => {
20298
- return mapObject(widgetProps || this.state.widgetProps, (props, widgetId) => {
19526
+ return perseusCore.mapObject(widgetProps || this.state.widgetProps, (props, widgetId) => {
20299
19527
  const widget = this.getWidgetInstance(widgetId);
20300
19528
  if (widget && widget.getSerializedState) {
20301
19529
  return widget.getSerializedState();
@@ -20331,7 +19559,7 @@ class Renderer extends React__namespace.Component {
20331
19559
  }
20332
19560
  };
20333
19561
  this.setState({
20334
- widgetProps: mapObject(serializedState, (props, widgetId) => {
19562
+ widgetProps: perseusCore.mapObject(serializedState, (props, widgetId) => {
20335
19563
  const widget = this.getWidgetInstance(widgetId);
20336
19564
  if (widget && widget.restoreSerializedState) {
20337
19565
  // Note that we probably can't call
@@ -22118,12 +21346,12 @@ function scoreInputNumber(userInput, rubric, strings) {
22118
21346
  // `KhanAnswerTypes.number.convertToPredicate`, but a string is
22119
21347
  // expected here
22120
21348
  const stringValue = `${rubric.value}`;
22121
- const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
21349
+ const val = perseusScore.KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
22122
21350
  simplify: rubric.simplify,
22123
21351
  inexact: rubric.inexact || undefined,
22124
21352
  maxError: rubric.maxError,
22125
21353
  forms: answerTypes[rubric.answerType].forms
22126
- }, strings);
21354
+ });
22127
21355
 
22128
21356
  // We may have received TeX; try to parse it before grading.
22129
21357
  // If `currentValue` is not TeX, this should be a no-op.
@@ -22450,14 +21678,14 @@ function scoreNumericInput(userInput, rubric, strings) {
22450
21678
  if (!answer.strict || validatorForms.length === 0) {
22451
21679
  validatorForms.push(...defaultAnswerForms);
22452
21680
  }
22453
- return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
21681
+ return perseusScore.KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
22454
21682
  message: answer.message,
22455
21683
  simplify: answer.status === "correct" ? answer.simplify : "optional",
22456
21684
  inexact: true,
22457
21685
  // TODO(merlob) backfill / delete
22458
21686
  maxError: answer.maxError,
22459
21687
  forms: validatorForms
22460
- }, strings);
21688
+ });
22461
21689
  };
22462
21690
 
22463
21691
  // We may have received TeX; try to parse it before grading.
@@ -22760,11 +21988,11 @@ var NumericInput$1 = {
22760
21988
  }
22761
21989
 
22762
21990
  // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
22763
- let answerString = KhanMath.toNumericString(answer.value, format);
21991
+ let answerString = kmath.KhanMath.toNumericString(answer.value, format);
22764
21992
  if (answer.maxError) {
22765
21993
  answerString += " \u00B1 " +
22766
21994
  // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
22767
- KhanMath.toNumericString(answer.maxError, format);
21995
+ kmath.KhanMath.toNumericString(answer.maxError, format);
22768
21996
  }
22769
21997
  return answerString;
22770
21998
  });
@@ -24567,7 +23795,7 @@ const CIRCLE_LABEL_STYLE = {
24567
23795
  // TODO(LEMS-2656): remove TS suppression
24568
23796
  // @ts-expect-error: State is not assignable to ParseState
24569
23797
  const builtParser = SimpleMarkdown__default["default"].parserFor(rules);
24570
- const parse$2 = (source, state) => {
23798
+ const parse$3 = (source, state) => {
24571
23799
  // @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
24572
23800
  state = state || {};
24573
23801
  const paragraphedSource = source + "\n\n";
@@ -24577,7 +23805,7 @@ const parse$2 = (source, state) => {
24577
23805
  // @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
24578
23806
  const output = SimpleMarkdown__default["default"].reactFor(SimpleMarkdown__default["default"].ruleOutput(rules, "react"));
24579
23807
  var PassageMarkdown = {
24580
- parse: parse$2,
23808
+ parse: parse$3,
24581
23809
  output,
24582
23810
  START_REF_PREFIX,
24583
23811
  END_REF_PREFIX,
@@ -29254,6 +28482,9 @@ const typeToButton = type => {
29254
28482
  };
29255
28483
 
29256
28484
  function getCoefficientsByType(data) {
28485
+ if (data.coords == null) {
28486
+ return undefined;
28487
+ }
29257
28488
  if (data.type === "exponential" || data.type === "logarithm") {
29258
28489
  const grader = functionForType(data.type);
29259
28490
  return grader.getCoefficients(data.coords, data.asymptote);
@@ -32043,9 +31274,10 @@ const AxisTicks = () => {
32043
31274
  * @param {number} axisIndex which axis we're getting options for
32044
31275
  */
32045
31276
  const axisOptions = (props, axisIndex) => {
31277
+ const lines = props.markings === "axes" ? false : props.gridStep[axisIndex];
32046
31278
  return {
32047
- axis: props.markings === "graph",
32048
- lines: props.gridStep[axisIndex],
31279
+ axis: props.markings === "graph" || props.markings === "axes",
31280
+ lines: lines,
32049
31281
  labels: false
32050
31282
  };
32051
31283
  };
@@ -32332,8 +31564,8 @@ const {
32332
31564
  firstNumericalParse,
32333
31565
  captureScratchpadTouchStart
32334
31566
  } = Util;
32335
- const toNumericString = KhanMath.toNumericString;
32336
- const getNumericFormat = KhanMath.getNumericFormat;
31567
+ const toNumericString = kmath.KhanMath.toNumericString;
31568
+ const getNumericFormat = kmath.KhanMath.getNumericFormat;
32337
31569
 
32338
31570
  /**
32339
31571
  * An input box that accepts only numeric strings
@@ -32628,231 +31860,6 @@ const LegacyGrid = _ref => {
32628
31860
  return null;
32629
31861
  };
32630
31862
 
32631
- /**
32632
- * The Perseus "data schema" file.
32633
- *
32634
- * This file, and the types in it, represents the "data schema" that Perseus
32635
- * uses. The @khanacademy/perseus-editor package edits and produces objects
32636
- * that conform to the types in this file. Similarly, the top-level renderers
32637
- * in @khanacademy/perseus, consume objects that conform to these types.
32638
- *
32639
- * WARNING: This file should not import any types from elsewhere so that it is
32640
- * easy to reason about changes that alter the Perseus schema. This helps
32641
- * ensure that it is not changed accidentally when upgrading a dependant
32642
- * package or other part of Perseus code. Note that TypeScript does type
32643
- * checking via something called "structural typing". This means that as long
32644
- * as the shape of a type matches, the name it goes by doesn't matter. As a
32645
- * result, a `Coord` type that looks like this `[x: number, y: number]` is
32646
- * _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
32647
- * number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
32648
- * number, y: number]` is compatible with `[min: number, max: number]`. The
32649
- * labels are for humans, not TypeScript. :)
32650
- *
32651
- * If you make changes to types in this file, be very sure that:
32652
- *
32653
- * a) the changes are backwards compatible. If they are not, old data from
32654
- * previous versions of the "schema" could become unrenderable, or worse,
32655
- * introduce hard-to-diagnose bugs.
32656
- * b) the parsing code (`util/parse-perseus-json/`) is updated to handle
32657
- * the new format _as well as_ the old format.
32658
- */
32659
-
32660
- // TODO(FEI-4010): Remove `Perseus` prefix for all types here
32661
-
32662
- // Same name as Mafs
32663
-
32664
- /**
32665
- * Our core set of Perseus widgets.
32666
- *
32667
- * This interface is the basis for "registering" all Perseus widget types.
32668
- * There should be one key/value pair for each supported widget. If you create
32669
- * a new widget, an entry should be added to this interface. Note that this
32670
- * only registers the widget options type, you'll also need to register the
32671
- * widget so that it's available at runtime (@see
32672
- * {@link file://./widgets.ts#registerWidget}).
32673
- *
32674
- * Importantly, the key should be the name that is used in widget IDs. For most
32675
- * widgets that is the same as the widget option's `type` field. In cases where
32676
- * a widget has been deprecated and replaced with the deprecated-standin
32677
- * widget, it should be the original widget type!
32678
- *
32679
- * If you define the widget outside of this package, you can still add the new
32680
- * widget to this interface by writing the following in that package that
32681
- * contains the widget. TypeScript will merge that definition of the
32682
- * `PerseusWidgets` with the one defined below.
32683
- *
32684
- * ```typescript
32685
- * declare module "@khanacademy/perseus" {
32686
- * interface PerseusWidgetTypes {
32687
- * // A new widget
32688
- * "new-awesomeness": MyAwesomeNewWidget;
32689
- *
32690
- * // A deprecated widget
32691
- * "super-old-widget": DeprecatedStandinWidget;
32692
- * }
32693
- * }
32694
- *
32695
- * // The new widget's options definition
32696
- * type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
32697
- *
32698
- * // The deprecated widget's options definition
32699
- * type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
32700
- * ```
32701
- *
32702
- * This interface can be extended through the magic of TypeScript "Declaration
32703
- * merging". Specifically, we augment this module and extend this interface.
32704
- *
32705
- * @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
32706
- */
32707
-
32708
- /**
32709
- * A map of widget IDs to widget options. This is most often used as the type
32710
- * for a set of widgets defined in a `PerseusItem` but can also be useful to
32711
- * represent a function parameter where only `widgets` from a `PerseusItem` are
32712
- * needed. Today Widget IDs are made up of the widget type and an incrementing
32713
- * integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
32714
- * reading/parsing the widget id to derive any information from it, except in
32715
- * the case of this map.
32716
- *
32717
- * @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
32718
- * by augmenting the PerseusWidgetTypes with new widget types!
32719
- */
32720
-
32721
- /**
32722
- * A "PerseusItem" is a classic Perseus item. It is rendered by the
32723
- * `ServerItemRenderer` and the layout is pre-set.
32724
- *
32725
- * To render more complex Perseus items, see the `Item` type in the multi item
32726
- * area.
32727
- */
32728
-
32729
- /**
32730
- * A "PerseusArticle" is an item that is meant to be rendered as an article.
32731
- * This item is never scored and is rendered by the `ArticleRenderer`.
32732
- */
32733
-
32734
- const ItemExtras = [
32735
- // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
32736
- "calculator",
32737
- // The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
32738
- "chi2Table",
32739
- // The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
32740
- "financialCalculatorMonthlyPayment",
32741
- // The user might benefit from a total amount calculator. Provided on Khan Academy when true
32742
- "financialCalculatorTotalAmount",
32743
- // The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
32744
- "financialCalculatorTimeToPayOff",
32745
- // The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
32746
- "periodicTable",
32747
- // The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
32748
- "periodicTableWithKey",
32749
- // The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
32750
- "tTable",
32751
- // The user might benefit from using a statistics Z Table like https://www.ztable.net/
32752
- "zTable"];
32753
-
32754
- /**
32755
- * The type representing the common structure of all widget's options. The
32756
- * `Options` generic type represents the widget-specific option data.
32757
- */
32758
-
32759
- // prettier-ignore
32760
-
32761
- // prettier-ignore
32762
-
32763
- // prettier-ignore
32764
-
32765
- // prettier-ignore
32766
-
32767
- // prettier-ignore
32768
-
32769
- // prettier-ignore
32770
-
32771
- // prettier-ignore
32772
-
32773
- // prettier-ignore
32774
-
32775
- // prettier-ignore
32776
-
32777
- // prettier-ignore
32778
-
32779
- // prettier-ignore
32780
-
32781
- // prettier-ignore
32782
-
32783
- // prettier-ignore
32784
-
32785
- // prettier-ignore
32786
-
32787
- // prettier-ignore
32788
-
32789
- // prettier-ignore
32790
-
32791
- // prettier-ignore
32792
-
32793
- // prettier-ignore
32794
-
32795
- // prettier-ignore
32796
-
32797
- // prettier-ignore
32798
-
32799
- // prettier-ignore
32800
-
32801
- // prettier-ignore
32802
-
32803
- // prettier-ignore
32804
-
32805
- // prettier-ignore
32806
-
32807
- // prettier-ignore
32808
-
32809
- // prettier-ignore
32810
-
32811
- // prettier-ignore
32812
-
32813
- // prettier-ignore
32814
-
32815
- // prettier-ignore
32816
-
32817
- // prettier-ignore
32818
-
32819
- // prettier-ignore
32820
-
32821
- // prettier-ignore
32822
-
32823
- // prettier-ignore
32824
-
32825
- //prettier-ignore
32826
-
32827
- /**
32828
- * A background image applied to various widgets.
32829
- */
32830
-
32831
- const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
32832
-
32833
- // 2D range: xMin, xMax, yMin, yMax
32834
-
32835
- const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
32836
- const lockedFigureColors = {
32837
- blue: "#3D7586",
32838
- green: "#447A53",
32839
- grayH: "#3B3D45",
32840
- purple: "#594094",
32841
- pink: "#B25071",
32842
- red: "#D92916",
32843
- orange: "#946700"
32844
- };
32845
- const lockedFigureFillStyles = {
32846
- none: 0,
32847
- white: 1,
32848
- translucent: 0.4,
32849
- solid: 1
32850
- };
32851
-
32852
- // Not associated with a specific figure
32853
-
32854
- const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
32855
-
32856
31863
  function LockedLabel(props) {
32857
31864
  const {
32858
31865
  coord,
@@ -32872,7 +31879,7 @@ function LockedLabel(props) {
32872
31879
  position: "absolute",
32873
31880
  left: x,
32874
31881
  top: y,
32875
- color: lockedFigureColors[color],
31882
+ color: perseusCore.lockedFigureColors[color],
32876
31883
  fontSize: wonderBlocksTokens.font.size[size],
32877
31884
  backgroundColor: "rgba(255, 255, 255, 0.8)"
32878
31885
  },
@@ -32921,16 +31928,16 @@ const LockedEllipse = props => {
32921
31928
  center: center,
32922
31929
  radius: radius,
32923
31930
  angle: angle,
32924
- fillOpacity: lockedFigureFillStyles[fillStyle],
31931
+ fillOpacity: perseusCore.lockedFigureFillStyles[fillStyle],
32925
31932
  strokeStyle: strokeStyle,
32926
- color: lockedFigureColors[color]
31933
+ color: perseusCore.lockedFigureColors[color]
32927
31934
  // We need to override the svg props if we want to have a
32928
31935
  // different fill color than the stroke color (specifically,
32929
31936
  // in the case where the fillStyle is "white").
32930
31937
  ,
32931
31938
  svgEllipseProps: {
32932
31939
  style: {
32933
- fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
31940
+ fill: fillStyle === "white" ? wonderBlocksTokens.color.white : perseusCore.lockedFigureColors[color]
32934
31941
  }
32935
31942
  }
32936
31943
  }));
@@ -32945,7 +31952,7 @@ const LockedFunction = props => {
32945
31952
  domain
32946
31953
  } = props;
32947
31954
  const plotProps = {
32948
- color: lockedFigureColors[color],
31955
+ color: perseusCore.lockedFigureColors[color],
32949
31956
  style: strokeStyle,
32950
31957
  domain
32951
31958
  };
@@ -33087,7 +32094,7 @@ const LockedLine = props => {
33087
32094
  line = /*#__PURE__*/React__namespace.createElement(Vector, {
33088
32095
  tail: point1.coord,
33089
32096
  tip: extendedPoint,
33090
- color: lockedFigureColors[color],
32097
+ color: perseusCore.lockedFigureColors[color],
33091
32098
  style: {
33092
32099
  strokeDasharray: lineStyle === "dashed" ? "var(--mafs-line-stroke-dash-style)" : undefined
33093
32100
  }
@@ -33102,19 +32109,19 @@ const LockedLine = props => {
33102
32109
  const startArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
33103
32110
  angle: angle,
33104
32111
  tip: arrowTip,
33105
- color: lockedFigureColors[color]
32112
+ color: perseusCore.lockedFigureColors[color]
33106
32113
  });
33107
32114
  arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
33108
32115
  angle = angle > 180 ? angle - 180 : angle + 180;
33109
32116
  const endArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
33110
32117
  angle: angle,
33111
32118
  tip: arrowTip,
33112
- color: lockedFigureColors[color]
32119
+ color: perseusCore.lockedFigureColors[color]
33113
32120
  });
33114
32121
  line = /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, startArrowHead, /*#__PURE__*/React__namespace.createElement(LineType, {
33115
32122
  point1: point1.coord,
33116
32123
  point2: point2.coord,
33117
- color: lockedFigureColors[color],
32124
+ color: perseusCore.lockedFigureColors[color],
33118
32125
  style: lineStyle
33119
32126
  }), endArrowHead);
33120
32127
  }
@@ -33127,8 +32134,8 @@ const LockedLine = props => {
33127
32134
  y: point1.coord[Y],
33128
32135
  svgCircleProps: {
33129
32136
  style: {
33130
- fill: point1.filled ? lockedFigureColors[point1.color] : wonderBlocksTokens.color.white,
33131
- stroke: lockedFigureColors[point1.color],
32137
+ fill: point1.filled ? perseusCore.lockedFigureColors[point1.color] : wonderBlocksTokens.color.white,
32138
+ stroke: perseusCore.lockedFigureColors[point1.color],
33132
32139
  strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
33133
32140
  }
33134
32141
  }
@@ -33137,8 +32144,8 @@ const LockedLine = props => {
33137
32144
  y: point2.coord[Y],
33138
32145
  svgCircleProps: {
33139
32146
  style: {
33140
- fill: point2.filled ? lockedFigureColors[point2.color] : wonderBlocksTokens.color.white,
33141
- stroke: lockedFigureColors[point2.color],
32147
+ fill: point2.filled ? perseusCore.lockedFigureColors[point2.color] : wonderBlocksTokens.color.white,
32148
+ stroke: perseusCore.lockedFigureColors[point2.color],
33142
32149
  strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
33143
32150
  }
33144
32151
  }
@@ -33163,8 +32170,8 @@ const LockedPoint = props => {
33163
32170
  y: y,
33164
32171
  svgCircleProps: {
33165
32172
  style: {
33166
- fill: filled ? lockedFigureColors[color] : wonderBlocksTokens.color.white,
33167
- stroke: lockedFigureColors[color],
32173
+ fill: filled ? perseusCore.lockedFigureColors[color] : wonderBlocksTokens.color.white,
32174
+ stroke: perseusCore.lockedFigureColors[color],
33168
32175
  strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
33169
32176
  }
33170
32177
  }
@@ -33186,23 +32193,23 @@ const LockedPolygon = props => {
33186
32193
  "aria-hidden": !hasAria
33187
32194
  }, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
33188
32195
  points: [...points],
33189
- fillOpacity: lockedFigureFillStyles[fillStyle],
32196
+ fillOpacity: perseusCore.lockedFigureFillStyles[fillStyle],
33190
32197
  strokeStyle: strokeStyle,
33191
- color: lockedFigureColors[color]
32198
+ color: perseusCore.lockedFigureColors[color]
33192
32199
  // We need to override the svg props if we want to have a
33193
32200
  // different fill color than the stroke color (specifically,
33194
32201
  // in the case where the fillStyle is "white").
33195
32202
  ,
33196
32203
  svgPolygonProps: {
33197
32204
  style: {
33198
- fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
32205
+ fill: fillStyle === "white" ? wonderBlocksTokens.color.white : perseusCore.lockedFigureColors[color]
33199
32206
  }
33200
32207
  }
33201
32208
  }), showVertices && points.map((point, index) => /*#__PURE__*/React__namespace.createElement(mafs.Point, {
33202
32209
  key: `locked-polygon-point-${index}`,
33203
32210
  x: point[X],
33204
32211
  y: point[Y],
33205
- color: lockedFigureColors[color]
32212
+ color: perseusCore.lockedFigureColors[color]
33206
32213
  })));
33207
32214
  };
33208
32215
 
@@ -33221,7 +32228,7 @@ const LockedVector = props => {
33221
32228
  }, /*#__PURE__*/React__namespace.createElement(Vector, {
33222
32229
  tail: tail,
33223
32230
  tip: tip,
33224
- color: lockedFigureColors[color]
32231
+ color: perseusCore.lockedFigureColors[color]
33225
32232
  }));
33226
32233
  };
33227
32234
 
@@ -34657,7 +33664,9 @@ function renderCircleGraph(state, dispatch) {
34657
33664
  graphState: state,
34658
33665
  dispatch: dispatch
34659
33666
  }),
34660
- interactiveElementsDescription: null
33667
+ interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(CircleGraphDescription, {
33668
+ state: state
33669
+ })
34661
33670
  };
34662
33671
  }
34663
33672
  function CircleGraph(props) {
@@ -34681,38 +33690,26 @@ function CircleGraph(props) {
34681
33690
  const outerPointsId = id + "-outer-points";
34682
33691
 
34683
33692
  // Aria label strings
34684
- const circleGraphAriaLabel = strings.srCircleGraph;
34685
- const circleShapeAriaLabel = strings.srCircleShape({
34686
- centerX: srFormatNumber(center[0], locale),
34687
- centerY: srFormatNumber(center[1], locale)
34688
- });
34689
- const circleRadiusPointAriaLabel = strings.srCircleRadiusPoint({
34690
- radiusPointX: srFormatNumber(radiusPoint[0], locale),
34691
- radiusPointY: srFormatNumber(radiusPoint[1], locale)
34692
- });
34693
- const circleRadiusDescription = strings.srCircleRadius({
34694
- radius
34695
- });
34696
- const circleOuterPointsDescription = strings.srCircleOuterPoints({
34697
- point1X: srFormatNumber(center[0] + radius, locale),
34698
- point1Y: srFormatNumber(center[1], locale),
34699
- point2X: srFormatNumber(center[0], locale),
34700
- point2Y: srFormatNumber(center[1] + radius, locale),
34701
- point3X: srFormatNumber(center[0] - radius, locale),
34702
- point3Y: srFormatNumber(center[1], locale),
34703
- point4X: srFormatNumber(center[0], locale),
34704
- point4Y: srFormatNumber(center[1] - radius, locale)
33693
+ const {
33694
+ srCircleGraph,
33695
+ srCircleShape,
33696
+ srCircleRadiusPoint,
33697
+ srCircleRadius,
33698
+ srCircleOuterPoints
33699
+ } = describeCircleGraph(graphState, {
33700
+ strings,
33701
+ locale
34705
33702
  });
34706
33703
  return /*#__PURE__*/React__namespace.createElement("g", {
34707
33704
  // Outer circle minimal description
34708
- "aria-label": circleGraphAriaLabel,
33705
+ "aria-label": srCircleGraph,
34709
33706
  "aria-describedby": `${circleId} ${radiusId} ${outerPointsId}`
34710
33707
  }, /*#__PURE__*/React__namespace.createElement(MovableCircle, {
34711
33708
  id: circleId
34712
33709
  // Focusable circle aria label reads with every update
34713
33710
  // because of the aria-live property in the circle <g>.
34714
33711
  ,
34715
- ariaLabel: circleShapeAriaLabel
33712
+ ariaLabel: srCircleShape
34716
33713
  // Aria-describedby describes additional info on focus.
34717
33714
  ,
34718
33715
  ariaDescribedBy: `${radiusId} ${outerPointsId}`,
@@ -34725,7 +33722,7 @@ function CircleGraph(props) {
34725
33722
  }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1
34726
33723
  // Radius point aria label reads with every update.
34727
33724
  , {
34728
- ariaLabel: `${circleRadiusPointAriaLabel} ${circleRadiusDescription}`
33725
+ ariaLabel: `${srCircleRadiusPoint} ${srCircleRadius}`
34729
33726
  // Aria-describedby describes additional info on focus.
34730
33727
  ,
34731
33728
  ariaDescribedBy: `${outerPointsId}`
@@ -34748,12 +33745,12 @@ function CircleGraph(props) {
34748
33745
  style: {
34749
33746
  display: "hidden"
34750
33747
  }
34751
- }, circleRadiusDescription), /*#__PURE__*/React__namespace.createElement("g", {
33748
+ }, srCircleRadius), /*#__PURE__*/React__namespace.createElement("g", {
34752
33749
  id: outerPointsId,
34753
33750
  style: {
34754
33751
  display: "hidden"
34755
33752
  }
34756
- }, circleOuterPointsDescription));
33753
+ }, srCircleOuterPoints));
34757
33754
  }
34758
33755
  function MovableCircle(props) {
34759
33756
  const {
@@ -34840,6 +33837,65 @@ function crossProduct(as, bs) {
34840
33837
  }
34841
33838
  return result;
34842
33839
  }
33840
+ function CircleGraphDescription(_ref) {
33841
+ let {
33842
+ state
33843
+ } = _ref;
33844
+ // The reason that CircleGraphDescription is a component (rather than a
33845
+ // function that returns a string) is because it needs to use a
33846
+ // hook: `usePerseusI18n`.
33847
+ const i18n = usePerseusI18n();
33848
+ const strings = describeCircleGraph(state, i18n);
33849
+ return strings.srCircleInteractiveElement;
33850
+ }
33851
+
33852
+ // Exported for testing
33853
+ function describeCircleGraph(state, i18n) {
33854
+ const {
33855
+ strings,
33856
+ locale
33857
+ } = i18n;
33858
+ const {
33859
+ center,
33860
+ radiusPoint
33861
+ } = state;
33862
+ const radius = getRadius(state);
33863
+
33864
+ // Aria label strings
33865
+ const srCircleGraph = strings.srCircleGraph;
33866
+ const srCircleShape = strings.srCircleShape({
33867
+ centerX: srFormatNumber(center[0], locale),
33868
+ centerY: srFormatNumber(center[1], locale)
33869
+ });
33870
+ const srCircleRadiusPoint = strings.srCircleRadiusPoint({
33871
+ radiusPointX: srFormatNumber(radiusPoint[0], locale),
33872
+ radiusPointY: srFormatNumber(radiusPoint[1], locale)
33873
+ });
33874
+ const srCircleRadius = strings.srCircleRadius({
33875
+ radius
33876
+ });
33877
+ const srCircleOuterPoints = strings.srCircleOuterPoints({
33878
+ point1X: srFormatNumber(center[0] + radius, locale),
33879
+ point1Y: srFormatNumber(center[1], locale),
33880
+ point2X: srFormatNumber(center[0], locale),
33881
+ point2Y: srFormatNumber(center[1] + radius, locale),
33882
+ point3X: srFormatNumber(center[0] - radius, locale),
33883
+ point3Y: srFormatNumber(center[1], locale),
33884
+ point4X: srFormatNumber(center[0], locale),
33885
+ point4Y: srFormatNumber(center[1] - radius, locale)
33886
+ });
33887
+ const srCircleInteractiveElement = strings.srInteractiveElements({
33888
+ elements: [srCircleShape, srCircleRadius].join(" ")
33889
+ });
33890
+ return {
33891
+ srCircleGraph,
33892
+ srCircleShape,
33893
+ srCircleRadiusPoint,
33894
+ srCircleRadius,
33895
+ srCircleOuterPoints,
33896
+ srCircleInteractiveElement
33897
+ };
33898
+ }
34843
33899
 
34844
33900
  function renderLinearGraph(state, dispatch) {
34845
33901
  return {
@@ -35833,7 +34889,9 @@ const MafsGraph = props => {
35833
34889
  labels,
35834
34890
  disableKeyboardInteraction: readOnly || !!props.static
35835
34891
  }
35836
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
34892
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
34893
+ className: "mafs-graph-container"
34894
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
35837
34895
  className: "mafs-graph",
35838
34896
  style: {
35839
34897
  position: "relative",
@@ -35876,7 +34934,7 @@ const MafsGraph = props => {
35876
34934
  bottom: 0,
35877
34935
  left: 0
35878
34936
  }
35879
- }, props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null)), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
34937
+ }, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null)), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
35880
34938
  preserveAspectRatio: false,
35881
34939
  viewBox: {
35882
34940
  x: state.range[X],
@@ -35896,7 +34954,7 @@ const MafsGraph = props => {
35896
34954
  height: height
35897
34955
  })),
35898
34956
  // Only render the axis ticks and arrows if the markings are set to a full "graph"
35899
- props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
34957
+ (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
35900
34958
  lockedFigures: props.lockedFigures,
35901
34959
  range: state.range
35902
34960
  }))), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
@@ -41426,7 +40484,7 @@ function scoreMatrix(userInput, rubric, strings) {
41426
40484
  const solutionSize = getMatrixSize(solution);
41427
40485
  const suppliedSize = getMatrixSize(supplied);
41428
40486
  const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
41429
- const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
40487
+ const createValidator = perseusScore.KhanAnswerTypes.number.createValidatorFunctional;
41430
40488
  let message = null;
41431
40489
  let incorrect = false;
41432
40490
  ___default["default"](suppliedSize[0]).times(row => {
@@ -41436,7 +40494,7 @@ function scoreMatrix(userInput, rubric, strings) {
41436
40494
  // @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
41437
40495
  solution[row][col], {
41438
40496
  simplify: true
41439
- }, strings);
40497
+ });
41440
40498
  const result = validator(supplied[row][col]);
41441
40499
  if (result.message) {
41442
40500
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
@@ -42588,10 +41646,10 @@ function parseBondModifier(smiles, ctx) {
42588
41646
  const firstChar = smiles[0];
42589
41647
  const rest = smiles.slice(1);
42590
41648
  if (firstChar === "=") {
42591
- return parse$1(rest, _mset(ctx, ["bond", "bondType"], "double"));
41649
+ return parse$2(rest, _mset(ctx, ["bond", "bondType"], "double"));
42592
41650
  }
42593
41651
  if (firstChar === "#") {
42594
- return parse$1(rest, _mset(ctx, ["bond", "bondType"], "triple"));
41652
+ return parse$2(rest, _mset(ctx, ["bond", "bondType"], "triple"));
42595
41653
  }
42596
41654
  throw new ParseError$1("Invalid character: " + firstChar);
42597
41655
  }
@@ -42652,8 +41710,8 @@ function parseParenthesizedExpression(smiles, ctx) {
42652
41710
  idx: newCtx.idx.concat([[inBranchIdx, 0]]),
42653
41711
  parens: newCtx.parens.concat("(")
42654
41712
  };
42655
- const parenExpr = parse$1(rest, parenCtx);
42656
- const remainder = parse$1(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
41713
+ const parenExpr = parse$2(rest, parenCtx);
41714
+ const remainder = parse$2(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
42657
41715
  return [parenExpr].concat(remainder);
42658
41716
  }
42659
41717
  if (firstChar === ")") {
@@ -42717,7 +41775,7 @@ function parseAtom(smiles, ctx) {
42717
41775
 
42718
41776
  // increment the atom counter and reset the branch counter
42719
41777
  const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
42720
- let restOfMolecule = parse$1(rest, _mset(newCtx, ["bond", "bondType"], "single"));
41778
+ let restOfMolecule = parse$2(rest, _mset(newCtx, ["bond", "bondType"], "single"));
42721
41779
  if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
42722
41780
  //TODO(colin): fix this awkwardness.
42723
41781
  restOfMolecule = [restOfMolecule];
@@ -42756,7 +41814,7 @@ function isModifierChar(s) {
42756
41814
  * ParseError: if the input is not valid SMILES or contains features not
42757
41815
  * yet implemented.
42758
41816
  */
42759
- function parse$1(smiles, ctx) {
41817
+ function parse$2(smiles, ctx) {
42760
41818
  if (!validate(smiles)) {
42761
41819
  throw new ParseError$1("Invalid input.");
42762
41820
  }
@@ -42781,7 +41839,7 @@ function parse$1(smiles, ctx) {
42781
41839
  return parseParenthesizedExpression(smiles, ctx);
42782
41840
  }
42783
41841
  var SmilesParser = {
42784
- parse: parse$1,
41842
+ parse: parse$2,
42785
41843
  ParseError: ParseError$1
42786
41844
  };
42787
41845
 
@@ -42789,7 +41847,7 @@ var SmilesParser = {
42789
41847
  const {
42790
41848
  layout
42791
41849
  } = MoleculeLayout;
42792
- const parse = SmilesParser.parse;
41850
+ const parse$1 = SmilesParser.parse;
42793
41851
  const ParseError = SmilesParser.ParseError;
42794
41852
  const borderSize = 30;
42795
41853
  class Molecule extends React__namespace.Component {
@@ -42816,7 +41874,7 @@ class Molecule extends React__namespace.Component {
42816
41874
  try {
42817
41875
  this.setState({
42818
41876
  // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
42819
- parsedSmiles: parse(smiles),
41877
+ parsedSmiles: parse$1(smiles),
42820
41878
  error: null
42821
41879
  });
42822
41880
  } catch (e) {
@@ -43029,15 +42087,15 @@ const _label = (graphie, labelStyle, pos, value, base) => {
43029
42087
  return graphie.label([pos, -0.53], Math.round(value * 100) / 100, "center");
43030
42088
  }
43031
42089
  if (labelStyle === "improper") {
43032
- const frac = KhanMath.toFraction(value);
42090
+ const frac = kmath.KhanMath.toFraction(value);
43033
42091
  return graphie.label([pos, -0.17], formatImproper(frac[0], frac[1]), "below");
43034
42092
  }
43035
42093
  if (labelStyle === "mixed") {
43036
- const frac = KhanMath.toFraction(value);
42094
+ const frac = kmath.KhanMath.toFraction(value);
43037
42095
  return graphie.label([pos, -0.17], formatMixed(frac[0], frac[1]), "below");
43038
42096
  }
43039
42097
  if (labelStyle === "non-reduced") {
43040
- const frac = KhanMath.toFraction(value);
42098
+ const frac = kmath.KhanMath.toFraction(value);
43041
42099
  return graphie.label([pos, -0.17], formatNonReduced(frac[0], frac[1], base), "below");
43042
42100
  }
43043
42101
  };
@@ -43073,7 +42131,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
43073
42131
  }
43074
42132
  const getDenom = x => kmath.number.toFraction(x)[1];
43075
42133
  const denoms = ___default["default"].map(fractions, getDenom);
43076
- base = ___default["default"].reduce(denoms, (x, y) => KhanMath.getLCM(x, y));
42134
+ base = ___default["default"].reduce(denoms, (x, y) => kmath.KhanMath.getLCM(x, y));
43077
42135
  } else {
43078
42136
  base = undefined;
43079
42137
  }
@@ -44609,7 +43667,7 @@ class Plotter extends React__namespace.Component {
44609
43667
  // If we have isMobile, we skip the 0 label.
44610
43668
  const initialY = isMobile ? c.scaleY : 0;
44611
43669
  for (let y = initialY; y <= c.dimY; y += c.scaleY) {
44612
- graphie.label([0, y], KhanMath.roundToApprox(y, 2), "left", /* isTeX */true /* for the \approx symbol */);
43670
+ graphie.label([0, y], kmath.KhanMath.roundToApprox(y, 2), "left", /* isTeX */true /* for the \approx symbol */);
44613
43671
 
44614
43672
  graphie.style({
44615
43673
  stroke: isMobile ? "#e9ebec" : "#000",
@@ -45477,7 +44535,7 @@ function scoreTable(userInput, rubric, strings) {
45477
44535
  message: null
45478
44536
  };
45479
44537
  }
45480
- const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
44538
+ const createValidator = perseusScore.KhanAnswerTypes.number.createValidatorFunctional;
45481
44539
  let message = null;
45482
44540
  const allCorrect = solution.every(function (rowSolution) {
45483
44541
  for (let i = 0; i < supplied.length; i++) {
@@ -45486,7 +44544,7 @@ function scoreTable(userInput, rubric, strings) {
45486
44544
  const cellSolution = rowSolution[i];
45487
44545
  const validator = createValidator(cellSolution, {
45488
44546
  simplify: true
45489
- }, strings);
44547
+ });
45490
44548
  const result = validator(cellSupplied);
45491
44549
  if (result.message) {
45492
44550
  message = result.message;
@@ -45909,7 +44967,7 @@ const init = function () {
45909
44967
 
45910
44968
  // This file is processed by a Rollup plugin (replace) to inject the production
45911
44969
  const libName = "@khanacademy/perseus";
45912
- const libVersion = "49.2.2";
44970
+ const libVersion = "50.0.0";
45913
44971
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45914
44972
 
45915
44973
  const apiVersion = {
@@ -46849,7 +45907,7 @@ const traverseRenderer$1 = function (rendererOptions, contentCallback, deepWidge
46849
45907
  newContent = modifiedContent;
46850
45908
  }
46851
45909
  }
46852
- const newWidgets = mapObject(rendererOptions.widgets || {}, function (widgetInfo, widgetId) {
45910
+ const newWidgets = perseusCore.mapObject(rendererOptions.widgets || {}, function (widgetInfo, widgetId) {
46853
45911
  // Widgets without info or a type are empty widgets, and
46854
45912
  // should always be renderable. It's also annoying to write
46855
45913
  // checks for this everywhere, so we just filter them out once and
@@ -48000,6 +47058,69 @@ function all(results) {
48000
47058
  return success(values);
48001
47059
  }
48002
47060
 
47061
+ class ErrorTrackingParseContext {
47062
+ constructor(path) {
47063
+ this.path = path;
47064
+ }
47065
+ failure(expected, badValue) {
47066
+ return failure([{
47067
+ expected: wrapInArray(expected),
47068
+ badValue,
47069
+ path: this.path
47070
+ }]);
47071
+ }
47072
+ forSubtree(key) {
47073
+ return new ErrorTrackingParseContext([...this.path, key]);
47074
+ }
47075
+ success(value) {
47076
+ return success(value);
47077
+ }
47078
+ }
47079
+ function wrapInArray(a) {
47080
+ return Array.isArray(a) ? a : [a];
47081
+ }
47082
+
47083
+ function formatPath(path) {
47084
+ return "(root)" + path.map(formatPathSegment).join("");
47085
+ }
47086
+ function formatPathSegment(segment) {
47087
+ if (typeof segment === "string") {
47088
+ return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
47089
+ }
47090
+ return `[${segment.toString()}]`;
47091
+ }
47092
+ const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
47093
+
47094
+ function message(failure) {
47095
+ const expected = conjoin(failure.expected);
47096
+ const path = formatPath(failure.path);
47097
+ const badValue = JSON.stringify(failure.badValue);
47098
+ return `At ${path} -- expected ${expected}, but got ${badValue}`;
47099
+ }
47100
+ function conjoin(items) {
47101
+ switch (items.length) {
47102
+ // TODO(benchristel): handle 0 if this is reused elsewhere.
47103
+ case 1:
47104
+ return items[0];
47105
+ case 2:
47106
+ return items.join(" or ");
47107
+ default:
47108
+ {
47109
+ const allButLast = items.slice(0, items.length - 1);
47110
+ const last = items[items.length - 1];
47111
+ return allButLast.join(", ") + ", or " + last;
47112
+ }
47113
+ }
47114
+ }
47115
+
47116
+ function parse(value, parser) {
47117
+ const result = parser(value, new ErrorTrackingParseContext([]));
47118
+ if (isFailure(result)) {
47119
+ return failure(result.detail.map(message).join("; "));
47120
+ }
47121
+ return result;
47122
+ }
47123
+
48003
47124
  const any = (rawValue, ctx) => ctx.success(rawValue);
48004
47125
 
48005
47126
  function array(elementParser) {
@@ -48285,7 +47406,6 @@ const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
48285
47406
  })),
48286
47407
  showEditor: boolean,
48287
47408
  showButtons: boolean,
48288
- width: number,
48289
47409
  height: number,
48290
47410
  static: defaulted(boolean, () => false)
48291
47411
  }));
@@ -48474,25 +47594,6 @@ function migrateV0ToV1(widget) {
48474
47594
  }
48475
47595
  const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
48476
47596
 
48477
- const parsePerseusRenderer = defaulted(object({
48478
- // TODO(benchristel): content is also defaulted to empty string in
48479
- // renderer.tsx. See if we can remove one default or the other.
48480
- content: defaulted(string, () => ""),
48481
- // This module has an import cycle with parseWidgetsMap, because the
48482
- // `group` widget can contain another renderer.
48483
- // The anonymous function below ensures that we don't try to access
48484
- // parseWidgetsMap before it's defined.
48485
- widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
48486
- metadata: optional(array(string)),
48487
- images: parseImages
48488
- }),
48489
- // Default value
48490
- () => ({
48491
- content: "",
48492
- widgets: {},
48493
- images: {}
48494
- }));
48495
-
48496
47597
  const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
48497
47598
  const parseGradedGroupWidgetOptions = object({
48498
47599
  title: defaulted(string, () => ""),
@@ -48563,27 +47664,27 @@ const parseGrapherWidget = parseWidget(constant("grapher"), object({
48563
47664
  availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
48564
47665
  correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
48565
47666
  type: constant("absolute_value"),
48566
- coords: pairOfPoints
47667
+ coords: nullable(pairOfPoints)
48567
47668
  })).withBranch("exponential", object({
48568
47669
  type: constant("exponential"),
48569
47670
  asymptote: pairOfPoints,
48570
- coords: pairOfPoints
47671
+ coords: nullable(pairOfPoints)
48571
47672
  })).withBranch("linear", object({
48572
47673
  type: constant("linear"),
48573
- coords: defaulted(pairOfPoints, () => [[-5, 5], [5, 5]])
47674
+ coords: nullable(pairOfPoints)
48574
47675
  })).withBranch("logarithm", object({
48575
47676
  type: constant("logarithm"),
48576
47677
  asymptote: pairOfPoints,
48577
- coords: pairOfPoints
47678
+ coords: nullable(pairOfPoints)
48578
47679
  })).withBranch("quadratic", object({
48579
47680
  type: constant("quadratic"),
48580
- coords: pairOfPoints
47681
+ coords: nullable(pairOfPoints)
48581
47682
  })).withBranch("sinusoid", object({
48582
47683
  type: constant("sinusoid"),
48583
- coords: pairOfPoints
47684
+ coords: nullable(pairOfPoints)
48584
47685
  })).withBranch("tangent", object({
48585
47686
  type: constant("tangent"),
48586
- coords: pairOfPoints
47687
+ coords: nullable(pairOfPoints)
48587
47688
  })).parser,
48588
47689
  graph: object({
48589
47690
  backgroundImage: object({
@@ -48619,13 +47720,13 @@ const parseGroupWidget = parseWidget(constant("group"),
48619
47720
 
48620
47721
  const parseIframeWidget = parseWidget(constant("iframe"), object({
48621
47722
  url: string,
48622
- settings: array(object({
47723
+ settings: optional(array(object({
48623
47724
  name: string,
48624
47725
  value: string
48625
- })),
47726
+ }))),
48626
47727
  width: union(number).or(string).parser,
48627
47728
  height: union(number).or(string).parser,
48628
- allowFullScreen: boolean,
47729
+ allowFullScreen: defaulted(boolean, () => false),
48629
47730
  allowTopNavigation: optional(boolean),
48630
47731
  static: defaulted(boolean, () => false)
48631
47732
  }));
@@ -48698,10 +47799,10 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
48698
47799
 
48699
47800
  const pairOfNumbers$1 = pair(number, number);
48700
47801
  const stringOrEmpty = defaulted(string, () => "");
48701
- const parseFunctionType = constant("function");
47802
+ const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
48702
47803
  const parseFunctionElement = object({
48703
- type: parseFunctionType,
48704
- key: string,
47804
+ type: constant("function"),
47805
+ key: parseKey,
48705
47806
  options: object({
48706
47807
  value: string,
48707
47808
  funcName: string,
@@ -48712,10 +47813,9 @@ const parseFunctionElement = object({
48712
47813
  strokeWidth: number
48713
47814
  })
48714
47815
  });
48715
- const parseLabelType = constant("label");
48716
47816
  const parseLabelElement = object({
48717
- type: parseLabelType,
48718
- key: string,
47817
+ type: constant("label"),
47818
+ key: parseKey,
48719
47819
  options: object({
48720
47820
  label: string,
48721
47821
  color: string,
@@ -48723,10 +47823,9 @@ const parseLabelElement = object({
48723
47823
  coordY: string
48724
47824
  })
48725
47825
  });
48726
- const parseLineType = constant("line");
48727
47826
  const parseLineElement = object({
48728
- type: parseLineType,
48729
- key: string,
47827
+ type: constant("line"),
47828
+ key: parseKey,
48730
47829
  options: object({
48731
47830
  color: string,
48732
47831
  startX: string,
@@ -48738,10 +47837,9 @@ const parseLineElement = object({
48738
47837
  arrows: string
48739
47838
  })
48740
47839
  });
48741
- const parseMovableLineType = constant("movable-line");
48742
47840
  const parseMovableLineElement = object({
48743
- type: parseMovableLineType,
48744
- key: string,
47841
+ type: constant("movable-line"),
47842
+ key: parseKey,
48745
47843
  options: object({
48746
47844
  startX: string,
48747
47845
  startY: string,
@@ -48758,10 +47856,9 @@ const parseMovableLineElement = object({
48758
47856
  constraintYMax: string
48759
47857
  })
48760
47858
  });
48761
- const parseMovablePointType = constant("movable-point");
48762
47859
  const parseMovablePointElement = object({
48763
- type: parseMovablePointType,
48764
- key: string,
47860
+ type: constant("movable-point"),
47861
+ key: parseKey,
48765
47862
  options: object({
48766
47863
  startX: string,
48767
47864
  startY: string,
@@ -48775,10 +47872,9 @@ const parseMovablePointElement = object({
48775
47872
  constraintYMax: stringOrEmpty
48776
47873
  })
48777
47874
  });
48778
- const parseParametricType = constant("parametric");
48779
47875
  const parseParametricElement = object({
48780
- type: parseParametricType,
48781
- key: string,
47876
+ type: constant("parametric"),
47877
+ key: parseKey,
48782
47878
  options: object({
48783
47879
  x: string,
48784
47880
  y: string,
@@ -48789,20 +47885,18 @@ const parseParametricElement = object({
48789
47885
  strokeWidth: number
48790
47886
  })
48791
47887
  });
48792
- const parsePointType = constant("point");
48793
47888
  const parsePointElement = object({
48794
- type: parsePointType,
48795
- key: string,
47889
+ type: constant("point"),
47890
+ key: parseKey,
48796
47891
  options: object({
48797
47892
  color: string,
48798
47893
  coordX: string,
48799
47894
  coordY: string
48800
47895
  })
48801
47896
  });
48802
- const parseRectangleType = constant("rectangle");
48803
47897
  const parseRectangleElement = object({
48804
- type: parseRectangleType,
48805
- key: string,
47898
+ type: constant("rectangle"),
47899
+ key: parseKey,
48806
47900
  options: object({
48807
47901
  color: string,
48808
47902
  coordX: string,
@@ -48923,7 +48017,7 @@ const parsePerseusGraphTypeSinusoid = object({
48923
48017
  coord: optional(pairOfNumbers)
48924
48018
  });
48925
48019
  const parsePerseusGraphType = discriminatedUnionOn("type").withBranch("angle", parsePerseusGraphTypeAngle).withBranch("circle", parsePerseusGraphTypeCircle).withBranch("linear", parsePerseusGraphTypeLinear).withBranch("linear-system", parsePerseusGraphTypeLinearSystem).withBranch("none", parsePerseusGraphTypeNone).withBranch("point", parsePerseusGraphTypePoint).withBranch("polygon", parsePerseusGraphTypePolygon).withBranch("quadratic", parsePerseusGraphTypeQuadratic).withBranch("ray", parsePerseusGraphTypeRay).withBranch("segment", parsePerseusGraphTypeSegment).withBranch("sinusoid", parsePerseusGraphTypeSinusoid).parser;
48926
- const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
48020
+ const parseLockedFigureColor = enumeration(...perseusCore.lockedFigureColorNames);
48927
48021
  const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
48928
48022
  const parseLockedLineStyle = enumeration("solid", "dashed");
48929
48023
  const parseLockedLabelType = object({
@@ -48948,8 +48042,8 @@ const parseLockedLineType = object({
48948
48042
  points: pair(parseLockedPointType, parseLockedPointType),
48949
48043
  color: parseLockedFigureColor,
48950
48044
  lineStyle: parseLockedLineStyle,
48951
- showPoint1: boolean,
48952
- showPoint2: boolean,
48045
+ showPoint1: defaulted(boolean, () => false),
48046
+ showPoint2: defaulted(boolean, () => false),
48953
48047
  // TODO(benchristel): default labels to empty array?
48954
48048
  labels: optional(array(parseLockedLabelType)),
48955
48049
  ariaLabel: optional(string)
@@ -48996,7 +48090,7 @@ const parseLockedFunctionType = object({
48996
48090
  labels: optional(array(parseLockedLabelType)),
48997
48091
  ariaLabel: optional(string)
48998
48092
  });
48999
- const parseLockedFigure = union(parseLockedPointType).or(parseLockedLineType).or(parseLockedVectorType).or(parseLockedEllipseType).or(parseLockedPolygonType).or(parseLockedFunctionType).or(parseLockedLabelType).parser;
48093
+ 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;
49000
48094
  const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
49001
48095
  step: pairOfNumbers,
49002
48096
  // TODO(benchristel): rather than making gridStep and snapStep
@@ -49064,7 +48158,14 @@ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matri
49064
48158
  }));
49065
48159
 
49066
48160
  const parseMeasurerWidget = parseWidget(constant("measurer"), object({
49067
- image: parsePerseusImageBackground,
48161
+ // The default value for image comes from measurer.tsx.
48162
+ // See parse-perseus-json/README.md for why we want to duplicate the
48163
+ // defaults here.
48164
+ image: defaulted(parsePerseusImageBackground, () => ({
48165
+ url: null,
48166
+ top: 0,
48167
+ left: 0
48168
+ })),
49068
48169
  showProtractor: boolean,
49069
48170
  showRuler: boolean,
49070
48171
  rulerLabel: string,
@@ -49173,11 +48274,17 @@ const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), objec
49173
48274
  const parsePlotterWidget = parseWidget(constant("plotter"), object({
49174
48275
  labels: array(string),
49175
48276
  categories: array(string),
49176
- type: enumeration(...plotterPlotTypes),
48277
+ type: enumeration(...perseusCore.plotterPlotTypes),
49177
48278
  maxY: number,
49178
- scaleY: number,
48279
+ // The default value for scaleY comes from plotter.tsx.
48280
+ // See parse-perseus-json/README.md for why we want to duplicate the
48281
+ // defaults here.
48282
+ scaleY: defaulted(number, () => 1),
49179
48283
  labelInterval: optional(nullable(number)),
49180
- snapsPerLine: number,
48284
+ // The default value for snapsPerLine comes from plotter.tsx.
48285
+ // See parse-perseus-json/README.md for why we want to duplicate the
48286
+ // defaults here.
48287
+ snapsPerLine: defaulted(number, () => 2),
49181
48288
  starting: array(number),
49182
48289
  correct: array(number),
49183
48290
  picUrl: optional(nullable(string)),
@@ -49255,12 +48362,12 @@ const parseWidgetsMap = (rawValue, ctx) => {
49255
48362
  return ctx.success(widgetsMap);
49256
48363
  };
49257
48364
  const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
49258
- let [key, widget] = _ref;
49259
- const keyComponentsResult = parseWidgetMapKeyComponents(key.split(" "), ctx);
49260
- if (isFailure(keyComponentsResult)) {
49261
- return keyComponentsResult;
48365
+ let [id, widget] = _ref;
48366
+ const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
48367
+ if (isFailure(idComponentsResult)) {
48368
+ return idComponentsResult;
49262
48369
  }
49263
- const [type, id] = keyComponentsResult.value;
48370
+ const [type, n] = idComponentsResult.value;
49264
48371
  function parseAndAssign(key, parse) {
49265
48372
  const widgetResult = parse(widget, ctx);
49266
48373
  if (isFailure(widgetResult)) {
@@ -49271,88 +48378,88 @@ const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
49271
48378
  }
49272
48379
  switch (type) {
49273
48380
  case "categorizer":
49274
- return parseAndAssign(`categorizer ${id}`, parseCategorizerWidget);
48381
+ return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
49275
48382
  case "cs-program":
49276
- return parseAndAssign(`cs-program ${id}`, parseCSProgramWidget);
48383
+ return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
49277
48384
  case "definition":
49278
- return parseAndAssign(`definition ${id}`, parseDefinitionWidget);
48385
+ return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
49279
48386
  case "dropdown":
49280
- return parseAndAssign(`dropdown ${id}`, parseDropdownWidget);
48387
+ return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
49281
48388
  case "explanation":
49282
- return parseAndAssign(`explanation ${id}`, parseExplanationWidget);
48389
+ return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
49283
48390
  case "expression":
49284
- return parseAndAssign(`expression ${id}`, parseExpressionWidget);
48391
+ return parseAndAssign(`expression ${n}`, parseExpressionWidget);
49285
48392
  case "grapher":
49286
- return parseAndAssign(`grapher ${id}`, parseGrapherWidget);
48393
+ return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
49287
48394
  case "group":
49288
- return parseAndAssign(`group ${id}`, parseGroupWidget);
48395
+ return parseAndAssign(`group ${n}`, parseGroupWidget);
49289
48396
  case "graded-group":
49290
- return parseAndAssign(`graded-group ${id}`, parseGradedGroupWidget);
48397
+ return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
49291
48398
  case "graded-group-set":
49292
- return parseAndAssign(`graded-group-set ${id}`, parseGradedGroupSetWidget);
48399
+ return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
49293
48400
  case "iframe":
49294
- return parseAndAssign(`iframe ${id}`, parseIframeWidget);
48401
+ return parseAndAssign(`iframe ${n}`, parseIframeWidget);
49295
48402
  case "image":
49296
- return parseAndAssign(`image ${id}`, parseImageWidget);
48403
+ return parseAndAssign(`image ${n}`, parseImageWidget);
49297
48404
  case "input-number":
49298
- return parseAndAssign(`input-number ${id}`, parseInputNumberWidget);
48405
+ return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
49299
48406
  case "interaction":
49300
- return parseAndAssign(`interaction ${id}`, parseInteractionWidget);
48407
+ return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
49301
48408
  case "interactive-graph":
49302
- return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
48409
+ return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
49303
48410
  case "label-image":
49304
- return parseAndAssign(`label-image ${id}`, parseLabelImageWidget);
48411
+ return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
49305
48412
  case "matcher":
49306
- return parseAndAssign(`matcher ${id}`, parseMatcherWidget);
48413
+ return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
49307
48414
  case "matrix":
49308
- return parseAndAssign(`matrix ${id}`, parseMatrixWidget);
48415
+ return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
49309
48416
  case "measurer":
49310
- return parseAndAssign(`measurer ${id}`, parseMeasurerWidget);
48417
+ return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
49311
48418
  case "molecule-renderer":
49312
- return parseAndAssign(`molecule-renderer ${id}`, parseMoleculeRendererWidget);
48419
+ return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
49313
48420
  case "number-line":
49314
- return parseAndAssign(`number-line ${id}`, parseNumberLineWidget);
48421
+ return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
49315
48422
  case "numeric-input":
49316
- return parseAndAssign(`numeric-input ${id}`, parseNumericInputWidget);
48423
+ return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
49317
48424
  case "orderer":
49318
- return parseAndAssign(`orderer ${id}`, parseOrdererWidget);
48425
+ return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
49319
48426
  case "passage":
49320
- return parseAndAssign(`passage ${id}`, parsePassageWidget);
48427
+ return parseAndAssign(`passage ${n}`, parsePassageWidget);
49321
48428
  case "passage-ref":
49322
- return parseAndAssign(`passage-ref ${id}`, parsePassageRefWidget);
48429
+ return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
49323
48430
  case "passage-ref-target":
49324
48431
  // NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
49325
48432
  // used in test content. See:
49326
48433
  // https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
49327
- return parseAndAssign(`passage-ref-target ${id}`, any);
48434
+ return parseAndAssign(`passage-ref-target ${n}`, any);
49328
48435
  case "phet-simulation":
49329
- return parseAndAssign(`phet-simulation ${id}`, parsePhetSimulationWidget);
48436
+ return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
49330
48437
  case "plotter":
49331
- return parseAndAssign(`plotter ${id}`, parsePlotterWidget);
48438
+ return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
49332
48439
  case "python-program":
49333
- return parseAndAssign(`python-program ${id}`, parsePythonProgramWidget);
48440
+ return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
49334
48441
  case "radio":
49335
- return parseAndAssign(`radio ${id}`, parseRadioWidget);
48442
+ return parseAndAssign(`radio ${n}`, parseRadioWidget);
49336
48443
  case "sorter":
49337
- return parseAndAssign(`sorter ${id}`, parseSorterWidget);
48444
+ return parseAndAssign(`sorter ${n}`, parseSorterWidget);
49338
48445
  case "table":
49339
- return parseAndAssign(`table ${id}`, parseTableWidget);
48446
+ return parseAndAssign(`table ${n}`, parseTableWidget);
49340
48447
  case "video":
49341
- return parseAndAssign(`video ${id}`, parseVideoWidget);
48448
+ return parseAndAssign(`video ${n}`, parseVideoWidget);
49342
48449
  case "sequence":
49343
48450
  // sequence is a deprecated widget type, and the corresponding
49344
48451
  // widget component no longer exists.
49345
- return parseAndAssign(`sequence ${id}`, parseDeprecatedWidget);
48452
+ return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
49346
48453
  case "lights-puzzle":
49347
- return parseAndAssign(`lights-puzzle ${id}`, parseDeprecatedWidget);
48454
+ return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
49348
48455
  case "simulator":
49349
- return parseAndAssign(`simulator ${id}`, parseDeprecatedWidget);
48456
+ return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
49350
48457
  case "transformer":
49351
- return parseAndAssign(`transformer ${id}`, parseDeprecatedWidget);
48458
+ return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
49352
48459
  default:
49353
48460
  if (getWidget(type)) {
49354
48461
  // @ts-expect-error - 'type' is not a valid widget type
49355
- return parseAndAssign(`${type} ${id}`, any);
48462
+ return parseAndAssign(`${type} ${n}`, any);
49356
48463
  }
49357
48464
  return ctx.failure("a valid widget type", type);
49358
48465
  }
@@ -49364,11 +48471,32 @@ const parseDeprecatedWidget = parseWidget(
49364
48471
  object({}));
49365
48472
  const parseStringToPositiveInt = (rawValue, ctx) => {
49366
48473
  if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
49367
- return ctx.failure("numeric string", rawValue);
48474
+ return ctx.failure("a string representing a positive integer", rawValue);
49368
48475
  }
49369
48476
  return ctx.success(+rawValue);
49370
48477
  };
49371
- const parseWidgetMapKeyComponents = pair(string, parseStringToPositiveInt);
48478
+ const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
48479
+
48480
+ const parsePerseusRenderer = defaulted(object({
48481
+ // TODO(benchristel): content is also defaulted to empty string in
48482
+ // renderer.tsx. See if we can remove one default or the other.
48483
+ content: defaulted(string, () => ""),
48484
+ // This module has an import cycle with parseWidgetsMap, because the
48485
+ // `group` widget can contain another renderer.
48486
+ // The anonymous function below ensures that we don't try to access
48487
+ // parseWidgetsMap before it's defined.
48488
+ widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
48489
+ metadata: optional(array(string)),
48490
+ images: parseImages
48491
+ }),
48492
+ // Default value
48493
+ () => ({
48494
+ content: "",
48495
+ widgets: {},
48496
+ images: {}
48497
+ }));
48498
+
48499
+ const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
49372
48500
 
49373
48501
  const parseHint = object({
49374
48502
  replace: optional(boolean),
@@ -49378,10 +48506,10 @@ const parseHint = object({
49378
48506
  images: parseImages
49379
48507
  });
49380
48508
 
49381
- object({
48509
+ const parsePerseusItem$1 = object({
49382
48510
  question: parsePerseusRenderer,
49383
48511
  hints: defaulted(array(parseHint), () => []),
49384
- answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
48512
+ answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...perseusCore.ItemExtras), boolean)).parser,
49385
48513
  itemDataVersion: optional(object({
49386
48514
  major: number,
49387
48515
  minor: number
@@ -49417,6 +48545,7 @@ function migrateAnswerArea(rawValue, ctx) {
49417
48545
  * Why not just use JSON.parse? We want:
49418
48546
  * - To make sure types are correct
49419
48547
  * - To give us a central place to validate/transform output if needed
48548
+ * @deprecated - use parseAndMigratePerseusItem instead
49420
48549
  * @param {string} json - the stringified PerseusItem JSON
49421
48550
  * @returns {PerseusItem} the parsed PerseusItem object
49422
48551
  */
@@ -49428,6 +48557,60 @@ function parsePerseusItem(json) {
49428
48557
  }
49429
48558
  throw new Error("Something went wrong.");
49430
48559
  }
48560
+ /**
48561
+ * Parses a PerseusItem from a JSON string, migrates old formats to the latest
48562
+ * schema, and runtime-typechecks the result. Use this to parse assessmentItem
48563
+ * data.
48564
+ *
48565
+ * @returns a {@link Result} of the parsed PerseusItem. If the result is a
48566
+ * failure, it will contain an error message describing where in the tree
48567
+ * parsing failed.
48568
+ * @throws SyntaxError if the argument is not well-formed JSON.
48569
+ */
48570
+ function parseAndMigratePerseusItem(json) {
48571
+ throwErrorIfCheatingDetected();
48572
+ const object = JSON.parse(json);
48573
+ const result = parse(object, parsePerseusItem$1);
48574
+ if (isFailure(result)) {
48575
+ return failure({
48576
+ message: result.detail,
48577
+ invalidObject: object
48578
+ });
48579
+ }
48580
+ return result;
48581
+ }
48582
+
48583
+ /**
48584
+ * Parses a PerseusArticle from a JSON string, migrates old formats to the
48585
+ * latest schema, and runtime-typechecks the result.
48586
+ *
48587
+ * @returns a {@link Result} of the parsed PerseusArticle. If the result is a
48588
+ * failure, it will contain an error message describing where in the tree
48589
+ * parsing failed.
48590
+ * @throws SyntaxError if the argument is not well-formed JSON.
48591
+ */
48592
+ function parseAndMigratePerseusArticle(json) {
48593
+ throwErrorIfCheatingDetected();
48594
+ const object = JSON.parse(json);
48595
+ const result = parse(object, parsePerseusArticle);
48596
+ if (isFailure(result)) {
48597
+ return failure({
48598
+ message: result.detail,
48599
+ invalidObject: object
48600
+ });
48601
+ }
48602
+ return result;
48603
+ }
48604
+
48605
+ /**
48606
+ * Tries to block a cheating vector that relies on monkey-patching JSON.parse.
48607
+ */
48608
+ // TODO(LEMS-2331): delete this function once server-side scoring is done.
48609
+ function throwErrorIfCheatingDetected() {
48610
+ if (!isRealJSONParse(JSON.parse)) {
48611
+ throw new Error("Something went wrong.");
48612
+ }
48613
+ }
49431
48614
 
49432
48615
  const genericPerseusItemData = {
49433
48616
  question: {
@@ -49666,15 +48849,12 @@ exports.HintRenderer = HintRenderer;
49666
48849
  exports.HintsRenderer = HintsRenderer;
49667
48850
  exports.InputNumber = InputNumber$1;
49668
48851
  exports.InteractiveGraphWidget = InteractiveGraph$1;
49669
- exports.ItemExtras = ItemExtras;
49670
48852
  exports.JiptParagraphs = JiptParagraphs;
49671
48853
  exports.KhanColors = KhanColors;
49672
- exports.KhanMath = KhanMath;
49673
48854
  exports.LoadingContext = context;
49674
48855
  exports.Log = Log;
49675
48856
  exports.MatrixWidget = Matrix$1;
49676
48857
  exports.NumericInput = NumericInput$1;
49677
- exports.PerseusExpressionAnswerFormConsidered = PerseusExpressionAnswerFormConsidered;
49678
48858
  exports.PerseusI18nContext = PerseusI18nContext;
49679
48859
  exports.PerseusI18nContextProvider = PerseusI18nContextProvider;
49680
48860
  exports.PerseusMarkdown = PerseusMarkdown;
@@ -49730,20 +48910,21 @@ exports.iconTrash = iconTrash;
49730
48910
  exports.init = init;
49731
48911
  exports.injectWidgets = injectWidgets;
49732
48912
  exports.interactiveSizes = interactiveSizes$1;
48913
+ exports.isFailure = isFailure;
49733
48914
  exports.isItemRenderableByVersion = isItemRenderableByVersion;
48915
+ exports.isSuccess = isSuccess;
49734
48916
  exports.isWidgetIdInContent = isWidgetIdInContent;
49735
48917
  exports.isWrongAnswerSupported = isWrongAnswerSupported;
49736
48918
  exports.itemDataVersion = itemDataVersion;
49737
48919
  exports.itemVersion = ItemVersion;
49738
48920
  exports.libVersion = libVersion;
49739
- exports.lockedFigureColors = lockedFigureColors;
49740
- exports.lockedFigureFillStyles = lockedFigureFillStyles;
49741
48921
  exports.makeSafeUrl = makeSafeUrl;
49742
48922
  exports.mathOnlyParser = mathOnlyParser;
49743
48923
  exports.mediaQueries = mediaQueries;
48924
+ exports.parseAndMigratePerseusArticle = parseAndMigratePerseusArticle;
48925
+ exports.parseAndMigratePerseusItem = parseAndMigratePerseusItem;
49744
48926
  exports.parseDataFromJSONP = parseDataFromJSONP;
49745
48927
  exports.parsePerseusItem = parsePerseusItem;
49746
- exports.plotterPlotTypes = plotterPlotTypes;
49747
48928
  exports.preprocessTex = preprocessTex;
49748
48929
  exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
49749
48930
  exports.scorePerseusItem = scorePerseusItem;