@khanacademy/perseus 51.0.0 → 52.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 (130) hide show
  1. package/dist/es/index.js +682 -1997
  2. package/dist/es/index.js.map +1 -1
  3. package/dist/es/strings.js +103 -3
  4. package/dist/es/strings.js.map +1 -1
  5. package/dist/index.d.ts +36 -6
  6. package/dist/index.js +737 -2005
  7. package/dist/index.js.map +1 -1
  8. package/dist/item-version.d.ts +1 -1
  9. package/dist/renderer-util.d.ts +7 -2
  10. package/dist/strings.d.ts +100 -0
  11. package/dist/strings.js +159 -14
  12. package/dist/strings.js.map +1 -1
  13. package/dist/types.d.ts +13 -12
  14. package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +1 -1
  15. package/dist/widget-type-utils.d.ts +1 -22
  16. package/dist/widgets/categorizer/categorizer.d.ts +5 -5
  17. package/dist/widgets/cs-program/cs-program.d.ts +3 -2
  18. package/dist/widgets/dropdown/dropdown.d.ts +2 -1
  19. package/dist/widgets/expression/expression.d.ts +8 -10
  20. package/dist/widgets/grapher/grapher.d.ts +2 -1
  21. package/dist/widgets/group/group.d.ts +4 -2
  22. package/dist/widgets/group/score-group.d.ts +2 -2
  23. package/dist/widgets/group/validate-group.d.ts +4 -0
  24. package/dist/widgets/iframe/iframe.d.ts +3 -2
  25. package/dist/widgets/input-number/input-number.d.ts +2 -1
  26. package/dist/widgets/interactive-graph.d.ts +2 -1
  27. package/dist/widgets/interactive-graphs/graphs/linear.d.ts +2 -0
  28. package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +12 -1
  29. package/dist/widgets/interactive-graphs/graphs/segment.d.ts +2 -0
  30. package/dist/widgets/interactive-graphs/graphs/utils.d.ts +6 -0
  31. package/dist/widgets/label-image/label-image.d.ts +5 -4
  32. package/dist/widgets/matcher/matcher.d.ts +2 -1
  33. package/dist/widgets/matrix/matrix.d.ts +4 -2
  34. package/dist/widgets/measurer/measurer.d.ts +3 -8
  35. package/dist/widgets/mock-widgets/mock-widget-types.d.ts +17 -0
  36. package/dist/widgets/mock-widgets/mock-widget.d.ts +5 -4
  37. package/dist/widgets/mock-widgets/score-mock-widget.d.ts +2 -1
  38. package/dist/widgets/mock-widgets/validate-mock-widget.d.ts +4 -0
  39. package/dist/widgets/number-line/number-line.d.ts +3 -1
  40. package/dist/widgets/numeric-input/numeric-input.d.ts +2 -1
  41. package/dist/widgets/orderer/orderer.d.ts +6 -2
  42. package/dist/widgets/passage-ref/passage-ref.d.ts +2 -5
  43. package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +0 -4
  44. package/dist/widgets/plotter/plotter.d.ts +4 -2
  45. package/dist/widgets/radio/radio-component.d.ts +1 -1
  46. package/dist/widgets/radio/radio.d.ts +5 -9
  47. package/dist/widgets/sorter/sorter.d.ts +3 -1
  48. package/dist/widgets/table/table.d.ts +3 -1
  49. package/dist/widgets.d.ts +3 -2
  50. package/package.json +14 -14
  51. package/dist/util/is-real-json-parse.d.ts +0 -1
  52. package/dist/util/parse-perseus-json/error-tracking-parse-context.d.ts +0 -9
  53. package/dist/util/parse-perseus-json/exhaustive-test-tool/index.d.ts +0 -2
  54. package/dist/util/parse-perseus-json/general-purpose-parsers/any.d.ts +0 -2
  55. package/dist/util/parse-perseus-json/general-purpose-parsers/array.d.ts +0 -2
  56. package/dist/util/parse-perseus-json/general-purpose-parsers/array.typetest.d.ts +0 -1
  57. package/dist/util/parse-perseus-json/general-purpose-parsers/boolean.d.ts +0 -2
  58. package/dist/util/parse-perseus-json/general-purpose-parsers/constant.d.ts +0 -4
  59. package/dist/util/parse-perseus-json/general-purpose-parsers/convert.d.ts +0 -2
  60. package/dist/util/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +0 -2
  61. package/dist/util/parse-perseus-json/general-purpose-parsers/discriminated-union.d.ts +0 -21
  62. package/dist/util/parse-perseus-json/general-purpose-parsers/discriminated-union.typetest.d.ts +0 -1
  63. package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.d.ts +0 -2
  64. package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.typetest.d.ts +0 -1
  65. package/dist/util/parse-perseus-json/general-purpose-parsers/index.d.ts +0 -17
  66. package/dist/util/parse-perseus-json/general-purpose-parsers/is-object.d.ts +0 -1
  67. package/dist/util/parse-perseus-json/general-purpose-parsers/nullable.d.ts +0 -2
  68. package/dist/util/parse-perseus-json/general-purpose-parsers/number.d.ts +0 -2
  69. package/dist/util/parse-perseus-json/general-purpose-parsers/object.d.ts +0 -6
  70. package/dist/util/parse-perseus-json/general-purpose-parsers/optional.d.ts +0 -2
  71. package/dist/util/parse-perseus-json/general-purpose-parsers/pair.d.ts +0 -2
  72. package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.d.ts +0 -7
  73. package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.typetest.d.ts +0 -1
  74. package/dist/util/parse-perseus-json/general-purpose-parsers/record.d.ts +0 -2
  75. package/dist/util/parse-perseus-json/general-purpose-parsers/string-to-number.d.ts +0 -2
  76. package/dist/util/parse-perseus-json/general-purpose-parsers/string.d.ts +0 -2
  77. package/dist/util/parse-perseus-json/general-purpose-parsers/test-helpers.d.ts +0 -8
  78. package/dist/util/parse-perseus-json/general-purpose-parsers/trio.d.ts +0 -2
  79. package/dist/util/parse-perseus-json/general-purpose-parsers/union.d.ts +0 -7
  80. package/dist/util/parse-perseus-json/general-purpose-parsers/union.typetest.d.ts +0 -1
  81. package/dist/util/parse-perseus-json/general-purpose-parsers/unknown.d.ts +0 -2
  82. package/dist/util/parse-perseus-json/index.d.ts +0 -45
  83. package/dist/util/parse-perseus-json/object-path.d.ts +0 -2
  84. package/dist/util/parse-perseus-json/parse-failure-detail.d.ts +0 -2
  85. package/dist/util/parse-perseus-json/parse.d.ts +0 -3
  86. package/dist/util/parse-perseus-json/parser-types.d.ts +0 -30
  87. package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +0 -3
  88. package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +0 -3
  89. package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +0 -3
  90. package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +0 -3
  91. package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +0 -3
  92. package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +0 -3
  93. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +0 -3
  94. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +0 -16
  95. package/dist/util/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +0 -3
  96. package/dist/util/parse-perseus-json/perseus-parsers/group-widget.d.ts +0 -3
  97. package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +0 -3
  98. package/dist/util/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +0 -3
  99. package/dist/util/parse-perseus-json/perseus-parsers/image-widget.d.ts +0 -3
  100. package/dist/util/parse-perseus-json/perseus-parsers/images-map.d.ts +0 -5
  101. package/dist/util/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +0 -3
  102. package/dist/util/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +0 -3
  103. package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +0 -3
  104. package/dist/util/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +0 -3
  105. package/dist/util/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +0 -3
  106. package/dist/util/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +0 -3
  107. package/dist/util/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +0 -3
  108. package/dist/util/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +0 -3
  109. package/dist/util/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +0 -3
  110. package/dist/util/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +0 -3
  111. package/dist/util/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +0 -3
  112. package/dist/util/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +0 -3
  113. package/dist/util/parse-perseus-json/perseus-parsers/passage-widget.d.ts +0 -3
  114. package/dist/util/parse-perseus-json/perseus-parsers/perseus-article.d.ts +0 -3
  115. package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +0 -3
  116. package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +0 -3
  117. package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +0 -3
  118. package/dist/util/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +0 -3
  119. package/dist/util/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +0 -3
  120. package/dist/util/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +0 -3
  121. package/dist/util/parse-perseus-json/perseus-parsers/radio-widget.d.ts +0 -3
  122. package/dist/util/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +0 -3
  123. package/dist/util/parse-perseus-json/perseus-parsers/table-widget.d.ts +0 -3
  124. package/dist/util/parse-perseus-json/perseus-parsers/versioned-widget-options.d.ts +0 -36
  125. package/dist/util/parse-perseus-json/perseus-parsers/video-widget.d.ts +0 -3
  126. package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +0 -7
  127. package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +0 -3
  128. package/dist/util/parse-perseus-json/result.d.ts +0 -16
  129. package/dist/util/snowman-utils.d.ts +0 -17
  130. package/dist/widgets/categorizer/categorizer.util.d.ts +0 -17
package/dist/es/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import * as KAS from '@khanacademy/kas';
2
2
  import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
- import { PerseusError, Errors, getDecimalSeparator, approximateDeepEqual, pluck, mapObject, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getMatrixSize, addLibraryVersionToPerseusDebug, lockedFigureColorNames, plotterPlotTypes, ItemExtras } from '@khanacademy/perseus-core';
3
+ import { PerseusError, Errors, expressionLogic, getExpressionPublicWidgetOptions, getDecimalSeparator, approximateDeepEqual, pluck, getWidgetIdsFromContent, mapObject, passageRefLogic, radioLogic, getCategorizerPublicWidgetOptions, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getMatrixSize, measurerLogic, getOrdererPublicWidgetOptions, addLibraryVersionToPerseusDebug, getWidgetIdsFromContentByType } from '@khanacademy/perseus-core';
4
+ export { isFailure, isSuccess, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parsePerseusItem } from '@khanacademy/perseus-core';
4
5
  import * as PerseusLinter from '@khanacademy/perseus-linter';
5
6
  import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
6
- import { scoreExpression, KhanAnswerTypes, scoreInputNumber, inputNumberAnswerTypes, scoreNumericInput, scoreRadio, scoreCategorizer, scoreCSProgram, scoreDropdown, scoreGrapher, scoreIframe, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, scoreNumberLine, scoreOrderer, scorePlotter, scoreSorter, scoreTable } from '@khanacademy/perseus-score';
7
+ import { scoreExpression, validateExpression, KhanAnswerTypes, scoreInputNumber, inputNumberAnswerTypes, scoreNumericInput, scoreRadio, validateRadio, scoreCategorizer, validateCategorizer, scoreCSProgram, scoreDropdown, validateDropdown, scoreGrapher, scoreIframe, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, validateMatrix, scoreNumberLine, validateNumberLine, scoreOrderer, validateOrderer, scorePlotter, validatePlotter, scoreSorter, validateSorter, scoreTable, validateTable } from '@khanacademy/perseus-score';
7
8
  import { View, RenderStateRoot, Id, useOnMountEffect, useLatestRef, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
8
9
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
9
10
  import { HeadingMedium, LabelSmall, LabelLarge, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
@@ -22,7 +23,7 @@ import { v4 } from 'uuid';
22
23
  import PropTypes from 'prop-types';
23
24
  import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
24
25
  import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
25
- import { point, KhanMath, number as number$1, vector as vector$3, line, geometry, angles, coefficients } from '@khanacademy/kmath';
26
+ import { point, KhanMath, number, vector as vector$3, line, geometry, angles, coefficients } from '@khanacademy/kmath';
26
27
  import SimpleMarkdown from '@khanacademy/simple-markdown';
27
28
  import { pureMarkdownRules } from '@khanacademy/pure-markdown';
28
29
  import { TextField } from '@khanacademy/wonder-blocks-form';
@@ -232,14 +233,36 @@ const strings = {
232
233
  srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
233
234
  srAngleGraphAriaLabel: "An angle on a coordinate plane.",
234
235
  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",
236
+ srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
237
+ srMultipleSegmentGraphAriaLabel: "%(countOfSegments)s line segments on a coordinate plane.",
238
+ srMultipleSegmentIndividualLabel: "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s.",
239
+ srSingleSegmentLabel: "Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s.",
240
+ srSegmentLength: "Segment length %(length)s units.",
241
+ srSingleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.",
242
+ srMultipleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s on segment %(indexOfSegment)s at %(x)s comma %(y)s.",
243
+ srSegmentGrabHandle: "Segment from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.",
235
244
  srLinearSystemGraph: "Two lines on a coordinate plane.",
236
245
  srLinearSystemPoints: "Line %(lineNumber)s has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
237
246
  srLinearSystemPoint: "Point %(pointSequence)s on line %(lineNumber)s at %(x)s comma %(y)s.",
238
247
  srRayGraph: "A ray on a coordinate plane.",
239
248
  srRayPoints: "The endpoint is at %(point1X)s comma %(point1Y)s and the ray goes through point %(point2X)s comma %(point2Y)s.",
240
249
  srRayGrabHandle: "Ray with endpoint %(point1X)s comma %(point1Y)s going through point %(point2X)s comma %(point2Y)s.",
241
- srRayEndpoint: "Endpoint at %(point1X)s comma %(point1Y)s.",
242
- srRayTerminalPoint: "Through point at %(point2X)s comma %(point2Y)s."
250
+ srRayEndpoint: "Endpoint at %(x)s comma %(y)s.",
251
+ srRayTerminalPoint: "Through point at %(x)s comma %(y)s.",
252
+ srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
253
+ srQuadraticFaceUp: "The parabola opens upward.",
254
+ srQuadraticFaceDown: "The parabola opens downward.",
255
+ srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
256
+ srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
257
+ srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
258
+ srQuadraticGraphVertexQuadrant: "Vertex is in quadrant %(quadrant)s.",
259
+ srQuadraticTwoXIntercepts: "The X-intercepts are at %(intercept1)s comma 0 and %(intercept2)s comma 0.",
260
+ srQuadraticOneXIntercept: "The X-intercept is at %(intercept)s comma 0.",
261
+ srQuadraticYIntercept: "The Y-intercept is at 0 comma %(intercept)s.",
262
+ srQuadraticPointOrigin: "Point %(pointNumber)s on parabola at the origin.",
263
+ srQuadraticPointAxis: "Point %(pointNumber)s on parabola at %(x)s comma %(y)s.",
264
+ srQuadraticPointQuadrant: "Point %(pointNumber)s on parabola in quadrant %(quadrant)s at %(x)s comma %(y)s.",
265
+ srQuadraticInteractiveElements: "Parabola with points at %(point1X)s comma %(point1Y)s, %(point2X)s comma %(point2Y)s, and %(point3X)s comma %(point3Y)s."
243
266
  // The above strings are used for interactive graph SR descriptions.
244
267
  };
245
268
 
@@ -509,6 +532,43 @@ const mockStrings = {
509
532
  endingSideX,
510
533
  endingSideY
511
534
  }) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}.`,
535
+ srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
536
+ srMultipleSegmentGraphAriaLabel: ({
537
+ countOfSegments
538
+ }) => `${countOfSegments} segments on a coordinate plane.`,
539
+ srMultipleSegmentIndividualLabel: ({
540
+ point1X,
541
+ point1Y,
542
+ point2X,
543
+ point2Y,
544
+ indexOfSegment
545
+ }) => `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`,
546
+ srSingleSegmentLabel: ({
547
+ point1X,
548
+ point1Y,
549
+ point2X,
550
+ point2Y
551
+ }) => `Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`,
552
+ srSegmentLength: ({
553
+ length
554
+ }) => `Segment length ${length} units.`,
555
+ srSingleSegmentGraphEndpointAriaLabel: ({
556
+ endpointNumber,
557
+ x,
558
+ y
559
+ }) => `Endpoint ${endpointNumber} at ${x} comma ${y}.`,
560
+ srMultipleSegmentGraphEndpointAriaLabel: ({
561
+ endpointNumber,
562
+ x,
563
+ y,
564
+ indexOfSegment
565
+ }) => `Endpoint ${endpointNumber} on segment ${indexOfSegment} at ${x} comma ${y}.`,
566
+ srSegmentGrabHandle: ({
567
+ point1X,
568
+ point1Y,
569
+ point2X,
570
+ point2Y
571
+ }) => `Segment from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`,
512
572
  srLinearSystemGraph: "Two lines on a coordinate plane.",
513
573
  srLinearSystemPoints: ({
514
574
  lineNumber,
@@ -543,7 +603,48 @@ const mockStrings = {
543
603
  srRayTerminalPoint: ({
544
604
  x,
545
605
  y
546
- }) => `Through point at ${x} comma ${y}.`
606
+ }) => `Through point at ${x} comma ${y}.`,
607
+ srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
608
+ srQuadraticFaceUp: "The parabola opens upward.",
609
+ srQuadraticFaceDown: "The parabola opens downward.",
610
+ srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
611
+ srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
612
+ srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
613
+ srQuadraticGraphVertexQuadrant: ({
614
+ quadrant
615
+ }) => `Vertex is in quadrant ${quadrant}.`,
616
+ srQuadraticTwoXIntercepts: ({
617
+ intercept1,
618
+ intercept2
619
+ }) => `The X-intercepts are at ${intercept1} comma 0 and ${intercept2} comma 0.`,
620
+ srQuadraticOneXIntercept: ({
621
+ intercept
622
+ }) => `The X-intercept is at ${intercept} comma 0.`,
623
+ srQuadraticYIntercept: ({
624
+ intercept
625
+ }) => `The Y-intercept is at 0 comma ${intercept}.`,
626
+ srQuadraticPointOrigin: ({
627
+ pointNumber
628
+ }) => `Point ${pointNumber} on parabola at the origin.`,
629
+ srQuadraticPointAxis: ({
630
+ pointNumber,
631
+ x,
632
+ y
633
+ }) => `Point ${pointNumber} on parabola at ${x} comma ${y}.`,
634
+ srQuadraticPointQuadrant: ({
635
+ pointNumber,
636
+ x,
637
+ y,
638
+ quadrant
639
+ }) => `Point ${pointNumber} on parabola in quadrant ${quadrant} at ${x} comma ${y}.`,
640
+ srQuadraticInteractiveElements: ({
641
+ point1X,
642
+ point1Y,
643
+ point2X,
644
+ point2Y,
645
+ point3X,
646
+ point3Y
647
+ }) => `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`
547
648
  // The above strings are used for interactive graph SR descriptions.
548
649
  };
549
650
 
@@ -1830,23 +1931,6 @@ const keypadConfigurationForProps = widgetOptions => {
1830
1931
  times: widgetOptions.times
1831
1932
  };
1832
1933
  };
1833
- const propUpgrades$2 = {
1834
- /* c8 ignore next */
1835
- "1": v0props => ({
1836
- times: v0props.times,
1837
- buttonSets: v0props.buttonSets,
1838
- functions: v0props.functions,
1839
- buttonsVisible: v0props.buttonsVisible,
1840
- visibleLabel: v0props.visibleLabel,
1841
- ariaLabel: v0props.ariaLabel,
1842
- answerForms: [{
1843
- considered: "correct",
1844
- form: v0props.form,
1845
- simplify: v0props.simplify,
1846
- value: v0props.value
1847
- }]
1848
- })
1849
- };
1850
1934
  const ExpressionWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) => {
1851
1935
  const deps = useDependencies();
1852
1936
  return /*#__PURE__*/React.createElement(Expression, _extends({
@@ -1886,17 +1970,18 @@ var Expression$1 = {
1886
1970
  ariaLabel
1887
1971
  };
1888
1972
  },
1889
- version: {
1890
- major: 1,
1891
- minor: 0
1892
- },
1893
- propUpgrades: propUpgrades$2,
1973
+ version: expressionLogic.version,
1974
+ propUpgrades: expressionLogic.widgetOptionsUpgrades,
1894
1975
  // For use by the editor
1895
1976
  isLintable: true,
1896
1977
  // TODO(LEMS-2656): remove TS suppression
1897
1978
  // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
1898
1979
  scorer: scoreExpression,
1899
1980
  // TODO(LEMS-2656): remove TS suppression
1981
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
1982
+ validator: validateExpression,
1983
+ getPublicWidgetOptions: getExpressionPublicWidgetOptions,
1984
+ // TODO(LEMS-2656): remove TS suppression
1900
1985
  // @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
1901
1986
  getOneCorrectAnswerFromRubric(rubric) {
1902
1987
  const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
@@ -8580,7 +8665,7 @@ class Graphie$1 {
8580
8665
  // We round the coordinates to make testing easier, because trig
8581
8666
  // operations return unround numbers.
8582
8667
  // TODO(benchristel): move rounding to scalePoint?
8583
- const round = x => number$1.round(x, 6);
8668
+ const round = x => number.round(x, 6);
8584
8669
  const startPoint = this.scalePoint([round(center[0] + startVector[0]), round(center[1] + startVector[1])]);
8585
8670
  const endPoint = this.scalePoint([round(center[0] + endVector[0]), round(center[1] + endVector[1])]);
8586
8671
  const largeAngle = ((endAngle - startAngle) % 360 + 360) % 360 > 180;
@@ -17336,7 +17421,7 @@ const inlineNodeTypes = {
17336
17421
 
17337
17422
  // @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'.
17338
17423
  const builtParser$1 = SimpleMarkdown.parserFor(rules$1);
17339
- const parse$4 = (source, state) => {
17424
+ const parse$3 = (source, state) => {
17340
17425
  const paragraphedSource = source + "\n\n";
17341
17426
  return builtParser$1(paragraphedSource, _extends({}, state, {
17342
17427
  inline: false
@@ -17419,14 +17504,14 @@ const getContent = ast => {
17419
17504
  */
17420
17505
  const characterCount = source => {
17421
17506
  // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
17422
- const ast = parse$4(source);
17507
+ const ast = parse$3(source);
17423
17508
  const content = getContent(ast).join("");
17424
17509
  return content.length;
17425
17510
  };
17426
17511
  var PerseusMarkdown = {
17427
17512
  characterCount: characterCount,
17428
17513
  traverseContent: traverseContent,
17429
- parse: parse$4,
17514
+ parse: parse$3,
17430
17515
  parseInline: inlineParser,
17431
17516
  reactFor: SimpleMarkdown.reactFor,
17432
17517
  ruleOutput: SimpleMarkdown.ruleOutput(rules$1, "react"),
@@ -17568,138 +17653,6 @@ guess, state) {
17568
17653
  });
17569
17654
  }
17570
17655
 
17571
- // These are all the current widgets that require user-input and are supported for automatic scoring
17572
- const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
17573
-
17574
- /**
17575
- * Regex for widget placeholders in a string.
17576
- *
17577
- * First capture group is the widget ID (ex. 'radio 1')
17578
- * Second capture group is the widget type (ex. "radio)
17579
- * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
17580
- */
17581
- function getWidgetRegex() {
17582
- return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
17583
- }
17584
-
17585
- /**
17586
- * Add a widget placeholder using the widget ID.
17587
- * ex. addWidget("radio 1") => "[[☃ radio 1]]"
17588
- *
17589
- * @param {string} id
17590
- * @returns {string}
17591
- */
17592
- function addWidget(id) {
17593
- return `[[☃ ${id}]]`;
17594
- }
17595
-
17596
- /**
17597
- * Get a widget type by a widget's ID
17598
- * (please don't derive type from ID)
17599
- *
17600
- * @param {string} widgetId the ID of the widget
17601
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
17602
- * @returns {string} the widget type (ie "radio")
17603
- */
17604
- function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
17605
- var _widget$type;
17606
- const widget = widgetMap[WidgetId];
17607
- return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
17608
- }
17609
-
17610
- /**
17611
- * Does the content have a specific type of widget?
17612
- *
17613
- * @param {string} type the type of the widget in question (ie "radio")
17614
- * @param {string} content the string to search through
17615
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
17616
- * @returns {boolean} if the content includes the widget type
17617
- */
17618
- function contentHasWidgetType(type, content, widgetMap) {
17619
- return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
17620
- }
17621
-
17622
- /**
17623
- * Extract all widget IDs, which includes the widget type and instance number.
17624
- * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
17625
- *
17626
- * Content should contain Perseus widget placeholders,
17627
- * which look like: '[[☃ radio 1]]'.
17628
- *
17629
- * @param {string} content
17630
- * @returns {ReadonlyArray<string>} widgetIds
17631
- */
17632
- function getWidgetIdsFromContent(content) {
17633
- const widgets = [];
17634
- const localWidgetRegex = getWidgetRegex();
17635
- let match = localWidgetRegex.exec(content);
17636
- while (match !== null) {
17637
- widgets.push(match[1]);
17638
- match = localWidgetRegex.exec(content);
17639
- }
17640
- return widgets;
17641
- }
17642
-
17643
- /**
17644
- * Get a list of widget IDs from content,
17645
- * but only for specific widget types
17646
- *
17647
- * @param {string} type the type of widget (ie "radio")
17648
- * @param {string} content the string to parse
17649
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
17650
- * @returns {ReadonlyArray<string>} the widget type (ie "radio")
17651
- */
17652
- function getWidgetIdsFromContentByType(type, content, widgetMap) {
17653
- const rv = [];
17654
- const widgetIdsInContent = getWidgetIdsFromContent(content);
17655
- widgetIdsInContent.forEach(widgetId => {
17656
- const widget = widgetMap[widgetId];
17657
- if ((widget == null ? void 0 : widget.type) === type) {
17658
- rv.push(widgetId);
17659
- }
17660
- });
17661
- return rv;
17662
- }
17663
-
17664
- /**
17665
- * Pull the widget map out of ItemData
17666
- *
17667
- * @param {PerseusItem} PerseusItem containing a widgetMap
17668
- * @returns {WidgetMap} the widget map in the PerseusItem
17669
- */
17670
- function getWidgetsMapFromItemData(itemData) {
17671
- return itemData.question.widgets;
17672
- }
17673
-
17674
- /**
17675
- * Get the widget information from a WidgetMap
17676
- *
17677
- * @param {string} widgetId the ID of the widget
17678
- * @returns {PerseusWidget | null} the widget data if it exists, otherwise null
17679
- */
17680
- function getWidgetFromWidgetMap(widgetId, widgetMap) {
17681
- var _widgetMap$widgetId;
17682
- return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
17683
- }
17684
-
17685
- /**
17686
- * Get select widgets from a widget map.
17687
- *
17688
- * @param {ReadonlyArray<string>} widgetIds to extract
17689
- * @param {PerseusWidgetsMap} widgetMap to extract from
17690
- * @return {PerseusWidgetsMap} a new widget map with requested widgets
17691
- */
17692
- function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
17693
- const widgets = {};
17694
- widgetIds.forEach(widgetId => {
17695
- const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
17696
- if (widget) {
17697
- widgets[widgetId] = widget;
17698
- }
17699
- });
17700
- return widgets;
17701
- }
17702
-
17703
17656
  const DEFAULT_ALIGNMENT$1 = "block";
17704
17657
  // NOTE(kevinb): "default" is not one in `validAlignments`.
17705
17658
  const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
@@ -17804,13 +17757,17 @@ const getWidgetExport = name => {
17804
17757
  var _widgets$name3;
17805
17758
  return (_widgets$name3 = widgets[name]) != null ? _widgets$name3 : null;
17806
17759
  };
17760
+ const getWidgetValidator = name => {
17761
+ var _widgets$name$validat, _widgets$name4;
17762
+ return (_widgets$name$validat = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.validator) != null ? _widgets$name$validat : null;
17763
+ };
17807
17764
  const getWidgetScorer = name => {
17808
- var _widgets$name$scorer, _widgets$name4;
17809
- return (_widgets$name$scorer = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.scorer) != null ? _widgets$name$scorer : null;
17765
+ var _widgets$name$scorer, _widgets$name5;
17766
+ return (_widgets$name$scorer = (_widgets$name5 = widgets[name]) == null ? void 0 : _widgets$name5.scorer) != null ? _widgets$name$scorer : null;
17810
17767
  };
17811
17768
  const getPublicWidgetOptionsFunction = name => {
17812
- var _widgets$name$getPubl, _widgets$name5;
17813
- return (_widgets$name$getPubl = (_widgets$name5 = widgets[name]) == null ? void 0 : _widgets$name5.getPublicWidgetOptions) != null ? _widgets$name$getPubl : i => i;
17769
+ var _widgets$name$getPubl, _widgets$name6;
17770
+ return (_widgets$name$getPubl = (_widgets$name6 = widgets[name]) == null ? void 0 : _widgets$name6.getPublicWidgetOptions) != null ? _widgets$name$getPubl : i => i;
17814
17771
  };
17815
17772
  const getEditor = name => {
17816
17773
  return _.has(editors, name) ? editors[name] : null;
@@ -18115,6 +18072,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
18115
18072
  replaceDeprecatedEditors: replaceDeprecatedEditors,
18116
18073
  getWidget: getWidget,
18117
18074
  getWidgetExport: getWidgetExport,
18075
+ getWidgetValidator: getWidgetValidator,
18118
18076
  getWidgetScorer: getWidgetScorer,
18119
18077
  getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
18120
18078
  getEditor: getEditor,
@@ -18137,7 +18095,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
18137
18095
  });
18138
18096
 
18139
18097
  function getUpgradedWidgetOptions(oldWidgetOptions) {
18140
- // @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; }'.
18141
18098
  return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
18142
18099
  if (!widgetInfo.type || !widgetInfo.alignment) {
18143
18100
  const newValues = {};
@@ -18152,22 +18109,30 @@ function getUpgradedWidgetOptions(oldWidgetOptions) {
18152
18109
  }
18153
18110
  widgetInfo = _extends({}, widgetInfo, newValues);
18154
18111
  }
18112
+ // TODO(LEMS-2656): remove TS suppression
18155
18113
  return upgradeWidgetInfoToLatestVersion(widgetInfo);
18156
18114
  });
18157
18115
  }
18116
+
18117
+ /**
18118
+ * Checks the given user input to see if any answerable widgets have not been
18119
+ * "filled in" (ie. if they're empty). Another way to think about this
18120
+ * function is that its a check to see if we can score the provided input.
18121
+ */
18158
18122
  function emptyWidgetsFunctional(widgets,
18159
18123
  // This is a port of old code, I'm not sure why
18160
18124
  // we need widgetIds vs the keys of the widgets object
18161
18125
  widgetIds, userInputMap, strings, locale) {
18162
- const upgradedWidgets = getUpgradedWidgetOptions(widgets);
18163
18126
  return widgetIds.filter(id => {
18164
- const widget = upgradedWidgets[id];
18165
- if (!widget || widget.static) {
18127
+ const widget = widgets[id];
18128
+ if (!widget || widget.static === true) {
18166
18129
  // Static widgets shouldn't count as empty
18167
18130
  return false;
18168
18131
  }
18169
- const scorer = getWidgetScorer(widget.type);
18170
- const score = scorer == null ? void 0 : scorer(userInputMap[id], widget.options, strings, locale);
18132
+ const validator = getWidgetValidator(widget.type);
18133
+ const userInput = userInputMap[id];
18134
+ const validationData = widget.options;
18135
+ const score = validator == null ? void 0 : validator(userInput, validationData, strings, locale);
18171
18136
  if (score) {
18172
18137
  return scoreIsEmpty(score);
18173
18138
  }
@@ -18201,13 +18166,18 @@ widgetIds, userInputMap, strings, locale) {
18201
18166
  });
18202
18167
  const widgetScores = {};
18203
18168
  gradedWidgetIds.forEach(id => {
18169
+ var _validator;
18204
18170
  const widget = upgradedWidgets[id];
18205
18171
  if (!widget) {
18206
18172
  return;
18207
18173
  }
18208
18174
  const userInput = userInputMap[id];
18175
+ const validator = getWidgetValidator(widget.type);
18209
18176
  const scorer = getWidgetScorer(widget.type);
18210
- const score = scorer == null ? void 0 : scorer(userInput, widget.options, strings, locale);
18177
+
18178
+ // We do validation (empty checks) first and then scoring. If
18179
+ // validation fails, it's result is itself a PerseusScore.
18180
+ const score = (_validator = validator == null ? void 0 : validator(userInput, widget.options, strings, locale)) != null ? _validator : scorer == null ? void 0 : scorer(userInput, widget.options, strings, locale);
18211
18181
  if (score != null) {
18212
18182
  widgetScores[id] = score;
18213
18183
  }
@@ -19607,7 +19577,7 @@ class Renderer extends React.Component {
19607
19577
  const apiOptions = this.getApiOptions();
19608
19578
  const widgetProps = this.state.widgetProps[widgetId] || {};
19609
19579
 
19610
- // The widget needs access to its "rubric" at all times when in review
19580
+ // The widget needs access to its "scoring data" at all times when in review
19611
19581
  // mode (which is really just part of its widget info).
19612
19582
  const widgetInfo = this.state.widgetInfo[widgetId];
19613
19583
  const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
@@ -19938,7 +19908,7 @@ function _objectWithoutPropertiesLoose(r, e) {
19938
19908
  return t;
19939
19909
  }
19940
19910
 
19941
- const _excluded$7 = ["position", "visibility", "left", "top"];
19911
+ const _excluded$4 = ["position", "visibility", "left", "top"];
19942
19912
  const zIndex = 10;
19943
19913
  let HorizontalDirection = /*#__PURE__*/function (HorizontalDirection) {
19944
19914
  HorizontalDirection["Left"] = "left";
@@ -19989,7 +19959,7 @@ const TooltipArrow = _ref => {
19989
19959
  left = 100,
19990
19960
  top = 0
19991
19961
  } = _ref,
19992
- props = _objectWithoutPropertiesLoose(_ref, _excluded$7);
19962
+ props = _objectWithoutPropertiesLoose(_ref, _excluded$4);
19993
19963
  // TODO(aria): Think about adding a box-shadow to the triangle here
19994
19964
  // See http://css-tricks.com/triangle-with-shadow/
19995
19965
 
@@ -20363,7 +20333,7 @@ InputWithExamples.defaultProps = {
20363
20333
  className: ""
20364
20334
  };
20365
20335
 
20366
- const _excluded$6 = ["keypadElement", "onFocus", "value"];
20336
+ const _excluded$3 = ["keypadElement", "onFocus", "value"];
20367
20337
  class SimpleKeypadInput extends React.Component {
20368
20338
  constructor(...args) {
20369
20339
  super(...args);
@@ -20408,7 +20378,7 @@ class SimpleKeypadInput extends React.Component {
20408
20378
  onFocus,
20409
20379
  value
20410
20380
  } = _this$props,
20411
- rest = _objectWithoutPropertiesLoose(_this$props, _excluded$6);
20381
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
20412
20382
  return (
20413
20383
  /*#__PURE__*/
20414
20384
  // @ts-expect-error - TS2769 - No overload matches this call.
@@ -22745,7 +22715,7 @@ const CIRCLE_LABEL_STYLE = {
22745
22715
  // TODO(LEMS-2656): remove TS suppression
22746
22716
  // @ts-expect-error: State is not assignable to ParseState
22747
22717
  const builtParser = SimpleMarkdown.parserFor(rules);
22748
- const parse$3 = (source, state) => {
22718
+ const parse$2 = (source, state) => {
22749
22719
  // @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
22750
22720
  state = state || {};
22751
22721
  const paragraphedSource = source + "\n\n";
@@ -22755,7 +22725,7 @@ const parse$3 = (source, state) => {
22755
22725
  // @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
22756
22726
  const output = SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules, "react"));
22757
22727
  var PassageMarkdown = {
22758
- parse: parse$3,
22728
+ parse: parse$2,
22759
22729
  output,
22760
22730
  START_REF_PREFIX,
22761
22731
  END_REF_PREFIX,
@@ -23312,10 +23282,7 @@ var PassageRef$1 = {
23312
23282
  referenceNumber: widgetOptions.referenceNumber,
23313
23283
  summaryText: widgetOptions.summaryText
23314
23284
  }),
23315
- version: {
23316
- major: 0,
23317
- minor: 1
23318
- },
23285
+ version: passageRefLogic.version,
23319
23286
  // TODO: things that aren't interactive shouldn't need scoring functions
23320
23287
  scorer: () => scoreNoop()
23321
23288
  };
@@ -24297,14 +24264,14 @@ var Choice$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.
24297
24264
  forwardedRef: ref
24298
24265
  })));
24299
24266
 
24300
- const _excluded$5 = ["content", "forwardedRef", "showContent"];
24267
+ const _excluded$2 = ["content", "forwardedRef", "showContent"];
24301
24268
  const ChoiceNoneAbove = function ChoiceNoneAbove(_ref) {
24302
24269
  let {
24303
24270
  content,
24304
24271
  forwardedRef,
24305
24272
  showContent = true
24306
24273
  } = _ref,
24307
- rest = _objectWithoutPropertiesLoose(_ref, _excluded$5);
24274
+ rest = _objectWithoutPropertiesLoose(_ref, _excluded$2);
24308
24275
  const {
24309
24276
  strings
24310
24277
  } = usePerseusI18n();
@@ -24968,7 +24935,7 @@ class Radio$1 extends React.Component {
24968
24935
  // Current versions of the radio widget always pass in the
24969
24936
  // "correct" value through the choices. Old serialized state
24970
24937
  // for radio widgets doesn't have this though, so we have to
24971
- // pull the correctness out of the review mode rubric. This
24938
+ // pull the correctness out of the review mode scoring data. This
24972
24939
  // only works because all of the places we use
24973
24940
  // `restoreSerializedState()` also turn on reviewMode, but is
24974
24941
  // fine for now.
@@ -25014,7 +24981,6 @@ Radio$1.defaultProps = {
25014
24981
  showSolutions: "none"
25015
24982
  };
25016
24983
 
25017
- const _excluded$4 = ["noneOfTheAbove"];
25018
24984
  const {
25019
24985
  shuffle: shuffle$2,
25020
24986
  random
@@ -25101,20 +25067,6 @@ const transform = (widgetOptions, strings, problemNum) => {
25101
25067
  selectedChoices: _.pluck(choices, "correct")
25102
25068
  };
25103
25069
  };
25104
- const propUpgrades$1 = {
25105
- "1": v0props => {
25106
- const {
25107
- noneOfTheAbove
25108
- } = v0props,
25109
- rest = _objectWithoutPropertiesLoose(v0props, _excluded$4);
25110
- if (noneOfTheAbove) {
25111
- throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
25112
- }
25113
- return _extends({}, rest, {
25114
- hasNoneOfTheAbove: false
25115
- });
25116
- }
25117
- };
25118
25070
  var Radio = {
25119
25071
  name: "radio",
25120
25072
  displayName: "Radio / Multiple choice",
@@ -25122,15 +25074,15 @@ var Radio = {
25122
25074
  widget: Radio$1,
25123
25075
  transform: transform,
25124
25076
  staticTransform: transform,
25125
- version: {
25126
- major: 1,
25127
- minor: 0
25128
- },
25129
- propUpgrades: propUpgrades$1,
25077
+ version: radioLogic.version,
25078
+ propUpgrades: radioLogic.widgetOptionsUpgrades,
25130
25079
  isLintable: true,
25131
25080
  // TODO(LEMS-2656): remove TS suppression
25132
25081
  // @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
25133
- scorer: scoreRadio
25082
+ scorer: scoreRadio,
25083
+ // TODO(LEMS-2656): remove TS suppression
25084
+ // @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
25085
+ validator: validateRadio
25134
25086
  };
25135
25087
 
25136
25088
  /* eslint-disable import/no-named-as-default */
@@ -25149,24 +25101,6 @@ const getPromptJSON$n = (renderProps, userInput) => {
25149
25101
  };
25150
25102
  };
25151
25103
 
25152
- /**
25153
- * For details on the individual options, see the
25154
- * PerseusCategorizerWidgetOptions type
25155
- */
25156
-
25157
- /**
25158
- * Given a PerseusCategorizerWidgetOptions object, return a new object with only
25159
- * the public options that should be exposed to the client.
25160
- */
25161
- function getCategorizerPublicWidgetOptions(options) {
25162
- return {
25163
- items: options.items,
25164
- categories: options.categories,
25165
- randomizeItems: options.randomizeItems,
25166
- static: options.static
25167
- };
25168
- }
25169
-
25170
25104
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
25171
25105
  class Categorizer extends React.Component {
25172
25106
  constructor(..._args) {
@@ -25346,6 +25280,9 @@ var Categorizer$1 = {
25346
25280
  // TODO(LEMS-2656): remove TS suppression
25347
25281
  // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
25348
25282
  scorer: scoreCategorizer,
25283
+ // TODO(LEMS-2656): remove TS suppression
25284
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
25285
+ validator: validateCategorizer,
25349
25286
  getPublicWidgetOptions: getCategorizerPublicWidgetOptions
25350
25287
  };
25351
25288
 
@@ -25783,7 +25720,10 @@ var Dropdown$1 = {
25783
25720
  transform: optionsTransform,
25784
25721
  // TODO(LEMS-2656): remove TS suppression
25785
25722
  // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
25786
- scorer: scoreDropdown
25723
+ scorer: scoreDropdown,
25724
+ // TODO(LEMS-2656): remove TS suppression
25725
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
25726
+ validator: validateDropdown
25787
25727
  };
25788
25728
 
25789
25729
  const getPromptJSON$j = renderProps => {
@@ -26974,7 +26914,7 @@ class FunctionGrapher extends React.Component {
26974
26914
  constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
26975
26915
  // Always enforce that this is a function
26976
26916
  const isFunction = _.all(this._coords(), (otherCoord, j) => {
26977
- return i === j || !otherCoord || !number$1.equal(coord[0], otherCoord[0]);
26917
+ return i === j || !otherCoord || !number.equal(coord[0], otherCoord[0]);
26978
26918
  });
26979
26919
 
26980
26920
  // Evaluate this criteria before per-point
@@ -27282,11 +27222,22 @@ const getPromptJSON$f = rendererJSON => {
27282
27222
 
27283
27223
  // The `group` widget is basically a widget hosting a full Perseus system in
27284
27224
  // it. As such, scoring a group means scoring all widgets it contains.
27285
- function scoreGroup(userInput, options, strings, locale) {
27286
- const scores = scoreWidgetsFunctional(options.widgets, Object.keys(options.widgets), userInput, strings, locale);
27225
+ function scoreGroup(userInput, rubric, strings, locale) {
27226
+ const scores = scoreWidgetsFunctional(rubric.widgets, Object.keys(rubric.widgets), userInput, strings, locale);
27287
27227
  return flattenScores(scores);
27288
27228
  }
27289
27229
 
27230
+ function validateGroup(userInput, validationData, strings, locale) {
27231
+ const emptyWidgets = emptyWidgetsFunctional(validationData.widgets, Object.keys(validationData.widgets), userInput, strings, locale);
27232
+ if (emptyWidgets.length === 0) {
27233
+ return null;
27234
+ }
27235
+ return {
27236
+ type: "invalid",
27237
+ message: null
27238
+ };
27239
+ }
27240
+
27290
27241
  // exports has no 'transform'
27291
27242
 
27292
27243
  class Group extends React.Component {
@@ -27442,8 +27393,11 @@ var Group$1 = {
27442
27393
  widget: Group,
27443
27394
  traverseChildWidgets: traverseChildWidgets,
27444
27395
  // TODO(LEMS-2656): remove TS suppression
27445
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
27396
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
27446
27397
  scorer: scoreGroup,
27398
+ // TODO(LEMS-2656): remove TS suppression
27399
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
27400
+ validator: validateGroup,
27447
27401
  hidden: true,
27448
27402
  isLintable: true
27449
27403
  };
@@ -29779,7 +29733,7 @@ const styles$d = StyleSheet.create({
29779
29733
  }
29780
29734
  });
29781
29735
 
29782
- const _excluded$3 = ["onFormatChange", "checkValidity", "useArrowKeys"];
29736
+ const _excluded$1 = ["onFormatChange", "checkValidity", "useArrowKeys"];
29783
29737
  const {
29784
29738
  firstNumericalParse,
29785
29739
  captureScratchpadTouchStart
@@ -29917,7 +29871,7 @@ class NumberInput extends React.Component {
29917
29871
  };
29918
29872
  }
29919
29873
  componentDidUpdate(prevProps) {
29920
- if (!number$1.equal(this.getValue(), this.props.value)) {
29874
+ if (!number.equal(this.getValue(), this.props.value)) {
29921
29875
  this._setValue(this.props.value, this.state.format);
29922
29876
  }
29923
29877
  }
@@ -29933,7 +29887,7 @@ class NumberInput extends React.Component {
29933
29887
  classes = classes + " " + this.props.className;
29934
29888
  }
29935
29889
  const _this$props = this.props,
29936
- restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
29890
+ restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$1);
29937
29891
  return /*#__PURE__*/React.createElement("input", _extends({}, restProps, {
29938
29892
  className: classes,
29939
29893
  type: "text",
@@ -30331,6 +30285,96 @@ function getInterceptStringForLine(line, strings, locale) {
30331
30285
  yIntercept: srFormatNumber(yIntercept, locale)
30332
30286
  });
30333
30287
  }
30288
+ function getCoordQuadrant(coord) {
30289
+ const [unroundedX, unroundedY] = coord;
30290
+ const x = Number(unroundedX.toFixed(3));
30291
+ const y = Number(unroundedY.toFixed(3));
30292
+ if (x === 0 && y === 0) {
30293
+ return "origin";
30294
+ }
30295
+ if (y === 0) {
30296
+ return "x-axis";
30297
+ }
30298
+ if (x === 0) {
30299
+ return "y-axis";
30300
+ }
30301
+ if (x > 0 && y > 0) {
30302
+ return 1;
30303
+ }
30304
+ if (x < 0 && y > 0) {
30305
+ return 2;
30306
+ }
30307
+ if (x < 0 && y < 0) {
30308
+ return 3;
30309
+ }
30310
+ return 4;
30311
+ }
30312
+ function getQuadraticVertexString(vertex, strings) {
30313
+ const location = getCoordQuadrant(vertex);
30314
+ switch (location) {
30315
+ case "origin":
30316
+ return strings.srQuadraticGraphVertexOrigin;
30317
+ case "x-axis":
30318
+ return strings.srQuadraticGraphVertexXAxis;
30319
+ case "y-axis":
30320
+ return strings.srQuadraticGraphVertexYAxis;
30321
+ default:
30322
+ return strings.srQuadraticGraphVertexQuadrant({
30323
+ quadrant: location
30324
+ });
30325
+ }
30326
+ }
30327
+ function getQuadraticPointString(pointNumber, coord, strings, locale) {
30328
+ const location = getCoordQuadrant(coord);
30329
+ const [x, y] = coord;
30330
+ switch (location) {
30331
+ case "origin":
30332
+ return strings.srQuadraticPointOrigin({
30333
+ pointNumber: pointNumber
30334
+ });
30335
+ case "x-axis":
30336
+ case "y-axis":
30337
+ return strings.srQuadraticPointAxis({
30338
+ pointNumber: pointNumber,
30339
+ x: srFormatNumber(x, locale),
30340
+ y: srFormatNumber(y, locale)
30341
+ });
30342
+ default:
30343
+ // return `Point ${pointNumber} on parabola in quadrant ${location} at ${srFormatNumber(x, locale)} comma ${srFormatNumber(y, locale)}.`;
30344
+ return strings.srQuadraticPointQuadrant({
30345
+ pointNumber: pointNumber,
30346
+ quadrant: location,
30347
+ x: srFormatNumber(x, locale),
30348
+ y: srFormatNumber(y, locale)
30349
+ });
30350
+ }
30351
+ }
30352
+ function getQuadraticXIntercepts(
30353
+ // Coefficients of the quadratic equation
30354
+ a, b, c) {
30355
+ // If a is 0, the equation is a line and has maybe only one x-intercept.
30356
+ if (a === 0) {
30357
+ // If b is 0, the equation a horizontal line and has no x-intercepts.
30358
+ if (b === 0) {
30359
+ return [];
30360
+ }
30361
+ return [-c / b];
30362
+ }
30363
+
30364
+ // Find the x-intercepts by plugging in y = 0 into the quadratic equation.
30365
+ // 0 = ax^2 + bx + c
30366
+ // x = (-b ± sqrt(b^2 - 4ac)) / 2a
30367
+ const discriminant = b * b - 4 * a * c;
30368
+ if (discriminant < 0) {
30369
+ return [];
30370
+ }
30371
+ const x1 = (-b + Math.sqrt(discriminant)) / (2 * a);
30372
+ const x2 = (-b - Math.sqrt(discriminant)) / (2 * a);
30373
+ if (x1 === x2) {
30374
+ return [x1];
30375
+ }
30376
+ return [x1, x2];
30377
+ }
30334
30378
 
30335
30379
  const {
30336
30380
  calculateAngleInDegrees: calculateAngleInDegrees$2
@@ -30748,12 +30792,12 @@ const MafsCssTransformWrapper = ({
30748
30792
  }
30749
30793
  }, children);
30750
30794
 
30751
- const _excluded$2 = ["children"];
30795
+ const _excluded = ["children"];
30752
30796
  const TextLabel = _ref => {
30753
30797
  let {
30754
30798
  children
30755
30799
  } = _ref,
30756
- rest = _objectWithoutPropertiesLoose(_ref, _excluded$2);
30800
+ rest = _objectWithoutPropertiesLoose(_ref, _excluded);
30757
30801
  return /*#__PURE__*/React__default.createElement(Text$1, _extends({
30758
30802
  size: 16,
30759
30803
  svgTextProps: {
@@ -32135,7 +32179,9 @@ function renderLinearGraph(state, dispatch) {
32135
32179
  graphState: state,
32136
32180
  dispatch: dispatch
32137
32181
  }),
32138
- interactiveElementsDescription: null
32182
+ interactiveElementsDescription: /*#__PURE__*/React.createElement(LinearGraphDescription, {
32183
+ state: state
32184
+ })
32139
32185
  };
32140
32186
  }
32141
32187
  const LinearGraph = (props, key) => {
@@ -32154,33 +32200,29 @@ const LinearGraph = (props, key) => {
32154
32200
  const interceptDescriptionId = id + "-intercept";
32155
32201
  const slopeDescriptionId = id + "-slope";
32156
32202
 
32157
- // Aria label strings
32158
- const linearGraphPointsDescription = strings.srLinearGraphPoints({
32159
- point1X: srFormatNumber(line[0][0], locale),
32160
- point1Y: srFormatNumber(line[0][1], locale),
32161
- point2X: srFormatNumber(line[1][0], locale),
32162
- point2Y: srFormatNumber(line[1][1], locale)
32163
- });
32164
- const grabHandleAriaLabel = strings.srLinearGrabHandle({
32165
- point1X: srFormatNumber(line[0][0], locale),
32166
- point1Y: srFormatNumber(line[0][1], locale),
32167
- point2X: srFormatNumber(line[1][0], locale),
32168
- point2Y: srFormatNumber(line[1][1], locale)
32203
+ // Aria strings
32204
+ const {
32205
+ srLinearGraph,
32206
+ srLinearGraphPoints,
32207
+ srLinearGrabHandle,
32208
+ slopeString,
32209
+ interceptString
32210
+ } = describeLinearGraph(props.graphState, {
32211
+ strings,
32212
+ locale
32169
32213
  });
32170
- const slopeString = getSlopeStringForLine(line, strings);
32171
- const interceptString = getInterceptStringForLine(line, strings, locale);
32172
32214
 
32173
32215
  // Linear graphs only have one line
32174
32216
  // (LEMS-2050): Update the reducer so that we have a separate action for moving one line
32175
32217
  // and another action for moving multiple lines
32176
32218
  return /*#__PURE__*/React.createElement("g", {
32177
32219
  // Outer line minimal description
32178
- "aria-label": strings.srLinearGraph,
32220
+ "aria-label": srLinearGraph,
32179
32221
  "aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`
32180
32222
  }, /*#__PURE__*/React.createElement(MovableLine, {
32181
32223
  key: 0,
32182
32224
  ariaLabels: {
32183
- grabHandleAriaLabel: grabHandleAriaLabel
32225
+ grabHandleAriaLabel: srLinearGrabHandle
32184
32226
  },
32185
32227
  ariaDescribedBy: `${interceptDescriptionId} ${slopeDescriptionId}`,
32186
32228
  points: line,
@@ -32196,7 +32238,7 @@ const LinearGraph = (props, key) => {
32196
32238
  }), /*#__PURE__*/React.createElement("g", {
32197
32239
  id: pointsDescriptionId,
32198
32240
  style: a11y.srOnly
32199
- }, linearGraphPointsDescription), /*#__PURE__*/React.createElement("g", {
32241
+ }, srLinearGraphPoints), /*#__PURE__*/React.createElement("g", {
32200
32242
  id: interceptDescriptionId,
32201
32243
  style: a11y.srOnly
32202
32244
  }, interceptString), /*#__PURE__*/React.createElement("g", {
@@ -32204,6 +32246,55 @@ const LinearGraph = (props, key) => {
32204
32246
  style: a11y.srOnly
32205
32247
  }, slopeString));
32206
32248
  };
32249
+ function LinearGraphDescription({
32250
+ state
32251
+ }) {
32252
+ // The reason that LinearGraphDescription is a component (rather than a
32253
+ // function that returns a string) is because it needs to use a
32254
+ // hook: `usePerseusI18n`.
32255
+ const i18n = usePerseusI18n();
32256
+ const strings = describeLinearGraph(state, i18n);
32257
+ return strings.srLinearInteractiveElement;
32258
+ }
32259
+
32260
+ // Exported for testing
32261
+ function describeLinearGraph(state, i18n) {
32262
+ const {
32263
+ coords: line
32264
+ } = state;
32265
+ const {
32266
+ strings,
32267
+ locale
32268
+ } = i18n;
32269
+
32270
+ // Aria label strings
32271
+ const srLinearGraph = strings.srLinearGraph;
32272
+ const srLinearGraphPoints = strings.srLinearGraphPoints({
32273
+ point1X: srFormatNumber(line[0][0], locale),
32274
+ point1Y: srFormatNumber(line[0][1], locale),
32275
+ point2X: srFormatNumber(line[1][0], locale),
32276
+ point2Y: srFormatNumber(line[1][1], locale)
32277
+ });
32278
+ const srLinearGrabHandle = strings.srLinearGrabHandle({
32279
+ point1X: srFormatNumber(line[0][0], locale),
32280
+ point1Y: srFormatNumber(line[0][1], locale),
32281
+ point2X: srFormatNumber(line[1][0], locale),
32282
+ point2Y: srFormatNumber(line[1][1], locale)
32283
+ });
32284
+ const slopeString = getSlopeStringForLine(line, strings);
32285
+ const interceptString = getInterceptStringForLine(line, strings, locale);
32286
+ const srLinearInteractiveElement = strings.srInteractiveElements({
32287
+ elements: [srLinearGraph, srLinearGraphPoints].join(" ")
32288
+ });
32289
+ return {
32290
+ srLinearGraph,
32291
+ srLinearGraphPoints,
32292
+ srLinearGrabHandle,
32293
+ slopeString,
32294
+ interceptString,
32295
+ srLinearInteractiveElement
32296
+ };
32297
+ }
32207
32298
 
32208
32299
  function renderLinearSystemGraph(state, dispatch) {
32209
32300
  return {
@@ -32752,13 +32843,13 @@ const hasFocusVisible = element => {
32752
32843
  }
32753
32844
  };
32754
32845
 
32755
- function renderQuadraticGraph(state, dispatch) {
32846
+ function renderQuadraticGraph(state, dispatch, i18n) {
32756
32847
  return {
32757
32848
  graph: /*#__PURE__*/React.createElement(QuadraticGraph, {
32758
32849
  graphState: state,
32759
32850
  dispatch: dispatch
32760
32851
  }),
32761
- interactiveElementsDescription: null
32852
+ interactiveElementsDescription: getQuadraticGraphDescription(state, i18n)
32762
32853
  };
32763
32854
  }
32764
32855
  function QuadraticGraph(props) {
@@ -32769,6 +32860,14 @@ function QuadraticGraph(props) {
32769
32860
  const {
32770
32861
  coords
32771
32862
  } = graphState;
32863
+ const {
32864
+ strings,
32865
+ locale
32866
+ } = usePerseusI18n();
32867
+ const id = React.useId();
32868
+ const quadraticDirectionId = id + "-direction";
32869
+ const quadraticVertexId = id + "-vertex";
32870
+ const quadraticInterceptsId = id + "-intercepts";
32772
32871
 
32773
32872
  // The coefficients are used to calculate the quadratic equation, plot the graph, and to indicate
32774
32873
  // to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
@@ -32784,15 +32883,46 @@ function QuadraticGraph(props) {
32784
32883
 
32785
32884
  // Calculate the y value based on the current x value
32786
32885
  const y = x => (a * x + b) * x + c;
32787
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$2.OfX, {
32886
+
32887
+ // Aria strings
32888
+ const {
32889
+ srQuadraticGraph,
32890
+ srQuadraticDirection,
32891
+ srQuadraticVertex,
32892
+ srQuadraticXIntercepts,
32893
+ srQuadraticYIntercept
32894
+ } = describeQuadraticGraph(graphState, {
32895
+ strings,
32896
+ locale
32897
+ });
32898
+ return /*#__PURE__*/React.createElement("g", {
32899
+ // Outer graph minimal description
32900
+ "aria-label": srQuadraticGraph,
32901
+ "aria-describedby": `${quadraticDirectionId} ${quadraticVertexId} ${quadraticInterceptsId}`
32902
+ }, /*#__PURE__*/React.createElement(Plot$2.OfX, {
32788
32903
  y: y,
32789
32904
  color: color.blue
32790
- }), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
32791
- key: "point-" + i,
32792
- point: coord,
32793
- sequenceNumber: i + 1,
32794
- onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
32795
- })));
32905
+ }), coords.map((coord, i) => {
32906
+ const srQuadraticPoint = getQuadraticPointString(i + 1, coord, strings, locale);
32907
+ // Vertex might not exist if the quadratic graph is a line
32908
+ const srVertex = srQuadraticVertex ? ` ${srQuadraticVertex}` : "";
32909
+ return /*#__PURE__*/React.createElement(MovablePoint$1, {
32910
+ key: "point-" + i,
32911
+ ariaLabel: `${srQuadraticPoint}${srVertex}`,
32912
+ point: coord,
32913
+ sequenceNumber: i + 1,
32914
+ onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
32915
+ });
32916
+ }), srQuadraticDirection && /*#__PURE__*/React.createElement("g", {
32917
+ id: quadraticDirectionId,
32918
+ style: a11y.srOnly
32919
+ }, srQuadraticDirection), srQuadraticVertex && /*#__PURE__*/React.createElement("g", {
32920
+ id: quadraticVertexId,
32921
+ style: a11y.srOnly
32922
+ }, srQuadraticVertex), /*#__PURE__*/React.createElement("g", {
32923
+ id: quadraticInterceptsId,
32924
+ style: a11y.srOnly
32925
+ }, srQuadraticXIntercepts ? `${srQuadraticXIntercepts} ${srQuadraticYIntercept}` : `${srQuadraticYIntercept}`));
32796
32926
  }
32797
32927
 
32798
32928
  // Get the quadratic coefficients from the 3 control points
@@ -32815,6 +32945,60 @@ const getQuadraticCoefficients$2 = coords => {
32815
32945
  const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
32816
32946
  return [a, b, c];
32817
32947
  };
32948
+ function getQuadraticGraphDescription(state, i18n) {
32949
+ const strings = describeQuadraticGraph(state, i18n);
32950
+ return strings.srQuadraticInteractiveElements;
32951
+ }
32952
+ // Exported for testing
32953
+ function describeQuadraticGraph(state, i18n) {
32954
+ const {
32955
+ strings,
32956
+ locale
32957
+ } = i18n;
32958
+ const coeffs = getQuadraticCoefficients$2(state.coords);
32959
+ const [a, b, c] = coeffs != null ? coeffs : [0, 0, 0];
32960
+ const vertex = [-b / (2 * a), c - b * b / (4 * a)];
32961
+ const xIntercepts = getQuadraticXIntercepts(a, b, c);
32962
+
32963
+ // Aria label strings
32964
+ const srQuadraticGraph = strings.srQuadraticGraph;
32965
+ const srQuadraticFaceUp = strings.srQuadraticFaceUp;
32966
+ const srQuadraticFaceDown = strings.srQuadraticFaceDown;
32967
+ // The graph only has a direction if it is not a line.
32968
+ const srQuadraticDirection = a === 0 ? undefined : a > 0 ? srQuadraticFaceUp : srQuadraticFaceDown;
32969
+ // Only describe vertex if the quadratic graph is not a line.
32970
+ // (Undefined means the quadratic graph is a line and has no vertex.)
32971
+ const srQuadraticVertex = a !== 0 ? getQuadraticVertexString(vertex, strings) : undefined;
32972
+ // Undefined means the quadratic graph has no x-intercepts,
32973
+ // such as when the graph is a horizontal line.
32974
+ const srQuadraticXIntercepts = xIntercepts.length === 2 ? strings.srQuadraticTwoXIntercepts({
32975
+ intercept1: srFormatNumber(xIntercepts[0], locale),
32976
+ intercept2: srFormatNumber(xIntercepts[1], locale)
32977
+ }) : xIntercepts.length === 1 ? strings.srQuadraticOneXIntercept({
32978
+ intercept: srFormatNumber(xIntercepts[0], locale)
32979
+ }) : undefined;
32980
+ const srQuadraticYIntercept = strings.srQuadraticYIntercept({
32981
+ intercept: srFormatNumber(c, locale)
32982
+ });
32983
+ const srQuadraticInteractiveElements = strings.srInteractiveElements({
32984
+ elements: strings.srQuadraticInteractiveElements({
32985
+ point1X: srFormatNumber(state.coords[0][0], locale),
32986
+ point1Y: srFormatNumber(state.coords[0][1], locale),
32987
+ point2X: srFormatNumber(state.coords[1][0], locale),
32988
+ point2Y: srFormatNumber(state.coords[1][1], locale),
32989
+ point3X: srFormatNumber(state.coords[2][0], locale),
32990
+ point3Y: srFormatNumber(state.coords[2][1], locale)
32991
+ })
32992
+ });
32993
+ return {
32994
+ srQuadraticGraph,
32995
+ srQuadraticDirection,
32996
+ srQuadraticVertex,
32997
+ srQuadraticXIntercepts,
32998
+ srQuadraticYIntercept,
32999
+ srQuadraticInteractiveElements
33000
+ };
33001
+ }
32818
33002
 
32819
33003
  function renderRayGraph(state, dispatch) {
32820
33004
  return {
@@ -32940,17 +33124,69 @@ function renderSegmentGraph(state, dispatch) {
32940
33124
  graphState: state,
32941
33125
  dispatch: dispatch
32942
33126
  }),
32943
- interactiveElementsDescription: null
33127
+ interactiveElementsDescription: /*#__PURE__*/React.createElement(SegmentGraphDescription, {
33128
+ state: state
33129
+ })
32944
33130
  };
32945
33131
  }
32946
- const SegmentGraph = props => {
32947
- const {
32948
- dispatch
32949
- } = props;
33132
+ const SegmentGraph = ({
33133
+ dispatch,
33134
+ graphState
33135
+ }) => {
32950
33136
  const {
32951
33137
  coords: segments
32952
- } = props.graphState;
32953
- return /*#__PURE__*/React.createElement(React.Fragment, null, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement(MovableLine, {
33138
+ } = graphState;
33139
+ const {
33140
+ strings,
33141
+ locale
33142
+ } = usePerseusI18n();
33143
+ const segmentUniqueId = React.useId();
33144
+ const lengthDescriptionId = segmentUniqueId + "-length";
33145
+ const wholeGraphDescriptionId = segmentUniqueId + "-whole-graph";
33146
+ function getWholeSegmentGraphAriaLabel() {
33147
+ return (segments == null ? void 0 : segments.length) > 1 ? strings.srMultipleSegmentGraphAriaLabel({
33148
+ countOfSegments: segments.length
33149
+ }) : strings.srSingleSegmentGraphAriaLabel;
33150
+ }
33151
+ const wholeSegmentGraphAriaLabel = getWholeSegmentGraphAriaLabel();
33152
+ function getIndividualSegmentAriaLabel(segment, index) {
33153
+ if (segments.length === 1) {
33154
+ return strings.srSingleSegmentLabel({
33155
+ point1X: srFormatNumber(segments[0][0][X], locale),
33156
+ point1Y: srFormatNumber(segments[0][0][Y], locale),
33157
+ point2X: srFormatNumber(segments[0][1][X], locale),
33158
+ point2Y: srFormatNumber(segments[0][1][Y], locale)
33159
+ });
33160
+ }
33161
+ return strings.srMultipleSegmentIndividualLabel({
33162
+ point1X: srFormatNumber(segment[0][X], locale),
33163
+ point1Y: srFormatNumber(segment[0][Y], locale),
33164
+ point2X: srFormatNumber(segment[1][X], locale),
33165
+ point2Y: srFormatNumber(segment[1][Y], locale),
33166
+ indexOfSegment: index + 1
33167
+ });
33168
+ }
33169
+ function getWholeSegmentGraphAriaDescription() {
33170
+ return segments.map((segment, index) => getIndividualSegmentAriaLabel(segment, index)).join(" ");
33171
+ }
33172
+ function formatSegment(endpointNumber, x, y, index) {
33173
+ const segObj = {
33174
+ endpointNumber: endpointNumber,
33175
+ x: srFormatNumber(x, locale),
33176
+ y: srFormatNumber(y, locale)
33177
+ };
33178
+ return segments.length > 1 ? strings.srMultipleSegmentGraphEndpointAriaLabel(_extends({}, segObj, {
33179
+ indexOfSegment: index
33180
+ })) : strings.srSingleSegmentGraphEndpointAriaLabel(segObj);
33181
+ }
33182
+ return /*#__PURE__*/React.createElement("g", {
33183
+ "aria-label": wholeSegmentGraphAriaLabel,
33184
+ "aria-describedby": wholeGraphDescriptionId
33185
+ }, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement("g", {
33186
+ "aria-label": getIndividualSegmentAriaLabel(segment, i),
33187
+ "aria-describedby": lengthDescriptionId,
33188
+ key: `${segmentUniqueId}-${i}`
33189
+ }, /*#__PURE__*/React.createElement(MovableLine, {
32954
33190
  key: i,
32955
33191
  points: segment,
32956
33192
  onMoveLine: delta => {
@@ -32958,9 +33194,57 @@ const SegmentGraph = props => {
32958
33194
  },
32959
33195
  onMovePoint: (endpointIndex, destination) => {
32960
33196
  dispatch(actions.segment.movePointInFigure(i, endpointIndex, destination));
33197
+ },
33198
+ ariaLabels: {
33199
+ point1AriaLabel: formatSegment(1, segment[0][X], segment[0][Y], i + 1),
33200
+ point2AriaLabel: formatSegment(2, segment[1][X], segment[1][Y], i + 1),
33201
+ grabHandleAriaLabel: strings.srSegmentGrabHandle({
33202
+ point1X: srFormatNumber(segment[0][X], locale),
33203
+ point1Y: srFormatNumber(segment[0][Y], locale),
33204
+ point2X: srFormatNumber(segment[1][X], locale),
33205
+ point2Y: srFormatNumber(segment[1][Y], locale)
33206
+ })
32961
33207
  }
32962
- })));
33208
+ }), /*#__PURE__*/React.createElement("g", {
33209
+ id: lengthDescriptionId,
33210
+ style: a11y.srOnly
33211
+ }, strings.srSegmentLength({
33212
+ length: srFormatNumber(getLengthOfSegment(segment), locale)
33213
+ })))), /*#__PURE__*/React.createElement("g", {
33214
+ style: a11y.srOnly,
33215
+ id: wholeGraphDescriptionId
33216
+ }, getWholeSegmentGraphAriaDescription()));
32963
33217
  };
33218
+ function getLengthOfSegment(segment) {
33219
+ return point.distanceToPoint(...segment);
33220
+ }
33221
+ function SegmentGraphDescription({
33222
+ state
33223
+ }) {
33224
+ // The reason that SegmentGraphDescription is a component (rather than a
33225
+ // function that returns a string) is because it needs to use a
33226
+ // hook: `usePerseusI18n`.
33227
+ const i18n = usePerseusI18n();
33228
+ return describeSegmentGraph(state, i18n);
33229
+ }
33230
+
33231
+ // Exported for testing
33232
+ function describeSegmentGraph(state, i18n) {
33233
+ const {
33234
+ strings,
33235
+ locale
33236
+ } = i18n;
33237
+ const segmentDescriptions = state.coords.map(([point1, point2], index) => strings.srMultipleSegmentIndividualLabel({
33238
+ point1X: srFormatNumber(point1[X], locale),
33239
+ point1Y: srFormatNumber(point1[Y], locale),
33240
+ point2X: srFormatNumber(point2[X], locale),
33241
+ point2Y: srFormatNumber(point2[Y], locale),
33242
+ indexOfSegment: index + 1
33243
+ }));
33244
+ return strings.srInteractiveElements({
33245
+ elements: segmentDescriptions.join(" ")
33246
+ });
33247
+ }
32964
33248
 
32965
33249
  function renderSinusoidGraph(state, dispatch) {
32966
33250
  return {
@@ -33236,9 +33520,10 @@ const MafsGraph = props => {
33236
33520
  x: viewboxX,
33237
33521
  y: viewboxY
33238
33522
  };
33523
+ const i18n = usePerseusI18n();
33239
33524
  const {
33240
33525
  strings
33241
- } = usePerseusI18n();
33526
+ } = i18n;
33242
33527
  const interactionPrompt = isUnlimitedGraphState(state) && state.showKeyboardInteractionInvitation;
33243
33528
  useOnMountEffect(() => {
33244
33529
  analytics.onAnalyticsEvent({
@@ -33255,7 +33540,8 @@ const MafsGraph = props => {
33255
33540
  interactiveElementsDescription
33256
33541
  } = renderGraphElements({
33257
33542
  state,
33258
- dispatch
33543
+ dispatch,
33544
+ i18n
33259
33545
  });
33260
33546
  return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
33261
33547
  value: {
@@ -33633,7 +33919,8 @@ const calculateNestedSVGCoords = (range, width, height) => {
33633
33919
  const renderGraphElements = props => {
33634
33920
  const {
33635
33921
  state,
33636
- dispatch
33922
+ dispatch,
33923
+ i18n
33637
33924
  } = props;
33638
33925
  const {
33639
33926
  type
@@ -33656,7 +33943,7 @@ const renderGraphElements = props => {
33656
33943
  case "circle":
33657
33944
  return renderCircleGraph(state, dispatch);
33658
33945
  case "quadratic":
33659
- return renderQuadraticGraph(state, dispatch);
33946
+ return renderQuadraticGraph(state, dispatch, i18n);
33660
33947
  case "sinusoid":
33661
33948
  return renderSinusoidGraph(state, dispatch);
33662
33949
  case "none":
@@ -35188,7 +35475,7 @@ class LegacyInteractiveGraph extends React.Component {
35188
35475
  };
35189
35476
  this.setTrashCanVisibility = opacity => {
35190
35477
  const graphie = this.graphie;
35191
- if (number$1.equal(opacity, 0)) {
35478
+ if (number.equal(opacity, 0)) {
35192
35479
  if (this.trashCan) {
35193
35480
  this.trashCan.remove();
35194
35481
  this.trashCan = null;
@@ -37238,19 +37525,21 @@ class LabelImage extends React.Component {
37238
37525
  this._mounted = false;
37239
37526
  }
37240
37527
  getUserInput() {
37241
- const {
37242
- markers
37243
- } = this.props;
37244
37528
  return {
37245
- markers
37529
+ markers: this.props.markers.map(marker => ({
37530
+ selected: marker.selected,
37531
+ label: marker.label
37532
+ }))
37246
37533
  };
37247
37534
  }
37248
37535
  getPromptJSON() {
37249
37536
  return getPromptJSON$a(this.props, this.getUserInput());
37250
37537
  }
37251
37538
 
37252
- // TODO(LEMS-2544): Investigate impact on scoring; possibly pull out &/or remove rubric parameter.
37253
- showRationalesForCurrentlySelectedChoices(rubric) {
37539
+ // TODO(LEMS-2544): Investigate impact on scoring
37540
+ // Also consider how scoreMarker is being called as it seems to require the marker.answers property.
37541
+ // Removed rubric parameter, but it gets a full widget options object from the renderer
37542
+ showRationalesForCurrentlySelectedChoices() {
37254
37543
  const {
37255
37544
  markers
37256
37545
  } = this.props;
@@ -37258,7 +37547,7 @@ class LabelImage extends React.Component {
37258
37547
  onChange
37259
37548
  } = this.props;
37260
37549
  const updatedMarkers = markers.map(marker => {
37261
- const score = scoreLabelImageMarker(marker);
37550
+ const score = scoreLabelImageMarker(marker.selected, marker.answers);
37262
37551
  return _extends({}, marker, {
37263
37552
  // Reveal correctness state for markers with answers.
37264
37553
  showCorrectness: score.hasAnswers ? score.isCorrect ? "correct" : "incorrect" : undefined
@@ -37364,6 +37653,7 @@ class LabelImage extends React.Component {
37364
37653
  selected: selected.length ? selected : undefined
37365
37654
  }));
37366
37655
  }
37656
+ // TODO(LEMS-2723): Investigate if possible to change this to not require answers
37367
37657
  renderMarkers() {
37368
37658
  const {
37369
37659
  markers,
@@ -37405,7 +37695,7 @@ class LabelImage extends React.Component {
37405
37695
  bottom: "top"
37406
37696
  }[markerPosition];
37407
37697
  }
37408
- const score = scoreLabelImageMarker(marker);
37698
+ const score = scoreLabelImageMarker(marker.selected, marker.answers);
37409
37699
  // Once the question is answered, show markers
37410
37700
  // with correct answers, otherwise passthrough
37411
37701
  // the correctness state.
@@ -38950,14 +39240,16 @@ var Matrix$1 = {
38950
39240
  isLintable: true,
38951
39241
  // TODO(LEMS-2656): remove TS suppression
38952
39242
  // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
38953
- scorer: scoreMatrix
39243
+ scorer: scoreMatrix,
39244
+ // TODO(LEMS-2656): remove TS suppression
39245
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
39246
+ validator: validateMatrix
38954
39247
  };
38955
39248
 
38956
39249
  const getPromptJSON$7 = () => {
38957
39250
  return getUnsupportedPromptJSON("measurer");
38958
39251
  };
38959
39252
 
38960
- const _excluded$1 = ["imageUrl", "imageTop", "imageLeft"];
38961
39253
  const defaultImage = {
38962
39254
  url: null,
38963
39255
  top: 0,
@@ -39062,33 +39354,13 @@ Measurer.defaultProps = {
39062
39354
  rulerPixels: 40,
39063
39355
  rulerLength: 10
39064
39356
  };
39065
- const propUpgrades = {
39066
- "1": v0props => {
39067
- const {
39068
- imageUrl,
39069
- imageTop,
39070
- imageLeft
39071
- } = v0props,
39072
- rest = _objectWithoutPropertiesLoose(v0props, _excluded$1);
39073
- return _extends({}, rest, {
39074
- image: {
39075
- url: imageUrl,
39076
- top: imageTop,
39077
- left: imageLeft
39078
- }
39079
- });
39080
- }
39081
- };
39082
39357
  var Measurer$1 = {
39083
39358
  name: "measurer",
39084
39359
  displayName: "Measurer",
39085
39360
  hidden: true,
39086
39361
  widget: Measurer,
39087
- version: {
39088
- major: 1,
39089
- minor: 0
39090
- },
39091
- propUpgrades: propUpgrades,
39362
+ version: measurerLogic.version,
39363
+ propUpgrades: measurerLogic.widgetOptionsUpgrades,
39092
39364
  // TODO: things that aren't interactive shouldn't need scoring functions
39093
39365
  scorer: () => scoreNoop(1)
39094
39366
  };
@@ -39661,10 +39933,10 @@ function parseBondModifier(smiles, ctx) {
39661
39933
  const firstChar = smiles[0];
39662
39934
  const rest = smiles.slice(1);
39663
39935
  if (firstChar === "=") {
39664
- return parse$2(rest, _mset(ctx, ["bond", "bondType"], "double"));
39936
+ return parse$1(rest, _mset(ctx, ["bond", "bondType"], "double"));
39665
39937
  }
39666
39938
  if (firstChar === "#") {
39667
- return parse$2(rest, _mset(ctx, ["bond", "bondType"], "triple"));
39939
+ return parse$1(rest, _mset(ctx, ["bond", "bondType"], "triple"));
39668
39940
  }
39669
39941
  throw new ParseError$1("Invalid character: " + firstChar);
39670
39942
  }
@@ -39723,8 +39995,8 @@ function parseParenthesizedExpression(smiles, ctx) {
39723
39995
  idx: newCtx.idx.concat([[inBranchIdx, 0]]),
39724
39996
  parens: newCtx.parens.concat("(")
39725
39997
  });
39726
- const parenExpr = parse$2(rest, parenCtx);
39727
- const remainder = parse$2(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
39998
+ const parenExpr = parse$1(rest, parenCtx);
39999
+ const remainder = parse$1(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
39728
40000
  return [parenExpr].concat(remainder);
39729
40001
  }
39730
40002
  if (firstChar === ")") {
@@ -39788,7 +40060,7 @@ function parseAtom(smiles, ctx) {
39788
40060
 
39789
40061
  // increment the atom counter and reset the branch counter
39790
40062
  const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
39791
- let restOfMolecule = parse$2(rest, _mset(newCtx, ["bond", "bondType"], "single"));
40063
+ let restOfMolecule = parse$1(rest, _mset(newCtx, ["bond", "bondType"], "single"));
39792
40064
  if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
39793
40065
  //TODO(colin): fix this awkwardness.
39794
40066
  restOfMolecule = [restOfMolecule];
@@ -39827,7 +40099,7 @@ function isModifierChar(s) {
39827
40099
  * ParseError: if the input is not valid SMILES or contains features not
39828
40100
  * yet implemented.
39829
40101
  */
39830
- function parse$2(smiles, ctx) {
40102
+ function parse$1(smiles, ctx) {
39831
40103
  if (!validate(smiles)) {
39832
40104
  throw new ParseError$1("Invalid input.");
39833
40105
  }
@@ -39852,7 +40124,7 @@ function parse$2(smiles, ctx) {
39852
40124
  return parseParenthesizedExpression(smiles, ctx);
39853
40125
  }
39854
40126
  var SmilesParser = {
39855
- parse: parse$2,
40127
+ parse: parse$1,
39856
40128
  ParseError: ParseError$1
39857
40129
  };
39858
40130
 
@@ -39860,7 +40132,7 @@ var SmilesParser = {
39860
40132
  const {
39861
40133
  layout
39862
40134
  } = MoleculeLayout;
39863
- const parse$1 = SmilesParser.parse;
40135
+ const parse = SmilesParser.parse;
39864
40136
  const ParseError = SmilesParser.ParseError;
39865
40137
  const borderSize = 30;
39866
40138
  class Molecule extends React.Component {
@@ -39874,7 +40146,7 @@ class Molecule extends React.Component {
39874
40146
  try {
39875
40147
  this.setState({
39876
40148
  // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
39877
- parsedSmiles: parse$1(smiles),
40149
+ parsedSmiles: parse(smiles),
39878
40150
  error: null
39879
40151
  });
39880
40152
  } catch (e) {
@@ -40093,7 +40365,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
40093
40365
  const x = range[0] + i * tickStep;
40094
40366
  fractions.push(x);
40095
40367
  }
40096
- const getDenom = x => number$1.toFraction(x)[1];
40368
+ const getDenom = x => number.toFraction(x)[1];
40097
40369
  const denoms = _.map(fractions, getDenom);
40098
40370
  base = _.reduce(denoms, (x, y) => KhanMath.getLCM(x, y));
40099
40371
  } else {
@@ -40156,7 +40428,7 @@ class NumberLine extends React.Component {
40156
40428
  let initialX = this.props.numLinePosition;
40157
40429
  const divisionRange = this.props.divisionRange;
40158
40430
  initialX = initialX == null ? range[0] : initialX;
40159
- return range[0] < range[1] && number$1.sign(initialX - range[0]) >= 0 && number$1.sign(initialX - range[1]) <= 0 && divisionRange[0] < divisionRange[1] && 0 < this.props.numDivisions && 0 < this.props.snapDivisions;
40431
+ return range[0] < range[1] && number.sign(initialX - range[0]) >= 0 && number.sign(initialX - range[1]) <= 0 && divisionRange[0] < divisionRange[1] && 0 < this.props.numDivisions && 0 < this.props.snapDivisions;
40160
40432
  };
40161
40433
  this.onNumDivisionsChange = (numDivisions, cb) => {
40162
40434
  const divRange = this.props.divisionRange.slice();
@@ -40259,7 +40531,7 @@ class NumberLine extends React.Component {
40259
40531
  const right = props.range[1];
40260
40532
  const snapX = props.tickStep / props.snapDivisions;
40261
40533
  let x = bound(numLinePosition, left, right);
40262
- x = left + number$1.roundTo(x - left, snapX);
40534
+ x = left + number.roundTo(x - left, snapX);
40263
40535
  assert$1(_.isFinite(x));
40264
40536
  return x;
40265
40537
  };
@@ -40550,7 +40822,10 @@ var NumberLine$1 = {
40550
40822
  staticTransform: staticTransform$1,
40551
40823
  // TODO(LEMS-2656): remove TS suppression
40552
40824
  // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
40553
- scorer: scoreNumberLine
40825
+ scorer: scoreNumberLine,
40826
+ // TODO(LEMS-2656): remove TS suppression
40827
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
40828
+ validator: validateNumberLine
40554
40829
  };
40555
40830
 
40556
40831
  const getPromptJSON$5 = (renderProps, userInput) => {
@@ -41113,7 +41388,11 @@ var Orderer$1 = {
41113
41388
  isLintable: true,
41114
41389
  // TODO(LEMS-2656): remove TS suppression
41115
41390
  // @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
41116
- scorer: scoreOrderer
41391
+ scorer: scoreOrderer,
41392
+ // TODO(LEMS-2656): remove TS suppression
41393
+ // @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
41394
+ validator: validateOrderer,
41395
+ getPublicWidgetOptions: getOrdererPublicWidgetOptions
41117
41396
  };
41118
41397
 
41119
41398
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -41157,10 +41436,6 @@ var PassageRefTarget$1 = {
41157
41436
  transform: editorProps => {
41158
41437
  return _.pick(editorProps, "content");
41159
41438
  },
41160
- version: {
41161
- major: 0,
41162
- minor: 0
41163
- },
41164
41439
  isLintable: true,
41165
41440
  // TODO: things that aren't interactive shouldn't need scoring functions
41166
41441
  scorer: () => scoreNoop()
@@ -42183,7 +42458,10 @@ var Plotter$1 = {
42183
42458
  staticTransform: staticTransform,
42184
42459
  // TODO(LEMS-2656): remove TS suppression
42185
42460
  // @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
42186
- scorer: scorePlotter
42461
+ scorer: scorePlotter,
42462
+ // TODO(LEMS-2656): remove TS suppression
42463
+ // @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
42464
+ validator: validatePlotter
42187
42465
  };
42188
42466
 
42189
42467
  const getPromptJSON$2 = () => {
@@ -42348,7 +42626,10 @@ var Sorter$1 = {
42348
42626
  isLintable: true,
42349
42627
  // TODO(LEMS-2656): remove TS suppression
42350
42628
  // @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
42351
- scorer: scoreSorter
42629
+ scorer: scoreSorter,
42630
+ // TODO(LEMS-2656): remove TS suppression
42631
+ // @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
42632
+ validator: validateSorter
42352
42633
  };
42353
42634
 
42354
42635
  const {
@@ -42570,7 +42851,10 @@ var Table$1 = {
42570
42851
  isLintable: true,
42571
42852
  // TODO(LEMS-2656): remove TS suppression
42572
42853
  // @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
42573
- scorer: scoreTable
42854
+ scorer: scoreTable,
42855
+ // TODO(LEMS-2656): remove TS suppression
42856
+ // @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
42857
+ validator: validateTable
42574
42858
  };
42575
42859
 
42576
42860
  const getPromptJSON = () => {
@@ -42753,7 +43037,7 @@ const init = function init() {
42753
43037
 
42754
43038
  // This file is processed by a Rollup plugin (replace) to inject the production
42755
43039
  const libName = "@khanacademy/perseus";
42756
- const libVersion = "51.0.0";
43040
+ const libVersion = "52.0.0";
42757
43041
  addLibraryVersionToPerseusDebug(libName, libVersion);
42758
43042
 
42759
43043
  const apiVersion = {
@@ -44191,6 +44475,71 @@ const registerAllWidgetsForTesting = () => {
44191
44475
  replaceDeprecatedWidgets();
44192
44476
  };
44193
44477
 
44478
+ /**
44479
+ * Get a widget type by a widget's ID
44480
+ * (please don't derive type from ID)
44481
+ *
44482
+ * @param {string} widgetId the ID of the widget
44483
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
44484
+ * @returns {string} the widget type (ie "radio")
44485
+ */
44486
+ function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
44487
+ var _widget$type;
44488
+ const widget = widgetMap[WidgetId];
44489
+ return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
44490
+ }
44491
+
44492
+ /**
44493
+ * Does the content have a specific type of widget?
44494
+ *
44495
+ * @param {string} type the type of the widget in question (ie "radio")
44496
+ * @param {string} content the string to search through
44497
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
44498
+ * @returns {boolean} if the content includes the widget type
44499
+ */
44500
+ function contentHasWidgetType(type, content, widgetMap) {
44501
+ return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
44502
+ }
44503
+
44504
+ /**
44505
+ * Pull the widget map out of ItemData
44506
+ *
44507
+ * @param {PerseusItem} PerseusItem containing a widgetMap
44508
+ * @returns {WidgetMap} the widget map in the PerseusItem
44509
+ */
44510
+ function getWidgetsMapFromItemData(itemData) {
44511
+ return itemData.question.widgets;
44512
+ }
44513
+
44514
+ /**
44515
+ * Get the widget information from a WidgetMap
44516
+ *
44517
+ * @param {string} widgetId the ID of the widget
44518
+ * @returns {PerseusWidget | null} the widget data if it exists, otherwise null
44519
+ */
44520
+ function getWidgetFromWidgetMap(widgetId, widgetMap) {
44521
+ var _widgetMap$widgetId;
44522
+ return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
44523
+ }
44524
+
44525
+ /**
44526
+ * Get select widgets from a widget map.
44527
+ *
44528
+ * @param {ReadonlyArray<string>} widgetIds to extract
44529
+ * @param {PerseusWidgetsMap} widgetMap to extract from
44530
+ * @return {PerseusWidgetsMap} a new widget map with requested widgets
44531
+ */
44532
+ function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
44533
+ const widgets = {};
44534
+ widgetIds.forEach(widgetId => {
44535
+ const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
44536
+ if (widget) {
44537
+ widgets[widgetId] = widget;
44538
+ }
44539
+ });
44540
+ return widgets;
44541
+ }
44542
+
44194
44543
  /**
44195
44544
  * This function extracts the answers from the widgets.
44196
44545
  *
@@ -44714,1670 +45063,6 @@ const getValidWidgetIds = perseusItem => {
44714
45063
  return keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
44715
45064
  };
44716
45065
 
44717
- function isRealJSONParse(jsonParse) {
44718
- const randomPhrase = buildRandomPhrase();
44719
- const randomHintPhrase = buildRandomPhrase();
44720
- const randomString = buildRandomString();
44721
- const testingObject = JSON.stringify({
44722
- answerArea: {
44723
- calculator: false,
44724
- chi2Table: false,
44725
- financialCalculatorMonthlyPayment: false,
44726
- financialCalculatorTimeToPayOff: false,
44727
- financialCalculatorTotalAmount: false,
44728
- periodicTable: false,
44729
- periodicTableWithKey: false,
44730
- tTable: false,
44731
- zTable: false
44732
- },
44733
- hints: [randomHintPhrase, `=${Math.floor(Math.random() * 50) + 1}`],
44734
- itemDataVersion: {
44735
- major: 0,
44736
- minor: 1
44737
- },
44738
- question: {
44739
- content: `${randomPhrase}`,
44740
- images: {},
44741
- widgets: {
44742
- expression1: {
44743
- alignment: "default",
44744
- graded: false,
44745
- options: {
44746
- answerForms: [{
44747
- considered: "wrong",
44748
- form: false,
44749
- key: 0,
44750
- simplify: false,
44751
- value: `${randomString}`
44752
- }],
44753
- ariaLabel: "Answer",
44754
- buttonSets: ["basic"],
44755
- functions: ["f", "g", "h"],
44756
- static: true,
44757
- times: false,
44758
- visibleLabel: "Answer"
44759
- },
44760
- static: true,
44761
- type: "expression",
44762
- version: {
44763
- major: 1,
44764
- minor: 0
44765
- }
44766
- }
44767
- }
44768
- }
44769
- });
44770
- const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
44771
- const parsedTestJSON = jsonParse(testJSON);
44772
- const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
44773
- return approximateDeepEqual(parsedTestItemData, testingObject);
44774
- }
44775
- function buildRandomString(capitalize = false) {
44776
- let randomString = "";
44777
- const randomLength = Math.floor(Math.random() * 8) + 3;
44778
- for (let i = 0; i < randomLength; i++) {
44779
- const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
44780
- randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
44781
- }
44782
- return randomString;
44783
- }
44784
- function buildRandomPhrase() {
44785
- const phrases = [];
44786
- const randomLength = Math.floor(Math.random() * 10) + 5;
44787
- for (let i = 0; i < randomLength; i++) {
44788
- phrases.push(buildRandomString(i === 0));
44789
- }
44790
- const modifierStart = ["**", "$"];
44791
- const modifierEnd = ["**", "$"];
44792
- const modifierIndex = Math.floor(Math.random() * modifierStart.length);
44793
- return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
44794
- }
44795
- function buildTestData(testObject) {
44796
- return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
44797
- }
44798
-
44799
- function success(value) {
44800
- return {
44801
- type: "success",
44802
- value
44803
- };
44804
- }
44805
- function failure(detail) {
44806
- return {
44807
- type: "failure",
44808
- detail
44809
- };
44810
- }
44811
- function isFailure(result) {
44812
- return result.type === "failure";
44813
- }
44814
- function isSuccess(result) {
44815
- return result.type === "success";
44816
- }
44817
-
44818
- // Result's `all` function is similar to Promise.all: given an array of
44819
- // results, it returns success if all succeeded, and failure if any failed.
44820
- function all(results, combineFailureDetails = a => a) {
44821
- const values = [];
44822
- const failureDetails = [];
44823
- for (const result of results) {
44824
- if (result.type === "success") {
44825
- values.push(result.value);
44826
- } else {
44827
- failureDetails.push(result.detail);
44828
- }
44829
- }
44830
- if (failureDetails.length > 0) {
44831
- return failure(failureDetails.reduce(combineFailureDetails));
44832
- }
44833
- return success(values);
44834
- }
44835
-
44836
- class ErrorTrackingParseContext {
44837
- constructor(path) {
44838
- this.path = path;
44839
- }
44840
- failure(expected, badValue) {
44841
- return failure([{
44842
- expected: wrapInArray(expected),
44843
- badValue,
44844
- path: this.path
44845
- }]);
44846
- }
44847
- forSubtree(key) {
44848
- return new ErrorTrackingParseContext([...this.path, key]);
44849
- }
44850
- success(value) {
44851
- return success(value);
44852
- }
44853
- }
44854
- function wrapInArray(a) {
44855
- return Array.isArray(a) ? a : [a];
44856
- }
44857
-
44858
- function formatPath(path) {
44859
- return "(root)" + path.map(formatPathSegment).join("");
44860
- }
44861
- function formatPathSegment(segment) {
44862
- if (typeof segment === "string") {
44863
- return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
44864
- }
44865
- return `[${segment.toString()}]`;
44866
- }
44867
- const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
44868
-
44869
- function message(failure) {
44870
- const expected = conjoin(failure.expected);
44871
- const path = formatPath(failure.path);
44872
- const badValue = JSON.stringify(failure.badValue);
44873
- return `At ${path} -- expected ${expected}, but got ${badValue}`;
44874
- }
44875
- function conjoin(items) {
44876
- switch (items.length) {
44877
- // TODO(benchristel): handle 0 if this is reused elsewhere.
44878
- case 1:
44879
- return items[0];
44880
- case 2:
44881
- return items.join(" or ");
44882
- default:
44883
- {
44884
- const allButLast = items.slice(0, items.length - 1);
44885
- const last = items[items.length - 1];
44886
- return allButLast.join(", ") + ", or " + last;
44887
- }
44888
- }
44889
- }
44890
-
44891
- function parse(value, parser) {
44892
- const result = parser(value, new ErrorTrackingParseContext([]));
44893
- if (isFailure(result)) {
44894
- return failure(result.detail.map(message).join("; "));
44895
- }
44896
- return result;
44897
- }
44898
-
44899
- const any = (rawValue, ctx) => ctx.success(rawValue);
44900
-
44901
- function array(elementParser) {
44902
- return (rawValue, ctx) => {
44903
- if (!Array.isArray(rawValue)) {
44904
- return ctx.failure("array", rawValue);
44905
- }
44906
- const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
44907
- return all(elementResults, concat);
44908
- };
44909
- }
44910
- function concat(a, b) {
44911
- return [...a, ...b];
44912
- }
44913
-
44914
- function boolean(rawValue, ctx) {
44915
- if (typeof rawValue === "boolean") {
44916
- return ctx.success(rawValue);
44917
- }
44918
- return ctx.failure("boolean", rawValue);
44919
- }
44920
-
44921
- function constant(acceptedValue) {
44922
- return (rawValue, ctx) => {
44923
- if (rawValue !== acceptedValue) {
44924
- return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
44925
- }
44926
- return ctx.success(acceptedValue);
44927
- };
44928
- }
44929
-
44930
- function enumeration(...acceptedValues) {
44931
- return (rawValue, ctx) => {
44932
- if (typeof rawValue === "string") {
44933
- const index = acceptedValues.indexOf(rawValue);
44934
- if (index > -1) {
44935
- return ctx.success(acceptedValues[index]);
44936
- }
44937
- }
44938
- const expected = acceptedValues.map(v => JSON.stringify(v));
44939
- return ctx.failure(expected, rawValue);
44940
- };
44941
- }
44942
-
44943
- function isObject(x) {
44944
- return x != null && Object.getPrototypeOf(x) === Object.prototype;
44945
- }
44946
-
44947
- function nullable(parseValue) {
44948
- return (rawValue, ctx) => {
44949
- if (rawValue === null) {
44950
- return ctx.success(rawValue);
44951
- }
44952
- return parseValue(rawValue, ctx);
44953
- };
44954
- }
44955
-
44956
- const number = (rawValue, ctx) => {
44957
- if (typeof rawValue === "number") {
44958
- return ctx.success(rawValue);
44959
- }
44960
- return ctx.failure("number", rawValue);
44961
- };
44962
-
44963
- function object(schema) {
44964
- return (rawValue, ctx) => {
44965
- if (!isObject(rawValue)) {
44966
- return ctx.failure("object", rawValue);
44967
- }
44968
- const ret = _extends({}, rawValue);
44969
- const mismatches = [];
44970
- for (const [prop, propParser] of Object.entries(schema)) {
44971
- const result = propParser(rawValue[prop], ctx.forSubtree(prop));
44972
- if (isSuccess(result)) {
44973
- if (result.value !== undefined || prop in rawValue) {
44974
- ret[prop] = result.value;
44975
- }
44976
- } else {
44977
- mismatches.push(...result.detail);
44978
- }
44979
- }
44980
- if (mismatches.length > 0) {
44981
- return failure(mismatches);
44982
- }
44983
- return ctx.success(ret);
44984
- };
44985
- }
44986
-
44987
- function optional(parseValue) {
44988
- return (rawValue, ctx) => {
44989
- if (rawValue === undefined) {
44990
- return ctx.success(rawValue);
44991
- }
44992
- return parseValue(rawValue, ctx);
44993
- };
44994
- }
44995
-
44996
- function pair(parseA, parseB) {
44997
- return (rawValue, ctx) => {
44998
- if (!Array.isArray(rawValue)) {
44999
- return ctx.failure("array", rawValue);
45000
- }
45001
- if (rawValue.length !== 2) {
45002
- return ctx.failure("array of length 2", rawValue);
45003
- }
45004
- const [rawA, rawB] = rawValue;
45005
- const resultA = parseA(rawA, ctx.forSubtree(0));
45006
- if (isFailure(resultA)) {
45007
- return resultA;
45008
- }
45009
- const resultB = parseB(rawB, ctx.forSubtree(1));
45010
- if (isFailure(resultB)) {
45011
- return resultB;
45012
- }
45013
- return ctx.success([resultA.value, resultB.value]);
45014
- };
45015
- }
45016
-
45017
- function pipeParsers(p) {
45018
- return new ParserPipeline(p);
45019
- }
45020
- class ParserPipeline {
45021
- constructor(parser) {
45022
- this.parser = parser;
45023
- }
45024
- then(nextParser) {
45025
- return new ParserPipeline(composeParsers(this.parser, nextParser));
45026
- }
45027
- }
45028
- function composeParsers(parserA, parserB) {
45029
- return (rawValue, ctx) => {
45030
- const partialResult = parserA(rawValue, ctx);
45031
- if (isFailure(partialResult)) {
45032
- return partialResult;
45033
- }
45034
- return parserB(partialResult.value, ctx);
45035
- };
45036
- }
45037
-
45038
- function record(parseKey, parseValue) {
45039
- return (rawValue, ctx) => {
45040
- if (!isObject(rawValue)) {
45041
- return ctx.failure("object", rawValue);
45042
- }
45043
- const result = {};
45044
- const mismatches = [];
45045
- for (const [key, value] of Object.entries(rawValue)) {
45046
- const entryCtx = ctx.forSubtree(key);
45047
- const keyResult = parseKey(key, entryCtx);
45048
- if (isFailure(keyResult)) {
45049
- mismatches.push(...keyResult.detail);
45050
- }
45051
- const valueResult = parseValue(value, entryCtx);
45052
- if (isFailure(valueResult)) {
45053
- mismatches.push(...valueResult.detail);
45054
- }
45055
- if (isSuccess(keyResult) && isSuccess(valueResult)) {
45056
- result[keyResult.value] = valueResult.value;
45057
- }
45058
- }
45059
- if (mismatches.length > 0) {
45060
- return failure(mismatches);
45061
- }
45062
- return ctx.success(result);
45063
- };
45064
- }
45065
-
45066
- const string = (rawValue, ctx) => {
45067
- if (typeof rawValue === "string") {
45068
- return ctx.success(rawValue);
45069
- }
45070
- return ctx.failure("string", rawValue);
45071
- };
45072
-
45073
- function trio(parseA, parseB, parseC) {
45074
- return (rawValue, ctx) => {
45075
- if (!Array.isArray(rawValue)) {
45076
- return ctx.failure("array", rawValue);
45077
- }
45078
- if (rawValue.length !== 3) {
45079
- return ctx.failure("array of length 3", rawValue);
45080
- }
45081
- const resultA = parseA(rawValue[0], ctx.forSubtree(0));
45082
- if (isFailure(resultA)) {
45083
- return resultA;
45084
- }
45085
- const resultB = parseB(rawValue[1], ctx.forSubtree(1));
45086
- if (isFailure(resultB)) {
45087
- return resultB;
45088
- }
45089
- const resultC = parseC(rawValue[2], ctx.forSubtree(2));
45090
- if (isFailure(resultC)) {
45091
- return resultC;
45092
- }
45093
- return ctx.success([resultA.value, resultB.value, resultC.value]);
45094
- };
45095
- }
45096
-
45097
- function union(parseBranch) {
45098
- return new UnionBuilder(parseBranch);
45099
- }
45100
- class UnionBuilder {
45101
- constructor(parser) {
45102
- this.parser = parser;
45103
- }
45104
- or(newBranch) {
45105
- return new UnionBuilder(either(this.parser, newBranch));
45106
- }
45107
- }
45108
- function either(parseA, parseB) {
45109
- return (rawValue, ctx) => {
45110
- const resultA = parseA(rawValue, ctx);
45111
- if (isSuccess(resultA)) {
45112
- return resultA;
45113
- }
45114
- return parseB(rawValue, ctx);
45115
- };
45116
- }
45117
-
45118
- function defaulted(parser, fallback) {
45119
- return (rawValue, ctx) => {
45120
- if (rawValue == null) {
45121
- return success(fallback(rawValue));
45122
- }
45123
- return parser(rawValue, ctx);
45124
- };
45125
- }
45126
-
45127
- const parseImages = defaulted(record(string, object({
45128
- width: number,
45129
- height: number
45130
- })), () => ({}));
45131
-
45132
- function parseWidget(parseType, parseOptions) {
45133
- return object({
45134
- type: parseType,
45135
- static: optional(boolean),
45136
- graded: optional(boolean),
45137
- alignment: optional(string),
45138
- options: parseOptions,
45139
- key: optional(number),
45140
- version: optional(object({
45141
- major: number,
45142
- minor: number
45143
- }))
45144
- });
45145
- }
45146
- function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
45147
- return object({
45148
- type: parseType,
45149
- static: optional(boolean),
45150
- graded: optional(boolean),
45151
- alignment: optional(string),
45152
- options: parseOptions,
45153
- key: optional(number),
45154
- version: parseVersion
45155
- });
45156
- }
45157
-
45158
- const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
45159
- items: array(string),
45160
- categories: array(string),
45161
- randomizeItems: defaulted(boolean, () => false),
45162
- static: defaulted(boolean, () => false),
45163
- values: array(defaulted(number, () => 0)),
45164
- highlightLint: optional(boolean),
45165
- linterContext: optional(object({
45166
- contentType: string,
45167
- paths: array(string),
45168
- stack: array(string)
45169
- }))
45170
- }));
45171
-
45172
- const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
45173
- programID: string,
45174
- programType: any,
45175
- settings: array(object({
45176
- name: string,
45177
- value: string
45178
- })),
45179
- showEditor: boolean,
45180
- showButtons: boolean,
45181
- height: number,
45182
- static: defaulted(boolean, () => false)
45183
- }));
45184
-
45185
- const parseDefinitionWidget = parseWidget(constant("definition"), object({
45186
- togglePrompt: string,
45187
- definition: string,
45188
- static: defaulted(boolean, () => false)
45189
- }));
45190
-
45191
- const parseDropdownWidget = parseWidget(constant("dropdown"), object({
45192
- placeholder: defaulted(string, () => ""),
45193
- ariaLabel: optional(string),
45194
- visibleLabel: optional(string),
45195
- static: defaulted(boolean, () => false),
45196
- choices: array(object({
45197
- content: string,
45198
- correct: boolean
45199
- }))
45200
- }));
45201
-
45202
- const parseExplanationWidget = parseWidget(constant("explanation"), object({
45203
- showPrompt: string,
45204
- hidePrompt: string,
45205
- explanation: string,
45206
- // We wrap parseWidgetsMap in a function here to make sure it is not
45207
- // referenced before it is defined. There is an import cycle between
45208
- // this file and widgets-map.ts that could cause it to be undefined.
45209
- widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
45210
- static: defaulted(boolean, () => false)
45211
- }));
45212
-
45213
- // Given a function, creates a PartialParser that converts one type to another
45214
- // using that function. The returned parser never fails.
45215
- function convert(f) {
45216
- return (rawValue, ctx) => ctx.success(f(rawValue));
45217
- }
45218
-
45219
- /**
45220
- * Creates a parser for a widget options type with multiple major versions. Old
45221
- * versions are migrated to the latest version. The parse fails if the input
45222
- * data does not match any of the versions.
45223
- *
45224
- * @example
45225
- * const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
45226
- * .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
45227
- * .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
45228
- * .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
45229
- * .parser;
45230
- *
45231
- * @param latestMajorVersion the latest major version of the widget options.
45232
- * @param parseLatest a {@link Parser} for the latest version of the widget
45233
- * options.
45234
- * @returns a builder object, to which migrations from earlier versions can be
45235
- * added. Migrations must be added in "reverse chronological" order as in the
45236
- * example above.
45237
- */
45238
- function versionedWidgetOptions(latestMajorVersion, parseLatest) {
45239
- return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
45240
- }
45241
- class VersionedWidgetOptionsParserBuilder {
45242
- constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
45243
- this.parser = void 0;
45244
- this.migrateToLatest = migrateToLatest;
45245
- this.parseOtherVersions = parseOtherVersions;
45246
- const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
45247
- this.parser = (raw, ctx) => {
45248
- if (!isObject(raw)) {
45249
- return ctx.failure("object", raw);
45250
- }
45251
- const versionParseResult = parseVersionedObject(raw, ctx);
45252
- if (isFailure(versionParseResult)) {
45253
- return versionParseResult;
45254
- }
45255
- if (versionParseResult.value.version.major !== majorVersion) {
45256
- return this.parseOtherVersions(raw, ctx);
45257
- }
45258
- return parseThisVersionAndMigrateToLatest(raw, ctx);
45259
- };
45260
- }
45261
-
45262
- /**
45263
- * Add a migration from an old version of the widget options.
45264
- */
45265
- withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
45266
- const parseOtherVersions = this.parser;
45267
- const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
45268
- return new VersionedWidgetOptionsParserBuilder(majorVersion, parseOldVersion, migrateToLatest, parseOtherVersions);
45269
- }
45270
- }
45271
- const parseVersionedObject = object({
45272
- version: defaulted(object({
45273
- major: number,
45274
- minor: number
45275
- }), () => ({
45276
- major: 0,
45277
- minor: 0
45278
- }))
45279
- });
45280
-
45281
- const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
45282
- const parsePossiblyInvalidAnswerForm = object({
45283
- // `value` is the possibly invalid part of this. It should always be a
45284
- // string, but some answer forms don't have it. The Expression widget
45285
- // ignores invalid values, so we can safely filter them out during parsing.
45286
- value: optional(string),
45287
- form: defaulted(boolean, () => false),
45288
- simplify: defaulted(boolean, () => false),
45289
- considered: enumeration("correct", "wrong", "ungraded"),
45290
- key: pipeParsers(stringOrNumberOrNullOrUndefined).then(convert(String)).parser
45291
- });
45292
- function removeInvalidAnswerForms(possiblyInvalid) {
45293
- const valid = [];
45294
- for (const answerForm of possiblyInvalid) {
45295
- const {
45296
- value
45297
- } = answerForm;
45298
- if (value != null) {
45299
- // Copying the object seems to be needed to make TypeScript happy
45300
- valid.push(_extends({}, answerForm, {
45301
- value
45302
- }));
45303
- }
45304
- }
45305
- return valid;
45306
- }
45307
-
45308
- // NOTE(benchristel): I copied the default buttonSets from
45309
- // expression.tsx. See the parse-perseus-json/README.md for
45310
- // an explanation of why we want to duplicate the default here.
45311
- const parseButtonSets = defaulted(array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")), () => ["basic", "trig", "prealgebra", "logarithms"]);
45312
- const version1 = object({
45313
- major: constant(1),
45314
- minor: number
45315
- });
45316
- const parseExpressionWidgetV1 = parseWidgetWithVersion(version1, constant("expression"), object({
45317
- answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
45318
- functions: array(string),
45319
- times: boolean,
45320
- visibleLabel: optional(string),
45321
- ariaLabel: optional(string),
45322
- buttonSets: parseButtonSets,
45323
- buttonsVisible: optional(enumeration("always", "never", "focused"))
45324
- }));
45325
- const version0 = optional(object({
45326
- major: constant(0),
45327
- minor: number
45328
- }));
45329
- const parseExpressionWidgetV0 = parseWidgetWithVersion(version0, constant("expression"), object({
45330
- functions: array(string),
45331
- times: boolean,
45332
- visibleLabel: optional(string),
45333
- ariaLabel: optional(string),
45334
- form: boolean,
45335
- simplify: boolean,
45336
- value: string,
45337
- buttonSets: parseButtonSets,
45338
- buttonsVisible: optional(enumeration("always", "never", "focused"))
45339
- }));
45340
- function migrateV0ToV1(widget) {
45341
- const {
45342
- options
45343
- } = widget;
45344
- return _extends({}, widget, {
45345
- version: {
45346
- major: 1,
45347
- minor: 0
45348
- },
45349
- options: {
45350
- times: options.times,
45351
- buttonSets: options.buttonSets,
45352
- functions: options.functions,
45353
- buttonsVisible: options.buttonsVisible,
45354
- visibleLabel: options.visibleLabel,
45355
- ariaLabel: options.ariaLabel,
45356
- answerForms: [{
45357
- considered: "correct",
45358
- form: options.form,
45359
- simplify: options.simplify,
45360
- value: options.value
45361
- }]
45362
- }
45363
- });
45364
- }
45365
- const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
45366
-
45367
- const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
45368
- const parseGradedGroupWidgetOptions = object({
45369
- title: defaulted(string, () => ""),
45370
- hasHint: optional(nullable(boolean)),
45371
- // This module has an import cycle with parsePerseusRenderer.
45372
- // The anonymous function below ensures that we don't try to access
45373
- // parsePerseusRenderer before it's defined.
45374
- hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
45375
- content: string,
45376
- // This module has an import cycle with parseWidgetsMap.
45377
- // The anonymous function below ensures that we don't try to access
45378
- // parseWidgetsMap before it's defined.
45379
- widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
45380
- widgetEnabled: optional(nullable(boolean)),
45381
- immutableWidgets: optional(nullable(boolean)),
45382
- images: record(string, object({
45383
- width: number,
45384
- height: number
45385
- }))
45386
- });
45387
- const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
45388
-
45389
- const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
45390
- gradedGroups: array(parseGradedGroupWidgetOptions)
45391
- }));
45392
-
45393
- /**
45394
- * discriminatedUnion() should be preferred over union() when parsing a
45395
- * discriminated union type, because discriminatedUnion() produces more
45396
- * understandable failure messages. It takes the discriminant as the source of
45397
- * truth for which variant is to be parsed, and expects the other data to match
45398
- * that variant.
45399
- */
45400
- function discriminatedUnionOn(discriminantKey) {
45401
- const noMoreBranches = (raw, ctx) => {
45402
- if (!isObject(raw)) {
45403
- return ctx.failure("object", raw);
45404
- }
45405
- return ctx.forSubtree(discriminantKey).failure("a valid value", raw[discriminantKey]);
45406
- };
45407
- return new DiscriminatedUnionBuilder(discriminantKey, noMoreBranches);
45408
- }
45409
- class DiscriminatedUnionBuilder {
45410
- constructor(discriminantKey, parser) {
45411
- this.discriminantKey = discriminantKey;
45412
- this.parser = parser;
45413
- }
45414
- withBranch(discriminantValue, parseNewVariant) {
45415
- const parseNewBranch = discriminatedUnionBranch(this.discriminantKey, discriminantValue, parseNewVariant, this.parser);
45416
- return new DiscriminatedUnionBuilder(this.discriminantKey, parseNewBranch);
45417
- }
45418
- }
45419
- function discriminatedUnionBranch(discriminantKey, discriminantValue, parseVariant, parseOtherBranches) {
45420
- return (raw, ctx) => {
45421
- if (!isObject(raw)) {
45422
- return ctx.failure("object", raw);
45423
- }
45424
- if (raw[discriminantKey] === discriminantValue) {
45425
- return parseVariant(raw, ctx);
45426
- }
45427
- return parseOtherBranches(raw, ctx);
45428
- };
45429
- }
45430
-
45431
- const pairOfNumbers$3 = pair(number, number);
45432
- const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
45433
- const parseGrapherWidget = parseWidget(constant("grapher"), object({
45434
- availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
45435
- correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
45436
- type: constant("absolute_value"),
45437
- coords: nullable(pairOfPoints)
45438
- })).withBranch("exponential", object({
45439
- type: constant("exponential"),
45440
- asymptote: pairOfPoints,
45441
- coords: nullable(pairOfPoints)
45442
- })).withBranch("linear", object({
45443
- type: constant("linear"),
45444
- coords: nullable(pairOfPoints)
45445
- })).withBranch("logarithm", object({
45446
- type: constant("logarithm"),
45447
- asymptote: pairOfPoints,
45448
- coords: nullable(pairOfPoints)
45449
- })).withBranch("quadratic", object({
45450
- type: constant("quadratic"),
45451
- coords: nullable(pairOfPoints)
45452
- })).withBranch("sinusoid", object({
45453
- type: constant("sinusoid"),
45454
- coords: nullable(pairOfPoints)
45455
- })).withBranch("tangent", object({
45456
- type: constant("tangent"),
45457
- coords: nullable(pairOfPoints)
45458
- })).parser,
45459
- graph: object({
45460
- backgroundImage: object({
45461
- bottom: optional(number),
45462
- height: optional(number),
45463
- left: optional(number),
45464
- scale: optional(number),
45465
- url: optional(nullable(string)),
45466
- width: optional(number)
45467
- }),
45468
- box: optional(pairOfNumbers$3),
45469
- editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
45470
- gridStep: optional(pairOfNumbers$3),
45471
- labels: pair(string, string),
45472
- markings: enumeration("graph", "none", "grid"),
45473
- range: pair(pairOfNumbers$3, pairOfNumbers$3),
45474
- rulerLabel: constant(""),
45475
- rulerTicks: number,
45476
- showProtractor: optional(boolean),
45477
- showRuler: optional(boolean),
45478
- showTooltips: optional(boolean),
45479
- snapStep: optional(pairOfNumbers$3),
45480
- step: pairOfNumbers$3,
45481
- valid: optional(union(boolean).or(string).parser)
45482
- })
45483
- }));
45484
-
45485
- const parseGroupWidget = parseWidget(constant("group"),
45486
- // This module has an import cycle with parsePerseusRenderer.
45487
- // The anonymous function below ensures that we don't try to access
45488
- // parsePerseusRenderer before it's defined.
45489
- (rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
45490
-
45491
- const parseIframeWidget = parseWidget(constant("iframe"), object({
45492
- url: string,
45493
- settings: optional(array(object({
45494
- name: string,
45495
- value: string
45496
- }))),
45497
- width: union(number).or(string).parser,
45498
- height: union(number).or(string).parser,
45499
- allowFullScreen: defaulted(boolean, () => false),
45500
- allowTopNavigation: optional(boolean),
45501
- static: defaulted(boolean, () => false)
45502
- }));
45503
-
45504
- const stringToNumber = (rawValue, ctx) => {
45505
- if (typeof rawValue === "number") {
45506
- return ctx.success(rawValue);
45507
- }
45508
- const parsedNumber = +rawValue;
45509
- if (rawValue === "" || isNaN(parsedNumber)) {
45510
- return ctx.failure("a number or numeric string", rawValue);
45511
- }
45512
- return ctx.success(parsedNumber);
45513
- };
45514
-
45515
- function emptyToZero(x) {
45516
- return x === "" ? 0 : x;
45517
- }
45518
- const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
45519
- // In this specific case, empty string is equivalent to zero. An empty
45520
- // string parses to either NaN (using parseInt) or 0 (using unary +) and
45521
- // CSS will treat NaN as invalid and default to 0 instead.
45522
- .then(convert(emptyToZero)).then(stringToNumber).parser;
45523
- const parsePerseusImageBackground = object({
45524
- url: optional(nullable(string)),
45525
- width: optional(imageDimensionToNumber),
45526
- height: optional(imageDimensionToNumber),
45527
- top: optional(imageDimensionToNumber),
45528
- left: optional(imageDimensionToNumber),
45529
- bottom: optional(imageDimensionToNumber),
45530
- scale: optional(imageDimensionToNumber)
45531
- });
45532
-
45533
- const pairOfNumbers$2 = pair(number, number);
45534
- const parseImageWidget = parseWidget(constant("image"), object({
45535
- title: optional(string),
45536
- caption: optional(string),
45537
- alt: optional(string),
45538
- backgroundImage: parsePerseusImageBackground,
45539
- static: optional(boolean),
45540
- labels: optional(array(object({
45541
- content: string,
45542
- alignment: string,
45543
- coordinates: array(number)
45544
- }))),
45545
- range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
45546
- box: optional(pairOfNumbers$2)
45547
- }));
45548
-
45549
- const booleanToString = (rawValue, ctx) => {
45550
- if (typeof rawValue === "boolean") {
45551
- return ctx.success(String(rawValue));
45552
- }
45553
- return ctx.failure("boolean", rawValue);
45554
- };
45555
- const parseInputNumberWidget = parseWidget(constant("input-number"), object({
45556
- answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
45557
- inexact: optional(boolean),
45558
- maxError: optional(union(number).or(string).parser),
45559
- rightAlign: optional(boolean),
45560
- simplify: enumeration("required", "optional", "enforced"),
45561
- size: enumeration("normal", "small"),
45562
- // TODO(benchristel): there are some content items where value is a
45563
- // boolean, even though that makes no sense. We should figure out if
45564
- // those content items are actually published anywhere, and consider
45565
- // updating them.
45566
- value: union(number).or(string).or(booleanToString).parser,
45567
- customKeypad: optional(boolean)
45568
- }));
45569
-
45570
- const pairOfNumbers$1 = pair(number, number);
45571
- const stringOrEmpty = defaulted(string, () => "");
45572
- const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
45573
- const parseFunctionElement = object({
45574
- type: constant("function"),
45575
- key: parseKey,
45576
- options: object({
45577
- value: string,
45578
- funcName: string,
45579
- rangeMin: string,
45580
- rangeMax: string,
45581
- color: string,
45582
- strokeDasharray: string,
45583
- strokeWidth: number
45584
- })
45585
- });
45586
- const parseLabelElement = object({
45587
- type: constant("label"),
45588
- key: parseKey,
45589
- options: object({
45590
- label: string,
45591
- color: string,
45592
- coordX: string,
45593
- coordY: string
45594
- })
45595
- });
45596
- const parseLineElement = object({
45597
- type: constant("line"),
45598
- key: parseKey,
45599
- options: object({
45600
- color: string,
45601
- startX: string,
45602
- startY: string,
45603
- endX: string,
45604
- endY: string,
45605
- strokeDasharray: string,
45606
- strokeWidth: number,
45607
- arrows: string
45608
- })
45609
- });
45610
- const parseMovableLineElement = object({
45611
- type: constant("movable-line"),
45612
- key: parseKey,
45613
- options: object({
45614
- startX: string,
45615
- startY: string,
45616
- startSubscript: number,
45617
- endX: string,
45618
- endY: string,
45619
- endSubscript: number,
45620
- constraint: string,
45621
- snap: number,
45622
- constraintFn: string,
45623
- constraintXMin: string,
45624
- constraintXMax: string,
45625
- constraintYMin: string,
45626
- constraintYMax: string
45627
- })
45628
- });
45629
- const parseMovablePointElement = object({
45630
- type: constant("movable-point"),
45631
- key: parseKey,
45632
- options: object({
45633
- startX: string,
45634
- startY: string,
45635
- varSubscript: number,
45636
- constraint: string,
45637
- snap: number,
45638
- constraintFn: string,
45639
- constraintXMin: stringOrEmpty,
45640
- constraintXMax: stringOrEmpty,
45641
- constraintYMin: stringOrEmpty,
45642
- constraintYMax: stringOrEmpty
45643
- })
45644
- });
45645
- const parseParametricElement = object({
45646
- type: constant("parametric"),
45647
- key: parseKey,
45648
- options: object({
45649
- x: string,
45650
- y: string,
45651
- rangeMin: string,
45652
- rangeMax: string,
45653
- color: string,
45654
- strokeDasharray: string,
45655
- strokeWidth: number
45656
- })
45657
- });
45658
- const parsePointElement = object({
45659
- type: constant("point"),
45660
- key: parseKey,
45661
- options: object({
45662
- color: string,
45663
- coordX: string,
45664
- coordY: string
45665
- })
45666
- });
45667
- const parseRectangleElement = object({
45668
- type: constant("rectangle"),
45669
- key: parseKey,
45670
- options: object({
45671
- color: string,
45672
- coordX: string,
45673
- coordY: string,
45674
- width: string,
45675
- height: string
45676
- })
45677
- });
45678
- const parseInteractionWidget = parseWidget(constant("interaction"), object({
45679
- static: defaulted(boolean, () => false),
45680
- graph: object({
45681
- editableSettings: optional(array(enumeration("canvas", "graph"))),
45682
- box: pairOfNumbers$1,
45683
- labels: array(string),
45684
- range: pair(pairOfNumbers$1, pairOfNumbers$1),
45685
- gridStep: pairOfNumbers$1,
45686
- markings: enumeration("graph", "grid", "none"),
45687
- snapStep: optional(pairOfNumbers$1),
45688
- valid: optional(union(boolean).or(string).parser),
45689
- backgroundImage: optional(parsePerseusImageBackground),
45690
- showProtractor: optional(boolean),
45691
- showRuler: optional(boolean),
45692
- rulerLabel: optional(string),
45693
- rulerTicks: optional(number),
45694
- tickStep: pairOfNumbers$1
45695
- }),
45696
- elements: array(discriminatedUnionOn("type").withBranch("function", parseFunctionElement).withBranch("label", parseLabelElement).withBranch("line", parseLineElement).withBranch("movable-line", parseMovableLineElement).withBranch("movable-point", parseMovablePointElement).withBranch("parametric", parseParametricElement).withBranch("point", parsePointElement).withBranch("rectangle", parseRectangleElement).parser)
45697
- }));
45698
-
45699
- // Used to represent 2-D points and ranges
45700
- const pairOfNumbers = pair(number, number);
45701
- const parsePerseusGraphTypeAngle = object({
45702
- type: constant("angle"),
45703
- showAngles: optional(boolean),
45704
- allowReflexAngles: optional(boolean),
45705
- angleOffsetDeg: optional(number),
45706
- snapDegrees: optional(number),
45707
- match: optional(constant("congruent")),
45708
- coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
45709
- startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
45710
- });
45711
- const parsePerseusGraphTypeCircle = object({
45712
- type: constant("circle"),
45713
- center: optional(pairOfNumbers),
45714
- radius: optional(number),
45715
- startCoords: optional(object({
45716
- center: pairOfNumbers,
45717
- radius: number
45718
- })),
45719
- // TODO: remove coord? it's legacy.
45720
- coord: optional(pairOfNumbers)
45721
- });
45722
- const parsePerseusGraphTypeLinear = object({
45723
- type: constant("linear"),
45724
- coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
45725
- startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
45726
- // TODO: remove coord? it's legacy.
45727
- coord: optional(pairOfNumbers)
45728
- });
45729
- const parsePerseusGraphTypeLinearSystem = object({
45730
- type: constant("linear-system"),
45731
- // TODO(benchristel): default coords to empty array?
45732
- coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
45733
- startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
45734
- // TODO: remove coord? it's legacy.
45735
- coord: optional(pairOfNumbers)
45736
- });
45737
- const parsePerseusGraphTypeNone = object({
45738
- type: constant("none")
45739
- });
45740
- const parsePerseusGraphTypePoint = object({
45741
- type: constant("point"),
45742
- numPoints: optional(union(number).or(constant("unlimited")).parser),
45743
- coords: optional(nullable(array(pairOfNumbers))),
45744
- startCoords: optional(array(pairOfNumbers)),
45745
- // TODO: remove coord? it's legacy.
45746
- coord: optional(pairOfNumbers)
45747
- });
45748
- const parsePerseusGraphTypePolygon = object({
45749
- type: constant("polygon"),
45750
- numSides: optional(union(number).or(constant("unlimited")).parser),
45751
- showAngles: optional(boolean),
45752
- showSides: optional(boolean),
45753
- snapTo: optional(enumeration("grid", "angles", "sides")),
45754
- match: optional(enumeration("similar", "congruent", "approx", "exact")),
45755
- startCoords: optional(array(pairOfNumbers)),
45756
- // TODO: remove coord? it's legacy.
45757
- coord: optional(pairOfNumbers)
45758
- });
45759
- const parsePerseusGraphTypeQuadratic = object({
45760
- type: constant("quadratic"),
45761
- coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
45762
- startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
45763
- // TODO: remove coord? it's legacy.
45764
- coord: optional(pairOfNumbers)
45765
- });
45766
- const parsePerseusGraphTypeRay = object({
45767
- type: constant("ray"),
45768
- coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
45769
- startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
45770
- // TODO: remove coord? it's legacy.
45771
- coord: optional(pairOfNumbers)
45772
- });
45773
- const parsePerseusGraphTypeSegment = object({
45774
- type: constant("segment"),
45775
- // TODO(benchristel): default numSegments?
45776
- numSegments: optional(number),
45777
- coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
45778
- startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
45779
- // TODO: remove coord? it's legacy.
45780
- coord: optional(pairOfNumbers)
45781
- });
45782
- const parsePerseusGraphTypeSinusoid = object({
45783
- type: constant("sinusoid"),
45784
- coords: optional(nullable(array(pairOfNumbers))),
45785
- startCoords: optional(array(pairOfNumbers)),
45786
- // TODO: remove coord? it's legacy.
45787
- coord: optional(pairOfNumbers)
45788
- });
45789
- 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;
45790
- const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
45791
- const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
45792
- const parseLockedLineStyle = enumeration("solid", "dashed");
45793
- const parseLockedLabelType = object({
45794
- type: constant("label"),
45795
- coord: pairOfNumbers,
45796
- text: string,
45797
- color: parseLockedFigureColor,
45798
- size: enumeration("small", "medium", "large")
45799
- });
45800
- const parseLockedPointType = object({
45801
- type: constant("point"),
45802
- coord: pairOfNumbers,
45803
- color: parseLockedFigureColor,
45804
- filled: boolean,
45805
- // TODO(benchristel): default labels to empty array?
45806
- labels: optional(array(parseLockedLabelType)),
45807
- ariaLabel: optional(string)
45808
- });
45809
- const parseLockedLineType = object({
45810
- type: constant("line"),
45811
- kind: enumeration("line", "ray", "segment"),
45812
- points: pair(parseLockedPointType, parseLockedPointType),
45813
- color: parseLockedFigureColor,
45814
- lineStyle: parseLockedLineStyle,
45815
- showPoint1: defaulted(boolean, () => false),
45816
- showPoint2: defaulted(boolean, () => false),
45817
- // TODO(benchristel): default labels to empty array?
45818
- labels: optional(array(parseLockedLabelType)),
45819
- ariaLabel: optional(string)
45820
- });
45821
- const parseLockedVectorType = object({
45822
- type: constant("vector"),
45823
- points: pair(pairOfNumbers, pairOfNumbers),
45824
- color: parseLockedFigureColor,
45825
- // TODO(benchristel): default labels to empty array?
45826
- labels: optional(array(parseLockedLabelType)),
45827
- ariaLabel: optional(string)
45828
- });
45829
- const parseLockedEllipseType = object({
45830
- type: constant("ellipse"),
45831
- center: pairOfNumbers,
45832
- radius: pairOfNumbers,
45833
- angle: number,
45834
- color: parseLockedFigureColor,
45835
- fillStyle: parseLockedFigureFillType,
45836
- strokeStyle: parseLockedLineStyle,
45837
- // TODO(benchristel): default labels to empty array?
45838
- labels: optional(array(parseLockedLabelType)),
45839
- ariaLabel: optional(string)
45840
- });
45841
- const parseLockedPolygonType = object({
45842
- type: constant("polygon"),
45843
- points: array(pairOfNumbers),
45844
- color: parseLockedFigureColor,
45845
- showVertices: boolean,
45846
- fillStyle: parseLockedFigureFillType,
45847
- strokeStyle: parseLockedLineStyle,
45848
- // TODO(benchristel): default labels to empty array?
45849
- labels: optional(array(parseLockedLabelType)),
45850
- ariaLabel: optional(string)
45851
- });
45852
- const parseLockedFunctionType = object({
45853
- type: constant("function"),
45854
- color: parseLockedFigureColor,
45855
- strokeStyle: parseLockedLineStyle,
45856
- equation: string,
45857
- directionalAxis: enumeration("x", "y"),
45858
- domain: optional(pairOfNumbers),
45859
- // TODO(benchristel): default labels to empty array?
45860
- labels: optional(array(parseLockedLabelType)),
45861
- ariaLabel: optional(string)
45862
- });
45863
- 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;
45864
- const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
45865
- step: pairOfNumbers,
45866
- // TODO(benchristel): rather than making gridStep and snapStep
45867
- // optional, we should duplicate the defaulting logic from the
45868
- // InteractiveGraph component. See parse-perseus-json/README.md for
45869
- // why.
45870
- gridStep: optional(pairOfNumbers),
45871
- snapStep: optional(pairOfNumbers),
45872
- backgroundImage: optional(parsePerseusImageBackground),
45873
- markings: enumeration("graph", "grid", "none"),
45874
- labels: optional(array(string)),
45875
- showProtractor: boolean,
45876
- showRuler: optional(boolean),
45877
- showTooltips: optional(boolean),
45878
- rulerLabel: optional(string),
45879
- rulerTicks: optional(number),
45880
- range: pair(pairOfNumbers, pairOfNumbers),
45881
- // NOTE(benchristel): I copied the default graph from
45882
- // interactive-graph.tsx. See the parse-perseus-json/README.md for
45883
- // an explanation of why we want to duplicate the default here.
45884
- graph: defaulted(parsePerseusGraphType, () => ({
45885
- type: "linear"
45886
- })),
45887
- correct: parsePerseusGraphType,
45888
- // TODO(benchristel): default lockedFigures to empty array
45889
- lockedFigures: optional(array(parseLockedFigure)),
45890
- fullGraphLabel: optional(string),
45891
- fullGraphAriaDescription: optional(string)
45892
- }));
45893
-
45894
- const parseLabelImageWidget = parseWidget(constant("label-image"), object({
45895
- choices: array(string),
45896
- imageUrl: string,
45897
- imageAlt: string,
45898
- imageHeight: number,
45899
- imageWidth: number,
45900
- markers: array(object({
45901
- answers: array(string),
45902
- label: string,
45903
- x: number,
45904
- y: number
45905
- })),
45906
- hideChoicesFromInstructions: boolean,
45907
- multipleAnswers: boolean,
45908
- static: defaulted(boolean, () => false)
45909
- }));
45910
-
45911
- const parseMatcherWidget = parseWidget(constant("matcher"), object({
45912
- labels: array(string),
45913
- left: array(string),
45914
- right: array(string),
45915
- orderMatters: boolean,
45916
- padding: boolean
45917
- }));
45918
-
45919
- const numberOrString = union(number).or(string).parser;
45920
- const numeric = pipeParsers(defaulted(numberOrString, () => NaN)).then(stringToNumber).parser;
45921
- const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
45922
- prefix: optional(string),
45923
- suffix: optional(string),
45924
- answers: array(array(numeric)),
45925
- cursorPosition: optional(array(number)),
45926
- matrixBoardSize: array(number),
45927
- static: optional(boolean)
45928
- }));
45929
-
45930
- const parseMeasurerWidget = parseWidget(constant("measurer"), object({
45931
- // The default value for image comes from measurer.tsx.
45932
- // See parse-perseus-json/README.md for why we want to duplicate the
45933
- // defaults here.
45934
- image: defaulted(parsePerseusImageBackground, () => ({
45935
- url: null,
45936
- top: 0,
45937
- left: 0
45938
- })),
45939
- showProtractor: boolean,
45940
- showRuler: boolean,
45941
- rulerLabel: string,
45942
- rulerTicks: number,
45943
- rulerPixels: number,
45944
- rulerLength: number,
45945
- box: pair(number, number),
45946
- // TODO(benchristel): static is not used. Remove it?
45947
- static: defaulted(boolean, () => false)
45948
- }));
45949
-
45950
- const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
45951
- widgetId: string,
45952
- rotationAngle: optional(number),
45953
- smiles: optional(string)
45954
- }));
45955
-
45956
- const emptyStringToNull = pipeParsers(constant("")).then(convert(() => null)).parser;
45957
- const parseNumberLineWidget = parseWidget(constant("number-line"), object({
45958
- range: array(number),
45959
- labelRange: array(nullable(union(number).or(emptyStringToNull).parser)),
45960
- labelStyle: string,
45961
- labelTicks: boolean,
45962
- isTickCtrl: optional(nullable(boolean)),
45963
- divisionRange: array(number),
45964
- numDivisions: optional(nullable(number)),
45965
- // NOTE(benchristel): I copied the default snapDivisions from
45966
- // number-line.tsx. See the parse-perseus-json/README.md for
45967
- // an explanation of why we want to duplicate the default here.
45968
- snapDivisions: defaulted(number, () => 2),
45969
- tickStep: optional(nullable(number)),
45970
- correctRel: optional(nullable(string)),
45971
- correctX: nullable(number),
45972
- initialX: optional(nullable(number)),
45973
- showTooltip: optional(boolean),
45974
- static: defaulted(boolean, () => false)
45975
- }));
45976
-
45977
- const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
45978
- const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
45979
- answers: array(object({
45980
- message: string,
45981
- // TODO(benchristel): value should never be null or undefined,
45982
- // but we have some content where it is anyway. If we backfill
45983
- // the data, simplify this.
45984
- value: optional(nullable(number)),
45985
- status: string,
45986
- answerForms: optional(array(parseMathFormat)),
45987
- strict: boolean,
45988
- maxError: optional(nullable(number)),
45989
- // TODO(benchristel): simplify should never be a boolean, but we
45990
- // have some content where it is anyway. If we ever backfill
45991
- // the data, we should simplify `simplify`.
45992
- simplify: optional(nullable(union(string).or(pipeParsers(boolean).then(convert(String)).parser).parser))
45993
- })),
45994
- labelText: optional(string),
45995
- size: string,
45996
- coefficient: defaulted(boolean, () => false),
45997
- rightAlign: optional(boolean),
45998
- static: defaulted(boolean, () => false),
45999
- answerForms: optional(array(object({
46000
- name: parseMathFormat,
46001
- simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
46002
- })))
46003
- }));
46004
-
46005
- // There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
46006
- // This wrapper ensures that we don't refer to parsePerseusRenderer before
46007
- // it's defined.
46008
- function parseRenderer(rawValue, ctx) {
46009
- return parsePerseusRenderer(rawValue, ctx);
46010
- }
46011
- const largeToAuto = (height, ctx) => {
46012
- if (height === "large") {
46013
- return ctx.success("auto");
46014
- }
46015
- return ctx.success(height);
46016
- };
46017
- const parseOrdererWidget = parseWidget(constant("orderer"), object({
46018
- options: defaulted(array(parseRenderer), () => []),
46019
- correctOptions: array(parseRenderer),
46020
- otherOptions: array(parseRenderer),
46021
- height: pipeParsers(enumeration("normal", "auto", "large")).then(largeToAuto).parser,
46022
- layout: defaulted(enumeration("horizontal", "vertical"), () => "horizontal")
46023
- }));
46024
-
46025
- const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
46026
- passageNumber: number,
46027
- referenceNumber: number,
46028
- summaryText: optional(string)
46029
- }));
46030
-
46031
- const parsePassageWidget = parseWidget(constant("passage"), object({
46032
- footnotes: defaulted(string, () => ""),
46033
- passageText: string,
46034
- passageTitle: defaulted(string, () => ""),
46035
- showLineNumbers: boolean,
46036
- static: defaulted(boolean, () => false)
46037
- }));
46038
-
46039
- const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
46040
- url: string,
46041
- description: string
46042
- }));
46043
-
46044
- const parsePlotterWidget = parseWidget(constant("plotter"), object({
46045
- labels: array(string),
46046
- categories: array(string),
46047
- type: enumeration(...plotterPlotTypes),
46048
- maxY: number,
46049
- // The default value for scaleY comes from plotter.tsx.
46050
- // See parse-perseus-json/README.md for why we want to duplicate the
46051
- // defaults here.
46052
- scaleY: defaulted(number, () => 1),
46053
- labelInterval: optional(nullable(number)),
46054
- // The default value for snapsPerLine comes from plotter.tsx.
46055
- // See parse-perseus-json/README.md for why we want to duplicate the
46056
- // defaults here.
46057
- snapsPerLine: defaulted(number, () => 2),
46058
- starting: array(number),
46059
- correct: array(number),
46060
- picUrl: optional(nullable(string)),
46061
- picSize: optional(nullable(number)),
46062
- picBoxHeight: optional(nullable(number)),
46063
- // NOTE(benchristel): I copied the default plotDimensions from
46064
- // plotter.tsx. See the parse-perseus-json/README.md for an explanation
46065
- // of why we want to duplicate the defaults here.
46066
- plotDimensions: defaulted(array(number), () => [380, 300])
46067
- }));
46068
-
46069
- const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
46070
- programID: string,
46071
- height: number
46072
- }));
46073
-
46074
- const parseRadioWidget = parseWidget(constant("radio"), object({
46075
- choices: array(object({
46076
- content: defaulted(string, () => ""),
46077
- clue: optional(string),
46078
- correct: optional(boolean),
46079
- isNoneOfTheAbove: optional(boolean),
46080
- // deprecated
46081
- // There is an import cycle between radio-widget.ts and
46082
- // widgets-map.ts. The anonymous function below ensures that we
46083
- // don't refer to parseWidgetsMap before it's defined.
46084
- widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
46085
- })),
46086
- hasNoneOfTheAbove: optional(boolean),
46087
- countChoices: optional(boolean),
46088
- randomize: optional(boolean),
46089
- multipleSelect: optional(boolean),
46090
- deselectEnabled: optional(boolean),
46091
- // deprecated
46092
- onePerLine: optional(boolean),
46093
- // deprecated
46094
- displayCount: optional(any),
46095
- // v0 props
46096
- // `noneOfTheAbove` is still in use (but only set to `false`).
46097
- noneOfTheAbove: optional(constant(false))
46098
- }));
46099
-
46100
- const parseSorterWidget = parseWidget(constant("sorter"), object({
46101
- correct: array(string),
46102
- padding: boolean,
46103
- layout: enumeration("horizontal", "vertical")
46104
- }));
46105
-
46106
- const parseTableWidget = parseWidget(constant("table"), object({
46107
- headers: array(string),
46108
- rows: number,
46109
- columns: number,
46110
- answers: array(array(string))
46111
- }));
46112
-
46113
- const parseVideoWidget = parseWidget(constant("video"), object({
46114
- location: string,
46115
- static: optional(boolean)
46116
- }));
46117
-
46118
- const parseWidgetsMap = (rawValue, ctx) => {
46119
- if (!isObject(rawValue)) {
46120
- return ctx.failure("PerseusWidgetsMap", rawValue);
46121
- }
46122
- const widgetsMap = {};
46123
- for (const key of Object.keys(rawValue)) {
46124
- // parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
46125
- // but it's the only way I could find to make TypeScript check the key
46126
- // against the widget type.
46127
- const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
46128
- if (isFailure(entryResult)) {
46129
- return entryResult;
46130
- }
46131
- }
46132
- return ctx.success(widgetsMap);
46133
- };
46134
- const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
46135
- const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
46136
- if (isFailure(idComponentsResult)) {
46137
- return idComponentsResult;
46138
- }
46139
- const [type, n] = idComponentsResult.value;
46140
- function parseAndAssign(key, parse) {
46141
- const widgetResult = parse(widget, ctx);
46142
- if (isFailure(widgetResult)) {
46143
- return widgetResult;
46144
- }
46145
- widgetMap[key] = widgetResult.value;
46146
- return ctx.success(undefined);
46147
- }
46148
- switch (type) {
46149
- case "categorizer":
46150
- return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
46151
- case "cs-program":
46152
- return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
46153
- case "definition":
46154
- return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
46155
- case "dropdown":
46156
- return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
46157
- case "explanation":
46158
- return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
46159
- case "expression":
46160
- return parseAndAssign(`expression ${n}`, parseExpressionWidget);
46161
- case "grapher":
46162
- return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
46163
- case "group":
46164
- return parseAndAssign(`group ${n}`, parseGroupWidget);
46165
- case "graded-group":
46166
- return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
46167
- case "graded-group-set":
46168
- return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
46169
- case "iframe":
46170
- return parseAndAssign(`iframe ${n}`, parseIframeWidget);
46171
- case "image":
46172
- return parseAndAssign(`image ${n}`, parseImageWidget);
46173
- case "input-number":
46174
- return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
46175
- case "interaction":
46176
- return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
46177
- case "interactive-graph":
46178
- return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
46179
- case "label-image":
46180
- return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
46181
- case "matcher":
46182
- return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
46183
- case "matrix":
46184
- return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
46185
- case "measurer":
46186
- return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
46187
- case "molecule-renderer":
46188
- return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
46189
- case "number-line":
46190
- return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
46191
- case "numeric-input":
46192
- return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
46193
- case "orderer":
46194
- return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
46195
- case "passage":
46196
- return parseAndAssign(`passage ${n}`, parsePassageWidget);
46197
- case "passage-ref":
46198
- return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
46199
- case "passage-ref-target":
46200
- // NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
46201
- // used in test content. See:
46202
- // https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
46203
- return parseAndAssign(`passage-ref-target ${n}`, any);
46204
- case "phet-simulation":
46205
- return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
46206
- case "plotter":
46207
- return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
46208
- case "python-program":
46209
- return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
46210
- case "radio":
46211
- return parseAndAssign(`radio ${n}`, parseRadioWidget);
46212
- case "sorter":
46213
- return parseAndAssign(`sorter ${n}`, parseSorterWidget);
46214
- case "table":
46215
- return parseAndAssign(`table ${n}`, parseTableWidget);
46216
- case "video":
46217
- return parseAndAssign(`video ${n}`, parseVideoWidget);
46218
- case "sequence":
46219
- // sequence is a deprecated widget type, and the corresponding
46220
- // widget component no longer exists.
46221
- return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
46222
- case "lights-puzzle":
46223
- return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
46224
- case "simulator":
46225
- return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
46226
- case "transformer":
46227
- return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
46228
- default:
46229
- if (getWidget(type)) {
46230
- // @ts-expect-error - 'type' is not a valid widget type
46231
- return parseAndAssign(`${type} ${n}`, any);
46232
- }
46233
- return ctx.failure("a valid widget type", type);
46234
- }
46235
- };
46236
- const parseDeprecatedWidget = parseWidget(
46237
- // Ignore the incoming widget type and hardcode "deprecated-standin"
46238
- (_, ctx) => ctx.success("deprecated-standin"),
46239
- // Allow any widget options
46240
- object({}));
46241
- const parseStringToPositiveInt = (rawValue, ctx) => {
46242
- if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
46243
- return ctx.failure("a string representing a positive integer", rawValue);
46244
- }
46245
- return ctx.success(+rawValue);
46246
- };
46247
- const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
46248
-
46249
- const parsePerseusRenderer = defaulted(object({
46250
- // TODO(benchristel): content is also defaulted to empty string in
46251
- // renderer.tsx. See if we can remove one default or the other.
46252
- content: defaulted(string, () => ""),
46253
- // This module has an import cycle with parseWidgetsMap, because the
46254
- // `group` widget can contain another renderer.
46255
- // The anonymous function below ensures that we don't try to access
46256
- // parseWidgetsMap before it's defined.
46257
- widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
46258
- metadata: optional(array(string)),
46259
- images: parseImages
46260
- }),
46261
- // Default value
46262
- () => ({
46263
- content: "",
46264
- widgets: {},
46265
- images: {}
46266
- }));
46267
-
46268
- const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
46269
-
46270
- const parseHint = object({
46271
- replace: optional(boolean),
46272
- content: string,
46273
- widgets: defaulted(parseWidgetsMap, () => ({})),
46274
- metadata: optional(array(string)),
46275
- images: parseImages
46276
- });
46277
-
46278
- const _excluded = ["type", "options"];
46279
- const parsePerseusItem$1 = object({
46280
- question: parsePerseusRenderer,
46281
- hints: defaulted(array(parseHint), () => []),
46282
- answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
46283
- itemDataVersion: optional(object({
46284
- major: number,
46285
- minor: number
46286
- })),
46287
- // Deprecated field
46288
- answer: any
46289
- });
46290
-
46291
- // Some answerAreas have extra fields, like:
46292
- //
46293
- // "answerArea": {
46294
- // "type": "multiple",
46295
- // "options": {
46296
- // "content": "",
46297
- // "images": {},
46298
- // "widgets": {}
46299
- // }
46300
- // }
46301
- //
46302
- // The "type" and "options" fields don't seem to be used anywhere. This
46303
- // migration function removes them.
46304
- function migrateAnswerArea(rawValue, ctx) {
46305
- const rest = _objectWithoutPropertiesLoose(rawValue, _excluded);
46306
- return ctx.success(rest);
46307
- }
46308
-
46309
- /**
46310
- * Helper to parse PerseusItem JSON
46311
- * Why not just use JSON.parse? We want:
46312
- * - To make sure types are correct
46313
- * - To give us a central place to validate/transform output if needed
46314
- * @deprecated - use parseAndMigratePerseusItem instead
46315
- * @param {string} json - the stringified PerseusItem JSON
46316
- * @returns {PerseusItem} the parsed PerseusItem object
46317
- */
46318
- function parsePerseusItem(json) {
46319
- // Try to block a cheating vector which relies on monkey-patching
46320
- // JSON.parse
46321
- if (isRealJSONParse(JSON.parse)) {
46322
- return JSON.parse(json);
46323
- }
46324
- throw new Error("Something went wrong.");
46325
- }
46326
- /**
46327
- * Parses a PerseusItem from a JSON string, migrates old formats to the latest
46328
- * schema, and runtime-typechecks the result. Use this to parse assessmentItem
46329
- * data.
46330
- *
46331
- * @returns a {@link Result} of the parsed PerseusItem. If the result is a
46332
- * failure, it will contain an error message describing where in the tree
46333
- * parsing failed.
46334
- * @throws SyntaxError if the argument is not well-formed JSON.
46335
- */
46336
- function parseAndMigratePerseusItem(json) {
46337
- throwErrorIfCheatingDetected();
46338
- const object = JSON.parse(json);
46339
- const result = parse(object, parsePerseusItem$1);
46340
- if (isFailure(result)) {
46341
- return failure({
46342
- message: result.detail,
46343
- invalidObject: object
46344
- });
46345
- }
46346
- return result;
46347
- }
46348
-
46349
- /**
46350
- * Parses a PerseusArticle from a JSON string, migrates old formats to the
46351
- * latest schema, and runtime-typechecks the result.
46352
- *
46353
- * @returns a {@link Result} of the parsed PerseusArticle. If the result is a
46354
- * failure, it will contain an error message describing where in the tree
46355
- * parsing failed.
46356
- * @throws SyntaxError if the argument is not well-formed JSON.
46357
- */
46358
- function parseAndMigratePerseusArticle(json) {
46359
- throwErrorIfCheatingDetected();
46360
- const object = JSON.parse(json);
46361
- const result = parse(object, parsePerseusArticle);
46362
- if (isFailure(result)) {
46363
- return failure({
46364
- message: result.detail,
46365
- invalidObject: object
46366
- });
46367
- }
46368
- return result;
46369
- }
46370
-
46371
- /**
46372
- * Tries to block a cheating vector that relies on monkey-patching JSON.parse.
46373
- */
46374
- // TODO(LEMS-2331): delete this function once server-side scoring is done.
46375
- function throwErrorIfCheatingDetected() {
46376
- if (!isRealJSONParse(JSON.parse)) {
46377
- throw new Error("Something went wrong.");
46378
- }
46379
- }
46380
-
46381
45066
  const genericPerseusItemData = {
46382
45067
  question: {
46383
45068
  content: "",
@@ -46595,5 +45280,5 @@ const ScoringUtil = {
46595
45280
  * Types
46596
45281
  */
46597
45282
 
46598
- export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isFailure, isItemRenderableByVersion, isSuccess, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parseDataFromJSONP, parsePerseusItem, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
45283
+ export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseDataFromJSONP, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
46599
45284
  //# sourceMappingURL=index.js.map