@khanacademy/perseus 49.1.7 → 49.2.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.
@@ -5,13 +5,14 @@ import type { LegacyButtonSets } from "../perseus-types";
5
5
  import type { Keys, MathFieldInterface } from "@khanacademy/math-input";
6
6
  import type { AnalyticsEventHandlerFn } from "@khanacademy/perseus-core";
7
7
  type ButtonsVisibleType = "always" | "never" | "focused";
8
- type KeypadButtonSets = {
8
+ export type KeypadButtonSets = {
9
9
  advancedRelations?: boolean;
10
10
  basicRelations?: boolean;
11
11
  divisionKey?: boolean;
12
12
  logarithms?: boolean;
13
13
  preAlgebra?: boolean;
14
14
  trigonometry?: boolean;
15
+ scientific?: boolean;
15
16
  };
16
17
  type Props = {
17
18
  className?: string;
package/dist/es/index.css CHANGED
@@ -2747,6 +2747,11 @@ img.zoom-img {
2747
2747
  box-sizing: content-box;
2748
2748
  padding: 25px 25px 0 0;
2749
2749
  }
2750
+ .perseus-dropdown .perseus-renderer .paragraph {
2751
+ /* overriding overly broad selectors in .framework-perseus rules */
2752
+ margin-bottom: 0 !important;
2753
+ font-size: 18px !important;
2754
+ }
2750
2755
  .categorizer-container {
2751
2756
  margin-top: 20px;
2752
2757
  }
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,
@@ -34131,10 +34139,10 @@ function AngleGraph(props) {
34131
34139
  angleMeasure,
34132
34140
  vertexX: srFormatNumber(coords[1][X], locale),
34133
34141
  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)
34142
+ startingSideX: srFormatNumber(coords[2][X], locale),
34143
+ startingSideY: srFormatNumber(coords[2][Y], locale),
34144
+ endingSideX: srFormatNumber(coords[0][X], locale),
34145
+ endingSideY: srFormatNumber(coords[0][Y], locale)
34138
34146
  });
34139
34147
  const formatCoordinates = (x, y) => ({
34140
34148
  x: srFormatNumber(x, locale),
@@ -45552,7 +45560,7 @@ const init = function init() {
45552
45560
 
45553
45561
  // This file is processed by a Rollup plugin (replace) to inject the production
45554
45562
  const libName = "@khanacademy/perseus";
45555
- const libVersion = "49.1.7";
45563
+ const libVersion = "49.2.0";
45556
45564
  addLibraryVersionToPerseusDebug(libName, libVersion);
45557
45565
 
45558
45566
  const apiVersion = {
@@ -47837,10 +47845,10 @@ class UnionBuilder {
47837
47845
  this.parser = parser;
47838
47846
  }
47839
47847
  or(newBranch) {
47840
- return new UnionBuilder(either(this.parser, newBranch));
47848
+ return new UnionBuilder(either$1(this.parser, newBranch));
47841
47849
  }
47842
47850
  }
47843
- function either(parseA, parseB) {
47851
+ function either$1(parseA, parseB) {
47844
47852
  return (rawValue, ctx) => {
47845
47853
  const resultA = parseA(rawValue, ctx);
47846
47854
  if (isSuccess(resultA)) {
@@ -47942,22 +47950,107 @@ const parseExplanationWidget = parseWidget(constant("explanation"), object({
47942
47950
  // We wrap parseWidgetsMap in a function here to make sure it is not
47943
47951
  // referenced before it is defined. There is an import cycle between
47944
47952
  // this file and widgets-map.ts that could cause it to be undefined.
47945
- widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
47946
- static: boolean
47953
+ widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
47954
+ static: defaulted(boolean, () => false)
47947
47955
  }));
47948
47956
 
47949
- const parseAnswerForm = object({
47950
- value: string,
47951
- form: boolean,
47952
- simplify: boolean,
47957
+ // Given a function, creates a PartialParser that converts one type to another
47958
+ // using that function. The returned parser never fails.
47959
+ function convert(f) {
47960
+ return (rawValue, ctx) => ctx.success(f(rawValue));
47961
+ }
47962
+
47963
+ /**
47964
+ * Creates a parser for a widget options type with multiple major versions. Old
47965
+ * versions are migrated to the latest version. The parse fails if the input
47966
+ * data does not match any of the versions.
47967
+ *
47968
+ * @example
47969
+ * const parseOptions = versionedWidgetOptions(parseOptionsV3)
47970
+ * .withMigrationFrom(parseOptionsV2, migrateV2ToV3)
47971
+ * .withMigrationFrom(parseOptionsV1, migrateV1ToV2)
47972
+ * .withMigrationFrom(parseOptionsV0, migrateV0ToV1)
47973
+ * .parser;
47974
+ *
47975
+ * @param parseLatest a {@link Parser} for the latest version of the widget
47976
+ * options. This should check version.major and fail if it's not the latest
47977
+ * version.
47978
+ * @returns a builder object, to which migrations from earlier versions can be
47979
+ * added. Migrations must be added in "reverse chronological" order as in the
47980
+ * example above.
47981
+ */
47982
+ function versionedWidgetOptions(parseLatest) {
47983
+ return new VersionedWidgetOptionsParserBuilder(parseLatest, latest => latest);
47984
+ }
47985
+ class VersionedWidgetOptionsParserBuilder {
47986
+ constructor(parser, migrate) {
47987
+ this.parser = parser;
47988
+ this.migrate = migrate;
47989
+ }
47990
+
47991
+ /**
47992
+ * Add a migration from an old version of the widget options.
47993
+ *
47994
+ * @returns a VersionedWidgetOptionsParserBuilder whose `parser` function
47995
+ * is capable of migrating the old version to the latest version. The parser
47996
+ * will always return the latest version of the widget options on a
47997
+ * successful parse.
47998
+ * @param parseOldVersion should be a {@link Parser} for the old options
47999
+ * type. It should fail if version.major isn't correct.
48000
+ * @param migrateToNextVersion should migrate the `Old` data to the
48001
+ * `Migratable` version of the current VersionedWidgetOptionsParserBuilder.
48002
+ * Usually, this means migrating to the next major version.
48003
+ */
48004
+ withMigrationFrom(parseOldVersion, migrateToNextVersion) {
48005
+ const parseLatestVersion = this.parser;
48006
+ const migrateToLatest = old => this.migrate(migrateToNextVersion(old));
48007
+ return new VersionedWidgetOptionsParserBuilder((raw, ctx) => {
48008
+ const resultOfParsingLatest = parseLatestVersion(raw, ctx);
48009
+ if (isSuccess(resultOfParsingLatest)) {
48010
+ return resultOfParsingLatest;
48011
+ }
48012
+ const resultOfParsingOld = parseOldVersion(raw, ctx);
48013
+ if (isSuccess(resultOfParsingOld)) {
48014
+ return success(migrateToLatest(resultOfParsingOld.value));
48015
+ }
48016
+
48017
+ // If we're here, neither parse succeeded. Return the failure.
48018
+ return resultOfParsingOld;
48019
+ }, migrateToLatest);
48020
+ }
48021
+ }
48022
+
48023
+ const parsePossiblyInvalidAnswerForm = object({
48024
+ // `value` is the possibly invalid part of this. It should always be a
48025
+ // string, but some answer forms don't have it. The Expression widget
48026
+ // ignores invalid values, so we can safely filter them out during parsing.
48027
+ value: optional(string),
48028
+ form: defaulted(boolean, () => false),
48029
+ simplify: defaulted(boolean, () => false),
47953
48030
  considered: enumeration("correct", "wrong", "ungraded"),
47954
48031
  key: pipeParsers(optional(union(string).or(number).parser)).then((key, ctx) => ctx.success(String(key))).parser
47955
48032
  });
47956
- const parseExpressionWidgetV1 = parseWidgetWithVersion(object({
48033
+ function removeInvalidAnswerForms(possiblyInvalid) {
48034
+ const valid = [];
48035
+ for (const answerForm of possiblyInvalid) {
48036
+ const {
48037
+ value
48038
+ } = answerForm;
48039
+ if (value != null) {
48040
+ // Copying the object seems to be needed to make TypeScript happy
48041
+ valid.push(_extends({}, answerForm, {
48042
+ value
48043
+ }));
48044
+ }
48045
+ }
48046
+ return valid;
48047
+ }
48048
+ const version1 = object({
47957
48049
  major: constant(1),
47958
48050
  minor: number
47959
- }), constant("expression"), object({
47960
- answerForms: array(parseAnswerForm),
48051
+ });
48052
+ const parseExpressionWidgetV1 = parseWidgetWithVersion(version1, constant("expression"), object({
48053
+ answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
47961
48054
  functions: array(string),
47962
48055
  times: boolean,
47963
48056
  visibleLabel: optional(string),
@@ -47965,10 +48058,11 @@ const parseExpressionWidgetV1 = parseWidgetWithVersion(object({
47965
48058
  buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
47966
48059
  buttonsVisible: optional(enumeration("always", "never", "focused"))
47967
48060
  }));
47968
- const parseExpressionWidgetV0 = parseWidgetWithVersion(optional(object({
48061
+ const version0 = optional(object({
47969
48062
  major: constant(0),
47970
48063
  minor: number
47971
- })), constant("expression"), object({
48064
+ }));
48065
+ const parseExpressionWidgetV0 = parseWidgetWithVersion(version0, constant("expression"), object({
47972
48066
  functions: array(string),
47973
48067
  times: boolean,
47974
48068
  visibleLabel: optional(string),
@@ -47979,11 +48073,11 @@ const parseExpressionWidgetV0 = parseWidgetWithVersion(optional(object({
47979
48073
  buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
47980
48074
  buttonsVisible: optional(enumeration("always", "never", "focused"))
47981
48075
  }));
47982
- function migrateV0ToV1(widget, ctx) {
48076
+ function migrateV0ToV1(widget) {
47983
48077
  const {
47984
48078
  options
47985
48079
  } = widget;
47986
- return ctx.success(_extends({}, widget, {
48080
+ return _extends({}, widget, {
47987
48081
  version: {
47988
48082
  major: 1,
47989
48083
  minor: 0
@@ -48002,9 +48096,9 @@ function migrateV0ToV1(widget, ctx) {
48002
48096
  value: options.value
48003
48097
  }]
48004
48098
  }
48005
- }));
48099
+ });
48006
48100
  }
48007
- const parseExpressionWidget = union(parseExpressionWidgetV1).or(pipeParsers(parseExpressionWidgetV0).then(migrateV0ToV1).parser).parser;
48101
+ const parseExpressionWidget = versionedWidgetOptions(parseExpressionWidgetV1).withMigrationFrom(parseExpressionWidgetV0, migrateV0ToV1).parser;
48008
48102
 
48009
48103
  const parsePerseusRenderer = defaulted(object({
48010
48104
  // TODO(benchristel): content is also defaulted to empty string in
@@ -48050,31 +48144,70 @@ const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), obje
48050
48144
  gradedGroups: array(parseGradedGroupWidgetOptions)
48051
48145
  }));
48052
48146
 
48147
+ // discriminatedUnion() should be preferred over union() when parsing a
48148
+ // discriminated union type, because discriminatedUnion() produces more
48149
+ // understandable failure messages. It takes the discriminant as the source of
48150
+ // truth for which variant is to be parsed, and expects the other data to match
48151
+ // that variant.
48152
+ function discriminatedUnion(narrow, parseVariant) {
48153
+ return new DiscriminatedUnionBuilder(pipeParsers(narrow).then(parseVariant).parser);
48154
+ }
48155
+ class DiscriminatedUnionBuilder {
48156
+ constructor(parser) {
48157
+ this.parser = parser;
48158
+ }
48159
+ or(narrow, parseVariant) {
48160
+ return new DiscriminatedUnionBuilder(either(narrow, parseVariant, this.parser));
48161
+ }
48162
+ }
48163
+ function either(narrowToA, parseA, parseB) {
48164
+ return (rawValue, ctx) => {
48165
+ if (isSuccess(narrowToA(rawValue, ctx))) {
48166
+ return parseA(rawValue, ctx);
48167
+ }
48168
+ return parseB(rawValue, ctx);
48169
+ };
48170
+ }
48171
+
48053
48172
  const pairOfNumbers$3 = pair(number, number);
48054
48173
  const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
48055
48174
  const parseGrapherWidget = parseWidget(constant("grapher"), object({
48056
48175
  availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
48057
- correct: union(object({
48176
+ correct: discriminatedUnion(object({
48177
+ type: constant("absolute_value")
48178
+ }), object({
48058
48179
  type: constant("absolute_value"),
48059
48180
  coords: pairOfPoints
48060
48181
  })).or(object({
48182
+ type: constant("exponential")
48183
+ }), object({
48061
48184
  type: constant("exponential"),
48062
48185
  asymptote: pairOfPoints,
48063
48186
  coords: pairOfPoints
48064
48187
  })).or(object({
48188
+ type: constant("linear")
48189
+ }), object({
48065
48190
  type: constant("linear"),
48066
48191
  coords: pairOfPoints
48067
48192
  })).or(object({
48193
+ type: constant("logarithm")
48194
+ }), object({
48068
48195
  type: constant("logarithm"),
48069
48196
  asymptote: pairOfPoints,
48070
48197
  coords: pairOfPoints
48071
48198
  })).or(object({
48199
+ type: constant("quadratic")
48200
+ }), object({
48072
48201
  type: constant("quadratic"),
48073
48202
  coords: pairOfPoints
48074
48203
  })).or(object({
48204
+ type: constant("sinusoid")
48205
+ }), object({
48075
48206
  type: constant("sinusoid"),
48076
48207
  coords: pairOfPoints
48077
48208
  })).or(object({
48209
+ type: constant("tangent")
48210
+ }), object({
48078
48211
  type: constant("tangent"),
48079
48212
  coords: pairOfPoints
48080
48213
  })).parser,
@@ -48134,15 +48267,22 @@ const stringToNumber = (rawValue, ctx) => {
48134
48267
  return ctx.success(parsedNumber);
48135
48268
  };
48136
48269
 
48137
- const numericToNumber = pipeParsers(union(number).or(string).parser).then(stringToNumber).parser;
48270
+ function emptyToZero(x) {
48271
+ return x === "" ? 0 : x;
48272
+ }
48273
+ const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
48274
+ // In this specific case, empty string is equivalent to zero. An empty
48275
+ // string parses to either NaN (using parseInt) or 0 (using unary +) and
48276
+ // CSS will treat NaN as invalid and default to 0 instead.
48277
+ .then(convert(emptyToZero)).then(stringToNumber).parser;
48138
48278
  const parsePerseusImageBackground = object({
48139
48279
  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)
48280
+ width: optional(imageDimensionToNumber),
48281
+ height: optional(imageDimensionToNumber),
48282
+ top: optional(imageDimensionToNumber),
48283
+ left: optional(imageDimensionToNumber),
48284
+ bottom: optional(imageDimensionToNumber),
48285
+ scale: optional(imageDimensionToNumber)
48146
48286
  });
48147
48287
 
48148
48288
  const pairOfNumbers$2 = pair(number, number);
@@ -48183,8 +48323,10 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
48183
48323
  }));
48184
48324
 
48185
48325
  const pairOfNumbers$1 = pair(number, number);
48326
+ const stringOrEmpty = defaulted(string, () => "");
48327
+ const parseFunctionType = constant("function");
48186
48328
  const parseFunctionElement = object({
48187
- type: constant("function"),
48329
+ type: parseFunctionType,
48188
48330
  key: string,
48189
48331
  options: object({
48190
48332
  value: string,
@@ -48196,8 +48338,9 @@ const parseFunctionElement = object({
48196
48338
  strokeWidth: number
48197
48339
  })
48198
48340
  });
48341
+ const parseLabelType = constant("label");
48199
48342
  const parseLabelElement = object({
48200
- type: constant("label"),
48343
+ type: parseLabelType,
48201
48344
  key: string,
48202
48345
  options: object({
48203
48346
  label: string,
@@ -48206,8 +48349,9 @@ const parseLabelElement = object({
48206
48349
  coordY: string
48207
48350
  })
48208
48351
  });
48352
+ const parseLineType = constant("line");
48209
48353
  const parseLineElement = object({
48210
- type: constant("line"),
48354
+ type: parseLineType,
48211
48355
  key: string,
48212
48356
  options: object({
48213
48357
  color: string,
@@ -48220,8 +48364,9 @@ const parseLineElement = object({
48220
48364
  arrows: string
48221
48365
  })
48222
48366
  });
48367
+ const parseMovableLineType = constant("movable-line");
48223
48368
  const parseMovableLineElement = object({
48224
- type: constant("movable-line"),
48369
+ type: parseMovableLineType,
48225
48370
  key: string,
48226
48371
  options: object({
48227
48372
  startX: string,
@@ -48239,8 +48384,9 @@ const parseMovableLineElement = object({
48239
48384
  constraintYMax: string
48240
48385
  })
48241
48386
  });
48387
+ const parseMovablePointType = constant("movable-point");
48242
48388
  const parseMovablePointElement = object({
48243
- type: constant("movable-point"),
48389
+ type: parseMovablePointType,
48244
48390
  key: string,
48245
48391
  options: object({
48246
48392
  startX: string,
@@ -48249,14 +48395,15 @@ const parseMovablePointElement = object({
48249
48395
  constraint: string,
48250
48396
  snap: number,
48251
48397
  constraintFn: string,
48252
- constraintXMin: string,
48253
- constraintXMax: string,
48254
- constraintYMin: string,
48255
- constraintYMax: string
48398
+ constraintXMin: stringOrEmpty,
48399
+ constraintXMax: stringOrEmpty,
48400
+ constraintYMin: stringOrEmpty,
48401
+ constraintYMax: stringOrEmpty
48256
48402
  })
48257
48403
  });
48404
+ const parseParametricType = constant("parametric");
48258
48405
  const parseParametricElement = object({
48259
- type: constant("parametric"),
48406
+ type: parseParametricType,
48260
48407
  key: string,
48261
48408
  options: object({
48262
48409
  x: string,
@@ -48268,8 +48415,9 @@ const parseParametricElement = object({
48268
48415
  strokeWidth: number
48269
48416
  })
48270
48417
  });
48418
+ const parsePointType = constant("point");
48271
48419
  const parsePointElement = object({
48272
- type: constant("point"),
48420
+ type: parsePointType,
48273
48421
  key: string,
48274
48422
  options: object({
48275
48423
  color: string,
@@ -48277,8 +48425,9 @@ const parsePointElement = object({
48277
48425
  coordY: string
48278
48426
  })
48279
48427
  });
48428
+ const parseRectangleType = constant("rectangle");
48280
48429
  const parseRectangleElement = object({
48281
- type: constant("rectangle"),
48430
+ type: parseRectangleType,
48282
48431
  key: string,
48283
48432
  options: object({
48284
48433
  color: string,
@@ -48289,7 +48438,7 @@ const parseRectangleElement = object({
48289
48438
  })
48290
48439
  });
48291
48440
  const parseInteractionWidget = parseWidget(constant("interaction"), object({
48292
- static: boolean,
48441
+ static: defaulted(boolean, () => false),
48293
48442
  graph: object({
48294
48443
  editableSettings: optional(array(enumeration("canvas", "graph"))),
48295
48444
  box: pairOfNumbers$1,
@@ -48306,7 +48455,23 @@ const parseInteractionWidget = parseWidget(constant("interaction"), object({
48306
48455
  rulerTicks: optional(number),
48307
48456
  tickStep: pairOfNumbers$1
48308
48457
  }),
48309
- elements: array(union(parseFunctionElement).or(parseLabelElement).or(parseLineElement).or(parseMovableLineElement).or(parseMovablePointElement).or(parseParametricElement).or(parsePointElement).or(parseRectangleElement).parser)
48458
+ elements: array(discriminatedUnion(object({
48459
+ type: parseFunctionType
48460
+ }), parseFunctionElement).or(object({
48461
+ type: parseLabelType
48462
+ }), parseLabelElement).or(object({
48463
+ type: parseLineType
48464
+ }), parseLineElement).or(object({
48465
+ type: parseMovableLineType
48466
+ }), parseMovableLineElement).or(object({
48467
+ type: parseMovablePointType
48468
+ }), parseMovablePointElement).or(object({
48469
+ type: parseParametricType
48470
+ }), parseParametricElement).or(object({
48471
+ type: parsePointType
48472
+ }), parsePointElement).or(object({
48473
+ type: parseRectangleType
48474
+ }), parseRectangleElement).parser)
48310
48475
  }));
48311
48476
 
48312
48477
  // Used to represent 2-D points and ranges
@@ -48529,10 +48694,11 @@ const parseMatcherWidget = parseWidget(constant("matcher"), object({
48529
48694
  padding: boolean
48530
48695
  }));
48531
48696
 
48697
+ const numeric = pipeParsers(union(number).or(string).parser).then(stringToNumber).parser;
48532
48698
  const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
48533
48699
  prefix: optional(string),
48534
48700
  suffix: optional(string),
48535
- answers: array(array(number)),
48701
+ answers: array(array(numeric)),
48536
48702
  cursorPosition: optional(array(number)),
48537
48703
  matrixBoardSize: array(number),
48538
48704
  static: optional(boolean)
@@ -48580,12 +48746,18 @@ const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "d
48580
48746
  const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
48581
48747
  answers: array(object({
48582
48748
  message: string,
48583
- value: optional(number),
48749
+ // TODO(benchristel): value should never be null or undefined,
48750
+ // but we have some content where it is anyway. If we backfill
48751
+ // the data, simplify this.
48752
+ value: optional(nullable(number)),
48584
48753
  status: string,
48585
48754
  answerForms: optional(array(parseMathFormat)),
48586
48755
  strict: boolean,
48587
48756
  maxError: optional(nullable(number)),
48588
- simplify: optional(nullable(string))
48757
+ // TODO(benchristel): simplify should never be `true`, but we
48758
+ // have some content where it is anyway. If we ever backfill
48759
+ // the data, we should simplify `simplify`.
48760
+ simplify: optional(nullable(union(string).or(pipeParsers(constant(true)).then(convert(String)).parser).parser))
48589
48761
  })),
48590
48762
  labelText: optional(string),
48591
48763
  size: string,
@@ -48621,7 +48793,7 @@ const parseOrdererWidget = parseWidget(constant("orderer"), object({
48621
48793
  const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
48622
48794
  passageNumber: number,
48623
48795
  referenceNumber: number,
48624
- summaryText: string
48796
+ summaryText: optional(string)
48625
48797
  }));
48626
48798
 
48627
48799
  const parsePassageWidget = parseWidget(constant("passage"), object({
@@ -48809,6 +48981,8 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
48809
48981
  // sequence is a deprecated widget type, and the corresponding
48810
48982
  // widget component no longer exists.
48811
48983
  return parseAndAssign(`sequence ${id}`, parseDeprecatedWidget);
48984
+ case "lights-puzzle":
48985
+ return parseAndAssign(`lights-puzzle ${id}`, parseDeprecatedWidget);
48812
48986
  default:
48813
48987
  if (getWidget(type)) {
48814
48988
  // @ts-expect-error - 'type' is not a valid widget type