@khanacademy/perseus 49.1.7 → 49.2.1

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 (30) hide show
  1. package/dist/components/math-input.d.ts +2 -1
  2. package/dist/es/index.css +6 -0
  3. package/dist/es/index.js +277 -147
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/es/strings.js +16 -12
  6. package/dist/es/strings.js.map +1 -1
  7. package/dist/index.css +6 -0
  8. package/dist/index.js +277 -138
  9. package/dist/index.js.map +1 -1
  10. package/dist/perseus-types.d.ts +12 -9
  11. package/dist/server-item-renderer.d.ts +1 -1
  12. package/dist/strings.d.ts +6 -5
  13. package/dist/strings.js +16 -12
  14. package/dist/strings.js.map +1 -1
  15. package/dist/types.d.ts +2 -5
  16. package/dist/util/parse-perseus-json/general-purpose-parsers/convert.d.ts +2 -0
  17. package/dist/util/parse-perseus-json/general-purpose-parsers/discriminated-union.d.ts +21 -0
  18. package/dist/util/parse-perseus-json/general-purpose-parsers/discriminated-union.typetest.d.ts +1 -0
  19. package/dist/util/parse-perseus-json/perseus-parsers/versioned-widget-options.d.ts +36 -0
  20. package/dist/widgets/expression/expression.d.ts +3 -3
  21. package/dist/widgets/interactive-graphs/graph-locked-labels-layer.d.ts +1 -3
  22. package/dist/widgets/interactive-graphs/graph-locked-layer.d.ts +0 -2
  23. package/dist/widgets/interactive-graphs/locked-figures/locked-ellipse.d.ts +1 -5
  24. package/dist/widgets/interactive-graphs/locked-figures/locked-function.d.ts +1 -5
  25. package/dist/widgets/interactive-graphs/locked-figures/locked-line.d.ts +0 -2
  26. package/dist/widgets/interactive-graphs/locked-figures/locked-point.d.ts +1 -5
  27. package/dist/widgets/interactive-graphs/locked-figures/locked-polygon.d.ts +1 -5
  28. package/dist/widgets/interactive-graphs/locked-figures/locked-vector.d.ts +1 -5
  29. package/dist/widgets/passage-ref/passage-ref.d.ts +7 -3
  30. package/package.json +2 -2
package/dist/es/index.js CHANGED
@@ -326,11 +326,11 @@ const mockStrings = {
326
326
  angleMeasure,
327
327
  vertexX,
328
328
  vertexY,
329
- isX,
330
- isY,
331
- tsX,
332
- tsY
333
- }) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the initial side at ${isX} comma ${isY} and a point on the terminal side at ${tsX} comma ${tsY}.`
329
+ startingSideX,
330
+ startingSideY,
331
+ endingSideX,
332
+ endingSideY
333
+ }) => `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}.`
334
334
  // The above strings are used for interactive graph SR descriptions.
335
335
  };
336
336
 
@@ -741,6 +741,9 @@ const mapButtonSets = buttonSets => {
741
741
  case "trig":
742
742
  keypadButtonSets.trigonometry = true;
743
743
  break;
744
+ case "scientific":
745
+ keypadButtonSets.scientific = true;
746
+ break;
744
747
  }
745
748
  });
746
749
  return keypadButtonSets;
@@ -3821,7 +3824,9 @@ ZoomServiceClass.prototype._scrollHandler = function (e) {
3821
3824
  }
3822
3825
  };
3823
3826
  ZoomServiceClass.prototype._keyHandler = function (e) {
3824
- if (e.keyCode === 27) {
3827
+ // 27: Esc, 13: Enter, 32: Space
3828
+ const keyCodes = [27, 13, 32];
3829
+ if (keyCodes.includes(e.keyCode)) {
3825
3830
  this._activeZoomClose();
3826
3831
  }
3827
3832
  };
@@ -3910,6 +3915,7 @@ Zoom.prototype.zoomImage = function () {
3910
3915
  }.bind(this);
3911
3916
  img.src = this._targetImage.src;
3912
3917
  img.alt = this._targetImage.alt;
3918
+ img.tabIndex = 0;
3913
3919
  this.$zoomedImage = $zoomedImage;
3914
3920
  };
3915
3921
  Zoom.prototype._zoomOriginal = function () {
@@ -17020,6 +17026,7 @@ class ImageLoader extends React.Component {
17020
17026
  }
17021
17027
  const staticUrl = getDependencies().staticUrl;
17022
17028
  return /*#__PURE__*/React.createElement("img", _extends({
17029
+ tabIndex: 0,
17023
17030
  src: staticUrl(src),
17024
17031
  onKeyUp: onKeyUp,
17025
17032
  onKeyDown: onKeyDown
@@ -22219,7 +22226,7 @@ function scoreNumericInput(userInput, rubric, strings) {
22219
22226
  // We may have received TeX; try to parse it before grading.
22220
22227
  // If `currentValue` is not TeX, this should be a no-op.
22221
22228
  const currentValue = ParseTex(userInput.currentValue);
22222
- const normalizedAnswerExpected = rubric.answers.filter(answer => answer.status === "correct").every(answer => answer.value !== undefined && Math.abs(answer.value) <= 1);
22229
+ const normalizedAnswerExpected = rubric.answers.filter(answer => answer.status === "correct").every(answer => answer.value != null && Math.abs(answer.value) <= 1);
22223
22230
 
22224
22231
  // The coefficient is an attribute of the widget
22225
22232
  let localValue = currentValue;
@@ -27455,6 +27462,7 @@ class Dropdown extends React.Component {
27455
27462
  }, this.props.visibleLabel), /*#__PURE__*/React.createElement(SingleSelect, {
27456
27463
  id: dropdownId,
27457
27464
  placeholder: "",
27465
+ className: "perseus-dropdown",
27458
27466
  onChange: value => this._handleChange(parseInt(value)),
27459
27467
  selectedValue: String(this.props.selected),
27460
27468
  disabled: this.props.apiOptions.readOnly,
@@ -32362,7 +32370,7 @@ const LegacyGrid = ({
32362
32370
  * "new-awesomeness": MyAwesomeNewWidget;
32363
32371
  *
32364
32372
  * // A deprecated widget
32365
- * "super-old-widget": AutoCorrectWidget;
32373
+ * "super-old-widget": DeprecatedStandinWidget;
32366
32374
  * }
32367
32375
  * }
32368
32376
  *
@@ -32556,46 +32564,28 @@ function LockedLabel(props) {
32556
32564
 
32557
32565
  function GraphLockedLabelsLayer(props) {
32558
32566
  const {
32559
- flags,
32560
32567
  lockedFigures
32561
32568
  } = props;
32562
32569
  return lockedFigures.map((figure, i) => {
32563
- var _flags$mafs, _flags$mafs2, _flags$mafs3, _flags$mafs4, _flags$mafs5, _flags$mafs6;
32570
+ var _figure$labels, _figure$points$0$labe, _figure$points$1$labe;
32564
32571
  if (figure.type === "label") {
32565
32572
  return /*#__PURE__*/React.createElement(LockedLabel, _extends({
32566
32573
  key: `label-${i}`
32567
32574
  }, figure));
32568
32575
  }
32569
- if (
32570
- // Point flag + point type
32571
- flags != null && (_flags$mafs = flags["mafs"]) != null && _flags$mafs["locked-point-labels"] && figure.type === "point" ||
32572
- // Line flag + line type
32573
- flags != null && (_flags$mafs2 = flags["mafs"]) != null && _flags$mafs2["locked-line-labels"] && figure.type === "line" ||
32574
- // Vector flag + vector type
32575
- flags != null && (_flags$mafs3 = flags["mafs"]) != null && _flags$mafs3["locked-vector-labels"] && figure.type === "vector" ||
32576
- // Ellipse flag + ellipse type
32577
- flags != null && (_flags$mafs4 = flags["mafs"]) != null && _flags$mafs4["locked-ellipse-labels"] && figure.type === "ellipse" ||
32578
- // Polygon flag + polygon type
32579
- flags != null && (_flags$mafs5 = flags["mafs"]) != null && _flags$mafs5["locked-polygon-labels"] && figure.type === "polygon" ||
32580
- // Function flag + function type
32581
- flags != null && (_flags$mafs6 = flags["mafs"]) != null && _flags$mafs6["locked-function-labels"] && figure.type === "function") {
32582
- var _figure$labels, _figure$points$0$labe, _figure$points$1$labe;
32583
- return /*#__PURE__*/React.createElement(React.Fragment, {
32584
- key: i
32585
- }, (_figure$labels = figure.labels) == null ? void 0 : _figure$labels.map((label, j) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
32586
- key: `${i}-label-${j}`
32587
- }, label))), figure.type === "line" && /*#__PURE__*/React.createElement(React.Fragment, null, (_figure$points$0$labe = figure.points[0].labels) == null ? void 0 : _figure$points$0$labe.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
32588
- key: `locked-figure-${i}-point-1-label-${k}`
32589
- }, label))), (_figure$points$1$labe = figure.points[1].labels) == null ? void 0 : _figure$points$1$labe.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
32590
- key: `locked-figure-${i}-point-2-label-${k}`
32591
- }, label)))));
32592
- }
32593
- return null;
32576
+ return /*#__PURE__*/React.createElement(React.Fragment, {
32577
+ key: i
32578
+ }, (_figure$labels = figure.labels) == null ? void 0 : _figure$labels.map((label, j) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
32579
+ key: `${i}-label-${j}`
32580
+ }, label))), figure.type === "line" && /*#__PURE__*/React.createElement(React.Fragment, null, (_figure$points$0$labe = figure.points[0].labels) == null ? void 0 : _figure$points$0$labe.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
32581
+ key: `locked-figure-${i}-point-1-label-${k}`
32582
+ }, label))), (_figure$points$1$labe = figure.points[1].labels) == null ? void 0 : _figure$points$1$labe.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
32583
+ key: `locked-figure-${i}-point-2-label-${k}`
32584
+ }, label)))));
32594
32585
  });
32595
32586
  }
32596
32587
 
32597
32588
  const LockedEllipse = props => {
32598
- var _flags$mafs;
32599
32589
  const {
32600
32590
  center,
32601
32591
  radius,
@@ -32603,10 +32593,9 @@ const LockedEllipse = props => {
32603
32593
  color: color$1,
32604
32594
  fillStyle,
32605
32595
  strokeStyle,
32606
- ariaLabel,
32607
- flags
32596
+ ariaLabel
32608
32597
  } = props;
32609
- const hasAria = ariaLabel && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-figures-aria"]);
32598
+ const hasAria = !!ariaLabel;
32610
32599
  return /*#__PURE__*/React.createElement("g", {
32611
32600
  className: "locked-ellipse",
32612
32601
  "aria-label": hasAria ? ariaLabel : undefined,
@@ -32631,7 +32620,6 @@ const LockedEllipse = props => {
32631
32620
  };
32632
32621
 
32633
32622
  const LockedFunction = props => {
32634
- var _props$flags;
32635
32623
  const [equation, setEquation] = useState();
32636
32624
  const {
32637
32625
  color,
@@ -32644,7 +32632,7 @@ const LockedFunction = props => {
32644
32632
  style: strokeStyle,
32645
32633
  domain
32646
32634
  };
32647
- const hasAria = props.ariaLabel && ((_props$flags = props.flags) == null || (_props$flags = _props$flags["mafs"]) == null ? void 0 : _props$flags["locked-figures-aria"]);
32635
+ const hasAria = !!props.ariaLabel;
32648
32636
  useEffect(() => {
32649
32637
  // Parsing the equation in a "useEffect" hook saves about 2ms each frame
32650
32638
  // when the learner is interacting with the graph (i.e. moving points).
@@ -32760,7 +32748,6 @@ function getArrayWithoutDuplicates(array) {
32760
32748
  }
32761
32749
 
32762
32750
  const LockedLine = props => {
32763
- var _flags$mafs;
32764
32751
  const {
32765
32752
  color: color$1,
32766
32753
  lineStyle,
@@ -32769,11 +32756,10 @@ const LockedLine = props => {
32769
32756
  showPoint1,
32770
32757
  showPoint2,
32771
32758
  ariaLabel,
32772
- flags,
32773
32759
  range
32774
32760
  } = props;
32775
32761
  const [point1, point2] = points;
32776
- const hasAria = ariaLabel && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-figures-aria"]);
32762
+ const hasAria = !!ariaLabel;
32777
32763
  let line;
32778
32764
  if (kind === "ray") {
32779
32765
  // Rays extend to the end of the graph in one direction.
@@ -32840,16 +32826,14 @@ const LockedLine = props => {
32840
32826
  };
32841
32827
 
32842
32828
  const LockedPoint = props => {
32843
- var _flags$mafs;
32844
32829
  const {
32845
- flags,
32846
32830
  color: color$1,
32847
32831
  coord,
32848
32832
  filled,
32849
32833
  ariaLabel
32850
32834
  } = props;
32851
32835
  const [x, y] = coord;
32852
- const hasAria = ariaLabel && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-figures-aria"]);
32836
+ const hasAria = !!ariaLabel;
32853
32837
  return /*#__PURE__*/React.createElement("g", {
32854
32838
  className: "locked-point",
32855
32839
  "aria-label": hasAria ? ariaLabel : undefined,
@@ -32868,7 +32852,6 @@ const LockedPoint = props => {
32868
32852
  };
32869
32853
 
32870
32854
  const LockedPolygon = props => {
32871
- var _props$flags;
32872
32855
  const {
32873
32856
  points,
32874
32857
  color: color$1,
@@ -32876,7 +32859,7 @@ const LockedPolygon = props => {
32876
32859
  fillStyle,
32877
32860
  strokeStyle
32878
32861
  } = props;
32879
- const hasAria = props.ariaLabel && ((_props$flags = props.flags) == null || (_props$flags = _props$flags["mafs"]) == null ? void 0 : _props$flags["locked-figures-aria"]);
32862
+ const hasAria = !!props.ariaLabel;
32880
32863
  return /*#__PURE__*/React.createElement("g", {
32881
32864
  className: "locked-polygon",
32882
32865
  "aria-label": hasAria ? props.ariaLabel : undefined,
@@ -32904,15 +32887,13 @@ const LockedPolygon = props => {
32904
32887
  };
32905
32888
 
32906
32889
  const LockedVector = props => {
32907
- var _flags$mafs;
32908
32890
  const {
32909
32891
  color,
32910
32892
  points,
32911
- ariaLabel,
32912
- flags
32893
+ ariaLabel
32913
32894
  } = props;
32914
32895
  const [tail, tip] = points;
32915
- const hasAria = ariaLabel && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-figures-aria"]);
32896
+ const hasAria = !!ariaLabel;
32916
32897
  return /*#__PURE__*/React.createElement("g", {
32917
32898
  className: "locked-vector",
32918
32899
  "aria-label": hasAria ? ariaLabel : undefined,
@@ -32926,7 +32907,6 @@ const LockedVector = props => {
32926
32907
 
32927
32908
  const GraphLockedLayer = props => {
32928
32909
  const {
32929
- flags,
32930
32910
  lockedFigures
32931
32911
  } = props;
32932
32912
  return /*#__PURE__*/React.createElement(React.Fragment, null, lockedFigures.map((figure, index) => {
@@ -32934,40 +32914,28 @@ const GraphLockedLayer = props => {
32934
32914
  case "point":
32935
32915
  return /*#__PURE__*/React.createElement(LockedPoint, _extends({
32936
32916
  key: `point-${index}`
32937
- }, figure, {
32938
- flags: flags
32939
- }));
32917
+ }, figure));
32940
32918
  case "line":
32941
32919
  return /*#__PURE__*/React.createElement(LockedLine, _extends({
32942
32920
  key: `line-${index}`,
32943
32921
  range: props.range
32944
- }, figure, {
32945
- flags: flags
32946
- }));
32922
+ }, figure));
32947
32923
  case "vector":
32948
32924
  return /*#__PURE__*/React.createElement(LockedVector, _extends({
32949
32925
  key: `vector-${index}`
32950
- }, figure, {
32951
- flags: flags
32952
- }));
32926
+ }, figure));
32953
32927
  case "ellipse":
32954
32928
  return /*#__PURE__*/React.createElement(LockedEllipse, _extends({
32955
32929
  key: `ellipse-${index}`
32956
- }, figure, {
32957
- flags: flags
32958
- }));
32930
+ }, figure));
32959
32931
  case "polygon":
32960
32932
  return /*#__PURE__*/React.createElement(LockedPolygon, _extends({
32961
32933
  key: `polygon-${index}`
32962
- }, figure, {
32963
- flags: flags
32964
- }));
32934
+ }, figure));
32965
32935
  case "function":
32966
32936
  return /*#__PURE__*/React.createElement(LockedFunction, _extends({
32967
32937
  key: `function-${index}`
32968
- }, figure, {
32969
- flags: flags
32970
- }));
32938
+ }, figure));
32971
32939
  case "label":
32972
32940
  // This is rendered outside the SVG element, since
32973
32941
  // TeX cannot be rendered inside an SVG.
@@ -34131,10 +34099,10 @@ function AngleGraph(props) {
34131
34099
  angleMeasure,
34132
34100
  vertexX: srFormatNumber(coords[1][X], locale),
34133
34101
  vertexY: srFormatNumber(coords[1][Y], locale),
34134
- isX: srFormatNumber(coords[2][X], locale),
34135
- isY: srFormatNumber(coords[2][Y], locale),
34136
- tsX: srFormatNumber(coords[0][X], locale),
34137
- tsY: srFormatNumber(coords[0][Y], locale)
34102
+ startingSideX: srFormatNumber(coords[2][X], locale),
34103
+ startingSideY: srFormatNumber(coords[2][Y], locale),
34104
+ endingSideX: srFormatNumber(coords[0][X], locale),
34105
+ endingSideY: srFormatNumber(coords[0][Y], locale)
34138
34106
  });
34139
34107
  const formatCoordinates = (x, y) => ({
34140
34108
  x: srFormatNumber(x, locale),
@@ -35438,7 +35406,6 @@ function useDraggablePx(args) {
35438
35406
  * - Interactive Graph Elements
35439
35407
  */
35440
35408
  const MafsGraph = props => {
35441
- var _props$flags;
35442
35409
  const {
35443
35410
  state,
35444
35411
  dispatch,
@@ -35575,11 +35542,9 @@ const MafsGraph = props => {
35575
35542
  })),
35576
35543
  // Only render the axis ticks and arrows if the markings are set to a full "graph"
35577
35544
  props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
35578
- flags: props.flags,
35579
35545
  lockedFigures: props.lockedFigures,
35580
35546
  range: state.range
35581
- }))), ((_props$flags = props.flags) == null || (_props$flags = _props$flags["mafs"]) == null ? void 0 : _props$flags["interactive-graph-locked-features-labels"]) && props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
35582
- flags: props.flags,
35547
+ }))), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
35583
35548
  lockedFigures: props.lockedFigures
35584
35549
  }), /*#__PURE__*/React.createElement(View, {
35585
35550
  style: {
@@ -45552,7 +45517,7 @@ const init = function init() {
45552
45517
 
45553
45518
  // This file is processed by a Rollup plugin (replace) to inject the production
45554
45519
  const libName = "@khanacademy/perseus";
45555
- const libVersion = "49.1.7";
45520
+ const libVersion = "49.2.1";
45556
45521
  addLibraryVersionToPerseusDebug(libName, libVersion);
45557
45522
 
45558
45523
  const apiVersion = {
@@ -47893,9 +47858,9 @@ function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
47893
47858
  const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
47894
47859
  items: array(string),
47895
47860
  categories: array(string),
47896
- randomizeItems: boolean,
47861
+ randomizeItems: defaulted(boolean, () => false),
47897
47862
  static: defaulted(boolean, () => false),
47898
- values: array(number),
47863
+ values: array(defaulted(number, () => 0)),
47899
47864
  highlightLint: optional(boolean),
47900
47865
  linterContext: optional(object({
47901
47866
  contentType: string,
@@ -47915,7 +47880,7 @@ const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
47915
47880
  showButtons: boolean,
47916
47881
  width: number,
47917
47882
  height: number,
47918
- static: boolean
47883
+ static: defaulted(boolean, () => false)
47919
47884
  }));
47920
47885
 
47921
47886
  const parseDefinitionWidget = parseWidget(constant("definition"), object({
@@ -47925,7 +47890,7 @@ const parseDefinitionWidget = parseWidget(constant("definition"), object({
47925
47890
  }));
47926
47891
 
47927
47892
  const parseDropdownWidget = parseWidget(constant("dropdown"), object({
47928
- placeholder: string,
47893
+ placeholder: defaulted(string, () => ""),
47929
47894
  ariaLabel: optional(string),
47930
47895
  visibleLabel: optional(string),
47931
47896
  static: defaulted(boolean, () => false),
@@ -47942,33 +47907,127 @@ const parseExplanationWidget = parseWidget(constant("explanation"), object({
47942
47907
  // We wrap parseWidgetsMap in a function here to make sure it is not
47943
47908
  // referenced before it is defined. There is an import cycle between
47944
47909
  // this file and widgets-map.ts that could cause it to be undefined.
47945
- widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
47946
- static: boolean
47910
+ widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
47911
+ static: defaulted(boolean, () => false)
47947
47912
  }));
47948
47913
 
47949
- const parseAnswerForm = object({
47950
- value: string,
47951
- form: boolean,
47952
- simplify: boolean,
47914
+ // Given a function, creates a PartialParser that converts one type to another
47915
+ // using that function. The returned parser never fails.
47916
+ function convert(f) {
47917
+ return (rawValue, ctx) => ctx.success(f(rawValue));
47918
+ }
47919
+
47920
+ /**
47921
+ * Creates a parser for a widget options type with multiple major versions. Old
47922
+ * versions are migrated to the latest version. The parse fails if the input
47923
+ * data does not match any of the versions.
47924
+ *
47925
+ * @example
47926
+ * const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
47927
+ * .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
47928
+ * .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
47929
+ * .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
47930
+ * .parser;
47931
+ *
47932
+ * @param latestMajorVersion the latest major version of the widget options.
47933
+ * @param parseLatest a {@link Parser} for the latest version of the widget
47934
+ * options.
47935
+ * @returns a builder object, to which migrations from earlier versions can be
47936
+ * added. Migrations must be added in "reverse chronological" order as in the
47937
+ * example above.
47938
+ */
47939
+ function versionedWidgetOptions(latestMajorVersion, parseLatest) {
47940
+ return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
47941
+ }
47942
+ class VersionedWidgetOptionsParserBuilder {
47943
+ constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
47944
+ this.parser = void 0;
47945
+ this.migrateToLatest = migrateToLatest;
47946
+ this.parseOtherVersions = parseOtherVersions;
47947
+ const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
47948
+ this.parser = (raw, ctx) => {
47949
+ if (!isObject(raw)) {
47950
+ return ctx.failure("object", raw);
47951
+ }
47952
+ const versionParseResult = parseVersionedObject(raw, ctx);
47953
+ if (isFailure(versionParseResult)) {
47954
+ return versionParseResult;
47955
+ }
47956
+ if (versionParseResult.value.version.major !== majorVersion) {
47957
+ return this.parseOtherVersions(raw, ctx);
47958
+ }
47959
+ return parseThisVersionAndMigrateToLatest(raw, ctx);
47960
+ };
47961
+ }
47962
+
47963
+ /**
47964
+ * Add a migration from an old version of the widget options.
47965
+ */
47966
+ withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
47967
+ const parseOtherVersions = this.parser;
47968
+ const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
47969
+ return new VersionedWidgetOptionsParserBuilder(majorVersion, parseOldVersion, migrateToLatest, parseOtherVersions);
47970
+ }
47971
+ }
47972
+ const parseVersionedObject = object({
47973
+ version: defaulted(object({
47974
+ major: number,
47975
+ minor: number
47976
+ }), () => ({
47977
+ major: 0,
47978
+ minor: 0
47979
+ }))
47980
+ });
47981
+
47982
+ const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
47983
+ const parsePossiblyInvalidAnswerForm = object({
47984
+ // `value` is the possibly invalid part of this. It should always be a
47985
+ // string, but some answer forms don't have it. The Expression widget
47986
+ // ignores invalid values, so we can safely filter them out during parsing.
47987
+ value: optional(string),
47988
+ form: defaulted(boolean, () => false),
47989
+ simplify: defaulted(boolean, () => false),
47953
47990
  considered: enumeration("correct", "wrong", "ungraded"),
47954
- key: pipeParsers(optional(union(string).or(number).parser)).then((key, ctx) => ctx.success(String(key))).parser
47991
+ key: pipeParsers(stringOrNumberOrNullOrUndefined).then(convert(String)).parser
47955
47992
  });
47956
- const parseExpressionWidgetV1 = parseWidgetWithVersion(object({
47993
+ function removeInvalidAnswerForms(possiblyInvalid) {
47994
+ const valid = [];
47995
+ for (const answerForm of possiblyInvalid) {
47996
+ const {
47997
+ value
47998
+ } = answerForm;
47999
+ if (value != null) {
48000
+ // Copying the object seems to be needed to make TypeScript happy
48001
+ valid.push(_extends({}, answerForm, {
48002
+ value
48003
+ }));
48004
+ }
48005
+ }
48006
+ return valid;
48007
+ }
48008
+
48009
+ // NOTE(benchristel): I copied the default buttonSets from
48010
+ // expression.tsx. See the parse-perseus-json/README.md for
48011
+ // an explanation of why we want to duplicate the default here.
48012
+ const parseButtonSets = defaulted(array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")), () => ["basic", "trig", "prealgebra", "logarithms"]);
48013
+ const version1 = object({
47957
48014
  major: constant(1),
47958
48015
  minor: number
47959
- }), constant("expression"), object({
47960
- answerForms: array(parseAnswerForm),
48016
+ });
48017
+ const parseExpressionWidgetV1 = parseWidgetWithVersion(version1, constant("expression"), object({
48018
+ answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
47961
48019
  functions: array(string),
47962
48020
  times: boolean,
47963
48021
  visibleLabel: optional(string),
47964
48022
  ariaLabel: optional(string),
47965
- buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
48023
+ buttonSets: parseButtonSets,
47966
48024
  buttonsVisible: optional(enumeration("always", "never", "focused"))
47967
48025
  }));
47968
- const parseExpressionWidgetV0 = parseWidgetWithVersion(optional(object({
48026
+ const version0 = optional(object({
47969
48027
  major: constant(0),
47970
48028
  minor: number
47971
- })), constant("expression"), object({
48029
+ }));
48030
+ const parseExpressionWidgetV0 = parseWidgetWithVersion(version0, constant("expression"), object({
47972
48031
  functions: array(string),
47973
48032
  times: boolean,
47974
48033
  visibleLabel: optional(string),
@@ -47976,14 +48035,14 @@ const parseExpressionWidgetV0 = parseWidgetWithVersion(optional(object({
47976
48035
  form: boolean,
47977
48036
  simplify: boolean,
47978
48037
  value: string,
47979
- buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
48038
+ buttonSets: parseButtonSets,
47980
48039
  buttonsVisible: optional(enumeration("always", "never", "focused"))
47981
48040
  }));
47982
- function migrateV0ToV1(widget, ctx) {
48041
+ function migrateV0ToV1(widget) {
47983
48042
  const {
47984
48043
  options
47985
48044
  } = widget;
47986
- return ctx.success(_extends({}, widget, {
48045
+ return _extends({}, widget, {
47987
48046
  version: {
47988
48047
  major: 1,
47989
48048
  minor: 0
@@ -48002,9 +48061,9 @@ function migrateV0ToV1(widget, ctx) {
48002
48061
  value: options.value
48003
48062
  }]
48004
48063
  }
48005
- }));
48064
+ });
48006
48065
  }
48007
- const parseExpressionWidget = union(parseExpressionWidgetV1).or(pipeParsers(parseExpressionWidgetV0).then(migrateV0ToV1).parser).parser;
48066
+ const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
48008
48067
 
48009
48068
  const parsePerseusRenderer = defaulted(object({
48010
48069
  // TODO(benchristel): content is also defaulted to empty string in
@@ -48025,13 +48084,14 @@ const parsePerseusRenderer = defaulted(object({
48025
48084
  images: {}
48026
48085
  }));
48027
48086
 
48087
+ const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
48028
48088
  const parseGradedGroupWidgetOptions = object({
48029
48089
  title: defaulted(string, () => ""),
48030
48090
  hasHint: optional(nullable(boolean)),
48031
48091
  // This module has an import cycle with parsePerseusRenderer.
48032
48092
  // The anonymous function below ensures that we don't try to access
48033
48093
  // parsePerseusRenderer before it's defined.
48034
- hint: optional(nullable((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx))),
48094
+ hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
48035
48095
  content: string,
48036
48096
  // This module has an import cycle with parseWidgetsMap.
48037
48097
  // The anonymous function below ensures that we don't try to access
@@ -48050,31 +48110,69 @@ const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), obje
48050
48110
  gradedGroups: array(parseGradedGroupWidgetOptions)
48051
48111
  }));
48052
48112
 
48113
+ /**
48114
+ * discriminatedUnion() should be preferred over union() when parsing a
48115
+ * discriminated union type, because discriminatedUnion() produces more
48116
+ * understandable failure messages. It takes the discriminant as the source of
48117
+ * truth for which variant is to be parsed, and expects the other data to match
48118
+ * that variant.
48119
+ */
48120
+ function discriminatedUnionOn(discriminantKey) {
48121
+ const noMoreBranches = (raw, ctx) => {
48122
+ if (!isObject(raw)) {
48123
+ return ctx.failure("object", raw);
48124
+ }
48125
+ return ctx.forSubtree(discriminantKey).failure("a valid value", raw[discriminantKey]);
48126
+ };
48127
+ return new DiscriminatedUnionBuilder(discriminantKey, noMoreBranches);
48128
+ }
48129
+ class DiscriminatedUnionBuilder {
48130
+ constructor(discriminantKey, parser) {
48131
+ this.discriminantKey = discriminantKey;
48132
+ this.parser = parser;
48133
+ }
48134
+ withBranch(discriminantValue, parseNewVariant) {
48135
+ const parseNewBranch = discriminatedUnionBranch(this.discriminantKey, discriminantValue, parseNewVariant, this.parser);
48136
+ return new DiscriminatedUnionBuilder(this.discriminantKey, parseNewBranch);
48137
+ }
48138
+ }
48139
+ function discriminatedUnionBranch(discriminantKey, discriminantValue, parseVariant, parseOtherBranches) {
48140
+ return (raw, ctx) => {
48141
+ if (!isObject(raw)) {
48142
+ return ctx.failure("object", raw);
48143
+ }
48144
+ if (raw[discriminantKey] === discriminantValue) {
48145
+ return parseVariant(raw, ctx);
48146
+ }
48147
+ return parseOtherBranches(raw, ctx);
48148
+ };
48149
+ }
48150
+
48053
48151
  const pairOfNumbers$3 = pair(number, number);
48054
48152
  const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
48055
48153
  const parseGrapherWidget = parseWidget(constant("grapher"), object({
48056
48154
  availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
48057
- correct: union(object({
48155
+ correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
48058
48156
  type: constant("absolute_value"),
48059
48157
  coords: pairOfPoints
48060
- })).or(object({
48158
+ })).withBranch("exponential", object({
48061
48159
  type: constant("exponential"),
48062
48160
  asymptote: pairOfPoints,
48063
48161
  coords: pairOfPoints
48064
- })).or(object({
48162
+ })).withBranch("linear", object({
48065
48163
  type: constant("linear"),
48066
- coords: pairOfPoints
48067
- })).or(object({
48164
+ coords: defaulted(pairOfPoints, () => [[-5, 5], [5, 5]])
48165
+ })).withBranch("logarithm", object({
48068
48166
  type: constant("logarithm"),
48069
48167
  asymptote: pairOfPoints,
48070
48168
  coords: pairOfPoints
48071
- })).or(object({
48169
+ })).withBranch("quadratic", object({
48072
48170
  type: constant("quadratic"),
48073
48171
  coords: pairOfPoints
48074
- })).or(object({
48172
+ })).withBranch("sinusoid", object({
48075
48173
  type: constant("sinusoid"),
48076
48174
  coords: pairOfPoints
48077
- })).or(object({
48175
+ })).withBranch("tangent", object({
48078
48176
  type: constant("tangent"),
48079
48177
  coords: pairOfPoints
48080
48178
  })).parser,
@@ -48134,15 +48232,22 @@ const stringToNumber = (rawValue, ctx) => {
48134
48232
  return ctx.success(parsedNumber);
48135
48233
  };
48136
48234
 
48137
- const numericToNumber = pipeParsers(union(number).or(string).parser).then(stringToNumber).parser;
48235
+ function emptyToZero(x) {
48236
+ return x === "" ? 0 : x;
48237
+ }
48238
+ const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
48239
+ // In this specific case, empty string is equivalent to zero. An empty
48240
+ // string parses to either NaN (using parseInt) or 0 (using unary +) and
48241
+ // CSS will treat NaN as invalid and default to 0 instead.
48242
+ .then(convert(emptyToZero)).then(stringToNumber).parser;
48138
48243
  const parsePerseusImageBackground = object({
48139
48244
  url: optional(nullable(string)),
48140
- width: optional(numericToNumber),
48141
- height: optional(numericToNumber),
48142
- top: optional(numericToNumber),
48143
- left: optional(numericToNumber),
48144
- bottom: optional(numericToNumber),
48145
- scale: optional(numericToNumber)
48245
+ width: optional(imageDimensionToNumber),
48246
+ height: optional(imageDimensionToNumber),
48247
+ top: optional(imageDimensionToNumber),
48248
+ left: optional(imageDimensionToNumber),
48249
+ bottom: optional(imageDimensionToNumber),
48250
+ scale: optional(imageDimensionToNumber)
48146
48251
  });
48147
48252
 
48148
48253
  const pairOfNumbers$2 = pair(number, number);
@@ -48183,8 +48288,10 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
48183
48288
  }));
48184
48289
 
48185
48290
  const pairOfNumbers$1 = pair(number, number);
48291
+ const stringOrEmpty = defaulted(string, () => "");
48292
+ const parseFunctionType = constant("function");
48186
48293
  const parseFunctionElement = object({
48187
- type: constant("function"),
48294
+ type: parseFunctionType,
48188
48295
  key: string,
48189
48296
  options: object({
48190
48297
  value: string,
@@ -48196,8 +48303,9 @@ const parseFunctionElement = object({
48196
48303
  strokeWidth: number
48197
48304
  })
48198
48305
  });
48306
+ const parseLabelType = constant("label");
48199
48307
  const parseLabelElement = object({
48200
- type: constant("label"),
48308
+ type: parseLabelType,
48201
48309
  key: string,
48202
48310
  options: object({
48203
48311
  label: string,
@@ -48206,8 +48314,9 @@ const parseLabelElement = object({
48206
48314
  coordY: string
48207
48315
  })
48208
48316
  });
48317
+ const parseLineType = constant("line");
48209
48318
  const parseLineElement = object({
48210
- type: constant("line"),
48319
+ type: parseLineType,
48211
48320
  key: string,
48212
48321
  options: object({
48213
48322
  color: string,
@@ -48220,8 +48329,9 @@ const parseLineElement = object({
48220
48329
  arrows: string
48221
48330
  })
48222
48331
  });
48332
+ const parseMovableLineType = constant("movable-line");
48223
48333
  const parseMovableLineElement = object({
48224
- type: constant("movable-line"),
48334
+ type: parseMovableLineType,
48225
48335
  key: string,
48226
48336
  options: object({
48227
48337
  startX: string,
@@ -48239,8 +48349,9 @@ const parseMovableLineElement = object({
48239
48349
  constraintYMax: string
48240
48350
  })
48241
48351
  });
48352
+ const parseMovablePointType = constant("movable-point");
48242
48353
  const parseMovablePointElement = object({
48243
- type: constant("movable-point"),
48354
+ type: parseMovablePointType,
48244
48355
  key: string,
48245
48356
  options: object({
48246
48357
  startX: string,
@@ -48249,14 +48360,15 @@ const parseMovablePointElement = object({
48249
48360
  constraint: string,
48250
48361
  snap: number,
48251
48362
  constraintFn: string,
48252
- constraintXMin: string,
48253
- constraintXMax: string,
48254
- constraintYMin: string,
48255
- constraintYMax: string
48363
+ constraintXMin: stringOrEmpty,
48364
+ constraintXMax: stringOrEmpty,
48365
+ constraintYMin: stringOrEmpty,
48366
+ constraintYMax: stringOrEmpty
48256
48367
  })
48257
48368
  });
48369
+ const parseParametricType = constant("parametric");
48258
48370
  const parseParametricElement = object({
48259
- type: constant("parametric"),
48371
+ type: parseParametricType,
48260
48372
  key: string,
48261
48373
  options: object({
48262
48374
  x: string,
@@ -48268,8 +48380,9 @@ const parseParametricElement = object({
48268
48380
  strokeWidth: number
48269
48381
  })
48270
48382
  });
48383
+ const parsePointType = constant("point");
48271
48384
  const parsePointElement = object({
48272
- type: constant("point"),
48385
+ type: parsePointType,
48273
48386
  key: string,
48274
48387
  options: object({
48275
48388
  color: string,
@@ -48277,8 +48390,9 @@ const parsePointElement = object({
48277
48390
  coordY: string
48278
48391
  })
48279
48392
  });
48393
+ const parseRectangleType = constant("rectangle");
48280
48394
  const parseRectangleElement = object({
48281
- type: constant("rectangle"),
48395
+ type: parseRectangleType,
48282
48396
  key: string,
48283
48397
  options: object({
48284
48398
  color: string,
@@ -48289,7 +48403,7 @@ const parseRectangleElement = object({
48289
48403
  })
48290
48404
  });
48291
48405
  const parseInteractionWidget = parseWidget(constant("interaction"), object({
48292
- static: boolean,
48406
+ static: defaulted(boolean, () => false),
48293
48407
  graph: object({
48294
48408
  editableSettings: optional(array(enumeration("canvas", "graph"))),
48295
48409
  box: pairOfNumbers$1,
@@ -48306,7 +48420,7 @@ const parseInteractionWidget = parseWidget(constant("interaction"), object({
48306
48420
  rulerTicks: optional(number),
48307
48421
  tickStep: pairOfNumbers$1
48308
48422
  }),
48309
- elements: array(union(parseFunctionElement).or(parseLabelElement).or(parseLineElement).or(parseMovableLineElement).or(parseMovablePointElement).or(parseParametricElement).or(parsePointElement).or(parseRectangleElement).parser)
48423
+ 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)
48310
48424
  }));
48311
48425
 
48312
48426
  // Used to represent 2-D points and ranges
@@ -48364,7 +48478,7 @@ const parsePerseusGraphTypePolygon = object({
48364
48478
  showAngles: optional(boolean),
48365
48479
  showSides: optional(boolean),
48366
48480
  snapTo: optional(enumeration("grid", "angles", "sides")),
48367
- match: optional(enumeration("similar", "congruent", "approx")),
48481
+ match: optional(enumeration("similar", "congruent", "approx", "exact")),
48368
48482
  startCoords: optional(array(pairOfNumbers)),
48369
48483
  // TODO: remove coord? it's legacy.
48370
48484
  coord: optional(pairOfNumbers)
@@ -48399,7 +48513,7 @@ const parsePerseusGraphTypeSinusoid = object({
48399
48513
  // TODO: remove coord? it's legacy.
48400
48514
  coord: optional(pairOfNumbers)
48401
48515
  });
48402
- const parsePerseusGraphType = union(parsePerseusGraphTypeAngle).or(parsePerseusGraphTypeAngle).or(parsePerseusGraphTypeCircle).or(parsePerseusGraphTypeLinear).or(parsePerseusGraphTypeLinearSystem).or(parsePerseusGraphTypeNone).or(parsePerseusGraphTypePoint).or(parsePerseusGraphTypePolygon).or(parsePerseusGraphTypeQuadratic).or(parsePerseusGraphTypeRay).or(parsePerseusGraphTypeSegment).or(parsePerseusGraphTypeSinusoid).parser;
48516
+ 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;
48403
48517
  const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
48404
48518
  const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
48405
48519
  const parseLockedLineStyle = enumeration("solid", "dashed");
@@ -48518,7 +48632,7 @@ const parseLabelImageWidget = parseWidget(constant("label-image"), object({
48518
48632
  })),
48519
48633
  hideChoicesFromInstructions: boolean,
48520
48634
  multipleAnswers: boolean,
48521
- static: boolean
48635
+ static: defaulted(boolean, () => false)
48522
48636
  }));
48523
48637
 
48524
48638
  const parseMatcherWidget = parseWidget(constant("matcher"), object({
@@ -48529,10 +48643,12 @@ const parseMatcherWidget = parseWidget(constant("matcher"), object({
48529
48643
  padding: boolean
48530
48644
  }));
48531
48645
 
48646
+ const numberOrString = union(number).or(string).parser;
48647
+ const numeric = pipeParsers(defaulted(numberOrString, () => NaN)).then(stringToNumber).parser;
48532
48648
  const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
48533
48649
  prefix: optional(string),
48534
48650
  suffix: optional(string),
48535
- answers: array(array(number)),
48651
+ answers: array(array(numeric)),
48536
48652
  cursorPosition: optional(array(number)),
48537
48653
  matrixBoardSize: array(number),
48538
48654
  static: optional(boolean)
@@ -48547,7 +48663,8 @@ const parseMeasurerWidget = parseWidget(constant("measurer"), object({
48547
48663
  rulerPixels: number,
48548
48664
  rulerLength: number,
48549
48665
  box: pair(number, number),
48550
- static: boolean
48666
+ // TODO(benchristel): static is not used. Remove it?
48667
+ static: defaulted(boolean, () => false)
48551
48668
  }));
48552
48669
 
48553
48670
  const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
@@ -48556,9 +48673,10 @@ const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), o
48556
48673
  smiles: optional(string)
48557
48674
  }));
48558
48675
 
48676
+ const emptyStringToNull = pipeParsers(constant("")).then(convert(() => null)).parser;
48559
48677
  const parseNumberLineWidget = parseWidget(constant("number-line"), object({
48560
48678
  range: array(number),
48561
- labelRange: array(nullable(number)),
48679
+ labelRange: array(nullable(union(number).or(emptyStringToNull).parser)),
48562
48680
  labelStyle: string,
48563
48681
  labelTicks: boolean,
48564
48682
  isTickCtrl: optional(nullable(boolean)),
@@ -48570,26 +48688,32 @@ const parseNumberLineWidget = parseWidget(constant("number-line"), object({
48570
48688
  snapDivisions: defaulted(number, () => 2),
48571
48689
  tickStep: optional(nullable(number)),
48572
48690
  correctRel: optional(nullable(string)),
48573
- correctX: number,
48691
+ correctX: nullable(number),
48574
48692
  initialX: optional(nullable(number)),
48575
48693
  showTooltip: optional(boolean),
48576
- static: boolean
48694
+ static: defaulted(boolean, () => false)
48577
48695
  }));
48578
48696
 
48579
48697
  const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
48580
48698
  const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
48581
48699
  answers: array(object({
48582
48700
  message: string,
48583
- value: optional(number),
48701
+ // TODO(benchristel): value should never be null or undefined,
48702
+ // but we have some content where it is anyway. If we backfill
48703
+ // the data, simplify this.
48704
+ value: optional(nullable(number)),
48584
48705
  status: string,
48585
48706
  answerForms: optional(array(parseMathFormat)),
48586
48707
  strict: boolean,
48587
48708
  maxError: optional(nullable(number)),
48588
- simplify: optional(nullable(string))
48709
+ // TODO(benchristel): simplify should never be a boolean, but we
48710
+ // have some content where it is anyway. If we ever backfill
48711
+ // the data, we should simplify `simplify`.
48712
+ simplify: optional(nullable(union(string).or(pipeParsers(boolean).then(convert(String)).parser).parser))
48589
48713
  })),
48590
48714
  labelText: optional(string),
48591
48715
  size: string,
48592
- coefficient: boolean,
48716
+ coefficient: defaulted(boolean, () => false),
48593
48717
  rightAlign: optional(boolean),
48594
48718
  static: defaulted(boolean, () => false),
48595
48719
  answerForms: optional(array(object({
@@ -48621,13 +48745,13 @@ const parseOrdererWidget = parseWidget(constant("orderer"), object({
48621
48745
  const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
48622
48746
  passageNumber: number,
48623
48747
  referenceNumber: number,
48624
- summaryText: string
48748
+ summaryText: optional(string)
48625
48749
  }));
48626
48750
 
48627
48751
  const parsePassageWidget = parseWidget(constant("passage"), object({
48628
- footnotes: string,
48752
+ footnotes: defaulted(string, () => ""),
48629
48753
  passageText: string,
48630
- passageTitle: string,
48754
+ passageTitle: defaulted(string, () => ""),
48631
48755
  showLineNumbers: boolean,
48632
48756
  static: defaulted(boolean, () => false)
48633
48757
  }));
@@ -48809,6 +48933,12 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
48809
48933
  // sequence is a deprecated widget type, and the corresponding
48810
48934
  // widget component no longer exists.
48811
48935
  return parseAndAssign(`sequence ${id}`, parseDeprecatedWidget);
48936
+ case "lights-puzzle":
48937
+ return parseAndAssign(`lights-puzzle ${id}`, parseDeprecatedWidget);
48938
+ case "simulator":
48939
+ return parseAndAssign(`simulator ${id}`, parseDeprecatedWidget);
48940
+ case "transformer":
48941
+ return parseAndAssign(`transformer ${id}`, parseDeprecatedWidget);
48812
48942
  default:
48813
48943
  if (getWidget(type)) {
48814
48944
  // @ts-expect-error - 'type' is not a valid widget type