@khanacademy/perseus-core 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/data-schema.d.ts +57 -12
  2. package/dist/es/index.js +2968 -280
  3. package/dist/es/index.js.map +1 -1
  4. package/dist/index.d.ts +80 -1
  5. package/dist/index.js +3027 -281
  6. package/dist/index.js.map +1 -1
  7. package/dist/parse-perseus-json/error-tracking-parse-context.d.ts +9 -0
  8. package/dist/parse-perseus-json/exhaustive-test-tool/index.d.ts +2 -0
  9. package/dist/parse-perseus-json/general-purpose-parsers/any.d.ts +2 -0
  10. package/dist/parse-perseus-json/general-purpose-parsers/array.d.ts +2 -0
  11. package/dist/parse-perseus-json/general-purpose-parsers/array.typetest.d.ts +1 -0
  12. package/dist/parse-perseus-json/general-purpose-parsers/boolean.d.ts +2 -0
  13. package/dist/parse-perseus-json/general-purpose-parsers/constant.d.ts +4 -0
  14. package/dist/parse-perseus-json/general-purpose-parsers/convert.d.ts +2 -0
  15. package/dist/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +2 -0
  16. package/dist/parse-perseus-json/general-purpose-parsers/discriminated-union.d.ts +21 -0
  17. package/dist/parse-perseus-json/general-purpose-parsers/discriminated-union.typetest.d.ts +1 -0
  18. package/dist/parse-perseus-json/general-purpose-parsers/enumeration.d.ts +2 -0
  19. package/dist/parse-perseus-json/general-purpose-parsers/enumeration.typetest.d.ts +1 -0
  20. package/dist/parse-perseus-json/general-purpose-parsers/index.d.ts +17 -0
  21. package/dist/parse-perseus-json/general-purpose-parsers/is-object.d.ts +1 -0
  22. package/dist/parse-perseus-json/general-purpose-parsers/nullable.d.ts +2 -0
  23. package/dist/parse-perseus-json/general-purpose-parsers/number.d.ts +2 -0
  24. package/dist/parse-perseus-json/general-purpose-parsers/object.d.ts +6 -0
  25. package/dist/parse-perseus-json/general-purpose-parsers/optional.d.ts +2 -0
  26. package/dist/parse-perseus-json/general-purpose-parsers/pair.d.ts +2 -0
  27. package/dist/parse-perseus-json/general-purpose-parsers/pipe-parsers.d.ts +7 -0
  28. package/dist/parse-perseus-json/general-purpose-parsers/pipe-parsers.typetest.d.ts +1 -0
  29. package/dist/parse-perseus-json/general-purpose-parsers/record.d.ts +2 -0
  30. package/dist/parse-perseus-json/general-purpose-parsers/string-to-number.d.ts +2 -0
  31. package/dist/parse-perseus-json/general-purpose-parsers/string.d.ts +2 -0
  32. package/dist/parse-perseus-json/general-purpose-parsers/test-helpers.d.ts +8 -0
  33. package/dist/parse-perseus-json/general-purpose-parsers/trio.d.ts +2 -0
  34. package/dist/parse-perseus-json/general-purpose-parsers/union.d.ts +7 -0
  35. package/dist/parse-perseus-json/general-purpose-parsers/union.typetest.d.ts +1 -0
  36. package/dist/parse-perseus-json/general-purpose-parsers/unknown.d.ts +2 -0
  37. package/dist/parse-perseus-json/index.d.ts +45 -0
  38. package/dist/parse-perseus-json/object-path.d.ts +2 -0
  39. package/dist/parse-perseus-json/parse-failure-detail.d.ts +2 -0
  40. package/dist/parse-perseus-json/parse.d.ts +3 -0
  41. package/dist/parse-perseus-json/parser-types.d.ts +30 -0
  42. package/dist/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +3 -0
  43. package/dist/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +3 -0
  44. package/dist/parse-perseus-json/perseus-parsers/definition-widget.d.ts +3 -0
  45. package/dist/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +3 -0
  46. package/dist/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +3 -0
  47. package/dist/parse-perseus-json/perseus-parsers/expression-widget.d.ts +3 -0
  48. package/dist/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +3 -0
  49. package/dist/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +16 -0
  50. package/dist/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +3 -0
  51. package/dist/parse-perseus-json/perseus-parsers/group-widget.d.ts +3 -0
  52. package/dist/parse-perseus-json/perseus-parsers/hint.d.ts +3 -0
  53. package/dist/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +3 -0
  54. package/dist/parse-perseus-json/perseus-parsers/image-widget.d.ts +3 -0
  55. package/dist/parse-perseus-json/perseus-parsers/images-map.d.ts +5 -0
  56. package/dist/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +3 -0
  57. package/dist/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +3 -0
  58. package/dist/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +3 -0
  59. package/dist/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +3 -0
  60. package/dist/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +3 -0
  61. package/dist/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +3 -0
  62. package/dist/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +3 -0
  63. package/dist/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +3 -0
  64. package/dist/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +3 -0
  65. package/dist/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +3 -0
  66. package/dist/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +3 -0
  67. package/dist/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +3 -0
  68. package/dist/parse-perseus-json/perseus-parsers/passage-widget.d.ts +3 -0
  69. package/dist/parse-perseus-json/perseus-parsers/perseus-article.d.ts +3 -0
  70. package/dist/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +3 -0
  71. package/dist/parse-perseus-json/perseus-parsers/perseus-item.d.ts +3 -0
  72. package/dist/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +3 -0
  73. package/dist/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +3 -0
  74. package/dist/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +3 -0
  75. package/dist/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +3 -0
  76. package/dist/parse-perseus-json/perseus-parsers/radio-widget.d.ts +3 -0
  77. package/dist/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +3 -0
  78. package/dist/parse-perseus-json/perseus-parsers/table-widget.d.ts +3 -0
  79. package/dist/parse-perseus-json/perseus-parsers/versioned-widget-options.d.ts +36 -0
  80. package/dist/parse-perseus-json/perseus-parsers/video-widget.d.ts +3 -0
  81. package/dist/parse-perseus-json/perseus-parsers/widget.d.ts +7 -0
  82. package/dist/parse-perseus-json/perseus-parsers/widgets-map.d.ts +3 -0
  83. package/dist/parse-perseus-json/result.d.ts +16 -0
  84. package/dist/types.d.ts +1 -0
  85. package/dist/utils/is-real-json-parse.d.ts +1 -0
  86. package/dist/utils/widget-id-utils.d.ts +38 -0
  87. package/dist/widgets/categorizer/categorizer-util.d.ts +17 -0
  88. package/dist/widgets/categorizer/index.d.ts +5 -0
  89. package/dist/widgets/core-widget-registry.d.ts +28 -0
  90. package/dist/widgets/cs-program/cs-program-util.d.ts +2 -0
  91. package/dist/widgets/cs-program/index.d.ts +5 -0
  92. package/dist/widgets/definition/index.d.ts +5 -0
  93. package/dist/widgets/dropdown/dropdown-util.d.ts +20 -0
  94. package/dist/widgets/dropdown/index.d.ts +5 -0
  95. package/dist/widgets/explanation/index.d.ts +5 -0
  96. package/dist/widgets/expression/expression-upgrade.d.ts +10 -0
  97. package/dist/widgets/expression/expression-util.d.ts +19 -0
  98. package/dist/widgets/expression/index.d.ts +4 -0
  99. package/dist/widgets/graded-group/index.d.ts +5 -0
  100. package/dist/widgets/graded-group-set/index.d.ts +5 -0
  101. package/dist/widgets/grapher/index.d.ts +5 -0
  102. package/dist/widgets/group/index.d.ts +5 -0
  103. package/dist/widgets/iframe/index.d.ts +5 -0
  104. package/dist/widgets/image/index.d.ts +5 -0
  105. package/dist/widgets/input-number/index.d.ts +5 -0
  106. package/dist/widgets/interaction/index.d.ts +5 -0
  107. package/dist/widgets/interactive-graph/index.d.ts +5 -0
  108. package/dist/widgets/label-image/index.d.ts +5 -0
  109. package/dist/widgets/label-image/label-image-util.d.ts +19 -0
  110. package/dist/widgets/logic-export.types.d.ts +13 -0
  111. package/dist/widgets/matcher/index.d.ts +5 -0
  112. package/dist/widgets/matrix/index.d.ts +5 -0
  113. package/dist/widgets/measurer/index.d.ts +4 -0
  114. package/dist/widgets/measurer/measurer-upgrade.d.ts +10 -0
  115. package/dist/widgets/number-line/index.d.ts +5 -0
  116. package/dist/widgets/number-line/number-line-util.d.ts +4 -0
  117. package/dist/widgets/numeric-input/index.d.ts +5 -0
  118. package/dist/widgets/numeric-input/numeric-input-util.d.ts +19 -0
  119. package/dist/widgets/orderer/index.d.ts +5 -0
  120. package/dist/widgets/orderer/orderer-util.d.ts +16 -0
  121. package/dist/widgets/passage/index.d.ts +5 -0
  122. package/dist/widgets/passage-ref/index.d.ts +4 -0
  123. package/dist/widgets/passage-ref/passage-ref-upgrade.d.ts +7 -0
  124. package/dist/widgets/passage-ref-target/index.d.ts +5 -0
  125. package/dist/widgets/phet-simulation/index.d.ts +5 -0
  126. package/dist/widgets/plotter/index.d.ts +5 -0
  127. package/dist/widgets/python-program/index.d.ts +5 -0
  128. package/dist/widgets/radio/index.d.ts +4 -0
  129. package/dist/widgets/radio/radio-upgrade.d.ts +10 -0
  130. package/dist/widgets/sorter/index.d.ts +5 -0
  131. package/dist/widgets/sorter/sorter-util.d.ts +16 -0
  132. package/dist/widgets/table/index.d.ts +5 -0
  133. package/dist/widgets/upgrade.d.ts +3 -0
  134. package/dist/widgets/video/index.d.ts +5 -0
  135. package/package.json +1 -1
package/dist/es/index.js CHANGED
@@ -113,7 +113,7 @@ function restArguments(func, startIndex) {
113
113
  }
114
114
 
115
115
  // Is a given variable an object?
116
- function isObject(obj) {
116
+ function isObject$1(obj) {
117
117
  var type = typeof obj;
118
118
  return type === 'function' || (type === 'object' && !!obj);
119
119
  }
@@ -227,7 +227,7 @@ function isNaN$1(obj) {
227
227
  }
228
228
 
229
229
  // Predicate-generating function. Often useful outside of Underscore.
230
- function constant(value) {
230
+ function constant$1(value) {
231
231
  return function() {
232
232
  return value;
233
233
  };
@@ -264,7 +264,7 @@ function isTypedArray(obj) {
264
264
  isBufferLike(obj) && typedArrayPattern.test(toString.call(obj));
265
265
  }
266
266
 
267
- var isTypedArray$1 = supportsArrayBuffer ? isTypedArray : constant(false);
267
+ var isTypedArray$1 = supportsArrayBuffer ? isTypedArray : constant$1(false);
268
268
 
269
269
  // Internal helper to obtain the `length` property of an object.
270
270
  var getLength = shallowProperty('length');
@@ -309,7 +309,7 @@ function collectNonEnumProps(obj, keys) {
309
309
  // Retrieve the names of an object's own properties.
310
310
  // Delegates to **ECMAScript 5**'s native `Object.keys`.
311
311
  function keys(obj) {
312
- if (!isObject(obj)) return [];
312
+ if (!isObject$1(obj)) return [];
313
313
  if (nativeKeys) return nativeKeys(obj);
314
314
  var keys = [];
315
315
  for (var key in obj) if (has$1(obj, key)) keys.push(key);
@@ -507,7 +507,7 @@ function isEqual(a, b) {
507
507
 
508
508
  // Retrieve all the enumerable property names of an object.
509
509
  function allKeys(obj) {
510
- if (!isObject(obj)) return [];
510
+ if (!isObject$1(obj)) return [];
511
511
  var keys = [];
512
512
  for (var key in obj) keys.push(key);
513
513
  // Ahem, IE < 9.
@@ -636,7 +636,7 @@ function ctor() {
636
636
 
637
637
  // An internal function for creating a new object that inherits from another.
638
638
  function baseCreate(prototype) {
639
- if (!isObject(prototype)) return {};
639
+ if (!isObject$1(prototype)) return {};
640
640
  if (nativeCreate) return nativeCreate(prototype);
641
641
  var Ctor = ctor();
642
642
  Ctor.prototype = prototype;
@@ -656,7 +656,7 @@ function create(prototype, props) {
656
656
 
657
657
  // Create a (shallow-cloned) duplicate of an object.
658
658
  function clone(obj) {
659
- if (!isObject(obj)) return obj;
659
+ if (!isObject$1(obj)) return obj;
660
660
  return isArray(obj) ? obj.slice() : extend({}, obj);
661
661
  }
662
662
 
@@ -765,7 +765,7 @@ function optimizeCb(func, context, argCount) {
765
765
  function baseIteratee(value, context, argCount) {
766
766
  if (value == null) return identity;
767
767
  if (isFunction$1(value)) return optimizeCb(value, context, argCount);
768
- if (isObject(value) && !isArray(value)) return matcher(value);
768
+ if (isObject$1(value) && !isArray(value)) return matcher(value);
769
769
  return property(value);
770
770
  }
771
771
 
@@ -1014,7 +1014,7 @@ function executeBound(sourceFunc, boundFunc, context, callingContext, args) {
1014
1014
  if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
1015
1015
  var self = baseCreate(sourceFunc.prototype);
1016
1016
  var result = sourceFunc.apply(self, args);
1017
- if (isObject(result)) return result;
1017
+ if (isObject$1(result)) return result;
1018
1018
  return self;
1019
1019
  }
1020
1020
 
@@ -1776,7 +1776,7 @@ function uniq(array, isSorted, iteratee, context) {
1776
1776
 
1777
1777
  // Produce an array that contains the union: each distinct element from all of
1778
1778
  // the passed-in arrays.
1779
- var union = restArguments(function(arrays) {
1779
+ var union$1 = restArguments(function(arrays) {
1780
1780
  return uniq(flatten$1(arrays, true, true));
1781
1781
  });
1782
1782
 
@@ -1816,7 +1816,7 @@ var zip = restArguments(unzip);
1816
1816
  // Converts lists into objects. Pass either a single array of `[key, value]`
1817
1817
  // pairs, or two parallel arrays of the same length -- one of keys, and one of
1818
1818
  // the corresponding values. Passing by pairs is the reverse of `_.pairs`.
1819
- function object(list, values) {
1819
+ function object$1(list, values) {
1820
1820
  var result = {};
1821
1821
  for (var i = 0, length = getLength(list); i < length; i++) {
1822
1822
  if (values) {
@@ -1911,7 +1911,7 @@ var allExports = /*#__PURE__*/Object.freeze({
1911
1911
  __proto__: null,
1912
1912
  VERSION: VERSION,
1913
1913
  restArguments: restArguments,
1914
- isObject: isObject,
1914
+ isObject: isObject$1,
1915
1915
  isNull: isNull,
1916
1916
  isUndefined: isUndefined,
1917
1917
  isBoolean: isBoolean,
@@ -1955,7 +1955,7 @@ var allExports = /*#__PURE__*/Object.freeze({
1955
1955
  has: has,
1956
1956
  mapObject: mapObject$1,
1957
1957
  identity: identity,
1958
- constant: constant,
1958
+ constant: constant$1,
1959
1959
  noop: noop,
1960
1960
  toPath: toPath$1,
1961
1961
  property: property,
@@ -2044,13 +2044,13 @@ var allExports = /*#__PURE__*/Object.freeze({
2044
2044
  without: without,
2045
2045
  uniq: uniq,
2046
2046
  unique: uniq,
2047
- union: union,
2047
+ union: union$1,
2048
2048
  intersection: intersection,
2049
2049
  difference: difference,
2050
2050
  unzip: unzip,
2051
2051
  transpose: unzip,
2052
2052
  zip: zip,
2053
- object: object,
2053
+ object: object$1,
2054
2054
  range: range,
2055
2055
  chunk: chunk,
2056
2056
  mixin: mixin,
@@ -2164,6 +2164,70 @@ function approximateDeepEqual(x, y) {
2164
2164
  return approximateEqual(x, y);
2165
2165
  }
2166
2166
 
2167
+ /**
2168
+ * Add a widget placeholder using the widget ID.
2169
+ * ex. addWidget("radio 1") => "[[☃ radio 1]]"
2170
+ *
2171
+ * @param {string} id
2172
+ * @returns {string}
2173
+ */
2174
+ function addWidget(id) {
2175
+ return `[[☃ ${id}]]`;
2176
+ }
2177
+
2178
+ /**
2179
+ * Regex for widget placeholders in a string.
2180
+ *
2181
+ * First capture group is the widget ID (ex. 'radio 1')
2182
+ * Second capture group is the widget type (ex. "radio)
2183
+ * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
2184
+ */
2185
+ function getWidgetRegex() {
2186
+ return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
2187
+ }
2188
+
2189
+ /**
2190
+ * Extract all widget IDs, which includes the widget type and instance number.
2191
+ * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
2192
+ *
2193
+ * Content should contain Perseus widget placeholders,
2194
+ * which look like: '[[☃ radio 1]]'.
2195
+ *
2196
+ * @param {string} content
2197
+ * @returns {ReadonlyArray<string>} widgetIds
2198
+ */
2199
+ function getWidgetIdsFromContent(content) {
2200
+ const widgets = [];
2201
+ const localWidgetRegex = getWidgetRegex();
2202
+ let match = localWidgetRegex.exec(content);
2203
+ while (match !== null) {
2204
+ widgets.push(match[1]);
2205
+ match = localWidgetRegex.exec(content);
2206
+ }
2207
+ return widgets;
2208
+ }
2209
+
2210
+ /**
2211
+ * Get a list of widget IDs from content,
2212
+ * but only for specific widget types
2213
+ *
2214
+ * @param {string} type the type of widget (ie "radio")
2215
+ * @param {string} content the string to parse
2216
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
2217
+ * @returns {ReadonlyArray<string>} the widget type (ie "radio")
2218
+ */
2219
+ function getWidgetIdsFromContentByType(type, content, widgetMap) {
2220
+ const rv = [];
2221
+ const widgetIdsInContent = getWidgetIdsFromContent(content);
2222
+ widgetIdsInContent.forEach(widgetId => {
2223
+ const widget = widgetMap[widgetId];
2224
+ if ((widget == null ? void 0 : widget.type) === type) {
2225
+ rv.push(widgetId);
2226
+ }
2227
+ });
2228
+ return rv;
2229
+ }
2230
+
2167
2231
  // TODO(benchristel): in the future, we may want to make deepClone work for
2168
2232
  // Record<string, Cloneable> as well. Currently, it only does arrays.
2169
2233
 
@@ -2578,341 +2642,2965 @@ var grapherUtil = /*#__PURE__*/Object.freeze({
2578
2642
  functionForType: functionForType
2579
2643
  });
2580
2644
 
2581
- // This file is processed by a Rollup plugin (replace) to inject the production
2582
- const libName = "@khanacademy/perseus-core";
2583
- const libVersion = "3.2.0";
2584
- addLibraryVersionToPerseusDebug(libName, libVersion);
2645
+ function isRealJSONParse(jsonParse) {
2646
+ const randomPhrase = buildRandomPhrase();
2647
+ const randomHintPhrase = buildRandomPhrase();
2648
+ const randomString = buildRandomString();
2649
+ const testingObject = JSON.stringify({
2650
+ answerArea: {
2651
+ calculator: false,
2652
+ chi2Table: false,
2653
+ financialCalculatorMonthlyPayment: false,
2654
+ financialCalculatorTimeToPayOff: false,
2655
+ financialCalculatorTotalAmount: false,
2656
+ periodicTable: false,
2657
+ periodicTableWithKey: false,
2658
+ tTable: false,
2659
+ zTable: false
2660
+ },
2661
+ hints: [randomHintPhrase, `=${Math.floor(Math.random() * 50) + 1}`],
2662
+ itemDataVersion: {
2663
+ major: 0,
2664
+ minor: 1
2665
+ },
2666
+ question: {
2667
+ content: `${randomPhrase}`,
2668
+ images: {},
2669
+ widgets: {
2670
+ expression1: {
2671
+ alignment: "default",
2672
+ graded: false,
2673
+ options: {
2674
+ answerForms: [{
2675
+ considered: "wrong",
2676
+ form: false,
2677
+ key: 0,
2678
+ simplify: false,
2679
+ value: `${randomString}`
2680
+ }],
2681
+ ariaLabel: "Answer",
2682
+ buttonSets: ["basic"],
2683
+ functions: ["f", "g", "h"],
2684
+ static: true,
2685
+ times: false,
2686
+ visibleLabel: "Answer"
2687
+ },
2688
+ static: true,
2689
+ type: "expression",
2690
+ version: {
2691
+ major: 1,
2692
+ minor: 0
2693
+ }
2694
+ }
2695
+ }
2696
+ }
2697
+ });
2698
+ const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
2699
+ const parsedTestJSON = jsonParse(testJSON);
2700
+ const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
2701
+ return approximateDeepEqual(parsedTestItemData, testingObject);
2702
+ }
2703
+ function buildRandomString(capitalize = false) {
2704
+ let randomString = "";
2705
+ const randomLength = Math.floor(Math.random() * 8) + 3;
2706
+ for (let i = 0; i < randomLength; i++) {
2707
+ const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
2708
+ randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
2709
+ }
2710
+ return randomString;
2711
+ }
2712
+ function buildRandomPhrase() {
2713
+ const phrases = [];
2714
+ const randomLength = Math.floor(Math.random() * 10) + 5;
2715
+ for (let i = 0; i < randomLength; i++) {
2716
+ phrases.push(buildRandomString(i === 0));
2717
+ }
2718
+ const modifierStart = ["**", "$"];
2719
+ const modifierEnd = ["**", "$"];
2720
+ const modifierIndex = Math.floor(Math.random() * modifierStart.length);
2721
+ return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
2722
+ }
2723
+ function buildTestData(testObject) {
2724
+ return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
2725
+ }
2585
2726
 
2586
- /**
2587
- * @typedef {Object} Errors utility for referencing the Perseus error taxonomy.
2588
- */
2589
- const Errors = Object.freeze({
2590
- /**
2591
- * @property {ErrorKind} Unknown The kind of error is not known.
2592
- */
2593
- Unknown: "Unknown",
2594
- /**
2595
- * @property {ErrorKind} Internal The error is internal to the executing code.
2596
- */
2597
- Internal: "Internal",
2598
- /**
2599
- * @property {ErrorKind} InvalidInput There was a problem with the provided
2600
- * input, such as the wrong format or a null value.
2601
- */
2602
- InvalidInput: "InvalidInput",
2603
- /**
2604
- * @property {ErrorKind} NotAllowed There was a problem due to the state of
2605
- * the system not matching the requested operation or input. For example,
2606
- * trying to create a username that is valid, but is already taken by
2607
- * another user. Use {@link InvalidInput} instead when the input isn't
2608
- * valid regardless of the state of the system. Use {@link NotFound} when
2609
- * the failure is due to not being able to find a resource.
2610
- */
2611
- NotAllowed: "NotAllowed",
2612
- /**
2613
- * @property {ErrorKind} TransientService There was a problem when making a
2614
- * request to a service.
2615
- */
2616
- TransientService: "TransientService",
2617
- /**
2618
- * @property {ErrorKind} Service There was a non-transient problem when
2619
- * making a request to service.
2620
- */
2621
- Service: "Service"
2622
- });
2727
+ process.env.NODE_ENV === 'production';
2623
2728
 
2624
- /**
2625
- * @type {ErrorKind} The kind of error being reported
2626
- */
2729
+ function success(value) {
2730
+ return {
2731
+ type: "success",
2732
+ value
2733
+ };
2734
+ }
2735
+ function failure(detail) {
2736
+ return {
2737
+ type: "failure",
2738
+ detail
2739
+ };
2740
+ }
2741
+ function isFailure(result) {
2742
+ return result.type === "failure";
2743
+ }
2744
+ function isSuccess(result) {
2745
+ return result.type === "success";
2746
+ }
2627
2747
 
2628
- class PerseusError extends Error {
2629
- constructor(message, kind, options) {
2630
- super(message);
2631
- this.kind = void 0;
2632
- this.metadata = void 0;
2633
- this.kind = kind;
2634
- this.metadata = options == null ? void 0 : options.metadata;
2748
+ // Result's `all` function is similar to Promise.all: given an array of
2749
+ // results, it returns success if all succeeded, and failure if any failed.
2750
+ function all(results, combineFailureDetails = a => a) {
2751
+ const values = [];
2752
+ const failureDetails = [];
2753
+ for (const result of results) {
2754
+ if (result.type === "success") {
2755
+ values.push(result.value);
2756
+ } else {
2757
+ failureDetails.push(result.detail);
2758
+ }
2759
+ }
2760
+ if (failureDetails.length > 0) {
2761
+ return failure(failureDetails.reduce(combineFailureDetails));
2635
2762
  }
2763
+ return success(values);
2636
2764
  }
2637
2765
 
2638
- /**
2639
- * The Perseus "data schema" file.
2640
- *
2641
- * This file, and the types in it, represents the "data schema" that Perseus
2642
- * uses. The @khanacademy/perseus-editor package edits and produces objects
2643
- * that conform to the types in this file. Similarly, the top-level renderers
2644
- * in @khanacademy/perseus, consume objects that conform to these types.
2645
- *
2646
- * WARNING: This file should not import any types from elsewhere so that it is
2647
- * easy to reason about changes that alter the Perseus schema. This helps
2648
- * ensure that it is not changed accidentally when upgrading a dependant
2649
- * package or other part of Perseus code. Note that TypeScript does type
2650
- * checking via something called "structural typing". This means that as long
2651
- * as the shape of a type matches, the name it goes by doesn't matter. As a
2652
- * result, a `Coord` type that looks like this `[x: number, y: number]` is
2653
- * _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
2654
- * number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
2655
- * number, y: number]` is compatible with `[min: number, max: number]`. The
2656
- * labels are for humans, not TypeScript. :)
2657
- *
2658
- * If you make changes to types in this file, be very sure that:
2659
- *
2660
- * a) the changes are backwards compatible. If they are not, old data from
2661
- * previous versions of the "schema" could become unrenderable, or worse,
2662
- * introduce hard-to-diagnose bugs.
2663
- * b) the parsing code (`util/parse-perseus-json/`) is updated to handle
2664
- * the new format _as well as_ the old format.
2665
- */
2666
-
2667
- // TODO(FEI-4010): Remove `Perseus` prefix for all types here
2766
+ class ErrorTrackingParseContext {
2767
+ constructor(path) {
2768
+ this.path = path;
2769
+ }
2770
+ failure(expected, badValue) {
2771
+ return failure([{
2772
+ expected: wrapInArray(expected),
2773
+ badValue,
2774
+ path: this.path
2775
+ }]);
2776
+ }
2777
+ forSubtree(key) {
2778
+ return new ErrorTrackingParseContext([...this.path, key]);
2779
+ }
2780
+ success(value) {
2781
+ return success(value);
2782
+ }
2783
+ }
2784
+ function wrapInArray(a) {
2785
+ return Array.isArray(a) ? a : [a];
2786
+ }
2668
2787
 
2669
- // Same name as Mafs
2788
+ function formatPath(path) {
2789
+ return "(root)" + path.map(formatPathSegment).join("");
2790
+ }
2791
+ function formatPathSegment(segment) {
2792
+ if (typeof segment === "string") {
2793
+ return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
2794
+ }
2795
+ return `[${segment.toString()}]`;
2796
+ }
2797
+ const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
2670
2798
 
2671
- /**
2672
- * Our core set of Perseus widgets.
2673
- *
2674
- * This interface is the basis for "registering" all Perseus widget types.
2675
- * There should be one key/value pair for each supported widget. If you create
2676
- * a new widget, an entry should be added to this interface. Note that this
2677
- * only registers the widget options type, you'll also need to register the
2678
- * widget so that it's available at runtime (@see
2679
- * {@link file://./widgets.ts#registerWidget}).
2680
- *
2681
- * Importantly, the key should be the name that is used in widget IDs. For most
2682
- * widgets that is the same as the widget option's `type` field. In cases where
2683
- * a widget has been deprecated and replaced with the deprecated-standin
2684
- * widget, it should be the original widget type!
2685
- *
2686
- * If you define the widget outside of this package, you can still add the new
2687
- * widget to this interface by writing the following in that package that
2688
- * contains the widget. TypeScript will merge that definition of the
2689
- * `PerseusWidgets` with the one defined below.
2690
- *
2691
- * ```typescript
2692
- * declare module "@khanacademy/perseus" {
2693
- * interface PerseusWidgetTypes {
2694
- * // A new widget
2695
- * "new-awesomeness": MyAwesomeNewWidget;
2696
- *
2697
- * // A deprecated widget
2698
- * "super-old-widget": DeprecatedStandinWidget;
2699
- * }
2700
- * }
2701
- *
2702
- * // The new widget's options definition
2703
- * type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
2704
- *
2705
- * // The deprecated widget's options definition
2706
- * type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
2707
- * ```
2708
- *
2709
- * This interface can be extended through the magic of TypeScript "Declaration
2710
- * merging". Specifically, we augment this module and extend this interface.
2711
- *
2712
- * @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
2713
- */
2799
+ function message(failure) {
2800
+ const expected = conjoin(failure.expected);
2801
+ const path = formatPath(failure.path);
2802
+ const badValue = JSON.stringify(failure.badValue);
2803
+ return `At ${path} -- expected ${expected}, but got ${badValue}`;
2804
+ }
2805
+ function conjoin(items) {
2806
+ switch (items.length) {
2807
+ // TODO(benchristel): handle 0 if this is reused elsewhere.
2808
+ case 1:
2809
+ return items[0];
2810
+ case 2:
2811
+ return items.join(" or ");
2812
+ default:
2813
+ {
2814
+ const allButLast = items.slice(0, items.length - 1);
2815
+ const last = items[items.length - 1];
2816
+ return allButLast.join(", ") + ", or " + last;
2817
+ }
2818
+ }
2819
+ }
2714
2820
 
2715
- /**
2716
- * A map of widget IDs to widget options. This is most often used as the type
2717
- * for a set of widgets defined in a `PerseusItem` but can also be useful to
2718
- * represent a function parameter where only `widgets` from a `PerseusItem` are
2719
- * needed. Today Widget IDs are made up of the widget type and an incrementing
2720
- * integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
2721
- * reading/parsing the widget id to derive any information from it, except in
2722
- * the case of this map.
2723
- *
2724
- * @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
2725
- * by augmenting the PerseusWidgetTypes with new widget types!
2726
- */
2821
+ function parse(value, parser) {
2822
+ const result = parser(value, new ErrorTrackingParseContext([]));
2823
+ if (isFailure(result)) {
2824
+ return failure(result.detail.map(message).join("; "));
2825
+ }
2826
+ return result;
2827
+ }
2727
2828
 
2728
- /**
2729
- * A "PerseusItem" is a classic Perseus item. It is rendered by the
2730
- * `ServerItemRenderer` and the layout is pre-set.
2731
- *
2732
- * To render more complex Perseus items, see the `Item` type in the multi item
2733
- * area.
2734
- */
2829
+ const any = (rawValue, ctx) => ctx.success(rawValue);
2735
2830
 
2736
- /**
2737
- * A "PerseusArticle" is an item that is meant to be rendered as an article.
2738
- * This item is never scored and is rendered by the `ArticleRenderer`.
2739
- */
2831
+ function array(elementParser) {
2832
+ return (rawValue, ctx) => {
2833
+ if (!Array.isArray(rawValue)) {
2834
+ return ctx.failure("array", rawValue);
2835
+ }
2836
+ const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
2837
+ return all(elementResults, concat);
2838
+ };
2839
+ }
2840
+ function concat(a, b) {
2841
+ return [...a, ...b];
2842
+ }
2740
2843
 
2741
- const ItemExtras = [
2742
- // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
2743
- "calculator",
2744
- // The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
2745
- "chi2Table",
2746
- // The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
2747
- "financialCalculatorMonthlyPayment",
2748
- // The user might benefit from a total amount calculator. Provided on Khan Academy when true
2749
- "financialCalculatorTotalAmount",
2750
- // The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
2751
- "financialCalculatorTimeToPayOff",
2752
- // The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
2753
- "periodicTable",
2754
- // The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
2755
- "periodicTableWithKey",
2756
- // The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
2757
- "tTable",
2758
- // The user might benefit from using a statistics Z Table like https://www.ztable.net/
2759
- "zTable"];
2844
+ function boolean(rawValue, ctx) {
2845
+ if (typeof rawValue === "boolean") {
2846
+ return ctx.success(rawValue);
2847
+ }
2848
+ return ctx.failure("boolean", rawValue);
2849
+ }
2760
2850
 
2761
- /**
2762
- * The type representing the common structure of all widget's options. The
2763
- * `Options` generic type represents the widget-specific option data.
2764
- */
2851
+ function constant(acceptedValue) {
2852
+ return (rawValue, ctx) => {
2853
+ if (rawValue !== acceptedValue) {
2854
+ return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
2855
+ }
2856
+ return ctx.success(acceptedValue);
2857
+ };
2858
+ }
2765
2859
 
2766
- // prettier-ignore
2860
+ function enumeration(...acceptedValues) {
2861
+ return (rawValue, ctx) => {
2862
+ if (typeof rawValue === "string") {
2863
+ const index = acceptedValues.indexOf(rawValue);
2864
+ if (index > -1) {
2865
+ return ctx.success(acceptedValues[index]);
2866
+ }
2867
+ }
2868
+ const expected = acceptedValues.map(v => JSON.stringify(v));
2869
+ return ctx.failure(expected, rawValue);
2870
+ };
2871
+ }
2767
2872
 
2768
- // prettier-ignore
2873
+ function isObject(x) {
2874
+ return x != null && Object.getPrototypeOf(x) === Object.prototype;
2875
+ }
2769
2876
 
2770
- // prettier-ignore
2877
+ function nullable(parseValue) {
2878
+ return (rawValue, ctx) => {
2879
+ if (rawValue === null) {
2880
+ return ctx.success(rawValue);
2881
+ }
2882
+ return parseValue(rawValue, ctx);
2883
+ };
2884
+ }
2771
2885
 
2772
- // prettier-ignore
2886
+ const number = (rawValue, ctx) => {
2887
+ if (typeof rawValue === "number") {
2888
+ return ctx.success(rawValue);
2889
+ }
2890
+ return ctx.failure("number", rawValue);
2891
+ };
2773
2892
 
2774
- // prettier-ignore
2893
+ function _extends() {
2894
+ return _extends = Object.assign ? Object.assign.bind() : function (n) {
2895
+ for (var e = 1; e < arguments.length; e++) {
2896
+ var t = arguments[e];
2897
+ for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
2898
+ }
2899
+ return n;
2900
+ }, _extends.apply(null, arguments);
2901
+ }
2902
+
2903
+ function object(schema) {
2904
+ return (rawValue, ctx) => {
2905
+ if (!isObject(rawValue)) {
2906
+ return ctx.failure("object", rawValue);
2907
+ }
2908
+ const ret = _extends({}, rawValue);
2909
+ const mismatches = [];
2910
+ for (const [prop, propParser] of Object.entries(schema)) {
2911
+ const result = propParser(rawValue[prop], ctx.forSubtree(prop));
2912
+ if (isSuccess(result)) {
2913
+ if (result.value !== undefined || prop in rawValue) {
2914
+ ret[prop] = result.value;
2915
+ }
2916
+ } else {
2917
+ mismatches.push(...result.detail);
2918
+ }
2919
+ }
2920
+ if (mismatches.length > 0) {
2921
+ return failure(mismatches);
2922
+ }
2923
+ return ctx.success(ret);
2924
+ };
2925
+ }
2926
+
2927
+ function optional(parseValue) {
2928
+ return (rawValue, ctx) => {
2929
+ if (rawValue === undefined) {
2930
+ return ctx.success(rawValue);
2931
+ }
2932
+ return parseValue(rawValue, ctx);
2933
+ };
2934
+ }
2935
+
2936
+ function pair(parseA, parseB) {
2937
+ return (rawValue, ctx) => {
2938
+ if (!Array.isArray(rawValue)) {
2939
+ return ctx.failure("array", rawValue);
2940
+ }
2941
+ if (rawValue.length !== 2) {
2942
+ return ctx.failure("array of length 2", rawValue);
2943
+ }
2944
+ const [rawA, rawB] = rawValue;
2945
+ const resultA = parseA(rawA, ctx.forSubtree(0));
2946
+ if (isFailure(resultA)) {
2947
+ return resultA;
2948
+ }
2949
+ const resultB = parseB(rawB, ctx.forSubtree(1));
2950
+ if (isFailure(resultB)) {
2951
+ return resultB;
2952
+ }
2953
+ return ctx.success([resultA.value, resultB.value]);
2954
+ };
2955
+ }
2956
+
2957
+ function pipeParsers(p) {
2958
+ return new ParserPipeline(p);
2959
+ }
2960
+ class ParserPipeline {
2961
+ constructor(parser) {
2962
+ this.parser = parser;
2963
+ }
2964
+ then(nextParser) {
2965
+ return new ParserPipeline(composeParsers(this.parser, nextParser));
2966
+ }
2967
+ }
2968
+ function composeParsers(parserA, parserB) {
2969
+ return (rawValue, ctx) => {
2970
+ const partialResult = parserA(rawValue, ctx);
2971
+ if (isFailure(partialResult)) {
2972
+ return partialResult;
2973
+ }
2974
+ return parserB(partialResult.value, ctx);
2975
+ };
2976
+ }
2977
+
2978
+ function record(parseKey, parseValue) {
2979
+ return (rawValue, ctx) => {
2980
+ if (!isObject(rawValue)) {
2981
+ return ctx.failure("object", rawValue);
2982
+ }
2983
+ const result = {};
2984
+ const mismatches = [];
2985
+ for (const [key, value] of Object.entries(rawValue)) {
2986
+ const entryCtx = ctx.forSubtree(key);
2987
+ const keyResult = parseKey(key, entryCtx);
2988
+ if (isFailure(keyResult)) {
2989
+ mismatches.push(...keyResult.detail);
2990
+ }
2991
+ const valueResult = parseValue(value, entryCtx);
2992
+ if (isFailure(valueResult)) {
2993
+ mismatches.push(...valueResult.detail);
2994
+ }
2995
+ if (isSuccess(keyResult) && isSuccess(valueResult)) {
2996
+ result[keyResult.value] = valueResult.value;
2997
+ }
2998
+ }
2999
+ if (mismatches.length > 0) {
3000
+ return failure(mismatches);
3001
+ }
3002
+ return ctx.success(result);
3003
+ };
3004
+ }
3005
+
3006
+ const string = (rawValue, ctx) => {
3007
+ if (typeof rawValue === "string") {
3008
+ return ctx.success(rawValue);
3009
+ }
3010
+ return ctx.failure("string", rawValue);
3011
+ };
3012
+
3013
+ function trio(parseA, parseB, parseC) {
3014
+ return (rawValue, ctx) => {
3015
+ if (!Array.isArray(rawValue)) {
3016
+ return ctx.failure("array", rawValue);
3017
+ }
3018
+ if (rawValue.length !== 3) {
3019
+ return ctx.failure("array of length 3", rawValue);
3020
+ }
3021
+ const resultA = parseA(rawValue[0], ctx.forSubtree(0));
3022
+ if (isFailure(resultA)) {
3023
+ return resultA;
3024
+ }
3025
+ const resultB = parseB(rawValue[1], ctx.forSubtree(1));
3026
+ if (isFailure(resultB)) {
3027
+ return resultB;
3028
+ }
3029
+ const resultC = parseC(rawValue[2], ctx.forSubtree(2));
3030
+ if (isFailure(resultC)) {
3031
+ return resultC;
3032
+ }
3033
+ return ctx.success([resultA.value, resultB.value, resultC.value]);
3034
+ };
3035
+ }
3036
+
3037
+ function union(parseBranch) {
3038
+ return new UnionBuilder(parseBranch);
3039
+ }
3040
+ class UnionBuilder {
3041
+ constructor(parser) {
3042
+ this.parser = parser;
3043
+ }
3044
+ or(newBranch) {
3045
+ return new UnionBuilder(either(this.parser, newBranch));
3046
+ }
3047
+ }
3048
+ function either(parseA, parseB) {
3049
+ return (rawValue, ctx) => {
3050
+ const resultA = parseA(rawValue, ctx);
3051
+ if (isSuccess(resultA)) {
3052
+ return resultA;
3053
+ }
3054
+ return parseB(rawValue, ctx);
3055
+ };
3056
+ }
3057
+
3058
+ function defaulted(parser, fallback) {
3059
+ return (rawValue, ctx) => {
3060
+ if (rawValue == null) {
3061
+ return success(fallback(rawValue));
3062
+ }
3063
+ return parser(rawValue, ctx);
3064
+ };
3065
+ }
3066
+
3067
+ const parseImages = defaulted(record(string, object({
3068
+ width: number,
3069
+ height: number
3070
+ })), () => ({}));
3071
+
3072
+ function parseWidget(parseType, parseOptions) {
3073
+ return object({
3074
+ type: parseType,
3075
+ static: optional(boolean),
3076
+ graded: optional(boolean),
3077
+ alignment: optional(string),
3078
+ options: parseOptions,
3079
+ key: optional(number),
3080
+ version: optional(object({
3081
+ major: number,
3082
+ minor: number
3083
+ }))
3084
+ });
3085
+ }
3086
+ function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
3087
+ return object({
3088
+ type: parseType,
3089
+ static: optional(boolean),
3090
+ graded: optional(boolean),
3091
+ alignment: optional(string),
3092
+ options: parseOptions,
3093
+ key: optional(number),
3094
+ version: parseVersion
3095
+ });
3096
+ }
3097
+
3098
+ const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
3099
+ items: array(string),
3100
+ categories: array(string),
3101
+ randomizeItems: defaulted(boolean, () => false),
3102
+ static: defaulted(boolean, () => false),
3103
+ values: array(defaulted(number, () => 0)),
3104
+ highlightLint: optional(boolean),
3105
+ linterContext: optional(object({
3106
+ contentType: string,
3107
+ paths: array(string),
3108
+ stack: array(string)
3109
+ }))
3110
+ }));
3111
+
3112
+ const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
3113
+ programID: string,
3114
+ programType: any,
3115
+ settings: array(object({
3116
+ name: string,
3117
+ value: string
3118
+ })),
3119
+ showEditor: boolean,
3120
+ showButtons: boolean,
3121
+ height: number,
3122
+ static: defaulted(boolean, () => false)
3123
+ }));
3124
+
3125
+ const parseDefinitionWidget = parseWidget(constant("definition"), object({
3126
+ togglePrompt: string,
3127
+ definition: string,
3128
+ static: defaulted(boolean, () => false)
3129
+ }));
3130
+
3131
+ const parseDropdownWidget = parseWidget(constant("dropdown"), object({
3132
+ placeholder: defaulted(string, () => ""),
3133
+ ariaLabel: optional(string),
3134
+ visibleLabel: optional(string),
3135
+ static: defaulted(boolean, () => false),
3136
+ choices: array(object({
3137
+ content: string,
3138
+ correct: boolean
3139
+ }))
3140
+ }));
3141
+
3142
+ const parseExplanationWidget = parseWidget(constant("explanation"), object({
3143
+ showPrompt: string,
3144
+ hidePrompt: string,
3145
+ explanation: string,
3146
+ // We wrap parseWidgetsMap in a function here to make sure it is not
3147
+ // referenced before it is defined. There is an import cycle between
3148
+ // this file and widgets-map.ts that could cause it to be undefined.
3149
+ widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
3150
+ static: defaulted(boolean, () => false)
3151
+ }));
3152
+
3153
+ // Given a function, creates a PartialParser that converts one type to another
3154
+ // using that function. The returned parser never fails.
3155
+ function convert(f) {
3156
+ return (rawValue, ctx) => ctx.success(f(rawValue));
3157
+ }
3158
+
3159
+ /**
3160
+ * Creates a parser for a widget options type with multiple major versions. Old
3161
+ * versions are migrated to the latest version. The parse fails if the input
3162
+ * data does not match any of the versions.
3163
+ *
3164
+ * @example
3165
+ * const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
3166
+ * .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
3167
+ * .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
3168
+ * .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
3169
+ * .parser;
3170
+ *
3171
+ * @param latestMajorVersion the latest major version of the widget options.
3172
+ * @param parseLatest a {@link Parser} for the latest version of the widget
3173
+ * options.
3174
+ * @returns a builder object, to which migrations from earlier versions can be
3175
+ * added. Migrations must be added in "reverse chronological" order as in the
3176
+ * example above.
3177
+ */
3178
+ function versionedWidgetOptions(latestMajorVersion, parseLatest) {
3179
+ return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
3180
+ }
3181
+ class VersionedWidgetOptionsParserBuilder {
3182
+ constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
3183
+ this.parser = void 0;
3184
+ this.migrateToLatest = migrateToLatest;
3185
+ this.parseOtherVersions = parseOtherVersions;
3186
+ const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
3187
+ this.parser = (raw, ctx) => {
3188
+ if (!isObject(raw)) {
3189
+ return ctx.failure("object", raw);
3190
+ }
3191
+ const versionParseResult = parseVersionedObject(raw, ctx);
3192
+ if (isFailure(versionParseResult)) {
3193
+ return versionParseResult;
3194
+ }
3195
+ if (versionParseResult.value.version.major !== majorVersion) {
3196
+ return this.parseOtherVersions(raw, ctx);
3197
+ }
3198
+ return parseThisVersionAndMigrateToLatest(raw, ctx);
3199
+ };
3200
+ }
3201
+
3202
+ /**
3203
+ * Add a migration from an old version of the widget options.
3204
+ */
3205
+ withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
3206
+ const parseOtherVersions = this.parser;
3207
+ const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
3208
+ return new VersionedWidgetOptionsParserBuilder(majorVersion, parseOldVersion, migrateToLatest, parseOtherVersions);
3209
+ }
3210
+ }
3211
+ const parseVersionedObject = object({
3212
+ version: defaulted(object({
3213
+ major: number,
3214
+ minor: number
3215
+ }), () => ({
3216
+ major: 0,
3217
+ minor: 0
3218
+ }))
3219
+ });
3220
+
3221
+ const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
3222
+ const parsePossiblyInvalidAnswerForm = object({
3223
+ // `value` is the possibly invalid part of this. It should always be a
3224
+ // string, but some answer forms don't have it. The Expression widget
3225
+ // ignores invalid values, so we can safely filter them out during parsing.
3226
+ value: optional(string),
3227
+ form: defaulted(boolean, () => false),
3228
+ simplify: defaulted(boolean, () => false),
3229
+ considered: enumeration("correct", "wrong", "ungraded"),
3230
+ key: pipeParsers(stringOrNumberOrNullOrUndefined).then(convert(String)).parser
3231
+ });
3232
+ function removeInvalidAnswerForms(possiblyInvalid) {
3233
+ const valid = [];
3234
+ for (const answerForm of possiblyInvalid) {
3235
+ const {
3236
+ value
3237
+ } = answerForm;
3238
+ if (value != null) {
3239
+ // Copying the object seems to be needed to make TypeScript happy
3240
+ valid.push(_extends({}, answerForm, {
3241
+ value
3242
+ }));
3243
+ }
3244
+ }
3245
+ return valid;
3246
+ }
3247
+
3248
+ // NOTE(benchristel): I copied the default buttonSets from
3249
+ // expression.tsx. See the parse-perseus-json/README.md for
3250
+ // an explanation of why we want to duplicate the default here.
3251
+ const parseButtonSets = defaulted(array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")), () => ["basic", "trig", "prealgebra", "logarithms"]);
3252
+ const version1 = object({
3253
+ major: constant(1),
3254
+ minor: number
3255
+ });
3256
+ const parseExpressionWidgetV1 = parseWidgetWithVersion(version1, constant("expression"), object({
3257
+ answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
3258
+ functions: array(string),
3259
+ times: boolean,
3260
+ visibleLabel: optional(string),
3261
+ ariaLabel: optional(string),
3262
+ buttonSets: parseButtonSets,
3263
+ buttonsVisible: optional(enumeration("always", "never", "focused"))
3264
+ }));
3265
+ const version0 = optional(object({
3266
+ major: constant(0),
3267
+ minor: number
3268
+ }));
3269
+ const parseExpressionWidgetV0 = parseWidgetWithVersion(version0, constant("expression"), object({
3270
+ functions: array(string),
3271
+ times: boolean,
3272
+ visibleLabel: optional(string),
3273
+ ariaLabel: optional(string),
3274
+ form: boolean,
3275
+ simplify: boolean,
3276
+ value: string,
3277
+ buttonSets: parseButtonSets,
3278
+ buttonsVisible: optional(enumeration("always", "never", "focused"))
3279
+ }));
3280
+ function migrateV0ToV1(widget) {
3281
+ const {
3282
+ options
3283
+ } = widget;
3284
+ return _extends({}, widget, {
3285
+ version: {
3286
+ major: 1,
3287
+ minor: 0
3288
+ },
3289
+ options: {
3290
+ times: options.times,
3291
+ buttonSets: options.buttonSets,
3292
+ functions: options.functions,
3293
+ buttonsVisible: options.buttonsVisible,
3294
+ visibleLabel: options.visibleLabel,
3295
+ ariaLabel: options.ariaLabel,
3296
+ answerForms: [{
3297
+ considered: "correct",
3298
+ form: options.form,
3299
+ simplify: options.simplify,
3300
+ value: options.value
3301
+ }]
3302
+ }
3303
+ });
3304
+ }
3305
+ const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
3306
+
3307
+ const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
3308
+ const parseGradedGroupWidgetOptions = object({
3309
+ title: defaulted(string, () => ""),
3310
+ hasHint: optional(nullable(boolean)),
3311
+ // This module has an import cycle with parsePerseusRenderer.
3312
+ // The anonymous function below ensures that we don't try to access
3313
+ // parsePerseusRenderer before it's defined.
3314
+ hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
3315
+ content: string,
3316
+ // This module has an import cycle with parseWidgetsMap.
3317
+ // The anonymous function below ensures that we don't try to access
3318
+ // parseWidgetsMap before it's defined.
3319
+ widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
3320
+ widgetEnabled: optional(nullable(boolean)),
3321
+ immutableWidgets: optional(nullable(boolean)),
3322
+ images: record(string, object({
3323
+ width: number,
3324
+ height: number
3325
+ }))
3326
+ });
3327
+ const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
3328
+
3329
+ const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
3330
+ gradedGroups: array(parseGradedGroupWidgetOptions)
3331
+ }));
3332
+
3333
+ /**
3334
+ * discriminatedUnion() should be preferred over union() when parsing a
3335
+ * discriminated union type, because discriminatedUnion() produces more
3336
+ * understandable failure messages. It takes the discriminant as the source of
3337
+ * truth for which variant is to be parsed, and expects the other data to match
3338
+ * that variant.
3339
+ */
3340
+ function discriminatedUnionOn(discriminantKey) {
3341
+ const noMoreBranches = (raw, ctx) => {
3342
+ if (!isObject(raw)) {
3343
+ return ctx.failure("object", raw);
3344
+ }
3345
+ return ctx.forSubtree(discriminantKey).failure("a valid value", raw[discriminantKey]);
3346
+ };
3347
+ return new DiscriminatedUnionBuilder(discriminantKey, noMoreBranches);
3348
+ }
3349
+ class DiscriminatedUnionBuilder {
3350
+ constructor(discriminantKey, parser) {
3351
+ this.discriminantKey = discriminantKey;
3352
+ this.parser = parser;
3353
+ }
3354
+ withBranch(discriminantValue, parseNewVariant) {
3355
+ const parseNewBranch = discriminatedUnionBranch(this.discriminantKey, discriminantValue, parseNewVariant, this.parser);
3356
+ return new DiscriminatedUnionBuilder(this.discriminantKey, parseNewBranch);
3357
+ }
3358
+ }
3359
+ function discriminatedUnionBranch(discriminantKey, discriminantValue, parseVariant, parseOtherBranches) {
3360
+ return (raw, ctx) => {
3361
+ if (!isObject(raw)) {
3362
+ return ctx.failure("object", raw);
3363
+ }
3364
+ if (raw[discriminantKey] === discriminantValue) {
3365
+ return parseVariant(raw, ctx);
3366
+ }
3367
+ return parseOtherBranches(raw, ctx);
3368
+ };
3369
+ }
3370
+
3371
+ const pairOfNumbers$3 = pair(number, number);
3372
+ const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
3373
+ const parseGrapherWidget = parseWidget(constant("grapher"), object({
3374
+ availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
3375
+ correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
3376
+ type: constant("absolute_value"),
3377
+ coords: nullable(pairOfPoints)
3378
+ })).withBranch("exponential", object({
3379
+ type: constant("exponential"),
3380
+ asymptote: pairOfPoints,
3381
+ coords: nullable(pairOfPoints)
3382
+ })).withBranch("linear", object({
3383
+ type: constant("linear"),
3384
+ coords: nullable(pairOfPoints)
3385
+ })).withBranch("logarithm", object({
3386
+ type: constant("logarithm"),
3387
+ asymptote: pairOfPoints,
3388
+ coords: nullable(pairOfPoints)
3389
+ })).withBranch("quadratic", object({
3390
+ type: constant("quadratic"),
3391
+ coords: nullable(pairOfPoints)
3392
+ })).withBranch("sinusoid", object({
3393
+ type: constant("sinusoid"),
3394
+ coords: nullable(pairOfPoints)
3395
+ })).withBranch("tangent", object({
3396
+ type: constant("tangent"),
3397
+ coords: nullable(pairOfPoints)
3398
+ })).parser,
3399
+ graph: object({
3400
+ backgroundImage: object({
3401
+ bottom: optional(number),
3402
+ height: optional(number),
3403
+ left: optional(number),
3404
+ scale: optional(number),
3405
+ url: optional(nullable(string)),
3406
+ width: optional(number)
3407
+ }),
3408
+ box: optional(pairOfNumbers$3),
3409
+ editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
3410
+ gridStep: optional(pairOfNumbers$3),
3411
+ labels: pair(string, string),
3412
+ markings: enumeration("graph", "none", "grid"),
3413
+ range: pair(pairOfNumbers$3, pairOfNumbers$3),
3414
+ rulerLabel: constant(""),
3415
+ rulerTicks: number,
3416
+ showProtractor: optional(boolean),
3417
+ showRuler: optional(boolean),
3418
+ showTooltips: optional(boolean),
3419
+ snapStep: optional(pairOfNumbers$3),
3420
+ step: pairOfNumbers$3,
3421
+ valid: optional(union(boolean).or(string).parser)
3422
+ })
3423
+ }));
3424
+
3425
+ const parseGroupWidget = parseWidget(constant("group"),
3426
+ // This module has an import cycle with parsePerseusRenderer.
3427
+ // The anonymous function below ensures that we don't try to access
3428
+ // parsePerseusRenderer before it's defined.
3429
+ (rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
3430
+
3431
+ const parseIframeWidget = parseWidget(constant("iframe"), object({
3432
+ url: string,
3433
+ settings: optional(array(object({
3434
+ name: string,
3435
+ value: string
3436
+ }))),
3437
+ width: union(number).or(string).parser,
3438
+ height: union(number).or(string).parser,
3439
+ allowFullScreen: defaulted(boolean, () => false),
3440
+ allowTopNavigation: optional(boolean),
3441
+ static: defaulted(boolean, () => false)
3442
+ }));
3443
+
3444
+ const stringToNumber = (rawValue, ctx) => {
3445
+ if (typeof rawValue === "number") {
3446
+ return ctx.success(rawValue);
3447
+ }
3448
+ const parsedNumber = +rawValue;
3449
+ if (rawValue === "" || isNaN(parsedNumber)) {
3450
+ return ctx.failure("a number or numeric string", rawValue);
3451
+ }
3452
+ return ctx.success(parsedNumber);
3453
+ };
3454
+
3455
+ function emptyToZero(x) {
3456
+ return x === "" ? 0 : x;
3457
+ }
3458
+ const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
3459
+ // In this specific case, empty string is equivalent to zero. An empty
3460
+ // string parses to either NaN (using parseInt) or 0 (using unary +) and
3461
+ // CSS will treat NaN as invalid and default to 0 instead.
3462
+ .then(convert(emptyToZero)).then(stringToNumber).parser;
3463
+ const parsePerseusImageBackground = object({
3464
+ url: optional(nullable(string)),
3465
+ width: optional(imageDimensionToNumber),
3466
+ height: optional(imageDimensionToNumber),
3467
+ top: optional(imageDimensionToNumber),
3468
+ left: optional(imageDimensionToNumber),
3469
+ bottom: optional(imageDimensionToNumber),
3470
+ scale: optional(imageDimensionToNumber)
3471
+ });
3472
+
3473
+ const pairOfNumbers$2 = pair(number, number);
3474
+ const parseImageWidget = parseWidget(constant("image"), object({
3475
+ title: optional(string),
3476
+ caption: optional(string),
3477
+ alt: optional(string),
3478
+ backgroundImage: parsePerseusImageBackground,
3479
+ static: optional(boolean),
3480
+ labels: optional(array(object({
3481
+ content: string,
3482
+ alignment: string,
3483
+ coordinates: array(number)
3484
+ }))),
3485
+ range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
3486
+ box: optional(pairOfNumbers$2)
3487
+ }));
3488
+
3489
+ const booleanToString = (rawValue, ctx) => {
3490
+ if (typeof rawValue === "boolean") {
3491
+ return ctx.success(String(rawValue));
3492
+ }
3493
+ return ctx.failure("boolean", rawValue);
3494
+ };
3495
+ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
3496
+ answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
3497
+ inexact: optional(boolean),
3498
+ maxError: optional(union(number).or(string).parser),
3499
+ rightAlign: optional(boolean),
3500
+ simplify: enumeration("required", "optional", "enforced"),
3501
+ size: enumeration("normal", "small"),
3502
+ // TODO(benchristel): there are some content items where value is a
3503
+ // boolean, even though that makes no sense. We should figure out if
3504
+ // those content items are actually published anywhere, and consider
3505
+ // updating them.
3506
+ value: union(number).or(string).or(booleanToString).parser,
3507
+ customKeypad: optional(boolean)
3508
+ }));
3509
+
3510
+ const pairOfNumbers$1 = pair(number, number);
3511
+ const stringOrEmpty = defaulted(string, () => "");
3512
+ const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
3513
+ const parseFunctionElement = object({
3514
+ type: constant("function"),
3515
+ key: parseKey,
3516
+ options: object({
3517
+ value: string,
3518
+ funcName: string,
3519
+ rangeMin: string,
3520
+ rangeMax: string,
3521
+ color: string,
3522
+ strokeDasharray: string,
3523
+ strokeWidth: number
3524
+ })
3525
+ });
3526
+ const parseLabelElement = object({
3527
+ type: constant("label"),
3528
+ key: parseKey,
3529
+ options: object({
3530
+ label: string,
3531
+ color: string,
3532
+ coordX: string,
3533
+ coordY: string
3534
+ })
3535
+ });
3536
+ const parseLineElement = object({
3537
+ type: constant("line"),
3538
+ key: parseKey,
3539
+ options: object({
3540
+ color: string,
3541
+ startX: string,
3542
+ startY: string,
3543
+ endX: string,
3544
+ endY: string,
3545
+ strokeDasharray: string,
3546
+ strokeWidth: number,
3547
+ arrows: string
3548
+ })
3549
+ });
3550
+ const parseMovableLineElement = object({
3551
+ type: constant("movable-line"),
3552
+ key: parseKey,
3553
+ options: object({
3554
+ startX: string,
3555
+ startY: string,
3556
+ startSubscript: number,
3557
+ endX: string,
3558
+ endY: string,
3559
+ endSubscript: number,
3560
+ constraint: string,
3561
+ snap: number,
3562
+ constraintFn: string,
3563
+ constraintXMin: string,
3564
+ constraintXMax: string,
3565
+ constraintYMin: string,
3566
+ constraintYMax: string
3567
+ })
3568
+ });
3569
+ const parseMovablePointElement = object({
3570
+ type: constant("movable-point"),
3571
+ key: parseKey,
3572
+ options: object({
3573
+ startX: string,
3574
+ startY: string,
3575
+ varSubscript: number,
3576
+ constraint: string,
3577
+ snap: number,
3578
+ constraintFn: string,
3579
+ constraintXMin: stringOrEmpty,
3580
+ constraintXMax: stringOrEmpty,
3581
+ constraintYMin: stringOrEmpty,
3582
+ constraintYMax: stringOrEmpty
3583
+ })
3584
+ });
3585
+ const parseParametricElement = object({
3586
+ type: constant("parametric"),
3587
+ key: parseKey,
3588
+ options: object({
3589
+ x: string,
3590
+ y: string,
3591
+ rangeMin: string,
3592
+ rangeMax: string,
3593
+ color: string,
3594
+ strokeDasharray: string,
3595
+ strokeWidth: number
3596
+ })
3597
+ });
3598
+ const parsePointElement = object({
3599
+ type: constant("point"),
3600
+ key: parseKey,
3601
+ options: object({
3602
+ color: string,
3603
+ coordX: string,
3604
+ coordY: string
3605
+ })
3606
+ });
3607
+ const parseRectangleElement = object({
3608
+ type: constant("rectangle"),
3609
+ key: parseKey,
3610
+ options: object({
3611
+ color: string,
3612
+ coordX: string,
3613
+ coordY: string,
3614
+ width: string,
3615
+ height: string
3616
+ })
3617
+ });
3618
+ const parseInteractionWidget = parseWidget(constant("interaction"), object({
3619
+ static: defaulted(boolean, () => false),
3620
+ graph: object({
3621
+ editableSettings: optional(array(enumeration("canvas", "graph"))),
3622
+ box: pairOfNumbers$1,
3623
+ labels: array(string),
3624
+ range: pair(pairOfNumbers$1, pairOfNumbers$1),
3625
+ gridStep: pairOfNumbers$1,
3626
+ markings: enumeration("graph", "grid", "none"),
3627
+ snapStep: optional(pairOfNumbers$1),
3628
+ valid: optional(union(boolean).or(string).parser),
3629
+ backgroundImage: optional(parsePerseusImageBackground),
3630
+ showProtractor: optional(boolean),
3631
+ showRuler: optional(boolean),
3632
+ rulerLabel: optional(string),
3633
+ rulerTicks: optional(number),
3634
+ tickStep: pairOfNumbers$1
3635
+ }),
3636
+ 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)
3637
+ }));
3638
+
3639
+ /**
3640
+ * The Perseus "data schema" file.
3641
+ *
3642
+ * This file, and the types in it, represents the "data schema" that Perseus
3643
+ * uses. The @khanacademy/perseus-editor package edits and produces objects
3644
+ * that conform to the types in this file. Similarly, the top-level renderers
3645
+ * in @khanacademy/perseus, consume objects that conform to these types.
3646
+ *
3647
+ * WARNING: This file should not import any types from elsewhere so that it is
3648
+ * easy to reason about changes that alter the Perseus schema. This helps
3649
+ * ensure that it is not changed accidentally when upgrading a dependant
3650
+ * package or other part of Perseus code. Note that TypeScript does type
3651
+ * checking via something called "structural typing". This means that as long
3652
+ * as the shape of a type matches, the name it goes by doesn't matter. As a
3653
+ * result, a `Coord` type that looks like this `[x: number, y: number]` is
3654
+ * _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
3655
+ * number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
3656
+ * number, y: number]` is compatible with `[min: number, max: number]`. The
3657
+ * labels are for humans, not TypeScript. :)
3658
+ *
3659
+ * If you make changes to types in this file, be very sure that:
3660
+ *
3661
+ * a) the changes are backwards compatible. If they are not, old data from
3662
+ * previous versions of the "schema" could become unrenderable, or worse,
3663
+ * introduce hard-to-diagnose bugs.
3664
+ * b) the parsing code (`util/parse-perseus-json/`) is updated to handle
3665
+ * the new format _as well as_ the old format.
3666
+ */
3667
+
3668
+ // TODO(FEI-4010): Remove `Perseus` prefix for all types here
3669
+
3670
+ // Same name as Mafs
3671
+
3672
+ /**
3673
+ * A utility type that constructs a widget map from a "registry interface".
3674
+ * The keys of the registry should be the widget type (aka, "categorizer" or
3675
+ * "radio", etc) and the value should be the option type stored in the value
3676
+ * of the map.
3677
+ *
3678
+ * You can think of this as a type that generates another type. We use
3679
+ * "registry interfaces" as a way to keep a set of widget types to their data
3680
+ * type in several places in Perseus. This type then allows us to generate a
3681
+ * map type that maps a widget id to its data type and keep strong typing by
3682
+ * widget id.
3683
+ *
3684
+ * For example, given a fictitious registry such as this:
3685
+ *
3686
+ * ```
3687
+ * interface DummyRegistry {
3688
+ * categorizer: { categories: ReadonlyArray<string> };
3689
+ * dropdown: { choices: ReadonlyArray<string> }:
3690
+ * }
3691
+ * ```
3692
+ *
3693
+ * If we create a DummyMap using this helper:
3694
+ *
3695
+ * ```
3696
+ * type DummyMap = MakeWidgetMap<DummyRegistry>;
3697
+ * ```
3698
+ *
3699
+ * We'll get a map that looks like this:
3700
+ *
3701
+ * ```
3702
+ * type DummyMap = {
3703
+ * `categorizer ${number}`: { categories: ReadonlyArray<string> };
3704
+ * `dropdown ${number}`: { choices: ReadonlyArray<string> };
3705
+ * }
3706
+ * ```
3707
+ *
3708
+ * We use interfaces for the registries so that they can be extended in cases
3709
+ * where the consuming app brings along their own widgets. Interfaces in
3710
+ * TypeScript are always open (ie. you can extend them) whereas types aren't.
3711
+ */
3712
+
3713
+ /**
3714
+ * Our core set of Perseus widgets.
3715
+ *
3716
+ * This interface is the basis for "registering" all Perseus widget types.
3717
+ * There should be one key/value pair for each supported widget. If you create
3718
+ * a new widget, an entry should be added to this interface. Note that this
3719
+ * only registers the widget options type, you'll also need to register the
3720
+ * widget so that it's available at runtime (@see
3721
+ * {@link file://./widgets.ts#registerWidget}).
3722
+ *
3723
+ * Importantly, the key should be the name that is used in widget IDs. For most
3724
+ * widgets that is the same as the widget option's `type` field. In cases where
3725
+ * a widget has been deprecated and replaced with the deprecated-standin
3726
+ * widget, it should be the original widget type!
3727
+ *
3728
+ * If you define the widget outside of this package, you can still add the new
3729
+ * widget to this interface by writing the following in that package that
3730
+ * contains the widget. TypeScript will merge that definition of the
3731
+ * `PerseusWidgets` with the one defined below.
3732
+ *
3733
+ * ```typescript
3734
+ * declare module "@khanacademy/perseus-core" {
3735
+ * interface PerseusWidgetTypes {
3736
+ * // A new widget
3737
+ * "new-awesomeness": MyAwesomeNewWidget;
3738
+ *
3739
+ * // A deprecated widget
3740
+ * "super-old-widget": DeprecatedStandinWidget;
3741
+ * }
3742
+ * }
3743
+ *
3744
+ * // The new widget's options definition
3745
+ * type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
3746
+ *
3747
+ * // The deprecated widget's options definition
3748
+ * type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
3749
+ * ```
3750
+ *
3751
+ * This interface can be extended through the magic of TypeScript "Declaration
3752
+ * merging". Specifically, we augment this module and extend this interface.
3753
+ *
3754
+ * @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
3755
+ */
3756
+
3757
+ /**
3758
+ * A map of widget IDs to widget options. This is most often used as the type
3759
+ * for a set of widgets defined in a `PerseusItem` but can also be useful to
3760
+ * represent a function parameter where only `widgets` from a `PerseusItem` are
3761
+ * needed. Today Widget IDs are made up of the widget type and an incrementing
3762
+ * integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
3763
+ * reading/parsing the widget id to derive any information from it, except in
3764
+ * the case of this map.
3765
+ *
3766
+ * @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
3767
+ * by augmenting the PerseusWidgetTypes with new widget types!
3768
+ */
3769
+
3770
+ /**
3771
+ * PerseusWidget is a union of all the different types of widget options that
3772
+ * Perseus knows about.
3773
+ *
3774
+ * Thanks to it being based on PerseusWidgetTypes interface, this union is
3775
+ * automatically extended to include widgets used in tests without those widget
3776
+ * option types seeping into our production types.
3777
+ *
3778
+ * @see MockWidget for an example
3779
+ */
3780
+
3781
+ /**
3782
+ * A "PerseusItem" is a classic Perseus item. It is rendered by the
3783
+ * `ServerItemRenderer` and the layout is pre-set.
3784
+ *
3785
+ * To render more complex Perseus items, see the `Item` type in the multi item
3786
+ * area.
3787
+ */
3788
+
3789
+ /**
3790
+ * A "PerseusArticle" is an item that is meant to be rendered as an article.
3791
+ * This item is never scored and is rendered by the `ArticleRenderer`.
3792
+ */
3793
+
3794
+ const ItemExtras = [
3795
+ // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
3796
+ "calculator",
3797
+ // The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
3798
+ "chi2Table",
3799
+ // The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
3800
+ "financialCalculatorMonthlyPayment",
3801
+ // The user might benefit from a total amount calculator. Provided on Khan Academy when true
3802
+ "financialCalculatorTotalAmount",
3803
+ // The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
3804
+ "financialCalculatorTimeToPayOff",
3805
+ // The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
3806
+ "periodicTable",
3807
+ // The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
3808
+ "periodicTableWithKey",
3809
+ // The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
3810
+ "tTable",
3811
+ // The user might benefit from using a statistics Z Table like https://www.ztable.net/
3812
+ "zTable"];
3813
+
3814
+ /**
3815
+ * The type representing the common structure of all widget's options. The
3816
+ * `Options` generic type represents the widget-specific option data.
3817
+ */
3818
+
3819
+ // prettier-ignore
3820
+
3821
+ // prettier-ignore
3822
+
3823
+ // prettier-ignore
3824
+
3825
+ // prettier-ignore
3826
+
3827
+ // prettier-ignore
3828
+
3829
+ // prettier-ignore
3830
+
3831
+ // prettier-ignore
3832
+
3833
+ // prettier-ignore
3834
+
3835
+ // prettier-ignore
3836
+
3837
+ // prettier-ignore
3838
+
3839
+ // prettier-ignore
3840
+
3841
+ // prettier-ignore
3842
+
3843
+ // prettier-ignore
3844
+
3845
+ // prettier-ignore
3846
+
3847
+ // prettier-ignore
3848
+
3849
+ // prettier-ignore
3850
+
3851
+ // prettier-ignore
3852
+
3853
+ // prettier-ignore
3854
+
3855
+ // prettier-ignore
3856
+
3857
+ // prettier-ignore
3858
+
3859
+ // prettier-ignore
3860
+
3861
+ // prettier-ignore
3862
+
3863
+ // prettier-ignore
3864
+
3865
+ // prettier-ignore
3866
+
3867
+ // prettier-ignore
3868
+
3869
+ // prettier-ignore
3870
+
3871
+ // prettier-ignore
3872
+
3873
+ // prettier-ignore
3874
+
3875
+ // prettier-ignore
3876
+
3877
+ // prettier-ignore
3878
+
3879
+ // prettier-ignore
3880
+
3881
+ // prettier-ignore
2775
3882
 
2776
3883
  // prettier-ignore
2777
3884
 
2778
- // prettier-ignore
3885
+ //prettier-ignore
3886
+
3887
+ /**
3888
+ * A background image applied to various widgets.
3889
+ */
3890
+
3891
+ /**
3892
+ * The type of markings to display on the graph.
3893
+ * - axes: shows the axes without the gride lines
3894
+ * - graph: shows the axes and the grid lines
3895
+ * - grid: shows only the grid lines
3896
+ * - none: shows no markings
3897
+ */
3898
+
3899
+ const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
3900
+
3901
+ // 2D range: xMin, xMax, yMin, yMax
3902
+
3903
+ const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
3904
+ const lockedFigureColors = {
3905
+ blue: "#3D7586",
3906
+ green: "#447A53",
3907
+ grayH: "#3B3D45",
3908
+ purple: "#594094",
3909
+ pink: "#B25071",
3910
+ red: "#D92916",
3911
+ orange: "#946700"
3912
+ };
3913
+ const lockedFigureFillStyles = {
3914
+ none: 0,
3915
+ white: 1,
3916
+ translucent: 0.4,
3917
+ solid: 1
3918
+ };
3919
+
3920
+ // Not associated with a specific figure
3921
+
3922
+ const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
3923
+
3924
+ // Used to represent 2-D points and ranges
3925
+ const pairOfNumbers = pair(number, number);
3926
+ const parsePerseusGraphTypeAngle = object({
3927
+ type: constant("angle"),
3928
+ showAngles: optional(boolean),
3929
+ allowReflexAngles: optional(boolean),
3930
+ angleOffsetDeg: optional(number),
3931
+ snapDegrees: optional(number),
3932
+ match: optional(constant("congruent")),
3933
+ coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
3934
+ startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
3935
+ });
3936
+ const parsePerseusGraphTypeCircle = object({
3937
+ type: constant("circle"),
3938
+ center: optional(pairOfNumbers),
3939
+ radius: optional(number),
3940
+ startCoords: optional(object({
3941
+ center: pairOfNumbers,
3942
+ radius: number
3943
+ })),
3944
+ // TODO: remove coord? it's legacy.
3945
+ coord: optional(pairOfNumbers)
3946
+ });
3947
+ const parsePerseusGraphTypeLinear = object({
3948
+ type: constant("linear"),
3949
+ coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
3950
+ startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
3951
+ // TODO: remove coord? it's legacy.
3952
+ coord: optional(pairOfNumbers)
3953
+ });
3954
+ const parsePerseusGraphTypeLinearSystem = object({
3955
+ type: constant("linear-system"),
3956
+ // TODO(benchristel): default coords to empty array?
3957
+ coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
3958
+ startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
3959
+ // TODO: remove coord? it's legacy.
3960
+ coord: optional(pairOfNumbers)
3961
+ });
3962
+ const parsePerseusGraphTypeNone = object({
3963
+ type: constant("none")
3964
+ });
3965
+ const parsePerseusGraphTypePoint = object({
3966
+ type: constant("point"),
3967
+ numPoints: optional(union(number).or(constant("unlimited")).parser),
3968
+ coords: optional(nullable(array(pairOfNumbers))),
3969
+ startCoords: optional(array(pairOfNumbers)),
3970
+ // TODO: remove coord? it's legacy.
3971
+ coord: optional(pairOfNumbers)
3972
+ });
3973
+ const parsePerseusGraphTypePolygon = object({
3974
+ type: constant("polygon"),
3975
+ numSides: optional(union(number).or(constant("unlimited")).parser),
3976
+ showAngles: optional(boolean),
3977
+ showSides: optional(boolean),
3978
+ snapTo: optional(enumeration("grid", "angles", "sides")),
3979
+ match: optional(enumeration("similar", "congruent", "approx", "exact")),
3980
+ startCoords: optional(array(pairOfNumbers)),
3981
+ // TODO: remove coord? it's legacy.
3982
+ coord: optional(pairOfNumbers)
3983
+ });
3984
+ const parsePerseusGraphTypeQuadratic = object({
3985
+ type: constant("quadratic"),
3986
+ coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
3987
+ startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
3988
+ // TODO: remove coord? it's legacy.
3989
+ coord: optional(pairOfNumbers)
3990
+ });
3991
+ const parsePerseusGraphTypeRay = object({
3992
+ type: constant("ray"),
3993
+ coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
3994
+ startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
3995
+ // TODO: remove coord? it's legacy.
3996
+ coord: optional(pairOfNumbers)
3997
+ });
3998
+ const parsePerseusGraphTypeSegment = object({
3999
+ type: constant("segment"),
4000
+ // TODO(benchristel): default numSegments?
4001
+ numSegments: optional(number),
4002
+ coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
4003
+ startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
4004
+ // TODO: remove coord? it's legacy.
4005
+ coord: optional(pairOfNumbers)
4006
+ });
4007
+ const parsePerseusGraphTypeSinusoid = object({
4008
+ type: constant("sinusoid"),
4009
+ coords: optional(nullable(array(pairOfNumbers))),
4010
+ startCoords: optional(array(pairOfNumbers)),
4011
+ // TODO: remove coord? it's legacy.
4012
+ coord: optional(pairOfNumbers)
4013
+ });
4014
+ 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;
4015
+ const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
4016
+ const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
4017
+ const parseLockedLineStyle = enumeration("solid", "dashed");
4018
+ const parseLockedLabelType = object({
4019
+ type: constant("label"),
4020
+ coord: pairOfNumbers,
4021
+ text: string,
4022
+ color: parseLockedFigureColor,
4023
+ size: enumeration("small", "medium", "large")
4024
+ });
4025
+ const parseLockedPointType = object({
4026
+ type: constant("point"),
4027
+ coord: pairOfNumbers,
4028
+ color: parseLockedFigureColor,
4029
+ filled: boolean,
4030
+ // TODO(benchristel): default labels to empty array?
4031
+ labels: optional(array(parseLockedLabelType)),
4032
+ ariaLabel: optional(string)
4033
+ });
4034
+ const parseLockedLineType = object({
4035
+ type: constant("line"),
4036
+ kind: enumeration("line", "ray", "segment"),
4037
+ points: pair(parseLockedPointType, parseLockedPointType),
4038
+ color: parseLockedFigureColor,
4039
+ lineStyle: parseLockedLineStyle,
4040
+ showPoint1: defaulted(boolean, () => false),
4041
+ showPoint2: defaulted(boolean, () => false),
4042
+ // TODO(benchristel): default labels to empty array?
4043
+ labels: optional(array(parseLockedLabelType)),
4044
+ ariaLabel: optional(string)
4045
+ });
4046
+ const parseLockedVectorType = object({
4047
+ type: constant("vector"),
4048
+ points: pair(pairOfNumbers, pairOfNumbers),
4049
+ color: parseLockedFigureColor,
4050
+ // TODO(benchristel): default labels to empty array?
4051
+ labels: optional(array(parseLockedLabelType)),
4052
+ ariaLabel: optional(string)
4053
+ });
4054
+ const parseLockedEllipseType = object({
4055
+ type: constant("ellipse"),
4056
+ center: pairOfNumbers,
4057
+ radius: pairOfNumbers,
4058
+ angle: number,
4059
+ color: parseLockedFigureColor,
4060
+ fillStyle: parseLockedFigureFillType,
4061
+ strokeStyle: parseLockedLineStyle,
4062
+ // TODO(benchristel): default labels to empty array?
4063
+ labels: optional(array(parseLockedLabelType)),
4064
+ ariaLabel: optional(string)
4065
+ });
4066
+ const parseLockedPolygonType = object({
4067
+ type: constant("polygon"),
4068
+ points: array(pairOfNumbers),
4069
+ color: parseLockedFigureColor,
4070
+ showVertices: boolean,
4071
+ fillStyle: parseLockedFigureFillType,
4072
+ strokeStyle: parseLockedLineStyle,
4073
+ // TODO(benchristel): default labels to empty array?
4074
+ labels: optional(array(parseLockedLabelType)),
4075
+ ariaLabel: optional(string)
4076
+ });
4077
+ const parseLockedFunctionType = object({
4078
+ type: constant("function"),
4079
+ color: parseLockedFigureColor,
4080
+ strokeStyle: parseLockedLineStyle,
4081
+ equation: string,
4082
+ directionalAxis: enumeration("x", "y"),
4083
+ domain: optional(pairOfNumbers),
4084
+ // TODO(benchristel): default labels to empty array?
4085
+ labels: optional(array(parseLockedLabelType)),
4086
+ ariaLabel: optional(string)
4087
+ });
4088
+ 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;
4089
+ const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
4090
+ step: pairOfNumbers,
4091
+ // TODO(benchristel): rather than making gridStep and snapStep
4092
+ // optional, we should duplicate the defaulting logic from the
4093
+ // InteractiveGraph component. See parse-perseus-json/README.md for
4094
+ // why.
4095
+ gridStep: optional(pairOfNumbers),
4096
+ snapStep: optional(pairOfNumbers),
4097
+ backgroundImage: optional(parsePerseusImageBackground),
4098
+ markings: enumeration("graph", "grid", "none"),
4099
+ labels: optional(array(string)),
4100
+ showProtractor: boolean,
4101
+ showRuler: optional(boolean),
4102
+ showTooltips: optional(boolean),
4103
+ rulerLabel: optional(string),
4104
+ rulerTicks: optional(number),
4105
+ range: pair(pairOfNumbers, pairOfNumbers),
4106
+ // NOTE(benchristel): I copied the default graph from
4107
+ // interactive-graph.tsx. See the parse-perseus-json/README.md for
4108
+ // an explanation of why we want to duplicate the default here.
4109
+ graph: defaulted(parsePerseusGraphType, () => ({
4110
+ type: "linear"
4111
+ })),
4112
+ correct: parsePerseusGraphType,
4113
+ // TODO(benchristel): default lockedFigures to empty array
4114
+ lockedFigures: optional(array(parseLockedFigure)),
4115
+ fullGraphLabel: optional(string),
4116
+ fullGraphAriaDescription: optional(string)
4117
+ }));
4118
+
4119
+ const parseLabelImageWidget = parseWidget(constant("label-image"), object({
4120
+ choices: array(string),
4121
+ imageUrl: string,
4122
+ imageAlt: string,
4123
+ imageHeight: number,
4124
+ imageWidth: number,
4125
+ markers: array(object({
4126
+ answers: array(string),
4127
+ label: string,
4128
+ x: number,
4129
+ y: number
4130
+ })),
4131
+ hideChoicesFromInstructions: boolean,
4132
+ multipleAnswers: boolean,
4133
+ static: defaulted(boolean, () => false)
4134
+ }));
4135
+
4136
+ const parseMatcherWidget = parseWidget(constant("matcher"), object({
4137
+ labels: array(string),
4138
+ left: array(string),
4139
+ right: array(string),
4140
+ orderMatters: boolean,
4141
+ padding: boolean
4142
+ }));
4143
+
4144
+ const numberOrString = union(number).or(string).parser;
4145
+ const numeric = pipeParsers(defaulted(numberOrString, () => NaN)).then(stringToNumber).parser;
4146
+ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
4147
+ prefix: optional(string),
4148
+ suffix: optional(string),
4149
+ answers: array(array(numeric)),
4150
+ cursorPosition: optional(array(number)),
4151
+ matrixBoardSize: array(number),
4152
+ static: optional(boolean)
4153
+ }));
4154
+
4155
+ const parseMeasurerWidget = parseWidget(constant("measurer"), object({
4156
+ // The default value for image comes from measurer.tsx.
4157
+ // See parse-perseus-json/README.md for why we want to duplicate the
4158
+ // defaults here.
4159
+ image: defaulted(parsePerseusImageBackground, () => ({
4160
+ url: null,
4161
+ top: 0,
4162
+ left: 0
4163
+ })),
4164
+ showProtractor: boolean,
4165
+ showRuler: boolean,
4166
+ rulerLabel: string,
4167
+ rulerTicks: number,
4168
+ rulerPixels: number,
4169
+ rulerLength: number,
4170
+ box: pair(number, number),
4171
+ // TODO(benchristel): static is not used. Remove it?
4172
+ static: defaulted(boolean, () => false)
4173
+ }));
4174
+
4175
+ const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
4176
+ widgetId: string,
4177
+ rotationAngle: optional(number),
4178
+ smiles: optional(string)
4179
+ }));
4180
+
4181
+ const emptyStringToNull = pipeParsers(constant("")).then(convert(() => null)).parser;
4182
+ const parseNumberLineWidget = parseWidget(constant("number-line"), object({
4183
+ range: array(number),
4184
+ labelRange: array(nullable(union(number).or(emptyStringToNull).parser)),
4185
+ labelStyle: string,
4186
+ labelTicks: boolean,
4187
+ isTickCtrl: optional(nullable(boolean)),
4188
+ divisionRange: array(number),
4189
+ numDivisions: optional(nullable(number)),
4190
+ // NOTE(benchristel): I copied the default snapDivisions from
4191
+ // number-line.tsx. See the parse-perseus-json/README.md for
4192
+ // an explanation of why we want to duplicate the default here.
4193
+ snapDivisions: defaulted(number, () => 2),
4194
+ tickStep: optional(nullable(number)),
4195
+ correctRel: optional(nullable(string)),
4196
+ correctX: nullable(number),
4197
+ initialX: optional(nullable(number)),
4198
+ showTooltips: optional(boolean),
4199
+ static: defaulted(boolean, () => false)
4200
+ }));
4201
+
4202
+ const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
4203
+ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
4204
+ answers: array(object({
4205
+ message: string,
4206
+ // TODO(benchristel): value should never be null or undefined,
4207
+ // but we have some content where it is anyway. If we backfill
4208
+ // the data, simplify this.
4209
+ value: optional(nullable(number)),
4210
+ status: string,
4211
+ answerForms: optional(array(parseMathFormat)),
4212
+ strict: boolean,
4213
+ maxError: optional(nullable(number)),
4214
+ // TODO(benchristel): simplify should never be a boolean, but we
4215
+ // have some content where it is anyway. If we ever backfill
4216
+ // the data, we should simplify `simplify`.
4217
+ simplify: optional(nullable(union(string).or(pipeParsers(boolean).then(convert(String)).parser).parser))
4218
+ })),
4219
+ labelText: optional(string),
4220
+ size: string,
4221
+ coefficient: defaulted(boolean, () => false),
4222
+ rightAlign: optional(boolean),
4223
+ static: defaulted(boolean, () => false),
4224
+ answerForms: optional(array(object({
4225
+ name: parseMathFormat,
4226
+ simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
4227
+ })))
4228
+ }));
4229
+
4230
+ // There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
4231
+ // This wrapper ensures that we don't refer to parsePerseusRenderer before
4232
+ // it's defined.
4233
+ function parseRenderer(rawValue, ctx) {
4234
+ return parsePerseusRenderer(rawValue, ctx);
4235
+ }
4236
+ const largeToAuto = (height, ctx) => {
4237
+ if (height === "large") {
4238
+ return ctx.success("auto");
4239
+ }
4240
+ return ctx.success(height);
4241
+ };
4242
+ const parseOrdererWidget = parseWidget(constant("orderer"), object({
4243
+ options: defaulted(array(parseRenderer), () => []),
4244
+ correctOptions: array(parseRenderer),
4245
+ otherOptions: array(parseRenderer),
4246
+ height: pipeParsers(enumeration("normal", "auto", "large")).then(largeToAuto).parser,
4247
+ layout: defaulted(enumeration("horizontal", "vertical"), () => "horizontal")
4248
+ }));
4249
+
4250
+ const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
4251
+ passageNumber: number,
4252
+ referenceNumber: number,
4253
+ summaryText: optional(string)
4254
+ }));
4255
+
4256
+ const parsePassageWidget = parseWidget(constant("passage"), object({
4257
+ footnotes: defaulted(string, () => ""),
4258
+ passageText: string,
4259
+ passageTitle: defaulted(string, () => ""),
4260
+ showLineNumbers: boolean,
4261
+ static: defaulted(boolean, () => false)
4262
+ }));
4263
+
4264
+ const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
4265
+ url: string,
4266
+ description: string
4267
+ }));
4268
+
4269
+ const parsePlotterWidget = parseWidget(constant("plotter"), object({
4270
+ labels: array(string),
4271
+ categories: array(string),
4272
+ type: enumeration(...plotterPlotTypes),
4273
+ maxY: number,
4274
+ // The default value for scaleY comes from plotter.tsx.
4275
+ // See parse-perseus-json/README.md for why we want to duplicate the
4276
+ // defaults here.
4277
+ scaleY: defaulted(number, () => 1),
4278
+ labelInterval: optional(nullable(number)),
4279
+ // The default value for snapsPerLine comes from plotter.tsx.
4280
+ // See parse-perseus-json/README.md for why we want to duplicate the
4281
+ // defaults here.
4282
+ snapsPerLine: defaulted(number, () => 2),
4283
+ starting: array(number),
4284
+ correct: array(number),
4285
+ picUrl: optional(nullable(string)),
4286
+ picSize: optional(nullable(number)),
4287
+ picBoxHeight: optional(nullable(number)),
4288
+ // NOTE(benchristel): I copied the default plotDimensions from
4289
+ // plotter.tsx. See the parse-perseus-json/README.md for an explanation
4290
+ // of why we want to duplicate the defaults here.
4291
+ plotDimensions: defaulted(array(number), () => [380, 300])
4292
+ }));
4293
+
4294
+ const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
4295
+ programID: string,
4296
+ height: number
4297
+ }));
4298
+
4299
+ const parseRadioWidget = parseWidget(constant("radio"), object({
4300
+ choices: array(object({
4301
+ content: defaulted(string, () => ""),
4302
+ clue: optional(string),
4303
+ correct: optional(boolean),
4304
+ isNoneOfTheAbove: optional(boolean),
4305
+ // deprecated
4306
+ // There is an import cycle between radio-widget.ts and
4307
+ // widgets-map.ts. The anonymous function below ensures that we
4308
+ // don't refer to parseWidgetsMap before it's defined.
4309
+ widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
4310
+ })),
4311
+ hasNoneOfTheAbove: optional(boolean),
4312
+ countChoices: optional(boolean),
4313
+ randomize: optional(boolean),
4314
+ multipleSelect: optional(boolean),
4315
+ deselectEnabled: optional(boolean),
4316
+ // deprecated
4317
+ onePerLine: optional(boolean),
4318
+ // deprecated
4319
+ displayCount: optional(any),
4320
+ // v0 props
4321
+ // `noneOfTheAbove` is still in use (but only set to `false`).
4322
+ noneOfTheAbove: optional(constant(false))
4323
+ }));
4324
+
4325
+ const parseSorterWidget = parseWidget(constant("sorter"), object({
4326
+ correct: array(string),
4327
+ padding: boolean,
4328
+ layout: enumeration("horizontal", "vertical")
4329
+ }));
4330
+
4331
+ const parseTableWidget = parseWidget(constant("table"), object({
4332
+ headers: array(string),
4333
+ rows: number,
4334
+ columns: number,
4335
+ answers: array(array(string))
4336
+ }));
4337
+
4338
+ const parseVideoWidget = parseWidget(constant("video"), object({
4339
+ location: string,
4340
+ static: optional(boolean)
4341
+ }));
4342
+
4343
+ const parseWidgetsMap = (rawValue, ctx) => {
4344
+ if (!isObject(rawValue)) {
4345
+ return ctx.failure("PerseusWidgetsMap", rawValue);
4346
+ }
4347
+ const widgetsMap = {};
4348
+ for (const key of Object.keys(rawValue)) {
4349
+ // parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
4350
+ // but it's the only way I could find to make TypeScript check the key
4351
+ // against the widget type.
4352
+ const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
4353
+ if (isFailure(entryResult)) {
4354
+ return entryResult;
4355
+ }
4356
+ }
4357
+ return ctx.success(widgetsMap);
4358
+ };
4359
+ const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
4360
+ const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
4361
+ if (isFailure(idComponentsResult)) {
4362
+ return idComponentsResult;
4363
+ }
4364
+ const [type, n] = idComponentsResult.value;
4365
+ function parseAndAssign(key, parse) {
4366
+ const widgetResult = parse(widget, ctx);
4367
+ if (isFailure(widgetResult)) {
4368
+ return widgetResult;
4369
+ }
4370
+ widgetMap[key] = widgetResult.value;
4371
+ return ctx.success(undefined);
4372
+ }
4373
+ switch (type) {
4374
+ case "categorizer":
4375
+ return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
4376
+ case "cs-program":
4377
+ return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
4378
+ case "definition":
4379
+ return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
4380
+ case "dropdown":
4381
+ return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
4382
+ case "explanation":
4383
+ return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
4384
+ case "expression":
4385
+ return parseAndAssign(`expression ${n}`, parseExpressionWidget);
4386
+ case "grapher":
4387
+ return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
4388
+ case "group":
4389
+ return parseAndAssign(`group ${n}`, parseGroupWidget);
4390
+ case "graded-group":
4391
+ return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
4392
+ case "graded-group-set":
4393
+ return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
4394
+ case "iframe":
4395
+ return parseAndAssign(`iframe ${n}`, parseIframeWidget);
4396
+ case "image":
4397
+ return parseAndAssign(`image ${n}`, parseImageWidget);
4398
+ case "input-number":
4399
+ return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
4400
+ case "interaction":
4401
+ return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
4402
+ case "interactive-graph":
4403
+ return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
4404
+ case "label-image":
4405
+ return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
4406
+ case "matcher":
4407
+ return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
4408
+ case "matrix":
4409
+ return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
4410
+ case "measurer":
4411
+ return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
4412
+ case "molecule-renderer":
4413
+ return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
4414
+ case "number-line":
4415
+ return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
4416
+ case "numeric-input":
4417
+ return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
4418
+ case "orderer":
4419
+ return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
4420
+ case "passage":
4421
+ return parseAndAssign(`passage ${n}`, parsePassageWidget);
4422
+ case "passage-ref":
4423
+ return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
4424
+ case "passage-ref-target":
4425
+ // NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
4426
+ // used in test content. See:
4427
+ // https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
4428
+ return parseAndAssign(`passage-ref-target ${n}`, any);
4429
+ case "phet-simulation":
4430
+ return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
4431
+ case "plotter":
4432
+ return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
4433
+ case "python-program":
4434
+ return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
4435
+ case "radio":
4436
+ return parseAndAssign(`radio ${n}`, parseRadioWidget);
4437
+ case "sorter":
4438
+ return parseAndAssign(`sorter ${n}`, parseSorterWidget);
4439
+ case "table":
4440
+ return parseAndAssign(`table ${n}`, parseTableWidget);
4441
+ case "video":
4442
+ return parseAndAssign(`video ${n}`, parseVideoWidget);
4443
+ case "sequence":
4444
+ // sequence is a deprecated widget type, and the corresponding
4445
+ // widget component no longer exists.
4446
+ return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
4447
+ case "lights-puzzle":
4448
+ return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
4449
+ case "simulator":
4450
+ return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
4451
+ case "transformer":
4452
+ return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
4453
+ default:
4454
+ return parseAndAssign(`${type} ${n}`, parseWidget(constant(type), any));
4455
+ }
4456
+ };
4457
+ const parseDeprecatedWidget = parseWidget(
4458
+ // Ignore the incoming widget type and hardcode "deprecated-standin"
4459
+ (_, ctx) => ctx.success("deprecated-standin"),
4460
+ // Allow any widget options
4461
+ object({}));
4462
+ const parseStringToPositiveInt = (rawValue, ctx) => {
4463
+ if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
4464
+ return ctx.failure("a string representing a positive integer", rawValue);
4465
+ }
4466
+ return ctx.success(+rawValue);
4467
+ };
4468
+ const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
4469
+
4470
+ const parsePerseusRenderer = defaulted(object({
4471
+ // TODO(benchristel): content is also defaulted to empty string in
4472
+ // renderer.tsx. See if we can remove one default or the other.
4473
+ content: defaulted(string, () => ""),
4474
+ // This module has an import cycle with parseWidgetsMap, because the
4475
+ // `group` widget can contain another renderer.
4476
+ // The anonymous function below ensures that we don't try to access
4477
+ // parseWidgetsMap before it's defined.
4478
+ widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
4479
+ metadata: optional(array(string)),
4480
+ images: parseImages
4481
+ }),
4482
+ // Default value
4483
+ () => ({
4484
+ content: "",
4485
+ widgets: {},
4486
+ images: {}
4487
+ }));
4488
+
4489
+ const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
4490
+
4491
+ function _objectWithoutPropertiesLoose(r, e) {
4492
+ if (null == r) return {};
4493
+ var t = {};
4494
+ for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
4495
+ if (e.includes(n)) continue;
4496
+ t[n] = r[n];
4497
+ }
4498
+ return t;
4499
+ }
4500
+
4501
+ const parseHint = object({
4502
+ replace: optional(boolean),
4503
+ content: string,
4504
+ widgets: defaulted(parseWidgetsMap, () => ({})),
4505
+ metadata: optional(array(string)),
4506
+ images: parseImages
4507
+ });
4508
+
4509
+ const _excluded$5 = ["type", "options"];
4510
+ const parsePerseusItem$1 = object({
4511
+ question: parsePerseusRenderer,
4512
+ hints: defaulted(array(parseHint), () => []),
4513
+ answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
4514
+ itemDataVersion: optional(object({
4515
+ major: number,
4516
+ minor: number
4517
+ })),
4518
+ // Deprecated field
4519
+ answer: any
4520
+ });
4521
+
4522
+ // Some answerAreas have extra fields, like:
4523
+ //
4524
+ // "answerArea": {
4525
+ // "type": "multiple",
4526
+ // "options": {
4527
+ // "content": "",
4528
+ // "images": {},
4529
+ // "widgets": {}
4530
+ // }
4531
+ // }
4532
+ //
4533
+ // The "type" and "options" fields don't seem to be used anywhere. This
4534
+ // migration function removes them.
4535
+ function migrateAnswerArea(rawValue, ctx) {
4536
+ const rest = _objectWithoutPropertiesLoose(rawValue, _excluded$5);
4537
+ return ctx.success(rest);
4538
+ }
4539
+
4540
+ /**
4541
+ * Helper to parse PerseusItem JSON
4542
+ * Why not just use JSON.parse? We want:
4543
+ * - To make sure types are correct
4544
+ * - To give us a central place to validate/transform output if needed
4545
+ * @deprecated - use parseAndMigratePerseusItem instead
4546
+ * @param {string} json - the stringified PerseusItem JSON
4547
+ * @returns {PerseusItem} the parsed PerseusItem object
4548
+ */
4549
+ function parsePerseusItem(json) {
4550
+ // Try to block a cheating vector which relies on monkey-patching
4551
+ // JSON.parse
4552
+ if (isRealJSONParse(JSON.parse)) {
4553
+ return JSON.parse(json);
4554
+ }
4555
+ throw new Error("Something went wrong.");
4556
+ }
4557
+ /**
4558
+ * Parses a PerseusItem from a JSON string, migrates old formats to the latest
4559
+ * schema, and runtime-typechecks the result. Use this to parse assessmentItem
4560
+ * data.
4561
+ *
4562
+ * @returns a {@link Result} of the parsed PerseusItem. If the result is a
4563
+ * failure, it will contain an error message describing where in the tree
4564
+ * parsing failed.
4565
+ * @throws SyntaxError if the argument is not well-formed JSON.
4566
+ */
4567
+ function parseAndMigratePerseusItem(json) {
4568
+ throwErrorIfCheatingDetected();
4569
+ const object = JSON.parse(json);
4570
+ const result = parse(object, parsePerseusItem$1);
4571
+ if (isFailure(result)) {
4572
+ return failure({
4573
+ message: result.detail,
4574
+ invalidObject: object
4575
+ });
4576
+ }
4577
+ return result;
4578
+ }
4579
+
4580
+ /**
4581
+ * Parses a PerseusArticle from a JSON string, migrates old formats to the
4582
+ * latest schema, and runtime-typechecks the result.
4583
+ *
4584
+ * @returns a {@link Result} of the parsed PerseusArticle. If the result is a
4585
+ * failure, it will contain an error message describing where in the tree
4586
+ * parsing failed.
4587
+ * @throws SyntaxError if the argument is not well-formed JSON.
4588
+ */
4589
+ function parseAndMigratePerseusArticle(json) {
4590
+ throwErrorIfCheatingDetected();
4591
+ const object = JSON.parse(json);
4592
+ const result = parse(object, parsePerseusArticle);
4593
+ if (isFailure(result)) {
4594
+ return failure({
4595
+ message: result.detail,
4596
+ invalidObject: object
4597
+ });
4598
+ }
4599
+ return result;
4600
+ }
4601
+
4602
+ /**
4603
+ * Tries to block a cheating vector that relies on monkey-patching JSON.parse.
4604
+ */
4605
+ // TODO(LEMS-2331): delete this function once server-side scoring is done.
4606
+ function throwErrorIfCheatingDetected() {
4607
+ if (!isRealJSONParse(JSON.parse)) {
4608
+ throw new Error("Something went wrong.");
4609
+ }
4610
+ }
4611
+
4612
+ // This file is processed by a Rollup plugin (replace) to inject the production
4613
+ const libName = "@khanacademy/perseus-core";
4614
+ const libVersion = "3.4.0";
4615
+ addLibraryVersionToPerseusDebug(libName, libVersion);
4616
+
4617
+ /**
4618
+ * @typedef {Object} Errors utility for referencing the Perseus error taxonomy.
4619
+ */
4620
+ const Errors = Object.freeze({
4621
+ /**
4622
+ * @property {ErrorKind} Unknown The kind of error is not known.
4623
+ */
4624
+ Unknown: "Unknown",
4625
+ /**
4626
+ * @property {ErrorKind} Internal The error is internal to the executing code.
4627
+ */
4628
+ Internal: "Internal",
4629
+ /**
4630
+ * @property {ErrorKind} InvalidInput There was a problem with the provided
4631
+ * input, such as the wrong format or a null value.
4632
+ */
4633
+ InvalidInput: "InvalidInput",
4634
+ /**
4635
+ * @property {ErrorKind} NotAllowed There was a problem due to the state of
4636
+ * the system not matching the requested operation or input. For example,
4637
+ * trying to create a username that is valid, but is already taken by
4638
+ * another user. Use {@link InvalidInput} instead when the input isn't
4639
+ * valid regardless of the state of the system. Use {@link NotFound} when
4640
+ * the failure is due to not being able to find a resource.
4641
+ */
4642
+ NotAllowed: "NotAllowed",
4643
+ /**
4644
+ * @property {ErrorKind} TransientService There was a problem when making a
4645
+ * request to a service.
4646
+ */
4647
+ TransientService: "TransientService",
4648
+ /**
4649
+ * @property {ErrorKind} Service There was a non-transient problem when
4650
+ * making a request to service.
4651
+ */
4652
+ Service: "Service"
4653
+ });
4654
+
4655
+ /**
4656
+ * @type {ErrorKind} The kind of error being reported
4657
+ */
4658
+
4659
+ class PerseusError extends Error {
4660
+ constructor(message, kind, options) {
4661
+ super(message);
4662
+ this.kind = void 0;
4663
+ this.metadata = void 0;
4664
+ this.kind = kind;
4665
+ this.metadata = options == null ? void 0 : options.metadata;
4666
+ }
4667
+ }
4668
+
4669
+ /**
4670
+ * _ utilities for objects
4671
+ */
4672
+
4673
+ /**
4674
+ * Does a pluck on keys inside objects in an object
4675
+ *
4676
+ * Ex:
4677
+ * tools = {
4678
+ * translation: {
4679
+ * enabled: true
4680
+ * },
4681
+ * rotation: {
4682
+ * enabled: false
4683
+ * }
4684
+ * };
4685
+ * pluckObject(tools, "enabled") returns {
4686
+ * translation: true
4687
+ * rotation: false
4688
+ * }
4689
+ */
4690
+ const pluck = function pluck(table, subKey) {
4691
+ return _.object(_.map(table, function (value, key) {
4692
+ return [key, value[subKey]];
4693
+ }));
4694
+ };
4695
+
4696
+ /**
4697
+ * Maps an object to an object
4698
+ *
4699
+ * > mapObject({a: '1', b: '2'}, (value, key) => {
4700
+ * return value + 1;
4701
+ * });
4702
+ * {a: 2, b: 3}
4703
+ */
4704
+ const mapObject = function mapObject(obj, lambda) {
4705
+ const result = {};
4706
+ Object.keys(obj).forEach(key => {
4707
+ // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
4708
+ result[key] = lambda(obj[key], key);
4709
+ });
4710
+ return result;
4711
+ };
4712
+
4713
+ const defaultWidgetOptions$v = {
4714
+ items: [],
4715
+ categories: [],
4716
+ values: [],
4717
+ randomizeItems: false
4718
+ };
4719
+ const categorizerWidgetLogic = {
4720
+ name: "categorizer",
4721
+ defaultWidgetOptions: defaultWidgetOptions$v
4722
+ };
4723
+
4724
+ const DEFAULT_HEIGHT = 400;
4725
+ const defaultWidgetOptions$u = {
4726
+ programID: "",
4727
+ programType: null,
4728
+ settings: [{
4729
+ name: "",
4730
+ value: ""
4731
+ }],
4732
+ showEditor: false,
4733
+ showButtons: false,
4734
+ height: DEFAULT_HEIGHT
4735
+ };
4736
+ const csProgramWidgetLogic = {
4737
+ name: "cs-program",
4738
+ defaultWidgetOptions: defaultWidgetOptions$u,
4739
+ supportedAlignments: ["block", "full-width"]
4740
+ };
4741
+
4742
+ const defaultWidgetOptions$t = {
4743
+ togglePrompt: "",
4744
+ definition: ""
4745
+ };
4746
+ const definitionWidgetLogic = {
4747
+ name: "definition",
4748
+ defaultWidgetOptions: defaultWidgetOptions$t,
4749
+ defaultAlignment: "inline"
4750
+ };
4751
+
4752
+ const defaultWidgetOptions$s = {
4753
+ placeholder: "",
4754
+ choices: [{
4755
+ content: "",
4756
+ correct: false
4757
+ }]
4758
+ };
4759
+ const dropdownWidgetLogic = {
4760
+ name: "definition",
4761
+ defaultWidgetOptions: defaultWidgetOptions$s,
4762
+ defaultAlignment: "inline-block"
4763
+ };
4764
+
4765
+ const defaultWidgetOptions$r = {
4766
+ showPrompt: "Explain",
4767
+ hidePrompt: "Hide explanation",
4768
+ explanation: "explanation goes here\n\nmore explanation",
4769
+ widgets: {}
4770
+ };
4771
+ const explanationWidgetLogic = {
4772
+ name: "explanation",
4773
+ defaultWidgetOptions: defaultWidgetOptions$r,
4774
+ defaultAlignment: "inline"
4775
+ };
2779
4776
 
2780
- // prettier-ignore
4777
+ const currentVersion$3 = {
4778
+ major: 1,
4779
+ minor: 0
4780
+ };
4781
+ const widgetOptionsUpgrades$2 = {
4782
+ "1": v0options => ({
4783
+ times: v0options.times,
4784
+ buttonSets: v0options.buttonSets,
4785
+ functions: v0options.functions,
4786
+ buttonsVisible: v0options.buttonsVisible,
4787
+ visibleLabel: v0options.visibleLabel,
4788
+ ariaLabel: v0options.ariaLabel,
4789
+ answerForms: [{
4790
+ considered: "correct",
4791
+ form: v0options.form,
4792
+ simplify: v0options.simplify,
4793
+ value: v0options.value
4794
+ }]
4795
+ })
4796
+ };
4797
+ const defaultWidgetOptions$q = {
4798
+ answerForms: [],
4799
+ times: false,
4800
+ buttonSets: ["basic"],
4801
+ functions: ["f", "g", "h"]
4802
+ };
2781
4803
 
2782
- // prettier-ignore
4804
+ const expressionWidgetLogic = {
4805
+ name: "expression",
4806
+ version: currentVersion$3,
4807
+ widgetOptionsUpgrades: widgetOptionsUpgrades$2,
4808
+ defaultWidgetOptions: defaultWidgetOptions$q,
4809
+ defaultAlignment: "inline-block"
4810
+ };
2783
4811
 
2784
- // prettier-ignore
4812
+ const defaultWidgetOptions$p = {
4813
+ title: "",
4814
+ content: "",
4815
+ widgets: {},
4816
+ images: {},
4817
+ hint: null
4818
+ };
4819
+ const gradedGroupWidgetLogic = {
4820
+ name: "graded-group",
4821
+ defaultWidgetOptions: defaultWidgetOptions$p
4822
+ };
2785
4823
 
2786
- // prettier-ignore
4824
+ const defaultWidgetOptions$o = {
4825
+ gradedGroups: []
4826
+ };
4827
+ const gradedGroupSetWidgetLogic = {
4828
+ name: "graded-group-set",
4829
+ defaultWidgetOptions: defaultWidgetOptions$o
4830
+ };
2787
4831
 
2788
- // prettier-ignore
4832
+ const defaultWidgetOptions$n = {
4833
+ graph: {
4834
+ labels: ["x", "y"],
4835
+ range: [[-10, 10], [-10, 10]],
4836
+ step: [1, 1],
4837
+ backgroundImage: {
4838
+ url: null
4839
+ },
4840
+ markings: "graph",
4841
+ rulerLabel: "",
4842
+ rulerTicks: 10,
4843
+ valid: true,
4844
+ showTooltips: false
4845
+ },
4846
+ correct: {
4847
+ type: "linear",
4848
+ coords: null
4849
+ },
4850
+ availableTypes: ["linear"]
4851
+ };
4852
+ const grapherWidgetLogic = {
4853
+ name: "grapher",
4854
+ defaultWidgetOptions: defaultWidgetOptions$n
4855
+ };
2789
4856
 
2790
- // prettier-ignore
4857
+ const defaultWidgetOptions$m = {
4858
+ content: "",
4859
+ widgets: {},
4860
+ images: {},
4861
+ // `undefined` instead of `null` so that getDefaultProps works for
4862
+ // `the GroupMetadataEditor`
4863
+ metadata: undefined
4864
+ };
4865
+ const groupWidgetLogic = {
4866
+ name: "group",
4867
+ defaultWidgetOptions: defaultWidgetOptions$m
4868
+ };
2791
4869
 
2792
- // prettier-ignore
4870
+ const defaultWidgetOptions$l = {
4871
+ url: "",
4872
+ settings: [{
4873
+ name: "",
4874
+ value: ""
4875
+ }],
4876
+ width: "400",
4877
+ height: "400",
4878
+ allowFullScreen: false,
4879
+ allowTopNavigation: false
4880
+ };
4881
+ const iframeWidgetLogic = {
4882
+ name: "iframe",
4883
+ defaultWidgetOptions: defaultWidgetOptions$l
4884
+ };
2793
4885
 
2794
- // prettier-ignore
4886
+ const defaultWidgetOptions$k = {
4887
+ title: "",
4888
+ range: [[0, 10], [0, 10]],
4889
+ box: [400, 400],
4890
+ backgroundImage: {
4891
+ url: null,
4892
+ width: 0,
4893
+ height: 0
4894
+ },
4895
+ labels: [],
4896
+ alt: "",
4897
+ caption: ""
4898
+ };
4899
+ const imageWidgetLogic = {
4900
+ name: "image",
4901
+ defaultWidgetOptions: defaultWidgetOptions$k,
4902
+ supportedAlignments: ["block", "full-width"],
4903
+ defaultAlignment: "block"
4904
+ };
2795
4905
 
2796
- // prettier-ignore
4906
+ const defaultWidgetOptions$j = {
4907
+ value: 0,
4908
+ simplify: "required",
4909
+ size: "normal",
4910
+ inexact: false,
4911
+ maxError: 0.1,
4912
+ answerType: "number",
4913
+ rightAlign: false
4914
+ };
4915
+ const inputNumberWidgetLogic = {
4916
+ name: "input-number",
4917
+ defaultWidgetOptions: defaultWidgetOptions$j,
4918
+ defaultAlignment: "inline-block"
4919
+ };
2797
4920
 
2798
- // prettier-ignore
4921
+ const defaultWidgetOptions$i = {
4922
+ graph: {
4923
+ box: [400, 400],
4924
+ labels: ["x", "y"],
4925
+ range: [[-10, 10], [-10, 10]],
4926
+ tickStep: [1, 1],
4927
+ gridStep: [1, 1],
4928
+ markings: "graph"
4929
+ },
4930
+ elements: []
4931
+ };
4932
+ const interactionWidgetLogic = {
4933
+ name: "interaction",
4934
+ defaultWidgetOptions: defaultWidgetOptions$i
4935
+ };
2799
4936
 
2800
- // prettier-ignore
4937
+ const defaultWidgetOptions$h = {
4938
+ labels: ["x", "y"],
4939
+ range: [[-10, 10], [-10, 10]],
4940
+ step: [1, 1],
4941
+ backgroundImage: {
4942
+ url: null
4943
+ },
4944
+ markings: "graph",
4945
+ showTooltips: false,
4946
+ showProtractor: false,
4947
+ graph: {
4948
+ type: "linear"
4949
+ },
4950
+ correct: {
4951
+ type: "linear",
4952
+ coords: null
4953
+ }
4954
+ };
4955
+ const interactiveGraphWidgetLogic = {
4956
+ name: "interactive-graph",
4957
+ defaultWidgetOptions: defaultWidgetOptions$h
4958
+ };
2801
4959
 
2802
- // prettier-ignore
4960
+ const defaultWidgetOptions$g = {
4961
+ choices: [],
4962
+ imageAlt: "",
4963
+ imageUrl: "",
4964
+ imageWidth: 0,
4965
+ imageHeight: 0,
4966
+ markers: [],
4967
+ multipleAnswers: false,
4968
+ hideChoicesFromInstructions: false
4969
+ };
4970
+ const labelImageWidgetLogic = {
4971
+ name: "label-image",
4972
+ defaultWidgetOptions: defaultWidgetOptions$g
4973
+ };
2803
4974
 
2804
- // prettier-ignore
4975
+ const defaultWidgetOptions$f = {
4976
+ left: ["$x$", "$y$", "$z$"],
4977
+ right: ["$1$", "$2$", "$3$"],
4978
+ labels: ["test", "label"],
4979
+ orderMatters: false,
4980
+ padding: true
4981
+ };
4982
+ const matcherWidgetLogic = {
4983
+ name: "matcher",
4984
+ defaultWidgetOptions: defaultWidgetOptions$f
4985
+ };
2805
4986
 
2806
- // prettier-ignore
4987
+ const defaultWidgetOptions$e = {
4988
+ matrixBoardSize: [3, 3],
4989
+ answers: [[]],
4990
+ prefix: "",
4991
+ suffix: "",
4992
+ cursorPosition: [0, 0]
4993
+ };
4994
+ const matrixWidgetLogic = {
4995
+ name: "matrix",
4996
+ defaultWidgetOptions: defaultWidgetOptions$e
4997
+ };
2807
4998
 
2808
- // prettier-ignore
4999
+ const _excluded$4 = ["imageUrl", "imageTop", "imageLeft"];
5000
+ const currentVersion$2 = {
5001
+ major: 1,
5002
+ minor: 0
5003
+ };
5004
+ const widgetOptionsUpgrades$1 = {
5005
+ "1": v0options => {
5006
+ const {
5007
+ imageUrl,
5008
+ imageTop,
5009
+ imageLeft
5010
+ } = v0options,
5011
+ rest = _objectWithoutPropertiesLoose(v0options, _excluded$4);
5012
+ return _extends({}, rest, {
5013
+ image: {
5014
+ url: imageUrl,
5015
+ top: imageTop,
5016
+ left: imageLeft
5017
+ }
5018
+ });
5019
+ }
5020
+ };
5021
+ const defaultWidgetOptions$d = {
5022
+ box: [480, 480],
5023
+ image: {},
5024
+ showProtractor: true,
5025
+ showRuler: false,
5026
+ rulerLabel: "",
5027
+ rulerTicks: 10,
5028
+ rulerPixels: 40,
5029
+ rulerLength: 10
5030
+ };
2809
5031
 
2810
- // prettier-ignore
5032
+ const measurerWidgetLogic = {
5033
+ name: "measurer",
5034
+ version: currentVersion$2,
5035
+ widgetOptionsUpgrades: widgetOptionsUpgrades$1,
5036
+ defaultWidgetOptions: defaultWidgetOptions$d
5037
+ };
2811
5038
 
2812
- // prettier-ignore
5039
+ const defaultWidgetOptions$c = {
5040
+ range: [0, 10],
5041
+ labelRange: [null, null],
5042
+ labelStyle: "decimal",
5043
+ labelTicks: true,
5044
+ divisionRange: [1, 12],
5045
+ numDivisions: 5,
5046
+ snapDivisions: 2,
5047
+ tickStep: null,
5048
+ correctRel: "eq",
5049
+ correctX: null,
5050
+ initialX: null,
5051
+ showTooltips: false
5052
+ };
5053
+ const numberLineWidgetLogic = {
5054
+ name: "number-line",
5055
+ defaultWidgetOptions: defaultWidgetOptions$c
5056
+ };
2813
5057
 
2814
- // prettier-ignore
5058
+ const defaultWidgetOptions$b = {
5059
+ answers: [{
5060
+ value: null,
5061
+ status: "correct",
5062
+ message: "",
5063
+ simplify: "required",
5064
+ answerForms: [],
5065
+ strict: false,
5066
+ maxError: null
5067
+ }],
5068
+ size: "normal",
5069
+ coefficient: false,
5070
+ labelText: "",
5071
+ rightAlign: false
5072
+ };
5073
+ const numericInputWidgetLogic = {
5074
+ name: "numeric-input",
5075
+ defaultWidgetOptions: defaultWidgetOptions$b,
5076
+ defaultAlignment: "inline-block"
5077
+ };
2815
5078
 
2816
- // prettier-ignore
5079
+ const defaultWidgetOptions$a = {
5080
+ correctOptions: [{
5081
+ content: "$x$"
5082
+ }],
5083
+ otherOptions: [{
5084
+ content: "$y$"
5085
+ }],
5086
+ height: "normal",
5087
+ layout: "horizontal"
5088
+ };
5089
+ const ordererWidgetLogic = {
5090
+ name: "orderer",
5091
+ defaultWidgetOptions: defaultWidgetOptions$a
5092
+ };
2817
5093
 
2818
- // prettier-ignore
5094
+ const defaultWidgetOptions$9 = {
5095
+ passageTitle: "",
5096
+ passageText: "",
5097
+ footnotes: "",
5098
+ showLineNumbers: true
5099
+ };
5100
+ const passageWidgetLogic = {
5101
+ name: "passage",
5102
+ defaultWidgetOptions: defaultWidgetOptions$9
5103
+ };
2819
5104
 
2820
- // prettier-ignore
5105
+ const currentVersion$1 = {
5106
+ major: 0,
5107
+ minor: 1
5108
+ };
5109
+ const defaultWidgetOptions$8 = {
5110
+ passageNumber: 1,
5111
+ referenceNumber: 1,
5112
+ summaryText: ""
5113
+ };
2821
5114
 
2822
- // prettier-ignore
5115
+ const passageRefWidgetLogic = {
5116
+ name: "passageRef",
5117
+ version: currentVersion$1,
5118
+ defaultWidgetOptions: defaultWidgetOptions$8,
5119
+ defaultAlignment: "inline"
5120
+ };
2823
5121
 
2824
- // prettier-ignore
5122
+ const defaultWidgetOptions$7 = {
5123
+ content: ""
5124
+ };
5125
+ const passageRefTargetWidgetLogic = {
5126
+ name: "passageRefTarget",
5127
+ defaultWidgetOptions: defaultWidgetOptions$7,
5128
+ defaultAlignment: "inline"
5129
+ };
2825
5130
 
2826
- // prettier-ignore
5131
+ const defaultWidgetOptions$6 = {
5132
+ url: "",
5133
+ description: ""
5134
+ };
5135
+ const phetSimulationWidgetLogic = {
5136
+ name: "phet-simulation",
5137
+ defaultWidgetOptions: defaultWidgetOptions$6
5138
+ };
2827
5139
 
2828
- // prettier-ignore
5140
+ const defaultWidgetOptions$5 = {
5141
+ scaleY: 1,
5142
+ maxY: 10,
5143
+ snapsPerLine: 2,
5144
+ correct: [1],
5145
+ starting: [1],
5146
+ type: "bar",
5147
+ labels: ["", ""],
5148
+ categories: [""],
5149
+ picSize: 30,
5150
+ picBoxHeight: 36,
5151
+ plotDimensions: [275, 200],
5152
+ labelInterval: 1,
5153
+ picUrl: null
5154
+ };
5155
+ const plotterWidgetLogic = {
5156
+ name: "plotter",
5157
+ defaultWidgetOptions: defaultWidgetOptions$5
5158
+ };
2829
5159
 
2830
- // prettier-ignore
5160
+ const defaultWidgetOptions$4 = {
5161
+ programID: "",
5162
+ height: 400
5163
+ };
5164
+ const pythonProgramWidgetLogic = {
5165
+ name: "python-program",
5166
+ defaultWidgetOptions: defaultWidgetOptions$4
5167
+ };
2831
5168
 
2832
- // prettier-ignore
5169
+ const _excluded$3 = ["noneOfTheAbove"];
5170
+ const currentVersion = {
5171
+ major: 1,
5172
+ minor: 0
5173
+ };
5174
+ const widgetOptionsUpgrades = {
5175
+ "1": v0props => {
5176
+ const {
5177
+ noneOfTheAbove
5178
+ } = v0props,
5179
+ rest = _objectWithoutPropertiesLoose(v0props, _excluded$3);
5180
+ if (noneOfTheAbove) {
5181
+ throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
5182
+ }
5183
+ return _extends({}, rest, {
5184
+ hasNoneOfTheAbove: false
5185
+ });
5186
+ }
5187
+ };
5188
+ const defaultWidgetOptions$3 = {
5189
+ choices: [{}, {}, {}, {}],
5190
+ displayCount: null,
5191
+ randomize: false,
5192
+ hasNoneOfTheAbove: false,
5193
+ multipleSelect: false,
5194
+ countChoices: false,
5195
+ deselectEnabled: false
5196
+ };
2833
5197
 
2834
- //prettier-ignore
5198
+ const radioWidgetLogic = {
5199
+ name: "radio",
5200
+ version: currentVersion,
5201
+ widgetOptionsUpgrades: widgetOptionsUpgrades,
5202
+ defaultWidgetOptions: defaultWidgetOptions$3
5203
+ };
5204
+
5205
+ const defaultWidgetOptions$2 = {
5206
+ correct: ["$x$", "$y$", "$z$"],
5207
+ layout: "horizontal",
5208
+ padding: true
5209
+ };
5210
+ const sorterWidgetLogic = {
5211
+ name: "sorter",
5212
+ defaultWidgetOptions: defaultWidgetOptions$2
5213
+ };
5214
+
5215
+ const defaultRows = 4;
5216
+ const defaultColumns = 1;
5217
+
5218
+ // initialize a 2D array
5219
+ // (defaultRows x defaultColumns) of empty strings
5220
+ const answers = new Array(defaultRows).fill(0).map(() => new Array(defaultColumns).fill(""));
5221
+ const defaultWidgetOptions$1 = {
5222
+ headers: [""],
5223
+ rows: defaultRows,
5224
+ columns: defaultColumns,
5225
+ answers: answers
5226
+ };
5227
+ const tableWidgetLogic = {
5228
+ name: "table",
5229
+ defaultWidgetOptions: defaultWidgetOptions$1
5230
+ };
5231
+
5232
+ const defaultWidgetOptions = {
5233
+ location: ""
5234
+ };
5235
+ const videoWidgetLogic = {
5236
+ name: "video",
5237
+ defaultWidgetOptions,
5238
+ supportedAlignments: ["block", "float-left", "float-right", "full-width"],
5239
+ defaultAlignment: "block"
5240
+ };
5241
+
5242
+ const widgets = {};
5243
+ function registerWidget(type, logic) {
5244
+ widgets[type] = logic;
5245
+ }
5246
+ function isWidgetRegistered(type) {
5247
+ const widgetLogic = widgets[type];
5248
+ return !!widgetLogic;
5249
+ }
5250
+ function getCurrentVersion(type) {
5251
+ const widgetLogic = widgets[type];
5252
+ return (widgetLogic == null ? void 0 : widgetLogic.version) || {
5253
+ major: 0,
5254
+ minor: 0
5255
+ };
5256
+ }
5257
+ function getWidgetOptionsUpgrades(type) {
5258
+ const widgetLogic = widgets[type];
5259
+ return (widgetLogic == null ? void 0 : widgetLogic.widgetOptionsUpgrades) || {};
5260
+ }
5261
+ function getDefaultWidgetOptions(type) {
5262
+ const widgetLogic = widgets[type];
5263
+ return (widgetLogic == null ? void 0 : widgetLogic.defaultWidgetOptions) || {};
5264
+ }
2835
5265
 
2836
5266
  /**
2837
- * A background image applied to various widgets.
5267
+ * Handling for the optional alignments for widgets
5268
+ * See widget-container.jsx for details on how alignments are implemented.
2838
5269
  */
2839
5270
 
2840
5271
  /**
2841
- * The type of markings to display on the graph.
2842
- * - axes: shows the axes without the gride lines
2843
- * - graph: shows the axes and the grid lines
2844
- * - grid: shows only the grid lines
2845
- * - none: shows no markings
5272
+ * Returns the list of supported alignments for the given (string) widget
5273
+ * type. This is used primarily at editing time to display the choices
5274
+ * for the user.
5275
+ *
5276
+ * Supported alignments are given as an array of strings in the exports of
5277
+ * a widget's module.
2846
5278
  */
5279
+ const getSupportedAlignments = type => {
5280
+ var _widgetLogic$supporte;
5281
+ const widgetLogic = widgets[type];
5282
+ if (!(widgetLogic != null && (_widgetLogic$supporte = widgetLogic.supportedAlignments) != null && _widgetLogic$supporte[0])) {
5283
+ // default alignments
5284
+ return ["default"];
5285
+ }
5286
+ return widgetLogic == null ? void 0 : widgetLogic.supportedAlignments;
5287
+ };
2847
5288
 
2848
- const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
5289
+ /**
5290
+ * For the given (string) widget type, determine the default alignment for
5291
+ * the widget. This is used at rendering time to go from "default" alignment
5292
+ * to the actual alignment displayed on the screen.
5293
+ *
5294
+ * The default alignment is given either as a string (called
5295
+ * `defaultAlignment`) or a function (called `getDefaultAlignment`) on
5296
+ * the exports of a widget's module.
5297
+ */
5298
+ const getDefaultAlignment = type => {
5299
+ const widgetLogic = widgets[type];
5300
+ if (!(widgetLogic != null && widgetLogic.defaultAlignment)) {
5301
+ return "block";
5302
+ }
5303
+ return widgetLogic.defaultAlignment;
5304
+ };
5305
+ registerWidget("categorizer", categorizerWidgetLogic);
5306
+ registerWidget("cs-program", csProgramWidgetLogic);
5307
+ registerWidget("definition", definitionWidgetLogic);
5308
+ registerWidget("dropdown", dropdownWidgetLogic);
5309
+ registerWidget("explanation", explanationWidgetLogic);
5310
+ registerWidget("expression", expressionWidgetLogic);
5311
+ registerWidget("graded-group", gradedGroupWidgetLogic);
5312
+ registerWidget("graded-group-set", gradedGroupSetWidgetLogic);
5313
+ registerWidget("grapher", grapherWidgetLogic);
5314
+ registerWidget("group", groupWidgetLogic);
5315
+ registerWidget("iframe", iframeWidgetLogic);
5316
+ registerWidget("image", imageWidgetLogic);
5317
+ registerWidget("input-number", inputNumberWidgetLogic);
5318
+ registerWidget("interaction", interactionWidgetLogic);
5319
+ registerWidget("interactive-graph", interactiveGraphWidgetLogic);
5320
+ registerWidget("label-image", labelImageWidgetLogic);
5321
+ registerWidget("matcher", matcherWidgetLogic);
5322
+ registerWidget("matrix", matrixWidgetLogic);
5323
+ registerWidget("measurer", measurerWidgetLogic);
5324
+ registerWidget("number-line", numberLineWidgetLogic);
5325
+ registerWidget("numeric-input", numericInputWidgetLogic);
5326
+ registerWidget("orderer", ordererWidgetLogic);
5327
+ registerWidget("passage", passageWidgetLogic);
5328
+ registerWidget("passage-ref", passageRefWidgetLogic);
5329
+ registerWidget("passage-ref-target", passageRefTargetWidgetLogic);
5330
+ registerWidget("phet-simulation", phetSimulationWidgetLogic);
5331
+ registerWidget("plotter", plotterWidgetLogic);
5332
+ registerWidget("python-program", pythonProgramWidgetLogic);
5333
+ registerWidget("radio", radioWidgetLogic);
5334
+ registerWidget("sorter", sorterWidgetLogic);
5335
+ registerWidget("table", tableWidgetLogic);
5336
+ registerWidget("video", videoWidgetLogic);
5337
+
5338
+ var coreWidgetRegistry = /*#__PURE__*/Object.freeze({
5339
+ __proto__: null,
5340
+ isWidgetRegistered: isWidgetRegistered,
5341
+ getCurrentVersion: getCurrentVersion,
5342
+ getWidgetOptionsUpgrades: getWidgetOptionsUpgrades,
5343
+ getDefaultWidgetOptions: getDefaultWidgetOptions,
5344
+ getSupportedAlignments: getSupportedAlignments,
5345
+ getDefaultAlignment: getDefaultAlignment
5346
+ });
2849
5347
 
2850
- // 2D range: xMin, xMax, yMin, yMax
5348
+ const DEFAULT_STATIC = false;
5349
+ const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
5350
+ const type = oldWidgetInfo.type;
5351
+ // NOTE(jeremy): This looks like it could be replaced by fixing types so
5352
+ // that `type` is non-optional. But we're seeing this in Sentry today so I
5353
+ // suspect we have legacy data (potentially unpublished) and we should
5354
+ // figure that out before depending solely on types.
5355
+ if (!_.isString(type)) {
5356
+ throw new PerseusError("widget type must be a string, but was: " + type, Errors.Internal);
5357
+ }
5358
+ if (!isWidgetRegistered(type)) {
5359
+ // If we have a widget that isn't registered, we can't upgrade it
5360
+ // TODO(aria): Figure out what the best thing to do here would be
5361
+ return oldWidgetInfo;
5362
+ }
2851
5363
 
2852
- const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
2853
- const lockedFigureColors = {
2854
- blue: "#3D7586",
2855
- green: "#447A53",
2856
- grayH: "#3B3D45",
2857
- purple: "#594094",
2858
- pink: "#B25071",
2859
- red: "#D92916",
2860
- orange: "#946700"
2861
- };
2862
- const lockedFigureFillStyles = {
2863
- none: 0,
2864
- white: 1,
2865
- translucent: 0.4,
2866
- solid: 1
5364
+ // Unversioned widgets (pre-July 2014) are all implicitly 0.0
5365
+ const initialVersion = oldWidgetInfo.version || {
5366
+ major: 0,
5367
+ minor: 0
5368
+ };
5369
+ const latestVersion = getCurrentVersion(type);
5370
+
5371
+ // If the widget version is later than what we understand (major
5372
+ // version is higher than latest, or major versions are equal and minor
5373
+ // version is higher than latest), don't perform any upgrades.
5374
+ if (initialVersion.major > latestVersion.major || initialVersion.major === latestVersion.major && initialVersion.minor > latestVersion.minor) {
5375
+ return oldWidgetInfo;
5376
+ }
5377
+
5378
+ // We do a clone here so that it's safe to mutate the input parameter
5379
+ // in propUpgrades functions (which I will probably accidentally do at
5380
+ // some point, and we would like to not break when that happens).
5381
+ let newEditorOptions = _.clone(oldWidgetInfo.options) || {};
5382
+ const upgradePropsMap = getWidgetOptionsUpgrades(type);
5383
+
5384
+ // Empty props usually mean a newly created widget by the editor,
5385
+ // and are always considerered up-to-date.
5386
+ // Mostly, we'd rather not run upgrade functions on props that are
5387
+ // not complete.
5388
+ if (_.keys(newEditorOptions).length !== 0) {
5389
+ // We loop through all the versions after the current version of
5390
+ // the loaded widget, up to and including the latest version of the
5391
+ // loaded widget, and run the upgrade function to bring our loaded
5392
+ // widget's props up to that version.
5393
+ // There is a little subtlety here in that we call
5394
+ // upgradePropsMap[1] to upgrade *to* version 1,
5395
+ // (not from version 1).
5396
+ for (let nextVersion = initialVersion.major + 1; nextVersion <= latestVersion.major; nextVersion++) {
5397
+ if (upgradePropsMap[String(nextVersion)]) {
5398
+ newEditorOptions = upgradePropsMap[String(nextVersion)](newEditorOptions);
5399
+ } else {
5400
+ throw new PerseusError("No upgrade found for widget. Cannot render.", Errors.Internal, {
5401
+ metadata: {
5402
+ type,
5403
+ fromMajorVersion: nextVersion - 1,
5404
+ toMajorVersion: nextVersion,
5405
+ oldWidgetInfo: JSON.stringify(oldWidgetInfo)
5406
+ }
5407
+ });
5408
+ }
5409
+ }
5410
+ }
5411
+
5412
+ // Minor version upgrades (eg. new optional props) don't have
5413
+ // transform functions. Instead, we fill in the new props with their
5414
+ // defaults.
5415
+ const defaultOptions = getDefaultWidgetOptions(type);
5416
+ newEditorOptions = _extends({}, defaultOptions, newEditorOptions);
5417
+ let alignment = oldWidgetInfo.alignment;
5418
+
5419
+ // Widgets that support multiple alignments will "lock in" the
5420
+ // alignment to the alignment that would be listed first in the
5421
+ // select box. If the widget only supports one alignment, the
5422
+ // alignment value will likely just end up as "default".
5423
+ if (alignment == null || alignment === "default") {
5424
+ var _getSupportedAlignmen;
5425
+ alignment = (_getSupportedAlignmen = getSupportedAlignments(type)) == null ? void 0 : _getSupportedAlignmen[0];
5426
+ if (!alignment) {
5427
+ throw new PerseusError("No default alignment found when upgrading widget", Errors.Internal, {
5428
+ metadata: {
5429
+ widgetType: type
5430
+ }
5431
+ });
5432
+ }
5433
+ }
5434
+ let widgetStatic = oldWidgetInfo.static;
5435
+ if (widgetStatic == null) {
5436
+ widgetStatic = DEFAULT_STATIC;
5437
+ }
5438
+ return _extends({}, oldWidgetInfo, {
5439
+ // maintain other info, like type
5440
+ // After upgrading we guarantee that the version is up-to-date
5441
+ version: latestVersion,
5442
+ // Default graded to true (so null/undefined becomes true):
5443
+ graded: oldWidgetInfo.graded != null ? oldWidgetInfo.graded : true,
5444
+ alignment: alignment,
5445
+ static: widgetStatic,
5446
+ options: newEditorOptions
5447
+ });
2867
5448
  };
5449
+ function getUpgradedWidgetOptions(oldWidgetOptions) {
5450
+ return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
5451
+ if (!widgetInfo.type || !widgetInfo.alignment) {
5452
+ const newValues = {};
5453
+ if (!widgetInfo.type) {
5454
+ // TODO: why does widget have no type?
5455
+ // We don't want to derive type from widget ID
5456
+ // see: LEMS-1845
5457
+ newValues.type = widgetId.split(" ")[0];
5458
+ }
5459
+ if (!widgetInfo.alignment) {
5460
+ newValues.alignment = "default";
5461
+ }
5462
+ widgetInfo = _extends({}, widgetInfo, newValues);
5463
+ }
5464
+ return upgradeWidgetInfoToLatestVersion(widgetInfo);
5465
+ });
5466
+ }
2868
5467
 
2869
- // Not associated with a specific figure
5468
+ /**
5469
+ * For details on the individual options, see the
5470
+ * PerseusOrdererWidgetOptions type
5471
+ */
2870
5472
 
2871
- const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
5473
+ /**
5474
+ * Given a PerseusOrdererWidgetOptions object, return a new object with only
5475
+ * the public options that should be exposed to the client.
5476
+ */
5477
+ function getOrdererPublicWidgetOptions(options) {
5478
+ return {
5479
+ options: options.options,
5480
+ height: options.height,
5481
+ layout: options.layout
5482
+ };
5483
+ }
2872
5484
 
2873
5485
  /**
2874
- * _ utilities for objects
5486
+ * For details on the individual options, see the
5487
+ * PerseusCategorizerWidgetOptions type
2875
5488
  */
2876
5489
 
2877
5490
  /**
2878
- * Does a pluck on keys inside objects in an object
2879
- *
2880
- * Ex:
2881
- * tools = {
2882
- * translation: {
2883
- * enabled: true
2884
- * },
2885
- * rotation: {
2886
- * enabled: false
2887
- * }
2888
- * };
2889
- * pluckObject(tools, "enabled") returns {
2890
- * translation: true
2891
- * rotation: false
2892
- * }
5491
+ * Given a PerseusCategorizerWidgetOptions object, return a new object with only
5492
+ * the public options that should be exposed to the client.
2893
5493
  */
2894
- const pluck = function pluck(table, subKey) {
2895
- return _.object(_.map(table, function (value, key) {
2896
- return [key, value[subKey]];
2897
- }));
2898
- };
5494
+ function getCategorizerPublicWidgetOptions(options) {
5495
+ return {
5496
+ items: options.items,
5497
+ categories: options.categories,
5498
+ randomizeItems: options.randomizeItems,
5499
+ static: options.static
5500
+ };
5501
+ }
5502
+
5503
+ function getCSProgramPublicWidgetOptions(options) {
5504
+ return options;
5505
+ }
2899
5506
 
2900
5507
  /**
2901
- * Maps an object to an object
2902
- *
2903
- * > mapObject({a: '1', b: '2'}, (value, key) => {
2904
- * return value + 1;
2905
- * });
2906
- * {a: 2, b: 3}
5508
+ * For details on the individual options, see the
5509
+ * PerseusExpressionWidgetOptions type
2907
5510
  */
2908
- const mapObject = function mapObject(obj, lambda) {
2909
- const result = {};
2910
- Object.keys(obj).forEach(key => {
2911
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
2912
- result[key] = lambda(obj[key], key);
5511
+
5512
+ /**
5513
+ * Given a PerseusExpressionWidgetOptions object, return a new object with only
5514
+ * the public options that should be exposed to the client.
5515
+ */
5516
+ function getExpressionPublicWidgetOptions(options) {
5517
+ return {
5518
+ buttonSets: options.buttonSets,
5519
+ functions: options.functions,
5520
+ times: options.times,
5521
+ visibleLabel: options.visibleLabel,
5522
+ ariaLabel: options.ariaLabel,
5523
+ buttonsVisible: options.buttonsVisible
5524
+ };
5525
+ }
5526
+
5527
+ const _excluded$2 = ["answers"];
5528
+ /**
5529
+ * For details on the individual options, see the
5530
+ * PerseusLabelImageWidgetOptions type
5531
+ */
5532
+
5533
+ function getLabelImagePublicWidgetOptions(options) {
5534
+ return _extends({}, options, {
5535
+ markers: options.markers.map(getLabelImageMarkerPublicData)
2913
5536
  });
2914
- return result;
2915
- };
5537
+ }
5538
+ function getLabelImageMarkerPublicData(marker) {
5539
+ const publicData = _objectWithoutPropertiesLoose(marker, _excluded$2);
5540
+ return publicData;
5541
+ }
5542
+
5543
+ /**
5544
+ * For details on the individual options, see the
5545
+ * PerseusSorterWidgetOptions type
5546
+ */
5547
+
5548
+ /**
5549
+ * Given a PerseusSorterWidgetOptions object, return a new object with only
5550
+ * the public options that should be exposed to the client.
5551
+ */
5552
+ function getSorterPublicWidgetOptions(options) {
5553
+ return {
5554
+ // Note(Tamara): This does not provide correct answer information any longer.
5555
+ // To maintain compatibility with the original widget options, we are
5556
+ // keeping the key the same. Represents initial state of the cards here.
5557
+ correct: options.correct.slice().sort(),
5558
+ padding: options.padding,
5559
+ layout: options.layout
5560
+ };
5561
+ }
5562
+
5563
+ /**
5564
+ * For details on the individual options, see the
5565
+ * PerseusDropdownWidgetOptions type
5566
+ */
5567
+
5568
+ /**
5569
+ * Given a PerseusDropdownWidgetOptions object, return a new object with only
5570
+ * the public options that should be exposed to the client.
5571
+ */
5572
+ function getDropdownPublicWidgetOptions(options) {
5573
+ return {
5574
+ choices: options.choices.map(choice => ({
5575
+ content: choice.content
5576
+ })),
5577
+ placeholder: options.placeholder,
5578
+ static: options.static,
5579
+ visibleLabel: options.visibleLabel,
5580
+ ariaLabel: options.ariaLabel
5581
+ };
5582
+ }
5583
+
5584
+ const _excluded$1 = ["answers"];
5585
+ /**
5586
+ * For details on the individual options, see the
5587
+ * PerseusNumericInputWidgetOptions type
5588
+ */
5589
+
5590
+ /**
5591
+ * Given a PerseusNumericInputWidgetOptions object, return a new object with only
5592
+ * the public options that should be exposed to the client.
5593
+ */
5594
+ function getNumericInputPublicWidgetOptions(options) {
5595
+ const publicWidgetOptions = _objectWithoutPropertiesLoose(options, _excluded$1);
5596
+ return publicWidgetOptions;
5597
+ }
5598
+
5599
+ const _excluded = ["correctX", "correctRel"];
5600
+ function getNumberLinePublicWidgetOptions(options) {
5601
+ const publicOptions = _objectWithoutPropertiesLoose(options, _excluded);
5602
+ return publicOptions;
5603
+ }
2916
5604
 
2917
- export { Errors, grapherUtil as GrapherUtil, ItemExtras, PerseusError, PerseusExpressionAnswerFormConsidered, addLibraryVersionToPerseusDebug, approximateDeepEqual, approximateEqual, deepClone, getDecimalSeparator, getMatrixSize, libVersion, lockedFigureColorNames, lockedFigureColors, lockedFigureFillStyles, mapObject, plotterPlotTypes, pluck };
5605
+ export { coreWidgetRegistry as CoreWidgetRegistry, Errors, grapherUtil as GrapherUtil, ItemExtras, PerseusError, PerseusExpressionAnswerFormConsidered, addLibraryVersionToPerseusDebug, addWidget, approximateDeepEqual, approximateEqual, categorizerWidgetLogic as categorizerLogic, csProgramWidgetLogic as csProgramLogic, deepClone, definitionWidgetLogic as definitionLogic, dropdownWidgetLogic as dropdownLogic, explanationWidgetLogic as explanationLogic, expressionWidgetLogic as expressionLogic, getCSProgramPublicWidgetOptions, getCategorizerPublicWidgetOptions, getDecimalSeparator, getDropdownPublicWidgetOptions, getExpressionPublicWidgetOptions, getLabelImagePublicWidgetOptions, getMatrixSize, getNumberLinePublicWidgetOptions, getNumericInputPublicWidgetOptions, getOrdererPublicWidgetOptions, getSorterPublicWidgetOptions, 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, sorterWidgetLogic as sorterLogic, tableWidgetLogic as tableLogic, upgradeWidgetInfoToLatestVersion, videoWidgetLogic as videoLogic };
2918
5606
  //# sourceMappingURL=index.js.map