@khanacademy/perseus 41.4.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 (35) hide show
  1. package/dist/es/index.css +3 -0
  2. package/dist/es/index.js +556 -77
  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 +556 -77
  7. package/dist/index.js.map +1 -1
  8. package/dist/server-item-renderer.d.ts +1 -1
  9. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +3 -0
  10. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +16 -0
  11. package/dist/util/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +3 -0
  12. package/dist/util/parse-perseus-json/perseus-parsers/group-widget.d.ts +3 -0
  13. package/dist/util/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +3 -0
  14. package/dist/util/parse-perseus-json/perseus-parsers/image-widget.d.ts +3 -0
  15. package/dist/util/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +3 -0
  16. package/dist/util/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +3 -0
  17. package/dist/util/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +3 -0
  18. package/dist/util/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +3 -0
  19. package/dist/util/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +3 -0
  20. package/dist/util/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +3 -0
  21. package/dist/util/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +3 -0
  22. package/dist/util/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +3 -0
  23. package/dist/util/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +3 -0
  24. package/dist/util/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +3 -0
  25. package/dist/util/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +3 -0
  26. package/dist/util/parse-perseus-json/perseus-parsers/passage-widget.d.ts +3 -0
  27. package/dist/util/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +3 -0
  28. package/dist/util/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +3 -0
  29. package/dist/util/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +3 -0
  30. package/dist/util/parse-perseus-json/perseus-parsers/radio-widget.d.ts +3 -0
  31. package/dist/util/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +3 -0
  32. package/dist/util/parse-perseus-json/perseus-parsers/table-widget.d.ts +3 -0
  33. package/dist/util/parse-perseus-json/perseus-parsers/video-widget.d.ts +3 -0
  34. package/dist/widgets/numeric-input/numeric-input.d.ts +7 -0
  35. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2712,7 +2712,7 @@ const keypadConfigurationForProps = widgetOptions => {
2712
2712
  times: widgetOptions.times
2713
2713
  };
2714
2714
  };
2715
- const propUpgrades$2 = {
2715
+ const propUpgrades$3 = {
2716
2716
  /* c8 ignore next */
2717
2717
  "1": v0props => ({
2718
2718
  times: v0props.times,
@@ -2772,7 +2772,7 @@ var Expression$1 = {
2772
2772
  major: 1,
2773
2773
  minor: 0
2774
2774
  },
2775
- propUpgrades: propUpgrades$2,
2775
+ propUpgrades: propUpgrades$3,
2776
2776
  // For use by the editor
2777
2777
  isLintable: true,
2778
2778
  scorer: scoreExpression,
@@ -18924,6 +18924,9 @@ const replaceDeprecatedWidgets = () => {
18924
18924
  replaceWidget("sequence", "deprecated-standin");
18925
18925
  replaceWidget("simulator", "deprecated-standin");
18926
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");
18927
18930
  };
18928
18931
  const registerEditors = editorsToRegister => {
18929
18932
  editorsToRegister.forEach(editor => {
@@ -18958,6 +18961,11 @@ const replaceDeprecatedEditors = () => {
18958
18961
  replaceEditor("sequence", "deprecated-standin");
18959
18962
  replaceEditor("simulator", "deprecated-standin");
18960
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");
18961
18969
  };
18962
18970
  const getWidget = name => {
18963
18971
  // TODO(alex): Consider referring to these as renderers to avoid
@@ -19004,10 +19012,8 @@ const getVersionVector = () => {
19004
19012
  const getPublicWidgets = () => {
19005
19013
  // TODO(alex): Update underscore.js so that _.pick can take a function.
19006
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.
19007
- return ___default["default"].pick(widgets,
19008
- // @ts-expect-error - TS2345 - Argument of type '(name: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
19009
- ___default["default"].reject(___default["default"].keys(widgets), function (name) {
19010
- 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";
19011
19017
  }));
19012
19018
  };
19013
19019
  const isAccessible = widgetInfo => {
@@ -22377,7 +22383,7 @@ class NumericInput extends React__namespace.Component {
22377
22383
  height: "auto"
22378
22384
  }
22379
22385
  });
22380
- return /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement(InputWithExamples, {
22386
+ return /*#__PURE__*/React__namespace.createElement(InputWithExamples, {
22381
22387
  ref: ref => this.inputRef = ref,
22382
22388
  value: this.props.currentValue,
22383
22389
  onChange: this.handleChange,
@@ -22389,7 +22395,7 @@ class NumericInput extends React__namespace.Component {
22389
22395
  id: this.props.widgetId,
22390
22396
  disabled: this.props.apiOptions.readOnly,
22391
22397
  style: styles.input
22392
- }));
22398
+ });
22393
22399
  }
22394
22400
  }
22395
22401
 
@@ -22447,13 +22453,65 @@ const propsTransform = function (widgetOptions) {
22447
22453
  });
22448
22454
  return rendererProps;
22449
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
+ };
22450
22503
  var NumericInput$1 = {
22451
22504
  name: "numeric-input",
22452
22505
  displayName: "Numeric input",
22453
22506
  defaultAlignment: "inline-block",
22454
22507
  accessible: true,
22455
22508
  widget: NumericInput,
22509
+ version: {
22510
+ major: 1,
22511
+ minor: 0
22512
+ },
22456
22513
  transform: propsTransform,
22514
+ propUpgrades: propUpgrades$2,
22457
22515
  isLintable: true,
22458
22516
  scorer: scoreNumericInput,
22459
22517
  getOneCorrectAnswerFromRubric(rubric) {
@@ -32259,17 +32317,12 @@ const ItemExtras = [
32259
32317
  // prettier-ignore
32260
32318
 
32261
32319
  // prettier-ignore
32262
-
32320
+ // While this widget is deprecated, we still need the type for conversion purposes
32263
32321
  // prettier-ignore
32264
-
32265
32322
  // prettier-ignore
32266
-
32267
32323
  // prettier-ignore
32268
-
32269
32324
  //prettier-ignore
32270
-
32271
32325
  // A background image applied to various widgets.
32272
-
32273
32326
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
32274
32327
 
32275
32328
  // 2D range: xMin, xMax, yMin, yMax
@@ -44986,7 +45039,7 @@ const init = function () {
44986
45039
 
44987
45040
  // This file is processed by a Rollup plugin (replace) to inject the production
44988
45041
  const libName = "@khanacademy/perseus";
44989
- const libVersion = "41.4.0";
45042
+ const libVersion = "41.5.0";
44990
45043
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44991
45044
 
44992
45045
  const apiVersion = {
@@ -45679,7 +45732,7 @@ class ServerItemRenderer extends React__namespace.Component {
45679
45732
 
45680
45733
  /**
45681
45734
  * Accepts a question area widgetId, or an answer area widgetId of
45682
- * the form "answer-input-number 1", or the string "answer-area"
45735
+ * the form "answer-numeric-input 1", or the string "answer-area"
45683
45736
  * for the whole answer area (if the answer area is a single widget).
45684
45737
  */
45685
45738
  _setWidgetProps(widgetId, newProps, callback) {
@@ -48663,6 +48716,118 @@ const parseExpressionWidget = parseWidget(constant("expression"), object({
48663
48716
  buttonsVisible: optional(enumeration("always", "never", "focused"))
48664
48717
  }));
48665
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
+
48666
48831
  const parsePerseusImageBackground = object({
48667
48832
  url: optional(nullable(string)),
48668
48833
  width: optional(number),
@@ -48674,6 +48839,160 @@ const parsePerseusImageBackground = object({
48674
48839
  scale: optional(union(number).or(string).parser)
48675
48840
  });
48676
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
+
48677
48996
  // Used to represent 2-D points and ranges
48678
48997
  const pairOfNumbers = pair(number, number);
48679
48998
  const parsePerseusGraphTypeAngle = object({
@@ -48860,6 +49179,195 @@ const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), o
48860
49179
  fullGraphAriaDescription: optional(string)
48861
49180
  }));
48862
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
+
48863
49371
  const parseWidgetsMap = (rawValue, ctx) => {
48864
49372
  if (!isObject(rawValue)) {
48865
49373
  return ctx.failure("PerseusWidgetsMap", rawValue);
@@ -48905,86 +49413,67 @@ const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
48905
49413
  case "expression":
48906
49414
  return parseAndAssign(`expression ${id}`, parseExpressionWidget);
48907
49415
  case "grapher":
48908
- // TODO(LEMS-2585): implement a real parser for this widget
48909
- return parseAndAssign(`grapher ${id}`, any);
49416
+ return parseAndAssign(`grapher ${id}`, parseGrapherWidget);
48910
49417
  case "group":
48911
- // TODO(LEMS-2585): implement a real parser for this widget
48912
- return parseAndAssign(`group ${id}`, any);
49418
+ return parseAndAssign(`group ${id}`, parseGroupWidget);
48913
49419
  case "graded-group":
48914
- // TODO(LEMS-2585): implement a real parser for this widget
48915
- return parseAndAssign(`graded-group ${id}`, any);
49420
+ return parseAndAssign(`graded-group ${id}`, parseGradedGroupWidget);
48916
49421
  case "graded-group-set":
48917
- // TODO(LEMS-2585): implement a real parser for this widget
48918
- return parseAndAssign(`graded-group-set ${id}`, any);
49422
+ return parseAndAssign(`graded-group-set ${id}`, parseGradedGroupSetWidget);
48919
49423
  case "iframe":
48920
- // TODO(LEMS-2585): implement a real parser for this widget
48921
- return parseAndAssign(`iframe ${id}`, any);
49424
+ return parseAndAssign(`iframe ${id}`, parseIframeWidget);
48922
49425
  case "image":
48923
- // TODO(LEMS-2585): implement a real parser for this widget
48924
- return parseAndAssign(`image ${id}`, any);
49426
+ return parseAndAssign(`image ${id}`, parseImageWidget);
48925
49427
  case "input-number":
48926
- // TODO(LEMS-2585): implement a real parser for this widget
48927
- return parseAndAssign(`input-number ${id}`, any);
49428
+ return parseAndAssign(`input-number ${id}`, parseInputNumberWidget);
48928
49429
  case "interaction":
48929
- // TODO(LEMS-2585): implement a real parser for this widget
48930
- return parseAndAssign(`interaction ${id}`, any);
49430
+ return parseAndAssign(`interaction ${id}`, parseInteractionWidget);
48931
49431
  case "interactive-graph":
48932
49432
  return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
48933
49433
  case "label-image":
48934
- // TODO(LEMS-2585): implement a real parser for this widget
48935
- return parseAndAssign(`label-image ${id}`, any);
49434
+ return parseAndAssign(`label-image ${id}`, parseLabelImageWidget);
48936
49435
  case "matcher":
48937
- // TODO(LEMS-2585): implement a real parser for this widget
48938
- return parseAndAssign(`matcher ${id}`, any);
49436
+ return parseAndAssign(`matcher ${id}`, parseMatcherWidget);
48939
49437
  case "matrix":
48940
- // TODO(LEMS-2585): implement a real parser for this widget
48941
- return parseAndAssign(`matrix ${id}`, any);
49438
+ return parseAndAssign(`matrix ${id}`, parseMatrixWidget);
48942
49439
  case "measurer":
48943
- // TODO(LEMS-2585): implement a real parser for this widget
48944
- return parseAndAssign(`measurer ${id}`, any);
49440
+ return parseAndAssign(`measurer ${id}`, parseMeasurerWidget);
48945
49441
  case "molecule-renderer":
48946
- // TODO(LEMS-2585): implement a real parser for this widget
48947
- return parseAndAssign(`molecule-renderer ${id}`, any);
49442
+ return parseAndAssign(`molecule-renderer ${id}`, parseMoleculeRendererWidget);
48948
49443
  case "number-line":
48949
- // TODO(LEMS-2585): implement a real parser for this widget
48950
- return parseAndAssign(`number-line ${id}`, any);
49444
+ return parseAndAssign(`number-line ${id}`, parseNumberLineWidget);
48951
49445
  case "numeric-input":
48952
- // TODO(LEMS-2585): implement a real parser for this widget
48953
- return parseAndAssign(`numeric-input ${id}`, any);
49446
+ return parseAndAssign(`numeric-input ${id}`, parseNumericInputWidget);
48954
49447
  case "orderer":
48955
- // TODO(LEMS-2585): implement a real parser for this widget
48956
- return parseAndAssign(`orderer ${id}`, any);
49448
+ return parseAndAssign(`orderer ${id}`, parseOrdererWidget);
48957
49449
  case "passage":
48958
- // TODO(LEMS-2585): implement a real parser for this widget
48959
- return parseAndAssign(`passage ${id}`, any);
49450
+ return parseAndAssign(`passage ${id}`, parsePassageWidget);
48960
49451
  case "passage-ref":
48961
- // TODO(LEMS-2585): implement a real parser for this widget
48962
- return parseAndAssign(`passage-ref ${id}`, any);
49452
+ return parseAndAssign(`passage-ref ${id}`, parsePassageRefWidget);
48963
49453
  case "passage-ref-target":
48964
- // 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
48965
49457
  return parseAndAssign(`passage-ref-target ${id}`, any);
48966
49458
  case "phet-simulation":
48967
- // TODO(LEMS-2585): implement a real parser for this widget
48968
- return parseAndAssign(`phet-simulation ${id}`, any);
49459
+ return parseAndAssign(`phet-simulation ${id}`, parsePhetSimulationWidget);
48969
49460
  case "plotter":
48970
- // TODO(LEMS-2585): implement a real parser for this widget
48971
- return parseAndAssign(`plotter ${id}`, any);
49461
+ return parseAndAssign(`plotter ${id}`, parsePlotterWidget);
48972
49462
  case "python-program":
48973
- // TODO(LEMS-2585): implement a real parser for this widget
48974
- return parseAndAssign(`python-program ${id}`, any);
49463
+ return parseAndAssign(`python-program ${id}`, parsePythonProgramWidget);
48975
49464
  case "radio":
48976
- // TODO(LEMS-2585): implement a real parser for this widget
48977
- return parseAndAssign(`radio ${id}`, any);
49465
+ return parseAndAssign(`radio ${id}`, parseRadioWidget);
48978
49466
  case "sorter":
48979
- // TODO(LEMS-2585): implement a real parser for this widget
48980
- return parseAndAssign(`sorter ${id}`, any);
49467
+ return parseAndAssign(`sorter ${id}`, parseSorterWidget);
48981
49468
  case "table":
48982
- // TODO(LEMS-2585): implement a real parser for this widget
48983
- return parseAndAssign(`table ${id}`, any);
49469
+ return parseAndAssign(`table ${id}`, parseTableWidget);
48984
49470
  case "video":
48985
- // TODO(LEMS-2585): implement a real parser for this widget
48986
- return parseAndAssign(`video ${id}`, any);
49471
+ return parseAndAssign(`video ${id}`, parseVideoWidget);
48987
49472
  default:
49473
+ if (getWidget(type)) {
49474
+ // @ts-expect-error - 'type' is not a valid widget type
49475
+ return parseAndAssign(`${type} ${id}`, any);
49476
+ }
48988
49477
  return ctx.failure("a valid widget type", type);
48989
49478
  }
48990
49479
  };
@@ -49007,16 +49496,6 @@ const parseHint = object({
49007
49496
  }))
49008
49497
  });
49009
49498
 
49010
- const parsePerseusRenderer = object({
49011
- content: string,
49012
- widgets: parseWidgetsMap,
49013
- metadata: optional(array(string)),
49014
- images: record(string, object({
49015
- width: number,
49016
- height: number
49017
- }))
49018
- });
49019
-
49020
49499
  object({
49021
49500
  question: parsePerseusRenderer,
49022
49501
  hints: array(parseHint),