@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/index.js CHANGED
@@ -283,14 +283,36 @@ const strings = {
283
283
  srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
284
284
  srAngleGraphAriaLabel: "An angle on a coordinate plane.",
285
285
  srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s",
286
+ srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
287
+ srMultipleSegmentGraphAriaLabel: "%(countOfSegments)s line segments on a coordinate plane.",
288
+ srMultipleSegmentIndividualLabel: "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s.",
289
+ srSingleSegmentLabel: "Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s.",
290
+ srSegmentLength: "Segment length %(length)s units.",
291
+ srSingleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.",
292
+ srMultipleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s on segment %(indexOfSegment)s at %(x)s comma %(y)s.",
293
+ srSegmentGrabHandle: "Segment from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.",
286
294
  srLinearSystemGraph: "Two lines on a coordinate plane.",
287
295
  srLinearSystemPoints: "Line %(lineNumber)s has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
288
296
  srLinearSystemPoint: "Point %(pointSequence)s on line %(lineNumber)s at %(x)s comma %(y)s.",
289
297
  srRayGraph: "A ray on a coordinate plane.",
290
298
  srRayPoints: "The endpoint is at %(point1X)s comma %(point1Y)s and the ray goes through point %(point2X)s comma %(point2Y)s.",
291
299
  srRayGrabHandle: "Ray with endpoint %(point1X)s comma %(point1Y)s going through point %(point2X)s comma %(point2Y)s.",
292
- srRayEndpoint: "Endpoint at %(point1X)s comma %(point1Y)s.",
293
- srRayTerminalPoint: "Through point at %(point2X)s comma %(point2Y)s."
300
+ srRayEndpoint: "Endpoint at %(x)s comma %(y)s.",
301
+ srRayTerminalPoint: "Through point at %(x)s comma %(y)s.",
302
+ srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
303
+ srQuadraticFaceUp: "The parabola opens upward.",
304
+ srQuadraticFaceDown: "The parabola opens downward.",
305
+ srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
306
+ srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
307
+ srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
308
+ srQuadraticGraphVertexQuadrant: "Vertex is in quadrant %(quadrant)s.",
309
+ srQuadraticTwoXIntercepts: "The X-intercepts are at %(intercept1)s comma 0 and %(intercept2)s comma 0.",
310
+ srQuadraticOneXIntercept: "The X-intercept is at %(intercept)s comma 0.",
311
+ srQuadraticYIntercept: "The Y-intercept is at 0 comma %(intercept)s.",
312
+ srQuadraticPointOrigin: "Point %(pointNumber)s on parabola at the origin.",
313
+ srQuadraticPointAxis: "Point %(pointNumber)s on parabola at %(x)s comma %(y)s.",
314
+ srQuadraticPointQuadrant: "Point %(pointNumber)s on parabola in quadrant %(quadrant)s at %(x)s comma %(y)s.",
315
+ srQuadraticInteractiveElements: "Parabola with points at %(point1X)s comma %(point1Y)s, %(point2X)s comma %(point2Y)s, and %(point3X)s comma %(point3Y)s."
294
316
  // The above strings are used for interactive graph SR descriptions.
295
317
  };
296
318
 
@@ -680,58 +702,181 @@ const mockStrings = {
680
702
  } = _ref40;
681
703
  return `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}.`;
682
704
  },
705
+ srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
706
+ srMultipleSegmentGraphAriaLabel: _ref41 => {
707
+ let {
708
+ countOfSegments
709
+ } = _ref41;
710
+ return `${countOfSegments} segments on a coordinate plane.`;
711
+ },
712
+ srMultipleSegmentIndividualLabel: _ref42 => {
713
+ let {
714
+ point1X,
715
+ point1Y,
716
+ point2X,
717
+ point2Y,
718
+ indexOfSegment
719
+ } = _ref42;
720
+ return `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`;
721
+ },
722
+ srSingleSegmentLabel: _ref43 => {
723
+ let {
724
+ point1X,
725
+ point1Y,
726
+ point2X,
727
+ point2Y
728
+ } = _ref43;
729
+ return `Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`;
730
+ },
731
+ srSegmentLength: _ref44 => {
732
+ let {
733
+ length
734
+ } = _ref44;
735
+ return `Segment length ${length} units.`;
736
+ },
737
+ srSingleSegmentGraphEndpointAriaLabel: _ref45 => {
738
+ let {
739
+ endpointNumber,
740
+ x,
741
+ y
742
+ } = _ref45;
743
+ return `Endpoint ${endpointNumber} at ${x} comma ${y}.`;
744
+ },
745
+ srMultipleSegmentGraphEndpointAriaLabel: _ref46 => {
746
+ let {
747
+ endpointNumber,
748
+ x,
749
+ y,
750
+ indexOfSegment
751
+ } = _ref46;
752
+ return `Endpoint ${endpointNumber} on segment ${indexOfSegment} at ${x} comma ${y}.`;
753
+ },
754
+ srSegmentGrabHandle: _ref47 => {
755
+ let {
756
+ point1X,
757
+ point1Y,
758
+ point2X,
759
+ point2Y
760
+ } = _ref47;
761
+ return `Segment from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`;
762
+ },
683
763
  srLinearSystemGraph: "Two lines on a coordinate plane.",
684
- srLinearSystemPoints: _ref41 => {
764
+ srLinearSystemPoints: _ref48 => {
685
765
  let {
686
766
  lineNumber,
687
767
  point1X,
688
768
  point1Y,
689
769
  point2X,
690
770
  point2Y
691
- } = _ref41;
771
+ } = _ref48;
692
772
  return `Line ${lineNumber} has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`;
693
773
  },
694
- srLinearSystemPoint: _ref42 => {
774
+ srLinearSystemPoint: _ref49 => {
695
775
  let {
696
776
  lineNumber,
697
777
  pointSequence,
698
778
  x,
699
779
  y
700
- } = _ref42;
780
+ } = _ref49;
701
781
  return `Point ${pointSequence} on line ${lineNumber} at ${x} comma ${y}.`;
702
782
  },
703
783
  srRayGraph: "A ray on a coordinate plane.",
704
- srRayPoints: _ref43 => {
784
+ srRayPoints: _ref50 => {
705
785
  let {
706
786
  point1X,
707
787
  point1Y,
708
788
  point2X,
709
789
  point2Y
710
- } = _ref43;
790
+ } = _ref50;
711
791
  return `The endpoint is at ${point1X} comma ${point1Y} and the ray goes through point ${point2X} comma ${point2Y}.`;
712
792
  },
713
- srRayGrabHandle: _ref44 => {
793
+ srRayGrabHandle: _ref51 => {
714
794
  let {
715
795
  point1X,
716
796
  point1Y,
717
797
  point2X,
718
798
  point2Y
719
- } = _ref44;
799
+ } = _ref51;
720
800
  return `Ray with endpoint ${point1X} comma ${point1Y} going through point ${point2X} comma ${point2Y}.`;
721
801
  },
722
- srRayEndpoint: _ref45 => {
802
+ srRayEndpoint: _ref52 => {
723
803
  let {
724
804
  x,
725
805
  y
726
- } = _ref45;
806
+ } = _ref52;
727
807
  return `Endpoint at ${x} comma ${y}.`;
728
808
  },
729
- srRayTerminalPoint: _ref46 => {
809
+ srRayTerminalPoint: _ref53 => {
730
810
  let {
731
811
  x,
732
812
  y
733
- } = _ref46;
813
+ } = _ref53;
734
814
  return `Through point at ${x} comma ${y}.`;
815
+ },
816
+ srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
817
+ srQuadraticFaceUp: "The parabola opens upward.",
818
+ srQuadraticFaceDown: "The parabola opens downward.",
819
+ srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
820
+ srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
821
+ srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
822
+ srQuadraticGraphVertexQuadrant: _ref54 => {
823
+ let {
824
+ quadrant
825
+ } = _ref54;
826
+ return `Vertex is in quadrant ${quadrant}.`;
827
+ },
828
+ srQuadraticTwoXIntercepts: _ref55 => {
829
+ let {
830
+ intercept1,
831
+ intercept2
832
+ } = _ref55;
833
+ return `The X-intercepts are at ${intercept1} comma 0 and ${intercept2} comma 0.`;
834
+ },
835
+ srQuadraticOneXIntercept: _ref56 => {
836
+ let {
837
+ intercept
838
+ } = _ref56;
839
+ return `The X-intercept is at ${intercept} comma 0.`;
840
+ },
841
+ srQuadraticYIntercept: _ref57 => {
842
+ let {
843
+ intercept
844
+ } = _ref57;
845
+ return `The Y-intercept is at 0 comma ${intercept}.`;
846
+ },
847
+ srQuadraticPointOrigin: _ref58 => {
848
+ let {
849
+ pointNumber
850
+ } = _ref58;
851
+ return `Point ${pointNumber} on parabola at the origin.`;
852
+ },
853
+ srQuadraticPointAxis: _ref59 => {
854
+ let {
855
+ pointNumber,
856
+ x,
857
+ y
858
+ } = _ref59;
859
+ return `Point ${pointNumber} on parabola at ${x} comma ${y}.`;
860
+ },
861
+ srQuadraticPointQuadrant: _ref60 => {
862
+ let {
863
+ pointNumber,
864
+ x,
865
+ y,
866
+ quadrant
867
+ } = _ref60;
868
+ return `Point ${pointNumber} on parabola in quadrant ${quadrant} at ${x} comma ${y}.`;
869
+ },
870
+ srQuadraticInteractiveElements: _ref61 => {
871
+ let {
872
+ point1X,
873
+ point1Y,
874
+ point2X,
875
+ point2Y,
876
+ point3X,
877
+ point3Y
878
+ } = _ref61;
879
+ return `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`;
735
880
  }
736
881
  // The above strings are used for interactive graph SR descriptions.
737
882
  };
@@ -2006,23 +2151,6 @@ const keypadConfigurationForProps = widgetOptions => {
2006
2151
  times: widgetOptions.times
2007
2152
  };
2008
2153
  };
2009
- const propUpgrades$2 = {
2010
- /* c8 ignore next */
2011
- "1": v0props => ({
2012
- times: v0props.times,
2013
- buttonSets: v0props.buttonSets,
2014
- functions: v0props.functions,
2015
- buttonsVisible: v0props.buttonsVisible,
2016
- visibleLabel: v0props.visibleLabel,
2017
- ariaLabel: v0props.ariaLabel,
2018
- answerForms: [{
2019
- considered: "correct",
2020
- form: v0props.form,
2021
- simplify: v0props.simplify,
2022
- value: v0props.value
2023
- }]
2024
- })
2025
- };
2026
2154
  const ExpressionWithDependencies = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
2027
2155
  const deps = useDependencies();
2028
2156
  return /*#__PURE__*/React__namespace.createElement(Expression, _extends({
@@ -2062,17 +2190,18 @@ var Expression$1 = {
2062
2190
  ariaLabel
2063
2191
  };
2064
2192
  },
2065
- version: {
2066
- major: 1,
2067
- minor: 0
2068
- },
2069
- propUpgrades: propUpgrades$2,
2193
+ version: perseusCore.expressionLogic.version,
2194
+ propUpgrades: perseusCore.expressionLogic.widgetOptionsUpgrades,
2070
2195
  // For use by the editor
2071
2196
  isLintable: true,
2072
2197
  // TODO(LEMS-2656): remove TS suppression
2073
2198
  // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
2074
2199
  scorer: perseusScore.scoreExpression,
2075
2200
  // TODO(LEMS-2656): remove TS suppression
2201
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
2202
+ validator: perseusScore.validateExpression,
2203
+ getPublicWidgetOptions: perseusCore.getExpressionPublicWidgetOptions,
2204
+ // TODO(LEMS-2656): remove TS suppression
2076
2205
  // @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
2077
2206
  getOneCorrectAnswerFromRubric(rubric) {
2078
2207
  const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
@@ -17580,7 +17709,7 @@ const inlineNodeTypes = {
17580
17709
 
17581
17710
  // @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'.
17582
17711
  const builtParser$1 = SimpleMarkdown__default["default"].parserFor(rules$1);
17583
- const parse$4 = (source, state) => {
17712
+ const parse$3 = (source, state) => {
17584
17713
  const paragraphedSource = source + "\n\n";
17585
17714
  return builtParser$1(paragraphedSource, {
17586
17715
  ...state,
@@ -17665,14 +17794,14 @@ const getContent = ast => {
17665
17794
  */
17666
17795
  const characterCount = source => {
17667
17796
  // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
17668
- const ast = parse$4(source);
17797
+ const ast = parse$3(source);
17669
17798
  const content = getContent(ast).join("");
17670
17799
  return content.length;
17671
17800
  };
17672
17801
  var PerseusMarkdown = {
17673
17802
  characterCount: characterCount,
17674
17803
  traverseContent: traverseContent,
17675
- parse: parse$4,
17804
+ parse: parse$3,
17676
17805
  parseInline: inlineParser,
17677
17806
  reactFor: SimpleMarkdown__default["default"].reactFor,
17678
17807
  ruleOutput: SimpleMarkdown__default["default"].ruleOutput(rules$1, "react"),
@@ -17813,136 +17942,6 @@ guess, state) {
17813
17942
  });
17814
17943
  }
17815
17944
 
17816
- // These are all the current widgets that require user-input and are supported for automatic scoring
17817
- const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
17818
-
17819
- /**
17820
- * Regex for widget placeholders in a string.
17821
- *
17822
- * First capture group is the widget ID (ex. 'radio 1')
17823
- * Second capture group is the widget type (ex. "radio)
17824
- * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
17825
- */
17826
- function getWidgetRegex() {
17827
- return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
17828
- }
17829
-
17830
- /**
17831
- * Add a widget placeholder using the widget ID.
17832
- * ex. addWidget("radio 1") => "[[☃ radio 1]]"
17833
- *
17834
- * @param {string} id
17835
- * @returns {string}
17836
- */
17837
- function addWidget(id) {
17838
- return `[[☃ ${id}]]`;
17839
- }
17840
-
17841
- /**
17842
- * Get a widget type by a widget's ID
17843
- * (please don't derive type from ID)
17844
- *
17845
- * @param {string} widgetId the ID of the widget
17846
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
17847
- * @returns {string} the widget type (ie "radio")
17848
- */
17849
- function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
17850
- const widget = widgetMap[WidgetId];
17851
- return widget?.type ?? null;
17852
- }
17853
-
17854
- /**
17855
- * Does the content have a specific type of widget?
17856
- *
17857
- * @param {string} type the type of the widget in question (ie "radio")
17858
- * @param {string} content the string to search through
17859
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
17860
- * @returns {boolean} if the content includes the widget type
17861
- */
17862
- function contentHasWidgetType(type, content, widgetMap) {
17863
- return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
17864
- }
17865
-
17866
- /**
17867
- * Extract all widget IDs, which includes the widget type and instance number.
17868
- * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
17869
- *
17870
- * Content should contain Perseus widget placeholders,
17871
- * which look like: '[[☃ radio 1]]'.
17872
- *
17873
- * @param {string} content
17874
- * @returns {ReadonlyArray<string>} widgetIds
17875
- */
17876
- function getWidgetIdsFromContent(content) {
17877
- const widgets = [];
17878
- const localWidgetRegex = getWidgetRegex();
17879
- let match = localWidgetRegex.exec(content);
17880
- while (match !== null) {
17881
- widgets.push(match[1]);
17882
- match = localWidgetRegex.exec(content);
17883
- }
17884
- return widgets;
17885
- }
17886
-
17887
- /**
17888
- * Get a list of widget IDs from content,
17889
- * but only for specific widget types
17890
- *
17891
- * @param {string} type the type of widget (ie "radio")
17892
- * @param {string} content the string to parse
17893
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
17894
- * @returns {ReadonlyArray<string>} the widget type (ie "radio")
17895
- */
17896
- function getWidgetIdsFromContentByType(type, content, widgetMap) {
17897
- const rv = [];
17898
- const widgetIdsInContent = getWidgetIdsFromContent(content);
17899
- widgetIdsInContent.forEach(widgetId => {
17900
- const widget = widgetMap[widgetId];
17901
- if (widget?.type === type) {
17902
- rv.push(widgetId);
17903
- }
17904
- });
17905
- return rv;
17906
- }
17907
-
17908
- /**
17909
- * Pull the widget map out of ItemData
17910
- *
17911
- * @param {PerseusItem} PerseusItem containing a widgetMap
17912
- * @returns {WidgetMap} the widget map in the PerseusItem
17913
- */
17914
- function getWidgetsMapFromItemData(itemData) {
17915
- return itemData.question.widgets;
17916
- }
17917
-
17918
- /**
17919
- * Get the widget information from a WidgetMap
17920
- *
17921
- * @param {string} widgetId the ID of the widget
17922
- * @returns {PerseusWidget | null} the widget data if it exists, otherwise null
17923
- */
17924
- function getWidgetFromWidgetMap(widgetId, widgetMap) {
17925
- return widgetMap[widgetId] ?? null;
17926
- }
17927
-
17928
- /**
17929
- * Get select widgets from a widget map.
17930
- *
17931
- * @param {ReadonlyArray<string>} widgetIds to extract
17932
- * @param {PerseusWidgetsMap} widgetMap to extract from
17933
- * @return {PerseusWidgetsMap} a new widget map with requested widgets
17934
- */
17935
- function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
17936
- const widgets = {};
17937
- widgetIds.forEach(widgetId => {
17938
- const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
17939
- if (widget) {
17940
- widgets[widgetId] = widget;
17941
- }
17942
- });
17943
- return widgets;
17944
- }
17945
-
17946
17945
  const DEFAULT_ALIGNMENT$1 = "block";
17947
17946
  // NOTE(kevinb): "default" is not one in `validAlignments`.
17948
17947
  const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
@@ -18044,6 +18043,9 @@ const getWidget = name => {
18044
18043
  const getWidgetExport = name => {
18045
18044
  return widgets[name] ?? null;
18046
18045
  };
18046
+ const getWidgetValidator = name => {
18047
+ return widgets[name]?.validator ?? null;
18048
+ };
18047
18049
  const getWidgetScorer = name => {
18048
18050
  return widgets[name]?.scorer ?? null;
18049
18051
  };
@@ -18356,6 +18358,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
18356
18358
  replaceDeprecatedEditors: replaceDeprecatedEditors,
18357
18359
  getWidget: getWidget,
18358
18360
  getWidgetExport: getWidgetExport,
18361
+ getWidgetValidator: getWidgetValidator,
18359
18362
  getWidgetScorer: getWidgetScorer,
18360
18363
  getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
18361
18364
  getEditor: getEditor,
@@ -18378,7 +18381,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
18378
18381
  });
18379
18382
 
18380
18383
  function getUpgradedWidgetOptions(oldWidgetOptions) {
18381
- // @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; }'.
18382
18384
  return perseusCore.mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
18383
18385
  if (!widgetInfo.type || !widgetInfo.alignment) {
18384
18386
  const newValues = {};
@@ -18396,22 +18398,30 @@ function getUpgradedWidgetOptions(oldWidgetOptions) {
18396
18398
  ...newValues
18397
18399
  };
18398
18400
  }
18401
+ // TODO(LEMS-2656): remove TS suppression
18399
18402
  return upgradeWidgetInfoToLatestVersion(widgetInfo);
18400
18403
  });
18401
18404
  }
18405
+
18406
+ /**
18407
+ * Checks the given user input to see if any answerable widgets have not been
18408
+ * "filled in" (ie. if they're empty). Another way to think about this
18409
+ * function is that its a check to see if we can score the provided input.
18410
+ */
18402
18411
  function emptyWidgetsFunctional(widgets,
18403
18412
  // This is a port of old code, I'm not sure why
18404
18413
  // we need widgetIds vs the keys of the widgets object
18405
18414
  widgetIds, userInputMap, strings, locale) {
18406
- const upgradedWidgets = getUpgradedWidgetOptions(widgets);
18407
18415
  return widgetIds.filter(id => {
18408
- const widget = upgradedWidgets[id];
18409
- if (!widget || widget.static) {
18416
+ const widget = widgets[id];
18417
+ if (!widget || widget.static === true) {
18410
18418
  // Static widgets shouldn't count as empty
18411
18419
  return false;
18412
18420
  }
18413
- const scorer = getWidgetScorer(widget.type);
18414
- const score = scorer?.(userInputMap[id], widget.options, strings, locale);
18421
+ const validator = getWidgetValidator(widget.type);
18422
+ const userInput = userInputMap[id];
18423
+ const validationData = widget.options;
18424
+ const score = validator?.(userInput, validationData, strings, locale);
18415
18425
  if (score) {
18416
18426
  return scoreIsEmpty(score);
18417
18427
  }
@@ -18427,7 +18437,7 @@ strings, locale) {
18427
18437
  // There seems to be a chance that PerseusRenderer.widgets might include
18428
18438
  // widget data for widgets that are not in PerseusRenderer.content,
18429
18439
  // so this checks that the widgets are being used before scoring them
18430
- const usedWidgetIds = getWidgetIdsFromContent(perseusRenderData.content);
18440
+ const usedWidgetIds = perseusCore.getWidgetIdsFromContent(perseusRenderData.content);
18431
18441
  const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
18432
18442
  return flattenScores(scores);
18433
18443
  }
@@ -18450,8 +18460,12 @@ widgetIds, userInputMap, strings, locale) {
18450
18460
  return;
18451
18461
  }
18452
18462
  const userInput = userInputMap[id];
18463
+ const validator = getWidgetValidator(widget.type);
18453
18464
  const scorer = getWidgetScorer(widget.type);
18454
- const score = scorer?.(userInput, widget.options, strings, locale);
18465
+
18466
+ // We do validation (empty checks) first and then scoring. If
18467
+ // validation fails, it's result is itself a PerseusScore.
18468
+ const score = validator?.(userInput, widget.options, strings, locale) ?? scorer?.(userInput, widget.options, strings, locale);
18455
18469
  if (score != null) {
18456
18470
  widgetScores[id] = score;
18457
18471
  }
@@ -19060,7 +19074,7 @@ class Renderer extends React__namespace.Component {
19060
19074
  const apiOptions = this.getApiOptions();
19061
19075
  const widgetProps = this.state.widgetProps[widgetId] || {};
19062
19076
 
19063
- // The widget needs access to its "rubric" at all times when in review
19077
+ // The widget needs access to its "scoring data" at all times when in review
19064
19078
  // mode (which is really just part of its widget info).
19065
19079
  const widgetInfo = this.state.widgetInfo[widgetId];
19066
19080
  const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
@@ -23020,7 +23034,7 @@ const CIRCLE_LABEL_STYLE = {
23020
23034
  // TODO(LEMS-2656): remove TS suppression
23021
23035
  // @ts-expect-error: State is not assignable to ParseState
23022
23036
  const builtParser = SimpleMarkdown__default["default"].parserFor(rules);
23023
- const parse$3 = (source, state) => {
23037
+ const parse$2 = (source, state) => {
23024
23038
  // @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
23025
23039
  state = state || {};
23026
23040
  const paragraphedSource = source + "\n\n";
@@ -23030,7 +23044,7 @@ const parse$3 = (source, state) => {
23030
23044
  // @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
23031
23045
  const output = SimpleMarkdown__default["default"].reactFor(SimpleMarkdown__default["default"].ruleOutput(rules, "react"));
23032
23046
  var PassageMarkdown = {
23033
- parse: parse$3,
23047
+ parse: parse$2,
23034
23048
  output,
23035
23049
  START_REF_PREFIX,
23036
23050
  END_REF_PREFIX,
@@ -23585,10 +23599,7 @@ var PassageRef$1 = {
23585
23599
  referenceNumber: widgetOptions.referenceNumber,
23586
23600
  summaryText: widgetOptions.summaryText
23587
23601
  }),
23588
- version: {
23589
- major: 0,
23590
- minor: 1
23591
- },
23602
+ version: perseusCore.passageRefLogic.version,
23592
23603
  // TODO: things that aren't interactive shouldn't need scoring functions
23593
23604
  scorer: () => scoreNoop()
23594
23605
  };
@@ -25274,7 +25285,7 @@ class Radio$1 extends React__namespace.Component {
25274
25285
  // Current versions of the radio widget always pass in the
25275
25286
  // "correct" value through the choices. Old serialized state
25276
25287
  // for radio widgets doesn't have this though, so we have to
25277
- // pull the correctness out of the review mode rubric. This
25288
+ // pull the correctness out of the review mode scoring data. This
25278
25289
  // only works because all of the places we use
25279
25290
  // `restoreSerializedState()` also turn on reviewMode, but is
25280
25291
  // fine for now.
@@ -25398,21 +25409,6 @@ const transform = (widgetOptions, strings, problemNum) => {
25398
25409
  selectedChoices: ___default["default"].pluck(choices, "correct")
25399
25410
  };
25400
25411
  };
25401
- const propUpgrades$1 = {
25402
- "1": v0props => {
25403
- const {
25404
- noneOfTheAbove,
25405
- ...rest
25406
- } = v0props;
25407
- if (noneOfTheAbove) {
25408
- throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
25409
- }
25410
- return {
25411
- ...rest,
25412
- hasNoneOfTheAbove: false
25413
- };
25414
- }
25415
- };
25416
25412
  var Radio = {
25417
25413
  name: "radio",
25418
25414
  displayName: "Radio / Multiple choice",
@@ -25420,15 +25416,15 @@ var Radio = {
25420
25416
  widget: Radio$1,
25421
25417
  transform: transform,
25422
25418
  staticTransform: transform,
25423
- version: {
25424
- major: 1,
25425
- minor: 0
25426
- },
25427
- propUpgrades: propUpgrades$1,
25419
+ version: perseusCore.radioLogic.version,
25420
+ propUpgrades: perseusCore.radioLogic.widgetOptionsUpgrades,
25428
25421
  isLintable: true,
25429
25422
  // TODO(LEMS-2656): remove TS suppression
25430
25423
  // @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
25431
- scorer: perseusScore.scoreRadio
25424
+ scorer: perseusScore.scoreRadio,
25425
+ // TODO(LEMS-2656): remove TS suppression
25426
+ // @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
25427
+ validator: perseusScore.validateRadio
25432
25428
  };
25433
25429
 
25434
25430
  /* eslint-disable import/no-named-as-default */
@@ -25447,24 +25443,6 @@ const getPromptJSON$n = (renderProps, userInput) => {
25447
25443
  };
25448
25444
  };
25449
25445
 
25450
- /**
25451
- * For details on the individual options, see the
25452
- * PerseusCategorizerWidgetOptions type
25453
- */
25454
-
25455
- /**
25456
- * Given a PerseusCategorizerWidgetOptions object, return a new object with only
25457
- * the public options that should be exposed to the client.
25458
- */
25459
- function getCategorizerPublicWidgetOptions(options) {
25460
- return {
25461
- items: options.items,
25462
- categories: options.categories,
25463
- randomizeItems: options.randomizeItems,
25464
- static: options.static
25465
- };
25466
- }
25467
-
25468
25446
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
25469
25447
  class Categorizer extends React__namespace.Component {
25470
25448
  static contextType = PerseusI18nContext;
@@ -25647,7 +25625,10 @@ var Categorizer$1 = {
25647
25625
  // TODO(LEMS-2656): remove TS suppression
25648
25626
  // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
25649
25627
  scorer: perseusScore.scoreCategorizer,
25650
- getPublicWidgetOptions: getCategorizerPublicWidgetOptions
25628
+ // TODO(LEMS-2656): remove TS suppression
25629
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
25630
+ validator: perseusScore.validateCategorizer,
25631
+ getPublicWidgetOptions: perseusCore.getCategorizerPublicWidgetOptions
25651
25632
  };
25652
25633
 
25653
25634
  /**
@@ -26086,7 +26067,10 @@ var Dropdown$1 = {
26086
26067
  transform: optionsTransform,
26087
26068
  // TODO(LEMS-2656): remove TS suppression
26088
26069
  // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
26089
- scorer: perseusScore.scoreDropdown
26070
+ scorer: perseusScore.scoreDropdown,
26071
+ // TODO(LEMS-2656): remove TS suppression
26072
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
26073
+ validator: perseusScore.validateDropdown
26090
26074
  };
26091
26075
 
26092
26076
  const getPromptJSON$j = renderProps => {
@@ -27604,11 +27588,22 @@ const getPromptJSON$f = rendererJSON => {
27604
27588
 
27605
27589
  // The `group` widget is basically a widget hosting a full Perseus system in
27606
27590
  // it. As such, scoring a group means scoring all widgets it contains.
27607
- function scoreGroup(userInput, options, strings, locale) {
27608
- const scores = scoreWidgetsFunctional(options.widgets, Object.keys(options.widgets), userInput, strings, locale);
27591
+ function scoreGroup(userInput, rubric, strings, locale) {
27592
+ const scores = scoreWidgetsFunctional(rubric.widgets, Object.keys(rubric.widgets), userInput, strings, locale);
27609
27593
  return flattenScores(scores);
27610
27594
  }
27611
27595
 
27596
+ function validateGroup(userInput, validationData, strings, locale) {
27597
+ const emptyWidgets = emptyWidgetsFunctional(validationData.widgets, Object.keys(validationData.widgets), userInput, strings, locale);
27598
+ if (emptyWidgets.length === 0) {
27599
+ return null;
27600
+ }
27601
+ return {
27602
+ type: "invalid",
27603
+ message: null
27604
+ };
27605
+ }
27606
+
27612
27607
  // exports has no 'transform'
27613
27608
 
27614
27609
  class Group extends React__namespace.Component {
@@ -27761,8 +27756,11 @@ var Group$1 = {
27761
27756
  widget: Group,
27762
27757
  traverseChildWidgets: traverseChildWidgets,
27763
27758
  // TODO(LEMS-2656): remove TS suppression
27764
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
27759
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
27765
27760
  scorer: scoreGroup,
27761
+ // TODO(LEMS-2656): remove TS suppression
27762
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
27763
+ validator: validateGroup,
27766
27764
  hidden: true,
27767
27765
  isLintable: true
27768
27766
  };
@@ -30682,6 +30680,96 @@ function getInterceptStringForLine(line, strings, locale) {
30682
30680
  yIntercept: srFormatNumber(yIntercept, locale)
30683
30681
  });
30684
30682
  }
30683
+ function getCoordQuadrant(coord) {
30684
+ const [unroundedX, unroundedY] = coord;
30685
+ const x = Number(unroundedX.toFixed(3));
30686
+ const y = Number(unroundedY.toFixed(3));
30687
+ if (x === 0 && y === 0) {
30688
+ return "origin";
30689
+ }
30690
+ if (y === 0) {
30691
+ return "x-axis";
30692
+ }
30693
+ if (x === 0) {
30694
+ return "y-axis";
30695
+ }
30696
+ if (x > 0 && y > 0) {
30697
+ return 1;
30698
+ }
30699
+ if (x < 0 && y > 0) {
30700
+ return 2;
30701
+ }
30702
+ if (x < 0 && y < 0) {
30703
+ return 3;
30704
+ }
30705
+ return 4;
30706
+ }
30707
+ function getQuadraticVertexString(vertex, strings) {
30708
+ const location = getCoordQuadrant(vertex);
30709
+ switch (location) {
30710
+ case "origin":
30711
+ return strings.srQuadraticGraphVertexOrigin;
30712
+ case "x-axis":
30713
+ return strings.srQuadraticGraphVertexXAxis;
30714
+ case "y-axis":
30715
+ return strings.srQuadraticGraphVertexYAxis;
30716
+ default:
30717
+ return strings.srQuadraticGraphVertexQuadrant({
30718
+ quadrant: location
30719
+ });
30720
+ }
30721
+ }
30722
+ function getQuadraticPointString(pointNumber, coord, strings, locale) {
30723
+ const location = getCoordQuadrant(coord);
30724
+ const [x, y] = coord;
30725
+ switch (location) {
30726
+ case "origin":
30727
+ return strings.srQuadraticPointOrigin({
30728
+ pointNumber: pointNumber
30729
+ });
30730
+ case "x-axis":
30731
+ case "y-axis":
30732
+ return strings.srQuadraticPointAxis({
30733
+ pointNumber: pointNumber,
30734
+ x: srFormatNumber(x, locale),
30735
+ y: srFormatNumber(y, locale)
30736
+ });
30737
+ default:
30738
+ // return `Point ${pointNumber} on parabola in quadrant ${location} at ${srFormatNumber(x, locale)} comma ${srFormatNumber(y, locale)}.`;
30739
+ return strings.srQuadraticPointQuadrant({
30740
+ pointNumber: pointNumber,
30741
+ quadrant: location,
30742
+ x: srFormatNumber(x, locale),
30743
+ y: srFormatNumber(y, locale)
30744
+ });
30745
+ }
30746
+ }
30747
+ function getQuadraticXIntercepts(
30748
+ // Coefficients of the quadratic equation
30749
+ a, b, c) {
30750
+ // If a is 0, the equation is a line and has maybe only one x-intercept.
30751
+ if (a === 0) {
30752
+ // If b is 0, the equation a horizontal line and has no x-intercepts.
30753
+ if (b === 0) {
30754
+ return [];
30755
+ }
30756
+ return [-c / b];
30757
+ }
30758
+
30759
+ // Find the x-intercepts by plugging in y = 0 into the quadratic equation.
30760
+ // 0 = ax^2 + bx + c
30761
+ // x = (-b ± sqrt(b^2 - 4ac)) / 2a
30762
+ const discriminant = b * b - 4 * a * c;
30763
+ if (discriminant < 0) {
30764
+ return [];
30765
+ }
30766
+ const x1 = (-b + Math.sqrt(discriminant)) / (2 * a);
30767
+ const x2 = (-b - Math.sqrt(discriminant)) / (2 * a);
30768
+ if (x1 === x2) {
30769
+ return [x1];
30770
+ }
30771
+ return [x1, x2];
30772
+ }
30685
30773
 
30686
30774
  const {
30687
30775
  calculateAngleInDegrees: calculateAngleInDegrees$2
@@ -32519,7 +32607,9 @@ function renderLinearGraph(state, dispatch) {
32519
32607
  graphState: state,
32520
32608
  dispatch: dispatch
32521
32609
  }),
32522
- interactiveElementsDescription: null
32610
+ interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(LinearGraphDescription, {
32611
+ state: state
32612
+ })
32523
32613
  };
32524
32614
  }
32525
32615
  const LinearGraph = (props, key) => {
@@ -32538,33 +32628,29 @@ const LinearGraph = (props, key) => {
32538
32628
  const interceptDescriptionId = id + "-intercept";
32539
32629
  const slopeDescriptionId = id + "-slope";
32540
32630
 
32541
- // Aria label strings
32542
- const linearGraphPointsDescription = strings.srLinearGraphPoints({
32543
- point1X: srFormatNumber(line[0][0], locale),
32544
- point1Y: srFormatNumber(line[0][1], locale),
32545
- point2X: srFormatNumber(line[1][0], locale),
32546
- point2Y: srFormatNumber(line[1][1], locale)
32547
- });
32548
- const grabHandleAriaLabel = strings.srLinearGrabHandle({
32549
- point1X: srFormatNumber(line[0][0], locale),
32550
- point1Y: srFormatNumber(line[0][1], locale),
32551
- point2X: srFormatNumber(line[1][0], locale),
32552
- point2Y: srFormatNumber(line[1][1], locale)
32631
+ // Aria strings
32632
+ const {
32633
+ srLinearGraph,
32634
+ srLinearGraphPoints,
32635
+ srLinearGrabHandle,
32636
+ slopeString,
32637
+ interceptString
32638
+ } = describeLinearGraph(props.graphState, {
32639
+ strings,
32640
+ locale
32553
32641
  });
32554
- const slopeString = getSlopeStringForLine(line, strings);
32555
- const interceptString = getInterceptStringForLine(line, strings, locale);
32556
32642
 
32557
32643
  // Linear graphs only have one line
32558
32644
  // (LEMS-2050): Update the reducer so that we have a separate action for moving one line
32559
32645
  // and another action for moving multiple lines
32560
32646
  return /*#__PURE__*/React__namespace.createElement("g", {
32561
32647
  // Outer line minimal description
32562
- "aria-label": strings.srLinearGraph,
32648
+ "aria-label": srLinearGraph,
32563
32649
  "aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`
32564
32650
  }, /*#__PURE__*/React__namespace.createElement(MovableLine, {
32565
32651
  key: 0,
32566
32652
  ariaLabels: {
32567
- grabHandleAriaLabel: grabHandleAriaLabel
32653
+ grabHandleAriaLabel: srLinearGrabHandle
32568
32654
  },
32569
32655
  ariaDescribedBy: `${interceptDescriptionId} ${slopeDescriptionId}`,
32570
32656
  points: line,
@@ -32580,7 +32666,7 @@ const LinearGraph = (props, key) => {
32580
32666
  }), /*#__PURE__*/React__namespace.createElement("g", {
32581
32667
  id: pointsDescriptionId,
32582
32668
  style: a11y.srOnly
32583
- }, linearGraphPointsDescription), /*#__PURE__*/React__namespace.createElement("g", {
32669
+ }, srLinearGraphPoints), /*#__PURE__*/React__namespace.createElement("g", {
32584
32670
  id: interceptDescriptionId,
32585
32671
  style: a11y.srOnly
32586
32672
  }, interceptString), /*#__PURE__*/React__namespace.createElement("g", {
@@ -32588,6 +32674,56 @@ const LinearGraph = (props, key) => {
32588
32674
  style: a11y.srOnly
32589
32675
  }, slopeString));
32590
32676
  };
32677
+ function LinearGraphDescription(_ref) {
32678
+ let {
32679
+ state
32680
+ } = _ref;
32681
+ // The reason that LinearGraphDescription is a component (rather than a
32682
+ // function that returns a string) is because it needs to use a
32683
+ // hook: `usePerseusI18n`.
32684
+ const i18n = usePerseusI18n();
32685
+ const strings = describeLinearGraph(state, i18n);
32686
+ return strings.srLinearInteractiveElement;
32687
+ }
32688
+
32689
+ // Exported for testing
32690
+ function describeLinearGraph(state, i18n) {
32691
+ const {
32692
+ coords: line
32693
+ } = state;
32694
+ const {
32695
+ strings,
32696
+ locale
32697
+ } = i18n;
32698
+
32699
+ // Aria label strings
32700
+ const srLinearGraph = strings.srLinearGraph;
32701
+ const srLinearGraphPoints = strings.srLinearGraphPoints({
32702
+ point1X: srFormatNumber(line[0][0], locale),
32703
+ point1Y: srFormatNumber(line[0][1], locale),
32704
+ point2X: srFormatNumber(line[1][0], locale),
32705
+ point2Y: srFormatNumber(line[1][1], locale)
32706
+ });
32707
+ const srLinearGrabHandle = strings.srLinearGrabHandle({
32708
+ point1X: srFormatNumber(line[0][0], locale),
32709
+ point1Y: srFormatNumber(line[0][1], locale),
32710
+ point2X: srFormatNumber(line[1][0], locale),
32711
+ point2Y: srFormatNumber(line[1][1], locale)
32712
+ });
32713
+ const slopeString = getSlopeStringForLine(line, strings);
32714
+ const interceptString = getInterceptStringForLine(line, strings, locale);
32715
+ const srLinearInteractiveElement = strings.srInteractiveElements({
32716
+ elements: [srLinearGraph, srLinearGraphPoints].join(" ")
32717
+ });
32718
+ return {
32719
+ srLinearGraph,
32720
+ srLinearGraphPoints,
32721
+ srLinearGrabHandle,
32722
+ slopeString,
32723
+ interceptString,
32724
+ srLinearInteractiveElement
32725
+ };
32726
+ }
32591
32727
 
32592
32728
  function renderLinearSystemGraph(state, dispatch) {
32593
32729
  return {
@@ -33146,13 +33282,13 @@ const hasFocusVisible = element => {
33146
33282
  }
33147
33283
  };
33148
33284
 
33149
- function renderQuadraticGraph(state, dispatch) {
33285
+ function renderQuadraticGraph(state, dispatch, i18n) {
33150
33286
  return {
33151
33287
  graph: /*#__PURE__*/React__namespace.createElement(QuadraticGraph, {
33152
33288
  graphState: state,
33153
33289
  dispatch: dispatch
33154
33290
  }),
33155
- interactiveElementsDescription: null
33291
+ interactiveElementsDescription: getQuadraticGraphDescription(state, i18n)
33156
33292
  };
33157
33293
  }
33158
33294
  function QuadraticGraph(props) {
@@ -33163,6 +33299,14 @@ function QuadraticGraph(props) {
33163
33299
  const {
33164
33300
  coords
33165
33301
  } = graphState;
33302
+ const {
33303
+ strings,
33304
+ locale
33305
+ } = usePerseusI18n();
33306
+ const id = React__namespace.useId();
33307
+ const quadraticDirectionId = id + "-direction";
33308
+ const quadraticVertexId = id + "-vertex";
33309
+ const quadraticInterceptsId = id + "-intercepts";
33166
33310
 
33167
33311
  // The coefficients are used to calculate the quadratic equation, plot the graph, and to indicate
33168
33312
  // to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
@@ -33178,15 +33322,46 @@ function QuadraticGraph(props) {
33178
33322
 
33179
33323
  // Calculate the y value based on the current x value
33180
33324
  const y = x => (a * x + b) * x + c;
33181
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
33325
+
33326
+ // Aria strings
33327
+ const {
33328
+ srQuadraticGraph,
33329
+ srQuadraticDirection,
33330
+ srQuadraticVertex,
33331
+ srQuadraticXIntercepts,
33332
+ srQuadraticYIntercept
33333
+ } = describeQuadraticGraph(graphState, {
33334
+ strings,
33335
+ locale
33336
+ });
33337
+ return /*#__PURE__*/React__namespace.createElement("g", {
33338
+ // Outer graph minimal description
33339
+ "aria-label": srQuadraticGraph,
33340
+ "aria-describedby": `${quadraticDirectionId} ${quadraticVertexId} ${quadraticInterceptsId}`
33341
+ }, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
33182
33342
  y: y,
33183
33343
  color: wonderBlocksTokens.color.blue
33184
- }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
33185
- key: "point-" + i,
33186
- point: coord,
33187
- sequenceNumber: i + 1,
33188
- onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
33189
- })));
33344
+ }), coords.map((coord, i) => {
33345
+ const srQuadraticPoint = getQuadraticPointString(i + 1, coord, strings, locale);
33346
+ // Vertex might not exist if the quadratic graph is a line
33347
+ const srVertex = srQuadraticVertex ? ` ${srQuadraticVertex}` : "";
33348
+ return /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
33349
+ key: "point-" + i,
33350
+ ariaLabel: `${srQuadraticPoint}${srVertex}`,
33351
+ point: coord,
33352
+ sequenceNumber: i + 1,
33353
+ onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
33354
+ });
33355
+ }), srQuadraticDirection && /*#__PURE__*/React__namespace.createElement("g", {
33356
+ id: quadraticDirectionId,
33357
+ style: a11y.srOnly
33358
+ }, srQuadraticDirection), srQuadraticVertex && /*#__PURE__*/React__namespace.createElement("g", {
33359
+ id: quadraticVertexId,
33360
+ style: a11y.srOnly
33361
+ }, srQuadraticVertex), /*#__PURE__*/React__namespace.createElement("g", {
33362
+ id: quadraticInterceptsId,
33363
+ style: a11y.srOnly
33364
+ }, srQuadraticXIntercepts ? `${srQuadraticXIntercepts} ${srQuadraticYIntercept}` : `${srQuadraticYIntercept}`));
33190
33365
  }
33191
33366
 
33192
33367
  // Get the quadratic coefficients from the 3 control points
@@ -33209,6 +33384,60 @@ const getQuadraticCoefficients$2 = coords => {
33209
33384
  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;
33210
33385
  return [a, b, c];
33211
33386
  };
33387
+ function getQuadraticGraphDescription(state, i18n) {
33388
+ const strings = describeQuadraticGraph(state, i18n);
33389
+ return strings.srQuadraticInteractiveElements;
33390
+ }
33391
+ // Exported for testing
33392
+ function describeQuadraticGraph(state, i18n) {
33393
+ const {
33394
+ strings,
33395
+ locale
33396
+ } = i18n;
33397
+ const coeffs = getQuadraticCoefficients$2(state.coords);
33398
+ const [a, b, c] = coeffs ?? [0, 0, 0];
33399
+ const vertex = [-b / (2 * a), c - b * b / (4 * a)];
33400
+ const xIntercepts = getQuadraticXIntercepts(a, b, c);
33401
+
33402
+ // Aria label strings
33403
+ const srQuadraticGraph = strings.srQuadraticGraph;
33404
+ const srQuadraticFaceUp = strings.srQuadraticFaceUp;
33405
+ const srQuadraticFaceDown = strings.srQuadraticFaceDown;
33406
+ // The graph only has a direction if it is not a line.
33407
+ const srQuadraticDirection = a === 0 ? undefined : a > 0 ? srQuadraticFaceUp : srQuadraticFaceDown;
33408
+ // Only describe vertex if the quadratic graph is not a line.
33409
+ // (Undefined means the quadratic graph is a line and has no vertex.)
33410
+ const srQuadraticVertex = a !== 0 ? getQuadraticVertexString(vertex, strings) : undefined;
33411
+ // Undefined means the quadratic graph has no x-intercepts,
33412
+ // such as when the graph is a horizontal line.
33413
+ const srQuadraticXIntercepts = xIntercepts.length === 2 ? strings.srQuadraticTwoXIntercepts({
33414
+ intercept1: srFormatNumber(xIntercepts[0], locale),
33415
+ intercept2: srFormatNumber(xIntercepts[1], locale)
33416
+ }) : xIntercepts.length === 1 ? strings.srQuadraticOneXIntercept({
33417
+ intercept: srFormatNumber(xIntercepts[0], locale)
33418
+ }) : undefined;
33419
+ const srQuadraticYIntercept = strings.srQuadraticYIntercept({
33420
+ intercept: srFormatNumber(c, locale)
33421
+ });
33422
+ const srQuadraticInteractiveElements = strings.srInteractiveElements({
33423
+ elements: strings.srQuadraticInteractiveElements({
33424
+ point1X: srFormatNumber(state.coords[0][0], locale),
33425
+ point1Y: srFormatNumber(state.coords[0][1], locale),
33426
+ point2X: srFormatNumber(state.coords[1][0], locale),
33427
+ point2Y: srFormatNumber(state.coords[1][1], locale),
33428
+ point3X: srFormatNumber(state.coords[2][0], locale),
33429
+ point3Y: srFormatNumber(state.coords[2][1], locale)
33430
+ })
33431
+ });
33432
+ return {
33433
+ srQuadraticGraph,
33434
+ srQuadraticDirection,
33435
+ srQuadraticVertex,
33436
+ srQuadraticXIntercepts,
33437
+ srQuadraticYIntercept,
33438
+ srQuadraticInteractiveElements
33439
+ };
33440
+ }
33212
33441
 
33213
33442
  function renderRayGraph(state, dispatch) {
33214
33443
  return {
@@ -33335,17 +33564,71 @@ function renderSegmentGraph(state, dispatch) {
33335
33564
  graphState: state,
33336
33565
  dispatch: dispatch
33337
33566
  }),
33338
- interactiveElementsDescription: null
33567
+ interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(SegmentGraphDescription, {
33568
+ state: state
33569
+ })
33339
33570
  };
33340
33571
  }
33341
- const SegmentGraph = props => {
33342
- const {
33343
- dispatch
33344
- } = props;
33572
+ const SegmentGraph = _ref => {
33573
+ let {
33574
+ dispatch,
33575
+ graphState
33576
+ } = _ref;
33345
33577
  const {
33346
33578
  coords: segments
33347
- } = props.graphState;
33348
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement(MovableLine, {
33579
+ } = graphState;
33580
+ const {
33581
+ strings,
33582
+ locale
33583
+ } = usePerseusI18n();
33584
+ const segmentUniqueId = React__namespace.useId();
33585
+ const lengthDescriptionId = segmentUniqueId + "-length";
33586
+ const wholeGraphDescriptionId = segmentUniqueId + "-whole-graph";
33587
+ function getWholeSegmentGraphAriaLabel() {
33588
+ return segments?.length > 1 ? strings.srMultipleSegmentGraphAriaLabel({
33589
+ countOfSegments: segments.length
33590
+ }) : strings.srSingleSegmentGraphAriaLabel;
33591
+ }
33592
+ const wholeSegmentGraphAriaLabel = getWholeSegmentGraphAriaLabel();
33593
+ function getIndividualSegmentAriaLabel(segment, index) {
33594
+ if (segments.length === 1) {
33595
+ return strings.srSingleSegmentLabel({
33596
+ point1X: srFormatNumber(segments[0][0][X], locale),
33597
+ point1Y: srFormatNumber(segments[0][0][Y], locale),
33598
+ point2X: srFormatNumber(segments[0][1][X], locale),
33599
+ point2Y: srFormatNumber(segments[0][1][Y], locale)
33600
+ });
33601
+ }
33602
+ return strings.srMultipleSegmentIndividualLabel({
33603
+ point1X: srFormatNumber(segment[0][X], locale),
33604
+ point1Y: srFormatNumber(segment[0][Y], locale),
33605
+ point2X: srFormatNumber(segment[1][X], locale),
33606
+ point2Y: srFormatNumber(segment[1][Y], locale),
33607
+ indexOfSegment: index + 1
33608
+ });
33609
+ }
33610
+ function getWholeSegmentGraphAriaDescription() {
33611
+ return segments.map((segment, index) => getIndividualSegmentAriaLabel(segment, index)).join(" ");
33612
+ }
33613
+ function formatSegment(endpointNumber, x, y, index) {
33614
+ const segObj = {
33615
+ endpointNumber: endpointNumber,
33616
+ x: srFormatNumber(x, locale),
33617
+ y: srFormatNumber(y, locale)
33618
+ };
33619
+ return segments.length > 1 ? strings.srMultipleSegmentGraphEndpointAriaLabel({
33620
+ ...segObj,
33621
+ indexOfSegment: index
33622
+ }) : strings.srSingleSegmentGraphEndpointAriaLabel(segObj);
33623
+ }
33624
+ return /*#__PURE__*/React__namespace.createElement("g", {
33625
+ "aria-label": wholeSegmentGraphAriaLabel,
33626
+ "aria-describedby": wholeGraphDescriptionId
33627
+ }, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement("g", {
33628
+ "aria-label": getIndividualSegmentAriaLabel(segment, i),
33629
+ "aria-describedby": lengthDescriptionId,
33630
+ key: `${segmentUniqueId}-${i}`
33631
+ }, /*#__PURE__*/React__namespace.createElement(MovableLine, {
33349
33632
  key: i,
33350
33633
  points: segment,
33351
33634
  onMoveLine: delta => {
@@ -33353,9 +33636,61 @@ const SegmentGraph = props => {
33353
33636
  },
33354
33637
  onMovePoint: (endpointIndex, destination) => {
33355
33638
  dispatch(actions.segment.movePointInFigure(i, endpointIndex, destination));
33639
+ },
33640
+ ariaLabels: {
33641
+ point1AriaLabel: formatSegment(1, segment[0][X], segment[0][Y], i + 1),
33642
+ point2AriaLabel: formatSegment(2, segment[1][X], segment[1][Y], i + 1),
33643
+ grabHandleAriaLabel: strings.srSegmentGrabHandle({
33644
+ point1X: srFormatNumber(segment[0][X], locale),
33645
+ point1Y: srFormatNumber(segment[0][Y], locale),
33646
+ point2X: srFormatNumber(segment[1][X], locale),
33647
+ point2Y: srFormatNumber(segment[1][Y], locale)
33648
+ })
33356
33649
  }
33357
- })));
33650
+ }), /*#__PURE__*/React__namespace.createElement("g", {
33651
+ id: lengthDescriptionId,
33652
+ style: a11y.srOnly
33653
+ }, strings.srSegmentLength({
33654
+ length: srFormatNumber(getLengthOfSegment(segment), locale)
33655
+ })))), /*#__PURE__*/React__namespace.createElement("g", {
33656
+ style: a11y.srOnly,
33657
+ id: wholeGraphDescriptionId
33658
+ }, getWholeSegmentGraphAriaDescription()));
33358
33659
  };
33660
+ function getLengthOfSegment(segment) {
33661
+ return kmath.point.distanceToPoint(...segment);
33662
+ }
33663
+ function SegmentGraphDescription(_ref2) {
33664
+ let {
33665
+ state
33666
+ } = _ref2;
33667
+ // The reason that SegmentGraphDescription is a component (rather than a
33668
+ // function that returns a string) is because it needs to use a
33669
+ // hook: `usePerseusI18n`.
33670
+ const i18n = usePerseusI18n();
33671
+ return describeSegmentGraph(state, i18n);
33672
+ }
33673
+
33674
+ // Exported for testing
33675
+ function describeSegmentGraph(state, i18n) {
33676
+ const {
33677
+ strings,
33678
+ locale
33679
+ } = i18n;
33680
+ const segmentDescriptions = state.coords.map((_ref3, index) => {
33681
+ let [point1, point2] = _ref3;
33682
+ return strings.srMultipleSegmentIndividualLabel({
33683
+ point1X: srFormatNumber(point1[X], locale),
33684
+ point1Y: srFormatNumber(point1[Y], locale),
33685
+ point2X: srFormatNumber(point2[X], locale),
33686
+ point2Y: srFormatNumber(point2[Y], locale),
33687
+ indexOfSegment: index + 1
33688
+ });
33689
+ });
33690
+ return strings.srInteractiveElements({
33691
+ elements: segmentDescriptions.join(" ")
33692
+ });
33693
+ }
33359
33694
 
33360
33695
  function renderSinusoidGraph(state, dispatch) {
33361
33696
  return {
@@ -33631,9 +33966,10 @@ const MafsGraph = props => {
33631
33966
  x: viewboxX,
33632
33967
  y: viewboxY
33633
33968
  };
33969
+ const i18n = usePerseusI18n();
33634
33970
  const {
33635
33971
  strings
33636
- } = usePerseusI18n();
33972
+ } = i18n;
33637
33973
  const interactionPrompt = isUnlimitedGraphState(state) && state.showKeyboardInteractionInvitation;
33638
33974
  wonderBlocksCore.useOnMountEffect(() => {
33639
33975
  analytics.onAnalyticsEvent({
@@ -33650,7 +33986,8 @@ const MafsGraph = props => {
33650
33986
  interactiveElementsDescription
33651
33987
  } = renderGraphElements({
33652
33988
  state,
33653
- dispatch
33989
+ dispatch,
33990
+ i18n
33654
33991
  });
33655
33992
  return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
33656
33993
  value: {
@@ -34027,7 +34364,8 @@ const calculateNestedSVGCoords = (range, width, height) => {
34027
34364
  const renderGraphElements = props => {
34028
34365
  const {
34029
34366
  state,
34030
- dispatch
34367
+ dispatch,
34368
+ i18n
34031
34369
  } = props;
34032
34370
  const {
34033
34371
  type
@@ -34050,7 +34388,7 @@ const renderGraphElements = props => {
34050
34388
  case "circle":
34051
34389
  return renderCircleGraph(state, dispatch);
34052
34390
  case "quadratic":
34053
- return renderQuadraticGraph(state, dispatch);
34391
+ return renderQuadraticGraph(state, dispatch, i18n);
34054
34392
  case "sinusoid":
34055
34393
  return renderSinusoidGraph(state, dispatch);
34056
34394
  case "none":
@@ -37670,19 +38008,21 @@ class LabelImage extends React__namespace.Component {
37670
38008
  this._mounted = false;
37671
38009
  }
37672
38010
  getUserInput() {
37673
- const {
37674
- markers
37675
- } = this.props;
37676
38011
  return {
37677
- markers
38012
+ markers: this.props.markers.map(marker => ({
38013
+ selected: marker.selected,
38014
+ label: marker.label
38015
+ }))
37678
38016
  };
37679
38017
  }
37680
38018
  getPromptJSON() {
37681
38019
  return getPromptJSON$a(this.props, this.getUserInput());
37682
38020
  }
37683
38021
 
37684
- // TODO(LEMS-2544): Investigate impact on scoring; possibly pull out &/or remove rubric parameter.
37685
- showRationalesForCurrentlySelectedChoices(rubric) {
38022
+ // TODO(LEMS-2544): Investigate impact on scoring
38023
+ // Also consider how scoreMarker is being called as it seems to require the marker.answers property.
38024
+ // Removed rubric parameter, but it gets a full widget options object from the renderer
38025
+ showRationalesForCurrentlySelectedChoices() {
37686
38026
  const {
37687
38027
  markers
37688
38028
  } = this.props;
@@ -37690,7 +38030,7 @@ class LabelImage extends React__namespace.Component {
37690
38030
  onChange
37691
38031
  } = this.props;
37692
38032
  const updatedMarkers = markers.map(marker => {
37693
- const score = perseusScore.scoreLabelImageMarker(marker);
38033
+ const score = perseusScore.scoreLabelImageMarker(marker.selected, marker.answers);
37694
38034
  return {
37695
38035
  ...marker,
37696
38036
  // Reveal correctness state for markers with answers.
@@ -37798,6 +38138,7 @@ class LabelImage extends React__namespace.Component {
37798
38138
  selected: selected.length ? selected : undefined
37799
38139
  });
37800
38140
  }
38141
+ // TODO(LEMS-2723): Investigate if possible to change this to not require answers
37801
38142
  renderMarkers() {
37802
38143
  const {
37803
38144
  markers,
@@ -37839,7 +38180,7 @@ class LabelImage extends React__namespace.Component {
37839
38180
  bottom: "top"
37840
38181
  }[markerPosition];
37841
38182
  }
37842
- const score = perseusScore.scoreLabelImageMarker(marker);
38183
+ const score = perseusScore.scoreLabelImageMarker(marker.selected, marker.answers);
37843
38184
  // Once the question is answered, show markers
37844
38185
  // with correct answers, otherwise passthrough
37845
38186
  // the correctness state.
@@ -39384,7 +39725,10 @@ var Matrix$1 = {
39384
39725
  isLintable: true,
39385
39726
  // TODO(LEMS-2656): remove TS suppression
39386
39727
  // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
39387
- scorer: perseusScore.scoreMatrix
39728
+ scorer: perseusScore.scoreMatrix,
39729
+ // TODO(LEMS-2656): remove TS suppression
39730
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
39731
+ validator: perseusScore.validateMatrix
39388
39732
  };
39389
39733
 
39390
39734
  const getPromptJSON$7 = () => {
@@ -39493,34 +39837,13 @@ class Measurer extends React__namespace.Component {
39493
39837
  }));
39494
39838
  }
39495
39839
  }
39496
- const propUpgrades = {
39497
- "1": v0props => {
39498
- const {
39499
- imageUrl,
39500
- imageTop,
39501
- imageLeft,
39502
- ...rest
39503
- } = v0props;
39504
- return {
39505
- ...rest,
39506
- image: {
39507
- url: imageUrl,
39508
- top: imageTop,
39509
- left: imageLeft
39510
- }
39511
- };
39512
- }
39513
- };
39514
39840
  var Measurer$1 = {
39515
39841
  name: "measurer",
39516
39842
  displayName: "Measurer",
39517
39843
  hidden: true,
39518
39844
  widget: Measurer,
39519
- version: {
39520
- major: 1,
39521
- minor: 0
39522
- },
39523
- propUpgrades: propUpgrades,
39845
+ version: perseusCore.measurerLogic.version,
39846
+ propUpgrades: perseusCore.measurerLogic.widgetOptionsUpgrades,
39524
39847
  // TODO: things that aren't interactive shouldn't need scoring functions
39525
39848
  scorer: () => scoreNoop(1)
39526
39849
  };
@@ -40095,10 +40418,10 @@ function parseBondModifier(smiles, ctx) {
40095
40418
  const firstChar = smiles[0];
40096
40419
  const rest = smiles.slice(1);
40097
40420
  if (firstChar === "=") {
40098
- return parse$2(rest, _mset(ctx, ["bond", "bondType"], "double"));
40421
+ return parse$1(rest, _mset(ctx, ["bond", "bondType"], "double"));
40099
40422
  }
40100
40423
  if (firstChar === "#") {
40101
- return parse$2(rest, _mset(ctx, ["bond", "bondType"], "triple"));
40424
+ return parse$1(rest, _mset(ctx, ["bond", "bondType"], "triple"));
40102
40425
  }
40103
40426
  throw new ParseError$1("Invalid character: " + firstChar);
40104
40427
  }
@@ -40159,8 +40482,8 @@ function parseParenthesizedExpression(smiles, ctx) {
40159
40482
  idx: newCtx.idx.concat([[inBranchIdx, 0]]),
40160
40483
  parens: newCtx.parens.concat("(")
40161
40484
  };
40162
- const parenExpr = parse$2(rest, parenCtx);
40163
- const remainder = parse$2(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
40485
+ const parenExpr = parse$1(rest, parenCtx);
40486
+ const remainder = parse$1(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
40164
40487
  return [parenExpr].concat(remainder);
40165
40488
  }
40166
40489
  if (firstChar === ")") {
@@ -40224,7 +40547,7 @@ function parseAtom(smiles, ctx) {
40224
40547
 
40225
40548
  // increment the atom counter and reset the branch counter
40226
40549
  const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
40227
- let restOfMolecule = parse$2(rest, _mset(newCtx, ["bond", "bondType"], "single"));
40550
+ let restOfMolecule = parse$1(rest, _mset(newCtx, ["bond", "bondType"], "single"));
40228
40551
  if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
40229
40552
  //TODO(colin): fix this awkwardness.
40230
40553
  restOfMolecule = [restOfMolecule];
@@ -40263,7 +40586,7 @@ function isModifierChar(s) {
40263
40586
  * ParseError: if the input is not valid SMILES or contains features not
40264
40587
  * yet implemented.
40265
40588
  */
40266
- function parse$2(smiles, ctx) {
40589
+ function parse$1(smiles, ctx) {
40267
40590
  if (!validate(smiles)) {
40268
40591
  throw new ParseError$1("Invalid input.");
40269
40592
  }
@@ -40288,7 +40611,7 @@ function parse$2(smiles, ctx) {
40288
40611
  return parseParenthesizedExpression(smiles, ctx);
40289
40612
  }
40290
40613
  var SmilesParser = {
40291
- parse: parse$2,
40614
+ parse: parse$1,
40292
40615
  ParseError: ParseError$1
40293
40616
  };
40294
40617
 
@@ -40296,7 +40619,7 @@ var SmilesParser = {
40296
40619
  const {
40297
40620
  layout
40298
40621
  } = MoleculeLayout;
40299
- const parse$1 = SmilesParser.parse;
40622
+ const parse = SmilesParser.parse;
40300
40623
  const ParseError = SmilesParser.ParseError;
40301
40624
  const borderSize = 30;
40302
40625
  class Molecule extends React__namespace.Component {
@@ -40323,7 +40646,7 @@ class Molecule extends React__namespace.Component {
40323
40646
  try {
40324
40647
  this.setState({
40325
40648
  // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
40326
- parsedSmiles: parse$1(smiles),
40649
+ parsedSmiles: parse(smiles),
40327
40650
  error: null
40328
40651
  });
40329
40652
  } catch (e) {
@@ -40988,7 +41311,10 @@ var NumberLine$1 = {
40988
41311
  staticTransform: staticTransform$1,
40989
41312
  // TODO(LEMS-2656): remove TS suppression
40990
41313
  // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
40991
- scorer: perseusScore.scoreNumberLine
41314
+ scorer: perseusScore.scoreNumberLine,
41315
+ // TODO(LEMS-2656): remove TS suppression
41316
+ // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
41317
+ validator: perseusScore.validateNumberLine
40992
41318
  };
40993
41319
 
40994
41320
  const getPromptJSON$5 = (renderProps, userInput) => {
@@ -41542,7 +41868,11 @@ var Orderer$1 = {
41542
41868
  isLintable: true,
41543
41869
  // TODO(LEMS-2656): remove TS suppression
41544
41870
  // @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
41545
- scorer: perseusScore.scoreOrderer
41871
+ scorer: perseusScore.scoreOrderer,
41872
+ // TODO(LEMS-2656): remove TS suppression
41873
+ // @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
41874
+ validator: perseusScore.validateOrderer,
41875
+ getPublicWidgetOptions: perseusCore.getOrdererPublicWidgetOptions
41546
41876
  };
41547
41877
 
41548
41878
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -41591,10 +41921,6 @@ var PassageRefTarget$1 = {
41591
41921
  transform: editorProps => {
41592
41922
  return ___default["default"].pick(editorProps, "content");
41593
41923
  },
41594
- version: {
41595
- major: 0,
41596
- minor: 0
41597
- },
41598
41924
  isLintable: true,
41599
41925
  // TODO: things that aren't interactive shouldn't need scoring functions
41600
41926
  scorer: () => scoreNoop()
@@ -42618,7 +42944,10 @@ var Plotter$1 = {
42618
42944
  staticTransform: staticTransform,
42619
42945
  // TODO(LEMS-2656): remove TS suppression
42620
42946
  // @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
42621
- scorer: perseusScore.scorePlotter
42947
+ scorer: perseusScore.scorePlotter,
42948
+ // TODO(LEMS-2656): remove TS suppression
42949
+ // @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
42950
+ validator: perseusScore.validatePlotter
42622
42951
  };
42623
42952
 
42624
42953
  const getPromptJSON$2 = () => {
@@ -42781,7 +43110,10 @@ var Sorter$1 = {
42781
43110
  isLintable: true,
42782
43111
  // TODO(LEMS-2656): remove TS suppression
42783
43112
  // @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
42784
- scorer: perseusScore.scoreSorter
43113
+ scorer: perseusScore.scoreSorter,
43114
+ // TODO(LEMS-2656): remove TS suppression
43115
+ // @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
43116
+ validator: perseusScore.validateSorter
42785
43117
  };
42786
43118
 
42787
43119
  const {
@@ -43000,7 +43332,10 @@ var Table$1 = {
43000
43332
  isLintable: true,
43001
43333
  // TODO(LEMS-2656): remove TS suppression
43002
43334
  // @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
43003
- scorer: perseusScore.scoreTable
43335
+ scorer: perseusScore.scoreTable,
43336
+ // TODO(LEMS-2656): remove TS suppression
43337
+ // @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
43338
+ validator: perseusScore.validateTable
43004
43339
  };
43005
43340
 
43006
43341
  const getPromptJSON = () => {
@@ -43185,7 +43520,7 @@ const init = function () {
43185
43520
 
43186
43521
  // This file is processed by a Rollup plugin (replace) to inject the production
43187
43522
  const libName = "@khanacademy/perseus";
43188
- const libVersion = "51.0.0";
43523
+ const libVersion = "52.0.0";
43189
43524
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43190
43525
 
43191
43526
  const apiVersion = {
@@ -44631,6 +44966,69 @@ const registerAllWidgetsForTesting = () => {
44631
44966
  replaceDeprecatedWidgets();
44632
44967
  };
44633
44968
 
44969
+ /**
44970
+ * Get a widget type by a widget's ID
44971
+ * (please don't derive type from ID)
44972
+ *
44973
+ * @param {string} widgetId the ID of the widget
44974
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
44975
+ * @returns {string} the widget type (ie "radio")
44976
+ */
44977
+ function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
44978
+ const widget = widgetMap[WidgetId];
44979
+ return widget?.type ?? null;
44980
+ }
44981
+
44982
+ /**
44983
+ * Does the content have a specific type of widget?
44984
+ *
44985
+ * @param {string} type the type of the widget in question (ie "radio")
44986
+ * @param {string} content the string to search through
44987
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
44988
+ * @returns {boolean} if the content includes the widget type
44989
+ */
44990
+ function contentHasWidgetType(type, content, widgetMap) {
44991
+ return perseusCore.getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
44992
+ }
44993
+
44994
+ /**
44995
+ * Pull the widget map out of ItemData
44996
+ *
44997
+ * @param {PerseusItem} PerseusItem containing a widgetMap
44998
+ * @returns {WidgetMap} the widget map in the PerseusItem
44999
+ */
45000
+ function getWidgetsMapFromItemData(itemData) {
45001
+ return itemData.question.widgets;
45002
+ }
45003
+
45004
+ /**
45005
+ * Get the widget information from a WidgetMap
45006
+ *
45007
+ * @param {string} widgetId the ID of the widget
45008
+ * @returns {PerseusWidget | null} the widget data if it exists, otherwise null
45009
+ */
45010
+ function getWidgetFromWidgetMap(widgetId, widgetMap) {
45011
+ return widgetMap[widgetId] ?? null;
45012
+ }
45013
+
45014
+ /**
45015
+ * Get select widgets from a widget map.
45016
+ *
45017
+ * @param {ReadonlyArray<string>} widgetIds to extract
45018
+ * @param {PerseusWidgetsMap} widgetMap to extract from
45019
+ * @return {PerseusWidgetsMap} a new widget map with requested widgets
45020
+ */
45021
+ function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
45022
+ const widgets = {};
45023
+ widgetIds.forEach(widgetId => {
45024
+ const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
45025
+ if (widget) {
45026
+ widgets[widgetId] = widget;
45027
+ }
45028
+ });
45029
+ return widgets;
45030
+ }
45031
+
44634
45032
  /**
44635
45033
  * This function extracts the answers from the widgets.
44636
45034
  *
@@ -45154,1683 +45552,6 @@ const getValidWidgetIds = perseusItem => {
45154
45552
  return wonderStuffCore.keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
45155
45553
  };
45156
45554
 
45157
- function isRealJSONParse(jsonParse) {
45158
- const randomPhrase = buildRandomPhrase();
45159
- const randomHintPhrase = buildRandomPhrase();
45160
- const randomString = buildRandomString();
45161
- const testingObject = JSON.stringify({
45162
- answerArea: {
45163
- calculator: false,
45164
- chi2Table: false,
45165
- financialCalculatorMonthlyPayment: false,
45166
- financialCalculatorTimeToPayOff: false,
45167
- financialCalculatorTotalAmount: false,
45168
- periodicTable: false,
45169
- periodicTableWithKey: false,
45170
- tTable: false,
45171
- zTable: false
45172
- },
45173
- hints: [randomHintPhrase, `=${Math.floor(Math.random() * 50) + 1}`],
45174
- itemDataVersion: {
45175
- major: 0,
45176
- minor: 1
45177
- },
45178
- question: {
45179
- content: `${randomPhrase}`,
45180
- images: {},
45181
- widgets: {
45182
- expression1: {
45183
- alignment: "default",
45184
- graded: false,
45185
- options: {
45186
- answerForms: [{
45187
- considered: "wrong",
45188
- form: false,
45189
- key: 0,
45190
- simplify: false,
45191
- value: `${randomString}`
45192
- }],
45193
- ariaLabel: "Answer",
45194
- buttonSets: ["basic"],
45195
- functions: ["f", "g", "h"],
45196
- static: true,
45197
- times: false,
45198
- visibleLabel: "Answer"
45199
- },
45200
- static: true,
45201
- type: "expression",
45202
- version: {
45203
- major: 1,
45204
- minor: 0
45205
- }
45206
- }
45207
- }
45208
- }
45209
- });
45210
- const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
45211
- const parsedTestJSON = jsonParse(testJSON);
45212
- const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
45213
- return perseusCore.approximateDeepEqual(parsedTestItemData, testingObject);
45214
- }
45215
- function buildRandomString() {
45216
- let capitalize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
45217
- let randomString = "";
45218
- const randomLength = Math.floor(Math.random() * 8) + 3;
45219
- for (let i = 0; i < randomLength; i++) {
45220
- const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
45221
- randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
45222
- }
45223
- return randomString;
45224
- }
45225
- function buildRandomPhrase() {
45226
- const phrases = [];
45227
- const randomLength = Math.floor(Math.random() * 10) + 5;
45228
- for (let i = 0; i < randomLength; i++) {
45229
- phrases.push(buildRandomString(i === 0));
45230
- }
45231
- const modifierStart = ["**", "$"];
45232
- const modifierEnd = ["**", "$"];
45233
- const modifierIndex = Math.floor(Math.random() * modifierStart.length);
45234
- return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
45235
- }
45236
- function buildTestData(testObject) {
45237
- return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
45238
- }
45239
-
45240
- function success(value) {
45241
- return {
45242
- type: "success",
45243
- value
45244
- };
45245
- }
45246
- function failure(detail) {
45247
- return {
45248
- type: "failure",
45249
- detail
45250
- };
45251
- }
45252
- function isFailure(result) {
45253
- return result.type === "failure";
45254
- }
45255
- function isSuccess(result) {
45256
- return result.type === "success";
45257
- }
45258
-
45259
- // Result's `all` function is similar to Promise.all: given an array of
45260
- // results, it returns success if all succeeded, and failure if any failed.
45261
- function all(results) {
45262
- let combineFailureDetails = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : a => a;
45263
- const values = [];
45264
- const failureDetails = [];
45265
- for (const result of results) {
45266
- if (result.type === "success") {
45267
- values.push(result.value);
45268
- } else {
45269
- failureDetails.push(result.detail);
45270
- }
45271
- }
45272
- if (failureDetails.length > 0) {
45273
- return failure(failureDetails.reduce(combineFailureDetails));
45274
- }
45275
- return success(values);
45276
- }
45277
-
45278
- class ErrorTrackingParseContext {
45279
- constructor(path) {
45280
- this.path = path;
45281
- }
45282
- failure(expected, badValue) {
45283
- return failure([{
45284
- expected: wrapInArray(expected),
45285
- badValue,
45286
- path: this.path
45287
- }]);
45288
- }
45289
- forSubtree(key) {
45290
- return new ErrorTrackingParseContext([...this.path, key]);
45291
- }
45292
- success(value) {
45293
- return success(value);
45294
- }
45295
- }
45296
- function wrapInArray(a) {
45297
- return Array.isArray(a) ? a : [a];
45298
- }
45299
-
45300
- function formatPath(path) {
45301
- return "(root)" + path.map(formatPathSegment).join("");
45302
- }
45303
- function formatPathSegment(segment) {
45304
- if (typeof segment === "string") {
45305
- return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
45306
- }
45307
- return `[${segment.toString()}]`;
45308
- }
45309
- const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
45310
-
45311
- function message(failure) {
45312
- const expected = conjoin(failure.expected);
45313
- const path = formatPath(failure.path);
45314
- const badValue = JSON.stringify(failure.badValue);
45315
- return `At ${path} -- expected ${expected}, but got ${badValue}`;
45316
- }
45317
- function conjoin(items) {
45318
- switch (items.length) {
45319
- // TODO(benchristel): handle 0 if this is reused elsewhere.
45320
- case 1:
45321
- return items[0];
45322
- case 2:
45323
- return items.join(" or ");
45324
- default:
45325
- {
45326
- const allButLast = items.slice(0, items.length - 1);
45327
- const last = items[items.length - 1];
45328
- return allButLast.join(", ") + ", or " + last;
45329
- }
45330
- }
45331
- }
45332
-
45333
- function parse(value, parser) {
45334
- const result = parser(value, new ErrorTrackingParseContext([]));
45335
- if (isFailure(result)) {
45336
- return failure(result.detail.map(message).join("; "));
45337
- }
45338
- return result;
45339
- }
45340
-
45341
- const any = (rawValue, ctx) => ctx.success(rawValue);
45342
-
45343
- function array(elementParser) {
45344
- return (rawValue, ctx) => {
45345
- if (!Array.isArray(rawValue)) {
45346
- return ctx.failure("array", rawValue);
45347
- }
45348
- const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
45349
- return all(elementResults, concat);
45350
- };
45351
- }
45352
- function concat(a, b) {
45353
- return [...a, ...b];
45354
- }
45355
-
45356
- function boolean(rawValue, ctx) {
45357
- if (typeof rawValue === "boolean") {
45358
- return ctx.success(rawValue);
45359
- }
45360
- return ctx.failure("boolean", rawValue);
45361
- }
45362
-
45363
- function constant(acceptedValue) {
45364
- return (rawValue, ctx) => {
45365
- if (rawValue !== acceptedValue) {
45366
- return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
45367
- }
45368
- return ctx.success(acceptedValue);
45369
- };
45370
- }
45371
-
45372
- function enumeration() {
45373
- for (var _len = arguments.length, acceptedValues = new Array(_len), _key = 0; _key < _len; _key++) {
45374
- acceptedValues[_key] = arguments[_key];
45375
- }
45376
- return (rawValue, ctx) => {
45377
- if (typeof rawValue === "string") {
45378
- const index = acceptedValues.indexOf(rawValue);
45379
- if (index > -1) {
45380
- return ctx.success(acceptedValues[index]);
45381
- }
45382
- }
45383
- const expected = acceptedValues.map(v => JSON.stringify(v));
45384
- return ctx.failure(expected, rawValue);
45385
- };
45386
- }
45387
-
45388
- function isObject(x) {
45389
- return x != null && Object.getPrototypeOf(x) === Object.prototype;
45390
- }
45391
-
45392
- function nullable(parseValue) {
45393
- return (rawValue, ctx) => {
45394
- if (rawValue === null) {
45395
- return ctx.success(rawValue);
45396
- }
45397
- return parseValue(rawValue, ctx);
45398
- };
45399
- }
45400
-
45401
- const number = (rawValue, ctx) => {
45402
- if (typeof rawValue === "number") {
45403
- return ctx.success(rawValue);
45404
- }
45405
- return ctx.failure("number", rawValue);
45406
- };
45407
-
45408
- function object(schema) {
45409
- return (rawValue, ctx) => {
45410
- if (!isObject(rawValue)) {
45411
- return ctx.failure("object", rawValue);
45412
- }
45413
- const ret = {
45414
- ...rawValue
45415
- };
45416
- const mismatches = [];
45417
- for (const [prop, propParser] of Object.entries(schema)) {
45418
- const result = propParser(rawValue[prop], ctx.forSubtree(prop));
45419
- if (isSuccess(result)) {
45420
- if (result.value !== undefined || prop in rawValue) {
45421
- ret[prop] = result.value;
45422
- }
45423
- } else {
45424
- mismatches.push(...result.detail);
45425
- }
45426
- }
45427
- if (mismatches.length > 0) {
45428
- return failure(mismatches);
45429
- }
45430
- return ctx.success(ret);
45431
- };
45432
- }
45433
-
45434
- function optional(parseValue) {
45435
- return (rawValue, ctx) => {
45436
- if (rawValue === undefined) {
45437
- return ctx.success(rawValue);
45438
- }
45439
- return parseValue(rawValue, ctx);
45440
- };
45441
- }
45442
-
45443
- function pair(parseA, parseB) {
45444
- return (rawValue, ctx) => {
45445
- if (!Array.isArray(rawValue)) {
45446
- return ctx.failure("array", rawValue);
45447
- }
45448
- if (rawValue.length !== 2) {
45449
- return ctx.failure("array of length 2", rawValue);
45450
- }
45451
- const [rawA, rawB] = rawValue;
45452
- const resultA = parseA(rawA, ctx.forSubtree(0));
45453
- if (isFailure(resultA)) {
45454
- return resultA;
45455
- }
45456
- const resultB = parseB(rawB, ctx.forSubtree(1));
45457
- if (isFailure(resultB)) {
45458
- return resultB;
45459
- }
45460
- return ctx.success([resultA.value, resultB.value]);
45461
- };
45462
- }
45463
-
45464
- function pipeParsers(p) {
45465
- return new ParserPipeline(p);
45466
- }
45467
- class ParserPipeline {
45468
- constructor(parser) {
45469
- this.parser = parser;
45470
- }
45471
- then(nextParser) {
45472
- return new ParserPipeline(composeParsers(this.parser, nextParser));
45473
- }
45474
- }
45475
- function composeParsers(parserA, parserB) {
45476
- return (rawValue, ctx) => {
45477
- const partialResult = parserA(rawValue, ctx);
45478
- if (isFailure(partialResult)) {
45479
- return partialResult;
45480
- }
45481
- return parserB(partialResult.value, ctx);
45482
- };
45483
- }
45484
-
45485
- function record(parseKey, parseValue) {
45486
- return (rawValue, ctx) => {
45487
- if (!isObject(rawValue)) {
45488
- return ctx.failure("object", rawValue);
45489
- }
45490
- const result = {};
45491
- const mismatches = [];
45492
- for (const [key, value] of Object.entries(rawValue)) {
45493
- const entryCtx = ctx.forSubtree(key);
45494
- const keyResult = parseKey(key, entryCtx);
45495
- if (isFailure(keyResult)) {
45496
- mismatches.push(...keyResult.detail);
45497
- }
45498
- const valueResult = parseValue(value, entryCtx);
45499
- if (isFailure(valueResult)) {
45500
- mismatches.push(...valueResult.detail);
45501
- }
45502
- if (isSuccess(keyResult) && isSuccess(valueResult)) {
45503
- result[keyResult.value] = valueResult.value;
45504
- }
45505
- }
45506
- if (mismatches.length > 0) {
45507
- return failure(mismatches);
45508
- }
45509
- return ctx.success(result);
45510
- };
45511
- }
45512
-
45513
- const string = (rawValue, ctx) => {
45514
- if (typeof rawValue === "string") {
45515
- return ctx.success(rawValue);
45516
- }
45517
- return ctx.failure("string", rawValue);
45518
- };
45519
-
45520
- function trio(parseA, parseB, parseC) {
45521
- return (rawValue, ctx) => {
45522
- if (!Array.isArray(rawValue)) {
45523
- return ctx.failure("array", rawValue);
45524
- }
45525
- if (rawValue.length !== 3) {
45526
- return ctx.failure("array of length 3", rawValue);
45527
- }
45528
- const resultA = parseA(rawValue[0], ctx.forSubtree(0));
45529
- if (isFailure(resultA)) {
45530
- return resultA;
45531
- }
45532
- const resultB = parseB(rawValue[1], ctx.forSubtree(1));
45533
- if (isFailure(resultB)) {
45534
- return resultB;
45535
- }
45536
- const resultC = parseC(rawValue[2], ctx.forSubtree(2));
45537
- if (isFailure(resultC)) {
45538
- return resultC;
45539
- }
45540
- return ctx.success([resultA.value, resultB.value, resultC.value]);
45541
- };
45542
- }
45543
-
45544
- function union(parseBranch) {
45545
- return new UnionBuilder(parseBranch);
45546
- }
45547
- class UnionBuilder {
45548
- constructor(parser) {
45549
- this.parser = parser;
45550
- }
45551
- or(newBranch) {
45552
- return new UnionBuilder(either(this.parser, newBranch));
45553
- }
45554
- }
45555
- function either(parseA, parseB) {
45556
- return (rawValue, ctx) => {
45557
- const resultA = parseA(rawValue, ctx);
45558
- if (isSuccess(resultA)) {
45559
- return resultA;
45560
- }
45561
- return parseB(rawValue, ctx);
45562
- };
45563
- }
45564
-
45565
- function defaulted(parser, fallback) {
45566
- return (rawValue, ctx) => {
45567
- if (rawValue == null) {
45568
- return success(fallback(rawValue));
45569
- }
45570
- return parser(rawValue, ctx);
45571
- };
45572
- }
45573
-
45574
- const parseImages = defaulted(record(string, object({
45575
- width: number,
45576
- height: number
45577
- })), () => ({}));
45578
-
45579
- function parseWidget(parseType, parseOptions) {
45580
- return object({
45581
- type: parseType,
45582
- static: optional(boolean),
45583
- graded: optional(boolean),
45584
- alignment: optional(string),
45585
- options: parseOptions,
45586
- key: optional(number),
45587
- version: optional(object({
45588
- major: number,
45589
- minor: number
45590
- }))
45591
- });
45592
- }
45593
- function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
45594
- return object({
45595
- type: parseType,
45596
- static: optional(boolean),
45597
- graded: optional(boolean),
45598
- alignment: optional(string),
45599
- options: parseOptions,
45600
- key: optional(number),
45601
- version: parseVersion
45602
- });
45603
- }
45604
-
45605
- const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
45606
- items: array(string),
45607
- categories: array(string),
45608
- randomizeItems: defaulted(boolean, () => false),
45609
- static: defaulted(boolean, () => false),
45610
- values: array(defaulted(number, () => 0)),
45611
- highlightLint: optional(boolean),
45612
- linterContext: optional(object({
45613
- contentType: string,
45614
- paths: array(string),
45615
- stack: array(string)
45616
- }))
45617
- }));
45618
-
45619
- const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
45620
- programID: string,
45621
- programType: any,
45622
- settings: array(object({
45623
- name: string,
45624
- value: string
45625
- })),
45626
- showEditor: boolean,
45627
- showButtons: boolean,
45628
- height: number,
45629
- static: defaulted(boolean, () => false)
45630
- }));
45631
-
45632
- const parseDefinitionWidget = parseWidget(constant("definition"), object({
45633
- togglePrompt: string,
45634
- definition: string,
45635
- static: defaulted(boolean, () => false)
45636
- }));
45637
-
45638
- const parseDropdownWidget = parseWidget(constant("dropdown"), object({
45639
- placeholder: defaulted(string, () => ""),
45640
- ariaLabel: optional(string),
45641
- visibleLabel: optional(string),
45642
- static: defaulted(boolean, () => false),
45643
- choices: array(object({
45644
- content: string,
45645
- correct: boolean
45646
- }))
45647
- }));
45648
-
45649
- const parseExplanationWidget = parseWidget(constant("explanation"), object({
45650
- showPrompt: string,
45651
- hidePrompt: string,
45652
- explanation: string,
45653
- // We wrap parseWidgetsMap in a function here to make sure it is not
45654
- // referenced before it is defined. There is an import cycle between
45655
- // this file and widgets-map.ts that could cause it to be undefined.
45656
- widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
45657
- static: defaulted(boolean, () => false)
45658
- }));
45659
-
45660
- // Given a function, creates a PartialParser that converts one type to another
45661
- // using that function. The returned parser never fails.
45662
- function convert(f) {
45663
- return (rawValue, ctx) => ctx.success(f(rawValue));
45664
- }
45665
-
45666
- /**
45667
- * Creates a parser for a widget options type with multiple major versions. Old
45668
- * versions are migrated to the latest version. The parse fails if the input
45669
- * data does not match any of the versions.
45670
- *
45671
- * @example
45672
- * const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
45673
- * .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
45674
- * .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
45675
- * .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
45676
- * .parser;
45677
- *
45678
- * @param latestMajorVersion the latest major version of the widget options.
45679
- * @param parseLatest a {@link Parser} for the latest version of the widget
45680
- * options.
45681
- * @returns a builder object, to which migrations from earlier versions can be
45682
- * added. Migrations must be added in "reverse chronological" order as in the
45683
- * example above.
45684
- */
45685
- function versionedWidgetOptions(latestMajorVersion, parseLatest) {
45686
- return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
45687
- }
45688
- class VersionedWidgetOptionsParserBuilder {
45689
- parser;
45690
- constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
45691
- this.migrateToLatest = migrateToLatest;
45692
- this.parseOtherVersions = parseOtherVersions;
45693
- const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
45694
- this.parser = (raw, ctx) => {
45695
- if (!isObject(raw)) {
45696
- return ctx.failure("object", raw);
45697
- }
45698
- const versionParseResult = parseVersionedObject(raw, ctx);
45699
- if (isFailure(versionParseResult)) {
45700
- return versionParseResult;
45701
- }
45702
- if (versionParseResult.value.version.major !== majorVersion) {
45703
- return this.parseOtherVersions(raw, ctx);
45704
- }
45705
- return parseThisVersionAndMigrateToLatest(raw, ctx);
45706
- };
45707
- }
45708
-
45709
- /**
45710
- * Add a migration from an old version of the widget options.
45711
- */
45712
- withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
45713
- const parseOtherVersions = this.parser;
45714
- const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
45715
- return new VersionedWidgetOptionsParserBuilder(majorVersion, parseOldVersion, migrateToLatest, parseOtherVersions);
45716
- }
45717
- }
45718
- const parseVersionedObject = object({
45719
- version: defaulted(object({
45720
- major: number,
45721
- minor: number
45722
- }), () => ({
45723
- major: 0,
45724
- minor: 0
45725
- }))
45726
- });
45727
-
45728
- const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
45729
- const parsePossiblyInvalidAnswerForm = object({
45730
- // `value` is the possibly invalid part of this. It should always be a
45731
- // string, but some answer forms don't have it. The Expression widget
45732
- // ignores invalid values, so we can safely filter them out during parsing.
45733
- value: optional(string),
45734
- form: defaulted(boolean, () => false),
45735
- simplify: defaulted(boolean, () => false),
45736
- considered: enumeration("correct", "wrong", "ungraded"),
45737
- key: pipeParsers(stringOrNumberOrNullOrUndefined).then(convert(String)).parser
45738
- });
45739
- function removeInvalidAnswerForms(possiblyInvalid) {
45740
- const valid = [];
45741
- for (const answerForm of possiblyInvalid) {
45742
- const {
45743
- value
45744
- } = answerForm;
45745
- if (value != null) {
45746
- // Copying the object seems to be needed to make TypeScript happy
45747
- valid.push({
45748
- ...answerForm,
45749
- value
45750
- });
45751
- }
45752
- }
45753
- return valid;
45754
- }
45755
-
45756
- // NOTE(benchristel): I copied the default buttonSets from
45757
- // expression.tsx. See the parse-perseus-json/README.md for
45758
- // an explanation of why we want to duplicate the default here.
45759
- const parseButtonSets = defaulted(array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")), () => ["basic", "trig", "prealgebra", "logarithms"]);
45760
- const version1 = object({
45761
- major: constant(1),
45762
- minor: number
45763
- });
45764
- const parseExpressionWidgetV1 = parseWidgetWithVersion(version1, constant("expression"), object({
45765
- answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
45766
- functions: array(string),
45767
- times: boolean,
45768
- visibleLabel: optional(string),
45769
- ariaLabel: optional(string),
45770
- buttonSets: parseButtonSets,
45771
- buttonsVisible: optional(enumeration("always", "never", "focused"))
45772
- }));
45773
- const version0 = optional(object({
45774
- major: constant(0),
45775
- minor: number
45776
- }));
45777
- const parseExpressionWidgetV0 = parseWidgetWithVersion(version0, constant("expression"), object({
45778
- functions: array(string),
45779
- times: boolean,
45780
- visibleLabel: optional(string),
45781
- ariaLabel: optional(string),
45782
- form: boolean,
45783
- simplify: boolean,
45784
- value: string,
45785
- buttonSets: parseButtonSets,
45786
- buttonsVisible: optional(enumeration("always", "never", "focused"))
45787
- }));
45788
- function migrateV0ToV1(widget) {
45789
- const {
45790
- options
45791
- } = widget;
45792
- return {
45793
- ...widget,
45794
- version: {
45795
- major: 1,
45796
- minor: 0
45797
- },
45798
- options: {
45799
- times: options.times,
45800
- buttonSets: options.buttonSets,
45801
- functions: options.functions,
45802
- buttonsVisible: options.buttonsVisible,
45803
- visibleLabel: options.visibleLabel,
45804
- ariaLabel: options.ariaLabel,
45805
- answerForms: [{
45806
- considered: "correct",
45807
- form: options.form,
45808
- simplify: options.simplify,
45809
- value: options.value
45810
- }]
45811
- }
45812
- };
45813
- }
45814
- const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
45815
-
45816
- const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
45817
- const parseGradedGroupWidgetOptions = object({
45818
- title: defaulted(string, () => ""),
45819
- hasHint: optional(nullable(boolean)),
45820
- // This module has an import cycle with parsePerseusRenderer.
45821
- // The anonymous function below ensures that we don't try to access
45822
- // parsePerseusRenderer before it's defined.
45823
- hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
45824
- content: string,
45825
- // This module has an import cycle with parseWidgetsMap.
45826
- // The anonymous function below ensures that we don't try to access
45827
- // parseWidgetsMap before it's defined.
45828
- widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
45829
- widgetEnabled: optional(nullable(boolean)),
45830
- immutableWidgets: optional(nullable(boolean)),
45831
- images: record(string, object({
45832
- width: number,
45833
- height: number
45834
- }))
45835
- });
45836
- const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
45837
-
45838
- const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
45839
- gradedGroups: array(parseGradedGroupWidgetOptions)
45840
- }));
45841
-
45842
- /**
45843
- * discriminatedUnion() should be preferred over union() when parsing a
45844
- * discriminated union type, because discriminatedUnion() produces more
45845
- * understandable failure messages. It takes the discriminant as the source of
45846
- * truth for which variant is to be parsed, and expects the other data to match
45847
- * that variant.
45848
- */
45849
- function discriminatedUnionOn(discriminantKey) {
45850
- const noMoreBranches = (raw, ctx) => {
45851
- if (!isObject(raw)) {
45852
- return ctx.failure("object", raw);
45853
- }
45854
- return ctx.forSubtree(discriminantKey).failure("a valid value", raw[discriminantKey]);
45855
- };
45856
- return new DiscriminatedUnionBuilder(discriminantKey, noMoreBranches);
45857
- }
45858
- class DiscriminatedUnionBuilder {
45859
- constructor(discriminantKey, parser) {
45860
- this.discriminantKey = discriminantKey;
45861
- this.parser = parser;
45862
- }
45863
- withBranch(discriminantValue, parseNewVariant) {
45864
- const parseNewBranch = discriminatedUnionBranch(this.discriminantKey, discriminantValue, parseNewVariant, this.parser);
45865
- return new DiscriminatedUnionBuilder(this.discriminantKey, parseNewBranch);
45866
- }
45867
- }
45868
- function discriminatedUnionBranch(discriminantKey, discriminantValue, parseVariant, parseOtherBranches) {
45869
- return (raw, ctx) => {
45870
- if (!isObject(raw)) {
45871
- return ctx.failure("object", raw);
45872
- }
45873
- if (raw[discriminantKey] === discriminantValue) {
45874
- return parseVariant(raw, ctx);
45875
- }
45876
- return parseOtherBranches(raw, ctx);
45877
- };
45878
- }
45879
-
45880
- const pairOfNumbers$3 = pair(number, number);
45881
- const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
45882
- const parseGrapherWidget = parseWidget(constant("grapher"), object({
45883
- availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
45884
- correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
45885
- type: constant("absolute_value"),
45886
- coords: nullable(pairOfPoints)
45887
- })).withBranch("exponential", object({
45888
- type: constant("exponential"),
45889
- asymptote: pairOfPoints,
45890
- coords: nullable(pairOfPoints)
45891
- })).withBranch("linear", object({
45892
- type: constant("linear"),
45893
- coords: nullable(pairOfPoints)
45894
- })).withBranch("logarithm", object({
45895
- type: constant("logarithm"),
45896
- asymptote: pairOfPoints,
45897
- coords: nullable(pairOfPoints)
45898
- })).withBranch("quadratic", object({
45899
- type: constant("quadratic"),
45900
- coords: nullable(pairOfPoints)
45901
- })).withBranch("sinusoid", object({
45902
- type: constant("sinusoid"),
45903
- coords: nullable(pairOfPoints)
45904
- })).withBranch("tangent", object({
45905
- type: constant("tangent"),
45906
- coords: nullable(pairOfPoints)
45907
- })).parser,
45908
- graph: object({
45909
- backgroundImage: object({
45910
- bottom: optional(number),
45911
- height: optional(number),
45912
- left: optional(number),
45913
- scale: optional(number),
45914
- url: optional(nullable(string)),
45915
- width: optional(number)
45916
- }),
45917
- box: optional(pairOfNumbers$3),
45918
- editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
45919
- gridStep: optional(pairOfNumbers$3),
45920
- labels: pair(string, string),
45921
- markings: enumeration("graph", "none", "grid"),
45922
- range: pair(pairOfNumbers$3, pairOfNumbers$3),
45923
- rulerLabel: constant(""),
45924
- rulerTicks: number,
45925
- showProtractor: optional(boolean),
45926
- showRuler: optional(boolean),
45927
- showTooltips: optional(boolean),
45928
- snapStep: optional(pairOfNumbers$3),
45929
- step: pairOfNumbers$3,
45930
- valid: optional(union(boolean).or(string).parser)
45931
- })
45932
- }));
45933
-
45934
- const parseGroupWidget = parseWidget(constant("group"),
45935
- // This module has an import cycle with parsePerseusRenderer.
45936
- // The anonymous function below ensures that we don't try to access
45937
- // parsePerseusRenderer before it's defined.
45938
- (rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
45939
-
45940
- const parseIframeWidget = parseWidget(constant("iframe"), object({
45941
- url: string,
45942
- settings: optional(array(object({
45943
- name: string,
45944
- value: string
45945
- }))),
45946
- width: union(number).or(string).parser,
45947
- height: union(number).or(string).parser,
45948
- allowFullScreen: defaulted(boolean, () => false),
45949
- allowTopNavigation: optional(boolean),
45950
- static: defaulted(boolean, () => false)
45951
- }));
45952
-
45953
- const stringToNumber = (rawValue, ctx) => {
45954
- if (typeof rawValue === "number") {
45955
- return ctx.success(rawValue);
45956
- }
45957
- const parsedNumber = +rawValue;
45958
- if (rawValue === "" || isNaN(parsedNumber)) {
45959
- return ctx.failure("a number or numeric string", rawValue);
45960
- }
45961
- return ctx.success(parsedNumber);
45962
- };
45963
-
45964
- function emptyToZero(x) {
45965
- return x === "" ? 0 : x;
45966
- }
45967
- const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
45968
- // In this specific case, empty string is equivalent to zero. An empty
45969
- // string parses to either NaN (using parseInt) or 0 (using unary +) and
45970
- // CSS will treat NaN as invalid and default to 0 instead.
45971
- .then(convert(emptyToZero)).then(stringToNumber).parser;
45972
- const parsePerseusImageBackground = object({
45973
- url: optional(nullable(string)),
45974
- width: optional(imageDimensionToNumber),
45975
- height: optional(imageDimensionToNumber),
45976
- top: optional(imageDimensionToNumber),
45977
- left: optional(imageDimensionToNumber),
45978
- bottom: optional(imageDimensionToNumber),
45979
- scale: optional(imageDimensionToNumber)
45980
- });
45981
-
45982
- const pairOfNumbers$2 = pair(number, number);
45983
- const parseImageWidget = parseWidget(constant("image"), object({
45984
- title: optional(string),
45985
- caption: optional(string),
45986
- alt: optional(string),
45987
- backgroundImage: parsePerseusImageBackground,
45988
- static: optional(boolean),
45989
- labels: optional(array(object({
45990
- content: string,
45991
- alignment: string,
45992
- coordinates: array(number)
45993
- }))),
45994
- range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
45995
- box: optional(pairOfNumbers$2)
45996
- }));
45997
-
45998
- const booleanToString = (rawValue, ctx) => {
45999
- if (typeof rawValue === "boolean") {
46000
- return ctx.success(String(rawValue));
46001
- }
46002
- return ctx.failure("boolean", rawValue);
46003
- };
46004
- const parseInputNumberWidget = parseWidget(constant("input-number"), object({
46005
- answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
46006
- inexact: optional(boolean),
46007
- maxError: optional(union(number).or(string).parser),
46008
- rightAlign: optional(boolean),
46009
- simplify: enumeration("required", "optional", "enforced"),
46010
- size: enumeration("normal", "small"),
46011
- // TODO(benchristel): there are some content items where value is a
46012
- // boolean, even though that makes no sense. We should figure out if
46013
- // those content items are actually published anywhere, and consider
46014
- // updating them.
46015
- value: union(number).or(string).or(booleanToString).parser,
46016
- customKeypad: optional(boolean)
46017
- }));
46018
-
46019
- const pairOfNumbers$1 = pair(number, number);
46020
- const stringOrEmpty = defaulted(string, () => "");
46021
- const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
46022
- const parseFunctionElement = object({
46023
- type: constant("function"),
46024
- key: parseKey,
46025
- options: object({
46026
- value: string,
46027
- funcName: string,
46028
- rangeMin: string,
46029
- rangeMax: string,
46030
- color: string,
46031
- strokeDasharray: string,
46032
- strokeWidth: number
46033
- })
46034
- });
46035
- const parseLabelElement = object({
46036
- type: constant("label"),
46037
- key: parseKey,
46038
- options: object({
46039
- label: string,
46040
- color: string,
46041
- coordX: string,
46042
- coordY: string
46043
- })
46044
- });
46045
- const parseLineElement = object({
46046
- type: constant("line"),
46047
- key: parseKey,
46048
- options: object({
46049
- color: string,
46050
- startX: string,
46051
- startY: string,
46052
- endX: string,
46053
- endY: string,
46054
- strokeDasharray: string,
46055
- strokeWidth: number,
46056
- arrows: string
46057
- })
46058
- });
46059
- const parseMovableLineElement = object({
46060
- type: constant("movable-line"),
46061
- key: parseKey,
46062
- options: object({
46063
- startX: string,
46064
- startY: string,
46065
- startSubscript: number,
46066
- endX: string,
46067
- endY: string,
46068
- endSubscript: number,
46069
- constraint: string,
46070
- snap: number,
46071
- constraintFn: string,
46072
- constraintXMin: string,
46073
- constraintXMax: string,
46074
- constraintYMin: string,
46075
- constraintYMax: string
46076
- })
46077
- });
46078
- const parseMovablePointElement = object({
46079
- type: constant("movable-point"),
46080
- key: parseKey,
46081
- options: object({
46082
- startX: string,
46083
- startY: string,
46084
- varSubscript: number,
46085
- constraint: string,
46086
- snap: number,
46087
- constraintFn: string,
46088
- constraintXMin: stringOrEmpty,
46089
- constraintXMax: stringOrEmpty,
46090
- constraintYMin: stringOrEmpty,
46091
- constraintYMax: stringOrEmpty
46092
- })
46093
- });
46094
- const parseParametricElement = object({
46095
- type: constant("parametric"),
46096
- key: parseKey,
46097
- options: object({
46098
- x: string,
46099
- y: string,
46100
- rangeMin: string,
46101
- rangeMax: string,
46102
- color: string,
46103
- strokeDasharray: string,
46104
- strokeWidth: number
46105
- })
46106
- });
46107
- const parsePointElement = object({
46108
- type: constant("point"),
46109
- key: parseKey,
46110
- options: object({
46111
- color: string,
46112
- coordX: string,
46113
- coordY: string
46114
- })
46115
- });
46116
- const parseRectangleElement = object({
46117
- type: constant("rectangle"),
46118
- key: parseKey,
46119
- options: object({
46120
- color: string,
46121
- coordX: string,
46122
- coordY: string,
46123
- width: string,
46124
- height: string
46125
- })
46126
- });
46127
- const parseInteractionWidget = parseWidget(constant("interaction"), object({
46128
- static: defaulted(boolean, () => false),
46129
- graph: object({
46130
- editableSettings: optional(array(enumeration("canvas", "graph"))),
46131
- box: pairOfNumbers$1,
46132
- labels: array(string),
46133
- range: pair(pairOfNumbers$1, pairOfNumbers$1),
46134
- gridStep: pairOfNumbers$1,
46135
- markings: enumeration("graph", "grid", "none"),
46136
- snapStep: optional(pairOfNumbers$1),
46137
- valid: optional(union(boolean).or(string).parser),
46138
- backgroundImage: optional(parsePerseusImageBackground),
46139
- showProtractor: optional(boolean),
46140
- showRuler: optional(boolean),
46141
- rulerLabel: optional(string),
46142
- rulerTicks: optional(number),
46143
- tickStep: pairOfNumbers$1
46144
- }),
46145
- 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)
46146
- }));
46147
-
46148
- // Used to represent 2-D points and ranges
46149
- const pairOfNumbers = pair(number, number);
46150
- const parsePerseusGraphTypeAngle = object({
46151
- type: constant("angle"),
46152
- showAngles: optional(boolean),
46153
- allowReflexAngles: optional(boolean),
46154
- angleOffsetDeg: optional(number),
46155
- snapDegrees: optional(number),
46156
- match: optional(constant("congruent")),
46157
- coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
46158
- startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
46159
- });
46160
- const parsePerseusGraphTypeCircle = object({
46161
- type: constant("circle"),
46162
- center: optional(pairOfNumbers),
46163
- radius: optional(number),
46164
- startCoords: optional(object({
46165
- center: pairOfNumbers,
46166
- radius: number
46167
- })),
46168
- // TODO: remove coord? it's legacy.
46169
- coord: optional(pairOfNumbers)
46170
- });
46171
- const parsePerseusGraphTypeLinear = object({
46172
- type: constant("linear"),
46173
- coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
46174
- startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
46175
- // TODO: remove coord? it's legacy.
46176
- coord: optional(pairOfNumbers)
46177
- });
46178
- const parsePerseusGraphTypeLinearSystem = object({
46179
- type: constant("linear-system"),
46180
- // TODO(benchristel): default coords to empty array?
46181
- coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
46182
- startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
46183
- // TODO: remove coord? it's legacy.
46184
- coord: optional(pairOfNumbers)
46185
- });
46186
- const parsePerseusGraphTypeNone = object({
46187
- type: constant("none")
46188
- });
46189
- const parsePerseusGraphTypePoint = object({
46190
- type: constant("point"),
46191
- numPoints: optional(union(number).or(constant("unlimited")).parser),
46192
- coords: optional(nullable(array(pairOfNumbers))),
46193
- startCoords: optional(array(pairOfNumbers)),
46194
- // TODO: remove coord? it's legacy.
46195
- coord: optional(pairOfNumbers)
46196
- });
46197
- const parsePerseusGraphTypePolygon = object({
46198
- type: constant("polygon"),
46199
- numSides: optional(union(number).or(constant("unlimited")).parser),
46200
- showAngles: optional(boolean),
46201
- showSides: optional(boolean),
46202
- snapTo: optional(enumeration("grid", "angles", "sides")),
46203
- match: optional(enumeration("similar", "congruent", "approx", "exact")),
46204
- startCoords: optional(array(pairOfNumbers)),
46205
- // TODO: remove coord? it's legacy.
46206
- coord: optional(pairOfNumbers)
46207
- });
46208
- const parsePerseusGraphTypeQuadratic = object({
46209
- type: constant("quadratic"),
46210
- coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
46211
- startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
46212
- // TODO: remove coord? it's legacy.
46213
- coord: optional(pairOfNumbers)
46214
- });
46215
- const parsePerseusGraphTypeRay = object({
46216
- type: constant("ray"),
46217
- coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
46218
- startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
46219
- // TODO: remove coord? it's legacy.
46220
- coord: optional(pairOfNumbers)
46221
- });
46222
- const parsePerseusGraphTypeSegment = object({
46223
- type: constant("segment"),
46224
- // TODO(benchristel): default numSegments?
46225
- numSegments: optional(number),
46226
- coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
46227
- startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
46228
- // TODO: remove coord? it's legacy.
46229
- coord: optional(pairOfNumbers)
46230
- });
46231
- const parsePerseusGraphTypeSinusoid = object({
46232
- type: constant("sinusoid"),
46233
- coords: optional(nullable(array(pairOfNumbers))),
46234
- startCoords: optional(array(pairOfNumbers)),
46235
- // TODO: remove coord? it's legacy.
46236
- coord: optional(pairOfNumbers)
46237
- });
46238
- 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;
46239
- const parseLockedFigureColor = enumeration(...perseusCore.lockedFigureColorNames);
46240
- const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
46241
- const parseLockedLineStyle = enumeration("solid", "dashed");
46242
- const parseLockedLabelType = object({
46243
- type: constant("label"),
46244
- coord: pairOfNumbers,
46245
- text: string,
46246
- color: parseLockedFigureColor,
46247
- size: enumeration("small", "medium", "large")
46248
- });
46249
- const parseLockedPointType = object({
46250
- type: constant("point"),
46251
- coord: pairOfNumbers,
46252
- color: parseLockedFigureColor,
46253
- filled: boolean,
46254
- // TODO(benchristel): default labels to empty array?
46255
- labels: optional(array(parseLockedLabelType)),
46256
- ariaLabel: optional(string)
46257
- });
46258
- const parseLockedLineType = object({
46259
- type: constant("line"),
46260
- kind: enumeration("line", "ray", "segment"),
46261
- points: pair(parseLockedPointType, parseLockedPointType),
46262
- color: parseLockedFigureColor,
46263
- lineStyle: parseLockedLineStyle,
46264
- showPoint1: defaulted(boolean, () => false),
46265
- showPoint2: defaulted(boolean, () => false),
46266
- // TODO(benchristel): default labels to empty array?
46267
- labels: optional(array(parseLockedLabelType)),
46268
- ariaLabel: optional(string)
46269
- });
46270
- const parseLockedVectorType = object({
46271
- type: constant("vector"),
46272
- points: pair(pairOfNumbers, pairOfNumbers),
46273
- color: parseLockedFigureColor,
46274
- // TODO(benchristel): default labels to empty array?
46275
- labels: optional(array(parseLockedLabelType)),
46276
- ariaLabel: optional(string)
46277
- });
46278
- const parseLockedEllipseType = object({
46279
- type: constant("ellipse"),
46280
- center: pairOfNumbers,
46281
- radius: pairOfNumbers,
46282
- angle: number,
46283
- color: parseLockedFigureColor,
46284
- fillStyle: parseLockedFigureFillType,
46285
- strokeStyle: parseLockedLineStyle,
46286
- // TODO(benchristel): default labels to empty array?
46287
- labels: optional(array(parseLockedLabelType)),
46288
- ariaLabel: optional(string)
46289
- });
46290
- const parseLockedPolygonType = object({
46291
- type: constant("polygon"),
46292
- points: array(pairOfNumbers),
46293
- color: parseLockedFigureColor,
46294
- showVertices: boolean,
46295
- fillStyle: parseLockedFigureFillType,
46296
- strokeStyle: parseLockedLineStyle,
46297
- // TODO(benchristel): default labels to empty array?
46298
- labels: optional(array(parseLockedLabelType)),
46299
- ariaLabel: optional(string)
46300
- });
46301
- const parseLockedFunctionType = object({
46302
- type: constant("function"),
46303
- color: parseLockedFigureColor,
46304
- strokeStyle: parseLockedLineStyle,
46305
- equation: string,
46306
- directionalAxis: enumeration("x", "y"),
46307
- domain: optional(pairOfNumbers),
46308
- // TODO(benchristel): default labels to empty array?
46309
- labels: optional(array(parseLockedLabelType)),
46310
- ariaLabel: optional(string)
46311
- });
46312
- 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;
46313
- const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
46314
- step: pairOfNumbers,
46315
- // TODO(benchristel): rather than making gridStep and snapStep
46316
- // optional, we should duplicate the defaulting logic from the
46317
- // InteractiveGraph component. See parse-perseus-json/README.md for
46318
- // why.
46319
- gridStep: optional(pairOfNumbers),
46320
- snapStep: optional(pairOfNumbers),
46321
- backgroundImage: optional(parsePerseusImageBackground),
46322
- markings: enumeration("graph", "grid", "none"),
46323
- labels: optional(array(string)),
46324
- showProtractor: boolean,
46325
- showRuler: optional(boolean),
46326
- showTooltips: optional(boolean),
46327
- rulerLabel: optional(string),
46328
- rulerTicks: optional(number),
46329
- range: pair(pairOfNumbers, pairOfNumbers),
46330
- // NOTE(benchristel): I copied the default graph from
46331
- // interactive-graph.tsx. See the parse-perseus-json/README.md for
46332
- // an explanation of why we want to duplicate the default here.
46333
- graph: defaulted(parsePerseusGraphType, () => ({
46334
- type: "linear"
46335
- })),
46336
- correct: parsePerseusGraphType,
46337
- // TODO(benchristel): default lockedFigures to empty array
46338
- lockedFigures: optional(array(parseLockedFigure)),
46339
- fullGraphLabel: optional(string),
46340
- fullGraphAriaDescription: optional(string)
46341
- }));
46342
-
46343
- const parseLabelImageWidget = parseWidget(constant("label-image"), object({
46344
- choices: array(string),
46345
- imageUrl: string,
46346
- imageAlt: string,
46347
- imageHeight: number,
46348
- imageWidth: number,
46349
- markers: array(object({
46350
- answers: array(string),
46351
- label: string,
46352
- x: number,
46353
- y: number
46354
- })),
46355
- hideChoicesFromInstructions: boolean,
46356
- multipleAnswers: boolean,
46357
- static: defaulted(boolean, () => false)
46358
- }));
46359
-
46360
- const parseMatcherWidget = parseWidget(constant("matcher"), object({
46361
- labels: array(string),
46362
- left: array(string),
46363
- right: array(string),
46364
- orderMatters: boolean,
46365
- padding: boolean
46366
- }));
46367
-
46368
- const numberOrString = union(number).or(string).parser;
46369
- const numeric = pipeParsers(defaulted(numberOrString, () => NaN)).then(stringToNumber).parser;
46370
- const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
46371
- prefix: optional(string),
46372
- suffix: optional(string),
46373
- answers: array(array(numeric)),
46374
- cursorPosition: optional(array(number)),
46375
- matrixBoardSize: array(number),
46376
- static: optional(boolean)
46377
- }));
46378
-
46379
- const parseMeasurerWidget = parseWidget(constant("measurer"), object({
46380
- // The default value for image comes from measurer.tsx.
46381
- // See parse-perseus-json/README.md for why we want to duplicate the
46382
- // defaults here.
46383
- image: defaulted(parsePerseusImageBackground, () => ({
46384
- url: null,
46385
- top: 0,
46386
- left: 0
46387
- })),
46388
- showProtractor: boolean,
46389
- showRuler: boolean,
46390
- rulerLabel: string,
46391
- rulerTicks: number,
46392
- rulerPixels: number,
46393
- rulerLength: number,
46394
- box: pair(number, number),
46395
- // TODO(benchristel): static is not used. Remove it?
46396
- static: defaulted(boolean, () => false)
46397
- }));
46398
-
46399
- const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
46400
- widgetId: string,
46401
- rotationAngle: optional(number),
46402
- smiles: optional(string)
46403
- }));
46404
-
46405
- const emptyStringToNull = pipeParsers(constant("")).then(convert(() => null)).parser;
46406
- const parseNumberLineWidget = parseWidget(constant("number-line"), object({
46407
- range: array(number),
46408
- labelRange: array(nullable(union(number).or(emptyStringToNull).parser)),
46409
- labelStyle: string,
46410
- labelTicks: boolean,
46411
- isTickCtrl: optional(nullable(boolean)),
46412
- divisionRange: array(number),
46413
- numDivisions: optional(nullable(number)),
46414
- // NOTE(benchristel): I copied the default snapDivisions from
46415
- // number-line.tsx. See the parse-perseus-json/README.md for
46416
- // an explanation of why we want to duplicate the default here.
46417
- snapDivisions: defaulted(number, () => 2),
46418
- tickStep: optional(nullable(number)),
46419
- correctRel: optional(nullable(string)),
46420
- correctX: nullable(number),
46421
- initialX: optional(nullable(number)),
46422
- showTooltip: optional(boolean),
46423
- static: defaulted(boolean, () => false)
46424
- }));
46425
-
46426
- const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
46427
- const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
46428
- answers: array(object({
46429
- message: string,
46430
- // TODO(benchristel): value should never be null or undefined,
46431
- // but we have some content where it is anyway. If we backfill
46432
- // the data, simplify this.
46433
- value: optional(nullable(number)),
46434
- status: string,
46435
- answerForms: optional(array(parseMathFormat)),
46436
- strict: boolean,
46437
- maxError: optional(nullable(number)),
46438
- // TODO(benchristel): simplify should never be a boolean, but we
46439
- // have some content where it is anyway. If we ever backfill
46440
- // the data, we should simplify `simplify`.
46441
- simplify: optional(nullable(union(string).or(pipeParsers(boolean).then(convert(String)).parser).parser))
46442
- })),
46443
- labelText: optional(string),
46444
- size: string,
46445
- coefficient: defaulted(boolean, () => false),
46446
- rightAlign: optional(boolean),
46447
- static: defaulted(boolean, () => false),
46448
- answerForms: optional(array(object({
46449
- name: parseMathFormat,
46450
- simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
46451
- })))
46452
- }));
46453
-
46454
- // There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
46455
- // This wrapper ensures that we don't refer to parsePerseusRenderer before
46456
- // it's defined.
46457
- function parseRenderer(rawValue, ctx) {
46458
- return parsePerseusRenderer(rawValue, ctx);
46459
- }
46460
- const largeToAuto = (height, ctx) => {
46461
- if (height === "large") {
46462
- return ctx.success("auto");
46463
- }
46464
- return ctx.success(height);
46465
- };
46466
- const parseOrdererWidget = parseWidget(constant("orderer"), object({
46467
- options: defaulted(array(parseRenderer), () => []),
46468
- correctOptions: array(parseRenderer),
46469
- otherOptions: array(parseRenderer),
46470
- height: pipeParsers(enumeration("normal", "auto", "large")).then(largeToAuto).parser,
46471
- layout: defaulted(enumeration("horizontal", "vertical"), () => "horizontal")
46472
- }));
46473
-
46474
- const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
46475
- passageNumber: number,
46476
- referenceNumber: number,
46477
- summaryText: optional(string)
46478
- }));
46479
-
46480
- const parsePassageWidget = parseWidget(constant("passage"), object({
46481
- footnotes: defaulted(string, () => ""),
46482
- passageText: string,
46483
- passageTitle: defaulted(string, () => ""),
46484
- showLineNumbers: boolean,
46485
- static: defaulted(boolean, () => false)
46486
- }));
46487
-
46488
- const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
46489
- url: string,
46490
- description: string
46491
- }));
46492
-
46493
- const parsePlotterWidget = parseWidget(constant("plotter"), object({
46494
- labels: array(string),
46495
- categories: array(string),
46496
- type: enumeration(...perseusCore.plotterPlotTypes),
46497
- maxY: number,
46498
- // The default value for scaleY comes from plotter.tsx.
46499
- // See parse-perseus-json/README.md for why we want to duplicate the
46500
- // defaults here.
46501
- scaleY: defaulted(number, () => 1),
46502
- labelInterval: optional(nullable(number)),
46503
- // The default value for snapsPerLine comes from plotter.tsx.
46504
- // See parse-perseus-json/README.md for why we want to duplicate the
46505
- // defaults here.
46506
- snapsPerLine: defaulted(number, () => 2),
46507
- starting: array(number),
46508
- correct: array(number),
46509
- picUrl: optional(nullable(string)),
46510
- picSize: optional(nullable(number)),
46511
- picBoxHeight: optional(nullable(number)),
46512
- // NOTE(benchristel): I copied the default plotDimensions from
46513
- // plotter.tsx. See the parse-perseus-json/README.md for an explanation
46514
- // of why we want to duplicate the defaults here.
46515
- plotDimensions: defaulted(array(number), () => [380, 300])
46516
- }));
46517
-
46518
- const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
46519
- programID: string,
46520
- height: number
46521
- }));
46522
-
46523
- const parseRadioWidget = parseWidget(constant("radio"), object({
46524
- choices: array(object({
46525
- content: defaulted(string, () => ""),
46526
- clue: optional(string),
46527
- correct: optional(boolean),
46528
- isNoneOfTheAbove: optional(boolean),
46529
- // deprecated
46530
- // There is an import cycle between radio-widget.ts and
46531
- // widgets-map.ts. The anonymous function below ensures that we
46532
- // don't refer to parseWidgetsMap before it's defined.
46533
- widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
46534
- })),
46535
- hasNoneOfTheAbove: optional(boolean),
46536
- countChoices: optional(boolean),
46537
- randomize: optional(boolean),
46538
- multipleSelect: optional(boolean),
46539
- deselectEnabled: optional(boolean),
46540
- // deprecated
46541
- onePerLine: optional(boolean),
46542
- // deprecated
46543
- displayCount: optional(any),
46544
- // v0 props
46545
- // `noneOfTheAbove` is still in use (but only set to `false`).
46546
- noneOfTheAbove: optional(constant(false))
46547
- }));
46548
-
46549
- const parseSorterWidget = parseWidget(constant("sorter"), object({
46550
- correct: array(string),
46551
- padding: boolean,
46552
- layout: enumeration("horizontal", "vertical")
46553
- }));
46554
-
46555
- const parseTableWidget = parseWidget(constant("table"), object({
46556
- headers: array(string),
46557
- rows: number,
46558
- columns: number,
46559
- answers: array(array(string))
46560
- }));
46561
-
46562
- const parseVideoWidget = parseWidget(constant("video"), object({
46563
- location: string,
46564
- static: optional(boolean)
46565
- }));
46566
-
46567
- const parseWidgetsMap = (rawValue, ctx) => {
46568
- if (!isObject(rawValue)) {
46569
- return ctx.failure("PerseusWidgetsMap", rawValue);
46570
- }
46571
- const widgetsMap = {};
46572
- for (const key of Object.keys(rawValue)) {
46573
- // parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
46574
- // but it's the only way I could find to make TypeScript check the key
46575
- // against the widget type.
46576
- const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
46577
- if (isFailure(entryResult)) {
46578
- return entryResult;
46579
- }
46580
- }
46581
- return ctx.success(widgetsMap);
46582
- };
46583
- const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
46584
- let [id, widget] = _ref;
46585
- const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
46586
- if (isFailure(idComponentsResult)) {
46587
- return idComponentsResult;
46588
- }
46589
- const [type, n] = idComponentsResult.value;
46590
- function parseAndAssign(key, parse) {
46591
- const widgetResult = parse(widget, ctx);
46592
- if (isFailure(widgetResult)) {
46593
- return widgetResult;
46594
- }
46595
- widgetMap[key] = widgetResult.value;
46596
- return ctx.success(undefined);
46597
- }
46598
- switch (type) {
46599
- case "categorizer":
46600
- return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
46601
- case "cs-program":
46602
- return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
46603
- case "definition":
46604
- return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
46605
- case "dropdown":
46606
- return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
46607
- case "explanation":
46608
- return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
46609
- case "expression":
46610
- return parseAndAssign(`expression ${n}`, parseExpressionWidget);
46611
- case "grapher":
46612
- return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
46613
- case "group":
46614
- return parseAndAssign(`group ${n}`, parseGroupWidget);
46615
- case "graded-group":
46616
- return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
46617
- case "graded-group-set":
46618
- return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
46619
- case "iframe":
46620
- return parseAndAssign(`iframe ${n}`, parseIframeWidget);
46621
- case "image":
46622
- return parseAndAssign(`image ${n}`, parseImageWidget);
46623
- case "input-number":
46624
- return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
46625
- case "interaction":
46626
- return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
46627
- case "interactive-graph":
46628
- return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
46629
- case "label-image":
46630
- return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
46631
- case "matcher":
46632
- return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
46633
- case "matrix":
46634
- return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
46635
- case "measurer":
46636
- return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
46637
- case "molecule-renderer":
46638
- return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
46639
- case "number-line":
46640
- return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
46641
- case "numeric-input":
46642
- return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
46643
- case "orderer":
46644
- return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
46645
- case "passage":
46646
- return parseAndAssign(`passage ${n}`, parsePassageWidget);
46647
- case "passage-ref":
46648
- return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
46649
- case "passage-ref-target":
46650
- // NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
46651
- // used in test content. See:
46652
- // https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
46653
- return parseAndAssign(`passage-ref-target ${n}`, any);
46654
- case "phet-simulation":
46655
- return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
46656
- case "plotter":
46657
- return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
46658
- case "python-program":
46659
- return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
46660
- case "radio":
46661
- return parseAndAssign(`radio ${n}`, parseRadioWidget);
46662
- case "sorter":
46663
- return parseAndAssign(`sorter ${n}`, parseSorterWidget);
46664
- case "table":
46665
- return parseAndAssign(`table ${n}`, parseTableWidget);
46666
- case "video":
46667
- return parseAndAssign(`video ${n}`, parseVideoWidget);
46668
- case "sequence":
46669
- // sequence is a deprecated widget type, and the corresponding
46670
- // widget component no longer exists.
46671
- return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
46672
- case "lights-puzzle":
46673
- return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
46674
- case "simulator":
46675
- return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
46676
- case "transformer":
46677
- return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
46678
- default:
46679
- if (getWidget(type)) {
46680
- // @ts-expect-error - 'type' is not a valid widget type
46681
- return parseAndAssign(`${type} ${n}`, any);
46682
- }
46683
- return ctx.failure("a valid widget type", type);
46684
- }
46685
- };
46686
- const parseDeprecatedWidget = parseWidget(
46687
- // Ignore the incoming widget type and hardcode "deprecated-standin"
46688
- (_, ctx) => ctx.success("deprecated-standin"),
46689
- // Allow any widget options
46690
- object({}));
46691
- const parseStringToPositiveInt = (rawValue, ctx) => {
46692
- if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
46693
- return ctx.failure("a string representing a positive integer", rawValue);
46694
- }
46695
- return ctx.success(+rawValue);
46696
- };
46697
- const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
46698
-
46699
- const parsePerseusRenderer = defaulted(object({
46700
- // TODO(benchristel): content is also defaulted to empty string in
46701
- // renderer.tsx. See if we can remove one default or the other.
46702
- content: defaulted(string, () => ""),
46703
- // This module has an import cycle with parseWidgetsMap, because the
46704
- // `group` widget can contain another renderer.
46705
- // The anonymous function below ensures that we don't try to access
46706
- // parseWidgetsMap before it's defined.
46707
- widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
46708
- metadata: optional(array(string)),
46709
- images: parseImages
46710
- }),
46711
- // Default value
46712
- () => ({
46713
- content: "",
46714
- widgets: {},
46715
- images: {}
46716
- }));
46717
-
46718
- const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
46719
-
46720
- const parseHint = object({
46721
- replace: optional(boolean),
46722
- content: string,
46723
- widgets: defaulted(parseWidgetsMap, () => ({})),
46724
- metadata: optional(array(string)),
46725
- images: parseImages
46726
- });
46727
-
46728
- const parsePerseusItem$1 = object({
46729
- question: parsePerseusRenderer,
46730
- hints: defaulted(array(parseHint), () => []),
46731
- answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...perseusCore.ItemExtras), boolean)).parser,
46732
- itemDataVersion: optional(object({
46733
- major: number,
46734
- minor: number
46735
- })),
46736
- // Deprecated field
46737
- answer: any
46738
- });
46739
-
46740
- // Some answerAreas have extra fields, like:
46741
- //
46742
- // "answerArea": {
46743
- // "type": "multiple",
46744
- // "options": {
46745
- // "content": "",
46746
- // "images": {},
46747
- // "widgets": {}
46748
- // }
46749
- // }
46750
- //
46751
- // The "type" and "options" fields don't seem to be used anywhere. This
46752
- // migration function removes them.
46753
- function migrateAnswerArea(rawValue, ctx) {
46754
- const {
46755
- type: _,
46756
- options: __,
46757
- ...rest
46758
- } = rawValue;
46759
- return ctx.success(rest);
46760
- }
46761
-
46762
- /**
46763
- * Helper to parse PerseusItem JSON
46764
- * Why not just use JSON.parse? We want:
46765
- * - To make sure types are correct
46766
- * - To give us a central place to validate/transform output if needed
46767
- * @deprecated - use parseAndMigratePerseusItem instead
46768
- * @param {string} json - the stringified PerseusItem JSON
46769
- * @returns {PerseusItem} the parsed PerseusItem object
46770
- */
46771
- function parsePerseusItem(json) {
46772
- // Try to block a cheating vector which relies on monkey-patching
46773
- // JSON.parse
46774
- if (isRealJSONParse(JSON.parse)) {
46775
- return JSON.parse(json);
46776
- }
46777
- throw new Error("Something went wrong.");
46778
- }
46779
- /**
46780
- * Parses a PerseusItem from a JSON string, migrates old formats to the latest
46781
- * schema, and runtime-typechecks the result. Use this to parse assessmentItem
46782
- * data.
46783
- *
46784
- * @returns a {@link Result} of the parsed PerseusItem. If the result is a
46785
- * failure, it will contain an error message describing where in the tree
46786
- * parsing failed.
46787
- * @throws SyntaxError if the argument is not well-formed JSON.
46788
- */
46789
- function parseAndMigratePerseusItem(json) {
46790
- throwErrorIfCheatingDetected();
46791
- const object = JSON.parse(json);
46792
- const result = parse(object, parsePerseusItem$1);
46793
- if (isFailure(result)) {
46794
- return failure({
46795
- message: result.detail,
46796
- invalidObject: object
46797
- });
46798
- }
46799
- return result;
46800
- }
46801
-
46802
- /**
46803
- * Parses a PerseusArticle from a JSON string, migrates old formats to the
46804
- * latest schema, and runtime-typechecks the result.
46805
- *
46806
- * @returns a {@link Result} of the parsed PerseusArticle. If the result is a
46807
- * failure, it will contain an error message describing where in the tree
46808
- * parsing failed.
46809
- * @throws SyntaxError if the argument is not well-formed JSON.
46810
- */
46811
- function parseAndMigratePerseusArticle(json) {
46812
- throwErrorIfCheatingDetected();
46813
- const object = JSON.parse(json);
46814
- const result = parse(object, parsePerseusArticle);
46815
- if (isFailure(result)) {
46816
- return failure({
46817
- message: result.detail,
46818
- invalidObject: object
46819
- });
46820
- }
46821
- return result;
46822
- }
46823
-
46824
- /**
46825
- * Tries to block a cheating vector that relies on monkey-patching JSON.parse.
46826
- */
46827
- // TODO(LEMS-2331): delete this function once server-side scoring is done.
46828
- function throwErrorIfCheatingDetected() {
46829
- if (!isRealJSONParse(JSON.parse)) {
46830
- throw new Error("Something went wrong.");
46831
- }
46832
- }
46833
-
46834
45555
  const genericPerseusItemData = {
46835
45556
  question: {
46836
45557
  content: "",
@@ -47052,6 +45773,26 @@ const ScoringUtil = {
47052
45773
  * Types
47053
45774
  */
47054
45775
 
45776
+ Object.defineProperty(exports, 'isFailure', {
45777
+ enumerable: true,
45778
+ get: function () { return perseusCore.isFailure; }
45779
+ });
45780
+ Object.defineProperty(exports, 'isSuccess', {
45781
+ enumerable: true,
45782
+ get: function () { return perseusCore.isSuccess; }
45783
+ });
45784
+ Object.defineProperty(exports, 'parseAndMigratePerseusArticle', {
45785
+ enumerable: true,
45786
+ get: function () { return perseusCore.parseAndMigratePerseusArticle; }
45787
+ });
45788
+ Object.defineProperty(exports, 'parseAndMigratePerseusItem', {
45789
+ enumerable: true,
45790
+ get: function () { return perseusCore.parseAndMigratePerseusItem; }
45791
+ });
45792
+ Object.defineProperty(exports, 'parsePerseusItem', {
45793
+ enumerable: true,
45794
+ get: function () { return perseusCore.parsePerseusItem; }
45795
+ });
47055
45796
  exports.ApiOptions = ApiOptions;
47056
45797
  exports.ArticleRenderer = ArticleRenderer;
47057
45798
  exports.BaseRadio = BaseRadio;
@@ -47077,7 +45818,6 @@ exports.PerseusI18nContext = PerseusI18nContext;
47077
45818
  exports.PerseusI18nContextProvider = PerseusI18nContextProvider;
47078
45819
  exports.PerseusMarkdown = PerseusMarkdown;
47079
45820
  exports.PlotterWidget = Plotter$1;
47080
- exports.QUESTION_WIDGETS = QUESTION_WIDGETS;
47081
45821
  exports.Radio = Radio;
47082
45822
  exports.Renderer = Renderer;
47083
45823
  exports.ScoringUtil = ScoringUtil;
@@ -47086,7 +45826,6 @@ exports.TableWidget = Table$1;
47086
45826
  exports.Util = Util;
47087
45827
  exports.WIDGET_PROP_DENYLIST = denylist;
47088
45828
  exports.Widgets = widgets$1;
47089
- exports.addWidget = addWidget;
47090
45829
  exports.apiVersion = apiVersion;
47091
45830
  exports.bodyXsmallBold = bodyXsmallBold;
47092
45831
  exports.components = components;
@@ -47117,8 +45856,6 @@ exports.getSegmentCoords = getSegmentCoords;
47117
45856
  exports.getSinusoidCoords = getSinusoidCoords;
47118
45857
  exports.getValidWidgetIds = getValidWidgetIds;
47119
45858
  exports.getWidgetFromWidgetMap = getWidgetFromWidgetMap;
47120
- exports.getWidgetIdsFromContent = getWidgetIdsFromContent;
47121
- exports.getWidgetIdsFromContentByType = getWidgetIdsFromContentByType;
47122
45859
  exports.getWidgetTypeByWidgetId = getWidgetTypeByWidgetId;
47123
45860
  exports.getWidgetsFromWidgetMap = getWidgetsFromWidgetMap;
47124
45861
  exports.getWidgetsMapFromItemData = getWidgetsMapFromItemData;
@@ -47127,9 +45864,7 @@ exports.iconTrash = iconTrash;
47127
45864
  exports.init = init;
47128
45865
  exports.injectWidgets = injectWidgets;
47129
45866
  exports.interactiveSizes = interactiveSizes$1;
47130
- exports.isFailure = isFailure;
47131
45867
  exports.isItemRenderableByVersion = isItemRenderableByVersion;
47132
- exports.isSuccess = isSuccess;
47133
45868
  exports.isWidgetIdInContent = isWidgetIdInContent;
47134
45869
  exports.isWrongAnswerSupported = isWrongAnswerSupported;
47135
45870
  exports.itemDataVersion = itemDataVersion;
@@ -47138,10 +45873,7 @@ exports.libVersion = libVersion;
47138
45873
  exports.makeSafeUrl = makeSafeUrl;
47139
45874
  exports.mathOnlyParser = mathOnlyParser;
47140
45875
  exports.mediaQueries = mediaQueries;
47141
- exports.parseAndMigratePerseusArticle = parseAndMigratePerseusArticle;
47142
- exports.parseAndMigratePerseusItem = parseAndMigratePerseusItem;
47143
45876
  exports.parseDataFromJSONP = parseDataFromJSONP;
47144
- exports.parsePerseusItem = parsePerseusItem;
47145
45877
  exports.preprocessTex = preprocessTex;
47146
45878
  exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
47147
45879
  exports.scorePerseusItem = scorePerseusItem;