@khanacademy/perseus 41.3.0 → 41.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/es/index.css +3 -0
  2. package/dist/es/index.js +591 -97
  3. package/dist/es/index.js.map +1 -1
  4. package/dist/index.css +3 -0
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.js +591 -97
  7. package/dist/index.js.map +1 -1
  8. package/dist/server-item-renderer.d.ts +1 -1
  9. package/dist/util/answer-types.d.ts +45 -2
  10. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +3 -0
  11. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +16 -0
  12. package/dist/util/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +3 -0
  13. package/dist/util/parse-perseus-json/perseus-parsers/group-widget.d.ts +3 -0
  14. package/dist/util/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +3 -0
  15. package/dist/util/parse-perseus-json/perseus-parsers/image-widget.d.ts +3 -0
  16. package/dist/util/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +3 -0
  17. package/dist/util/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +3 -0
  18. package/dist/util/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +3 -0
  19. package/dist/util/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +3 -0
  20. package/dist/util/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +3 -0
  21. package/dist/util/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +3 -0
  22. package/dist/util/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +3 -0
  23. package/dist/util/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +3 -0
  24. package/dist/util/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +3 -0
  25. package/dist/util/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +3 -0
  26. package/dist/util/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +3 -0
  27. package/dist/util/parse-perseus-json/perseus-parsers/passage-widget.d.ts +3 -0
  28. package/dist/util/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +3 -0
  29. package/dist/util/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +3 -0
  30. package/dist/util/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +3 -0
  31. package/dist/util/parse-perseus-json/perseus-parsers/radio-widget.d.ts +3 -0
  32. package/dist/util/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +3 -0
  33. package/dist/util/parse-perseus-json/perseus-parsers/table-widget.d.ts +3 -0
  34. package/dist/util/parse-perseus-json/perseus-parsers/video-widget.d.ts +3 -0
  35. package/dist/validation.types.d.ts +4 -2
  36. package/dist/widgets/numeric-input/numeric-input.d.ts +7 -0
  37. package/dist/widgets/table/score-table.d.ts +1 -1
  38. package/dist/widgets/table/utils.d.ts +7 -0
  39. package/dist/widgets/table/validate-table.d.ts +6 -0
  40. package/package.json +2 -2
package/dist/es/index.js CHANGED
@@ -1464,7 +1464,7 @@ const MAXERROR_EPSILON = Math.pow(2, -42);
1464
1464
  // TOOD(kevinb): Figure out how this relates to KEScore in
1465
1465
  // perseus-all-package/types.js and see if there's a way to
1466
1466
  // unify these types.
1467
- /*
1467
+ /**
1468
1468
  * Answer types
1469
1469
  *
1470
1470
  * Utility for creating answerable questions displayed in exercises
@@ -1507,7 +1507,6 @@ const MAXERROR_EPSILON = Math.pow(2, -42);
1507
1507
  * TODO(alpert): Think of a less-absurd name for createValidatorFunctional.
1508
1508
  *
1509
1509
  */
1510
-
1511
1510
  const KhanAnswerTypes = {
1512
1511
  /*
1513
1512
  * predicate answer type
@@ -1978,9 +1977,7 @@ const KhanAnswerTypes = {
1978
1977
  })];
1979
1978
  },
1980
1979
  createValidatorFunctional: function (correctAnswer, options, strings) {
1981
- return KhanAnswerTypes.predicate.createValidatorFunctional(
1982
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
1983
- ...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
1980
+ return KhanAnswerTypes.predicate.createValidatorFunctional(...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
1984
1981
  }
1985
1982
  },
1986
1983
  /*
@@ -2594,7 +2591,7 @@ const keypadConfigurationForProps = widgetOptions => {
2594
2591
  times: widgetOptions.times
2595
2592
  };
2596
2593
  };
2597
- const propUpgrades$2 = {
2594
+ const propUpgrades$3 = {
2598
2595
  /* c8 ignore next */
2599
2596
  "1": v0props => ({
2600
2597
  times: v0props.times,
@@ -2654,7 +2651,7 @@ var Expression$1 = {
2654
2651
  major: 1,
2655
2652
  minor: 0
2656
2653
  },
2657
- propUpgrades: propUpgrades$2,
2654
+ propUpgrades: propUpgrades$3,
2658
2655
  // For use by the editor
2659
2656
  isLintable: true,
2660
2657
  scorer: scoreExpression,
@@ -18726,6 +18723,9 @@ const replaceDeprecatedWidgets = () => {
18726
18723
  replaceWidget("sequence", "deprecated-standin");
18727
18724
  replaceWidget("simulator", "deprecated-standin");
18728
18725
  replaceWidget("unit-input", "deprecated-standin");
18726
+
18727
+ // Input-Number is a special case as it is being replaced by Numeric-Input
18728
+ replaceWidget("input-number", "numeric-input");
18729
18729
  };
18730
18730
  const registerEditors = editorsToRegister => {
18731
18731
  editorsToRegister.forEach(editor => {
@@ -18760,6 +18760,11 @@ const replaceDeprecatedEditors = () => {
18760
18760
  replaceEditor("sequence", "deprecated-standin");
18761
18761
  replaceEditor("simulator", "deprecated-standin");
18762
18762
  replaceEditor("unit-input", "deprecated-standin");
18763
+
18764
+ // We're replacing the input-number editor with the numeric-input editor here,
18765
+ // but we're also modifying the JSON content to convert input-number into the
18766
+ // numeric-input format over in editor-page.tsx on line 92.
18767
+ replaceEditor("input-number", "numeric-input");
18763
18768
  };
18764
18769
  const getWidget = name => {
18765
18770
  var _widgets$name;
@@ -18810,10 +18815,8 @@ const getVersionVector = () => {
18810
18815
  const getPublicWidgets = () => {
18811
18816
  // TODO(alex): Update underscore.js so that _.pick can take a function.
18812
18817
  // @ts-expect-error - TS2740 - Type 'Pick<{ [key: string]: Readonly<{ name: string; displayName: string; getWidget?: (() => ComponentType<any>) | undefined; accessible?: boolean | ((props: any) => boolean) | undefined; hidden?: boolean | undefined; ... 10 more ...; widget: ComponentType<...>; }>; }, string>' is missing the following properties from type 'readonly Readonly<{ name: string; displayName: string; getWidget?: (() => ComponentType<any>) | undefined; accessible?: boolean | ((props: any) => boolean) | undefined; hidden?: boolean | undefined; ... 10 more ...; widget: ComponentType<...>; }>[]': length, concat, join, slice, and 18 more.
18813
- return _.pick(widgets,
18814
- // @ts-expect-error - TS2345 - Argument of type '(name: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
18815
- _.reject(_.keys(widgets), function (name) {
18816
- return widgets[name].hidden;
18818
+ return _.pick(widgets, _.reject(_.keys(widgets), function (name) {
18819
+ return widgets[name].hidden || name === "input-number";
18817
18820
  }));
18818
18821
  };
18819
18822
  const isAccessible = widgetInfo => {
@@ -22153,7 +22156,7 @@ class NumericInput extends React.Component {
22153
22156
  height: "auto"
22154
22157
  }
22155
22158
  });
22156
- return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(InputWithExamples, {
22159
+ return /*#__PURE__*/React.createElement(InputWithExamples, {
22157
22160
  ref: ref => this.inputRef = ref,
22158
22161
  value: this.props.currentValue,
22159
22162
  onChange: this.handleChange,
@@ -22165,7 +22168,7 @@ class NumericInput extends React.Component {
22165
22168
  id: this.props.widgetId,
22166
22169
  disabled: this.props.apiOptions.readOnly,
22167
22170
  style: styles.input
22168
- }));
22171
+ });
22169
22172
  }
22170
22173
  }
22171
22174
 
@@ -22234,13 +22237,65 @@ const propsTransform = function propsTransform(widgetOptions) {
22234
22237
  });
22235
22238
  return rendererProps;
22236
22239
  };
22240
+
22241
+ // This function is being used to replace the input-number widget
22242
+ // with the numeric-input widget
22243
+ const propUpgrades$2 = {
22244
+ /* c8 ignore next */
22245
+ "1": initialProps => {
22246
+ // If the initialProps has a value, it means we're upgrading from
22247
+ // input-number to numeric-input. In this case, we need to upgrade
22248
+ // the widget options accordingly.
22249
+ if (initialProps.value) {
22250
+ const provideAnswerForm = initialProps.answerType !== "number";
22251
+
22252
+ // We need to determine the mathFormat for the numeric-input widget
22253
+ const mathFormat = initialProps.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
22254
+ : initialProps.answerType; // Otherwise, we can use the answerType directly
22255
+
22256
+ // If adjusting this logic, also adjust the logic in the convertInputNumberWidgetOptions
22257
+ // function in input-number.ts in the Perseus Editor package's util folder
22258
+ const answers = [{
22259
+ value: initialProps.value,
22260
+ simplify: initialProps.simplify,
22261
+ answerForms: provideAnswerForm ? [mathFormat] : undefined,
22262
+ strict: initialProps.inexact,
22263
+ // We only want to set maxError if the inexact prop is true
22264
+ maxError: initialProps.inexact ? initialProps.maxError : 0,
22265
+ status: "correct",
22266
+ // Input-number only allows correct answers
22267
+ message: ""
22268
+ }];
22269
+ return {
22270
+ answers,
22271
+ size: initialProps.size,
22272
+ coefficient: false,
22273
+ // input-number doesn't have a coefficient prop
22274
+ labelText: "",
22275
+ // input-number doesn't have a labelText prop
22276
+ static: false,
22277
+ // static is always false for numeric-input
22278
+ rightAlign: initialProps.rightAlign || false
22279
+ };
22280
+ } else {
22281
+ // Otherwise simply return the initialProps as there's no differences
22282
+ // between v0 and v1 for numeric-input
22283
+ return initialProps;
22284
+ }
22285
+ }
22286
+ };
22237
22287
  var NumericInput$1 = {
22238
22288
  name: "numeric-input",
22239
22289
  displayName: "Numeric input",
22240
22290
  defaultAlignment: "inline-block",
22241
22291
  accessible: true,
22242
22292
  widget: NumericInput,
22293
+ version: {
22294
+ major: 1,
22295
+ minor: 0
22296
+ },
22243
22297
  transform: propsTransform,
22298
+ propUpgrades: propUpgrades$2,
22244
22299
  isLintable: true,
22245
22300
  scorer: scoreNumericInput,
22246
22301
  getOneCorrectAnswerFromRubric(rubric) {
@@ -31960,17 +32015,12 @@ const ItemExtras = [
31960
32015
  // prettier-ignore
31961
32016
 
31962
32017
  // prettier-ignore
31963
-
32018
+ // While this widget is deprecated, we still need the type for conversion purposes
31964
32019
  // prettier-ignore
31965
-
31966
32020
  // prettier-ignore
31967
-
31968
32021
  // prettier-ignore
31969
-
31970
32022
  //prettier-ignore
31971
-
31972
32023
  // A background image applied to various widgets.
31973
-
31974
32024
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
31975
32025
 
31976
32026
  // 2D range: xMin, xMax, yMin, yMax
@@ -32064,6 +32114,9 @@ function replaceOutsideTeX(mathString) {
32064
32114
  // Condense adjacent text nodes into a single text node
32065
32115
  function condenseTextNodes(nodes) {
32066
32116
  const result = [];
32117
+ if (!nodes) {
32118
+ return result;
32119
+ }
32067
32120
  let currentText = "";
32068
32121
  for (const node of nodes) {
32069
32122
  if (node.type === "math") {
@@ -44149,17 +44202,23 @@ var Sorter$1 = {
44149
44202
  scorer: scoreSorter
44150
44203
  };
44151
44204
 
44152
- function scoreTable(state, rubric, strings) {
44153
- const filterNonEmpty = function filterNonEmpty(table) {
44154
- return _.filter(table, function (row) {
44155
- // Check if row has a cell that is nonempty
44156
- return _.some(row, _.identity);
44157
- });
44158
- };
44159
- const solution = filterNonEmpty(rubric.answers);
44160
- const supplied = filterNonEmpty(state);
44161
- const hasEmptyCell = _.some(supplied, function (row) {
44162
- return _.some(row, function (cell) {
44205
+ /**
44206
+ * Filters the given table (modelled as a 2D array) to remove any rows that are
44207
+ * completely empty.
44208
+ *
44209
+ * @returns A new table with only non-empty rows.
44210
+ */
44211
+ const filterNonEmpty = function filterNonEmpty(table) {
44212
+ return table.filter(function (row) {
44213
+ // Return only rows that are non-empty.
44214
+ return row.some(cell => cell);
44215
+ });
44216
+ };
44217
+
44218
+ function validateTable(userInput) {
44219
+ const supplied = filterNonEmpty(userInput);
44220
+ const hasEmptyCell = supplied.some(function (row) {
44221
+ return row.some(function (cell) {
44163
44222
  return cell === "";
44164
44223
  });
44165
44224
  });
@@ -44169,6 +44228,16 @@ function scoreTable(state, rubric, strings) {
44169
44228
  message: null
44170
44229
  };
44171
44230
  }
44231
+ return null;
44232
+ }
44233
+
44234
+ function scoreTable(userInput, rubric, strings) {
44235
+ const validationResult = validateTable(userInput);
44236
+ if (validationResult != null) {
44237
+ return validationResult;
44238
+ }
44239
+ const supplied = filterNonEmpty(userInput);
44240
+ const solution = filterNonEmpty(rubric.answers);
44172
44241
  if (supplied.length !== solution.length) {
44173
44242
  return {
44174
44243
  type: "points",
@@ -44179,17 +44248,16 @@ function scoreTable(state, rubric, strings) {
44179
44248
  }
44180
44249
  const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
44181
44250
  let message = null;
44182
- const allCorrect = _.every(solution, function (rowSolution) {
44251
+ const allCorrect = solution.every(function (rowSolution) {
44183
44252
  for (let i = 0; i < supplied.length; i++) {
44184
44253
  const rowSupplied = supplied[i];
44185
- const correct = _.every(rowSupplied, function (cellSupplied, i) {
44254
+ const correct = rowSupplied.every(function (cellSupplied, i) {
44186
44255
  const cellSolution = rowSolution[i];
44187
44256
  const validator = createValidator(cellSolution, {
44188
44257
  simplify: true
44189
44258
  }, strings);
44190
44259
  const result = validator(cellSupplied);
44191
44260
  if (result.message) {
44192
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
44193
44261
  message = result.message;
44194
44262
  }
44195
44263
  return result.correct;
@@ -44205,7 +44273,7 @@ function scoreTable(state, rubric, strings) {
44205
44273
  type: "points",
44206
44274
  earned: allCorrect ? 1 : 0,
44207
44275
  total: 1,
44208
- message: message
44276
+ message
44209
44277
  };
44210
44278
  }
44211
44279
 
@@ -44609,7 +44677,7 @@ const init = function init() {
44609
44677
 
44610
44678
  // This file is processed by a Rollup plugin (replace) to inject the production
44611
44679
  const libName = "@khanacademy/perseus";
44612
- const libVersion = "41.3.0";
44680
+ const libVersion = "41.5.0";
44613
44681
  addLibraryVersionToPerseusDebug(libName, libVersion);
44614
44682
 
44615
44683
  const apiVersion = {
@@ -45313,7 +45381,7 @@ class ServerItemRenderer extends React.Component {
45313
45381
 
45314
45382
  /**
45315
45383
  * Accepts a question area widgetId, or an answer area widgetId of
45316
- * the form "answer-input-number 1", or the string "answer-area"
45384
+ * the form "answer-numeric-input 1", or the string "answer-area"
45317
45385
  * for the whole answer area (if the answer area is a single widget).
45318
45386
  */
45319
45387
  _setWidgetProps(widgetId, newProps, callback) {
@@ -48268,6 +48336,118 @@ const parseExpressionWidget = parseWidget(constant("expression"), object({
48268
48336
  buttonsVisible: optional(enumeration("always", "never", "focused"))
48269
48337
  }));
48270
48338
 
48339
+ const parsePerseusRenderer = object({
48340
+ content: string,
48341
+ // This module has an import cycle with parseWidgetsMap, because the
48342
+ // `group` widget can contain another renderer.
48343
+ // The anonymous function below ensures that we don't try to access
48344
+ // parseWidgetsMap before it's defined.
48345
+ widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
48346
+ metadata: optional(array(string)),
48347
+ images: record(string, object({
48348
+ width: number,
48349
+ height: number
48350
+ }))
48351
+ });
48352
+
48353
+ const parseGradedGroupWidgetOptions = object({
48354
+ title: string,
48355
+ hasHint: optional(nullable(boolean)),
48356
+ // This module has an import cycle with parsePerseusRenderer.
48357
+ // The anonymous function below ensures that we don't try to access
48358
+ // parsePerseusRenderer before it's defined.
48359
+ hint: optional(nullable((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx))),
48360
+ content: string,
48361
+ // This module has an import cycle with parseWidgetsMap.
48362
+ // The anonymous function below ensures that we don't try to access
48363
+ // parseWidgetsMap before it's defined.
48364
+ widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
48365
+ widgetEnabled: optional(nullable(boolean)),
48366
+ immutableWidgets: optional(nullable(boolean)),
48367
+ images: record(string, object({
48368
+ width: number,
48369
+ height: number
48370
+ }))
48371
+ });
48372
+ const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
48373
+
48374
+ const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
48375
+ gradedGroups: array(parseGradedGroupWidgetOptions)
48376
+ }));
48377
+
48378
+ const pairOfNumbers$3 = pair(number, number);
48379
+ const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
48380
+ const parseGrapherWidget = parseWidget(constant("grapher"), object({
48381
+ availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
48382
+ correct: union(object({
48383
+ type: constant("absolute_value"),
48384
+ coords: pairOfPoints
48385
+ })).or(object({
48386
+ type: constant("exponential"),
48387
+ asymptote: pairOfPoints,
48388
+ coords: pairOfPoints
48389
+ })).or(object({
48390
+ type: constant("linear"),
48391
+ coords: pairOfPoints
48392
+ })).or(object({
48393
+ type: constant("logarithm"),
48394
+ asymptote: pairOfPoints,
48395
+ coords: pairOfPoints
48396
+ })).or(object({
48397
+ type: constant("quadratic"),
48398
+ coords: pairOfPoints
48399
+ })).or(object({
48400
+ type: constant("sinusoid"),
48401
+ coords: pairOfPoints
48402
+ })).or(object({
48403
+ type: constant("tangent"),
48404
+ coords: pairOfPoints
48405
+ })).parser,
48406
+ graph: object({
48407
+ backgroundImage: object({
48408
+ bottom: optional(number),
48409
+ height: optional(number),
48410
+ left: optional(number),
48411
+ scale: optional(number),
48412
+ url: optional(nullable(string)),
48413
+ width: optional(number)
48414
+ }),
48415
+ box: optional(pairOfNumbers$3),
48416
+ editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
48417
+ gridStep: optional(pairOfNumbers$3),
48418
+ labels: pair(string, string),
48419
+ markings: enumeration("graph", "none", "grid"),
48420
+ range: pair(pairOfNumbers$3, pairOfNumbers$3),
48421
+ rulerLabel: constant(""),
48422
+ rulerTicks: number,
48423
+ showProtractor: optional(boolean),
48424
+ showRuler: optional(boolean),
48425
+ showTooltips: optional(boolean),
48426
+ snapStep: optional(pairOfNumbers$3),
48427
+ step: pairOfNumbers$3,
48428
+ valid: optional(union(boolean).or(string).parser)
48429
+ })
48430
+ }));
48431
+
48432
+ const parseGroupWidget = parseWidget(constant("group"),
48433
+ // This module has an import cycle with parsePerseusRenderer.
48434
+ // The anonymous function below ensures that we don't try to access
48435
+ // parsePerseusRenderer before it's defined.
48436
+ (rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
48437
+
48438
+ const parseIframeWidget = parseWidget(constant("iframe"), object({
48439
+ url: string,
48440
+ settings: array(object({
48441
+ name: string,
48442
+ value: string
48443
+ })),
48444
+ width: union(number).or(string).parser,
48445
+ height: union(number).or(string).parser,
48446
+ allowFullScreen: boolean,
48447
+ allowTopNavigation: optional(boolean),
48448
+ static: boolean
48449
+ }));
48450
+
48271
48451
  const parsePerseusImageBackground = object({
48272
48452
  url: optional(nullable(string)),
48273
48453
  width: optional(number),
@@ -48279,6 +48459,160 @@ const parsePerseusImageBackground = object({
48279
48459
  scale: optional(union(number).or(string).parser)
48280
48460
  });
48281
48461
 
48462
+ const pairOfNumbers$2 = pair(number, number);
48463
+ const parseImageWidget = parseWidget(constant("image"), object({
48464
+ title: optional(string),
48465
+ caption: optional(string),
48466
+ alt: optional(string),
48467
+ backgroundImage: parsePerseusImageBackground,
48468
+ static: optional(boolean),
48469
+ labels: optional(array(object({
48470
+ content: string,
48471
+ alignment: string,
48472
+ coordinates: array(number)
48473
+ }))),
48474
+ range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
48475
+ box: optional(pairOfNumbers$2)
48476
+ }));
48477
+
48478
+ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
48479
+ answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
48480
+ inexact: optional(boolean),
48481
+ maxError: optional(union(number).or(string).parser),
48482
+ rightAlign: optional(boolean),
48483
+ simplify: enumeration("required", "optional", "enforced"),
48484
+ size: enumeration("normal", "small"),
48485
+ value: union(number).or(string).parser,
48486
+ customKeypad: optional(boolean)
48487
+ }));
48488
+
48489
+ const pairOfNumbers$1 = pair(number, number);
48490
+ const parseFunctionElement = object({
48491
+ type: constant("function"),
48492
+ key: string,
48493
+ options: object({
48494
+ value: string,
48495
+ funcName: string,
48496
+ rangeMin: string,
48497
+ rangeMax: string,
48498
+ color: string,
48499
+ strokeDasharray: string,
48500
+ strokeWidth: number
48501
+ })
48502
+ });
48503
+ const parseLabelElement = object({
48504
+ type: constant("label"),
48505
+ key: string,
48506
+ options: object({
48507
+ label: string,
48508
+ color: string,
48509
+ coordX: string,
48510
+ coordY: string
48511
+ })
48512
+ });
48513
+ const parseLineElement = object({
48514
+ type: constant("line"),
48515
+ key: string,
48516
+ options: object({
48517
+ color: string,
48518
+ startX: string,
48519
+ startY: string,
48520
+ endX: string,
48521
+ endY: string,
48522
+ strokeDasharray: string,
48523
+ strokeWidth: number,
48524
+ arrows: string
48525
+ })
48526
+ });
48527
+ const parseMovableLineElement = object({
48528
+ type: constant("movable-line"),
48529
+ key: string,
48530
+ options: object({
48531
+ startX: string,
48532
+ startY: string,
48533
+ startSubscript: number,
48534
+ endX: string,
48535
+ endY: string,
48536
+ endSubscript: number,
48537
+ constraint: string,
48538
+ snap: number,
48539
+ constraintFn: string,
48540
+ constraintXMin: string,
48541
+ constraintXMax: string,
48542
+ constraintYMin: string,
48543
+ constraintYMax: string
48544
+ })
48545
+ });
48546
+ const parseMovablePointElement = object({
48547
+ type: constant("movable-point"),
48548
+ key: string,
48549
+ options: object({
48550
+ startX: string,
48551
+ startY: string,
48552
+ varSubscript: number,
48553
+ constraint: string,
48554
+ snap: number,
48555
+ constraintFn: string,
48556
+ constraintXMin: string,
48557
+ constraintXMax: string,
48558
+ constraintYMin: string,
48559
+ constraintYMax: string
48560
+ })
48561
+ });
48562
+ const parseParametricElement = object({
48563
+ type: constant("parametric"),
48564
+ key: string,
48565
+ options: object({
48566
+ x: string,
48567
+ y: string,
48568
+ rangeMin: string,
48569
+ rangeMax: string,
48570
+ color: string,
48571
+ strokeDasharray: string,
48572
+ strokeWidth: number
48573
+ })
48574
+ });
48575
+ const parsePointElement = object({
48576
+ type: constant("point"),
48577
+ key: string,
48578
+ options: object({
48579
+ color: string,
48580
+ coordX: string,
48581
+ coordY: string
48582
+ })
48583
+ });
48584
+ const parseRectangleElement = object({
48585
+ type: constant("rectangle"),
48586
+ key: string,
48587
+ options: object({
48588
+ color: string,
48589
+ coordX: string,
48590
+ coordY: string,
48591
+ width: string,
48592
+ height: string
48593
+ })
48594
+ });
48595
+ const parseInteractionWidget = parseWidget(constant("interaction"), object({
48596
+ static: boolean,
48597
+ graph: object({
48598
+ editableSettings: optional(array(enumeration("canvas", "graph"))),
48599
+ box: pairOfNumbers$1,
48600
+ labels: array(string),
48601
+ range: pair(pairOfNumbers$1, pairOfNumbers$1),
48602
+ gridStep: pairOfNumbers$1,
48603
+ markings: enumeration("graph", "grid", "none"),
48604
+ snapStep: optional(pairOfNumbers$1),
48605
+ valid: optional(union(boolean).or(string).parser),
48606
+ backgroundImage: optional(parsePerseusImageBackground),
48607
+ showProtractor: optional(boolean),
48608
+ showRuler: optional(boolean),
48609
+ rulerLabel: optional(string),
48610
+ rulerTicks: optional(number),
48611
+ tickStep: pairOfNumbers$1
48612
+ }),
48613
+ elements: array(union(parseFunctionElement).or(parseLabelElement).or(parseLineElement).or(parseMovableLineElement).or(parseMovablePointElement).or(parseParametricElement).or(parsePointElement).or(parseRectangleElement).parser)
48614
+ }));
48615
+
48282
48616
  // Used to represent 2-D points and ranges
48283
48617
  const pairOfNumbers = pair(number, number);
48284
48618
  const parsePerseusGraphTypeAngle = object({
@@ -48465,6 +48799,195 @@ const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), o
48465
48799
  fullGraphAriaDescription: optional(string)
48466
48800
  }));
48467
48801
 
48802
+ const parseLabelImageWidget = parseWidget(constant("label-image"), object({
48803
+ choices: array(string),
48804
+ imageUrl: string,
48805
+ imageAlt: string,
48806
+ imageHeight: number,
48807
+ imageWidth: number,
48808
+ markers: array(object({
48809
+ answers: array(string),
48810
+ label: string,
48811
+ x: number,
48812
+ y: number
48813
+ })),
48814
+ hideChoicesFromInstructions: boolean,
48815
+ multipleAnswers: boolean,
48816
+ static: boolean
48817
+ }));
48818
+
48819
+ const parseMatcherWidget = parseWidget(constant("matcher"), object({
48820
+ labels: array(string),
48821
+ left: array(string),
48822
+ right: array(string),
48823
+ orderMatters: boolean,
48824
+ padding: boolean
48825
+ }));
48826
+
48827
+ const parseMatrixWidget = parseWidget(constant("matrix"), object({
48828
+ prefix: string,
48829
+ suffix: string,
48830
+ answers: array(array(number)),
48831
+ cursorPosition: array(number),
48832
+ matrixBoardSize: array(number),
48833
+ static: boolean
48834
+ }));
48835
+
48836
+ const parseMeasurerWidget = parseWidget(constant("measurer"), object({
48837
+ image: parsePerseusImageBackground,
48838
+ showProtractor: boolean,
48839
+ showRuler: boolean,
48840
+ rulerLabel: string,
48841
+ rulerTicks: number,
48842
+ rulerPixels: number,
48843
+ rulerLength: number,
48844
+ box: pair(number, number),
48845
+ static: boolean
48846
+ }));
48847
+
48848
+ const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
48849
+ widgetId: string,
48850
+ rotationAngle: optional(number),
48851
+ smiles: optional(string)
48852
+ }));
48853
+
48854
+ const parseNumberLineWidget = parseWidget(constant("number-line"), object({
48855
+ range: array(number),
48856
+ labelRange: array(nullable(number)),
48857
+ labelStyle: string,
48858
+ labelTicks: boolean,
48859
+ isTickCtrl: optional(nullable(boolean)),
48860
+ divisionRange: array(number),
48861
+ numDivisions: optional(nullable(number)),
48862
+ snapDivisions: number,
48863
+ tickStep: optional(nullable(number)),
48864
+ correctRel: optional(nullable(string)),
48865
+ correctX: number,
48866
+ initialX: optional(nullable(number)),
48867
+ showTooltip: optional(boolean),
48868
+ static: boolean
48869
+ }));
48870
+
48871
+ const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
48872
+ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
48873
+ answers: array(object({
48874
+ message: string,
48875
+ value: number,
48876
+ status: string,
48877
+ answerForms: optional(array(parseMathFormat)),
48878
+ strict: boolean,
48879
+ maxError: optional(nullable(number)),
48880
+ simplify: optional(nullable(string))
48881
+ })),
48882
+ labelText: string,
48883
+ size: string,
48884
+ coefficient: boolean,
48885
+ rightAlign: optional(boolean),
48886
+ static: boolean,
48887
+ answerForms: optional(array(object({
48888
+ name: parseMathFormat,
48889
+ simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
48890
+ })))
48891
+ }));
48892
+
48893
+ // There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
48894
+ // This wrapper ensures that we don't refer to parsePerseusRenderer before
48895
+ // it's defined.
48896
+ function parseRenderer(rawValue, ctx) {
48897
+ return parsePerseusRenderer(rawValue, ctx);
48898
+ }
48899
+ const parseOrdererWidget = parseWidget(constant("orderer"), object({
48900
+ options: array(parseRenderer),
48901
+ correctOptions: array(parseRenderer),
48902
+ otherOptions: array(parseRenderer),
48903
+ height: string,
48904
+ layout: string
48905
+ }));
48906
+
48907
+ const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
48908
+ passageNumber: number,
48909
+ referenceNumber: number,
48910
+ summaryText: string
48911
+ }));
48912
+
48913
+ const parsePassageWidget = parseWidget(constant("passage"), object({
48914
+ footnotes: string,
48915
+ passageText: string,
48916
+ passageTitle: string,
48917
+ showLineNumbers: boolean,
48918
+ static: boolean
48919
+ }));
48920
+
48921
+ const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
48922
+ url: string,
48923
+ description: string
48924
+ }));
48925
+
48926
+ const parsePlotterWidget = parseWidget(constant("plotter"), object({
48927
+ labels: array(string),
48928
+ categories: array(string),
48929
+ type: enumeration(...plotterPlotTypes),
48930
+ maxY: number,
48931
+ scaleY: number,
48932
+ labelInterval: optional(nullable(number)),
48933
+ snapsPerLine: number,
48934
+ starting: array(number),
48935
+ correct: array(number),
48936
+ picUrl: optional(nullable(string)),
48937
+ picSize: optional(nullable(number)),
48938
+ picBoxHeight: optional(nullable(number)),
48939
+ plotDimensions: array(number)
48940
+ }));
48941
+
48942
+ const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
48943
+ programID: string,
48944
+ height: number
48945
+ }));
48946
+
48947
+ const parseRadioWidget = parseWidget(constant("radio"), object({
48948
+ choices: array(object({
48949
+ content: string,
48950
+ clue: optional(string),
48951
+ correct: optional(boolean),
48952
+ isNoneOfTheAbove: optional(boolean),
48953
+ // deprecated
48954
+ // There is an import cycle between radio-widget.ts and
48955
+ // widgets-map.ts. The anonymous function below ensures that we
48956
+ // don't refer to parseWidgetsMap before it's defined.
48957
+ widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
48958
+ })),
48959
+ hasNoneOfTheAbove: optional(boolean),
48960
+ countChoices: optional(boolean),
48961
+ randomize: optional(boolean),
48962
+ multipleSelect: optional(boolean),
48963
+ deselectEnabled: optional(boolean),
48964
+ // deprecated
48965
+ onePerLine: optional(boolean),
48966
+ // deprecated
48967
+ displayCount: optional(any),
48968
+ // v0 props
48969
+ // `noneOfTheAbove` is still in use (but only set to `false`).
48970
+ noneOfTheAbove: constant(false)
48971
+ }));
48972
+
48973
+ const parseSorterWidget = parseWidget(constant("sorter"), object({
48974
+ correct: array(string),
48975
+ padding: boolean,
48976
+ layout: enumeration("horizontal", "vertical")
48977
+ }));
48978
+
48979
+ const parseTableWidget = parseWidget(constant("table"), object({
48980
+ headers: array(string),
48981
+ rows: number,
48982
+ columns: number,
48983
+ answers: array(array(string))
48984
+ }));
48985
+
48986
+ const parseVideoWidget = parseWidget(constant("video"), object({
48987
+ location: string,
48988
+ static: optional(boolean)
48989
+ }));
48990
+
48468
48991
  const parseWidgetsMap = (rawValue, ctx) => {
48469
48992
  if (!isObject(rawValue)) {
48470
48993
  return ctx.failure("PerseusWidgetsMap", rawValue);
@@ -48509,86 +49032,67 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
48509
49032
  case "expression":
48510
49033
  return parseAndAssign(`expression ${id}`, parseExpressionWidget);
48511
49034
  case "grapher":
48512
- // TODO(LEMS-2585): implement a real parser for this widget
48513
- return parseAndAssign(`grapher ${id}`, any);
49035
+ return parseAndAssign(`grapher ${id}`, parseGrapherWidget);
48514
49036
  case "group":
48515
- // TODO(LEMS-2585): implement a real parser for this widget
48516
- return parseAndAssign(`group ${id}`, any);
49037
+ return parseAndAssign(`group ${id}`, parseGroupWidget);
48517
49038
  case "graded-group":
48518
- // TODO(LEMS-2585): implement a real parser for this widget
48519
- return parseAndAssign(`graded-group ${id}`, any);
49039
+ return parseAndAssign(`graded-group ${id}`, parseGradedGroupWidget);
48520
49040
  case "graded-group-set":
48521
- // TODO(LEMS-2585): implement a real parser for this widget
48522
- return parseAndAssign(`graded-group-set ${id}`, any);
49041
+ return parseAndAssign(`graded-group-set ${id}`, parseGradedGroupSetWidget);
48523
49042
  case "iframe":
48524
- // TODO(LEMS-2585): implement a real parser for this widget
48525
- return parseAndAssign(`iframe ${id}`, any);
49043
+ return parseAndAssign(`iframe ${id}`, parseIframeWidget);
48526
49044
  case "image":
48527
- // TODO(LEMS-2585): implement a real parser for this widget
48528
- return parseAndAssign(`image ${id}`, any);
49045
+ return parseAndAssign(`image ${id}`, parseImageWidget);
48529
49046
  case "input-number":
48530
- // TODO(LEMS-2585): implement a real parser for this widget
48531
- return parseAndAssign(`input-number ${id}`, any);
49047
+ return parseAndAssign(`input-number ${id}`, parseInputNumberWidget);
48532
49048
  case "interaction":
48533
- // TODO(LEMS-2585): implement a real parser for this widget
48534
- return parseAndAssign(`interaction ${id}`, any);
49049
+ return parseAndAssign(`interaction ${id}`, parseInteractionWidget);
48535
49050
  case "interactive-graph":
48536
49051
  return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
48537
49052
  case "label-image":
48538
- // TODO(LEMS-2585): implement a real parser for this widget
48539
- return parseAndAssign(`label-image ${id}`, any);
49053
+ return parseAndAssign(`label-image ${id}`, parseLabelImageWidget);
48540
49054
  case "matcher":
48541
- // TODO(LEMS-2585): implement a real parser for this widget
48542
- return parseAndAssign(`matcher ${id}`, any);
49055
+ return parseAndAssign(`matcher ${id}`, parseMatcherWidget);
48543
49056
  case "matrix":
48544
- // TODO(LEMS-2585): implement a real parser for this widget
48545
- return parseAndAssign(`matrix ${id}`, any);
49057
+ return parseAndAssign(`matrix ${id}`, parseMatrixWidget);
48546
49058
  case "measurer":
48547
- // TODO(LEMS-2585): implement a real parser for this widget
48548
- return parseAndAssign(`measurer ${id}`, any);
49059
+ return parseAndAssign(`measurer ${id}`, parseMeasurerWidget);
48549
49060
  case "molecule-renderer":
48550
- // TODO(LEMS-2585): implement a real parser for this widget
48551
- return parseAndAssign(`molecule-renderer ${id}`, any);
49061
+ return parseAndAssign(`molecule-renderer ${id}`, parseMoleculeRendererWidget);
48552
49062
  case "number-line":
48553
- // TODO(LEMS-2585): implement a real parser for this widget
48554
- return parseAndAssign(`number-line ${id}`, any);
49063
+ return parseAndAssign(`number-line ${id}`, parseNumberLineWidget);
48555
49064
  case "numeric-input":
48556
- // TODO(LEMS-2585): implement a real parser for this widget
48557
- return parseAndAssign(`numeric-input ${id}`, any);
49065
+ return parseAndAssign(`numeric-input ${id}`, parseNumericInputWidget);
48558
49066
  case "orderer":
48559
- // TODO(LEMS-2585): implement a real parser for this widget
48560
- return parseAndAssign(`orderer ${id}`, any);
49067
+ return parseAndAssign(`orderer ${id}`, parseOrdererWidget);
48561
49068
  case "passage":
48562
- // TODO(LEMS-2585): implement a real parser for this widget
48563
- return parseAndAssign(`passage ${id}`, any);
49069
+ return parseAndAssign(`passage ${id}`, parsePassageWidget);
48564
49070
  case "passage-ref":
48565
- // TODO(LEMS-2585): implement a real parser for this widget
48566
- return parseAndAssign(`passage-ref ${id}`, any);
49071
+ return parseAndAssign(`passage-ref ${id}`, parsePassageRefWidget);
48567
49072
  case "passage-ref-target":
48568
- // TODO(LEMS-2585): implement a real parser for this widget
49073
+ // NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
49074
+ // used in test content. See:
49075
+ // https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
48569
49076
  return parseAndAssign(`passage-ref-target ${id}`, any);
48570
49077
  case "phet-simulation":
48571
- // TODO(LEMS-2585): implement a real parser for this widget
48572
- return parseAndAssign(`phet-simulation ${id}`, any);
49078
+ return parseAndAssign(`phet-simulation ${id}`, parsePhetSimulationWidget);
48573
49079
  case "plotter":
48574
- // TODO(LEMS-2585): implement a real parser for this widget
48575
- return parseAndAssign(`plotter ${id}`, any);
49080
+ return parseAndAssign(`plotter ${id}`, parsePlotterWidget);
48576
49081
  case "python-program":
48577
- // TODO(LEMS-2585): implement a real parser for this widget
48578
- return parseAndAssign(`python-program ${id}`, any);
49082
+ return parseAndAssign(`python-program ${id}`, parsePythonProgramWidget);
48579
49083
  case "radio":
48580
- // TODO(LEMS-2585): implement a real parser for this widget
48581
- return parseAndAssign(`radio ${id}`, any);
49084
+ return parseAndAssign(`radio ${id}`, parseRadioWidget);
48582
49085
  case "sorter":
48583
- // TODO(LEMS-2585): implement a real parser for this widget
48584
- return parseAndAssign(`sorter ${id}`, any);
49086
+ return parseAndAssign(`sorter ${id}`, parseSorterWidget);
48585
49087
  case "table":
48586
- // TODO(LEMS-2585): implement a real parser for this widget
48587
- return parseAndAssign(`table ${id}`, any);
49088
+ return parseAndAssign(`table ${id}`, parseTableWidget);
48588
49089
  case "video":
48589
- // TODO(LEMS-2585): implement a real parser for this widget
48590
- return parseAndAssign(`video ${id}`, any);
49090
+ return parseAndAssign(`video ${id}`, parseVideoWidget);
48591
49091
  default:
49092
+ if (getWidget(type)) {
49093
+ // @ts-expect-error - 'type' is not a valid widget type
49094
+ return parseAndAssign(`${type} ${id}`, any);
49095
+ }
48592
49096
  return ctx.failure("a valid widget type", type);
48593
49097
  }
48594
49098
  };
@@ -48611,16 +49115,6 @@ const parseHint = object({
48611
49115
  }))
48612
49116
  });
48613
49117
 
48614
- const parsePerseusRenderer = object({
48615
- content: string,
48616
- widgets: parseWidgetsMap,
48617
- metadata: optional(array(string)),
48618
- images: record(string, object({
48619
- width: number,
48620
- height: number
48621
- }))
48622
- });
48623
-
48624
49118
  object({
48625
49119
  question: parsePerseusRenderer,
48626
49120
  hints: array(parseHint),