@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/es/index.js CHANGED
@@ -18674,6 +18674,187 @@ class QuestionParagraph extends React.Component {
18674
18674
  }
18675
18675
  }
18676
18676
 
18677
+ // Methods to convert input-number widgets to numeric-input widgets
18678
+
18679
+ const inputNumberToNumericInput = json => {
18680
+ // First we need to create a map of the old input-number keys to the new numeric-input keys
18681
+ // so that we can ensure we update the content and widgets accordingly
18682
+ const renameMap = getInputNumberRenameMap(json);
18683
+
18684
+ // Then we can use this to update the JSON
18685
+ return convertInputNumberJson(json, renameMap);
18686
+ };
18687
+
18688
+ // We need to be able to run this code recursively in order to convert nested input-number widgets
18689
+ const convertInputNumberJson = (json, renameMap) => {
18690
+ const updatedContent = convertDeprecatedWidgetsInContent(json, renameMap);
18691
+ const updatedWidgets = convertInputNumberWidgetOptions(json, renameMap);
18692
+ const modernizedJson = _extends({}, json, {
18693
+ content: updatedContent,
18694
+ widgets: updatedWidgets
18695
+ });
18696
+ return modernizedJson;
18697
+ };
18698
+
18699
+ // Convert the input-number json in the widgets of a PerseusRenderer object
18700
+ const convertInputNumberWidgetOptions = (json, renameMap) => {
18701
+ const widgets = _extends({}, json.widgets);
18702
+ // The question.widgets is a dictionary map of widgets, so we need to loop through in order to convert input-number to numeric-input
18703
+ // which can exist as both the key or as a value on widget.type.
18704
+ for (const key of Object.keys(widgets)) {
18705
+ // Loop through the keys of the widgets dictionary
18706
+ if (widgets[key].options.widgets) {
18707
+ widgets[key].options = _extends({}, inputNumberToNumericInput(widgets[key].options));
18708
+ }
18709
+ // Check if the widget is an input-number
18710
+ if (widgets[key].type === "input-number") {
18711
+ // If the answerType is not number or percent, we need to provide
18712
+ // the answer form for the numeric-input widget
18713
+ const provideAnswerForm = widgets[key].options.answerType !== "number" && widgets[key].options.answerType !== "percent";
18714
+ // We need to determine the mathFormat for the numeric-input widget
18715
+ const mathFormat = widgets[key].options.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
18716
+ : widgets[key].options.answerType; // Otherwise, we can use the answerType directly
18717
+
18718
+ // We need to update the answers prop to match the numeric-input widget format
18719
+ const answers = [{
18720
+ value: widgets[key].options.value,
18721
+ simplify: widgets[key].options.simplify,
18722
+ // Input Number doesn't have a strict prop, so we default to false
18723
+ strict: false,
18724
+ // We only want to set maxError if the inexact prop is true
18725
+ maxError: widgets[key].options.inexact ? widgets[key].options.maxError : 0,
18726
+ status: "correct",
18727
+ // Input-number only allows correct answers
18728
+ message: ""
18729
+ }];
18730
+
18731
+ // Add the required answerForms if provided/applicable
18732
+ if (provideAnswerForm) {
18733
+ answers[0].answerForms = [mathFormat];
18734
+ }
18735
+
18736
+ // Update the options prop to match the numeric-input widget format
18737
+ const upgradedWidgetOptions = {
18738
+ answers,
18739
+ size: widgets[key].options.size,
18740
+ coefficient: false,
18741
+ // input-number doesn't have a coefficient prop
18742
+ labelText: "",
18743
+ // input-number doesn't have a labelText prop
18744
+ static: false,
18745
+ // static is always false for numeric-input
18746
+ rightAlign: widgets[key].options.rightAlign || false
18747
+ };
18748
+ const upgradedWidget = {
18749
+ options: upgradedWidgetOptions,
18750
+ type: "numeric-input"
18751
+ };
18752
+ const newWidgetName = renameMap[key];
18753
+ // Create the new key entry
18754
+ widgets[newWidgetName] = upgradedWidget;
18755
+
18756
+ // We need to delete the old widget key
18757
+ delete widgets[key];
18758
+ }
18759
+ }
18760
+ return widgets;
18761
+ };
18762
+
18763
+ // Convert the deprecated widget refs in the content string
18764
+ // of a PerseusRenderer object to their renamed equivalents
18765
+ const convertDeprecatedWidgetsInContent = (json, renameMap) => {
18766
+ return Object.keys(renameMap).reduce((newContent, oldKey) => {
18767
+ const newKey = renameMap[oldKey];
18768
+ return newKey ? newContent.replace(oldKey, newKey) : newContent;
18769
+ }, json.content);
18770
+ };
18771
+
18772
+ // Create a map of the old input-number keys to the new numeric-input keys
18773
+ const getInputNumberRenameMap = json => {
18774
+ const numericRegex = /(?<=\[\[\u2603 )(numeric-input \d+)(?=\]\])/g;
18775
+ const inputNumberRegex = /(?<=\[\[\u2603 )(input-number \d+)(?=\]\])/g;
18776
+
18777
+ // Get all the content strings within the json, which might be nested within widgets
18778
+ const allContentStrings = json.content;
18779
+
18780
+ // Loop through the content strings to get all the input-number widgets
18781
+ const renameMap = {};
18782
+ const inputNumberMatches = [...(allContentStrings.match(inputNumberRegex) || [])];
18783
+
18784
+ // We want to count any pre-existing numeric-input widgets
18785
+ // so that we can start the new ids at the next number
18786
+ const numericMatches = [...(allContentStrings.match(numericRegex) || [])];
18787
+ let currentNumericCount = numericMatches.reduce((count, match) => {
18788
+ const id = parseInt(match.split(" ")[1], 10);
18789
+ return id >= count ? id + 1 : count; // We want to start at the next highest number
18790
+ }, 1);
18791
+
18792
+ // Now that we have all the necessary information, we can create the renameMap
18793
+ for (const match of inputNumberMatches) {
18794
+ const oldKey = match;
18795
+ const newKey = `numeric-input ${currentNumericCount}`;
18796
+ renameMap[oldKey] = newKey;
18797
+ currentNumericCount++;
18798
+ }
18799
+ return renameMap;
18800
+ };
18801
+
18802
+ // Convert the user input data keys from input-number to numeric-input
18803
+ const convertUserInputNumberData = userInputMap => {
18804
+ const updatedUserInputMap = _extends({}, userInputMap);
18805
+ for (const key of Object.keys(userInputMap)) {
18806
+ if (key.includes("input-number")) {
18807
+ const updatedKey = key.replace("input-number", "numeric-input");
18808
+ updatedUserInputMap[updatedKey] = userInputMap[key];
18809
+ delete updatedUserInputMap[key];
18810
+ }
18811
+ }
18812
+ return updatedUserInputMap;
18813
+ };
18814
+
18815
+ const widgetRegExes = [/input-number \d+/]; // We can add more regexes here in the future
18816
+
18817
+ // This utils in this file are used to modernize our Perseus JSON structure,
18818
+ // so that we can convert deprecated widgets to their modern equivalents when
18819
+ // content creators use the Editor Page to update content containing these widgets.
18820
+ // Currently, we're only converting input-number to numeric-input,
18821
+ // but we can add more conversions here in the future.
18822
+ // Modernize the json content of a PerseusRenderer object
18823
+ // by converting deprecated widgets to their modern equivalents
18824
+ const convertDeprecatedWidgets = json => {
18825
+ // Currently we're only converting input-number to numeric-input,
18826
+ // But we can add more conversions here in the future
18827
+ return inputNumberToNumericInput(json);
18828
+ };
18829
+ const conversionRequired = json => {
18830
+ // If there's no content, then there's no conversion required
18831
+ if (!json.content) {
18832
+ return false;
18833
+ }
18834
+ // Check the content string for any top-level input-number widgets
18835
+ if (widgetRegExes.some(regex => regex.test(json.content))) {
18836
+ return true;
18837
+ }
18838
+
18839
+ // If there's no deprecated widget in the top-level, then check for any nested widgets
18840
+ for (const key of Object.keys(json.widgets)) {
18841
+ if (json.widgets[key].options.widgets) {
18842
+ const nestedJson = json.widgets[key].options;
18843
+ if (conversionRequired(nestedJson)) {
18844
+ return true;
18845
+ }
18846
+ }
18847
+ }
18848
+ return false;
18849
+ };
18850
+
18851
+ // Convert the user input data keys for deprecated widgets to their modern equivalents
18852
+ const convertUserInputData = userInputMap => {
18853
+ // Currently we're only converting input-number to numeric-input,
18854
+ // But we can add more conversions here in the future
18855
+ return convertUserInputNumberData(userInputMap);
18856
+ };
18857
+
18677
18858
  // These are all the current widgets that require user-input and are supported for automatic scoring
18678
18859
  const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
18679
18860
 
@@ -19276,11 +19457,18 @@ function scorePerseusItem(perseusRenderData, userInputMap,
19276
19457
  // TODO(LEMS-2461,LEMS-2391): these probably
19277
19458
  // need to be removed before we move this to the server
19278
19459
  strings, locale) {
19460
+ // Check if the PerseusRenderer object contains any deprecated widgets that need to be converted
19461
+ const mustConvertData = conversionRequired(perseusRenderData);
19462
+ const convertedRenderData = mustConvertData ? convertDeprecatedWidgets(perseusRenderData) // Convert deprecated widgets to their modern equivalents
19463
+ : perseusRenderData;
19464
+ const convertedUserInputMap = mustConvertData ? convertUserInputData(userInputMap) // Convert deprecated user input data keys to their modern equivalents
19465
+ : userInputMap;
19466
+
19279
19467
  // There seems to be a chance that PerseusRenderer.widgets might include
19280
19468
  // widget data for widgets that are not in PerseusRenderer.content,
19281
19469
  // so this checks that the widgets are being used before scoring them
19282
- const usedWidgetIds = getWidgetIdsFromContent(perseusRenderData.content);
19283
- const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
19470
+ const usedWidgetIds = getWidgetIdsFromContent(convertedRenderData.content);
19471
+ const scores = scoreWidgetsFunctional(convertedRenderData.widgets, usedWidgetIds, convertedUserInputMap, strings, locale);
19284
19472
  return Util.flattenScores(scores);
19285
19473
  }
19286
19474
  function scoreWidgetsFunctional(widgets,
@@ -44842,7 +45030,7 @@ const init = function init() {
44842
45030
 
44843
45031
  // This file is processed by a Rollup plugin (replace) to inject the production
44844
45032
  const libName = "@khanacademy/perseus";
44845
- const libVersion = "42.0.0";
45033
+ const libVersion = "42.0.1";
44846
45034
  addLibraryVersionToPerseusDebug(libName, libVersion);
44847
45035
 
44848
45036
  const apiVersion = {
@@ -49412,5 +49600,5 @@ const GrapherUtil = {
49412
49600
  typeToButton
49413
49601
  };
49414
49602
 
49415
- export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, buildEmptyItemTreeForShape, buildMapper, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
49603
+ export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, buildEmptyItemTreeForShape, buildMapper, components, containerSizeClass, contentHasWidgetType, conversionRequired, convertDeprecatedWidgets, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
49416
49604
  //# sourceMappingURL=index.js.map