@khanacademy/perseus-core 5.4.0 → 5.4.2

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.
@@ -766,10 +766,18 @@ export type PerseusMeasurerWidgetOptions = {
766
766
  };
767
767
  export type MathFormat = "integer" | "mixed" | "improper" | "proper" | "decimal" | "percent" | "pi";
768
768
  export type PerseusNumericInputAnswerForm = {
769
- simplify: PerseusNumericInputSimplify | null | undefined;
769
+ simplify: PerseusNumericInputSimplify;
770
770
  name: MathFormat;
771
771
  };
772
- export type PerseusNumericInputSimplify = "required" | "correct" | "enforced" | "optional";
772
+ /**
773
+ * Determines how unsimplified fractions are scored.
774
+ *
775
+ * - "required" means unsimplified fractions are considered invalid input, and
776
+ * the learner can try again.
777
+ * - "enforced" means unsimplified fractions are marked incorrect.
778
+ * - "optional" means unsimplified fractions are accepted.
779
+ */
780
+ export type PerseusNumericInputSimplify = "required" | "enforced" | "optional";
773
781
  export type PerseusNumericInputWidgetOptions = {
774
782
  answers: ReadonlyArray<PerseusNumericInputAnswer>;
775
783
  labelText?: string | undefined;
@@ -785,7 +793,7 @@ export type PerseusNumericInputAnswer = {
785
793
  answerForms?: ReadonlyArray<MathFormat>;
786
794
  strict: boolean;
787
795
  maxError: number | null | undefined;
788
- simplify: PerseusNumericInputSimplify | null | undefined;
796
+ simplify: PerseusNumericInputSimplify;
789
797
  };
790
798
  export type PerseusNumberLineWidgetOptions = {
791
799
  range: ReadonlyArray<number>;
package/dist/es/index.js CHANGED
@@ -2,7 +2,7 @@ import _ from 'underscore';
2
2
  import _extends from '@babel/runtime/helpers/extends';
3
3
  import * as KAS from '@khanacademy/kas';
4
4
  import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
5
- import { seededRNG as seededRNG$1, shuffle as shuffle$1 } from '@khanacademy/perseus-core';
5
+ import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-utils';
6
6
 
7
7
  function getMatrixSize(matrix) {
8
8
  const matrixSize = [1, 1];
@@ -1959,6 +1959,15 @@ const lockedFigureFillStyles = {
1959
1959
 
1960
1960
  // Not associated with a specific figure
1961
1961
 
1962
+ /**
1963
+ * Determines how unsimplified fractions are scored.
1964
+ *
1965
+ * - "required" means unsimplified fractions are considered invalid input, and
1966
+ * the learner can try again.
1967
+ * - "enforced" means unsimplified fractions are marked incorrect.
1968
+ * - "optional" means unsimplified fractions are accepted.
1969
+ */
1970
+
1962
1971
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
1963
1972
 
1964
1973
  // Used to represent 2-D points and ranges
@@ -2243,7 +2252,21 @@ const parseNumberLineWidget = parseWidget(constant("number-line"), object({
2243
2252
  }));
2244
2253
 
2245
2254
  const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
2246
- const parseSimplify = enumeration("required", "correct", "enforced", "optional");
2255
+ const parseSimplify = pipeParsers(union(constant(null)).or(constant(undefined)).or(boolean).or(constant("required")).or(constant("correct")).or(constant("enforced")).or(constant("optional")).or(constant("accepted")).parser).then(convert(deprecatedSimplifyValuesToRequired)).parser;
2256
+ function deprecatedSimplifyValuesToRequired(simplify) {
2257
+ switch (simplify) {
2258
+ case "enforced":
2259
+ case "required":
2260
+ case "optional":
2261
+ return simplify;
2262
+ // NOTE(benchristel): "accepted", "correct", true, false, undefined, and
2263
+ // null are all treated the same as "required" during scoring, so we
2264
+ // convert them to "required" here to preserve behavior. See the tests
2265
+ // in score-numeric-input.test.ts
2266
+ default:
2267
+ return "required";
2268
+ }
2269
+ }
2247
2270
  const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
2248
2271
  answers: array(object({
2249
2272
  message: string,
@@ -2258,12 +2281,7 @@ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
2258
2281
  // TODO(benchristel): simplify should never be a boolean, but we
2259
2282
  // have some content where it is anyway. If we ever backfill
2260
2283
  // the data, we should simplify `simplify`.
2261
- simplify: optional(nullable(union(parseSimplify).or(pipeParsers(boolean).then(convert(value => {
2262
- if (typeof value === "boolean") {
2263
- return value ? "required" : "optional";
2264
- }
2265
- return value;
2266
- })).parser).parser))
2284
+ simplify: parseSimplify
2267
2285
  })),
2268
2286
  labelText: optional(string),
2269
2287
  size: string,
@@ -2272,7 +2290,7 @@ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
2272
2290
  static: defaulted(boolean, () => false),
2273
2291
  answerForms: optional(array(object({
2274
2292
  name: parseMathFormat,
2275
- simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
2293
+ simplify: parseSimplify
2276
2294
  })))
2277
2295
  }));
2278
2296
 
@@ -2720,10 +2738,10 @@ const parsePerseusItem$1 = object({
2720
2738
  question: parsePerseusRenderer,
2721
2739
  hints: defaulted(array(parseHint), () => []),
2722
2740
  answerArea: parsePerseusAnswerArea,
2723
- itemDataVersion: optional(object({
2741
+ itemDataVersion: optional(nullable(object({
2724
2742
  major: number,
2725
2743
  minor: number
2726
- })),
2744
+ }))),
2727
2745
  // Deprecated field
2728
2746
  answer: any
2729
2747
  });
@@ -2800,51 +2818,9 @@ function throwErrorIfCheatingDetected() {
2800
2818
  }
2801
2819
  }
2802
2820
 
2803
- /**
2804
- * Adds the given perseus library version information to the __perseus_debug__
2805
- * object and ensures that the object is attached to `globalThis` (`window` in
2806
- * browser environments).
2807
- *
2808
- * This allows each library to provide runtime version information to assist in
2809
- * debugging in production environments.
2810
- */
2811
- const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
2812
- // If the library version is the default value, then we don't want to
2813
- // prefix it with a "v" to indicate that it is a version number.
2814
- let prefix = "v";
2815
- if (libraryVersion === "__lib_version__") {
2816
- prefix = "";
2817
- }
2818
- const formattedVersion = `${prefix}${libraryVersion}`;
2819
- if (typeof globalThis !== "undefined") {
2820
- var _globalThis$__perseus;
2821
- globalThis.__perseus_debug__ = (_globalThis$__perseus = globalThis.__perseus_debug__) != null ? _globalThis$__perseus : {};
2822
- const existingVersionEntry = globalThis.__perseus_debug__[libraryName];
2823
- if (existingVersionEntry) {
2824
- // If we already have an entry and it doesn't match the registered
2825
- // version, we morph the entry into an array and log a warning.
2826
- if (existingVersionEntry !== formattedVersion) {
2827
- // Existing entry might be an array already (oops, at least 2
2828
- // versions of the library already loaded!).
2829
- const allVersions = Array.isArray(existingVersionEntry) ? existingVersionEntry : [existingVersionEntry];
2830
- allVersions.push(formattedVersion);
2831
- globalThis.__perseus_debug__[libraryName] = allVersions;
2832
-
2833
- // eslint-disable-next-line no-console
2834
- console.warn(`Multiple versions of ${libraryName} loaded on this page: ${allVersions.sort().join(", ")}`);
2835
- }
2836
- } else {
2837
- globalThis.__perseus_debug__[libraryName] = formattedVersion;
2838
- }
2839
- } else {
2840
- // eslint-disable-next-line no-console
2841
- console.warn(`globalThis not found found (${formattedVersion})`);
2842
- }
2843
- };
2844
-
2845
2821
  // This file is processed by a Rollup plugin (replace) to inject the production
2846
2822
  const libName = "@khanacademy/perseus-core";
2847
- const libVersion = "5.4.0";
2823
+ const libVersion = "5.4.2";
2848
2824
  addLibraryVersionToPerseusDebug(libName, libVersion);
2849
2825
 
2850
2826
  /**
@@ -3321,20 +3297,73 @@ const labelImageWidgetLogic = {
3321
3297
  getPublicWidgetOptions: getLabelImagePublicWidgetOptions
3322
3298
  };
3323
3299
 
3300
+ /* Note(tamara): Brought over from the perseus package packages/perseus/src/util.ts file.
3301
+ May be useful to bring other perseus package utilities here. Contains utility functions
3302
+ and types used across multiple widgets for randomization and shuffling. */
3303
+ const seededRNG = function seededRNG(seed) {
3304
+ let randomSeed = seed;
3305
+ return function () {
3306
+ // Robert Jenkins' 32 bit integer hash function.
3307
+ let seed = randomSeed;
3308
+ seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
3309
+ seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
3310
+ seed = seed + 0x165667b1 + (seed << 5) & 0xffffffff;
3311
+ seed = (seed + 0xd3a2646c ^ seed << 9) & 0xffffffff;
3312
+ seed = seed + 0xfd7046c5 + (seed << 3) & 0xffffffff;
3313
+ seed = (seed ^ 0xb55a4f09 ^ seed >>> 16) & 0xffffffff;
3314
+ return (randomSeed = seed & 0xfffffff) / 0x10000000;
3315
+ };
3316
+ };
3317
+
3318
+ // Shuffle an array using a given random seed or function.
3319
+ // If `ensurePermuted` is true, the input and output are guaranteed to be
3320
+ // distinct permutations.
3321
+ function shuffle(array, randomSeed, ensurePermuted = false) {
3322
+ // Always return a copy of the input array
3323
+ const shuffled = _.clone(array);
3324
+
3325
+ // Handle edge cases (input array is empty or uniform)
3326
+ if (!shuffled.length || _.all(shuffled, function (value) {
3327
+ return _.isEqual(value, shuffled[0]);
3328
+ })) {
3329
+ return shuffled;
3330
+ }
3331
+ let random;
3332
+ if (typeof randomSeed === "function") {
3333
+ random = randomSeed;
3334
+ } else {
3335
+ random = seededRNG(randomSeed);
3336
+ }
3337
+ do {
3338
+ // Fischer-Yates shuffle
3339
+ for (let top = shuffled.length; top > 0; top--) {
3340
+ const newEnd = Math.floor(random() * top);
3341
+ const temp = shuffled[newEnd];
3342
+
3343
+ // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
3344
+ shuffled[newEnd] = shuffled[top - 1];
3345
+ // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
3346
+ shuffled[top - 1] = temp;
3347
+ }
3348
+ } while (ensurePermuted && _.isEqual(array, shuffled));
3349
+ return shuffled;
3350
+ }
3351
+ const random = seededRNG(new Date().getTime() & 0xffffffff);
3352
+
3324
3353
  // TODO(LEMS-2841): Should be able to remove once getPublicWidgetOptions is hooked up
3325
3354
 
3326
3355
  // TODO(LEMS-2841): Should be able to remove once getPublicWidgetOptions is hooked up
3327
3356
  const shuffleMatcher = props => {
3328
3357
  // Use the same random() function to shuffle both columns sequentially
3329
- const rng = seededRNG$1(props.problemNum);
3358
+ const rng = seededRNG(props.problemNum);
3330
3359
  let left;
3331
3360
  if (!props.orderMatters) {
3332
3361
  // If the order doesn't matter, don't shuffle the left column
3333
3362
  left = props.left;
3334
3363
  } else {
3335
- left = shuffle$1(props.left, rng, /* ensurePermuted */true);
3364
+ left = shuffle(props.left, rng, /* ensurePermuted */true);
3336
3365
  }
3337
- const right = shuffle$1(props.right, rng, /* ensurePermuted */true);
3366
+ const right = shuffle(props.right, rng, /* ensurePermuted */true);
3338
3367
  return {
3339
3368
  left,
3340
3369
  right
@@ -3349,9 +3378,9 @@ function shuffleMatcherWithRandom(data) {
3349
3378
  // If the order doesn't matter, don't shuffle the left column
3350
3379
  left = data.left;
3351
3380
  } else {
3352
- left = shuffle$1(data.left, Math.random, /* ensurePermuted */true);
3381
+ left = shuffle(data.left, Math.random, /* ensurePermuted */true);
3353
3382
  }
3354
- const right = shuffle$1(data.right, Math.random, /* ensurePermuted */true);
3383
+ const right = shuffle(data.right, Math.random, /* ensurePermuted */true);
3355
3384
  return {
3356
3385
  left,
3357
3386
  right
@@ -3732,7 +3761,7 @@ const radioWidgetLogic = {
3732
3761
  * the public options that should be exposed to the client.
3733
3762
  */
3734
3763
  function getSorterPublicWidgetOptions(options) {
3735
- const shuffledCorrect = shuffle$1(options.correct, Math.random, /* ensurePermuted */true);
3764
+ const shuffledCorrect = shuffle(options.correct, Math.random, /* ensurePermuted */true);
3736
3765
  return _extends({}, options, {
3737
3766
  // Note(Tamara): This does not provide correct answer information any longer.
3738
3767
  // To maintain compatibility with the original widget options, we are
@@ -4025,8 +4054,9 @@ function getUpgradedWidgetOptions(oldWidgetOptions) {
4025
4054
  }
4026
4055
 
4027
4056
  /**
4028
- * Upgrades widget options and removes answerful data for all the widgets in a
4029
- * Perseus item.
4057
+ * Return a copy of a Perseus item with rubric data removed (ie answers)
4058
+ *
4059
+ * @param originalItem - the original, full Perseus item (which includes the rubric - aka answer data)
4030
4060
  */
4031
4061
  function splitPerseusItem(originalItem) {
4032
4062
  var _item$widgets;
@@ -4045,58 +4075,5 @@ function splitPerseusItem(originalItem) {
4045
4075
  });
4046
4076
  }
4047
4077
 
4048
- /* Note(tamara): Brought over from the perseus package packages/perseus/src/util.ts file.
4049
- May be useful to bring other perseus package utilities here. Contains utility functions
4050
- and types used across multiple widgets for randomization and shuffling. */
4051
- const seededRNG = function seededRNG(seed) {
4052
- let randomSeed = seed;
4053
- return function () {
4054
- // Robert Jenkins' 32 bit integer hash function.
4055
- let seed = randomSeed;
4056
- seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
4057
- seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
4058
- seed = seed + 0x165667b1 + (seed << 5) & 0xffffffff;
4059
- seed = (seed + 0xd3a2646c ^ seed << 9) & 0xffffffff;
4060
- seed = seed + 0xfd7046c5 + (seed << 3) & 0xffffffff;
4061
- seed = (seed ^ 0xb55a4f09 ^ seed >>> 16) & 0xffffffff;
4062
- return (randomSeed = seed & 0xfffffff) / 0x10000000;
4063
- };
4064
- };
4065
-
4066
- // Shuffle an array using a given random seed or function.
4067
- // If `ensurePermuted` is true, the input and output are guaranteed to be
4068
- // distinct permutations.
4069
- function shuffle(array, randomSeed, ensurePermuted = false) {
4070
- // Always return a copy of the input array
4071
- const shuffled = _.clone(array);
4072
-
4073
- // Handle edge cases (input array is empty or uniform)
4074
- if (!shuffled.length || _.all(shuffled, function (value) {
4075
- return _.isEqual(value, shuffled[0]);
4076
- })) {
4077
- return shuffled;
4078
- }
4079
- let random;
4080
- if (typeof randomSeed === "function") {
4081
- random = randomSeed;
4082
- } else {
4083
- random = seededRNG(randomSeed);
4084
- }
4085
- do {
4086
- // Fischer-Yates shuffle
4087
- for (let top = shuffled.length; top > 0; top--) {
4088
- const newEnd = Math.floor(random() * top);
4089
- const temp = shuffled[newEnd];
4090
-
4091
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
4092
- shuffled[newEnd] = shuffled[top - 1];
4093
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
4094
- shuffled[top - 1] = temp;
4095
- }
4096
- } while (ensurePermuted && _.isEqual(array, shuffled));
4097
- return shuffled;
4098
- }
4099
- const random = seededRNG(new Date().getTime() & 0xffffffff);
4100
-
4101
4078
  export { coreWidgetRegistry as CoreWidgetRegistry, Errors, grapherUtil as GrapherUtil, ItemExtras, PerseusError, PerseusExpressionAnswerFormConsidered, addWidget, approximateDeepEqual, approximateEqual, categorizerWidgetLogic as categorizerLogic, csProgramWidgetLogic as csProgramLogic, deepClone, definitionWidgetLogic as definitionLogic, deriveExtraKeys, deriveNumCorrect, dropdownWidgetLogic as dropdownLogic, explanationWidgetLogic as explanationLogic, expressionWidgetLogic as expressionLogic, getCSProgramPublicWidgetOptions, getCategorizerPublicWidgetOptions, getDecimalSeparator, getDropdownPublicWidgetOptions, getExpressionPublicWidgetOptions, getGrapherPublicWidgetOptions, getIFramePublicWidgetOptions, getInteractiveGraphPublicWidgetOptions, getLabelImagePublicWidgetOptions, getMatcherPublicWidgetOptions, getMatrixPublicWidgetOptions, getMatrixSize, getNumberLinePublicWidgetOptions, getNumericInputPublicWidgetOptions, getOrdererPublicWidgetOptions, getPlotterPublicWidgetOptions, getRadioPublicWidgetOptions, getSorterPublicWidgetOptions, getTablePublicWidgetOptions, getUpgradedWidgetOptions, getWidgetIdsFromContent, getWidgetIdsFromContentByType, gradedGroupWidgetLogic as gradedGroupLogic, gradedGroupSetWidgetLogic as gradedGroupSetLogic, grapherWidgetLogic as grapherLogic, groupWidgetLogic as groupLogic, iframeWidgetLogic as iframeLogic, imageWidgetLogic as imageLogic, inputNumberWidgetLogic as inputNumberLogic, interactionWidgetLogic as interactionLogic, interactiveGraphWidgetLogic as interactiveGraphLogic, isFailure, isSuccess, labelImageWidgetLogic as labelImageLogic, libVersion, lockedFigureColorNames, lockedFigureColors, lockedFigureFillStyles, mapObject, matcherWidgetLogic as matcherLogic, matrixWidgetLogic as matrixLogic, measurerWidgetLogic as measurerLogic, numberLineWidgetLogic as numberLineLogic, numericInputWidgetLogic as numericInputLogic, ordererWidgetLogic as ordererLogic, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parsePerseusItem, passageWidgetLogic as passageLogic, passageRefWidgetLogic as passageRefLogic, passageRefTargetWidgetLogic as passageRefTargetLogic, phetSimulationWidgetLogic as phetSimulationLogic, plotterWidgetLogic as plotterLogic, plotterPlotTypes, pluck, pythonProgramWidgetLogic as pythonProgramLogic, radioWidgetLogic as radioLogic, random, seededRNG, shuffle, shuffleMatcher, sorterWidgetLogic as sorterLogic, splitPerseusItem, tableWidgetLogic as tableLogic, upgradeWidgetInfoToLatestVersion, usesNumCorrect, videoWidgetLogic as videoLogic };
4102
4079
  //# sourceMappingURL=index.js.map