@khanacademy/perseus 49.1.6 → 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;
@@ -27427,8 +27434,7 @@ class Dropdown extends React.Component {
27427
27434
  disabled: true,
27428
27435
  label: /*#__PURE__*/React.createElement(Renderer, {
27429
27436
  content: this.props.placeholder,
27430
- strings: this.context.strings,
27431
- inline: true
27437
+ strings: this.context.strings
27432
27438
  }),
27433
27439
  labelAsText: this.props.placeholder
27434
27440
  }), ...this.props.choices.map((choice, i) => /*#__PURE__*/React.createElement(OptionItem, {
@@ -27436,8 +27442,7 @@ class Dropdown extends React.Component {
27436
27442
  value: String(i + 1),
27437
27443
  label: /*#__PURE__*/React.createElement(Renderer, {
27438
27444
  content: choice,
27439
- strings: this.context.strings,
27440
- inline: true
27445
+ strings: this.context.strings
27441
27446
  }),
27442
27447
  labelAsText: choice
27443
27448
  }))];
@@ -27457,6 +27462,7 @@ class Dropdown extends React.Component {
27457
27462
  }, this.props.visibleLabel), /*#__PURE__*/React.createElement(SingleSelect, {
27458
27463
  id: dropdownId,
27459
27464
  placeholder: "",
27465
+ className: "perseus-dropdown",
27460
27466
  onChange: value => this._handleChange(parseInt(value)),
27461
27467
  selectedValue: String(this.props.selected),
27462
27468
  disabled: this.props.apiOptions.readOnly,
@@ -34133,10 +34139,10 @@ function AngleGraph(props) {
34133
34139
  angleMeasure,
34134
34140
  vertexX: srFormatNumber(coords[1][X], locale),
34135
34141
  vertexY: srFormatNumber(coords[1][Y], locale),
34136
- isX: srFormatNumber(coords[2][X], locale),
34137
- isY: srFormatNumber(coords[2][Y], locale),
34138
- tsX: srFormatNumber(coords[0][X], locale),
34139
- 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)
34140
34146
  });
34141
34147
  const formatCoordinates = (x, y) => ({
34142
34148
  x: srFormatNumber(x, locale),
@@ -45554,7 +45560,7 @@ const init = function init() {
45554
45560
 
45555
45561
  // This file is processed by a Rollup plugin (replace) to inject the production
45556
45562
  const libName = "@khanacademy/perseus";
45557
- const libVersion = "49.1.6";
45563
+ const libVersion = "49.2.0";
45558
45564
  addLibraryVersionToPerseusDebug(libName, libVersion);
45559
45565
 
45560
45566
  const apiVersion = {
@@ -47839,10 +47845,10 @@ class UnionBuilder {
47839
47845
  this.parser = parser;
47840
47846
  }
47841
47847
  or(newBranch) {
47842
- return new UnionBuilder(either(this.parser, newBranch));
47848
+ return new UnionBuilder(either$1(this.parser, newBranch));
47843
47849
  }
47844
47850
  }
47845
- function either(parseA, parseB) {
47851
+ function either$1(parseA, parseB) {
47846
47852
  return (rawValue, ctx) => {
47847
47853
  const resultA = parseA(rawValue, ctx);
47848
47854
  if (isSuccess(resultA)) {
@@ -47944,22 +47950,107 @@ const parseExplanationWidget = parseWidget(constant("explanation"), object({
47944
47950
  // We wrap parseWidgetsMap in a function here to make sure it is not
47945
47951
  // referenced before it is defined. There is an import cycle between
47946
47952
  // this file and widgets-map.ts that could cause it to be undefined.
47947
- widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
47948
- static: boolean
47953
+ widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
47954
+ static: defaulted(boolean, () => false)
47949
47955
  }));
47950
47956
 
47951
- const parseAnswerForm = object({
47952
- value: string,
47953
- form: boolean,
47954
- 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),
47955
48030
  considered: enumeration("correct", "wrong", "ungraded"),
47956
48031
  key: pipeParsers(optional(union(string).or(number).parser)).then((key, ctx) => ctx.success(String(key))).parser
47957
48032
  });
47958
- 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({
47959
48049
  major: constant(1),
47960
48050
  minor: number
47961
- }), constant("expression"), object({
47962
- answerForms: array(parseAnswerForm),
48051
+ });
48052
+ const parseExpressionWidgetV1 = parseWidgetWithVersion(version1, constant("expression"), object({
48053
+ answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
47963
48054
  functions: array(string),
47964
48055
  times: boolean,
47965
48056
  visibleLabel: optional(string),
@@ -47967,10 +48058,11 @@ const parseExpressionWidgetV1 = parseWidgetWithVersion(object({
47967
48058
  buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
47968
48059
  buttonsVisible: optional(enumeration("always", "never", "focused"))
47969
48060
  }));
47970
- const parseExpressionWidgetV0 = parseWidgetWithVersion(optional(object({
48061
+ const version0 = optional(object({
47971
48062
  major: constant(0),
47972
48063
  minor: number
47973
- })), constant("expression"), object({
48064
+ }));
48065
+ const parseExpressionWidgetV0 = parseWidgetWithVersion(version0, constant("expression"), object({
47974
48066
  functions: array(string),
47975
48067
  times: boolean,
47976
48068
  visibleLabel: optional(string),
@@ -47981,11 +48073,11 @@ const parseExpressionWidgetV0 = parseWidgetWithVersion(optional(object({
47981
48073
  buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
47982
48074
  buttonsVisible: optional(enumeration("always", "never", "focused"))
47983
48075
  }));
47984
- function migrateV0ToV1(widget, ctx) {
48076
+ function migrateV0ToV1(widget) {
47985
48077
  const {
47986
48078
  options
47987
48079
  } = widget;
47988
- return ctx.success(_extends({}, widget, {
48080
+ return _extends({}, widget, {
47989
48081
  version: {
47990
48082
  major: 1,
47991
48083
  minor: 0
@@ -48004,9 +48096,9 @@ function migrateV0ToV1(widget, ctx) {
48004
48096
  value: options.value
48005
48097
  }]
48006
48098
  }
48007
- }));
48099
+ });
48008
48100
  }
48009
- const parseExpressionWidget = union(parseExpressionWidgetV1).or(pipeParsers(parseExpressionWidgetV0).then(migrateV0ToV1).parser).parser;
48101
+ const parseExpressionWidget = versionedWidgetOptions(parseExpressionWidgetV1).withMigrationFrom(parseExpressionWidgetV0, migrateV0ToV1).parser;
48010
48102
 
48011
48103
  const parsePerseusRenderer = defaulted(object({
48012
48104
  // TODO(benchristel): content is also defaulted to empty string in
@@ -48052,31 +48144,70 @@ const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), obje
48052
48144
  gradedGroups: array(parseGradedGroupWidgetOptions)
48053
48145
  }));
48054
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
+
48055
48172
  const pairOfNumbers$3 = pair(number, number);
48056
48173
  const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
48057
48174
  const parseGrapherWidget = parseWidget(constant("grapher"), object({
48058
48175
  availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
48059
- correct: union(object({
48176
+ correct: discriminatedUnion(object({
48177
+ type: constant("absolute_value")
48178
+ }), object({
48060
48179
  type: constant("absolute_value"),
48061
48180
  coords: pairOfPoints
48062
48181
  })).or(object({
48182
+ type: constant("exponential")
48183
+ }), object({
48063
48184
  type: constant("exponential"),
48064
48185
  asymptote: pairOfPoints,
48065
48186
  coords: pairOfPoints
48066
48187
  })).or(object({
48188
+ type: constant("linear")
48189
+ }), object({
48067
48190
  type: constant("linear"),
48068
48191
  coords: pairOfPoints
48069
48192
  })).or(object({
48193
+ type: constant("logarithm")
48194
+ }), object({
48070
48195
  type: constant("logarithm"),
48071
48196
  asymptote: pairOfPoints,
48072
48197
  coords: pairOfPoints
48073
48198
  })).or(object({
48199
+ type: constant("quadratic")
48200
+ }), object({
48074
48201
  type: constant("quadratic"),
48075
48202
  coords: pairOfPoints
48076
48203
  })).or(object({
48204
+ type: constant("sinusoid")
48205
+ }), object({
48077
48206
  type: constant("sinusoid"),
48078
48207
  coords: pairOfPoints
48079
48208
  })).or(object({
48209
+ type: constant("tangent")
48210
+ }), object({
48080
48211
  type: constant("tangent"),
48081
48212
  coords: pairOfPoints
48082
48213
  })).parser,
@@ -48136,15 +48267,22 @@ const stringToNumber = (rawValue, ctx) => {
48136
48267
  return ctx.success(parsedNumber);
48137
48268
  };
48138
48269
 
48139
- 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;
48140
48278
  const parsePerseusImageBackground = object({
48141
48279
  url: optional(nullable(string)),
48142
- width: optional(numericToNumber),
48143
- height: optional(numericToNumber),
48144
- top: optional(numericToNumber),
48145
- left: optional(numericToNumber),
48146
- bottom: optional(numericToNumber),
48147
- 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)
48148
48286
  });
48149
48287
 
48150
48288
  const pairOfNumbers$2 = pair(number, number);
@@ -48185,8 +48323,10 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
48185
48323
  }));
48186
48324
 
48187
48325
  const pairOfNumbers$1 = pair(number, number);
48326
+ const stringOrEmpty = defaulted(string, () => "");
48327
+ const parseFunctionType = constant("function");
48188
48328
  const parseFunctionElement = object({
48189
- type: constant("function"),
48329
+ type: parseFunctionType,
48190
48330
  key: string,
48191
48331
  options: object({
48192
48332
  value: string,
@@ -48198,8 +48338,9 @@ const parseFunctionElement = object({
48198
48338
  strokeWidth: number
48199
48339
  })
48200
48340
  });
48341
+ const parseLabelType = constant("label");
48201
48342
  const parseLabelElement = object({
48202
- type: constant("label"),
48343
+ type: parseLabelType,
48203
48344
  key: string,
48204
48345
  options: object({
48205
48346
  label: string,
@@ -48208,8 +48349,9 @@ const parseLabelElement = object({
48208
48349
  coordY: string
48209
48350
  })
48210
48351
  });
48352
+ const parseLineType = constant("line");
48211
48353
  const parseLineElement = object({
48212
- type: constant("line"),
48354
+ type: parseLineType,
48213
48355
  key: string,
48214
48356
  options: object({
48215
48357
  color: string,
@@ -48222,8 +48364,9 @@ const parseLineElement = object({
48222
48364
  arrows: string
48223
48365
  })
48224
48366
  });
48367
+ const parseMovableLineType = constant("movable-line");
48225
48368
  const parseMovableLineElement = object({
48226
- type: constant("movable-line"),
48369
+ type: parseMovableLineType,
48227
48370
  key: string,
48228
48371
  options: object({
48229
48372
  startX: string,
@@ -48241,8 +48384,9 @@ const parseMovableLineElement = object({
48241
48384
  constraintYMax: string
48242
48385
  })
48243
48386
  });
48387
+ const parseMovablePointType = constant("movable-point");
48244
48388
  const parseMovablePointElement = object({
48245
- type: constant("movable-point"),
48389
+ type: parseMovablePointType,
48246
48390
  key: string,
48247
48391
  options: object({
48248
48392
  startX: string,
@@ -48251,14 +48395,15 @@ const parseMovablePointElement = object({
48251
48395
  constraint: string,
48252
48396
  snap: number,
48253
48397
  constraintFn: string,
48254
- constraintXMin: string,
48255
- constraintXMax: string,
48256
- constraintYMin: string,
48257
- constraintYMax: string
48398
+ constraintXMin: stringOrEmpty,
48399
+ constraintXMax: stringOrEmpty,
48400
+ constraintYMin: stringOrEmpty,
48401
+ constraintYMax: stringOrEmpty
48258
48402
  })
48259
48403
  });
48404
+ const parseParametricType = constant("parametric");
48260
48405
  const parseParametricElement = object({
48261
- type: constant("parametric"),
48406
+ type: parseParametricType,
48262
48407
  key: string,
48263
48408
  options: object({
48264
48409
  x: string,
@@ -48270,8 +48415,9 @@ const parseParametricElement = object({
48270
48415
  strokeWidth: number
48271
48416
  })
48272
48417
  });
48418
+ const parsePointType = constant("point");
48273
48419
  const parsePointElement = object({
48274
- type: constant("point"),
48420
+ type: parsePointType,
48275
48421
  key: string,
48276
48422
  options: object({
48277
48423
  color: string,
@@ -48279,8 +48425,9 @@ const parsePointElement = object({
48279
48425
  coordY: string
48280
48426
  })
48281
48427
  });
48428
+ const parseRectangleType = constant("rectangle");
48282
48429
  const parseRectangleElement = object({
48283
- type: constant("rectangle"),
48430
+ type: parseRectangleType,
48284
48431
  key: string,
48285
48432
  options: object({
48286
48433
  color: string,
@@ -48291,7 +48438,7 @@ const parseRectangleElement = object({
48291
48438
  })
48292
48439
  });
48293
48440
  const parseInteractionWidget = parseWidget(constant("interaction"), object({
48294
- static: boolean,
48441
+ static: defaulted(boolean, () => false),
48295
48442
  graph: object({
48296
48443
  editableSettings: optional(array(enumeration("canvas", "graph"))),
48297
48444
  box: pairOfNumbers$1,
@@ -48308,7 +48455,23 @@ const parseInteractionWidget = parseWidget(constant("interaction"), object({
48308
48455
  rulerTicks: optional(number),
48309
48456
  tickStep: pairOfNumbers$1
48310
48457
  }),
48311
- 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)
48312
48475
  }));
48313
48476
 
48314
48477
  // Used to represent 2-D points and ranges
@@ -48531,10 +48694,11 @@ const parseMatcherWidget = parseWidget(constant("matcher"), object({
48531
48694
  padding: boolean
48532
48695
  }));
48533
48696
 
48697
+ const numeric = pipeParsers(union(number).or(string).parser).then(stringToNumber).parser;
48534
48698
  const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
48535
48699
  prefix: optional(string),
48536
48700
  suffix: optional(string),
48537
- answers: array(array(number)),
48701
+ answers: array(array(numeric)),
48538
48702
  cursorPosition: optional(array(number)),
48539
48703
  matrixBoardSize: array(number),
48540
48704
  static: optional(boolean)
@@ -48582,12 +48746,18 @@ const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "d
48582
48746
  const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
48583
48747
  answers: array(object({
48584
48748
  message: string,
48585
- 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)),
48586
48753
  status: string,
48587
48754
  answerForms: optional(array(parseMathFormat)),
48588
48755
  strict: boolean,
48589
48756
  maxError: optional(nullable(number)),
48590
- 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))
48591
48761
  })),
48592
48762
  labelText: optional(string),
48593
48763
  size: string,
@@ -48623,7 +48793,7 @@ const parseOrdererWidget = parseWidget(constant("orderer"), object({
48623
48793
  const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
48624
48794
  passageNumber: number,
48625
48795
  referenceNumber: number,
48626
- summaryText: string
48796
+ summaryText: optional(string)
48627
48797
  }));
48628
48798
 
48629
48799
  const parsePassageWidget = parseWidget(constant("passage"), object({
@@ -48811,6 +48981,8 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
48811
48981
  // sequence is a deprecated widget type, and the corresponding
48812
48982
  // widget component no longer exists.
48813
48983
  return parseAndAssign(`sequence ${id}`, parseDeprecatedWidget);
48984
+ case "lights-puzzle":
48985
+ return parseAndAssign(`lights-puzzle ${id}`, parseDeprecatedWidget);
48814
48986
  default:
48815
48987
  if (getWidget(type)) {
48816
48988
  // @ts-expect-error - 'type' is not a valid widget type