@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/es/index.js CHANGED
@@ -2591,7 +2591,7 @@ const keypadConfigurationForProps = widgetOptions => {
2591
2591
  times: widgetOptions.times
2592
2592
  };
2593
2593
  };
2594
- const propUpgrades$2 = {
2594
+ const propUpgrades$3 = {
2595
2595
  /* c8 ignore next */
2596
2596
  "1": v0props => ({
2597
2597
  times: v0props.times,
@@ -2651,7 +2651,7 @@ var Expression$1 = {
2651
2651
  major: 1,
2652
2652
  minor: 0
2653
2653
  },
2654
- propUpgrades: propUpgrades$2,
2654
+ propUpgrades: propUpgrades$3,
2655
2655
  // For use by the editor
2656
2656
  isLintable: true,
2657
2657
  scorer: scoreExpression,
@@ -18723,6 +18723,9 @@ const replaceDeprecatedWidgets = () => {
18723
18723
  replaceWidget("sequence", "deprecated-standin");
18724
18724
  replaceWidget("simulator", "deprecated-standin");
18725
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");
18726
18729
  };
18727
18730
  const registerEditors = editorsToRegister => {
18728
18731
  editorsToRegister.forEach(editor => {
@@ -18757,6 +18760,11 @@ const replaceDeprecatedEditors = () => {
18757
18760
  replaceEditor("sequence", "deprecated-standin");
18758
18761
  replaceEditor("simulator", "deprecated-standin");
18759
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");
18760
18768
  };
18761
18769
  const getWidget = name => {
18762
18770
  var _widgets$name;
@@ -18807,10 +18815,8 @@ const getVersionVector = () => {
18807
18815
  const getPublicWidgets = () => {
18808
18816
  // TODO(alex): Update underscore.js so that _.pick can take a function.
18809
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.
18810
- return _.pick(widgets,
18811
- // @ts-expect-error - TS2345 - Argument of type '(name: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
18812
- _.reject(_.keys(widgets), function (name) {
18813
- return widgets[name].hidden;
18818
+ return _.pick(widgets, _.reject(_.keys(widgets), function (name) {
18819
+ return widgets[name].hidden || name === "input-number";
18814
18820
  }));
18815
18821
  };
18816
18822
  const isAccessible = widgetInfo => {
@@ -22150,7 +22156,7 @@ class NumericInput extends React.Component {
22150
22156
  height: "auto"
22151
22157
  }
22152
22158
  });
22153
- return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(InputWithExamples, {
22159
+ return /*#__PURE__*/React.createElement(InputWithExamples, {
22154
22160
  ref: ref => this.inputRef = ref,
22155
22161
  value: this.props.currentValue,
22156
22162
  onChange: this.handleChange,
@@ -22162,7 +22168,7 @@ class NumericInput extends React.Component {
22162
22168
  id: this.props.widgetId,
22163
22169
  disabled: this.props.apiOptions.readOnly,
22164
22170
  style: styles.input
22165
- }));
22171
+ });
22166
22172
  }
22167
22173
  }
22168
22174
 
@@ -22231,13 +22237,65 @@ const propsTransform = function propsTransform(widgetOptions) {
22231
22237
  });
22232
22238
  return rendererProps;
22233
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
+ };
22234
22287
  var NumericInput$1 = {
22235
22288
  name: "numeric-input",
22236
22289
  displayName: "Numeric input",
22237
22290
  defaultAlignment: "inline-block",
22238
22291
  accessible: true,
22239
22292
  widget: NumericInput,
22293
+ version: {
22294
+ major: 1,
22295
+ minor: 0
22296
+ },
22240
22297
  transform: propsTransform,
22298
+ propUpgrades: propUpgrades$2,
22241
22299
  isLintable: true,
22242
22300
  scorer: scoreNumericInput,
22243
22301
  getOneCorrectAnswerFromRubric(rubric) {
@@ -31957,17 +32015,12 @@ const ItemExtras = [
31957
32015
  // prettier-ignore
31958
32016
 
31959
32017
  // prettier-ignore
31960
-
32018
+ // While this widget is deprecated, we still need the type for conversion purposes
31961
32019
  // prettier-ignore
31962
-
31963
32020
  // prettier-ignore
31964
-
31965
32021
  // prettier-ignore
31966
-
31967
32022
  //prettier-ignore
31968
-
31969
32023
  // A background image applied to various widgets.
31970
-
31971
32024
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
31972
32025
 
31973
32026
  // 2D range: xMin, xMax, yMin, yMax
@@ -44624,7 +44677,7 @@ const init = function init() {
44624
44677
 
44625
44678
  // This file is processed by a Rollup plugin (replace) to inject the production
44626
44679
  const libName = "@khanacademy/perseus";
44627
- const libVersion = "41.4.0";
44680
+ const libVersion = "41.5.0";
44628
44681
  addLibraryVersionToPerseusDebug(libName, libVersion);
44629
44682
 
44630
44683
  const apiVersion = {
@@ -45328,7 +45381,7 @@ class ServerItemRenderer extends React.Component {
45328
45381
 
45329
45382
  /**
45330
45383
  * Accepts a question area widgetId, or an answer area widgetId of
45331
- * the form "answer-input-number 1", or the string "answer-area"
45384
+ * the form "answer-numeric-input 1", or the string "answer-area"
45332
45385
  * for the whole answer area (if the answer area is a single widget).
45333
45386
  */
45334
45387
  _setWidgetProps(widgetId, newProps, callback) {
@@ -48283,6 +48336,118 @@ const parseExpressionWidget = parseWidget(constant("expression"), object({
48283
48336
  buttonsVisible: optional(enumeration("always", "never", "focused"))
48284
48337
  }));
48285
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
+
48286
48451
  const parsePerseusImageBackground = object({
48287
48452
  url: optional(nullable(string)),
48288
48453
  width: optional(number),
@@ -48294,6 +48459,160 @@ const parsePerseusImageBackground = object({
48294
48459
  scale: optional(union(number).or(string).parser)
48295
48460
  });
48296
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
+
48297
48616
  // Used to represent 2-D points and ranges
48298
48617
  const pairOfNumbers = pair(number, number);
48299
48618
  const parsePerseusGraphTypeAngle = object({
@@ -48480,6 +48799,195 @@ const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), o
48480
48799
  fullGraphAriaDescription: optional(string)
48481
48800
  }));
48482
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
+
48483
48991
  const parseWidgetsMap = (rawValue, ctx) => {
48484
48992
  if (!isObject(rawValue)) {
48485
48993
  return ctx.failure("PerseusWidgetsMap", rawValue);
@@ -48524,86 +49032,67 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
48524
49032
  case "expression":
48525
49033
  return parseAndAssign(`expression ${id}`, parseExpressionWidget);
48526
49034
  case "grapher":
48527
- // TODO(LEMS-2585): implement a real parser for this widget
48528
- return parseAndAssign(`grapher ${id}`, any);
49035
+ return parseAndAssign(`grapher ${id}`, parseGrapherWidget);
48529
49036
  case "group":
48530
- // TODO(LEMS-2585): implement a real parser for this widget
48531
- return parseAndAssign(`group ${id}`, any);
49037
+ return parseAndAssign(`group ${id}`, parseGroupWidget);
48532
49038
  case "graded-group":
48533
- // TODO(LEMS-2585): implement a real parser for this widget
48534
- return parseAndAssign(`graded-group ${id}`, any);
49039
+ return parseAndAssign(`graded-group ${id}`, parseGradedGroupWidget);
48535
49040
  case "graded-group-set":
48536
- // TODO(LEMS-2585): implement a real parser for this widget
48537
- return parseAndAssign(`graded-group-set ${id}`, any);
49041
+ return parseAndAssign(`graded-group-set ${id}`, parseGradedGroupSetWidget);
48538
49042
  case "iframe":
48539
- // TODO(LEMS-2585): implement a real parser for this widget
48540
- return parseAndAssign(`iframe ${id}`, any);
49043
+ return parseAndAssign(`iframe ${id}`, parseIframeWidget);
48541
49044
  case "image":
48542
- // TODO(LEMS-2585): implement a real parser for this widget
48543
- return parseAndAssign(`image ${id}`, any);
49045
+ return parseAndAssign(`image ${id}`, parseImageWidget);
48544
49046
  case "input-number":
48545
- // TODO(LEMS-2585): implement a real parser for this widget
48546
- return parseAndAssign(`input-number ${id}`, any);
49047
+ return parseAndAssign(`input-number ${id}`, parseInputNumberWidget);
48547
49048
  case "interaction":
48548
- // TODO(LEMS-2585): implement a real parser for this widget
48549
- return parseAndAssign(`interaction ${id}`, any);
49049
+ return parseAndAssign(`interaction ${id}`, parseInteractionWidget);
48550
49050
  case "interactive-graph":
48551
49051
  return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
48552
49052
  case "label-image":
48553
- // TODO(LEMS-2585): implement a real parser for this widget
48554
- return parseAndAssign(`label-image ${id}`, any);
49053
+ return parseAndAssign(`label-image ${id}`, parseLabelImageWidget);
48555
49054
  case "matcher":
48556
- // TODO(LEMS-2585): implement a real parser for this widget
48557
- return parseAndAssign(`matcher ${id}`, any);
49055
+ return parseAndAssign(`matcher ${id}`, parseMatcherWidget);
48558
49056
  case "matrix":
48559
- // TODO(LEMS-2585): implement a real parser for this widget
48560
- return parseAndAssign(`matrix ${id}`, any);
49057
+ return parseAndAssign(`matrix ${id}`, parseMatrixWidget);
48561
49058
  case "measurer":
48562
- // TODO(LEMS-2585): implement a real parser for this widget
48563
- return parseAndAssign(`measurer ${id}`, any);
49059
+ return parseAndAssign(`measurer ${id}`, parseMeasurerWidget);
48564
49060
  case "molecule-renderer":
48565
- // TODO(LEMS-2585): implement a real parser for this widget
48566
- return parseAndAssign(`molecule-renderer ${id}`, any);
49061
+ return parseAndAssign(`molecule-renderer ${id}`, parseMoleculeRendererWidget);
48567
49062
  case "number-line":
48568
- // TODO(LEMS-2585): implement a real parser for this widget
48569
- return parseAndAssign(`number-line ${id}`, any);
49063
+ return parseAndAssign(`number-line ${id}`, parseNumberLineWidget);
48570
49064
  case "numeric-input":
48571
- // TODO(LEMS-2585): implement a real parser for this widget
48572
- return parseAndAssign(`numeric-input ${id}`, any);
49065
+ return parseAndAssign(`numeric-input ${id}`, parseNumericInputWidget);
48573
49066
  case "orderer":
48574
- // TODO(LEMS-2585): implement a real parser for this widget
48575
- return parseAndAssign(`orderer ${id}`, any);
49067
+ return parseAndAssign(`orderer ${id}`, parseOrdererWidget);
48576
49068
  case "passage":
48577
- // TODO(LEMS-2585): implement a real parser for this widget
48578
- return parseAndAssign(`passage ${id}`, any);
49069
+ return parseAndAssign(`passage ${id}`, parsePassageWidget);
48579
49070
  case "passage-ref":
48580
- // TODO(LEMS-2585): implement a real parser for this widget
48581
- return parseAndAssign(`passage-ref ${id}`, any);
49071
+ return parseAndAssign(`passage-ref ${id}`, parsePassageRefWidget);
48582
49072
  case "passage-ref-target":
48583
- // 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
48584
49076
  return parseAndAssign(`passage-ref-target ${id}`, any);
48585
49077
  case "phet-simulation":
48586
- // TODO(LEMS-2585): implement a real parser for this widget
48587
- return parseAndAssign(`phet-simulation ${id}`, any);
49078
+ return parseAndAssign(`phet-simulation ${id}`, parsePhetSimulationWidget);
48588
49079
  case "plotter":
48589
- // TODO(LEMS-2585): implement a real parser for this widget
48590
- return parseAndAssign(`plotter ${id}`, any);
49080
+ return parseAndAssign(`plotter ${id}`, parsePlotterWidget);
48591
49081
  case "python-program":
48592
- // TODO(LEMS-2585): implement a real parser for this widget
48593
- return parseAndAssign(`python-program ${id}`, any);
49082
+ return parseAndAssign(`python-program ${id}`, parsePythonProgramWidget);
48594
49083
  case "radio":
48595
- // TODO(LEMS-2585): implement a real parser for this widget
48596
- return parseAndAssign(`radio ${id}`, any);
49084
+ return parseAndAssign(`radio ${id}`, parseRadioWidget);
48597
49085
  case "sorter":
48598
- // TODO(LEMS-2585): implement a real parser for this widget
48599
- return parseAndAssign(`sorter ${id}`, any);
49086
+ return parseAndAssign(`sorter ${id}`, parseSorterWidget);
48600
49087
  case "table":
48601
- // TODO(LEMS-2585): implement a real parser for this widget
48602
- return parseAndAssign(`table ${id}`, any);
49088
+ return parseAndAssign(`table ${id}`, parseTableWidget);
48603
49089
  case "video":
48604
- // TODO(LEMS-2585): implement a real parser for this widget
48605
- return parseAndAssign(`video ${id}`, any);
49090
+ return parseAndAssign(`video ${id}`, parseVideoWidget);
48606
49091
  default:
49092
+ if (getWidget(type)) {
49093
+ // @ts-expect-error - 'type' is not a valid widget type
49094
+ return parseAndAssign(`${type} ${id}`, any);
49095
+ }
48607
49096
  return ctx.failure("a valid widget type", type);
48608
49097
  }
48609
49098
  };
@@ -48626,16 +49115,6 @@ const parseHint = object({
48626
49115
  }))
48627
49116
  });
48628
49117
 
48629
- const parsePerseusRenderer = object({
48630
- content: string,
48631
- widgets: parseWidgetsMap,
48632
- metadata: optional(array(string)),
48633
- images: record(string, object({
48634
- width: number,
48635
- height: number
48636
- }))
48637
- });
48638
-
48639
49118
  object({
48640
49119
  question: parsePerseusRenderer,
48641
49120
  hints: array(parseHint),