@khanacademy/perseus 41.5.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 +513 -268
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +3 -2
- package/dist/es/strings.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +519 -263
- package/dist/index.js.map +1 -1
- package/dist/renderer-util.d.ts +3 -2
- package/dist/renderer.d.ts +7 -10
- package/dist/server-item-renderer.d.ts +33 -2
- package/dist/strings.d.ts +2 -1
- package/dist/strings.js +3 -2
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/util/deprecated-widgets/input-number.d.ts +9 -0
- package/dist/util/deprecated-widgets/modernize-widgets-utils.d.ts +4 -0
- package/dist/util/parse-perseus-json/exhaustive-test-tool/index.d.ts +2 -0
- package/dist/widget-ai-utils/graded-group/{prompt-utils.d.ts → graded-group-ai-utils.d.ts} +1 -1
- package/dist/widget-ai-utils/graded-group-set/{prompt-utils.d.ts → graded-group-set-ai-utils.d.ts} +1 -1
- package/dist/widget-ai-utils/group/{prompt-utils.d.ts → group-ai-utils.d.ts} +1 -1
- package/dist/widget-ai-utils/prompt-types.d.ts +33 -0
- package/dist/widget-ai-utils/unsupported-widget.d.ts +1 -1
- package/dist/widgets/categorizer/categorizer.d.ts +1 -1
- package/dist/widgets/definition/definition.d.ts +1 -1
- package/dist/widgets/dropdown/dropdown.d.ts +1 -1
- package/dist/widgets/explanation/explanation.d.ts +1 -1
- package/dist/widgets/expression/expression.d.ts +3 -3
- package/dist/widgets/graded-group/graded-group.d.ts +1 -1
- package/dist/widgets/graded-group-set/graded-group-set.d.ts +1 -1
- package/dist/widgets/grapher/grapher.d.ts +1 -1
- package/dist/widgets/group/group.d.ts +1 -1
- package/dist/widgets/image/image.d.ts +1 -1
- package/dist/widgets/input-number/input-number.d.ts +1 -1
- package/dist/widgets/interactive-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/components/movable-point.d.ts +9 -0
- package/dist/widgets/interactive-graphs/graphs/components/use-control-point.d.ts +9 -0
- package/dist/widgets/label-image/label-image.d.ts +1 -1
- package/dist/widgets/matcher/matcher.d.ts +1 -1
- package/dist/widgets/matrix/matrix.d.ts +1 -1
- package/dist/widgets/number-line/number-line.d.ts +1 -1
- package/dist/widgets/orderer/orderer.d.ts +1 -1
- package/dist/widgets/passage/passage.d.ts +1 -1
- package/dist/widgets/passage-ref/passage-ref.d.ts +1 -1
- package/dist/widgets/radio/radio-component.d.ts +1 -1
- package/dist/widgets/sorter/sorter.d.ts +1 -1
- package/package.json +1 -1
- package/dist/prompt-types.d.ts +0 -33
- /package/dist/widget-ai-utils/categorizer/{prompt-utils.d.ts → categorizer-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/cs-program/{prompt-utils.d.ts → cs-program-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/definition/{prompt-utils.d.ts → definition-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/dropdown/{prompt-utils.d.ts → dropdown-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/explanation/{prompt-utils.d.ts → explanation-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/expression/{prompt-utils.d.ts → expression-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/grapher/{prompt-utils.d.ts → grapher-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/iframe/{prompt-utils.d.ts → iframe-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/image/{prompt-utils.d.ts → image-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/input-number/{prompt-utils.d.ts → input-number-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/interaction/{prompt-utils.d.ts → interaction-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/interactive-graph/{prompt-utils.d.ts → interactive-graph-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/label-image/{prompt-utils.d.ts → label-image-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/matcher/{prompt-utils.d.ts → matcher-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/matrix/{prompt-utils.d.ts → matrix-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/measurer/{prompt-utils.d.ts → measurer-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/number-line/{prompt-utils.d.ts → number-line-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/orderer/{prompt-utils.d.ts → orderer-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/passage/{prompt-utils.d.ts → passage-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/passage-ref/{prompt-utils.d.ts → passage-ref-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/phet-simulation/{prompt-utils.d.ts → phet-simulation-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/plotter/{prompt-utils.d.ts → plotter-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/python-program/{prompt-utils.d.ts → python-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/radio/{prompt-utils.d.ts → radio-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/sorter/{prompt-utils.d.ts → sorter-ai-utils.d.ts} +0 -0
- /package/dist/widget-ai-utils/video/{prompt-utils.d.ts → video-ai-utils.d.ts} +0 -0
package/dist/es/index.js
CHANGED
|
@@ -241,9 +241,10 @@ const mockStrings = {
|
|
|
241
241
|
removePoint: "Remove Point",
|
|
242
242
|
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
243
243
|
srPointAtCoordinates: ({
|
|
244
|
+
num,
|
|
244
245
|
x,
|
|
245
246
|
y
|
|
246
|
-
}) => `Point at ${x} comma ${y}`,
|
|
247
|
+
}) => `Point ${num} at ${x} comma ${y}`,
|
|
247
248
|
srInteractiveElements: ({
|
|
248
249
|
elements
|
|
249
250
|
}) => `Interactive elements: ${elements}`,
|
|
@@ -18673,6 +18674,321 @@ class QuestionParagraph extends React.Component {
|
|
|
18673
18674
|
}
|
|
18674
18675
|
}
|
|
18675
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
|
+
|
|
18858
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
18859
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
18860
|
+
|
|
18861
|
+
/**
|
|
18862
|
+
* Regex for widget placeholders in a string.
|
|
18863
|
+
*
|
|
18864
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
18865
|
+
* Second capture group is the widget type (ex. "radio)
|
|
18866
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
18867
|
+
*/
|
|
18868
|
+
function getWidgetRegex() {
|
|
18869
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
18870
|
+
}
|
|
18871
|
+
|
|
18872
|
+
/**
|
|
18873
|
+
* Add a widget placeholder using the widget ID.
|
|
18874
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
18875
|
+
*
|
|
18876
|
+
* @param {string} id
|
|
18877
|
+
* @returns {string}
|
|
18878
|
+
*/
|
|
18879
|
+
function addWidget(id) {
|
|
18880
|
+
return `[[☃ ${id}]]`;
|
|
18881
|
+
}
|
|
18882
|
+
|
|
18883
|
+
/**
|
|
18884
|
+
* Get a widget type by a widget's ID
|
|
18885
|
+
* (please don't derive type from ID)
|
|
18886
|
+
*
|
|
18887
|
+
* @param {string} widgetId the ID of the widget
|
|
18888
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18889
|
+
* @returns {string} the widget type (ie "radio")
|
|
18890
|
+
*/
|
|
18891
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
18892
|
+
var _widget$type;
|
|
18893
|
+
const widget = widgetMap[WidgetId];
|
|
18894
|
+
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
18895
|
+
}
|
|
18896
|
+
|
|
18897
|
+
/**
|
|
18898
|
+
* Does the content have a specific type of widget?
|
|
18899
|
+
*
|
|
18900
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
18901
|
+
* @param {string} content the string to search through
|
|
18902
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18903
|
+
* @returns {boolean} if the content includes the widget type
|
|
18904
|
+
*/
|
|
18905
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
18906
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
18907
|
+
}
|
|
18908
|
+
|
|
18909
|
+
/**
|
|
18910
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
18911
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
18912
|
+
*
|
|
18913
|
+
* Content should contain Perseus widget placeholders,
|
|
18914
|
+
* which look like: '[[☃ radio 1]]'.
|
|
18915
|
+
*
|
|
18916
|
+
* @param {string} content
|
|
18917
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
18918
|
+
*/
|
|
18919
|
+
function getWidgetIdsFromContent(content) {
|
|
18920
|
+
const widgets = [];
|
|
18921
|
+
const localWidgetRegex = getWidgetRegex();
|
|
18922
|
+
let match = localWidgetRegex.exec(content);
|
|
18923
|
+
while (match !== null) {
|
|
18924
|
+
widgets.push(match[1]);
|
|
18925
|
+
match = localWidgetRegex.exec(content);
|
|
18926
|
+
}
|
|
18927
|
+
return widgets;
|
|
18928
|
+
}
|
|
18929
|
+
|
|
18930
|
+
/**
|
|
18931
|
+
* Get a list of widget IDs from content,
|
|
18932
|
+
* but only for specific widget types
|
|
18933
|
+
*
|
|
18934
|
+
* @param {string} type the type of widget (ie "radio")
|
|
18935
|
+
* @param {string} content the string to parse
|
|
18936
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18937
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
18938
|
+
*/
|
|
18939
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
18940
|
+
const rv = [];
|
|
18941
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
18942
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
18943
|
+
const widget = widgetMap[widgetId];
|
|
18944
|
+
if ((widget == null ? void 0 : widget.type) === type) {
|
|
18945
|
+
rv.push(widgetId);
|
|
18946
|
+
}
|
|
18947
|
+
});
|
|
18948
|
+
return rv;
|
|
18949
|
+
}
|
|
18950
|
+
|
|
18951
|
+
/**
|
|
18952
|
+
* Pull the widget map out of ItemData
|
|
18953
|
+
*
|
|
18954
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
18955
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
18956
|
+
*/
|
|
18957
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
18958
|
+
return itemData.question.widgets;
|
|
18959
|
+
}
|
|
18960
|
+
|
|
18961
|
+
/**
|
|
18962
|
+
* Get the widget information from a WidgetMap
|
|
18963
|
+
*
|
|
18964
|
+
* @param {string} widgetId the ID of the widget
|
|
18965
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
18966
|
+
*/
|
|
18967
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
18968
|
+
var _widgetMap$widgetId;
|
|
18969
|
+
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
18970
|
+
}
|
|
18971
|
+
|
|
18972
|
+
/**
|
|
18973
|
+
* Get select widgets from a widget map.
|
|
18974
|
+
* Useful for multi-items that needs to split
|
|
18975
|
+
* one PerseusItem into several
|
|
18976
|
+
*
|
|
18977
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
18978
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
18979
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
18980
|
+
*/
|
|
18981
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
18982
|
+
const widgets = {};
|
|
18983
|
+
widgetIds.forEach(widgetId => {
|
|
18984
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
18985
|
+
if (widget) {
|
|
18986
|
+
widgets[widgetId] = widget;
|
|
18987
|
+
}
|
|
18988
|
+
});
|
|
18989
|
+
return widgets;
|
|
18990
|
+
}
|
|
18991
|
+
|
|
18676
18992
|
const DEFAULT_ALIGNMENT$1 = "block";
|
|
18677
18993
|
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
18678
18994
|
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
@@ -19134,6 +19450,27 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19134
19450
|
}
|
|
19135
19451
|
});
|
|
19136
19452
|
}
|
|
19453
|
+
|
|
19454
|
+
// TODO: combine scorePerseusItem with scoreWidgetsFunctional
|
|
19455
|
+
// once scorePerseusItem is the only one calling scoreWidgetsFunctional
|
|
19456
|
+
function scorePerseusItem(perseusRenderData, userInputMap,
|
|
19457
|
+
// TODO(LEMS-2461,LEMS-2391): these probably
|
|
19458
|
+
// need to be removed before we move this to the server
|
|
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
|
+
|
|
19467
|
+
// There seems to be a chance that PerseusRenderer.widgets might include
|
|
19468
|
+
// widget data for widgets that are not in PerseusRenderer.content,
|
|
19469
|
+
// so this checks that the widgets are being used before scoring them
|
|
19470
|
+
const usedWidgetIds = getWidgetIdsFromContent(convertedRenderData.content);
|
|
19471
|
+
const scores = scoreWidgetsFunctional(convertedRenderData.widgets, usedWidgetIds, convertedUserInputMap, strings, locale);
|
|
19472
|
+
return Util.flattenScores(scores);
|
|
19473
|
+
}
|
|
19137
19474
|
function scoreWidgetsFunctional(widgets,
|
|
19138
19475
|
// This is a port of old code, I'm not sure why
|
|
19139
19476
|
// we need widgetIds vs the keys of the widgets object
|
|
@@ -19154,7 +19491,7 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19154
19491
|
const userInput = userInputMap[id];
|
|
19155
19492
|
const scorer = getWidgetScorer(widget.type);
|
|
19156
19493
|
if (widget.type === "group") {
|
|
19157
|
-
const scores = scoreWidgetsFunctional(widget.options.widgets,
|
|
19494
|
+
const scores = scoreWidgetsFunctional(widget.options.widgets, getWidgetIdsFromContent(widget.options.content), userInputMap[id], strings, locale);
|
|
19158
19495
|
widgetScores[id] = Util.flattenScores(scores);
|
|
19159
19496
|
} else if (scorer) {
|
|
19160
19497
|
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
@@ -19986,7 +20323,7 @@ class Renderer extends React.Component {
|
|
|
19986
20323
|
// Instead, we center them with css magic in styles.less
|
|
19987
20324
|
// /cry(aria)
|
|
19988
20325
|
this._foundTextNodes = true;
|
|
19989
|
-
if (
|
|
20326
|
+
if (this.widgetIds.includes(node.id)) {
|
|
19990
20327
|
// We don't want to render a duplicate widget key/ref,
|
|
19991
20328
|
// as this causes problems with react (for obvious
|
|
19992
20329
|
// reasons). Instead we just notify the
|
|
@@ -20290,7 +20627,7 @@ class Renderer extends React.Component {
|
|
|
20290
20627
|
};
|
|
20291
20628
|
this.getInputPaths = () => {
|
|
20292
20629
|
const inputPaths = [];
|
|
20293
|
-
|
|
20630
|
+
this.widgetIds.forEach(widgetId => {
|
|
20294
20631
|
const widget = this.getWidgetInstance(widgetId);
|
|
20295
20632
|
if (widget && widget.getInputPaths) {
|
|
20296
20633
|
// Grab all input paths and add widgetID to the front
|
|
@@ -20298,7 +20635,7 @@ class Renderer extends React.Component {
|
|
|
20298
20635
|
// Prefix paths with their widgetID and add to collective
|
|
20299
20636
|
// list of paths.
|
|
20300
20637
|
// @ts-expect-error - TS2345 - Argument of type '(inputPath: string) => void' is not assignable to parameter of type 'CollectionIterator<FocusPath, void, readonly FocusPath[]>'.
|
|
20301
|
-
|
|
20638
|
+
widgetInputPaths.forEach(inputPath => {
|
|
20302
20639
|
const relativeInputPath = [widgetId].concat(inputPath);
|
|
20303
20640
|
inputPaths.push(relativeInputPath);
|
|
20304
20641
|
});
|
|
@@ -20427,12 +20764,20 @@ class Renderer extends React.Component {
|
|
|
20427
20764
|
this.getWidgetIds = () => {
|
|
20428
20765
|
return this.widgetIds;
|
|
20429
20766
|
};
|
|
20767
|
+
/**
|
|
20768
|
+
* @deprecated use scorePerseusItem
|
|
20769
|
+
*/
|
|
20770
|
+
this.guessAndScore = () => {
|
|
20771
|
+
const totalGuess = this.getUserInput();
|
|
20772
|
+
const totalScore = this.score();
|
|
20773
|
+
return [totalGuess, totalScore];
|
|
20774
|
+
};
|
|
20430
20775
|
this.examples = () => {
|
|
20431
20776
|
const widgetIds = this.widgetIds;
|
|
20432
|
-
const examples =
|
|
20777
|
+
const examples = widgetIds.map(widgetId => {
|
|
20433
20778
|
const widget = this.getWidgetInstance(widgetId);
|
|
20434
20779
|
return widget != null && widget.examples ? widget.examples() : null;
|
|
20435
|
-
}));
|
|
20780
|
+
}).filter(Boolean);
|
|
20436
20781
|
|
|
20437
20782
|
// no widgets with examples
|
|
20438
20783
|
if (!examples.length) {
|
|
@@ -20687,27 +21032,15 @@ class Renderer extends React.Component {
|
|
|
20687
21032
|
}
|
|
20688
21033
|
|
|
20689
21034
|
/**
|
|
20690
|
-
*
|
|
20691
|
-
*
|
|
20692
|
-
*
|
|
20693
|
-
*/
|
|
20694
|
-
scoreWidgets() {
|
|
20695
|
-
return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20696
|
-
}
|
|
20697
|
-
|
|
20698
|
-
/**
|
|
20699
|
-
* Grades the content.
|
|
21035
|
+
* Scores the content.
|
|
21036
|
+
*
|
|
21037
|
+
* @deprecated use scorePerseusItem
|
|
20700
21038
|
*/
|
|
20701
21039
|
score() {
|
|
20702
|
-
const scores = this.
|
|
21040
|
+
const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20703
21041
|
const combinedScore = Util.flattenScores(scores);
|
|
20704
21042
|
return combinedScore;
|
|
20705
21043
|
}
|
|
20706
|
-
guessAndScore() {
|
|
20707
|
-
const totalGuess = this.getUserInput();
|
|
20708
|
-
const totalScore = this.score();
|
|
20709
|
-
return [totalGuess, totalScore];
|
|
20710
|
-
}
|
|
20711
21044
|
render() {
|
|
20712
21045
|
const apiOptions = this.getApiOptions();
|
|
20713
21046
|
if (this.reuseMarkdown) {
|
|
@@ -22247,7 +22580,9 @@ const propUpgrades$2 = {
|
|
|
22247
22580
|
// input-number to numeric-input. In this case, we need to upgrade
|
|
22248
22581
|
// the widget options accordingly.
|
|
22249
22582
|
if (initialProps.value) {
|
|
22250
|
-
|
|
22583
|
+
// If the answerType is not number or percent, we need to provide
|
|
22584
|
+
// the answer form for the numeric-input widget
|
|
22585
|
+
const provideAnswerForm = initialProps.answerType !== "number" && initialProps.answerType !== "percent";
|
|
22251
22586
|
|
|
22252
22587
|
// We need to determine the mathFormat for the numeric-input widget
|
|
22253
22588
|
const mathFormat = initialProps.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
|
|
@@ -29255,6 +29590,9 @@ class Group extends React.Component {
|
|
|
29255
29590
|
this.change("widgets", this.rendererRef.props.widgets);
|
|
29256
29591
|
}
|
|
29257
29592
|
};
|
|
29593
|
+
|
|
29594
|
+
// TODO(LEMS-2391): replace this when there's a separate check
|
|
29595
|
+
// for valid/invalid state
|
|
29258
29596
|
const score = (_this$rendererRef12 = this.rendererRef) == null ? void 0 : _this$rendererRef12.score();
|
|
29259
29597
|
const isValid = score && score.type !== "invalid";
|
|
29260
29598
|
const isInvalid = score && score.type === "invalid";
|
|
@@ -31073,6 +31411,107 @@ function AxisArrows() {
|
|
|
31073
31411
|
})));
|
|
31074
31412
|
}
|
|
31075
31413
|
|
|
31414
|
+
/**
|
|
31415
|
+
* 44 is touch best practice and AAA compliant for WCAG
|
|
31416
|
+
* https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
|
|
31417
|
+
*/
|
|
31418
|
+
const TARGET_SIZE = 44;
|
|
31419
|
+
const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
|
|
31420
|
+
|
|
31421
|
+
// same as pointsFromNormalized in interactive-graph.tsx
|
|
31422
|
+
const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
|
|
31423
|
+
// maps over [x, y]
|
|
31424
|
+
coords.map((coord, i) => {
|
|
31425
|
+
const axisRange = range[i];
|
|
31426
|
+
if (noSnap) {
|
|
31427
|
+
return axisRange[MIN] + size(axisRange) * coord;
|
|
31428
|
+
}
|
|
31429
|
+
const axisStep = step[i];
|
|
31430
|
+
const nSteps = Math.floor(size(axisRange) / axisStep);
|
|
31431
|
+
const tick = Math.round(coord * nSteps);
|
|
31432
|
+
return axisRange[MIN] + axisStep * tick;
|
|
31433
|
+
}));
|
|
31434
|
+
|
|
31435
|
+
// same as normalizeCoords in interactive-graph.tsx
|
|
31436
|
+
const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
|
|
31437
|
+
return (coord + ranges[i][1]) / size(ranges[i]);
|
|
31438
|
+
}));
|
|
31439
|
+
|
|
31440
|
+
// Returns the closest point to the given `point` that is within the graph
|
|
31441
|
+
// bounds given in `state`.
|
|
31442
|
+
function bound$1({
|
|
31443
|
+
snapStep,
|
|
31444
|
+
range,
|
|
31445
|
+
point
|
|
31446
|
+
}) {
|
|
31447
|
+
const boundingBox = inset(snapStep, range);
|
|
31448
|
+
return clampToBox(boundingBox, point);
|
|
31449
|
+
}
|
|
31450
|
+
function isUnlimitedGraphState(state) {
|
|
31451
|
+
return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
|
|
31452
|
+
}
|
|
31453
|
+
|
|
31454
|
+
/**
|
|
31455
|
+
* Replace all text outside of the $ TeX blocks with `\\text{...}`
|
|
31456
|
+
* This way, the entire resulting string can be rendered within <TeX>
|
|
31457
|
+
* and the text outside of the $ blocks will be non-TeX text.
|
|
31458
|
+
*/
|
|
31459
|
+
function replaceOutsideTeX(mathString) {
|
|
31460
|
+
// All the information we need is in the first section,
|
|
31461
|
+
// whether it's typed as "blockmath" or "paragraph"
|
|
31462
|
+
const firstSection = SimpleMarkdown.parse(mathString)[0];
|
|
31463
|
+
|
|
31464
|
+
// If it's blockMath, the outer level has the full math content.
|
|
31465
|
+
if (firstSection.type === "blockMath") {
|
|
31466
|
+
return firstSection.content;
|
|
31467
|
+
}
|
|
31468
|
+
|
|
31469
|
+
// If it's a paragraph, we need to iterate through the sections
|
|
31470
|
+
// to look for individual math blocks.
|
|
31471
|
+
const condensedNodes = condenseTextNodes(firstSection.content);
|
|
31472
|
+
let result = "";
|
|
31473
|
+
for (const piece of condensedNodes) {
|
|
31474
|
+
piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
|
|
31475
|
+
}
|
|
31476
|
+
return result;
|
|
31477
|
+
}
|
|
31478
|
+
// Helper function for replaceOutsideTeX()
|
|
31479
|
+
// Condense adjacent text nodes into a single text node
|
|
31480
|
+
function condenseTextNodes(nodes) {
|
|
31481
|
+
const result = [];
|
|
31482
|
+
if (!nodes) {
|
|
31483
|
+
return result;
|
|
31484
|
+
}
|
|
31485
|
+
let currentText = "";
|
|
31486
|
+
for (const node of nodes) {
|
|
31487
|
+
if (node.type === "math") {
|
|
31488
|
+
if (currentText) {
|
|
31489
|
+
result.push({
|
|
31490
|
+
type: "text",
|
|
31491
|
+
content: currentText
|
|
31492
|
+
});
|
|
31493
|
+
currentText = "";
|
|
31494
|
+
}
|
|
31495
|
+
result.push(node);
|
|
31496
|
+
} else {
|
|
31497
|
+
currentText += node.content;
|
|
31498
|
+
}
|
|
31499
|
+
}
|
|
31500
|
+
if (currentText) {
|
|
31501
|
+
result.push({
|
|
31502
|
+
type: "text",
|
|
31503
|
+
content: currentText
|
|
31504
|
+
});
|
|
31505
|
+
}
|
|
31506
|
+
return result;
|
|
31507
|
+
}
|
|
31508
|
+
|
|
31509
|
+
// Helper function for replaceOutsideTeX()
|
|
31510
|
+
function escapeSpecialChars(str) {
|
|
31511
|
+
// Escape $, \, {, and } characters
|
|
31512
|
+
return str.replace(/([$\\{}])/g, "\\$1");
|
|
31513
|
+
}
|
|
31514
|
+
|
|
31076
31515
|
function AxisLabels() {
|
|
31077
31516
|
const {
|
|
31078
31517
|
range,
|
|
@@ -31104,7 +31543,7 @@ function AxisLabels() {
|
|
|
31104
31543
|
fontSize: "14px",
|
|
31105
31544
|
transform: "translate(7px, -50%)"
|
|
31106
31545
|
}
|
|
31107
|
-
}, /*#__PURE__*/React__default.createElement(TeX, null, xAxisLabelText)), /*#__PURE__*/React__default.createElement("span", {
|
|
31546
|
+
}, /*#__PURE__*/React__default.createElement(TeX, null, replaceOutsideTeX(xAxisLabelText))), /*#__PURE__*/React__default.createElement("span", {
|
|
31108
31547
|
style: {
|
|
31109
31548
|
position: "absolute",
|
|
31110
31549
|
left: x2,
|
|
@@ -31112,7 +31551,7 @@ function AxisLabels() {
|
|
|
31112
31551
|
fontSize: "14px",
|
|
31113
31552
|
transform: "translate(-50%, 0px)"
|
|
31114
31553
|
}
|
|
31115
|
-
}, /*#__PURE__*/React__default.createElement(TeX, null, yAxisLabelText)));
|
|
31554
|
+
}, /*#__PURE__*/React__default.createElement(TeX, null, replaceOutsideTeX(yAxisLabelText))));
|
|
31116
31555
|
}
|
|
31117
31556
|
|
|
31118
31557
|
// The size of the ticks and labels in pixels
|
|
@@ -32046,107 +32485,6 @@ const lockedFigureFillStyles = {
|
|
|
32046
32485
|
|
|
32047
32486
|
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
32048
32487
|
|
|
32049
|
-
/**
|
|
32050
|
-
* 44 is touch best practice and AAA compliant for WCAG
|
|
32051
|
-
* https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
|
|
32052
|
-
*/
|
|
32053
|
-
const TARGET_SIZE = 44;
|
|
32054
|
-
const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
|
|
32055
|
-
|
|
32056
|
-
// same as pointsFromNormalized in interactive-graph.tsx
|
|
32057
|
-
const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
|
|
32058
|
-
// maps over [x, y]
|
|
32059
|
-
coords.map((coord, i) => {
|
|
32060
|
-
const axisRange = range[i];
|
|
32061
|
-
if (noSnap) {
|
|
32062
|
-
return axisRange[MIN] + size(axisRange) * coord;
|
|
32063
|
-
}
|
|
32064
|
-
const axisStep = step[i];
|
|
32065
|
-
const nSteps = Math.floor(size(axisRange) / axisStep);
|
|
32066
|
-
const tick = Math.round(coord * nSteps);
|
|
32067
|
-
return axisRange[MIN] + axisStep * tick;
|
|
32068
|
-
}));
|
|
32069
|
-
|
|
32070
|
-
// same as normalizeCoords in interactive-graph.tsx
|
|
32071
|
-
const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
|
|
32072
|
-
return (coord + ranges[i][1]) / size(ranges[i]);
|
|
32073
|
-
}));
|
|
32074
|
-
|
|
32075
|
-
// Returns the closest point to the given `point` that is within the graph
|
|
32076
|
-
// bounds given in `state`.
|
|
32077
|
-
function bound$1({
|
|
32078
|
-
snapStep,
|
|
32079
|
-
range,
|
|
32080
|
-
point
|
|
32081
|
-
}) {
|
|
32082
|
-
const boundingBox = inset(snapStep, range);
|
|
32083
|
-
return clampToBox(boundingBox, point);
|
|
32084
|
-
}
|
|
32085
|
-
function isUnlimitedGraphState(state) {
|
|
32086
|
-
return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
|
|
32087
|
-
}
|
|
32088
|
-
|
|
32089
|
-
/**
|
|
32090
|
-
* Replace all text outside of the $ TeX blocks with `\\text{...}`
|
|
32091
|
-
* This way, the entire resulting string can be rendered within <TeX>
|
|
32092
|
-
* and the text outside of the $ blocks will be non-TeX text.
|
|
32093
|
-
*/
|
|
32094
|
-
function replaceOutsideTeX(mathString) {
|
|
32095
|
-
// All the information we need is in the first section,
|
|
32096
|
-
// whether it's typed as "blockmath" or "paragraph"
|
|
32097
|
-
const firstSection = SimpleMarkdown.parse(mathString)[0];
|
|
32098
|
-
|
|
32099
|
-
// If it's blockMath, the outer level has the full math content.
|
|
32100
|
-
if (firstSection.type === "blockMath") {
|
|
32101
|
-
return firstSection.content;
|
|
32102
|
-
}
|
|
32103
|
-
|
|
32104
|
-
// If it's a paragraph, we need to iterate through the sections
|
|
32105
|
-
// to look for individual math blocks.
|
|
32106
|
-
const condensedNodes = condenseTextNodes(firstSection.content);
|
|
32107
|
-
let result = "";
|
|
32108
|
-
for (const piece of condensedNodes) {
|
|
32109
|
-
piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
|
|
32110
|
-
}
|
|
32111
|
-
return result;
|
|
32112
|
-
}
|
|
32113
|
-
// Helper function for replaceOutsideTeX()
|
|
32114
|
-
// Condense adjacent text nodes into a single text node
|
|
32115
|
-
function condenseTextNodes(nodes) {
|
|
32116
|
-
const result = [];
|
|
32117
|
-
if (!nodes) {
|
|
32118
|
-
return result;
|
|
32119
|
-
}
|
|
32120
|
-
let currentText = "";
|
|
32121
|
-
for (const node of nodes) {
|
|
32122
|
-
if (node.type === "math") {
|
|
32123
|
-
if (currentText) {
|
|
32124
|
-
result.push({
|
|
32125
|
-
type: "text",
|
|
32126
|
-
content: currentText
|
|
32127
|
-
});
|
|
32128
|
-
currentText = "";
|
|
32129
|
-
}
|
|
32130
|
-
result.push(node);
|
|
32131
|
-
} else {
|
|
32132
|
-
currentText += node.content;
|
|
32133
|
-
}
|
|
32134
|
-
}
|
|
32135
|
-
if (currentText) {
|
|
32136
|
-
result.push({
|
|
32137
|
-
type: "text",
|
|
32138
|
-
content: currentText
|
|
32139
|
-
});
|
|
32140
|
-
}
|
|
32141
|
-
return result;
|
|
32142
|
-
}
|
|
32143
|
-
|
|
32144
|
-
// Helper function for replaceOutsideTeX()
|
|
32145
|
-
function escapeSpecialChars(str) {
|
|
32146
|
-
// Escape $, \, {, and } characters
|
|
32147
|
-
return str.replace(/([$\\{}])/g, "\\$1");
|
|
32148
|
-
}
|
|
32149
|
-
|
|
32150
32488
|
function LockedLabel(props) {
|
|
32151
32489
|
const {
|
|
32152
32490
|
coord,
|
|
@@ -33369,6 +33707,7 @@ function useControlPoint(params) {
|
|
|
33369
33707
|
} = useGraphConfig();
|
|
33370
33708
|
const {
|
|
33371
33709
|
point,
|
|
33710
|
+
sequenceNumber,
|
|
33372
33711
|
color,
|
|
33373
33712
|
cursor,
|
|
33374
33713
|
constrain = p => snap(snapStep, p),
|
|
@@ -33409,6 +33748,7 @@ function useControlPoint(params) {
|
|
|
33409
33748
|
ref: focusableHandleRef,
|
|
33410
33749
|
role: "button",
|
|
33411
33750
|
"aria-label": strings.srPointAtCoordinates({
|
|
33751
|
+
num: sequenceNumber,
|
|
33412
33752
|
x: srFormatNumber(point[X], locale),
|
|
33413
33753
|
y: srFormatNumber(point[Y], locale)
|
|
33414
33754
|
})
|
|
@@ -33487,6 +33827,7 @@ const MovableLine = props => {
|
|
|
33487
33827
|
focusableHandle: focusableHandle1
|
|
33488
33828
|
} = useControlPoint({
|
|
33489
33829
|
point: start,
|
|
33830
|
+
sequenceNumber: 1,
|
|
33490
33831
|
color,
|
|
33491
33832
|
onMove: p => onMovePoint(0, p)
|
|
33492
33833
|
});
|
|
@@ -33495,6 +33836,7 @@ const MovableLine = props => {
|
|
|
33495
33836
|
focusableHandle: focusableHandle2
|
|
33496
33837
|
} = useControlPoint({
|
|
33497
33838
|
point: end,
|
|
33839
|
+
sequenceNumber: 2,
|
|
33498
33840
|
color,
|
|
33499
33841
|
onMove: p => onMovePoint(1, p)
|
|
33500
33842
|
});
|
|
@@ -33674,14 +34016,17 @@ function AngleGraph(props) {
|
|
|
33674
34016
|
// Render the lines, angle, and then movable points
|
|
33675
34017
|
return /*#__PURE__*/React.createElement(React.Fragment, null, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33676
34018
|
point: coords[1],
|
|
34019
|
+
sequenceNumber: 1,
|
|
33677
34020
|
constrain: p => p,
|
|
33678
34021
|
onMove: destination => dispatch(actions.angle.movePoint(1, destination))
|
|
33679
34022
|
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33680
34023
|
point: coords[0],
|
|
34024
|
+
sequenceNumber: 2,
|
|
33681
34025
|
constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
|
|
33682
34026
|
onMove: destination => dispatch(actions.angle.movePoint(0, destination))
|
|
33683
34027
|
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33684
34028
|
point: coords[2],
|
|
34029
|
+
sequenceNumber: 3,
|
|
33685
34030
|
constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
|
|
33686
34031
|
onMove: destination => dispatch(actions.angle.movePoint(2, destination))
|
|
33687
34032
|
}));
|
|
@@ -33835,6 +34180,7 @@ function CircleGraph(props) {
|
|
|
33835
34180
|
onMove: c => dispatch(actions.circle.moveCenter(c))
|
|
33836
34181
|
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33837
34182
|
point: radiusPoint,
|
|
34183
|
+
sequenceNumber: 1,
|
|
33838
34184
|
cursor: "ew-resize",
|
|
33839
34185
|
onMove: newRadiusPoint => {
|
|
33840
34186
|
dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
|
|
@@ -34010,6 +34356,7 @@ function LimitedPointGraph(props) {
|
|
|
34010
34356
|
return /*#__PURE__*/React.createElement(React.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34011
34357
|
key: i,
|
|
34012
34358
|
point: point,
|
|
34359
|
+
sequenceNumber: i + 1,
|
|
34013
34360
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
|
|
34014
34361
|
})));
|
|
34015
34362
|
}
|
|
@@ -34052,6 +34399,7 @@ function UnlimitedPointGraph(props) {
|
|
|
34052
34399
|
}), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34053
34400
|
key: i,
|
|
34054
34401
|
point: point,
|
|
34402
|
+
sequenceNumber: i + 1,
|
|
34055
34403
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
34056
34404
|
ref: ref => {
|
|
34057
34405
|
itemsRef.current[i] = ref;
|
|
@@ -34082,7 +34430,8 @@ function describePointGraph(state, i18n) {
|
|
|
34082
34430
|
if (state.coords.length === 0) {
|
|
34083
34431
|
return strings.srNoInteractiveElements;
|
|
34084
34432
|
}
|
|
34085
|
-
const pointDescriptions = state.coords.map(([x, y]) => strings.srPointAtCoordinates({
|
|
34433
|
+
const pointDescriptions = state.coords.map(([x, y], index) => strings.srPointAtCoordinates({
|
|
34434
|
+
num: index + 1,
|
|
34086
34435
|
x: srFormatNumber(x, locale),
|
|
34087
34436
|
y: srFormatNumber(y, locale)
|
|
34088
34437
|
}));
|
|
@@ -34200,6 +34549,7 @@ const LimitedPolygonGraph = props => {
|
|
|
34200
34549
|
key: "point-" + i,
|
|
34201
34550
|
constrain: constrain,
|
|
34202
34551
|
point: point,
|
|
34552
|
+
sequenceNumber: i + 1,
|
|
34203
34553
|
onMove: destination => {
|
|
34204
34554
|
const now = Date.now();
|
|
34205
34555
|
const targetFPS = 40;
|
|
@@ -34346,6 +34696,7 @@ const UnlimitedPolygonGraph = props => {
|
|
|
34346
34696
|
}), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34347
34697
|
key: i,
|
|
34348
34698
|
point: point,
|
|
34699
|
+
sequenceNumber: i + 1,
|
|
34349
34700
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
34350
34701
|
ref: ref => {
|
|
34351
34702
|
pointRef.current[i] = ref;
|
|
@@ -34421,6 +34772,7 @@ function QuadraticGraph(props) {
|
|
|
34421
34772
|
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34422
34773
|
key: "point-" + i,
|
|
34423
34774
|
point: coord,
|
|
34775
|
+
sequenceNumber: i + 1,
|
|
34424
34776
|
onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
|
|
34425
34777
|
})));
|
|
34426
34778
|
}
|
|
@@ -34549,6 +34901,7 @@ function SinusoidGraph(props) {
|
|
|
34549
34901
|
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34550
34902
|
key: "point-" + i,
|
|
34551
34903
|
point: coord,
|
|
34904
|
+
sequenceNumber: i + 1,
|
|
34552
34905
|
onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
|
|
34553
34906
|
})));
|
|
34554
34907
|
}
|
|
@@ -44677,7 +45030,7 @@ const init = function init() {
|
|
|
44677
45030
|
|
|
44678
45031
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44679
45032
|
const libName = "@khanacademy/perseus";
|
|
44680
|
-
const libVersion = "
|
|
45033
|
+
const libVersion = "42.0.1";
|
|
44681
45034
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44682
45035
|
|
|
44683
45036
|
const apiVersion = {
|
|
@@ -45301,6 +45654,13 @@ class ServerItemRenderer extends React.Component {
|
|
|
45301
45654
|
this.props.onRendered(true);
|
|
45302
45655
|
}
|
|
45303
45656
|
}
|
|
45657
|
+
if (this.props.score && this.props.score !== prevProps.score) {
|
|
45658
|
+
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45659
|
+
this.setState({
|
|
45660
|
+
questionCompleted: this.props.score.correct,
|
|
45661
|
+
questionHighlightedWidgets: emptyQuestionAreaWidgets
|
|
45662
|
+
});
|
|
45663
|
+
}
|
|
45304
45664
|
}
|
|
45305
45665
|
componentWillUnmount() {
|
|
45306
45666
|
if (this.blurTimeoutID != null) {
|
|
@@ -45418,18 +45778,37 @@ class ServerItemRenderer extends React.Component {
|
|
|
45418
45778
|
return this.questionRenderer.getPromptJSON();
|
|
45419
45779
|
}
|
|
45420
45780
|
|
|
45781
|
+
/**
|
|
45782
|
+
* Returns an array of the widget `.getUserInput()` results
|
|
45783
|
+
*
|
|
45784
|
+
* TODO: can we remove this? Seems to be just for backwards
|
|
45785
|
+
* compatibility with old Perseus Chrome logging
|
|
45786
|
+
* @deprecated use getUserInput
|
|
45787
|
+
*/
|
|
45788
|
+
getUserInputLegacy() {
|
|
45789
|
+
return this.questionRenderer.getUserInput();
|
|
45790
|
+
}
|
|
45791
|
+
|
|
45792
|
+
/**
|
|
45793
|
+
* Returns an object of the widget `.getUserInput()` results
|
|
45794
|
+
*/
|
|
45795
|
+
getUserInput() {
|
|
45796
|
+
return this.questionRenderer.getUserInputMap();
|
|
45797
|
+
}
|
|
45798
|
+
|
|
45421
45799
|
/**
|
|
45422
45800
|
* Grades the item.
|
|
45801
|
+
*
|
|
45802
|
+
* @deprecated use scorePerseusItem
|
|
45423
45803
|
*/
|
|
45424
45804
|
scoreInput() {
|
|
45425
|
-
const
|
|
45426
|
-
const
|
|
45427
|
-
const score = guessAndScore[1];
|
|
45805
|
+
const guess = this.getUserInput();
|
|
45806
|
+
const score = scorePerseusItem(this.props.item.question, guess, this.context.strings, this.context.locale);
|
|
45428
45807
|
|
|
45429
45808
|
// Continue to include an empty guess for the now defunct answer area.
|
|
45430
45809
|
// TODO(alex): Check whether we rely on the format here for
|
|
45431
45810
|
// analyzing ProblemLogs. If not, remove this layer.
|
|
45432
|
-
const maxCompatGuess = [
|
|
45811
|
+
const maxCompatGuess = [this.questionRenderer.getUserInput(), []];
|
|
45433
45812
|
const keScore = Util.keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
|
|
45434
45813
|
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45435
45814
|
this.setState({
|
|
@@ -47277,140 +47656,6 @@ const registerAllWidgetsForTesting = () => {
|
|
|
47277
47656
|
replaceDeprecatedWidgets();
|
|
47278
47657
|
};
|
|
47279
47658
|
|
|
47280
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
47281
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
47282
|
-
|
|
47283
|
-
/**
|
|
47284
|
-
* Regex for widget placeholders in a string.
|
|
47285
|
-
*
|
|
47286
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
47287
|
-
* Second capture group is the widget type (ex. "radio)
|
|
47288
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
47289
|
-
*/
|
|
47290
|
-
function getWidgetRegex() {
|
|
47291
|
-
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
47292
|
-
}
|
|
47293
|
-
|
|
47294
|
-
/**
|
|
47295
|
-
* Add a widget placeholder using the widget ID.
|
|
47296
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
47297
|
-
*
|
|
47298
|
-
* @param {string} id
|
|
47299
|
-
* @returns {string}
|
|
47300
|
-
*/
|
|
47301
|
-
function addWidget(id) {
|
|
47302
|
-
return `[[☃ ${id}]]`;
|
|
47303
|
-
}
|
|
47304
|
-
|
|
47305
|
-
/**
|
|
47306
|
-
* Get a widget type by a widget's ID
|
|
47307
|
-
* (please don't derive type from ID)
|
|
47308
|
-
*
|
|
47309
|
-
* @param {string} widgetId the ID of the widget
|
|
47310
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47311
|
-
* @returns {string} the widget type (ie "radio")
|
|
47312
|
-
*/
|
|
47313
|
-
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
47314
|
-
var _widget$type;
|
|
47315
|
-
const widget = widgetMap[WidgetId];
|
|
47316
|
-
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
47317
|
-
}
|
|
47318
|
-
|
|
47319
|
-
/**
|
|
47320
|
-
* Does the content have a specific type of widget?
|
|
47321
|
-
*
|
|
47322
|
-
* @param {string} type the type of the widget in question (ie "radio")
|
|
47323
|
-
* @param {string} content the string to search through
|
|
47324
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47325
|
-
* @returns {boolean} if the content includes the widget type
|
|
47326
|
-
*/
|
|
47327
|
-
function contentHasWidgetType(type, content, widgetMap) {
|
|
47328
|
-
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
47329
|
-
}
|
|
47330
|
-
|
|
47331
|
-
/**
|
|
47332
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
47333
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
47334
|
-
*
|
|
47335
|
-
* Content should contain Perseus widget placeholders,
|
|
47336
|
-
* which look like: '[[☃ radio 1]]'.
|
|
47337
|
-
*
|
|
47338
|
-
* @param {string} content
|
|
47339
|
-
* @returns {ReadonlyArray<string>} widgetIds
|
|
47340
|
-
*/
|
|
47341
|
-
function getWidgetIdsFromContent(content) {
|
|
47342
|
-
const widgets = [];
|
|
47343
|
-
const localWidgetRegex = getWidgetRegex();
|
|
47344
|
-
let match = localWidgetRegex.exec(content);
|
|
47345
|
-
while (match !== null) {
|
|
47346
|
-
widgets.push(match[1]);
|
|
47347
|
-
match = localWidgetRegex.exec(content);
|
|
47348
|
-
}
|
|
47349
|
-
return widgets;
|
|
47350
|
-
}
|
|
47351
|
-
|
|
47352
|
-
/**
|
|
47353
|
-
* Get a list of widget IDs from content,
|
|
47354
|
-
* but only for specific widget types
|
|
47355
|
-
*
|
|
47356
|
-
* @param {string} type the type of widget (ie "radio")
|
|
47357
|
-
* @param {string} content the string to parse
|
|
47358
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47359
|
-
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
47360
|
-
*/
|
|
47361
|
-
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
47362
|
-
const rv = [];
|
|
47363
|
-
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
47364
|
-
widgetIdsInContent.forEach(widgetId => {
|
|
47365
|
-
const widget = widgetMap[widgetId];
|
|
47366
|
-
if ((widget == null ? void 0 : widget.type) === type) {
|
|
47367
|
-
rv.push(widgetId);
|
|
47368
|
-
}
|
|
47369
|
-
});
|
|
47370
|
-
return rv;
|
|
47371
|
-
}
|
|
47372
|
-
|
|
47373
|
-
/**
|
|
47374
|
-
* Pull the widget map out of ItemData
|
|
47375
|
-
*
|
|
47376
|
-
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
47377
|
-
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
47378
|
-
*/
|
|
47379
|
-
function getWidgetsMapFromItemData(itemData) {
|
|
47380
|
-
return itemData.question.widgets;
|
|
47381
|
-
}
|
|
47382
|
-
|
|
47383
|
-
/**
|
|
47384
|
-
* Get the widget information from a WidgetMap
|
|
47385
|
-
*
|
|
47386
|
-
* @param {string} widgetId the ID of the widget
|
|
47387
|
-
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
47388
|
-
*/
|
|
47389
|
-
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
47390
|
-
var _widgetMap$widgetId;
|
|
47391
|
-
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
47392
|
-
}
|
|
47393
|
-
|
|
47394
|
-
/**
|
|
47395
|
-
* Get select widgets from a widget map.
|
|
47396
|
-
* Useful for multi-items that needs to split
|
|
47397
|
-
* one PerseusItem into several
|
|
47398
|
-
*
|
|
47399
|
-
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
47400
|
-
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
47401
|
-
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
47402
|
-
*/
|
|
47403
|
-
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
47404
|
-
const widgets = {};
|
|
47405
|
-
widgetIds.forEach(widgetId => {
|
|
47406
|
-
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
47407
|
-
if (widget) {
|
|
47408
|
-
widgets[widgetId] = widget;
|
|
47409
|
-
}
|
|
47410
|
-
});
|
|
47411
|
-
return widgets;
|
|
47412
|
-
}
|
|
47413
|
-
|
|
47414
47659
|
/**
|
|
47415
47660
|
* This function extracts the answers from the widgets.
|
|
47416
47661
|
*
|
|
@@ -49355,5 +49600,5 @@ const GrapherUtil = {
|
|
|
49355
49600
|
typeToButton
|
|
49356
49601
|
};
|
|
49357
49602
|
|
|
49358
|
-
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, 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 };
|
|
49359
49604
|
//# sourceMappingURL=index.js.map
|