@khanacademy/perseus-core 6.0.0 → 7.0.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.
package/dist/index.js CHANGED
@@ -1,19 +1,52 @@
1
- import _ from 'underscore';
2
- import _extends from '@babel/runtime/helpers/extends';
3
- import * as KAS from '@khanacademy/kas';
4
- import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
5
- import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-utils';
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _ = require('underscore');
6
+ var KAS = require('@khanacademy/kas');
7
+ var perseusUtils = require('@khanacademy/perseus-utils');
8
+
9
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
10
+
11
+ function _interopNamespaceCompat(e) {
12
+ if (e && typeof e === 'object' && 'default' in e) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var ___default = /*#__PURE__*/_interopDefaultCompat(_);
30
+ var KAS__namespace = /*#__PURE__*/_interopNamespaceCompat(KAS);
6
31
 
7
32
  function getMatrixSize(matrix) {
8
33
  const matrixSize = [1, 1];
9
- _(matrix).each((matrixRow, row) => {
34
+
35
+ // We need to find the widest row and tallest column to get the correct
36
+ // matrix size.
37
+ ___default.default(matrix).each((matrixRow, row) => {
10
38
  let rowWidth = 0;
11
- _(matrixRow).each((matrixCol, col) => {
39
+ ___default.default(matrixRow).each((matrixCol, col) => {
40
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
12
41
  if (matrixCol != null && matrixCol.toString().length) {
13
42
  rowWidth = col + 1;
14
43
  }
15
44
  });
45
+
46
+ // Matrix width:
16
47
  matrixSize[1] = Math.max(matrixSize[1], rowWidth);
48
+
49
+ // Matrix height:
17
50
  if (rowWidth > 0) {
18
51
  matrixSize[0] = Math.max(matrixSize[0], row + 1);
19
52
  }
@@ -21,24 +54,47 @@ function getMatrixSize(matrix) {
21
54
  return matrixSize;
22
55
  }
23
56
 
57
+ /**
58
+ * Get the character used for separating decimals.
59
+ */
24
60
  const getDecimalSeparator = locale => {
25
- var _match$;
26
61
  switch (locale) {
62
+ // TODO(somewhatabstract): Remove this when Chrome supports the `ka`
63
+ // locale properly.
64
+ // https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
65
+ //
66
+ // Supported locales in Chrome:
67
+ // https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
27
68
  case "ka":
28
69
  return ",";
29
70
  default:
30
71
  const numberWithDecimalSeparator = 1.1;
31
- const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator).match(/[^\d\u0661\u06F1\u0967\u09e7]/);
32
- return (_match$ = match == null ? void 0 : match[0]) != null ? _match$ : ".";
72
+ // TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
73
+ // support Safari 12.
74
+ const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
75
+ // 0x661 is ARABIC-INDIC DIGIT ONE
76
+ // 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
77
+ // 0x967 is DEVANAGARI DIGIT ONE
78
+ // 0x9e7 is BENGALI/BANGLA DIGIT ONE
79
+ .match(/[^\d\u0661\u06F1\u0967\u09e7]/);
80
+ return match?.[0] ?? ".";
33
81
  }
34
82
  };
35
83
 
84
+ /**
85
+ * APPROXIMATE equality on numbers and primitives.
86
+ */
36
87
  function approximateEqual(x, y) {
37
88
  if (typeof x === "number" && typeof y === "number") {
38
89
  return Math.abs(x - y) < 1e-9;
39
90
  }
40
91
  return x === y;
41
92
  }
93
+
94
+ /**
95
+ * Deep APPROXIMATE equality on primitives, numbers, arrays, and objects.
96
+ * Recursive.
97
+ */
42
98
  function approximateDeepEqual(x, y) {
43
99
  if (Array.isArray(x) && Array.isArray(y)) {
44
100
  if (x.length !== y.length) {
@@ -61,9 +117,11 @@ function approximateDeepEqual(x, y) {
61
117
  return false;
62
118
  }
63
119
  if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
64
- return x === y || _.all(x, function (v, k) {
120
+ return x === y || ___default.default.all(x, function (v, k) {
121
+ // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
65
122
  return approximateDeepEqual(y[k], v);
66
- }) && _.all(y, function (v, k) {
123
+ }) && ___default.default.all(y, function (v, k) {
124
+ // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
67
125
  return approximateDeepEqual(x[k], v);
68
126
  });
69
127
  }
@@ -73,12 +131,38 @@ function approximateDeepEqual(x, y) {
73
131
  return approximateEqual(x, y);
74
132
  }
75
133
 
134
+ /**
135
+ * Add a widget placeholder using the widget ID.
136
+ * ex. addWidget("radio 1") => "[[☃ radio 1]]"
137
+ *
138
+ * @param {string} id
139
+ * @returns {string}
140
+ */
76
141
  function addWidget(id) {
77
142
  return `[[☃ ${id}]]`;
78
143
  }
144
+
145
+ /**
146
+ * Regex for widget placeholders in a string.
147
+ *
148
+ * First capture group is the widget ID (ex. 'radio 1')
149
+ * Second capture group is the widget type (ex. "radio)
150
+ * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
151
+ */
79
152
  function getWidgetRegex() {
80
153
  return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
81
154
  }
155
+
156
+ /**
157
+ * Extract all widget IDs, which includes the widget type and instance number.
158
+ * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
159
+ *
160
+ * Content should contain Perseus widget placeholders,
161
+ * which look like: '[[☃ radio 1]]'.
162
+ *
163
+ * @param {string} content
164
+ * @returns {ReadonlyArray<string>} widgetIds
165
+ */
82
166
  function getWidgetIdsFromContent(content) {
83
167
  const widgets = [];
84
168
  const localWidgetRegex = getWidgetRegex();
@@ -89,18 +173,31 @@ function getWidgetIdsFromContent(content) {
89
173
  }
90
174
  return widgets;
91
175
  }
176
+
177
+ /**
178
+ * Get a list of widget IDs from content,
179
+ * but only for specific widget types
180
+ *
181
+ * @param {string} type the type of widget (ie "radio")
182
+ * @param {string} content the string to parse
183
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
184
+ * @returns {ReadonlyArray<string>} the widget type (ie "radio")
185
+ */
92
186
  function getWidgetIdsFromContentByType(type, content, widgetMap) {
93
187
  const rv = [];
94
188
  const widgetIdsInContent = getWidgetIdsFromContent(content);
95
189
  widgetIdsInContent.forEach(widgetId => {
96
190
  const widget = widgetMap[widgetId];
97
- if ((widget == null ? void 0 : widget.type) === type) {
191
+ if (widget?.type === type) {
98
192
  rv.push(widgetId);
99
193
  }
100
194
  });
101
195
  return rv;
102
196
  }
103
197
 
198
+ // TODO(benchristel): in the future, we may want to make deepClone work for
199
+ // Record<string, Cloneable> as well. Currently, it only does arrays.
200
+
104
201
  function deepClone(obj) {
105
202
  if (Array.isArray(obj)) {
106
203
  return obj.map(deepClone);
@@ -113,22 +210,33 @@ const MOVABLES = {
113
210
  PARABOLA: "PARABOLA",
114
211
  SINUSOID: "SINUSOID"
115
212
  };
213
+
214
+ // TODO(charlie): These really need to go into a utility file as they're being
215
+ // used by both interactive-graph and now grapher.
116
216
  function canonicalSineCoefficients(coeffs) {
217
+ // For a curve of the form f(x) = a * Sin(b * x - c) + d,
218
+ // this function ensures that a, b > 0, and c is its
219
+ // smallest possible positive value.
117
220
  let amplitude = coeffs[0];
118
221
  let angularFrequency = coeffs[1];
119
222
  let phase = coeffs[2];
120
223
  const verticalOffset = coeffs[3];
224
+
225
+ // Guarantee a > 0
121
226
  if (amplitude < 0) {
122
227
  amplitude *= -1;
123
228
  angularFrequency *= -1;
124
229
  phase *= -1;
125
230
  }
126
231
  const period = 2 * Math.PI;
232
+ // Guarantee b > 0
127
233
  if (angularFrequency < 0) {
128
234
  angularFrequency *= -1;
129
235
  phase *= -1;
130
236
  phase += period / 2;
131
237
  }
238
+
239
+ // Guarantee c is smallest possible positive value
132
240
  while (phase > 0) {
133
241
  phase -= period;
134
242
  }
@@ -138,21 +246,29 @@ function canonicalSineCoefficients(coeffs) {
138
246
  return [amplitude, angularFrequency, phase, verticalOffset];
139
247
  }
140
248
  function canonicalTangentCoefficients(coeffs) {
249
+ // For a curve of the form f(x) = a * Tan(b * x - c) + d,
250
+ // this function ensures that a, b > 0, and c is its
251
+ // smallest possible positive value.
141
252
  let amplitude = coeffs[0];
142
253
  let angularFrequency = coeffs[1];
143
254
  let phase = coeffs[2];
144
255
  const verticalOffset = coeffs[3];
256
+
257
+ // Guarantee a > 0
145
258
  if (amplitude < 0) {
146
259
  amplitude *= -1;
147
260
  angularFrequency *= -1;
148
261
  phase *= -1;
149
262
  }
150
263
  const period = Math.PI;
264
+ // Guarantee b > 0
151
265
  if (angularFrequency < 0) {
152
266
  angularFrequency *= -1;
153
267
  phase *= -1;
154
268
  phase += period / 2;
155
269
  }
270
+
271
+ // Guarantee c is smallest possible positive value
156
272
  while (phase > 0) {
157
273
  phase -= period;
158
274
  }
@@ -168,11 +284,12 @@ const PlotDefaults = {
168
284
  movable: MOVABLES.PLOT,
169
285
  getPropsForCoeffs: function (coeffs) {
170
286
  return {
171
- fn: _.partial(this.getFunctionForCoeffs, coeffs)
287
+ // @ts-expect-error - TS2339 - Property 'getFunctionForCoeffs' does not exist on type '{ readonly areEqual: (coeffs1: any, coeffs2: any) => boolean; readonly Movable: any; readonly getPropsForCoeffs: (coeffs: any) => any; }'.
288
+ fn: ___default.default.partial(this.getFunctionForCoeffs, coeffs)
172
289
  };
173
290
  }
174
291
  };
175
- const Linear = _.extend({}, PlotDefaults, {
292
+ const Linear = ___default.default.extend({}, PlotDefaults, {
176
293
  url: "https://ka-perseus-graphie.s3.amazonaws.com/67aaf581e6d9ef9038c10558a1f70ac21c11c9f8.png",
177
294
  defaultCoords: [[0.25, 0.75], [0.75, 0.75]],
178
295
  getCoefficients: function (coords) {
@@ -199,15 +316,19 @@ const Linear = _.extend({}, PlotDefaults, {
199
316
  return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
200
317
  }
201
318
  });
202
- const Quadratic = _.extend({}, PlotDefaults, {
319
+ const Quadratic = ___default.default.extend({}, PlotDefaults, {
203
320
  url: "https://ka-perseus-graphie.s3.amazonaws.com/e23d36e6fc29ee37174e92c9daba2a66677128ab.png",
204
321
  defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
205
322
  movable: MOVABLES.PARABOLA,
206
323
  getCoefficients: function (coords) {
207
324
  const p1 = coords[0];
208
325
  const p2 = coords[1];
326
+
327
+ // Parabola with vertex (h, k) has form: y = a * (h - k)^2 + k
209
328
  const h = p1[0];
210
329
  const k = p1[1];
330
+
331
+ // Use these to calculate familiar a, b, c
211
332
  const a = (p2[1] - k) / ((p2[0] - h) * (p2[0] - h));
212
333
  const b = -2 * h * a;
213
334
  const c = a * h * h + k;
@@ -234,7 +355,7 @@ const Quadratic = _.extend({}, PlotDefaults, {
234
355
  return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
235
356
  }
236
357
  });
237
- const Sinusoid = _.extend({}, PlotDefaults, {
358
+ const Sinusoid = ___default.default.extend({}, PlotDefaults, {
238
359
  url: "https://ka-perseus-graphie.s3.amazonaws.com/3d68e7718498475f53b206c2ab285626baf8857e.png",
239
360
  defaultCoords: [[0.5, 0.5], [0.6, 0.6]],
240
361
  movable: MOVABLES.SINUSOID,
@@ -274,7 +395,7 @@ const Sinusoid = _.extend({}, PlotDefaults, {
274
395
  return approximateDeepEqual(canonicalSineCoefficients(coeffs1), canonicalSineCoefficients(coeffs2));
275
396
  }
276
397
  });
277
- const Tangent = _.extend({}, PlotDefaults, {
398
+ const Tangent = ___default.default.extend({}, PlotDefaults, {
278
399
  url: "https://ka-perseus-graphie.s3.amazonaws.com/7db80d23c35214f98659fe1cf0765811c1bbfbba.png",
279
400
  defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
280
401
  getCoefficients: function (coords) {
@@ -305,27 +426,46 @@ const Tangent = _.extend({}, PlotDefaults, {
305
426
  return approximateDeepEqual(canonicalTangentCoefficients(coeffs1), canonicalTangentCoefficients(coeffs2));
306
427
  }
307
428
  });
308
- const Exponential = _.extend({}, PlotDefaults, {
429
+ const Exponential = ___default.default.extend({}, PlotDefaults, {
309
430
  url: "https://ka-perseus-graphie.s3.amazonaws.com/9cbfad55525e3ce755a31a631b074670a5dad611.png",
310
431
  defaultCoords: [[0.5, 0.55], [0.75, 0.75]],
311
432
  defaultAsymptote: [[0, 0.5], [1.0, 0.5]],
433
+ /**
434
+ * Add extra constraints for movement of the points or asymptote (below):
435
+ * newCoord: [x, y]
436
+ * The end position of the point or asymptote endpoint
437
+ * oldCoord: [x, y]
438
+ * The old position of the point or asymptote endpoint
439
+ * coords:
440
+ * An array of coordinates representing the proposed end configuration
441
+ * of the plot coordinates.
442
+ * asymptote:
443
+ * An array of coordinates representing the proposed end configuration
444
+ * of the asymptote.
445
+ *
446
+ * Return: either a coordinate (to be used as the resulting coordinate of
447
+ * the move) or a boolean, where `true` uses newCoord as the resulting
448
+ * coordinate, and `false` uses oldCoord as the resulting coordinate.
449
+ */
312
450
  extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
313
451
  const y = asymptote[0][1];
314
- return _.all(coords, coord => coord[1] !== y);
452
+ return ___default.default.all(coords, coord => coord[1] !== y);
315
453
  },
316
454
  extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
317
455
  const y = newCoord[1];
318
- const isValid = _.all(coords, coord => coord[1] > y) || _.all(coords, coord => coord[1] < y);
456
+ const isValid = ___default.default.all(coords, coord => coord[1] > y) || ___default.default.all(coords, coord => coord[1] < y);
319
457
  if (isValid) {
320
458
  return [oldCoord[0], y];
321
459
  }
460
+ // Snap the asymptote as close as possible, i.e., if the user moves
461
+ // the mouse really quickly into an invalid region
322
462
  const oldY = oldCoord[1];
323
- const wasBelow = _.all(coords, coord => coord[1] > oldY);
463
+ const wasBelow = ___default.default.all(coords, coord => coord[1] > oldY);
324
464
  if (wasBelow) {
325
- const bottomMost = _.min(_.map(coords, coord => coord[1]));
465
+ const bottomMost = ___default.default.min(___default.default.map(coords, coord => coord[1]));
326
466
  return [oldCoord[0], bottomMost - graph.snapStep[1]];
327
467
  }
328
- const topMost = _.max(_.map(coords, coord => coord[1]));
468
+ const topMost = ___default.default.max(___default.default.map(coords, coord => coord[1]));
329
469
  return [oldCoord[0], topMost + graph.snapStep[1]];
330
470
  },
331
471
  allowReflectOverAsymptote: true,
@@ -344,6 +484,7 @@ const Exponential = _.extend({}, PlotDefaults, {
344
484
  return a * Math.exp(b * x) + c;
345
485
  },
346
486
  getEquationString: function (coords, asymptote) {
487
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
347
488
  if (!asymptote) {
348
489
  return null;
349
490
  }
@@ -354,33 +495,39 @@ const Exponential = _.extend({}, PlotDefaults, {
354
495
  return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
355
496
  }
356
497
  });
357
- const Logarithm = _.extend({}, PlotDefaults, {
498
+ const Logarithm = ___default.default.extend({}, PlotDefaults, {
358
499
  url: "https://ka-perseus-graphie.s3.amazonaws.com/f6491e99d34af34d924bfe0231728ad912068dc3.png",
359
500
  defaultCoords: [[0.55, 0.5], [0.75, 0.75]],
360
501
  defaultAsymptote: [[0.5, 0], [0.5, 1.0]],
361
502
  extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
362
503
  const x = asymptote[0][0];
363
- return _.all(coords, coord => coord[0] !== x) && coords[0][1] !== coords[1][1];
504
+ return ___default.default.all(coords, coord => coord[0] !== x) && coords[0][1] !== coords[1][1];
364
505
  },
365
506
  extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
366
507
  const x = newCoord[0];
367
- const isValid = _.all(coords, coord => coord[0] > x) || _.all(coords, coord => coord[0] < x);
508
+ const isValid = ___default.default.all(coords, coord => coord[0] > x) || ___default.default.all(coords, coord => coord[0] < x);
368
509
  if (isValid) {
369
510
  return [x, oldCoord[1]];
370
511
  }
512
+ // Snap the asymptote as close as possible, i.e., if the user moves
513
+ // the mouse really quickly into an invalid region
371
514
  const oldX = oldCoord[0];
372
- const wasLeft = _.all(coords, coord => coord[0] > oldX);
515
+ const wasLeft = ___default.default.all(coords, coord => coord[0] > oldX);
373
516
  if (wasLeft) {
374
- const leftMost = _.min(_.map(coords, coord => coord[0]));
517
+ const leftMost = ___default.default.min(___default.default.map(coords, coord => coord[0]));
375
518
  return [leftMost - graph.snapStep[0], oldCoord[1]];
376
519
  }
377
- const rightMost = _.max(_.map(coords, coord => coord[0]));
520
+ const rightMost = ___default.default.max(___default.default.map(coords, coord => coord[0]));
378
521
  return [rightMost + graph.snapStep[0], oldCoord[1]];
379
522
  },
380
523
  allowReflectOverAsymptote: true,
381
524
  getCoefficients: function (coords, asymptote) {
525
+ // It's easiest to calculate the logarithm's coefficients by thinking
526
+ // about it as the inverse of the exponential, so we flip x and y and
527
+ // perform some algebra on the coefficients. This also unifies the
528
+ // logic between the two 'models'.
382
529
  const flip = coord => [coord[1], coord[0]];
383
- const inverseCoeffs = Exponential.getCoefficients(_.map(coords, flip), _.map(asymptote, flip));
530
+ const inverseCoeffs = Exponential.getCoefficients(___default.default.map(coords, flip), ___default.default.map(asymptote, flip));
384
531
  if (inverseCoeffs) {
385
532
  const c = -inverseCoeffs[2] / inverseCoeffs[0];
386
533
  const b = 1 / inverseCoeffs[0];
@@ -395,6 +542,7 @@ const Logarithm = _.extend({}, PlotDefaults, {
395
542
  return a * Math.log(b * x + c);
396
543
  },
397
544
  getEquationString: function (coords, asymptote) {
545
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
398
546
  if (!asymptote) {
399
547
  return null;
400
548
  }
@@ -405,7 +553,7 @@ const Logarithm = _.extend({}, PlotDefaults, {
405
553
  return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
406
554
  }
407
555
  });
408
- const AbsoluteValue = _.extend({}, PlotDefaults, {
556
+ const AbsoluteValue = ___default.default.extend({}, PlotDefaults, {
409
557
  url: "https://ka-perseus-graphie.s3.amazonaws.com/8256a630175a0cb1d11de223d6de0266daf98721.png",
410
558
  defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
411
559
  getCoefficients: function (coords) {
@@ -438,6 +586,8 @@ const AbsoluteValue = _.extend({}, PlotDefaults, {
438
586
  return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
439
587
  }
440
588
  });
589
+
590
+ /* Utility functions for dealing with graphing interfaces. */
441
591
  const functionTypeMapping = {
442
592
  linear: Linear,
443
593
  quadratic: Quadratic,
@@ -447,8 +597,10 @@ const functionTypeMapping = {
447
597
  logarithm: Logarithm,
448
598
  absolute_value: AbsoluteValue
449
599
  };
450
- const allTypes = _.keys(functionTypeMapping);
600
+ const allTypes = ___default.default.keys(functionTypeMapping);
451
601
  function functionForType(type) {
602
+ // @ts-expect-error: TypeScript doesn't know how to use deal with generics
603
+ // and conditional types in this way.
452
604
  return functionTypeMapping[type];
453
605
  }
454
606
 
@@ -517,7 +669,8 @@ function isRealJSONParse(jsonParse) {
517
669
  const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
518
670
  return approximateDeepEqual(parsedTestItemData, testingObject);
519
671
  }
520
- function buildRandomString(capitalize = false) {
672
+ function buildRandomString() {
673
+ let capitalize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
521
674
  let randomString = "";
522
675
  const randomLength = Math.floor(Math.random() * 8) + 3;
523
676
  for (let i = 0; i < randomLength; i++) {
@@ -559,7 +712,11 @@ function isFailure(result) {
559
712
  function isSuccess(result) {
560
713
  return result.type === "success";
561
714
  }
562
- function all(results, combineFailureDetails = a => a) {
715
+
716
+ // Result's `all` function is similar to Promise.all: given an array of
717
+ // results, it returns success if all succeeded, and failure if any failed.
718
+ function all(results) {
719
+ let combineFailureDetails = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : a => a;
563
720
  const values = [];
564
721
  const failureDetails = [];
565
722
  for (const result of results) {
@@ -616,6 +773,7 @@ function message(failure) {
616
773
  }
617
774
  function conjoin(items) {
618
775
  switch (items.length) {
776
+ // TODO(benchristel): handle 0 if this is reused elsewhere.
619
777
  case 1:
620
778
  return items[0];
621
779
  case 2:
@@ -668,7 +826,10 @@ function constant(acceptedValue) {
668
826
  };
669
827
  }
670
828
 
671
- function enumeration(...acceptedValues) {
829
+ function enumeration() {
830
+ for (var _len = arguments.length, acceptedValues = new Array(_len), _key = 0; _key < _len; _key++) {
831
+ acceptedValues[_key] = arguments[_key];
832
+ }
672
833
  return (rawValue, ctx) => {
673
834
  if (typeof rawValue === "string") {
674
835
  const index = acceptedValues.indexOf(rawValue);
@@ -706,7 +867,9 @@ function object(schema) {
706
867
  if (!isObject(rawValue)) {
707
868
  return ctx.failure("object", rawValue);
708
869
  }
709
- const ret = _extends({}, rawValue);
870
+ const ret = {
871
+ ...rawValue
872
+ };
710
873
  const mismatches = [];
711
874
  for (const [prop, propParser] of Object.entries(schema)) {
712
875
  const result = propParser(rawValue[prop], ctx.forSubtree(prop));
@@ -944,26 +1107,54 @@ const parseExplanationWidget = parseWidget(constant("explanation"), object({
944
1107
  showPrompt: string,
945
1108
  hidePrompt: string,
946
1109
  explanation: string,
1110
+ // We wrap parseWidgetsMap in a function here to make sure it is not
1111
+ // referenced before it is defined. There is an import cycle between
1112
+ // this file and widgets-map.ts that could cause it to be undefined.
947
1113
  widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
948
1114
  static: defaulted(boolean, () => false)
949
1115
  }));
950
1116
 
951
- const KeypadKeys = ["PLUS", "MINUS", "NEGATIVE", "TIMES", "DIVIDE", "DECIMAL", "PERIOD", "PERCENT", "CDOT", "EQUAL", "NEQ", "GT", "LT", "GEQ", "LEQ", "FRAC_INCLUSIVE", "FRAC_EXCLUSIVE", "FRAC", "EXP", "EXP_2", "EXP_3", "SQRT", "CUBE_ROOT", "RADICAL", "LEFT_PAREN", "RIGHT_PAREN", "LN", "LOG", "LOG_N", "SIN", "COS", "TAN", "PI", "THETA", "UP", "RIGHT", "DOWN", "LEFT", "BACKSPACE", "DISMISS", "JUMP_OUT_PARENTHESES", "JUMP_OUT_EXPONENT", "JUMP_OUT_BASE", "JUMP_INTO_NUMERATOR", "JUMP_OUT_NUMERATOR", "JUMP_OUT_DENOMINATOR", "NUM_0", "NUM_1", "NUM_2", "NUM_3", "NUM_4", "NUM_5", "NUM_6", "NUM_7", "NUM_8", "NUM_9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
952
-
1117
+ const KeypadKeys = ["PLUS", "MINUS", "NEGATIVE", "TIMES", "DIVIDE", "DECIMAL", "PERIOD", "PERCENT", "CDOT", "EQUAL", "NEQ", "GT", "LT", "GEQ", "LEQ",
1118
+ // mobile native only
1119
+ "FRAC_INCLUSIVE",
1120
+ // mobile native only
1121
+ "FRAC_EXCLUSIVE",
1122
+ // mobile native only
1123
+ "FRAC", "EXP", "EXP_2", "EXP_3", "SQRT", "CUBE_ROOT", "RADICAL", "LEFT_PAREN", "RIGHT_PAREN", "LN", "LOG", "LOG_N", "SIN", "COS",
1124
+ // TODO(charlie): Add in additional Greek letters.,
1125
+ "TAN", "PI", "THETA", "UP", "RIGHT", "DOWN", "LEFT", "BACKSPACE", "DISMISS", "JUMP_OUT_PARENTHESES", "JUMP_OUT_EXPONENT", "JUMP_OUT_BASE", "JUMP_INTO_NUMERATOR", "JUMP_OUT_NUMERATOR", "JUMP_OUT_DENOMINATOR",
1126
+ // Multi-functional keys.
1127
+ "NUM_0", "NUM_1", "NUM_2", "NUM_3", "NUM_4", "NUM_5", "NUM_6", "NUM_7", "NUM_8", "NUM_9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
1128
+
1129
+ // Used by KeypadContext to pass around a renderer reference
1130
+
1131
+ /**
1132
+ * Scrape the answer forms for any variables or contants (like Pi)
1133
+ * that need to be included as keys on the keypad.
1134
+ */
953
1135
  function deriveExtraKeys(widgetOptions) {
954
1136
  if (widgetOptions.extraKeys) {
955
1137
  return widgetOptions.extraKeys;
956
1138
  }
1139
+
1140
+ // If there are no extra symbols available, we include Pi anyway, so
1141
+ // that the "extra symbols" button doesn't appear empty.
957
1142
  const defaultKeys = ["PI"];
958
1143
  if (widgetOptions.answerForms == null) {
959
1144
  return defaultKeys;
960
1145
  }
1146
+
1147
+ // Extract any and all variables and constants from the answer forms.
961
1148
  const uniqueExtraVariables = {};
962
1149
  const uniqueExtraConstants = {};
963
1150
  for (const answerForm of widgetOptions.answerForms) {
964
- const maybeExpr = KAS.parse(answerForm.value, widgetOptions);
1151
+ const maybeExpr = KAS__namespace.parse(answerForm.value, widgetOptions);
965
1152
  if (maybeExpr.parsed) {
966
1153
  const expr = maybeExpr.expr;
1154
+
1155
+ // The keypad expects Greek letters to be capitalized (e.g., it
1156
+ // requires `PI` instead of `pi`). Right now, it only supports Pi
1157
+ // and Theta, so we special-case.
967
1158
  const isGreek = symbol => symbol === "pi" || symbol === "theta";
968
1159
  const toKey = symbol => isGreek(symbol) ? symbol.toUpperCase() : symbol;
969
1160
  const isKey = key => KeypadKeys.includes(key);
@@ -981,28 +1172,57 @@ function deriveExtraKeys(widgetOptions) {
981
1172
  }
982
1173
  }
983
1174
  }
1175
+
1176
+ // TODO(charlie): Alert the keypad as to which of these symbols should be
1177
+ // treated as functions.
984
1178
  const extraVariables = Object.keys(uniqueExtraVariables).sort();
985
1179
  const extraConstants = Object.keys(uniqueExtraConstants).sort();
986
1180
  const extraKeys = [...extraVariables, ...extraConstants];
1181
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
987
1182
  if (!extraKeys.length) {
988
1183
  return defaultKeys;
989
1184
  }
990
1185
  return extraKeys;
991
1186
  }
992
1187
 
1188
+ // Given a function, creates a PartialParser that converts one type to another
1189
+ // using that function. The returned parser never fails.
993
1190
  function convert(f) {
994
1191
  return (rawValue, ctx) => ctx.success(f(rawValue));
995
1192
  }
996
1193
 
997
1194
  const parseLegacyButtonSet = enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations", "scientific");
998
- const parseLegacyButtonSets = defaulted(array(parseLegacyButtonSet), () => ["basic", "trig", "prealgebra", "logarithms"]);
999
-
1195
+ const parseLegacyButtonSets = defaulted(array(parseLegacyButtonSet),
1196
+ // NOTE(benchristel): I copied the default buttonSets from
1197
+ // expression.tsx. See the parse-perseus-json/README.md for
1198
+ // an explanation of why we want to duplicate the default here.
1199
+ () => ["basic", "trig", "prealgebra", "logarithms"]);
1200
+
1201
+ /**
1202
+ * Creates a parser for a widget options type with multiple major versions. Old
1203
+ * versions are migrated to the latest version. The parse fails if the input
1204
+ * data does not match any of the versions.
1205
+ *
1206
+ * @example
1207
+ * const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
1208
+ * .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
1209
+ * .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
1210
+ * .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
1211
+ * .parser;
1212
+ *
1213
+ * @param latestMajorVersion the latest major version of the widget options.
1214
+ * @param parseLatest a {@link Parser} for the latest version of the widget
1215
+ * options.
1216
+ * @returns a builder object, to which migrations from earlier versions can be
1217
+ * added. Migrations must be added in "reverse chronological" order as in the
1218
+ * example above.
1219
+ */
1000
1220
  function versionedWidgetOptions(latestMajorVersion, parseLatest) {
1001
1221
  return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
1002
1222
  }
1003
1223
  class VersionedWidgetOptionsParserBuilder {
1224
+ parser;
1004
1225
  constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
1005
- this.parser = void 0;
1006
1226
  this.migrateToLatest = migrateToLatest;
1007
1227
  this.parseOtherVersions = parseOtherVersions;
1008
1228
  const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
@@ -1020,6 +1240,10 @@ class VersionedWidgetOptionsParserBuilder {
1020
1240
  return parseThisVersionAndMigrateToLatest(raw, ctx);
1021
1241
  };
1022
1242
  }
1243
+
1244
+ /**
1245
+ * Add a migration from an old version of the widget options.
1246
+ */
1023
1247
  withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
1024
1248
  const parseOtherVersions = this.parser;
1025
1249
  const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
@@ -1038,6 +1262,9 @@ const parseVersionedObject = object({
1038
1262
 
1039
1263
  const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
1040
1264
  const parsePossiblyInvalidAnswerForm = object({
1265
+ // `value` is the possibly invalid part of this. It should always be a
1266
+ // string, but some answer forms don't have it. The Expression widget
1267
+ // ignores invalid values, so we can safely filter them out during parsing.
1041
1268
  value: optional(string),
1042
1269
  form: defaulted(boolean, () => false),
1043
1270
  simplify: defaulted(boolean, () => false),
@@ -1051,9 +1278,11 @@ function removeInvalidAnswerForms(possiblyInvalid) {
1051
1278
  value
1052
1279
  } = answerForm;
1053
1280
  if (value != null) {
1054
- valid.push(_extends({}, answerForm, {
1281
+ // Copying the object seems to be needed to make TypeScript happy
1282
+ valid.push({
1283
+ ...answerForm,
1055
1284
  value
1056
- }));
1285
+ });
1057
1286
  }
1058
1287
  }
1059
1288
  return valid;
@@ -1089,7 +1318,8 @@ function migrateV1ToV2$1(widget) {
1089
1318
  const {
1090
1319
  options
1091
1320
  } = widget;
1092
- return _extends({}, widget, {
1321
+ return {
1322
+ ...widget,
1093
1323
  version: {
1094
1324
  major: 2,
1095
1325
  minor: 0
@@ -1104,7 +1334,7 @@ function migrateV1ToV2$1(widget) {
1104
1334
  answerForms: options.answerForms,
1105
1335
  extraKeys: deriveExtraKeys(options)
1106
1336
  }
1107
- });
1337
+ };
1108
1338
  }
1109
1339
  const version0$1 = optional(object({
1110
1340
  major: constant(0),
@@ -1125,7 +1355,8 @@ function migrateV0ToV1$1(widget) {
1125
1355
  const {
1126
1356
  options
1127
1357
  } = widget;
1128
- return _extends({}, widget, {
1358
+ return {
1359
+ ...widget,
1129
1360
  version: {
1130
1361
  major: 1,
1131
1362
  minor: 0
@@ -1144,7 +1375,7 @@ function migrateV0ToV1$1(widget) {
1144
1375
  value: options.value
1145
1376
  }]
1146
1377
  }
1147
- });
1378
+ };
1148
1379
  }
1149
1380
  const parseExpressionWidget = versionedWidgetOptions(2, parseExpressionWidgetV2).withMigrationFrom(1, parseExpressionWidgetV1, migrateV1ToV2$1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1$1).parser;
1150
1381
 
@@ -1152,8 +1383,14 @@ const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parse
1152
1383
  const parseGradedGroupWidgetOptions = object({
1153
1384
  title: defaulted(string, () => ""),
1154
1385
  hasHint: optional(nullable(boolean)),
1386
+ // This module has an import cycle with parsePerseusRenderer.
1387
+ // The anonymous function below ensures that we don't try to access
1388
+ // parsePerseusRenderer before it's defined.
1155
1389
  hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
1156
1390
  content: string,
1391
+ // This module has an import cycle with parseWidgetsMap.
1392
+ // The anonymous function below ensures that we don't try to access
1393
+ // parseWidgetsMap before it's defined.
1157
1394
  widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
1158
1395
  widgetEnabled: optional(nullable(boolean)),
1159
1396
  immutableWidgets: optional(nullable(boolean)),
@@ -1168,6 +1405,13 @@ const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), obje
1168
1405
  gradedGroups: array(parseGradedGroupWidgetOptions)
1169
1406
  }));
1170
1407
 
1408
+ /**
1409
+ * discriminatedUnion() should be preferred over union() when parsing a
1410
+ * discriminated union type, because discriminatedUnion() produces more
1411
+ * understandable failure messages. It takes the discriminant as the source of
1412
+ * truth for which variant is to be parsed, and expects the other data to match
1413
+ * that variant.
1414
+ */
1171
1415
  function discriminatedUnionOn(discriminantKey) {
1172
1416
  const noMoreBranches = (raw, ctx) => {
1173
1417
  if (!isObject(raw)) {
@@ -1253,7 +1497,11 @@ const parseGrapherWidget = parseWidget(constant("grapher"), object({
1253
1497
  })
1254
1498
  }));
1255
1499
 
1256
- const parseGroupWidget = parseWidget(constant("group"), (rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
1500
+ const parseGroupWidget = parseWidget(constant("group"),
1501
+ // This module has an import cycle with parsePerseusRenderer.
1502
+ // The anonymous function below ensures that we don't try to access
1503
+ // parsePerseusRenderer before it's defined.
1504
+ (rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
1257
1505
 
1258
1506
  const parseIframeWidget = parseWidget(constant("iframe"), object({
1259
1507
  url: string,
@@ -1282,7 +1530,11 @@ const stringToNumber = (rawValue, ctx) => {
1282
1530
  function emptyToZero(x) {
1283
1531
  return x === "" ? 0 : x;
1284
1532
  }
1285
- const imageDimensionToNumber = pipeParsers(union(number).or(string).parser).then(convert(emptyToZero)).then(stringToNumber).parser;
1533
+ const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
1534
+ // In this specific case, empty string is equivalent to zero. An empty
1535
+ // string parses to either NaN (using parseInt) or 0 (using unary +) and
1536
+ // CSS will treat NaN as invalid and default to 0 instead.
1537
+ .then(convert(emptyToZero)).then(stringToNumber).parser;
1286
1538
  const dimensionOrUndefined = defaulted(imageDimensionToNumber, () => undefined);
1287
1539
  const parsePerseusImageBackground = object({
1288
1540
  url: optional(nullable(string)),
@@ -1323,6 +1575,10 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
1323
1575
  rightAlign: optional(boolean),
1324
1576
  simplify: enumeration("required", "optional", "enforced"),
1325
1577
  size: enumeration("normal", "small"),
1578
+ // TODO(benchristel): there are some content items where value is a
1579
+ // boolean, even though that makes no sense. We should figure out if
1580
+ // those content items are actually published anywhere, and consider
1581
+ // updating them.
1326
1582
  value: union(number).or(string).or(booleanToString).parser,
1327
1583
  customKeypad: optional(boolean)
1328
1584
  }));
@@ -1456,8 +1712,270 @@ const parseInteractionWidget = parseWidget(constant("interaction"), object({
1456
1712
  elements: array(discriminatedUnionOn("type").withBranch("function", parseFunctionElement).withBranch("label", parseLabelElement).withBranch("line", parseLineElement).withBranch("movable-line", parseMovableLineElement).withBranch("movable-point", parseMovablePointElement).withBranch("parametric", parseParametricElement).withBranch("point", parsePointElement).withBranch("rectangle", parseRectangleElement).parser)
1457
1713
  }));
1458
1714
 
1459
- const ItemExtras = ["calculator", "chi2Table", "financialCalculatorMonthlyPayment", "financialCalculatorTotalAmount", "financialCalculatorTimeToPayOff", "periodicTable", "periodicTableWithKey", "tTable", "zTable"];
1715
+ /**
1716
+ * The Perseus "data schema" file.
1717
+ *
1718
+ * This file, and the types in it, represents the "data schema" that Perseus
1719
+ * uses. The @khanacademy/perseus-editor package edits and produces objects
1720
+ * that conform to the types in this file. Similarly, the top-level renderers
1721
+ * in @khanacademy/perseus, consume objects that conform to these types.
1722
+ *
1723
+ * WARNING: This file should not import any types from elsewhere so that it is
1724
+ * easy to reason about changes that alter the Perseus schema. This helps
1725
+ * ensure that it is not changed accidentally when upgrading a dependant
1726
+ * package or other part of Perseus code. Note that TypeScript does type
1727
+ * checking via something called "structural typing". This means that as long
1728
+ * as the shape of a type matches, the name it goes by doesn't matter. As a
1729
+ * result, a `Coord` type that looks like this `[x: number, y: number]` is
1730
+ * _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
1731
+ * number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
1732
+ * number, y: number]` is compatible with `[min: number, max: number]`. The
1733
+ * labels are for humans, not TypeScript. :)
1734
+ *
1735
+ * If you make changes to types in this file, be very sure that:
1736
+ *
1737
+ * a) the changes are backwards compatible. If they are not, old data from
1738
+ * previous versions of the "schema" could become unrenderable, or worse,
1739
+ * introduce hard-to-diagnose bugs.
1740
+ * b) the parsing code (`util/parse-perseus-json/`) is updated to handle
1741
+ * the new format _as well as_ the old format.
1742
+ */
1743
+
1744
+ // TODO(FEI-4010): Remove `Perseus` prefix for all types here
1745
+
1746
+ // Same name as Mafs
1747
+
1748
+ /**
1749
+ * A utility type that constructs a widget map from a "registry interface".
1750
+ * The keys of the registry should be the widget type (aka, "categorizer" or
1751
+ * "radio", etc) and the value should be the option type stored in the value
1752
+ * of the map.
1753
+ *
1754
+ * You can think of this as a type that generates another type. We use
1755
+ * "registry interfaces" as a way to keep a set of widget types to their data
1756
+ * type in several places in Perseus. This type then allows us to generate a
1757
+ * map type that maps a widget id to its data type and keep strong typing by
1758
+ * widget id.
1759
+ *
1760
+ * For example, given a fictitious registry such as this:
1761
+ *
1762
+ * ```
1763
+ * interface DummyRegistry {
1764
+ * categorizer: { categories: ReadonlyArray<string> };
1765
+ * dropdown: { choices: ReadonlyArray<string> }:
1766
+ * }
1767
+ * ```
1768
+ *
1769
+ * If we create a DummyMap using this helper:
1770
+ *
1771
+ * ```
1772
+ * type DummyMap = MakeWidgetMap<DummyRegistry>;
1773
+ * ```
1774
+ *
1775
+ * We'll get a map that looks like this:
1776
+ *
1777
+ * ```
1778
+ * type DummyMap = {
1779
+ * `categorizer ${number}`: { categories: ReadonlyArray<string> };
1780
+ * `dropdown ${number}`: { choices: ReadonlyArray<string> };
1781
+ * }
1782
+ * ```
1783
+ *
1784
+ * We use interfaces for the registries so that they can be extended in cases
1785
+ * where the consuming app brings along their own widgets. Interfaces in
1786
+ * TypeScript are always open (ie. you can extend them) whereas types aren't.
1787
+ */
1788
+
1789
+ /**
1790
+ * Our core set of Perseus widgets.
1791
+ *
1792
+ * This interface is the basis for "registering" all Perseus widget types.
1793
+ * There should be one key/value pair for each supported widget. If you create
1794
+ * a new widget, an entry should be added to this interface. Note that this
1795
+ * only registers the widget options type, you'll also need to register the
1796
+ * widget so that it's available at runtime (@see
1797
+ * {@link file://./widgets.ts#registerWidget}).
1798
+ *
1799
+ * Importantly, the key should be the name that is used in widget IDs. For most
1800
+ * widgets that is the same as the widget option's `type` field. In cases where
1801
+ * a widget has been deprecated and replaced with the deprecated-standin
1802
+ * widget, it should be the original widget type!
1803
+ *
1804
+ * If you define the widget outside of this package, you can still add the new
1805
+ * widget to this interface by writing the following in that package that
1806
+ * contains the widget. TypeScript will merge that definition of the
1807
+ * `PerseusWidgets` with the one defined below.
1808
+ *
1809
+ * ```typescript
1810
+ * declare module "@khanacademy/perseus-core" {
1811
+ * interface PerseusWidgetTypes {
1812
+ * // A new widget
1813
+ * "new-awesomeness": MyAwesomeNewWidget;
1814
+ *
1815
+ * // A deprecated widget
1816
+ * "super-old-widget": DeprecatedStandinWidget;
1817
+ * }
1818
+ * }
1819
+ *
1820
+ * // The new widget's options definition
1821
+ * type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
1822
+ *
1823
+ * // The deprecated widget's options definition
1824
+ * type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
1825
+ * ```
1826
+ *
1827
+ * This interface can be extended through the magic of TypeScript "Declaration
1828
+ * merging". Specifically, we augment this module and extend this interface.
1829
+ *
1830
+ * @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
1831
+ */
1832
+
1833
+ /**
1834
+ * A map of widget IDs to widget options. This is most often used as the type
1835
+ * for a set of widgets defined in a `PerseusItem` but can also be useful to
1836
+ * represent a function parameter where only `widgets` from a `PerseusItem` are
1837
+ * needed. Today Widget IDs are made up of the widget type and an incrementing
1838
+ * integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
1839
+ * reading/parsing the widget id to derive any information from it, except in
1840
+ * the case of this map.
1841
+ *
1842
+ * @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
1843
+ * by augmenting the PerseusWidgetTypes with new widget types!
1844
+ */
1845
+
1846
+ /**
1847
+ * PerseusWidget is a union of all the different types of widget options that
1848
+ * Perseus knows about.
1849
+ *
1850
+ * Thanks to it being based on PerseusWidgetTypes interface, this union is
1851
+ * automatically extended to include widgets used in tests without those widget
1852
+ * option types seeping into our production types.
1853
+ *
1854
+ * @see MockWidget for an example
1855
+ */
1856
+
1857
+ /**
1858
+ * A "PerseusItem" is a classic Perseus item. It is rendered by the
1859
+ * `ServerItemRenderer` and the layout is pre-set.
1860
+ *
1861
+ * To render more complex Perseus items, see the `Item` type in the multi item
1862
+ * area.
1863
+ */
1864
+
1865
+ /**
1866
+ * A "PerseusArticle" is an item that is meant to be rendered as an article.
1867
+ * This item is never scored and is rendered by the `ArticleRenderer`.
1868
+ */
1869
+
1870
+ const ItemExtras = [
1871
+ // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
1872
+ "calculator",
1873
+ // The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
1874
+ "chi2Table",
1875
+ // The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
1876
+ "financialCalculatorMonthlyPayment",
1877
+ // The user might benefit from a total amount calculator. Provided on Khan Academy when true
1878
+ "financialCalculatorTotalAmount",
1879
+ // The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
1880
+ "financialCalculatorTimeToPayOff",
1881
+ // The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
1882
+ "periodicTable",
1883
+ // The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
1884
+ "periodicTableWithKey",
1885
+ // The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
1886
+ "tTable",
1887
+ // The user might benefit from using a statistics Z Table like https://www.ztable.net/
1888
+ "zTable"];
1889
+
1890
+ /**
1891
+ * The type representing the common structure of all widget's options. The
1892
+ * `Options` generic type represents the widget-specific option data.
1893
+ */
1894
+
1895
+ // prettier-ignore
1896
+
1897
+ // prettier-ignore
1898
+
1899
+ // prettier-ignore
1900
+
1901
+ // prettier-ignore
1902
+
1903
+ // prettier-ignore
1904
+
1905
+ // prettier-ignore
1906
+
1907
+ // prettier-ignore
1908
+
1909
+ // prettier-ignore
1910
+
1911
+ // prettier-ignore
1912
+
1913
+ // prettier-ignore
1914
+
1915
+ // prettier-ignore
1916
+
1917
+ // prettier-ignore
1918
+
1919
+ // prettier-ignore
1920
+
1921
+ // prettier-ignore
1922
+
1923
+ // prettier-ignore
1924
+
1925
+ // prettier-ignore
1926
+
1927
+ // prettier-ignore
1928
+
1929
+ // prettier-ignore
1930
+
1931
+ // prettier-ignore
1932
+
1933
+ // prettier-ignore
1934
+
1935
+ // prettier-ignore
1936
+
1937
+ // prettier-ignore
1938
+
1939
+ // prettier-ignore
1940
+
1941
+ // prettier-ignore
1942
+
1943
+ // prettier-ignore
1944
+
1945
+ // prettier-ignore
1946
+
1947
+ // prettier-ignore
1948
+
1949
+ // prettier-ignore
1950
+
1951
+ // prettier-ignore
1952
+
1953
+ // prettier-ignore
1954
+
1955
+ // prettier-ignore
1956
+
1957
+ // prettier-ignore
1958
+
1959
+ // prettier-ignore
1960
+
1961
+ //prettier-ignore
1962
+
1963
+ /**
1964
+ * A background image applied to various widgets.
1965
+ */
1966
+
1967
+ /**
1968
+ * The type of markings to display on the graph.
1969
+ * - axes: shows the axes without the gride lines
1970
+ * - graph: shows the axes and the grid lines
1971
+ * - grid: shows only the grid lines
1972
+ * - none: shows no markings
1973
+ */
1974
+
1460
1975
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
1976
+
1977
+ // 2D range: xMin, xMax, yMin, yMax
1978
+
1461
1979
  const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
1462
1980
  const lockedFigureColors = {
1463
1981
  blue: "#3D7586",
@@ -1474,8 +1992,21 @@ const lockedFigureFillStyles = {
1474
1992
  translucent: 0.4,
1475
1993
  solid: 1
1476
1994
  };
1995
+
1996
+ // Not associated with a specific figure
1997
+
1998
+ /**
1999
+ * Determines how unsimplified fractions are scored.
2000
+ *
2001
+ * - "required" means unsimplified fractions are considered invalid input, and
2002
+ * the learner can try again.
2003
+ * - "enforced" means unsimplified fractions are marked incorrect.
2004
+ * - "optional" means unsimplified fractions are accepted.
2005
+ */
2006
+
1477
2007
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
1478
2008
 
2009
+ // Used to represent 2-D points and ranges
1479
2010
  const pairOfNumbers = pair(number, number);
1480
2011
  const parsePerseusGraphTypeAngle = object({
1481
2012
  type: constant("angle"),
@@ -1495,18 +2026,22 @@ const parsePerseusGraphTypeCircle = object({
1495
2026
  center: pairOfNumbers,
1496
2027
  radius: number
1497
2028
  })),
2029
+ // TODO: remove coord? it's legacy.
1498
2030
  coord: optional(pairOfNumbers)
1499
2031
  });
1500
2032
  const parsePerseusGraphTypeLinear = object({
1501
2033
  type: constant("linear"),
1502
2034
  coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
1503
2035
  startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
2036
+ // TODO: remove coord? it's legacy.
1504
2037
  coord: optional(pairOfNumbers)
1505
2038
  });
1506
2039
  const parsePerseusGraphTypeLinearSystem = object({
1507
2040
  type: constant("linear-system"),
2041
+ // TODO(benchristel): default coords to empty array?
1508
2042
  coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
1509
2043
  startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
2044
+ // TODO: remove coord? it's legacy.
1510
2045
  coord: optional(pairOfNumbers)
1511
2046
  });
1512
2047
  const parsePerseusGraphTypeNone = object({
@@ -1517,6 +2052,7 @@ const parsePerseusGraphTypePoint = object({
1517
2052
  numPoints: optional(union(number).or(constant("unlimited")).parser),
1518
2053
  coords: optional(nullable(array(pairOfNumbers))),
1519
2054
  startCoords: optional(array(pairOfNumbers)),
2055
+ // TODO: remove coord? it's legacy.
1520
2056
  coord: optional(pairOfNumbers)
1521
2057
  });
1522
2058
  const parsePerseusGraphTypePolygon = object({
@@ -1527,31 +2063,37 @@ const parsePerseusGraphTypePolygon = object({
1527
2063
  snapTo: optional(enumeration("grid", "angles", "sides")),
1528
2064
  match: optional(enumeration("similar", "congruent", "approx", "exact")),
1529
2065
  startCoords: optional(array(pairOfNumbers)),
2066
+ // TODO: remove coord? it's legacy.
1530
2067
  coord: optional(pairOfNumbers)
1531
2068
  });
1532
2069
  const parsePerseusGraphTypeQuadratic = object({
1533
2070
  type: constant("quadratic"),
1534
2071
  coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
1535
2072
  startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
2073
+ // TODO: remove coord? it's legacy.
1536
2074
  coord: optional(pairOfNumbers)
1537
2075
  });
1538
2076
  const parsePerseusGraphTypeRay = object({
1539
2077
  type: constant("ray"),
1540
2078
  coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
1541
2079
  startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
2080
+ // TODO: remove coord? it's legacy.
1542
2081
  coord: optional(pairOfNumbers)
1543
2082
  });
1544
2083
  const parsePerseusGraphTypeSegment = object({
1545
2084
  type: constant("segment"),
2085
+ // TODO(benchristel): default numSegments?
1546
2086
  numSegments: optional(number),
1547
2087
  coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
1548
2088
  startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
2089
+ // TODO: remove coord? it's legacy.
1549
2090
  coord: optional(pairOfNumbers)
1550
2091
  });
1551
2092
  const parsePerseusGraphTypeSinusoid = object({
1552
2093
  type: constant("sinusoid"),
1553
2094
  coords: optional(nullable(array(pairOfNumbers))),
1554
2095
  startCoords: optional(array(pairOfNumbers)),
2096
+ // TODO: remove coord? it's legacy.
1555
2097
  coord: optional(pairOfNumbers)
1556
2098
  });
1557
2099
  const parsePerseusGraphType = discriminatedUnionOn("type").withBranch("angle", parsePerseusGraphTypeAngle).withBranch("circle", parsePerseusGraphTypeCircle).withBranch("linear", parsePerseusGraphTypeLinear).withBranch("linear-system", parsePerseusGraphTypeLinearSystem).withBranch("none", parsePerseusGraphTypeNone).withBranch("point", parsePerseusGraphTypePoint).withBranch("polygon", parsePerseusGraphTypePolygon).withBranch("quadratic", parsePerseusGraphTypeQuadratic).withBranch("ray", parsePerseusGraphTypeRay).withBranch("segment", parsePerseusGraphTypeSegment).withBranch("sinusoid", parsePerseusGraphTypeSinusoid).parser;
@@ -1570,6 +2112,7 @@ const parseLockedPointType = object({
1570
2112
  coord: pairOfNumbers,
1571
2113
  color: parseLockedFigureColor,
1572
2114
  filled: boolean,
2115
+ // TODO(benchristel): default labels to empty array?
1573
2116
  labels: optional(array(parseLockedLabelType)),
1574
2117
  ariaLabel: optional(string)
1575
2118
  });
@@ -1581,6 +2124,7 @@ const parseLockedLineType = object({
1581
2124
  lineStyle: parseLockedLineStyle,
1582
2125
  showPoint1: defaulted(boolean, () => false),
1583
2126
  showPoint2: defaulted(boolean, () => false),
2127
+ // TODO(benchristel): default labels to empty array?
1584
2128
  labels: optional(array(parseLockedLabelType)),
1585
2129
  ariaLabel: optional(string)
1586
2130
  });
@@ -1588,6 +2132,7 @@ const parseLockedVectorType = object({
1588
2132
  type: constant("vector"),
1589
2133
  points: pair(pairOfNumbers, pairOfNumbers),
1590
2134
  color: parseLockedFigureColor,
2135
+ // TODO(benchristel): default labels to empty array?
1591
2136
  labels: optional(array(parseLockedLabelType)),
1592
2137
  ariaLabel: optional(string)
1593
2138
  });
@@ -1599,6 +2144,7 @@ const parseLockedEllipseType = object({
1599
2144
  color: parseLockedFigureColor,
1600
2145
  fillStyle: parseLockedFigureFillType,
1601
2146
  strokeStyle: parseLockedLineStyle,
2147
+ // TODO(benchristel): default labels to empty array?
1602
2148
  labels: optional(array(parseLockedLabelType)),
1603
2149
  ariaLabel: optional(string)
1604
2150
  });
@@ -1609,9 +2155,12 @@ const parseLockedPolygonType = object({
1609
2155
  showVertices: boolean,
1610
2156
  fillStyle: parseLockedFigureFillType,
1611
2157
  strokeStyle: parseLockedLineStyle,
2158
+ // TODO(benchristel): default labels to empty array?
1612
2159
  labels: optional(array(parseLockedLabelType)),
1613
2160
  ariaLabel: optional(string)
1614
2161
  });
2162
+
2163
+ // Exported for testing.
1615
2164
  const parseLockedFunctionDomain = defaulted(pair(defaulted(number, () => -Infinity), defaulted(number, () => Infinity)), () => [-Infinity, Infinity]);
1616
2165
  const parseLockedFunctionType = object({
1617
2166
  type: constant("function"),
@@ -1620,12 +2169,17 @@ const parseLockedFunctionType = object({
1620
2169
  equation: string,
1621
2170
  directionalAxis: enumeration("x", "y"),
1622
2171
  domain: parseLockedFunctionDomain,
2172
+ // TODO(benchristel): default labels to empty array?
1623
2173
  labels: optional(array(parseLockedLabelType)),
1624
2174
  ariaLabel: optional(string)
1625
2175
  });
1626
2176
  const parseLockedFigure = discriminatedUnionOn("type").withBranch("point", parseLockedPointType).withBranch("line", parseLockedLineType).withBranch("vector", parseLockedVectorType).withBranch("ellipse", parseLockedEllipseType).withBranch("polygon", parseLockedPolygonType).withBranch("function", parseLockedFunctionType).withBranch("label", parseLockedLabelType).parser;
1627
2177
  const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
1628
2178
  step: pairOfNumbers,
2179
+ // TODO(benchristel): rather than making gridStep and snapStep
2180
+ // optional, we should duplicate the defaulting logic from the
2181
+ // InteractiveGraph component. See parse-perseus-json/README.md for
2182
+ // why.
1629
2183
  gridStep: optional(pairOfNumbers),
1630
2184
  snapStep: optional(pairOfNumbers),
1631
2185
  backgroundImage: optional(parsePerseusImageBackground),
@@ -1637,10 +2191,14 @@ const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), o
1637
2191
  rulerLabel: optional(string),
1638
2192
  rulerTicks: optional(number),
1639
2193
  range: pair(pairOfNumbers, pairOfNumbers),
2194
+ // NOTE(benchristel): I copied the default graph from
2195
+ // interactive-graph.tsx. See the parse-perseus-json/README.md for
2196
+ // an explanation of why we want to duplicate the default here.
1640
2197
  graph: defaulted(parsePerseusGraphType, () => ({
1641
2198
  type: "linear"
1642
2199
  })),
1643
2200
  correct: parsePerseusGraphType,
2201
+ // TODO(benchristel): default lockedFigures to empty array
1644
2202
  lockedFigures: optional(array(parseLockedFigure)),
1645
2203
  fullGraphLabel: optional(string),
1646
2204
  fullGraphAriaDescription: optional(string)
@@ -1683,6 +2241,9 @@ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matri
1683
2241
  }));
1684
2242
 
1685
2243
  const parseMeasurerWidget = parseWidget(constant("measurer"), object({
2244
+ // The default value for image comes from measurer.tsx.
2245
+ // See parse-perseus-json/README.md for why we want to duplicate the
2246
+ // defaults here.
1686
2247
  image: defaulted(parsePerseusImageBackground, () => ({
1687
2248
  url: null,
1688
2249
  top: 0,
@@ -1695,6 +2256,7 @@ const parseMeasurerWidget = parseWidget(constant("measurer"), object({
1695
2256
  rulerPixels: number,
1696
2257
  rulerLength: number,
1697
2258
  box: pair(number, number),
2259
+ // TODO(benchristel): static is not used. Remove it?
1698
2260
  static: defaulted(boolean, () => false)
1699
2261
  }));
1700
2262
 
@@ -1713,6 +2275,9 @@ const parseNumberLineWidget = parseWidget(constant("number-line"), object({
1713
2275
  isTickCtrl: optional(nullable(boolean)),
1714
2276
  divisionRange: array(number),
1715
2277
  numDivisions: optional(nullable(number)),
2278
+ // NOTE(benchristel): I copied the default snapDivisions from
2279
+ // number-line.tsx. See the parse-perseus-json/README.md for
2280
+ // an explanation of why we want to duplicate the default here.
1716
2281
  snapDivisions: defaulted(number, () => 2),
1717
2282
  tickStep: optional(nullable(number)),
1718
2283
  correctRel: optional(nullable(string)),
@@ -1730,6 +2295,10 @@ function deprecatedSimplifyValuesToRequired(simplify) {
1730
2295
  case "required":
1731
2296
  case "optional":
1732
2297
  return simplify;
2298
+ // NOTE(benchristel): "accepted", "correct", true, false, undefined, and
2299
+ // null are all treated the same as "required" during scoring, so we
2300
+ // convert them to "required" here to preserve behavior. See the tests
2301
+ // in score-numeric-input.test.ts
1733
2302
  default:
1734
2303
  return "required";
1735
2304
  }
@@ -1737,11 +2306,17 @@ function deprecatedSimplifyValuesToRequired(simplify) {
1737
2306
  const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
1738
2307
  answers: array(object({
1739
2308
  message: string,
2309
+ // TODO(benchristel): value should never be null or undefined,
2310
+ // but we have some content where it is anyway. If we backfill
2311
+ // the data, simplify this.
1740
2312
  value: optional(nullable(number)),
1741
2313
  status: string,
1742
2314
  answerForms: defaulted(array(parseMathFormat), () => undefined),
1743
2315
  strict: boolean,
1744
2316
  maxError: optional(nullable(number)),
2317
+ // TODO(benchristel): simplify should never be a boolean, but we
2318
+ // have some content where it is anyway. If we ever backfill
2319
+ // the data, we should simplify `simplify`.
1745
2320
  simplify: parseSimplify
1746
2321
  })),
1747
2322
  labelText: optional(string),
@@ -1755,6 +2330,9 @@ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
1755
2330
  })))
1756
2331
  }));
1757
2332
 
2333
+ // There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
2334
+ // This wrapper ensures that we don't refer to parsePerseusRenderer before
2335
+ // it's defined.
1758
2336
  function parseRenderer(rawValue, ctx) {
1759
2337
  return parsePerseusRenderer(rawValue, ctx);
1760
2338
  }
@@ -1796,14 +2374,23 @@ const parsePlotterWidget = parseWidget(constant("plotter"), object({
1796
2374
  categories: array(string),
1797
2375
  type: enumeration(...plotterPlotTypes),
1798
2376
  maxY: number,
2377
+ // The default value for scaleY comes from plotter.tsx.
2378
+ // See parse-perseus-json/README.md for why we want to duplicate the
2379
+ // defaults here.
1799
2380
  scaleY: defaulted(number, () => 1),
1800
2381
  labelInterval: optional(nullable(number)),
2382
+ // The default value for snapsPerLine comes from plotter.tsx.
2383
+ // See parse-perseus-json/README.md for why we want to duplicate the
2384
+ // defaults here.
1801
2385
  snapsPerLine: defaulted(number, () => 2),
1802
2386
  starting: array(number),
1803
2387
  correct: array(number),
1804
2388
  picUrl: optional(nullable(string)),
1805
2389
  picSize: optional(nullable(number)),
1806
2390
  picBoxHeight: optional(nullable(number)),
2391
+ // NOTE(benchristel): I copied the default plotDimensions from
2392
+ // plotter.tsx. See the parse-perseus-json/README.md for an explanation
2393
+ // of why we want to duplicate the defaults here.
1807
2394
  plotDimensions: defaulted(array(number), () => [380, 300])
1808
2395
  }));
1809
2396
 
@@ -1812,7 +2399,6 @@ const parsePythonProgramWidget = parseWidget(constant("python-program"), object(
1812
2399
  height: number
1813
2400
  }));
1814
2401
 
1815
- const _excluded$a = ["noneOfTheAbove"];
1816
2402
  const currentVersion$3 = {
1817
2403
  major: 2,
1818
2404
  minor: 0
@@ -1822,26 +2408,28 @@ function deriveNumCorrect(options) {
1822
2408
  choices,
1823
2409
  numCorrect
1824
2410
  } = options;
1825
- return numCorrect != null ? numCorrect : choices.filter(c => c.correct).length;
2411
+ return numCorrect ?? choices.filter(c => c.correct).length;
1826
2412
  }
1827
2413
  const widgetOptionsUpgrades$2 = {
1828
2414
  "2": v1props => {
1829
- const upgraded = _extends({}, v1props, {
2415
+ const upgraded = {
2416
+ ...v1props,
1830
2417
  numCorrect: deriveNumCorrect(v1props)
1831
- });
2418
+ };
1832
2419
  return upgraded;
1833
2420
  },
1834
2421
  "1": v0props => {
1835
2422
  const {
1836
- noneOfTheAbove
1837
- } = v0props,
1838
- rest = _objectWithoutPropertiesLoose(v0props, _excluded$a);
2423
+ noneOfTheAbove,
2424
+ ...rest
2425
+ } = v0props;
1839
2426
  if (noneOfTheAbove) {
1840
2427
  throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
1841
2428
  }
1842
- return _extends({}, rest, {
2429
+ return {
2430
+ ...rest,
1843
2431
  hasNoneOfTheAbove: false
1844
- });
2432
+ };
1845
2433
  }
1846
2434
  };
1847
2435
  const defaultWidgetOptions$v = {
@@ -1854,7 +2442,6 @@ const defaultWidgetOptions$v = {
1854
2442
  deselectEnabled: false
1855
2443
  };
1856
2444
 
1857
- const _excluded$9 = ["noneOfTheAbove"];
1858
2445
  const version2 = optional(object({
1859
2446
  major: constant(2),
1860
2447
  minor: number
@@ -1866,6 +2453,10 @@ const parseRadioWidgetV2 = parseWidgetWithVersion(version2, constant("radio"), o
1866
2453
  clue: optional(string),
1867
2454
  correct: optional(boolean),
1868
2455
  isNoneOfTheAbove: optional(boolean),
2456
+ // deprecated
2457
+ // There is an import cycle between radio-widget.ts and
2458
+ // widgets-map.ts. The anonymous function below ensures that we
2459
+ // don't refer to parseWidgetsMap before it's defined.
1869
2460
  widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
1870
2461
  })),
1871
2462
  hasNoneOfTheAbove: optional(boolean),
@@ -1873,8 +2464,12 @@ const parseRadioWidgetV2 = parseWidgetWithVersion(version2, constant("radio"), o
1873
2464
  randomize: optional(boolean),
1874
2465
  multipleSelect: optional(boolean),
1875
2466
  deselectEnabled: optional(boolean),
2467
+ // deprecated
1876
2468
  onePerLine: optional(boolean),
2469
+ // deprecated
1877
2470
  displayCount: optional(any),
2471
+ // v0 props
2472
+ // `noneOfTheAbove` is still in use (but only set to `false`).
1878
2473
  noneOfTheAbove: optional(constant(false))
1879
2474
  }));
1880
2475
  const version1 = optional(object({
@@ -1887,6 +2482,10 @@ const parseRadioWidgetV1 = parseWidgetWithVersion(version1, constant("radio"), o
1887
2482
  clue: optional(string),
1888
2483
  correct: optional(boolean),
1889
2484
  isNoneOfTheAbove: optional(boolean),
2485
+ // deprecated
2486
+ // There is an import cycle between radio-widget.ts and
2487
+ // widgets-map.ts. The anonymous function below ensures that we
2488
+ // don't refer to parseWidgetsMap before it's defined.
1890
2489
  widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => undefined)
1891
2490
  })),
1892
2491
  hasNoneOfTheAbove: optional(boolean),
@@ -1894,23 +2493,29 @@ const parseRadioWidgetV1 = parseWidgetWithVersion(version1, constant("radio"), o
1894
2493
  randomize: optional(boolean),
1895
2494
  multipleSelect: optional(boolean),
1896
2495
  deselectEnabled: optional(boolean),
2496
+ // deprecated
1897
2497
  onePerLine: optional(boolean),
2498
+ // deprecated
1898
2499
  displayCount: optional(any),
2500
+ // v0 props
2501
+ // `noneOfTheAbove` is still in use (but only set to `false`).
1899
2502
  noneOfTheAbove: optional(constant(false))
1900
2503
  }));
1901
2504
  function migrateV1ToV2(widget) {
1902
2505
  const {
1903
2506
  options
1904
2507
  } = widget;
1905
- return _extends({}, widget, {
2508
+ return {
2509
+ ...widget,
1906
2510
  version: {
1907
2511
  major: 2,
1908
2512
  minor: 0
1909
2513
  },
1910
- options: _extends({}, options, {
2514
+ options: {
2515
+ ...options,
1911
2516
  numCorrect: deriveNumCorrect(options)
1912
- })
1913
- });
2517
+ }
2518
+ };
1914
2519
  }
1915
2520
  const version0 = optional(object({
1916
2521
  major: constant(0),
@@ -1922,6 +2527,10 @@ const parseRadioWidgetV0 = parseWidgetWithVersion(version0, constant("radio"), o
1922
2527
  clue: optional(string),
1923
2528
  correct: optional(boolean),
1924
2529
  isNoneOfTheAbove: optional(boolean),
2530
+ // deprecated
2531
+ // There is an import cycle between radio-widget.ts and
2532
+ // widgets-map.ts. The anonymous function below ensures that we
2533
+ // don't refer to parseWidgetsMap before it's defined.
1925
2534
  widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
1926
2535
  })),
1927
2536
  hasNoneOfTheAbove: optional(boolean),
@@ -1929,24 +2538,33 @@ const parseRadioWidgetV0 = parseWidgetWithVersion(version0, constant("radio"), o
1929
2538
  randomize: optional(boolean),
1930
2539
  multipleSelect: optional(boolean),
1931
2540
  deselectEnabled: optional(boolean),
2541
+ // deprecated
1932
2542
  onePerLine: optional(boolean),
2543
+ // deprecated
1933
2544
  displayCount: optional(any),
2545
+ // v0 props
2546
+ // `noneOfTheAbove` is still in use (but only set to `false`).
1934
2547
  noneOfTheAbove: optional(constant(false))
1935
2548
  }));
1936
2549
  function migrateV0ToV1(widget) {
1937
2550
  const {
1938
2551
  options
1939
2552
  } = widget;
1940
- const rest = _objectWithoutPropertiesLoose(options, _excluded$9);
1941
- return _extends({}, widget, {
2553
+ const {
2554
+ noneOfTheAbove: _,
2555
+ ...rest
2556
+ } = options;
2557
+ return {
2558
+ ...widget,
1942
2559
  version: {
1943
2560
  major: 1,
1944
2561
  minor: 0
1945
2562
  },
1946
- options: _extends({}, rest, {
2563
+ options: {
2564
+ ...rest,
1947
2565
  hasNoneOfTheAbove: false
1948
- })
1949
- });
2566
+ }
2567
+ };
1950
2568
  }
1951
2569
  const parseRadioWidget = versionedWidgetOptions(2, parseRadioWidgetV2).withMigrationFrom(1, parseRadioWidgetV1, migrateV1ToV2).withMigrationFrom(0, parseRadioWidgetV0, migrateV0ToV1).parser;
1952
2570
 
@@ -1974,6 +2592,9 @@ const parseWidgetsMap = (rawValue, ctx) => {
1974
2592
  }
1975
2593
  const widgetsMap = {};
1976
2594
  for (const key of Object.keys(rawValue)) {
2595
+ // parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
2596
+ // but it's the only way I could find to make TypeScript check the key
2597
+ // against the widget type.
1977
2598
  const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
1978
2599
  if (isFailure(entryResult)) {
1979
2600
  return entryResult;
@@ -1981,7 +2602,8 @@ const parseWidgetsMap = (rawValue, ctx) => {
1981
2602
  }
1982
2603
  return ctx.success(widgetsMap);
1983
2604
  };
1984
- const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
2605
+ const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
2606
+ let [id, widget] = _ref;
1985
2607
  const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
1986
2608
  if (isFailure(idComponentsResult)) {
1987
2609
  return idComponentsResult;
@@ -2047,6 +2669,9 @@ const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
2047
2669
  case "passage-ref":
2048
2670
  return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
2049
2671
  case "passage-ref-target":
2672
+ // NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
2673
+ // used in test content. See:
2674
+ // https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
2050
2675
  return parseAndAssign(`passage-ref-target ${n}`, any);
2051
2676
  case "phet-simulation":
2052
2677
  return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
@@ -2063,6 +2688,8 @@ const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
2063
2688
  case "video":
2064
2689
  return parseAndAssign(`video ${n}`, parseVideoWidget);
2065
2690
  case "sequence":
2691
+ // sequence is a deprecated widget type, and the corresponding
2692
+ // widget component no longer exists.
2066
2693
  return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
2067
2694
  case "lights-puzzle":
2068
2695
  return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
@@ -2074,8 +2701,16 @@ const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
2074
2701
  return parseAndAssign(`${type} ${n}`, parseWidget(constant(type), any));
2075
2702
  }
2076
2703
  };
2077
- const parseDeprecatedWidget = parseWidget((_, ctx) => ctx.success("deprecated-standin"), object({}));
2704
+ const parseDeprecatedWidget = parseWidget(
2705
+ // Ignore the incoming widget type and hardcode "deprecated-standin"
2706
+ (_, ctx) => ctx.success("deprecated-standin"),
2707
+ // Allow any widget options
2708
+ object({}));
2078
2709
  const parseStringToNonNegativeInt = (rawValue, ctx) => {
2710
+ // The article renderer seems to allow the numeric part of a widget ID to
2711
+ // be 0, at least for image widgets. However, if widget IDs in an exercise
2712
+ // contain 0, the exercise renderer will blow up. We allow 0 here for
2713
+ // compatibility with articles.
2079
2714
  if (typeof rawValue !== "string" || !/^(0|[1-9][0-9]*)$/.test(rawValue)) {
2080
2715
  return ctx.failure("a string representing a non-negative integer", rawValue);
2081
2716
  }
@@ -2084,11 +2719,20 @@ const parseStringToNonNegativeInt = (rawValue, ctx) => {
2084
2719
  const parseWidgetIdComponents = pair(string, parseStringToNonNegativeInt);
2085
2720
 
2086
2721
  const parsePerseusRenderer = defaulted(object({
2722
+ // TODO(benchristel): content is also defaulted to empty string in
2723
+ // renderer.tsx. See if we can remove one default or the other.
2087
2724
  content: defaulted(string, () => ""),
2725
+ // This module has an import cycle with parseWidgetsMap, because the
2726
+ // `group` widget can contain another renderer.
2727
+ // The anonymous function below ensures that we don't try to access
2728
+ // parseWidgetsMap before it's defined.
2088
2729
  widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
2089
2730
  images: parseImages,
2731
+ // deprecated
2090
2732
  metadata: any
2091
- }), () => ({
2733
+ }),
2734
+ // Default value
2735
+ () => ({
2092
2736
  content: "",
2093
2737
  widgets: {},
2094
2738
  images: {}
@@ -2101,10 +2745,25 @@ const parseHint = object({
2101
2745
  content: string,
2102
2746
  widgets: defaulted(parseWidgetsMap, () => ({})),
2103
2747
  images: parseImages,
2748
+ // deprecated
2104
2749
  metadata: any
2105
2750
  });
2106
2751
 
2107
2752
  const parsePerseusAnswerArea = pipeParsers(defaulted(object({}), () => ({}))).then(convert(toAnswerArea)).parser;
2753
+
2754
+ // Some answerAreas have extra, bogus fields, like:
2755
+ //
2756
+ // "answerArea": {
2757
+ // "type": "multiple",
2758
+ // "options": {},
2759
+ // "version": null,
2760
+ // "static": false,
2761
+ // "graded": false,
2762
+ // "alignment": "",
2763
+ // }
2764
+ //
2765
+ // This function filters the fields of an answerArea object, keeping only the
2766
+ // known ones, and converts `undefined` and `null` values to `false`.
2108
2767
  function toAnswerArea(raw) {
2109
2768
  return {
2110
2769
  zTable: !!raw.zTable,
@@ -2127,15 +2786,37 @@ const parsePerseusItem$1 = object({
2127
2786
  major: number,
2128
2787
  minor: number
2129
2788
  }))),
2789
+ // Deprecated field
2130
2790
  answer: any
2131
2791
  });
2132
2792
 
2793
+ /**
2794
+ * Helper to parse PerseusItem JSON
2795
+ * Why not just use JSON.parse? We want:
2796
+ * - To make sure types are correct
2797
+ * - To give us a central place to validate/transform output if needed
2798
+ * @deprecated - use parseAndMigratePerseusItem instead
2799
+ * @param {string} json - the stringified PerseusItem JSON
2800
+ * @returns {PerseusItem} the parsed PerseusItem object
2801
+ */
2133
2802
  function parsePerseusItem(json) {
2803
+ // Try to block a cheating vector which relies on monkey-patching
2804
+ // JSON.parse
2134
2805
  if (isRealJSONParse(JSON.parse)) {
2135
2806
  return JSON.parse(json);
2136
2807
  }
2137
2808
  throw new Error("Something went wrong.");
2138
2809
  }
2810
+ /**
2811
+ * Parses a PerseusItem from a JSON string, migrates old formats to the latest
2812
+ * schema, and runtime-typechecks the result. Use this to parse assessmentItem
2813
+ * data.
2814
+ *
2815
+ * @returns a {@link Result} of the parsed PerseusItem. If the result is a
2816
+ * failure, it will contain an error message describing where in the tree
2817
+ * parsing failed.
2818
+ * @throws SyntaxError if the argument is not well-formed JSON.
2819
+ */
2139
2820
  function parseAndMigratePerseusItem(json) {
2140
2821
  throwErrorIfCheatingDetected();
2141
2822
  const object = JSON.parse(json);
@@ -2148,6 +2829,16 @@ function parseAndMigratePerseusItem(json) {
2148
2829
  }
2149
2830
  return result;
2150
2831
  }
2832
+
2833
+ /**
2834
+ * Parses a PerseusArticle from a JSON string, migrates old formats to the
2835
+ * latest schema, and runtime-typechecks the result.
2836
+ *
2837
+ * @returns a {@link Result} of the parsed PerseusArticle. If the result is a
2838
+ * failure, it will contain an error message describing where in the tree
2839
+ * parsing failed.
2840
+ * @throws SyntaxError if the argument is not well-formed JSON.
2841
+ */
2151
2842
  function parseAndMigratePerseusArticle(json) {
2152
2843
  throwErrorIfCheatingDetected();
2153
2844
  const object = JSON.parse(json);
@@ -2160,48 +2851,131 @@ function parseAndMigratePerseusArticle(json) {
2160
2851
  }
2161
2852
  return result;
2162
2853
  }
2854
+
2855
+ /**
2856
+ * Tries to block a cheating vector that relies on monkey-patching JSON.parse.
2857
+ */
2858
+ // TODO(LEMS-2331): delete this function once server-side scoring is done.
2163
2859
  function throwErrorIfCheatingDetected() {
2164
2860
  if (!isRealJSONParse(JSON.parse)) {
2165
2861
  throw new Error("Something went wrong.");
2166
2862
  }
2167
2863
  }
2168
2864
 
2865
+ // This file is processed by a Rollup plugin (replace) to inject the production
2866
+ // version number during the release build.
2867
+ // In dev, you'll never see the version number.
2868
+
2169
2869
  const libName = "@khanacademy/perseus-core";
2170
- const libVersion = "6.0.0";
2171
- addLibraryVersionToPerseusDebug(libName, libVersion);
2870
+ const libVersion = "7.0.0";
2871
+ perseusUtils.addLibraryVersionToPerseusDebug(libName, libVersion);
2172
2872
 
2873
+ /**
2874
+ * @typedef {Object} Errors utility for referencing the Perseus error taxonomy.
2875
+ */
2173
2876
  const Errors = Object.freeze({
2877
+ /**
2878
+ * @property {ErrorKind} Unknown The kind of error is not known.
2879
+ */
2174
2880
  Unknown: "Unknown",
2881
+ /**
2882
+ * @property {ErrorKind} Internal The error is internal to the executing code.
2883
+ */
2175
2884
  Internal: "Internal",
2885
+ /**
2886
+ * @property {ErrorKind} InvalidInput There was a problem with the provided
2887
+ * input, such as the wrong format or a null value.
2888
+ */
2176
2889
  InvalidInput: "InvalidInput",
2890
+ /**
2891
+ * @property {ErrorKind} NotAllowed There was a problem due to the state of
2892
+ * the system not matching the requested operation or input. For example,
2893
+ * trying to create a username that is valid, but is already taken by
2894
+ * another user. Use {@link InvalidInput} instead when the input isn't
2895
+ * valid regardless of the state of the system. Use {@link NotFound} when
2896
+ * the failure is due to not being able to find a resource.
2897
+ */
2177
2898
  NotAllowed: "NotAllowed",
2899
+ /**
2900
+ * @property {ErrorKind} TransientService There was a problem when making a
2901
+ * request to a service.
2902
+ */
2178
2903
  TransientService: "TransientService",
2904
+ /**
2905
+ * @property {ErrorKind} Service There was a non-transient problem when
2906
+ * making a request to service.
2907
+ */
2179
2908
  Service: "Service"
2180
2909
  });
2181
2910
 
2911
+ /**
2912
+ * @type {ErrorKind} The kind of error being reported
2913
+ */
2914
+
2182
2915
  class PerseusError extends Error {
2916
+ kind;
2917
+ metadata;
2183
2918
  constructor(message, kind, options) {
2184
2919
  super(message);
2185
- this.kind = void 0;
2186
- this.metadata = void 0;
2187
2920
  this.kind = kind;
2188
- this.metadata = options == null ? void 0 : options.metadata;
2921
+ this.metadata = options?.metadata;
2189
2922
  }
2190
2923
  }
2191
2924
 
2192
- const pluck = function pluck(table, subKey) {
2193
- return _.object(_.map(table, function (value, key) {
2925
+ /**
2926
+ * _ utilities for objects
2927
+ */
2928
+
2929
+
2930
+ /**
2931
+ * Does a pluck on keys inside objects in an object
2932
+ *
2933
+ * Ex:
2934
+ * tools = {
2935
+ * translation: {
2936
+ * enabled: true
2937
+ * },
2938
+ * rotation: {
2939
+ * enabled: false
2940
+ * }
2941
+ * };
2942
+ * pluckObject(tools, "enabled") returns {
2943
+ * translation: true
2944
+ * rotation: false
2945
+ * }
2946
+ */
2947
+ const pluck = function (table, subKey) {
2948
+ return ___default.default.object(___default.default.map(table, function (value, key) {
2194
2949
  return [key, value[subKey]];
2195
2950
  }));
2196
2951
  };
2197
- const mapObject = function mapObject(obj, lambda) {
2952
+
2953
+ /**
2954
+ * Maps an object to an object
2955
+ *
2956
+ * > mapObject({a: '1', b: '2'}, (value, key) => {
2957
+ * return value + 1;
2958
+ * });
2959
+ * {a: 2, b: 3}
2960
+ */
2961
+ const mapObject = function (obj, lambda) {
2198
2962
  const result = {};
2199
2963
  Object.keys(obj).forEach(key => {
2964
+ // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
2200
2965
  result[key] = lambda(obj[key], key);
2201
2966
  });
2202
2967
  return result;
2203
2968
  };
2204
2969
 
2970
+ /**
2971
+ * For details on the individual options, see the
2972
+ * PerseusCategorizerWidgetOptions type
2973
+ */
2974
+
2975
+ /**
2976
+ * Given a PerseusCategorizerWidgetOptions object, return a new object with only
2977
+ * the public options that should be exposed to the client.
2978
+ */
2205
2979
  function getCategorizerPublicWidgetOptions(options) {
2206
2980
  return {
2207
2981
  items: options.items,
@@ -2256,6 +3030,15 @@ const definitionWidgetLogic = {
2256
3030
  defaultAlignment: "inline"
2257
3031
  };
2258
3032
 
3033
+ /**
3034
+ * For details on the individual options, see the
3035
+ * PerseusDropdownWidgetOptions type
3036
+ */
3037
+
3038
+ /**
3039
+ * Given a PerseusDropdownWidgetOptions object, return a new object with only
3040
+ * the public options that should be exposed to the client.
3041
+ */
2259
3042
  function getDropdownPublicWidgetOptions(options) {
2260
3043
  return {
2261
3044
  choices: options.choices.map(choice => ({
@@ -2336,6 +3119,15 @@ const defaultWidgetOptions$p = {
2336
3119
  functions: ["f", "g", "h"]
2337
3120
  };
2338
3121
 
3122
+ /**
3123
+ * For details on the individual options, see the
3124
+ * PerseusExpressionWidgetOptions type
3125
+ */
3126
+
3127
+ /**
3128
+ * Given a PerseusExpressionWidgetOptions object, return a new object with only
3129
+ * the public options that should be exposed to the client.
3130
+ */
2339
3131
  function getExpressionPublicWidgetOptions(options) {
2340
3132
  return {
2341
3133
  buttonSets: options.buttonSets,
@@ -2377,9 +3169,11 @@ const gradedGroupSetWidgetLogic = {
2377
3169
  defaultWidgetOptions: defaultWidgetOptions$n
2378
3170
  };
2379
3171
 
2380
- const _excluded$8 = ["correct"];
2381
3172
  function getGrapherPublicWidgetOptions(options) {
2382
- const publicOptions = _objectWithoutPropertiesLoose(options, _excluded$8);
3173
+ const {
3174
+ correct: _,
3175
+ ...publicOptions
3176
+ } = options;
2383
3177
  return publicOptions;
2384
3178
  }
2385
3179
 
@@ -2491,9 +3285,11 @@ const interactionWidgetLogic = {
2491
3285
  defaultWidgetOptions: defaultWidgetOptions$h
2492
3286
  };
2493
3287
 
2494
- const _excluded$7 = ["correct"];
2495
3288
  function getInteractiveGraphPublicWidgetOptions(options) {
2496
- const publicOptions = _objectWithoutPropertiesLoose(options, _excluded$7);
3289
+ const {
3290
+ correct: _,
3291
+ ...publicOptions
3292
+ } = options;
2497
3293
  return publicOptions;
2498
3294
  }
2499
3295
 
@@ -2521,14 +3317,22 @@ const interactiveGraphWidgetLogic = {
2521
3317
  getPublicWidgetOptions: getInteractiveGraphPublicWidgetOptions
2522
3318
  };
2523
3319
 
2524
- const _excluded$6 = ["answers"];
3320
+ /**
3321
+ * For details on the individual options, see the
3322
+ * PerseusLabelImageWidgetOptions type
3323
+ */
3324
+
2525
3325
  function getLabelImagePublicWidgetOptions(options) {
2526
- return _extends({}, options, {
3326
+ return {
3327
+ ...options,
2527
3328
  markers: options.markers.map(getLabelImageMarkerPublicData)
2528
- });
3329
+ };
2529
3330
  }
2530
3331
  function getLabelImageMarkerPublicData(marker) {
2531
- const publicData = _objectWithoutPropertiesLoose(marker, _excluded$6);
3332
+ const {
3333
+ answers: _,
3334
+ ...publicData
3335
+ } = marker;
2532
3336
  return publicData;
2533
3337
  }
2534
3338
 
@@ -2548,9 +3352,14 @@ const labelImageWidgetLogic = {
2548
3352
  getPublicWidgetOptions: getLabelImagePublicWidgetOptions
2549
3353
  };
2550
3354
 
2551
- const seededRNG = function seededRNG(seed) {
3355
+ /* Note(tamara): Brought over from the perseus package packages/perseus/src/util.ts file.
3356
+ May be useful to bring other perseus package utilities here. Contains utility functions
3357
+ and types used across multiple widgets for randomization and shuffling. */
3358
+
3359
+ const seededRNG = function (seed) {
2552
3360
  let randomSeed = seed;
2553
3361
  return function () {
3362
+ // Robert Jenkins' 32 bit integer hash function.
2554
3363
  let seed = randomSeed;
2555
3364
  seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
2556
3365
  seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
@@ -2561,10 +3370,20 @@ const seededRNG = function seededRNG(seed) {
2561
3370
  return (randomSeed = seed & 0xfffffff) / 0x10000000;
2562
3371
  };
2563
3372
  };
2564
- function shuffle(array, randomSeed, ensurePermuted = false) {
2565
- const shuffled = _.clone(array);
2566
- if (!shuffled.length || _.all(shuffled, function (value) {
2567
- return _.isEqual(value, shuffled[0]);
3373
+
3374
+ // Shuffle an array using a given random seed or function.
3375
+ // If `ensurePermuted` is true, the input and output are guaranteed to be
3376
+ // distinct permutations.
3377
+ function shuffle(array, randomSeed) {
3378
+ let ensurePermuted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
3379
+ // Always return a copy of the input array
3380
+ const shuffled = ___default.default.clone(array);
3381
+
3382
+ // Handle edge cases (input array is empty or uniform)
3383
+ if (
3384
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
3385
+ !shuffled.length || ___default.default.all(shuffled, function (value) {
3386
+ return ___default.default.isEqual(value, shuffled[0]);
2568
3387
  })) {
2569
3388
  return shuffled;
2570
3389
  }
@@ -2575,53 +3394,77 @@ function shuffle(array, randomSeed, ensurePermuted = false) {
2575
3394
  random = seededRNG(randomSeed);
2576
3395
  }
2577
3396
  do {
3397
+ // Fischer-Yates shuffle
2578
3398
  for (let top = shuffled.length; top > 0; top--) {
2579
3399
  const newEnd = Math.floor(random() * top);
2580
3400
  const temp = shuffled[newEnd];
3401
+
3402
+ // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
2581
3403
  shuffled[newEnd] = shuffled[top - 1];
3404
+ // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
2582
3405
  shuffled[top - 1] = temp;
2583
3406
  }
2584
- } while (ensurePermuted && _.isEqual(array, shuffled));
3407
+ } while (ensurePermuted && ___default.default.isEqual(array, shuffled));
2585
3408
  return shuffled;
2586
3409
  }
2587
3410
  const random = seededRNG(new Date().getTime() & 0xffffffff);
2588
3411
 
3412
+ // TODO(LEMS-2841): Should be able to remove once getPublicWidgetOptions is hooked up
3413
+
3414
+ // TODO(LEMS-2841): Should be able to remove once getPublicWidgetOptions is hooked up
2589
3415
  const shuffleMatcher = props => {
3416
+ // Use the same random() function to shuffle both columns sequentially
2590
3417
  const rng = seededRNG(props.problemNum);
2591
3418
  let left;
2592
3419
  if (!props.orderMatters) {
3420
+ // If the order doesn't matter, don't shuffle the left column
2593
3421
  left = props.left;
2594
3422
  } else {
2595
- left = shuffle(props.left, rng, true);
3423
+ left = shuffle(props.left, rng, /* ensurePermuted */true);
2596
3424
  }
2597
- const right = shuffle(props.right, rng, true);
3425
+ const right = shuffle(props.right, rng, /* ensurePermuted */true);
2598
3426
  return {
2599
3427
  left,
2600
3428
  right
2601
3429
  };
2602
3430
  };
3431
+
3432
+ // TODO(LEMS-2841): Can shorten to shuffleMatcher after above function removed
2603
3433
  function shuffleMatcherWithRandom(data) {
3434
+ // Use the same random() function to shuffle both columns sequentially
2604
3435
  let left;
2605
3436
  if (!data.orderMatters) {
3437
+ // If the order doesn't matter, don't shuffle the left column
2606
3438
  left = data.left;
2607
3439
  } else {
2608
- left = shuffle(data.left, Math.random, true);
3440
+ left = shuffle(data.left, Math.random, /* ensurePermuted */true);
2609
3441
  }
2610
- const right = shuffle(data.right, Math.random, true);
3442
+ const right = shuffle(data.right, Math.random, /* ensurePermuted */true);
2611
3443
  return {
2612
3444
  left,
2613
3445
  right
2614
3446
  };
2615
3447
  }
3448
+
3449
+ /**
3450
+ * For details on the individual options, see the
3451
+ * PerseusMatcherWidgetOptions type
3452
+ */
3453
+
3454
+ /**
3455
+ * Given a PerseusMatcherWidgetOptions object, return a new object with only
3456
+ * the public options that should be exposed to the client.
3457
+ */
2616
3458
  function getMatcherPublicWidgetOptions(options) {
2617
3459
  const {
2618
3460
  left,
2619
3461
  right
2620
3462
  } = shuffleMatcherWithRandom(options);
2621
- return _extends({}, options, {
3463
+ return {
3464
+ ...options,
2622
3465
  left: left,
2623
3466
  right: right
2624
- });
3467
+ };
2625
3468
  }
2626
3469
 
2627
3470
  const defaultWidgetOptions$e = {
@@ -2637,9 +3480,11 @@ const matcherWidgetLogic = {
2637
3480
  getPublicWidgetOptions: getMatcherPublicWidgetOptions
2638
3481
  };
2639
3482
 
2640
- const _excluded$5 = ["answers"];
2641
3483
  function getMatrixPublicWidgetOptions(options) {
2642
- const publicOptions = _objectWithoutPropertiesLoose(options, _excluded$5);
3484
+ const {
3485
+ answers: _,
3486
+ ...publicOptions
3487
+ } = options;
2643
3488
  return publicOptions;
2644
3489
  }
2645
3490
 
@@ -2656,7 +3501,6 @@ const matrixWidgetLogic = {
2656
3501
  getPublicWidgetOptions: getMatrixPublicWidgetOptions
2657
3502
  };
2658
3503
 
2659
- const _excluded$4 = ["imageUrl", "imageTop", "imageLeft"];
2660
3504
  const currentVersion$1 = {
2661
3505
  major: 1,
2662
3506
  minor: 0
@@ -2664,18 +3508,19 @@ const currentVersion$1 = {
2664
3508
  const widgetOptionsUpgrades = {
2665
3509
  "1": v0options => {
2666
3510
  const {
2667
- imageUrl,
2668
- imageTop,
2669
- imageLeft
2670
- } = v0options,
2671
- rest = _objectWithoutPropertiesLoose(v0options, _excluded$4);
2672
- return _extends({}, rest, {
3511
+ imageUrl,
3512
+ imageTop,
3513
+ imageLeft,
3514
+ ...rest
3515
+ } = v0options;
3516
+ return {
3517
+ ...rest,
2673
3518
  image: {
2674
3519
  url: imageUrl,
2675
3520
  top: imageTop,
2676
3521
  left: imageLeft
2677
3522
  }
2678
- });
3523
+ };
2679
3524
  }
2680
3525
  };
2681
3526
  const defaultWidgetOptions$c = {
@@ -2696,9 +3541,12 @@ const measurerWidgetLogic = {
2696
3541
  defaultWidgetOptions: defaultWidgetOptions$c
2697
3542
  };
2698
3543
 
2699
- const _excluded$3 = ["correctX", "correctRel"];
2700
3544
  function getNumberLinePublicWidgetOptions(options) {
2701
- const publicOptions = _objectWithoutPropertiesLoose(options, _excluded$3);
3545
+ const {
3546
+ correctX: _,
3547
+ correctRel: __,
3548
+ ...publicOptions
3549
+ } = options;
2702
3550
  return publicOptions;
2703
3551
  }
2704
3552
 
@@ -2722,7 +3570,15 @@ const numberLineWidgetLogic = {
2722
3570
  getPublicWidgetOptions: getNumberLinePublicWidgetOptions
2723
3571
  };
2724
3572
 
2725
- const _excluded$2 = ["answers"];
3573
+ /**
3574
+ * For details on the individual options, see the
3575
+ * PerseusNumericInputWidgetOptions type
3576
+ */
3577
+
3578
+ /**
3579
+ * This data from `answers` is used pre-scoring to give hints
3580
+ * to the learner regarding the format of accepted answers
3581
+ */
2726
3582
  function getNumericInputAnswerPublicData(answer) {
2727
3583
  const {
2728
3584
  answerForms,
@@ -2735,14 +3591,20 @@ function getNumericInputAnswerPublicData(answer) {
2735
3591
  status
2736
3592
  };
2737
3593
  }
3594
+
3595
+ /**
3596
+ * Given a PerseusNumericInputWidgetOptions object, return a new object with only
3597
+ * the public options that should be exposed to the client.
3598
+ */
2738
3599
  function getNumericInputPublicWidgetOptions(options) {
2739
3600
  const {
2740
- answers
2741
- } = options,
2742
- publicWidgetOptions = _objectWithoutPropertiesLoose(options, _excluded$2);
2743
- return _extends({}, publicWidgetOptions, {
3601
+ answers,
3602
+ ...publicWidgetOptions
3603
+ } = options;
3604
+ return {
3605
+ ...publicWidgetOptions,
2744
3606
  answers: answers.map(getNumericInputAnswerPublicData)
2745
- });
3607
+ };
2746
3608
  }
2747
3609
 
2748
3610
  const defaultWidgetOptions$a = {
@@ -2767,6 +3629,15 @@ const numericInputWidgetLogic = {
2767
3629
  getPublicWidgetOptions: getNumericInputPublicWidgetOptions
2768
3630
  };
2769
3631
 
3632
+ /**
3633
+ * For details on the individual options, see the
3634
+ * PerseusOrdererWidgetOptions type
3635
+ */
3636
+
3637
+ /**
3638
+ * Given a PerseusOrdererWidgetOptions object, return a new object with only
3639
+ * the public options that should be exposed to the client.
3640
+ */
2770
3641
  function getOrdererPublicWidgetOptions(options) {
2771
3642
  return {
2772
3643
  options: options.options,
@@ -2837,9 +3708,20 @@ const phetSimulationWidgetLogic = {
2837
3708
  defaultWidgetOptions: defaultWidgetOptions$5
2838
3709
  };
2839
3710
 
2840
- const _excluded$1 = ["correct"];
3711
+ /**
3712
+ * For details on the individual options, see the
3713
+ * PerseusPlotterWidgetOptions type
3714
+ */
3715
+
3716
+ /**
3717
+ * Given a PerseusPlotterWidgetOptions object, return a new object with only
3718
+ * the public options that should be exposed to the client.
3719
+ */
2841
3720
  function getPlotterPublicWidgetOptions(options) {
2842
- const publicOptions = _objectWithoutPropertiesLoose(options, _excluded$1);
3721
+ const {
3722
+ correct: _,
3723
+ ...publicOptions
3724
+ } = options;
2843
3725
  return publicOptions;
2844
3726
  }
2845
3727
 
@@ -2873,6 +3755,19 @@ const pythonProgramWidgetLogic = {
2873
3755
  defaultWidgetOptions: defaultWidgetOptions$3
2874
3756
  };
2875
3757
 
3758
+ /**
3759
+ * For details on the individual options, see the
3760
+ * PerseusRadioWidgetOptions type
3761
+ */
3762
+
3763
+ /**
3764
+ * Only the options from each Radio choice that should be exposed to the client.
3765
+ */
3766
+
3767
+ /**
3768
+ * Given a PerseusRadioChoice object, return a new object with only the public
3769
+ * data that should be included in the Radio public widget options.
3770
+ */
2876
3771
  function getRadioChoicePublicData(choice) {
2877
3772
  const {
2878
3773
  content,
@@ -2885,9 +3780,23 @@ function getRadioChoicePublicData(choice) {
2885
3780
  widgets
2886
3781
  };
2887
3782
  }
3783
+
3784
+ /**
3785
+ * Shared functionality to determine if numCorrect is used, because:
3786
+ *
3787
+ * 1. numCorrect is conditionally used for rendering pre-scoring
3788
+ * 2. numCorrect also exposes information about answers
3789
+ *
3790
+ * So only include/use numCorrect when we know it's useful.
3791
+ */
2888
3792
  function usesNumCorrect(multipleSelect, countChoices, numCorrect) {
2889
3793
  return multipleSelect && countChoices && numCorrect;
2890
3794
  }
3795
+
3796
+ /**
3797
+ * Given a PerseusRadioWidgetOptions object, return a new object with only
3798
+ * the public options that should be exposed to the client.
3799
+ */
2891
3800
  function getRadioPublicWidgetOptions(options) {
2892
3801
  const {
2893
3802
  numCorrect,
@@ -2895,10 +3804,12 @@ function getRadioPublicWidgetOptions(options) {
2895
3804
  multipleSelect,
2896
3805
  countChoices
2897
3806
  } = options;
2898
- return _extends({}, options, {
3807
+ return {
3808
+ ...options,
3809
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
2899
3810
  numCorrect: usesNumCorrect(multipleSelect, countChoices, numCorrect) ? numCorrect : undefined,
2900
3811
  choices: choices.map(getRadioChoicePublicData)
2901
- });
3812
+ };
2902
3813
  }
2903
3814
 
2904
3815
  const radioWidgetLogic = {
@@ -2909,12 +3820,28 @@ const radioWidgetLogic = {
2909
3820
  getPublicWidgetOptions: getRadioPublicWidgetOptions
2910
3821
  };
2911
3822
 
3823
+ /**
3824
+ * For details on the individual options, see the
3825
+ * PerseusSorterWidgetOptions type
3826
+ */
3827
+
3828
+ /**
3829
+ * Given a PerseusSorterWidgetOptions object, return a new object with only
3830
+ * the public options that should be exposed to the client.
3831
+ */
2912
3832
  function getSorterPublicWidgetOptions(options) {
2913
- const shuffledCorrect = shuffle(options.correct, Math.random, true);
2914
- return _extends({}, options, {
3833
+ const shuffledCorrect = shuffle(options.correct, Math.random, /* ensurePermuted */true);
3834
+ return {
3835
+ ...options,
3836
+ // Note(Tamara): This does not provide correct answer information any longer.
3837
+ // To maintain compatibility with the original widget options, we are
3838
+ // keeping the key the same. Represents initial state of the cards here.
2915
3839
  correct: shuffledCorrect,
3840
+ // Note(Tamara): This new key is only added here with "true". There isn't
3841
+ // a place where it is set to false. It indicates that the correct field
3842
+ // has been shuffled and no longer contains correct answer info.
2916
3843
  isCorrectShuffled: true
2917
- });
3844
+ };
2918
3845
  }
2919
3846
 
2920
3847
  const defaultWidgetOptions$2 = {
@@ -2928,14 +3855,19 @@ const sorterWidgetLogic = {
2928
3855
  getPublicWidgetOptions: getSorterPublicWidgetOptions
2929
3856
  };
2930
3857
 
2931
- const _excluded = ["answers"];
2932
3858
  function getTablePublicWidgetOptions(options) {
2933
- const publicOptions = _objectWithoutPropertiesLoose(options, _excluded);
3859
+ const {
3860
+ answers: _,
3861
+ ...publicOptions
3862
+ } = options;
2934
3863
  return publicOptions;
2935
3864
  }
2936
3865
 
2937
3866
  const defaultRows = 4;
2938
3867
  const defaultColumns = 1;
3868
+
3869
+ // initialize a 2D array
3870
+ // (defaultRows x defaultColumns) of empty strings
2939
3871
  const answers = new Array(defaultRows).fill(0).map(() => new Array(defaultColumns).fill(""));
2940
3872
  const defaultWidgetOptions$1 = {
2941
3873
  headers: [""],
@@ -2969,34 +3901,60 @@ function isWidgetRegistered(type) {
2969
3901
  }
2970
3902
  function getCurrentVersion(type) {
2971
3903
  const widgetLogic = widgets[type];
2972
- return (widgetLogic == null ? void 0 : widgetLogic.version) || {
3904
+ return widgetLogic?.version || {
2973
3905
  major: 0,
2974
3906
  minor: 0
2975
3907
  };
2976
3908
  }
3909
+
3910
+ // TODO(LEMS-2870): getPublicWidgetOptionsFunction/PublicWidgetOptionsFunction
3911
+ // need better types
2977
3912
  const getPublicWidgetOptionsFunction = name => {
2978
- var _widgets$name$getPubl, _widgets$name;
2979
- return (_widgets$name$getPubl = (_widgets$name = widgets[name]) == null ? void 0 : _widgets$name.getPublicWidgetOptions) != null ? _widgets$name$getPubl : i => i;
3913
+ return widgets[name]?.getPublicWidgetOptions ?? (i => i);
2980
3914
  };
2981
3915
  function getWidgetOptionsUpgrades(type) {
2982
3916
  const widgetLogic = widgets[type];
2983
- return (widgetLogic == null ? void 0 : widgetLogic.widgetOptionsUpgrades) || {};
3917
+ return widgetLogic?.widgetOptionsUpgrades || {};
2984
3918
  }
2985
3919
  function getDefaultWidgetOptions(type) {
2986
3920
  const widgetLogic = widgets[type];
2987
- return (widgetLogic == null ? void 0 : widgetLogic.defaultWidgetOptions) || {};
3921
+ return widgetLogic?.defaultWidgetOptions || {};
2988
3922
  }
3923
+
3924
+ /**
3925
+ * Handling for the optional alignments for widgets
3926
+ * See widget-container.jsx for details on how alignments are implemented.
3927
+ */
3928
+
3929
+ /**
3930
+ * Returns the list of supported alignments for the given (string) widget
3931
+ * type. This is used primarily at editing time to display the choices
3932
+ * for the user.
3933
+ *
3934
+ * Supported alignments are given as an array of strings in the exports of
3935
+ * a widget's module.
3936
+ */
2989
3937
  const getSupportedAlignments = type => {
2990
- var _widgetLogic$supporte;
2991
3938
  const widgetLogic = widgets[type];
2992
- if (!(widgetLogic != null && (_widgetLogic$supporte = widgetLogic.supportedAlignments) != null && _widgetLogic$supporte[0])) {
3939
+ if (!widgetLogic?.supportedAlignments?.[0]) {
3940
+ // default alignments
2993
3941
  return ["default"];
2994
3942
  }
2995
- return widgetLogic == null ? void 0 : widgetLogic.supportedAlignments;
3943
+ return widgetLogic?.supportedAlignments;
2996
3944
  };
3945
+
3946
+ /**
3947
+ * For the given (string) widget type, determine the default alignment for
3948
+ * the widget. This is used at rendering time to go from "default" alignment
3949
+ * to the actual alignment displayed on the screen.
3950
+ *
3951
+ * The default alignment is given either as a string (called
3952
+ * `defaultAlignment`) or a function (called `getDefaultAlignment`) on
3953
+ * the exports of a widget's module.
3954
+ */
2997
3955
  const getDefaultAlignment = type => {
2998
3956
  const widgetLogic = widgets[type];
2999
- if (!(widgetLogic != null && widgetLogic.defaultAlignment)) {
3957
+ if (!widgetLogic?.defaultAlignment) {
3000
3958
  return "block";
3001
3959
  }
3002
3960
  return widgetLogic.defaultAlignment;
@@ -3048,23 +4006,52 @@ var coreWidgetRegistry = /*#__PURE__*/Object.freeze({
3048
4006
  const DEFAULT_STATIC = false;
3049
4007
  const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
3050
4008
  const type = oldWidgetInfo.type;
3051
- if (!_.isString(type)) {
4009
+ // NOTE(jeremy): This looks like it could be replaced by fixing types so
4010
+ // that `type` is non-optional. But we're seeing this in Sentry today so I
4011
+ // suspect we have legacy data (potentially unpublished) and we should
4012
+ // figure that out before depending solely on types.
4013
+ if (!___default.default.isString(type)) {
3052
4014
  throw new PerseusError("widget type must be a string, but was: " + type, Errors.Internal);
3053
4015
  }
3054
4016
  if (!isWidgetRegistered(type)) {
4017
+ // If we have a widget that isn't registered, we can't upgrade it
4018
+ // TODO(aria): Figure out what the best thing to do here would be
3055
4019
  return oldWidgetInfo;
3056
4020
  }
4021
+
4022
+ // Unversioned widgets (pre-July 2014) are all implicitly 0.0
3057
4023
  const initialVersion = oldWidgetInfo.version || {
3058
4024
  major: 0,
3059
4025
  minor: 0
3060
4026
  };
3061
4027
  const latestVersion = getCurrentVersion(type);
4028
+
4029
+ // If the widget version is later than what we understand (major
4030
+ // version is higher than latest, or major versions are equal and minor
4031
+ // version is higher than latest), don't perform any upgrades.
3062
4032
  if (initialVersion.major > latestVersion.major || initialVersion.major === latestVersion.major && initialVersion.minor > latestVersion.minor) {
3063
4033
  return oldWidgetInfo;
3064
4034
  }
3065
- let newEditorOptions = _.clone(oldWidgetInfo.options) || {};
4035
+
4036
+ // We do a clone here so that it's safe to mutate the input parameter
4037
+ // in propUpgrades functions (which I will probably accidentally do at
4038
+ // some point, and we would like to not break when that happens).
4039
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
4040
+ let newEditorOptions = ___default.default.clone(oldWidgetInfo.options) || {};
3066
4041
  const upgradePropsMap = getWidgetOptionsUpgrades(type);
3067
- if (_.keys(newEditorOptions).length !== 0) {
4042
+
4043
+ // Empty props usually mean a newly created widget by the editor,
4044
+ // and are always considerered up-to-date.
4045
+ // Mostly, we'd rather not run upgrade functions on props that are
4046
+ // not complete.
4047
+ if (___default.default.keys(newEditorOptions).length !== 0) {
4048
+ // We loop through all the versions after the current version of
4049
+ // the loaded widget, up to and including the latest version of the
4050
+ // loaded widget, and run the upgrade function to bring our loaded
4051
+ // widget's props up to that version.
4052
+ // There is a little subtlety here in that we call
4053
+ // upgradePropsMap[1] to upgrade *to* version 1,
4054
+ // (not from version 1).
3068
4055
  for (let nextVersion = initialVersion.major + 1; nextVersion <= latestVersion.major; nextVersion++) {
3069
4056
  if (upgradePropsMap[String(nextVersion)]) {
3070
4057
  newEditorOptions = upgradePropsMap[String(nextVersion)](newEditorOptions);
@@ -3080,12 +4067,23 @@ const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
3080
4067
  }
3081
4068
  }
3082
4069
  }
4070
+
4071
+ // Minor version upgrades (eg. new optional props) don't have
4072
+ // transform functions. Instead, we fill in the new props with their
4073
+ // defaults.
3083
4074
  const defaultOptions = getDefaultWidgetOptions(type);
3084
- newEditorOptions = _extends({}, defaultOptions, newEditorOptions);
4075
+ newEditorOptions = {
4076
+ ...defaultOptions,
4077
+ ...newEditorOptions
4078
+ };
3085
4079
  let alignment = oldWidgetInfo.alignment;
4080
+
4081
+ // Widgets that support multiple alignments will "lock in" the
4082
+ // alignment to the alignment that would be listed first in the
4083
+ // select box. If the widget only supports one alignment, the
4084
+ // alignment value will likely just end up as "default".
3086
4085
  if (alignment == null || alignment === "default") {
3087
- var _getSupportedAlignmen;
3088
- alignment = (_getSupportedAlignmen = getSupportedAlignments(type)) == null ? void 0 : _getSupportedAlignmen[0];
4086
+ alignment = getSupportedAlignments(type)?.[0];
3089
4087
  if (!alignment) {
3090
4088
  throw new PerseusError("No default alignment found when upgrading widget", Errors.Internal, {
3091
4089
  metadata: {
@@ -3098,46 +4096,146 @@ const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
3098
4096
  if (widgetStatic == null) {
3099
4097
  widgetStatic = DEFAULT_STATIC;
3100
4098
  }
3101
- return _extends({}, oldWidgetInfo, {
4099
+ return {
4100
+ ...oldWidgetInfo,
4101
+ // maintain other info, like type
4102
+ // After upgrading we guarantee that the version is up-to-date
3102
4103
  version: latestVersion,
4104
+ // Default graded to true (so null/undefined becomes true):
3103
4105
  graded: oldWidgetInfo.graded != null ? oldWidgetInfo.graded : true,
3104
4106
  alignment: alignment,
3105
4107
  static: widgetStatic,
3106
4108
  options: newEditorOptions
3107
- });
4109
+ };
3108
4110
  };
3109
4111
  function getUpgradedWidgetOptions(oldWidgetOptions) {
3110
4112
  return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
3111
4113
  if (!widgetInfo.type || !widgetInfo.alignment) {
3112
4114
  const newValues = {};
3113
4115
  if (!widgetInfo.type) {
4116
+ // TODO: why does widget have no type?
4117
+ // We don't want to derive type from widget ID
4118
+ // see: LEMS-1845
3114
4119
  newValues.type = widgetId.split(" ")[0];
3115
4120
  }
3116
4121
  if (!widgetInfo.alignment) {
3117
4122
  newValues.alignment = "default";
3118
4123
  }
3119
- widgetInfo = _extends({}, widgetInfo, newValues);
4124
+ widgetInfo = {
4125
+ ...widgetInfo,
4126
+ ...newValues
4127
+ };
3120
4128
  }
3121
4129
  return upgradeWidgetInfoToLatestVersion(widgetInfo);
3122
4130
  });
3123
4131
  }
3124
4132
 
4133
+ /**
4134
+ * Return a copy of a Perseus item with rubric data removed (ie answers)
4135
+ *
4136
+ * @param originalItem - the original, full Perseus item (which includes the rubric - aka answer data)
4137
+ */
3125
4138
  function splitPerseusItem(originalItem) {
3126
- var _item$widgets;
3127
- const item = _.clone(originalItem);
3128
- const originalWidgets = (_item$widgets = item.widgets) != null ? _item$widgets : {};
4139
+ const item = ___default.default.clone(originalItem);
4140
+ const originalWidgets = item.widgets ?? {};
3129
4141
  const upgradedWidgets = getUpgradedWidgetOptions(originalWidgets);
3130
4142
  const splitWidgets = {};
3131
4143
  for (const [id, widget] of Object.entries(upgradedWidgets)) {
3132
4144
  const publicWidgetOptionsFun = getPublicWidgetOptionsFunction(widget.type);
3133
- splitWidgets[id] = _extends({}, widget, {
4145
+ splitWidgets[id] = {
4146
+ ...widget,
3134
4147
  options: publicWidgetOptionsFun(widget.options)
3135
- });
4148
+ };
3136
4149
  }
3137
- return _extends({}, item, {
4150
+ return {
4151
+ ...item,
3138
4152
  widgets: splitWidgets
3139
- });
4153
+ };
3140
4154
  }
3141
4155
 
3142
- export { coreWidgetRegistry as CoreWidgetRegistry, Errors, grapherUtil as GrapherUtil, ItemExtras, PerseusError, PerseusExpressionAnswerFormConsidered, addWidget, approximateDeepEqual, approximateEqual, categorizerWidgetLogic as categorizerLogic, csProgramWidgetLogic as csProgramLogic, deepClone, definitionWidgetLogic as definitionLogic, deriveExtraKeys, deriveNumCorrect, dropdownWidgetLogic as dropdownLogic, explanationWidgetLogic as explanationLogic, expressionWidgetLogic as expressionLogic, getCSProgramPublicWidgetOptions, getCategorizerPublicWidgetOptions, getDecimalSeparator, getDropdownPublicWidgetOptions, getExpressionPublicWidgetOptions, getGrapherPublicWidgetOptions, getIFramePublicWidgetOptions, getInteractiveGraphPublicWidgetOptions, getLabelImagePublicWidgetOptions, getMatcherPublicWidgetOptions, getMatrixPublicWidgetOptions, getMatrixSize, getNumberLinePublicWidgetOptions, getNumericInputPublicWidgetOptions, getOrdererPublicWidgetOptions, getPlotterPublicWidgetOptions, getRadioPublicWidgetOptions, getSorterPublicWidgetOptions, getTablePublicWidgetOptions, getUpgradedWidgetOptions, getWidgetIdsFromContent, getWidgetIdsFromContentByType, gradedGroupWidgetLogic as gradedGroupLogic, gradedGroupSetWidgetLogic as gradedGroupSetLogic, grapherWidgetLogic as grapherLogic, groupWidgetLogic as groupLogic, iframeWidgetLogic as iframeLogic, imageWidgetLogic as imageLogic, inputNumberWidgetLogic as inputNumberLogic, interactionWidgetLogic as interactionLogic, interactiveGraphWidgetLogic as interactiveGraphLogic, isFailure, isSuccess, labelImageWidgetLogic as labelImageLogic, libVersion, lockedFigureColorNames, lockedFigureColors, lockedFigureFillStyles, mapObject, matcherWidgetLogic as matcherLogic, matrixWidgetLogic as matrixLogic, measurerWidgetLogic as measurerLogic, numberLineWidgetLogic as numberLineLogic, numericInputWidgetLogic as numericInputLogic, ordererWidgetLogic as ordererLogic, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parsePerseusItem, passageWidgetLogic as passageLogic, passageRefWidgetLogic as passageRefLogic, passageRefTargetWidgetLogic as passageRefTargetLogic, phetSimulationWidgetLogic as phetSimulationLogic, plotterWidgetLogic as plotterLogic, plotterPlotTypes, pluck, pythonProgramWidgetLogic as pythonProgramLogic, radioWidgetLogic as radioLogic, random, seededRNG, shuffle, shuffleMatcher, sorterWidgetLogic as sorterLogic, splitPerseusItem, tableWidgetLogic as tableLogic, upgradeWidgetInfoToLatestVersion, usesNumCorrect, videoWidgetLogic as videoLogic };
4156
+ exports.CoreWidgetRegistry = coreWidgetRegistry;
4157
+ exports.Errors = Errors;
4158
+ exports.GrapherUtil = grapherUtil;
4159
+ exports.ItemExtras = ItemExtras;
4160
+ exports.PerseusError = PerseusError;
4161
+ exports.PerseusExpressionAnswerFormConsidered = PerseusExpressionAnswerFormConsidered;
4162
+ exports.addWidget = addWidget;
4163
+ exports.approximateDeepEqual = approximateDeepEqual;
4164
+ exports.approximateEqual = approximateEqual;
4165
+ exports.categorizerLogic = categorizerWidgetLogic;
4166
+ exports.csProgramLogic = csProgramWidgetLogic;
4167
+ exports.deepClone = deepClone;
4168
+ exports.definitionLogic = definitionWidgetLogic;
4169
+ exports.deriveExtraKeys = deriveExtraKeys;
4170
+ exports.deriveNumCorrect = deriveNumCorrect;
4171
+ exports.dropdownLogic = dropdownWidgetLogic;
4172
+ exports.explanationLogic = explanationWidgetLogic;
4173
+ exports.expressionLogic = expressionWidgetLogic;
4174
+ exports.getCSProgramPublicWidgetOptions = getCSProgramPublicWidgetOptions;
4175
+ exports.getCategorizerPublicWidgetOptions = getCategorizerPublicWidgetOptions;
4176
+ exports.getDecimalSeparator = getDecimalSeparator;
4177
+ exports.getDropdownPublicWidgetOptions = getDropdownPublicWidgetOptions;
4178
+ exports.getExpressionPublicWidgetOptions = getExpressionPublicWidgetOptions;
4179
+ exports.getGrapherPublicWidgetOptions = getGrapherPublicWidgetOptions;
4180
+ exports.getIFramePublicWidgetOptions = getIFramePublicWidgetOptions;
4181
+ exports.getInteractiveGraphPublicWidgetOptions = getInteractiveGraphPublicWidgetOptions;
4182
+ exports.getLabelImagePublicWidgetOptions = getLabelImagePublicWidgetOptions;
4183
+ exports.getMatcherPublicWidgetOptions = getMatcherPublicWidgetOptions;
4184
+ exports.getMatrixPublicWidgetOptions = getMatrixPublicWidgetOptions;
4185
+ exports.getMatrixSize = getMatrixSize;
4186
+ exports.getNumberLinePublicWidgetOptions = getNumberLinePublicWidgetOptions;
4187
+ exports.getNumericInputPublicWidgetOptions = getNumericInputPublicWidgetOptions;
4188
+ exports.getOrdererPublicWidgetOptions = getOrdererPublicWidgetOptions;
4189
+ exports.getPlotterPublicWidgetOptions = getPlotterPublicWidgetOptions;
4190
+ exports.getRadioPublicWidgetOptions = getRadioPublicWidgetOptions;
4191
+ exports.getSorterPublicWidgetOptions = getSorterPublicWidgetOptions;
4192
+ exports.getTablePublicWidgetOptions = getTablePublicWidgetOptions;
4193
+ exports.getUpgradedWidgetOptions = getUpgradedWidgetOptions;
4194
+ exports.getWidgetIdsFromContent = getWidgetIdsFromContent;
4195
+ exports.getWidgetIdsFromContentByType = getWidgetIdsFromContentByType;
4196
+ exports.gradedGroupLogic = gradedGroupWidgetLogic;
4197
+ exports.gradedGroupSetLogic = gradedGroupSetWidgetLogic;
4198
+ exports.grapherLogic = grapherWidgetLogic;
4199
+ exports.groupLogic = groupWidgetLogic;
4200
+ exports.iframeLogic = iframeWidgetLogic;
4201
+ exports.imageLogic = imageWidgetLogic;
4202
+ exports.inputNumberLogic = inputNumberWidgetLogic;
4203
+ exports.interactionLogic = interactionWidgetLogic;
4204
+ exports.interactiveGraphLogic = interactiveGraphWidgetLogic;
4205
+ exports.isFailure = isFailure;
4206
+ exports.isSuccess = isSuccess;
4207
+ exports.labelImageLogic = labelImageWidgetLogic;
4208
+ exports.libVersion = libVersion;
4209
+ exports.lockedFigureColorNames = lockedFigureColorNames;
4210
+ exports.lockedFigureColors = lockedFigureColors;
4211
+ exports.lockedFigureFillStyles = lockedFigureFillStyles;
4212
+ exports.mapObject = mapObject;
4213
+ exports.matcherLogic = matcherWidgetLogic;
4214
+ exports.matrixLogic = matrixWidgetLogic;
4215
+ exports.measurerLogic = measurerWidgetLogic;
4216
+ exports.numberLineLogic = numberLineWidgetLogic;
4217
+ exports.numericInputLogic = numericInputWidgetLogic;
4218
+ exports.ordererLogic = ordererWidgetLogic;
4219
+ exports.parseAndMigratePerseusArticle = parseAndMigratePerseusArticle;
4220
+ exports.parseAndMigratePerseusItem = parseAndMigratePerseusItem;
4221
+ exports.parsePerseusItem = parsePerseusItem;
4222
+ exports.passageLogic = passageWidgetLogic;
4223
+ exports.passageRefLogic = passageRefWidgetLogic;
4224
+ exports.passageRefTargetLogic = passageRefTargetWidgetLogic;
4225
+ exports.phetSimulationLogic = phetSimulationWidgetLogic;
4226
+ exports.plotterLogic = plotterWidgetLogic;
4227
+ exports.plotterPlotTypes = plotterPlotTypes;
4228
+ exports.pluck = pluck;
4229
+ exports.pythonProgramLogic = pythonProgramWidgetLogic;
4230
+ exports.radioLogic = radioWidgetLogic;
4231
+ exports.random = random;
4232
+ exports.seededRNG = seededRNG;
4233
+ exports.shuffle = shuffle;
4234
+ exports.shuffleMatcher = shuffleMatcher;
4235
+ exports.sorterLogic = sorterWidgetLogic;
4236
+ exports.splitPerseusItem = splitPerseusItem;
4237
+ exports.tableLogic = tableWidgetLogic;
4238
+ exports.upgradeWidgetInfoToLatestVersion = upgradeWidgetInfoToLatestVersion;
4239
+ exports.usesNumCorrect = usesNumCorrect;
4240
+ exports.videoLogic = videoWidgetLogic;
3143
4241
  //# sourceMappingURL=index.js.map