@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/index.js CHANGED
@@ -1588,7 +1588,7 @@ const MAXERROR_EPSILON = Math.pow(2, -42);
1588
1588
  // TOOD(kevinb): Figure out how this relates to KEScore in
1589
1589
  // perseus-all-package/types.js and see if there's a way to
1590
1590
  // unify these types.
1591
- /*
1591
+ /**
1592
1592
  * Answer types
1593
1593
  *
1594
1594
  * Utility for creating answerable questions displayed in exercises
@@ -1631,7 +1631,6 @@ const MAXERROR_EPSILON = Math.pow(2, -42);
1631
1631
  * TODO(alpert): Think of a less-absurd name for createValidatorFunctional.
1632
1632
  *
1633
1633
  */
1634
-
1635
1634
  const KhanAnswerTypes = {
1636
1635
  /*
1637
1636
  * predicate answer type
@@ -2103,9 +2102,7 @@ const KhanAnswerTypes = {
2103
2102
  })];
2104
2103
  },
2105
2104
  createValidatorFunctional: function (correctAnswer, options, strings) {
2106
- return KhanAnswerTypes.predicate.createValidatorFunctional(
2107
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
2108
- ...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
2105
+ return KhanAnswerTypes.predicate.createValidatorFunctional(...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
2109
2106
  }
2110
2107
  },
2111
2108
  /*
@@ -2715,7 +2712,7 @@ const keypadConfigurationForProps = widgetOptions => {
2715
2712
  times: widgetOptions.times
2716
2713
  };
2717
2714
  };
2718
- const propUpgrades$2 = {
2715
+ const propUpgrades$3 = {
2719
2716
  /* c8 ignore next */
2720
2717
  "1": v0props => ({
2721
2718
  times: v0props.times,
@@ -2775,7 +2772,7 @@ var Expression$1 = {
2775
2772
  major: 1,
2776
2773
  minor: 0
2777
2774
  },
2778
- propUpgrades: propUpgrades$2,
2775
+ propUpgrades: propUpgrades$3,
2779
2776
  // For use by the editor
2780
2777
  isLintable: true,
2781
2778
  scorer: scoreExpression,
@@ -18927,6 +18924,9 @@ const replaceDeprecatedWidgets = () => {
18927
18924
  replaceWidget("sequence", "deprecated-standin");
18928
18925
  replaceWidget("simulator", "deprecated-standin");
18929
18926
  replaceWidget("unit-input", "deprecated-standin");
18927
+
18928
+ // Input-Number is a special case as it is being replaced by Numeric-Input
18929
+ replaceWidget("input-number", "numeric-input");
18930
18930
  };
18931
18931
  const registerEditors = editorsToRegister => {
18932
18932
  editorsToRegister.forEach(editor => {
@@ -18961,6 +18961,11 @@ const replaceDeprecatedEditors = () => {
18961
18961
  replaceEditor("sequence", "deprecated-standin");
18962
18962
  replaceEditor("simulator", "deprecated-standin");
18963
18963
  replaceEditor("unit-input", "deprecated-standin");
18964
+
18965
+ // We're replacing the input-number editor with the numeric-input editor here,
18966
+ // but we're also modifying the JSON content to convert input-number into the
18967
+ // numeric-input format over in editor-page.tsx on line 92.
18968
+ replaceEditor("input-number", "numeric-input");
18964
18969
  };
18965
18970
  const getWidget = name => {
18966
18971
  // TODO(alex): Consider referring to these as renderers to avoid
@@ -19007,10 +19012,8 @@ const getVersionVector = () => {
19007
19012
  const getPublicWidgets = () => {
19008
19013
  // TODO(alex): Update underscore.js so that _.pick can take a function.
19009
19014
  // @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.
19010
- return ___default["default"].pick(widgets,
19011
- // @ts-expect-error - TS2345 - Argument of type '(name: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
19012
- ___default["default"].reject(___default["default"].keys(widgets), function (name) {
19013
- return widgets[name].hidden;
19015
+ return ___default["default"].pick(widgets, ___default["default"].reject(___default["default"].keys(widgets), function (name) {
19016
+ return widgets[name].hidden || name === "input-number";
19014
19017
  }));
19015
19018
  };
19016
19019
  const isAccessible = widgetInfo => {
@@ -22380,7 +22383,7 @@ class NumericInput extends React__namespace.Component {
22380
22383
  height: "auto"
22381
22384
  }
22382
22385
  });
22383
- return /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement(InputWithExamples, {
22386
+ return /*#__PURE__*/React__namespace.createElement(InputWithExamples, {
22384
22387
  ref: ref => this.inputRef = ref,
22385
22388
  value: this.props.currentValue,
22386
22389
  onChange: this.handleChange,
@@ -22392,7 +22395,7 @@ class NumericInput extends React__namespace.Component {
22392
22395
  id: this.props.widgetId,
22393
22396
  disabled: this.props.apiOptions.readOnly,
22394
22397
  style: styles.input
22395
- }));
22398
+ });
22396
22399
  }
22397
22400
  }
22398
22401
 
@@ -22450,13 +22453,65 @@ const propsTransform = function (widgetOptions) {
22450
22453
  });
22451
22454
  return rendererProps;
22452
22455
  };
22456
+
22457
+ // This function is being used to replace the input-number widget
22458
+ // with the numeric-input widget
22459
+ const propUpgrades$2 = {
22460
+ /* c8 ignore next */
22461
+ "1": initialProps => {
22462
+ // If the initialProps has a value, it means we're upgrading from
22463
+ // input-number to numeric-input. In this case, we need to upgrade
22464
+ // the widget options accordingly.
22465
+ if (initialProps.value) {
22466
+ const provideAnswerForm = initialProps.answerType !== "number";
22467
+
22468
+ // We need to determine the mathFormat for the numeric-input widget
22469
+ const mathFormat = initialProps.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
22470
+ : initialProps.answerType; // Otherwise, we can use the answerType directly
22471
+
22472
+ // If adjusting this logic, also adjust the logic in the convertInputNumberWidgetOptions
22473
+ // function in input-number.ts in the Perseus Editor package's util folder
22474
+ const answers = [{
22475
+ value: initialProps.value,
22476
+ simplify: initialProps.simplify,
22477
+ answerForms: provideAnswerForm ? [mathFormat] : undefined,
22478
+ strict: initialProps.inexact,
22479
+ // We only want to set maxError if the inexact prop is true
22480
+ maxError: initialProps.inexact ? initialProps.maxError : 0,
22481
+ status: "correct",
22482
+ // Input-number only allows correct answers
22483
+ message: ""
22484
+ }];
22485
+ return {
22486
+ answers,
22487
+ size: initialProps.size,
22488
+ coefficient: false,
22489
+ // input-number doesn't have a coefficient prop
22490
+ labelText: "",
22491
+ // input-number doesn't have a labelText prop
22492
+ static: false,
22493
+ // static is always false for numeric-input
22494
+ rightAlign: initialProps.rightAlign || false
22495
+ };
22496
+ } else {
22497
+ // Otherwise simply return the initialProps as there's no differences
22498
+ // between v0 and v1 for numeric-input
22499
+ return initialProps;
22500
+ }
22501
+ }
22502
+ };
22453
22503
  var NumericInput$1 = {
22454
22504
  name: "numeric-input",
22455
22505
  displayName: "Numeric input",
22456
22506
  defaultAlignment: "inline-block",
22457
22507
  accessible: true,
22458
22508
  widget: NumericInput,
22509
+ version: {
22510
+ major: 1,
22511
+ minor: 0
22512
+ },
22459
22513
  transform: propsTransform,
22514
+ propUpgrades: propUpgrades$2,
22460
22515
  isLintable: true,
22461
22516
  scorer: scoreNumericInput,
22462
22517
  getOneCorrectAnswerFromRubric(rubric) {
@@ -32262,17 +32317,12 @@ const ItemExtras = [
32262
32317
  // prettier-ignore
32263
32318
 
32264
32319
  // prettier-ignore
32265
-
32320
+ // While this widget is deprecated, we still need the type for conversion purposes
32266
32321
  // prettier-ignore
32267
-
32268
32322
  // prettier-ignore
32269
-
32270
32323
  // prettier-ignore
32271
-
32272
32324
  //prettier-ignore
32273
-
32274
32325
  // A background image applied to various widgets.
32275
-
32276
32326
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
32277
32327
 
32278
32328
  // 2D range: xMin, xMax, yMin, yMax
@@ -32367,6 +32417,9 @@ function replaceOutsideTeX(mathString) {
32367
32417
  // Condense adjacent text nodes into a single text node
32368
32418
  function condenseTextNodes(nodes) {
32369
32419
  const result = [];
32420
+ if (!nodes) {
32421
+ return result;
32422
+ }
32370
32423
  let currentText = "";
32371
32424
  for (const node of nodes) {
32372
32425
  if (node.type === "math") {
@@ -44512,17 +44565,23 @@ var Sorter$1 = {
44512
44565
  scorer: scoreSorter
44513
44566
  };
44514
44567
 
44515
- function scoreTable(state, rubric, strings) {
44516
- const filterNonEmpty = function (table) {
44517
- return ___default["default"].filter(table, function (row) {
44518
- // Check if row has a cell that is nonempty
44519
- return ___default["default"].some(row, ___default["default"].identity);
44520
- });
44521
- };
44522
- const solution = filterNonEmpty(rubric.answers);
44523
- const supplied = filterNonEmpty(state);
44524
- const hasEmptyCell = ___default["default"].some(supplied, function (row) {
44525
- return ___default["default"].some(row, function (cell) {
44568
+ /**
44569
+ * Filters the given table (modelled as a 2D array) to remove any rows that are
44570
+ * completely empty.
44571
+ *
44572
+ * @returns A new table with only non-empty rows.
44573
+ */
44574
+ const filterNonEmpty = function (table) {
44575
+ return table.filter(function (row) {
44576
+ // Return only rows that are non-empty.
44577
+ return row.some(cell => cell);
44578
+ });
44579
+ };
44580
+
44581
+ function validateTable(userInput) {
44582
+ const supplied = filterNonEmpty(userInput);
44583
+ const hasEmptyCell = supplied.some(function (row) {
44584
+ return row.some(function (cell) {
44526
44585
  return cell === "";
44527
44586
  });
44528
44587
  });
@@ -44532,6 +44591,16 @@ function scoreTable(state, rubric, strings) {
44532
44591
  message: null
44533
44592
  };
44534
44593
  }
44594
+ return null;
44595
+ }
44596
+
44597
+ function scoreTable(userInput, rubric, strings) {
44598
+ const validationResult = validateTable(userInput);
44599
+ if (validationResult != null) {
44600
+ return validationResult;
44601
+ }
44602
+ const supplied = filterNonEmpty(userInput);
44603
+ const solution = filterNonEmpty(rubric.answers);
44535
44604
  if (supplied.length !== solution.length) {
44536
44605
  return {
44537
44606
  type: "points",
@@ -44542,17 +44611,16 @@ function scoreTable(state, rubric, strings) {
44542
44611
  }
44543
44612
  const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
44544
44613
  let message = null;
44545
- const allCorrect = ___default["default"].every(solution, function (rowSolution) {
44614
+ const allCorrect = solution.every(function (rowSolution) {
44546
44615
  for (let i = 0; i < supplied.length; i++) {
44547
44616
  const rowSupplied = supplied[i];
44548
- const correct = ___default["default"].every(rowSupplied, function (cellSupplied, i) {
44617
+ const correct = rowSupplied.every(function (cellSupplied, i) {
44549
44618
  const cellSolution = rowSolution[i];
44550
44619
  const validator = createValidator(cellSolution, {
44551
44620
  simplify: true
44552
44621
  }, strings);
44553
44622
  const result = validator(cellSupplied);
44554
44623
  if (result.message) {
44555
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
44556
44624
  message = result.message;
44557
44625
  }
44558
44626
  return result.correct;
@@ -44568,7 +44636,7 @@ function scoreTable(state, rubric, strings) {
44568
44636
  type: "points",
44569
44637
  earned: allCorrect ? 1 : 0,
44570
44638
  total: 1,
44571
- message: message
44639
+ message
44572
44640
  };
44573
44641
  }
44574
44642
 
@@ -44971,7 +45039,7 @@ const init = function () {
44971
45039
 
44972
45040
  // This file is processed by a Rollup plugin (replace) to inject the production
44973
45041
  const libName = "@khanacademy/perseus";
44974
- const libVersion = "41.3.0";
45042
+ const libVersion = "41.5.0";
44975
45043
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44976
45044
 
44977
45045
  const apiVersion = {
@@ -45664,7 +45732,7 @@ class ServerItemRenderer extends React__namespace.Component {
45664
45732
 
45665
45733
  /**
45666
45734
  * Accepts a question area widgetId, or an answer area widgetId of
45667
- * the form "answer-input-number 1", or the string "answer-area"
45735
+ * the form "answer-numeric-input 1", or the string "answer-area"
45668
45736
  * for the whole answer area (if the answer area is a single widget).
45669
45737
  */
45670
45738
  _setWidgetProps(widgetId, newProps, callback) {
@@ -48648,6 +48716,118 @@ const parseExpressionWidget = parseWidget(constant("expression"), object({
48648
48716
  buttonsVisible: optional(enumeration("always", "never", "focused"))
48649
48717
  }));
48650
48718
 
48719
+ const parsePerseusRenderer = object({
48720
+ content: string,
48721
+ // This module has an import cycle with parseWidgetsMap, because the
48722
+ // `group` widget can contain another renderer.
48723
+ // The anonymous function below ensures that we don't try to access
48724
+ // parseWidgetsMap before it's defined.
48725
+ widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
48726
+ metadata: optional(array(string)),
48727
+ images: record(string, object({
48728
+ width: number,
48729
+ height: number
48730
+ }))
48731
+ });
48732
+
48733
+ const parseGradedGroupWidgetOptions = object({
48734
+ title: string,
48735
+ hasHint: optional(nullable(boolean)),
48736
+ // This module has an import cycle with parsePerseusRenderer.
48737
+ // The anonymous function below ensures that we don't try to access
48738
+ // parsePerseusRenderer before it's defined.
48739
+ hint: optional(nullable((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx))),
48740
+ content: string,
48741
+ // This module has an import cycle with parseWidgetsMap.
48742
+ // The anonymous function below ensures that we don't try to access
48743
+ // parseWidgetsMap before it's defined.
48744
+ widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
48745
+ widgetEnabled: optional(nullable(boolean)),
48746
+ immutableWidgets: optional(nullable(boolean)),
48747
+ images: record(string, object({
48748
+ width: number,
48749
+ height: number
48750
+ }))
48751
+ });
48752
+ const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
48753
+
48754
+ const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
48755
+ gradedGroups: array(parseGradedGroupWidgetOptions)
48756
+ }));
48757
+
48758
+ const pairOfNumbers$3 = pair(number, number);
48759
+ const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
48760
+ const parseGrapherWidget = parseWidget(constant("grapher"), object({
48761
+ availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
48762
+ correct: union(object({
48763
+ type: constant("absolute_value"),
48764
+ coords: pairOfPoints
48765
+ })).or(object({
48766
+ type: constant("exponential"),
48767
+ asymptote: pairOfPoints,
48768
+ coords: pairOfPoints
48769
+ })).or(object({
48770
+ type: constant("linear"),
48771
+ coords: pairOfPoints
48772
+ })).or(object({
48773
+ type: constant("logarithm"),
48774
+ asymptote: pairOfPoints,
48775
+ coords: pairOfPoints
48776
+ })).or(object({
48777
+ type: constant("quadratic"),
48778
+ coords: pairOfPoints
48779
+ })).or(object({
48780
+ type: constant("sinusoid"),
48781
+ coords: pairOfPoints
48782
+ })).or(object({
48783
+ type: constant("tangent"),
48784
+ coords: pairOfPoints
48785
+ })).parser,
48786
+ graph: object({
48787
+ backgroundImage: object({
48788
+ bottom: optional(number),
48789
+ height: optional(number),
48790
+ left: optional(number),
48791
+ scale: optional(number),
48792
+ url: optional(nullable(string)),
48793
+ width: optional(number)
48794
+ }),
48795
+ box: optional(pairOfNumbers$3),
48796
+ editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
48797
+ gridStep: optional(pairOfNumbers$3),
48798
+ labels: pair(string, string),
48799
+ markings: enumeration("graph", "none", "grid"),
48800
+ range: pair(pairOfNumbers$3, pairOfNumbers$3),
48801
+ rulerLabel: constant(""),
48802
+ rulerTicks: number,
48803
+ showProtractor: optional(boolean),
48804
+ showRuler: optional(boolean),
48805
+ showTooltips: optional(boolean),
48806
+ snapStep: optional(pairOfNumbers$3),
48807
+ step: pairOfNumbers$3,
48808
+ valid: optional(union(boolean).or(string).parser)
48809
+ })
48810
+ }));
48811
+
48812
+ const parseGroupWidget = parseWidget(constant("group"),
48813
+ // This module has an import cycle with parsePerseusRenderer.
48814
+ // The anonymous function below ensures that we don't try to access
48815
+ // parsePerseusRenderer before it's defined.
48816
+ (rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
48817
+
48818
+ const parseIframeWidget = parseWidget(constant("iframe"), object({
48819
+ url: string,
48820
+ settings: array(object({
48821
+ name: string,
48822
+ value: string
48823
+ })),
48824
+ width: union(number).or(string).parser,
48825
+ height: union(number).or(string).parser,
48826
+ allowFullScreen: boolean,
48827
+ allowTopNavigation: optional(boolean),
48828
+ static: boolean
48829
+ }));
48830
+
48651
48831
  const parsePerseusImageBackground = object({
48652
48832
  url: optional(nullable(string)),
48653
48833
  width: optional(number),
@@ -48659,6 +48839,160 @@ const parsePerseusImageBackground = object({
48659
48839
  scale: optional(union(number).or(string).parser)
48660
48840
  });
48661
48841
 
48842
+ const pairOfNumbers$2 = pair(number, number);
48843
+ const parseImageWidget = parseWidget(constant("image"), object({
48844
+ title: optional(string),
48845
+ caption: optional(string),
48846
+ alt: optional(string),
48847
+ backgroundImage: parsePerseusImageBackground,
48848
+ static: optional(boolean),
48849
+ labels: optional(array(object({
48850
+ content: string,
48851
+ alignment: string,
48852
+ coordinates: array(number)
48853
+ }))),
48854
+ range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
48855
+ box: optional(pairOfNumbers$2)
48856
+ }));
48857
+
48858
+ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
48859
+ answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
48860
+ inexact: optional(boolean),
48861
+ maxError: optional(union(number).or(string).parser),
48862
+ rightAlign: optional(boolean),
48863
+ simplify: enumeration("required", "optional", "enforced"),
48864
+ size: enumeration("normal", "small"),
48865
+ value: union(number).or(string).parser,
48866
+ customKeypad: optional(boolean)
48867
+ }));
48868
+
48869
+ const pairOfNumbers$1 = pair(number, number);
48870
+ const parseFunctionElement = object({
48871
+ type: constant("function"),
48872
+ key: string,
48873
+ options: object({
48874
+ value: string,
48875
+ funcName: string,
48876
+ rangeMin: string,
48877
+ rangeMax: string,
48878
+ color: string,
48879
+ strokeDasharray: string,
48880
+ strokeWidth: number
48881
+ })
48882
+ });
48883
+ const parseLabelElement = object({
48884
+ type: constant("label"),
48885
+ key: string,
48886
+ options: object({
48887
+ label: string,
48888
+ color: string,
48889
+ coordX: string,
48890
+ coordY: string
48891
+ })
48892
+ });
48893
+ const parseLineElement = object({
48894
+ type: constant("line"),
48895
+ key: string,
48896
+ options: object({
48897
+ color: string,
48898
+ startX: string,
48899
+ startY: string,
48900
+ endX: string,
48901
+ endY: string,
48902
+ strokeDasharray: string,
48903
+ strokeWidth: number,
48904
+ arrows: string
48905
+ })
48906
+ });
48907
+ const parseMovableLineElement = object({
48908
+ type: constant("movable-line"),
48909
+ key: string,
48910
+ options: object({
48911
+ startX: string,
48912
+ startY: string,
48913
+ startSubscript: number,
48914
+ endX: string,
48915
+ endY: string,
48916
+ endSubscript: number,
48917
+ constraint: string,
48918
+ snap: number,
48919
+ constraintFn: string,
48920
+ constraintXMin: string,
48921
+ constraintXMax: string,
48922
+ constraintYMin: string,
48923
+ constraintYMax: string
48924
+ })
48925
+ });
48926
+ const parseMovablePointElement = object({
48927
+ type: constant("movable-point"),
48928
+ key: string,
48929
+ options: object({
48930
+ startX: string,
48931
+ startY: string,
48932
+ varSubscript: number,
48933
+ constraint: string,
48934
+ snap: number,
48935
+ constraintFn: string,
48936
+ constraintXMin: string,
48937
+ constraintXMax: string,
48938
+ constraintYMin: string,
48939
+ constraintYMax: string
48940
+ })
48941
+ });
48942
+ const parseParametricElement = object({
48943
+ type: constant("parametric"),
48944
+ key: string,
48945
+ options: object({
48946
+ x: string,
48947
+ y: string,
48948
+ rangeMin: string,
48949
+ rangeMax: string,
48950
+ color: string,
48951
+ strokeDasharray: string,
48952
+ strokeWidth: number
48953
+ })
48954
+ });
48955
+ const parsePointElement = object({
48956
+ type: constant("point"),
48957
+ key: string,
48958
+ options: object({
48959
+ color: string,
48960
+ coordX: string,
48961
+ coordY: string
48962
+ })
48963
+ });
48964
+ const parseRectangleElement = object({
48965
+ type: constant("rectangle"),
48966
+ key: string,
48967
+ options: object({
48968
+ color: string,
48969
+ coordX: string,
48970
+ coordY: string,
48971
+ width: string,
48972
+ height: string
48973
+ })
48974
+ });
48975
+ const parseInteractionWidget = parseWidget(constant("interaction"), object({
48976
+ static: boolean,
48977
+ graph: object({
48978
+ editableSettings: optional(array(enumeration("canvas", "graph"))),
48979
+ box: pairOfNumbers$1,
48980
+ labels: array(string),
48981
+ range: pair(pairOfNumbers$1, pairOfNumbers$1),
48982
+ gridStep: pairOfNumbers$1,
48983
+ markings: enumeration("graph", "grid", "none"),
48984
+ snapStep: optional(pairOfNumbers$1),
48985
+ valid: optional(union(boolean).or(string).parser),
48986
+ backgroundImage: optional(parsePerseusImageBackground),
48987
+ showProtractor: optional(boolean),
48988
+ showRuler: optional(boolean),
48989
+ rulerLabel: optional(string),
48990
+ rulerTicks: optional(number),
48991
+ tickStep: pairOfNumbers$1
48992
+ }),
48993
+ elements: array(union(parseFunctionElement).or(parseLabelElement).or(parseLineElement).or(parseMovableLineElement).or(parseMovablePointElement).or(parseParametricElement).or(parsePointElement).or(parseRectangleElement).parser)
48994
+ }));
48995
+
48662
48996
  // Used to represent 2-D points and ranges
48663
48997
  const pairOfNumbers = pair(number, number);
48664
48998
  const parsePerseusGraphTypeAngle = object({
@@ -48845,6 +49179,195 @@ const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), o
48845
49179
  fullGraphAriaDescription: optional(string)
48846
49180
  }));
48847
49181
 
49182
+ const parseLabelImageWidget = parseWidget(constant("label-image"), object({
49183
+ choices: array(string),
49184
+ imageUrl: string,
49185
+ imageAlt: string,
49186
+ imageHeight: number,
49187
+ imageWidth: number,
49188
+ markers: array(object({
49189
+ answers: array(string),
49190
+ label: string,
49191
+ x: number,
49192
+ y: number
49193
+ })),
49194
+ hideChoicesFromInstructions: boolean,
49195
+ multipleAnswers: boolean,
49196
+ static: boolean
49197
+ }));
49198
+
49199
+ const parseMatcherWidget = parseWidget(constant("matcher"), object({
49200
+ labels: array(string),
49201
+ left: array(string),
49202
+ right: array(string),
49203
+ orderMatters: boolean,
49204
+ padding: boolean
49205
+ }));
49206
+
49207
+ const parseMatrixWidget = parseWidget(constant("matrix"), object({
49208
+ prefix: string,
49209
+ suffix: string,
49210
+ answers: array(array(number)),
49211
+ cursorPosition: array(number),
49212
+ matrixBoardSize: array(number),
49213
+ static: boolean
49214
+ }));
49215
+
49216
+ const parseMeasurerWidget = parseWidget(constant("measurer"), object({
49217
+ image: parsePerseusImageBackground,
49218
+ showProtractor: boolean,
49219
+ showRuler: boolean,
49220
+ rulerLabel: string,
49221
+ rulerTicks: number,
49222
+ rulerPixels: number,
49223
+ rulerLength: number,
49224
+ box: pair(number, number),
49225
+ static: boolean
49226
+ }));
49227
+
49228
+ const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
49229
+ widgetId: string,
49230
+ rotationAngle: optional(number),
49231
+ smiles: optional(string)
49232
+ }));
49233
+
49234
+ const parseNumberLineWidget = parseWidget(constant("number-line"), object({
49235
+ range: array(number),
49236
+ labelRange: array(nullable(number)),
49237
+ labelStyle: string,
49238
+ labelTicks: boolean,
49239
+ isTickCtrl: optional(nullable(boolean)),
49240
+ divisionRange: array(number),
49241
+ numDivisions: optional(nullable(number)),
49242
+ snapDivisions: number,
49243
+ tickStep: optional(nullable(number)),
49244
+ correctRel: optional(nullable(string)),
49245
+ correctX: number,
49246
+ initialX: optional(nullable(number)),
49247
+ showTooltip: optional(boolean),
49248
+ static: boolean
49249
+ }));
49250
+
49251
+ const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
49252
+ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
49253
+ answers: array(object({
49254
+ message: string,
49255
+ value: number,
49256
+ status: string,
49257
+ answerForms: optional(array(parseMathFormat)),
49258
+ strict: boolean,
49259
+ maxError: optional(nullable(number)),
49260
+ simplify: optional(nullable(string))
49261
+ })),
49262
+ labelText: string,
49263
+ size: string,
49264
+ coefficient: boolean,
49265
+ rightAlign: optional(boolean),
49266
+ static: boolean,
49267
+ answerForms: optional(array(object({
49268
+ name: parseMathFormat,
49269
+ simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
49270
+ })))
49271
+ }));
49272
+
49273
+ // There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
49274
+ // This wrapper ensures that we don't refer to parsePerseusRenderer before
49275
+ // it's defined.
49276
+ function parseRenderer(rawValue, ctx) {
49277
+ return parsePerseusRenderer(rawValue, ctx);
49278
+ }
49279
+ const parseOrdererWidget = parseWidget(constant("orderer"), object({
49280
+ options: array(parseRenderer),
49281
+ correctOptions: array(parseRenderer),
49282
+ otherOptions: array(parseRenderer),
49283
+ height: string,
49284
+ layout: string
49285
+ }));
49286
+
49287
+ const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
49288
+ passageNumber: number,
49289
+ referenceNumber: number,
49290
+ summaryText: string
49291
+ }));
49292
+
49293
+ const parsePassageWidget = parseWidget(constant("passage"), object({
49294
+ footnotes: string,
49295
+ passageText: string,
49296
+ passageTitle: string,
49297
+ showLineNumbers: boolean,
49298
+ static: boolean
49299
+ }));
49300
+
49301
+ const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
49302
+ url: string,
49303
+ description: string
49304
+ }));
49305
+
49306
+ const parsePlotterWidget = parseWidget(constant("plotter"), object({
49307
+ labels: array(string),
49308
+ categories: array(string),
49309
+ type: enumeration(...plotterPlotTypes),
49310
+ maxY: number,
49311
+ scaleY: number,
49312
+ labelInterval: optional(nullable(number)),
49313
+ snapsPerLine: number,
49314
+ starting: array(number),
49315
+ correct: array(number),
49316
+ picUrl: optional(nullable(string)),
49317
+ picSize: optional(nullable(number)),
49318
+ picBoxHeight: optional(nullable(number)),
49319
+ plotDimensions: array(number)
49320
+ }));
49321
+
49322
+ const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
49323
+ programID: string,
49324
+ height: number
49325
+ }));
49326
+
49327
+ const parseRadioWidget = parseWidget(constant("radio"), object({
49328
+ choices: array(object({
49329
+ content: string,
49330
+ clue: optional(string),
49331
+ correct: optional(boolean),
49332
+ isNoneOfTheAbove: optional(boolean),
49333
+ // deprecated
49334
+ // There is an import cycle between radio-widget.ts and
49335
+ // widgets-map.ts. The anonymous function below ensures that we
49336
+ // don't refer to parseWidgetsMap before it's defined.
49337
+ widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
49338
+ })),
49339
+ hasNoneOfTheAbove: optional(boolean),
49340
+ countChoices: optional(boolean),
49341
+ randomize: optional(boolean),
49342
+ multipleSelect: optional(boolean),
49343
+ deselectEnabled: optional(boolean),
49344
+ // deprecated
49345
+ onePerLine: optional(boolean),
49346
+ // deprecated
49347
+ displayCount: optional(any),
49348
+ // v0 props
49349
+ // `noneOfTheAbove` is still in use (but only set to `false`).
49350
+ noneOfTheAbove: constant(false)
49351
+ }));
49352
+
49353
+ const parseSorterWidget = parseWidget(constant("sorter"), object({
49354
+ correct: array(string),
49355
+ padding: boolean,
49356
+ layout: enumeration("horizontal", "vertical")
49357
+ }));
49358
+
49359
+ const parseTableWidget = parseWidget(constant("table"), object({
49360
+ headers: array(string),
49361
+ rows: number,
49362
+ columns: number,
49363
+ answers: array(array(string))
49364
+ }));
49365
+
49366
+ const parseVideoWidget = parseWidget(constant("video"), object({
49367
+ location: string,
49368
+ static: optional(boolean)
49369
+ }));
49370
+
48848
49371
  const parseWidgetsMap = (rawValue, ctx) => {
48849
49372
  if (!isObject(rawValue)) {
48850
49373
  return ctx.failure("PerseusWidgetsMap", rawValue);
@@ -48890,86 +49413,67 @@ const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
48890
49413
  case "expression":
48891
49414
  return parseAndAssign(`expression ${id}`, parseExpressionWidget);
48892
49415
  case "grapher":
48893
- // TODO(LEMS-2585): implement a real parser for this widget
48894
- return parseAndAssign(`grapher ${id}`, any);
49416
+ return parseAndAssign(`grapher ${id}`, parseGrapherWidget);
48895
49417
  case "group":
48896
- // TODO(LEMS-2585): implement a real parser for this widget
48897
- return parseAndAssign(`group ${id}`, any);
49418
+ return parseAndAssign(`group ${id}`, parseGroupWidget);
48898
49419
  case "graded-group":
48899
- // TODO(LEMS-2585): implement a real parser for this widget
48900
- return parseAndAssign(`graded-group ${id}`, any);
49420
+ return parseAndAssign(`graded-group ${id}`, parseGradedGroupWidget);
48901
49421
  case "graded-group-set":
48902
- // TODO(LEMS-2585): implement a real parser for this widget
48903
- return parseAndAssign(`graded-group-set ${id}`, any);
49422
+ return parseAndAssign(`graded-group-set ${id}`, parseGradedGroupSetWidget);
48904
49423
  case "iframe":
48905
- // TODO(LEMS-2585): implement a real parser for this widget
48906
- return parseAndAssign(`iframe ${id}`, any);
49424
+ return parseAndAssign(`iframe ${id}`, parseIframeWidget);
48907
49425
  case "image":
48908
- // TODO(LEMS-2585): implement a real parser for this widget
48909
- return parseAndAssign(`image ${id}`, any);
49426
+ return parseAndAssign(`image ${id}`, parseImageWidget);
48910
49427
  case "input-number":
48911
- // TODO(LEMS-2585): implement a real parser for this widget
48912
- return parseAndAssign(`input-number ${id}`, any);
49428
+ return parseAndAssign(`input-number ${id}`, parseInputNumberWidget);
48913
49429
  case "interaction":
48914
- // TODO(LEMS-2585): implement a real parser for this widget
48915
- return parseAndAssign(`interaction ${id}`, any);
49430
+ return parseAndAssign(`interaction ${id}`, parseInteractionWidget);
48916
49431
  case "interactive-graph":
48917
49432
  return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
48918
49433
  case "label-image":
48919
- // TODO(LEMS-2585): implement a real parser for this widget
48920
- return parseAndAssign(`label-image ${id}`, any);
49434
+ return parseAndAssign(`label-image ${id}`, parseLabelImageWidget);
48921
49435
  case "matcher":
48922
- // TODO(LEMS-2585): implement a real parser for this widget
48923
- return parseAndAssign(`matcher ${id}`, any);
49436
+ return parseAndAssign(`matcher ${id}`, parseMatcherWidget);
48924
49437
  case "matrix":
48925
- // TODO(LEMS-2585): implement a real parser for this widget
48926
- return parseAndAssign(`matrix ${id}`, any);
49438
+ return parseAndAssign(`matrix ${id}`, parseMatrixWidget);
48927
49439
  case "measurer":
48928
- // TODO(LEMS-2585): implement a real parser for this widget
48929
- return parseAndAssign(`measurer ${id}`, any);
49440
+ return parseAndAssign(`measurer ${id}`, parseMeasurerWidget);
48930
49441
  case "molecule-renderer":
48931
- // TODO(LEMS-2585): implement a real parser for this widget
48932
- return parseAndAssign(`molecule-renderer ${id}`, any);
49442
+ return parseAndAssign(`molecule-renderer ${id}`, parseMoleculeRendererWidget);
48933
49443
  case "number-line":
48934
- // TODO(LEMS-2585): implement a real parser for this widget
48935
- return parseAndAssign(`number-line ${id}`, any);
49444
+ return parseAndAssign(`number-line ${id}`, parseNumberLineWidget);
48936
49445
  case "numeric-input":
48937
- // TODO(LEMS-2585): implement a real parser for this widget
48938
- return parseAndAssign(`numeric-input ${id}`, any);
49446
+ return parseAndAssign(`numeric-input ${id}`, parseNumericInputWidget);
48939
49447
  case "orderer":
48940
- // TODO(LEMS-2585): implement a real parser for this widget
48941
- return parseAndAssign(`orderer ${id}`, any);
49448
+ return parseAndAssign(`orderer ${id}`, parseOrdererWidget);
48942
49449
  case "passage":
48943
- // TODO(LEMS-2585): implement a real parser for this widget
48944
- return parseAndAssign(`passage ${id}`, any);
49450
+ return parseAndAssign(`passage ${id}`, parsePassageWidget);
48945
49451
  case "passage-ref":
48946
- // TODO(LEMS-2585): implement a real parser for this widget
48947
- return parseAndAssign(`passage-ref ${id}`, any);
49452
+ return parseAndAssign(`passage-ref ${id}`, parsePassageRefWidget);
48948
49453
  case "passage-ref-target":
48949
- // TODO(LEMS-2585): implement a real parser for this widget
49454
+ // NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
49455
+ // used in test content. See:
49456
+ // https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
48950
49457
  return parseAndAssign(`passage-ref-target ${id}`, any);
48951
49458
  case "phet-simulation":
48952
- // TODO(LEMS-2585): implement a real parser for this widget
48953
- return parseAndAssign(`phet-simulation ${id}`, any);
49459
+ return parseAndAssign(`phet-simulation ${id}`, parsePhetSimulationWidget);
48954
49460
  case "plotter":
48955
- // TODO(LEMS-2585): implement a real parser for this widget
48956
- return parseAndAssign(`plotter ${id}`, any);
49461
+ return parseAndAssign(`plotter ${id}`, parsePlotterWidget);
48957
49462
  case "python-program":
48958
- // TODO(LEMS-2585): implement a real parser for this widget
48959
- return parseAndAssign(`python-program ${id}`, any);
49463
+ return parseAndAssign(`python-program ${id}`, parsePythonProgramWidget);
48960
49464
  case "radio":
48961
- // TODO(LEMS-2585): implement a real parser for this widget
48962
- return parseAndAssign(`radio ${id}`, any);
49465
+ return parseAndAssign(`radio ${id}`, parseRadioWidget);
48963
49466
  case "sorter":
48964
- // TODO(LEMS-2585): implement a real parser for this widget
48965
- return parseAndAssign(`sorter ${id}`, any);
49467
+ return parseAndAssign(`sorter ${id}`, parseSorterWidget);
48966
49468
  case "table":
48967
- // TODO(LEMS-2585): implement a real parser for this widget
48968
- return parseAndAssign(`table ${id}`, any);
49469
+ return parseAndAssign(`table ${id}`, parseTableWidget);
48969
49470
  case "video":
48970
- // TODO(LEMS-2585): implement a real parser for this widget
48971
- return parseAndAssign(`video ${id}`, any);
49471
+ return parseAndAssign(`video ${id}`, parseVideoWidget);
48972
49472
  default:
49473
+ if (getWidget(type)) {
49474
+ // @ts-expect-error - 'type' is not a valid widget type
49475
+ return parseAndAssign(`${type} ${id}`, any);
49476
+ }
48973
49477
  return ctx.failure("a valid widget type", type);
48974
49478
  }
48975
49479
  };
@@ -48992,16 +49496,6 @@ const parseHint = object({
48992
49496
  }))
48993
49497
  });
48994
49498
 
48995
- const parsePerseusRenderer = object({
48996
- content: string,
48997
- widgets: parseWidgetsMap,
48998
- metadata: optional(array(string)),
48999
- images: record(string, object({
49000
- width: number,
49001
- height: number
49002
- }))
49003
- });
49004
-
49005
49499
  object({
49006
49500
  question: parsePerseusRenderer,
49007
49501
  hints: array(parseHint),