@khanacademy/perseus 42.0.0 → 42.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -58,6 +58,7 @@ export { isItemRenderableByVersion } from "./renderability";
58
58
  export { violatingWidgets } from "./a11y";
59
59
  export { interactiveSizes } from "./styles/constants";
60
60
  export { displaySigFigs } from "./sigfigs";
61
+ export { conversionRequired, convertDeprecatedWidgets, } from "./util/deprecated-widgets/modernize-widgets-utils";
61
62
  export { PerseusI18nContextProvider, PerseusI18nContext, usePerseusI18n, } from "./components/i18n-context";
62
63
  /**
63
64
  * Util
package/dist/index.js CHANGED
@@ -18875,6 +18875,194 @@ class QuestionParagraph extends React__namespace.Component {
18875
18875
  }
18876
18876
  }
18877
18877
 
18878
+ // Methods to convert input-number widgets to numeric-input widgets
18879
+
18880
+ const inputNumberToNumericInput = json => {
18881
+ // First we need to create a map of the old input-number keys to the new numeric-input keys
18882
+ // so that we can ensure we update the content and widgets accordingly
18883
+ const renameMap = getInputNumberRenameMap(json);
18884
+
18885
+ // Then we can use this to update the JSON
18886
+ return convertInputNumberJson(json, renameMap);
18887
+ };
18888
+
18889
+ // We need to be able to run this code recursively in order to convert nested input-number widgets
18890
+ const convertInputNumberJson = (json, renameMap) => {
18891
+ const updatedContent = convertDeprecatedWidgetsInContent(json, renameMap);
18892
+ const updatedWidgets = convertInputNumberWidgetOptions(json, renameMap);
18893
+ const modernizedJson = {
18894
+ ...json,
18895
+ content: updatedContent,
18896
+ widgets: updatedWidgets
18897
+ };
18898
+ return modernizedJson;
18899
+ };
18900
+
18901
+ // Convert the input-number json in the widgets of a PerseusRenderer object
18902
+ const convertInputNumberWidgetOptions = (json, renameMap) => {
18903
+ const widgets = {
18904
+ ...json.widgets
18905
+ };
18906
+ // The question.widgets is a dictionary map of widgets, so we need to loop through in order to convert input-number to numeric-input
18907
+ // which can exist as both the key or as a value on widget.type.
18908
+ for (const key of Object.keys(widgets)) {
18909
+ // Loop through the keys of the widgets dictionary
18910
+ if (widgets[key].options.widgets) {
18911
+ widgets[key].options = {
18912
+ ...inputNumberToNumericInput(widgets[key].options)
18913
+ };
18914
+ }
18915
+ // Check if the widget is an input-number
18916
+ if (widgets[key].type === "input-number") {
18917
+ // If the answerType is not number or percent, we need to provide
18918
+ // the answer form for the numeric-input widget
18919
+ const provideAnswerForm = widgets[key].options.answerType !== "number" && widgets[key].options.answerType !== "percent";
18920
+ // We need to determine the mathFormat for the numeric-input widget
18921
+ const mathFormat = widgets[key].options.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
18922
+ : widgets[key].options.answerType; // Otherwise, we can use the answerType directly
18923
+
18924
+ // We need to update the answers prop to match the numeric-input widget format
18925
+ const answers = [{
18926
+ value: widgets[key].options.value,
18927
+ simplify: widgets[key].options.simplify,
18928
+ // Input Number doesn't have a strict prop, so we default to false
18929
+ strict: false,
18930
+ // We only want to set maxError if the inexact prop is true
18931
+ maxError: widgets[key].options.inexact ? widgets[key].options.maxError : 0,
18932
+ status: "correct",
18933
+ // Input-number only allows correct answers
18934
+ message: ""
18935
+ }];
18936
+
18937
+ // Add the required answerForms if provided/applicable
18938
+ if (provideAnswerForm) {
18939
+ answers[0].answerForms = [mathFormat];
18940
+ }
18941
+
18942
+ // Update the options prop to match the numeric-input widget format
18943
+ const upgradedWidgetOptions = {
18944
+ answers,
18945
+ size: widgets[key].options.size,
18946
+ coefficient: false,
18947
+ // input-number doesn't have a coefficient prop
18948
+ labelText: "",
18949
+ // input-number doesn't have a labelText prop
18950
+ static: false,
18951
+ // static is always false for numeric-input
18952
+ rightAlign: widgets[key].options.rightAlign || false
18953
+ };
18954
+ const upgradedWidget = {
18955
+ options: upgradedWidgetOptions,
18956
+ type: "numeric-input"
18957
+ };
18958
+ const newWidgetName = renameMap[key];
18959
+ // Create the new key entry
18960
+ widgets[newWidgetName] = upgradedWidget;
18961
+
18962
+ // We need to delete the old widget key
18963
+ delete widgets[key];
18964
+ }
18965
+ }
18966
+ return widgets;
18967
+ };
18968
+
18969
+ // Convert the deprecated widget refs in the content string
18970
+ // of a PerseusRenderer object to their renamed equivalents
18971
+ const convertDeprecatedWidgetsInContent = (json, renameMap) => {
18972
+ return Object.keys(renameMap).reduce((newContent, oldKey) => {
18973
+ const newKey = renameMap[oldKey];
18974
+ return newKey ? newContent.replace(oldKey, newKey) : newContent;
18975
+ }, json.content);
18976
+ };
18977
+
18978
+ // Create a map of the old input-number keys to the new numeric-input keys
18979
+ const getInputNumberRenameMap = json => {
18980
+ const numericRegex = /(?<=\[\[\u2603 )(numeric-input \d+)(?=\]\])/g;
18981
+ const inputNumberRegex = /(?<=\[\[\u2603 )(input-number \d+)(?=\]\])/g;
18982
+
18983
+ // Get all the content strings within the json, which might be nested within widgets
18984
+ const allContentStrings = json.content;
18985
+
18986
+ // Loop through the content strings to get all the input-number widgets
18987
+ const renameMap = {};
18988
+ const inputNumberMatches = [...(allContentStrings.match(inputNumberRegex) || [])];
18989
+
18990
+ // We want to count any pre-existing numeric-input widgets
18991
+ // so that we can start the new ids at the next number
18992
+ const numericMatches = [...(allContentStrings.match(numericRegex) || [])];
18993
+ let currentNumericCount = numericMatches.reduce((count, match) => {
18994
+ const id = parseInt(match.split(" ")[1], 10);
18995
+ return id >= count ? id + 1 : count; // We want to start at the next highest number
18996
+ }, 1);
18997
+
18998
+ // Now that we have all the necessary information, we can create the renameMap
18999
+ for (const match of inputNumberMatches) {
19000
+ const oldKey = match;
19001
+ const newKey = `numeric-input ${currentNumericCount}`;
19002
+ renameMap[oldKey] = newKey;
19003
+ currentNumericCount++;
19004
+ }
19005
+ return renameMap;
19006
+ };
19007
+
19008
+ // Convert the user input data keys from input-number to numeric-input
19009
+ const convertUserInputNumberData = userInputMap => {
19010
+ const updatedUserInputMap = {
19011
+ ...userInputMap
19012
+ };
19013
+ for (const key of Object.keys(userInputMap)) {
19014
+ if (key.includes("input-number")) {
19015
+ const updatedKey = key.replace("input-number", "numeric-input");
19016
+ updatedUserInputMap[updatedKey] = userInputMap[key];
19017
+ delete updatedUserInputMap[key];
19018
+ }
19019
+ }
19020
+ return updatedUserInputMap;
19021
+ };
19022
+
19023
+ const widgetRegExes = [/input-number \d+/]; // We can add more regexes here in the future
19024
+
19025
+ // This utils in this file are used to modernize our Perseus JSON structure,
19026
+ // so that we can convert deprecated widgets to their modern equivalents when
19027
+ // content creators use the Editor Page to update content containing these widgets.
19028
+ // Currently, we're only converting input-number to numeric-input,
19029
+ // but we can add more conversions here in the future.
19030
+ // Modernize the json content of a PerseusRenderer object
19031
+ // by converting deprecated widgets to their modern equivalents
19032
+ const convertDeprecatedWidgets = json => {
19033
+ // Currently we're only converting input-number to numeric-input,
19034
+ // But we can add more conversions here in the future
19035
+ return inputNumberToNumericInput(json);
19036
+ };
19037
+ const conversionRequired = json => {
19038
+ // If there's no content, then there's no conversion required
19039
+ if (!json.content) {
19040
+ return false;
19041
+ }
19042
+ // Check the content string for any top-level input-number widgets
19043
+ if (widgetRegExes.some(regex => regex.test(json.content))) {
19044
+ return true;
19045
+ }
19046
+
19047
+ // If there's no deprecated widget in the top-level, then check for any nested widgets
19048
+ for (const key of Object.keys(json.widgets)) {
19049
+ if (json.widgets[key].options.widgets) {
19050
+ const nestedJson = json.widgets[key].options;
19051
+ if (conversionRequired(nestedJson)) {
19052
+ return true;
19053
+ }
19054
+ }
19055
+ }
19056
+ return false;
19057
+ };
19058
+
19059
+ // Convert the user input data keys for deprecated widgets to their modern equivalents
19060
+ const convertUserInputData = userInputMap => {
19061
+ // Currently we're only converting input-number to numeric-input,
19062
+ // But we can add more conversions here in the future
19063
+ return convertUserInputNumberData(userInputMap);
19064
+ };
19065
+
18878
19066
  // These are all the current widgets that require user-input and are supported for automatic scoring
18879
19067
  const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
18880
19068
 
@@ -19474,11 +19662,18 @@ function scorePerseusItem(perseusRenderData, userInputMap,
19474
19662
  // TODO(LEMS-2461,LEMS-2391): these probably
19475
19663
  // need to be removed before we move this to the server
19476
19664
  strings, locale) {
19665
+ // Check if the PerseusRenderer object contains any deprecated widgets that need to be converted
19666
+ const mustConvertData = conversionRequired(perseusRenderData);
19667
+ const convertedRenderData = mustConvertData ? convertDeprecatedWidgets(perseusRenderData) // Convert deprecated widgets to their modern equivalents
19668
+ : perseusRenderData;
19669
+ const convertedUserInputMap = mustConvertData ? convertUserInputData(userInputMap) // Convert deprecated user input data keys to their modern equivalents
19670
+ : userInputMap;
19671
+
19477
19672
  // There seems to be a chance that PerseusRenderer.widgets might include
19478
19673
  // widget data for widgets that are not in PerseusRenderer.content,
19479
19674
  // so this checks that the widgets are being used before scoring them
19480
- const usedWidgetIds = getWidgetIdsFromContent(perseusRenderData.content);
19481
- const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
19675
+ const usedWidgetIds = getWidgetIdsFromContent(convertedRenderData.content);
19676
+ const scores = scoreWidgetsFunctional(convertedRenderData.widgets, usedWidgetIds, convertedUserInputMap, strings, locale);
19482
19677
  return Util.flattenScores(scores);
19483
19678
  }
19484
19679
  function scoreWidgetsFunctional(widgets,
@@ -45203,7 +45398,7 @@ const init = function () {
45203
45398
 
45204
45399
  // This file is processed by a Rollup plugin (replace) to inject the production
45205
45400
  const libName = "@khanacademy/perseus";
45206
- const libVersion = "42.0.0";
45401
+ const libVersion = "42.0.1";
45207
45402
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45208
45403
 
45209
45404
  const apiVersion = {
@@ -49843,6 +50038,8 @@ exports.buildMapper = buildMapper;
49843
50038
  exports.components = components;
49844
50039
  exports.containerSizeClass = containerSizeClass;
49845
50040
  exports.contentHasWidgetType = contentHasWidgetType;
50041
+ exports.conversionRequired = conversionRequired;
50042
+ exports.convertDeprecatedWidgets = convertDeprecatedWidgets;
49846
50043
  exports.convertWidgetNameToEnum = convertWidgetNameToEnum;
49847
50044
  exports.displaySigFigs = displaySigFigs;
49848
50045
  exports.generateTestCategorizerWidget = generateTestCategorizerWidget;