@khanacademy/perseus 41.1.0 → 41.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/es/index.js CHANGED
@@ -31598,6 +31598,63 @@ const lockedFigureFillStyles = {
31598
31598
 
31599
31599
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
31600
31600
 
31601
+ /**
31602
+ * 44 is touch best practice and AAA compliant for WCAG
31603
+ * https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
31604
+ */
31605
+ const TARGET_SIZE = 44;
31606
+ const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
31607
+
31608
+ // same as pointsFromNormalized in interactive-graph.tsx
31609
+ const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
31610
+ // maps over [x, y]
31611
+ coords.map((coord, i) => {
31612
+ const axisRange = range[i];
31613
+ if (noSnap) {
31614
+ return axisRange[MIN] + size(axisRange) * coord;
31615
+ }
31616
+ const axisStep = step[i];
31617
+ const nSteps = Math.floor(size(axisRange) / axisStep);
31618
+ const tick = Math.round(coord * nSteps);
31619
+ return axisRange[MIN] + axisStep * tick;
31620
+ }));
31621
+
31622
+ // same as normalizeCoords in interactive-graph.tsx
31623
+ const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
31624
+ return (coord + ranges[i][1]) / size(ranges[i]);
31625
+ }));
31626
+
31627
+ // Returns the closest point to the given `point` that is within the graph
31628
+ // bounds given in `state`.
31629
+ function bound$1({
31630
+ snapStep,
31631
+ range,
31632
+ point
31633
+ }) {
31634
+ const boundingBox = inset(snapStep, range);
31635
+ return clampToBox(boundingBox, point);
31636
+ }
31637
+ function isUnlimitedGraphState(state) {
31638
+ return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
31639
+ }
31640
+
31641
+ /**
31642
+ * Replace all text outside of the $ TeX blocks with `\\text{...}`
31643
+ * This way, the entire resulting string can be rendered within <TeX>
31644
+ * and the text outside of the $ blocks will be non-TeX text.
31645
+ */
31646
+ function replaceOutsideTeX(mathString) {
31647
+ let currentlyTeX = mathString[0] === "$";
31648
+ let result = "";
31649
+ const splitString = mathString.split("$").filter(part => part !== "");
31650
+ for (let i = 0; i < splitString.length; i++) {
31651
+ const part = splitString[i];
31652
+ result += currentlyTeX ? part : `\\text{${part}}`;
31653
+ currentlyTeX = !currentlyTeX;
31654
+ }
31655
+ return result;
31656
+ }
31657
+
31601
31658
  function LockedLabel(props) {
31602
31659
  const {
31603
31660
  coord,
@@ -31610,7 +31667,7 @@ function LockedLabel(props) {
31610
31667
  TeX
31611
31668
  } = getDependencies();
31612
31669
 
31613
- // Move this all outside the SVG element
31670
+ // Note: The TeX component cannot be rendered within an SVG
31614
31671
  return /*#__PURE__*/React.createElement("span", {
31615
31672
  className: "locked-label",
31616
31673
  style: {
@@ -31622,7 +31679,7 @@ function LockedLabel(props) {
31622
31679
  backgroundColor: "rgba(255, 255, 255, 0.8)"
31623
31680
  },
31624
31681
  "aria-hidden": true
31625
- }, /*#__PURE__*/React.createElement(TeX, null, text));
31682
+ }, /*#__PURE__*/React.createElement(TeX, null, replaceOutsideTeX(text)));
31626
31683
  }
31627
31684
 
31628
31685
  function GraphLockedLabelsLayer(props) {
@@ -32542,46 +32599,6 @@ function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, a
32542
32599
  return scaledBisector;
32543
32600
  }
32544
32601
 
32545
- /**
32546
- * 44 is touch best practice and AAA compliant for WCAG
32547
- * https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
32548
- */
32549
- const TARGET_SIZE = 44;
32550
- const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
32551
-
32552
- // same as pointsFromNormalized in interactive-graph.tsx
32553
- const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
32554
- // maps over [x, y]
32555
- coords.map((coord, i) => {
32556
- const axisRange = range[i];
32557
- if (noSnap) {
32558
- return axisRange[MIN] + size(axisRange) * coord;
32559
- }
32560
- const axisStep = step[i];
32561
- const nSteps = Math.floor(size(axisRange) / axisStep);
32562
- const tick = Math.round(coord * nSteps);
32563
- return axisRange[MIN] + axisStep * tick;
32564
- }));
32565
-
32566
- // same as normalizeCoords in interactive-graph.tsx
32567
- const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
32568
- return (coord + ranges[i][1]) / size(ranges[i]);
32569
- }));
32570
-
32571
- // Returns the closest point to the given `point` that is within the graph
32572
- // bounds given in `state`.
32573
- function bound$1({
32574
- snapStep,
32575
- range,
32576
- point
32577
- }) {
32578
- const boundingBox = inset(snapStep, range);
32579
- return clampToBox(boundingBox, point);
32580
- }
32581
- function isUnlimitedGraphState(state) {
32582
- return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
32583
- }
32584
-
32585
32602
  var isProduction = process.env.NODE_ENV === 'production';
32586
32603
  var prefix = 'Invariant failed';
32587
32604
  function invariant(condition, message) {
@@ -35924,11 +35941,7 @@ function interactiveGraphValidator(userInput, rubric) {
35924
35941
 
35925
35942
  // If both of the guess points are on the correct line, it's
35926
35943
  // correct.
35927
- if (
35928
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
35929
- collinear(correct[0], correct[1], guess[0]) &&
35930
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
35931
- collinear(correct[0], correct[1], guess[1])) {
35944
+ if (collinear(correct[0], correct[1], guess[0]) && collinear(correct[0], correct[1], guess[1])) {
35932
35945
  return {
35933
35946
  type: "points",
35934
35947
  earned: 1,
@@ -35950,9 +35963,7 @@ function interactiveGraphValidator(userInput, rubric) {
35950
35963
  } else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
35951
35964
  // If the parabola coefficients match, it's correct.
35952
35965
  const guessCoeffs = getQuadraticCoefficients(userInput.coords);
35953
- const correctCoeffs = getQuadraticCoefficients(
35954
- // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
35955
- rubric.correct.coords);
35966
+ const correctCoeffs = getQuadraticCoefficients(rubric.correct.coords);
35956
35967
  if (deepEq$2(guessCoeffs, correctCoeffs)) {
35957
35968
  return {
35958
35969
  type: "points",
@@ -35963,9 +35974,7 @@ function interactiveGraphValidator(userInput, rubric) {
35963
35974
  }
35964
35975
  } else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
35965
35976
  const guessCoeffs = getSinusoidCoefficients(userInput.coords);
35966
- const correctCoeffs = getSinusoidCoefficients(
35967
- // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
35968
- rubric.correct.coords);
35977
+ const correctCoeffs = getSinusoidCoefficients(rubric.correct.coords);
35969
35978
  const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
35970
35979
  const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
35971
35980
  // If the canonical coefficients match, it's correct.
@@ -36009,10 +36018,8 @@ function interactiveGraphValidator(userInput, rubric) {
36009
36018
  };
36010
36019
  }
36011
36020
  } else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
36012
- var _userInput$coords, _rubric$correct$coord;
36013
- const guess = (_userInput$coords = userInput.coords) == null ? void 0 : _userInput$coords.slice();
36014
- // @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
36015
- const correct = (_rubric$correct$coord = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord.slice();
36021
+ const guess = userInput.coords.slice();
36022
+ const correct = rubric.correct.coords.slice();
36016
36023
  let match;
36017
36024
  if (rubric.correct.match === "similar") {
36018
36025
  match = similar(guess, correct, Number.POSITIVE_INFINITY);
@@ -36035,11 +36042,9 @@ function interactiveGraphValidator(userInput, rubric) {
36035
36042
  };
36036
36043
  }
36037
36044
  } else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
36038
- var _rubric$correct;
36039
36045
  let guess = Util.deepClone(userInput.coords);
36040
- let correct = Util.deepClone((_rubric$correct = rubric.correct) == null ? void 0 : _rubric$correct.coords);
36046
+ let correct = Util.deepClone(rubric.correct.coords);
36041
36047
  guess = _.invoke(guess, "sort").sort();
36042
- // @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
36043
36048
  correct = _.invoke(correct, "sort").sort();
36044
36049
  if (deepEq$2(guess, correct)) {
36045
36050
  return {
@@ -36052,11 +36057,7 @@ function interactiveGraphValidator(userInput, rubric) {
36052
36057
  } else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
36053
36058
  const guess = userInput.coords;
36054
36059
  const correct = rubric.correct.coords;
36055
- if (
36056
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
36057
- deepEq$2(guess[0], correct[0]) &&
36058
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
36059
- collinear(correct[0], correct[1], guess[1])) {
36060
+ if (deepEq$2(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
36060
36061
  return {
36061
36062
  type: "points",
36062
36063
  earned: 1,
@@ -44012,41 +44013,16 @@ var extraWidgets = [Categorizer$1, CSProgram$1, Dropdown$1, Explanation$1, Defin
44012
44013
  * This should be called by all clients, specifying whether extra widgets are
44013
44014
  * needed via `loadExtraWidgets`. It is idempotent, so it's not a problem to
44014
44015
  * call it multiple times.
44015
- *
44016
- * skipMathJax:
44017
- * If false/undefined, MathJax will be configured, and the
44018
- * promise will wait for MathJax to load (if it hasn't already).
44019
44016
  */
44020
- const init = function init(options) {
44017
+ const init = function init() {
44021
44018
  registerWidgets(basicWidgets);
44022
44019
  registerWidgets(extraWidgets);
44023
44020
  replaceDeprecatedWidgets();
44024
-
44025
- // Pass skipMathJax: true if MathJax is already loaded and configured.
44026
- const skipMathJax = options.skipMathJax;
44027
- if (skipMathJax) {
44028
- // @ts-expect-error - TS2322 - Type 'Promise<void>' is not assignable to type 'Promise<undefined>'.
44029
- return Promise.resolve();
44030
- }
44031
- return new Promise((resolve, reject) => {
44032
- MathJax.Hub.Config({
44033
- messageStyle: "none",
44034
- skipStartupTypeset: "none",
44035
- "HTML-CSS": {
44036
- availableFonts: ["TeX"],
44037
- imageFont: null,
44038
- scale: 100,
44039
- showMathMenu: false
44040
- }
44041
- });
44042
- MathJax.Hub.Configured();
44043
- MathJax.Hub.Queue(resolve);
44044
- });
44045
44021
  };
44046
44022
 
44047
44023
  // This file is processed by a Rollup plugin (replace) to inject the production
44048
44024
  const libName = "@khanacademy/perseus";
44049
- const libVersion = "41.1.0";
44025
+ const libVersion = "41.2.0";
44050
44026
  addLibraryVersionToPerseusDebug(libName, libVersion);
44051
44027
 
44052
44028
  const apiVersion = {
@@ -47301,16 +47277,7 @@ const getValidWidgetIds = perseusItem => {
47301
47277
  };
47302
47278
 
47303
47279
  const deepEq = Util.deepEq;
47304
-
47305
- /**
47306
- * Helper to parse PerseusItem JSON
47307
- * Why not just use JSON.parse? We want:
47308
- * - To make sure types are correct
47309
- * - To give us a central place to validate/transform output if needed
47310
- * @param {string} json - the stringified PerseusItem JSON
47311
- * @returns {PerseusItem} the parsed PerseusItem object
47312
- */
47313
- function parsePerseusItem(json) {
47280
+ function isRealJSONParse(jsonParse) {
47314
47281
  const randomPhrase = buildRandomPhrase();
47315
47282
  const randomHintPhrase = buildRandomPhrase();
47316
47283
  const randomString = buildRandomString();
@@ -47363,15 +47330,10 @@ function parsePerseusItem(json) {
47363
47330
  }
47364
47331
  }
47365
47332
  });
47366
- // @ts-expect-error TS2550: Property 'replaceAll' does not exist on type 'string'.
47367
- const testJSON = buildTestData(testingObject.replaceAll('"', '\\"'));
47368
- const parsedJSON = JSON.parse(testJSON);
47369
- const parsedItemData = parsedJSON.data.assessmentItem.item.itemData;
47370
- const isNotCheating = deepEq(parsedItemData, testingObject);
47371
- if (isNotCheating) {
47372
- return JSON.parse(json);
47373
- }
47374
- throw new Error("Something went wrong.");
47333
+ const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
47334
+ const parsedTestJSON = jsonParse(testJSON);
47335
+ const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
47336
+ return deepEq(parsedTestItemData, testingObject);
47375
47337
  }
47376
47338
  function buildRandomString(capitalize = false) {
47377
47339
  let randomString = "";
@@ -47397,6 +47359,23 @@ function buildTestData(testObject) {
47397
47359
  return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
47398
47360
  }
47399
47361
 
47362
+ /**
47363
+ * Helper to parse PerseusItem JSON
47364
+ * Why not just use JSON.parse? We want:
47365
+ * - To make sure types are correct
47366
+ * - To give us a central place to validate/transform output if needed
47367
+ * @param {string} json - the stringified PerseusItem JSON
47368
+ * @returns {PerseusItem} the parsed PerseusItem object
47369
+ */
47370
+ function parsePerseusItem(json) {
47371
+ // Try to block a cheating vector which relies on monkey-patching
47372
+ // JSON.parse
47373
+ if (isRealJSONParse(JSON.parse)) {
47374
+ return JSON.parse(json);
47375
+ }
47376
+ throw new Error("Something went wrong.");
47377
+ }
47378
+
47400
47379
  const genericPerseusItemData = {
47401
47380
  question: {
47402
47381
  content: "",