@khanacademy/perseus 41.5.0 → 42.0.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 +325 -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 +3 -1
- package/dist/index.js +322 -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/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 +1 -1
- 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,140 @@ class QuestionParagraph extends React.Component {
|
|
|
18673
18674
|
}
|
|
18674
18675
|
}
|
|
18675
18676
|
|
|
18677
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
18678
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
18679
|
+
|
|
18680
|
+
/**
|
|
18681
|
+
* Regex for widget placeholders in a string.
|
|
18682
|
+
*
|
|
18683
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
18684
|
+
* Second capture group is the widget type (ex. "radio)
|
|
18685
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
18686
|
+
*/
|
|
18687
|
+
function getWidgetRegex() {
|
|
18688
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
18689
|
+
}
|
|
18690
|
+
|
|
18691
|
+
/**
|
|
18692
|
+
* Add a widget placeholder using the widget ID.
|
|
18693
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
18694
|
+
*
|
|
18695
|
+
* @param {string} id
|
|
18696
|
+
* @returns {string}
|
|
18697
|
+
*/
|
|
18698
|
+
function addWidget(id) {
|
|
18699
|
+
return `[[☃ ${id}]]`;
|
|
18700
|
+
}
|
|
18701
|
+
|
|
18702
|
+
/**
|
|
18703
|
+
* Get a widget type by a widget's ID
|
|
18704
|
+
* (please don't derive type from ID)
|
|
18705
|
+
*
|
|
18706
|
+
* @param {string} widgetId the ID of the widget
|
|
18707
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18708
|
+
* @returns {string} the widget type (ie "radio")
|
|
18709
|
+
*/
|
|
18710
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
18711
|
+
var _widget$type;
|
|
18712
|
+
const widget = widgetMap[WidgetId];
|
|
18713
|
+
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
18714
|
+
}
|
|
18715
|
+
|
|
18716
|
+
/**
|
|
18717
|
+
* Does the content have a specific type of widget?
|
|
18718
|
+
*
|
|
18719
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
18720
|
+
* @param {string} content the string to search through
|
|
18721
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18722
|
+
* @returns {boolean} if the content includes the widget type
|
|
18723
|
+
*/
|
|
18724
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
18725
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
18726
|
+
}
|
|
18727
|
+
|
|
18728
|
+
/**
|
|
18729
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
18730
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
18731
|
+
*
|
|
18732
|
+
* Content should contain Perseus widget placeholders,
|
|
18733
|
+
* which look like: '[[☃ radio 1]]'.
|
|
18734
|
+
*
|
|
18735
|
+
* @param {string} content
|
|
18736
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
18737
|
+
*/
|
|
18738
|
+
function getWidgetIdsFromContent(content) {
|
|
18739
|
+
const widgets = [];
|
|
18740
|
+
const localWidgetRegex = getWidgetRegex();
|
|
18741
|
+
let match = localWidgetRegex.exec(content);
|
|
18742
|
+
while (match !== null) {
|
|
18743
|
+
widgets.push(match[1]);
|
|
18744
|
+
match = localWidgetRegex.exec(content);
|
|
18745
|
+
}
|
|
18746
|
+
return widgets;
|
|
18747
|
+
}
|
|
18748
|
+
|
|
18749
|
+
/**
|
|
18750
|
+
* Get a list of widget IDs from content,
|
|
18751
|
+
* but only for specific widget types
|
|
18752
|
+
*
|
|
18753
|
+
* @param {string} type the type of widget (ie "radio")
|
|
18754
|
+
* @param {string} content the string to parse
|
|
18755
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18756
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
18757
|
+
*/
|
|
18758
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
18759
|
+
const rv = [];
|
|
18760
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
18761
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
18762
|
+
const widget = widgetMap[widgetId];
|
|
18763
|
+
if ((widget == null ? void 0 : widget.type) === type) {
|
|
18764
|
+
rv.push(widgetId);
|
|
18765
|
+
}
|
|
18766
|
+
});
|
|
18767
|
+
return rv;
|
|
18768
|
+
}
|
|
18769
|
+
|
|
18770
|
+
/**
|
|
18771
|
+
* Pull the widget map out of ItemData
|
|
18772
|
+
*
|
|
18773
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
18774
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
18775
|
+
*/
|
|
18776
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
18777
|
+
return itemData.question.widgets;
|
|
18778
|
+
}
|
|
18779
|
+
|
|
18780
|
+
/**
|
|
18781
|
+
* Get the widget information from a WidgetMap
|
|
18782
|
+
*
|
|
18783
|
+
* @param {string} widgetId the ID of the widget
|
|
18784
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
18785
|
+
*/
|
|
18786
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
18787
|
+
var _widgetMap$widgetId;
|
|
18788
|
+
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
18789
|
+
}
|
|
18790
|
+
|
|
18791
|
+
/**
|
|
18792
|
+
* Get select widgets from a widget map.
|
|
18793
|
+
* Useful for multi-items that needs to split
|
|
18794
|
+
* one PerseusItem into several
|
|
18795
|
+
*
|
|
18796
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
18797
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
18798
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
18799
|
+
*/
|
|
18800
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
18801
|
+
const widgets = {};
|
|
18802
|
+
widgetIds.forEach(widgetId => {
|
|
18803
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
18804
|
+
if (widget) {
|
|
18805
|
+
widgets[widgetId] = widget;
|
|
18806
|
+
}
|
|
18807
|
+
});
|
|
18808
|
+
return widgets;
|
|
18809
|
+
}
|
|
18810
|
+
|
|
18676
18811
|
const DEFAULT_ALIGNMENT$1 = "block";
|
|
18677
18812
|
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
18678
18813
|
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
@@ -19134,6 +19269,20 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19134
19269
|
}
|
|
19135
19270
|
});
|
|
19136
19271
|
}
|
|
19272
|
+
|
|
19273
|
+
// TODO: combine scorePerseusItem with scoreWidgetsFunctional
|
|
19274
|
+
// once scorePerseusItem is the only one calling scoreWidgetsFunctional
|
|
19275
|
+
function scorePerseusItem(perseusRenderData, userInputMap,
|
|
19276
|
+
// TODO(LEMS-2461,LEMS-2391): these probably
|
|
19277
|
+
// need to be removed before we move this to the server
|
|
19278
|
+
strings, locale) {
|
|
19279
|
+
// There seems to be a chance that PerseusRenderer.widgets might include
|
|
19280
|
+
// widget data for widgets that are not in PerseusRenderer.content,
|
|
19281
|
+
// 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);
|
|
19284
|
+
return Util.flattenScores(scores);
|
|
19285
|
+
}
|
|
19137
19286
|
function scoreWidgetsFunctional(widgets,
|
|
19138
19287
|
// This is a port of old code, I'm not sure why
|
|
19139
19288
|
// we need widgetIds vs the keys of the widgets object
|
|
@@ -19154,7 +19303,7 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19154
19303
|
const userInput = userInputMap[id];
|
|
19155
19304
|
const scorer = getWidgetScorer(widget.type);
|
|
19156
19305
|
if (widget.type === "group") {
|
|
19157
|
-
const scores = scoreWidgetsFunctional(widget.options.widgets,
|
|
19306
|
+
const scores = scoreWidgetsFunctional(widget.options.widgets, getWidgetIdsFromContent(widget.options.content), userInputMap[id], strings, locale);
|
|
19158
19307
|
widgetScores[id] = Util.flattenScores(scores);
|
|
19159
19308
|
} else if (scorer) {
|
|
19160
19309
|
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
@@ -19986,7 +20135,7 @@ class Renderer extends React.Component {
|
|
|
19986
20135
|
// Instead, we center them with css magic in styles.less
|
|
19987
20136
|
// /cry(aria)
|
|
19988
20137
|
this._foundTextNodes = true;
|
|
19989
|
-
if (
|
|
20138
|
+
if (this.widgetIds.includes(node.id)) {
|
|
19990
20139
|
// We don't want to render a duplicate widget key/ref,
|
|
19991
20140
|
// as this causes problems with react (for obvious
|
|
19992
20141
|
// reasons). Instead we just notify the
|
|
@@ -20290,7 +20439,7 @@ class Renderer extends React.Component {
|
|
|
20290
20439
|
};
|
|
20291
20440
|
this.getInputPaths = () => {
|
|
20292
20441
|
const inputPaths = [];
|
|
20293
|
-
|
|
20442
|
+
this.widgetIds.forEach(widgetId => {
|
|
20294
20443
|
const widget = this.getWidgetInstance(widgetId);
|
|
20295
20444
|
if (widget && widget.getInputPaths) {
|
|
20296
20445
|
// Grab all input paths and add widgetID to the front
|
|
@@ -20298,7 +20447,7 @@ class Renderer extends React.Component {
|
|
|
20298
20447
|
// Prefix paths with their widgetID and add to collective
|
|
20299
20448
|
// list of paths.
|
|
20300
20449
|
// @ts-expect-error - TS2345 - Argument of type '(inputPath: string) => void' is not assignable to parameter of type 'CollectionIterator<FocusPath, void, readonly FocusPath[]>'.
|
|
20301
|
-
|
|
20450
|
+
widgetInputPaths.forEach(inputPath => {
|
|
20302
20451
|
const relativeInputPath = [widgetId].concat(inputPath);
|
|
20303
20452
|
inputPaths.push(relativeInputPath);
|
|
20304
20453
|
});
|
|
@@ -20427,12 +20576,20 @@ class Renderer extends React.Component {
|
|
|
20427
20576
|
this.getWidgetIds = () => {
|
|
20428
20577
|
return this.widgetIds;
|
|
20429
20578
|
};
|
|
20579
|
+
/**
|
|
20580
|
+
* @deprecated use scorePerseusItem
|
|
20581
|
+
*/
|
|
20582
|
+
this.guessAndScore = () => {
|
|
20583
|
+
const totalGuess = this.getUserInput();
|
|
20584
|
+
const totalScore = this.score();
|
|
20585
|
+
return [totalGuess, totalScore];
|
|
20586
|
+
};
|
|
20430
20587
|
this.examples = () => {
|
|
20431
20588
|
const widgetIds = this.widgetIds;
|
|
20432
|
-
const examples =
|
|
20589
|
+
const examples = widgetIds.map(widgetId => {
|
|
20433
20590
|
const widget = this.getWidgetInstance(widgetId);
|
|
20434
20591
|
return widget != null && widget.examples ? widget.examples() : null;
|
|
20435
|
-
}));
|
|
20592
|
+
}).filter(Boolean);
|
|
20436
20593
|
|
|
20437
20594
|
// no widgets with examples
|
|
20438
20595
|
if (!examples.length) {
|
|
@@ -20687,27 +20844,15 @@ class Renderer extends React.Component {
|
|
|
20687
20844
|
}
|
|
20688
20845
|
|
|
20689
20846
|
/**
|
|
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.
|
|
20847
|
+
* Scores the content.
|
|
20848
|
+
*
|
|
20849
|
+
* @deprecated use scorePerseusItem
|
|
20700
20850
|
*/
|
|
20701
20851
|
score() {
|
|
20702
|
-
const scores = this.
|
|
20852
|
+
const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20703
20853
|
const combinedScore = Util.flattenScores(scores);
|
|
20704
20854
|
return combinedScore;
|
|
20705
20855
|
}
|
|
20706
|
-
guessAndScore() {
|
|
20707
|
-
const totalGuess = this.getUserInput();
|
|
20708
|
-
const totalScore = this.score();
|
|
20709
|
-
return [totalGuess, totalScore];
|
|
20710
|
-
}
|
|
20711
20856
|
render() {
|
|
20712
20857
|
const apiOptions = this.getApiOptions();
|
|
20713
20858
|
if (this.reuseMarkdown) {
|
|
@@ -22247,7 +22392,9 @@ const propUpgrades$2 = {
|
|
|
22247
22392
|
// input-number to numeric-input. In this case, we need to upgrade
|
|
22248
22393
|
// the widget options accordingly.
|
|
22249
22394
|
if (initialProps.value) {
|
|
22250
|
-
|
|
22395
|
+
// If the answerType is not number or percent, we need to provide
|
|
22396
|
+
// the answer form for the numeric-input widget
|
|
22397
|
+
const provideAnswerForm = initialProps.answerType !== "number" && initialProps.answerType !== "percent";
|
|
22251
22398
|
|
|
22252
22399
|
// We need to determine the mathFormat for the numeric-input widget
|
|
22253
22400
|
const mathFormat = initialProps.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
|
|
@@ -29255,6 +29402,9 @@ class Group extends React.Component {
|
|
|
29255
29402
|
this.change("widgets", this.rendererRef.props.widgets);
|
|
29256
29403
|
}
|
|
29257
29404
|
};
|
|
29405
|
+
|
|
29406
|
+
// TODO(LEMS-2391): replace this when there's a separate check
|
|
29407
|
+
// for valid/invalid state
|
|
29258
29408
|
const score = (_this$rendererRef12 = this.rendererRef) == null ? void 0 : _this$rendererRef12.score();
|
|
29259
29409
|
const isValid = score && score.type !== "invalid";
|
|
29260
29410
|
const isInvalid = score && score.type === "invalid";
|
|
@@ -31073,6 +31223,107 @@ function AxisArrows() {
|
|
|
31073
31223
|
})));
|
|
31074
31224
|
}
|
|
31075
31225
|
|
|
31226
|
+
/**
|
|
31227
|
+
* 44 is touch best practice and AAA compliant for WCAG
|
|
31228
|
+
* https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
|
|
31229
|
+
*/
|
|
31230
|
+
const TARGET_SIZE = 44;
|
|
31231
|
+
const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
|
|
31232
|
+
|
|
31233
|
+
// same as pointsFromNormalized in interactive-graph.tsx
|
|
31234
|
+
const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
|
|
31235
|
+
// maps over [x, y]
|
|
31236
|
+
coords.map((coord, i) => {
|
|
31237
|
+
const axisRange = range[i];
|
|
31238
|
+
if (noSnap) {
|
|
31239
|
+
return axisRange[MIN] + size(axisRange) * coord;
|
|
31240
|
+
}
|
|
31241
|
+
const axisStep = step[i];
|
|
31242
|
+
const nSteps = Math.floor(size(axisRange) / axisStep);
|
|
31243
|
+
const tick = Math.round(coord * nSteps);
|
|
31244
|
+
return axisRange[MIN] + axisStep * tick;
|
|
31245
|
+
}));
|
|
31246
|
+
|
|
31247
|
+
// same as normalizeCoords in interactive-graph.tsx
|
|
31248
|
+
const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
|
|
31249
|
+
return (coord + ranges[i][1]) / size(ranges[i]);
|
|
31250
|
+
}));
|
|
31251
|
+
|
|
31252
|
+
// Returns the closest point to the given `point` that is within the graph
|
|
31253
|
+
// bounds given in `state`.
|
|
31254
|
+
function bound$1({
|
|
31255
|
+
snapStep,
|
|
31256
|
+
range,
|
|
31257
|
+
point
|
|
31258
|
+
}) {
|
|
31259
|
+
const boundingBox = inset(snapStep, range);
|
|
31260
|
+
return clampToBox(boundingBox, point);
|
|
31261
|
+
}
|
|
31262
|
+
function isUnlimitedGraphState(state) {
|
|
31263
|
+
return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
|
|
31264
|
+
}
|
|
31265
|
+
|
|
31266
|
+
/**
|
|
31267
|
+
* Replace all text outside of the $ TeX blocks with `\\text{...}`
|
|
31268
|
+
* This way, the entire resulting string can be rendered within <TeX>
|
|
31269
|
+
* and the text outside of the $ blocks will be non-TeX text.
|
|
31270
|
+
*/
|
|
31271
|
+
function replaceOutsideTeX(mathString) {
|
|
31272
|
+
// All the information we need is in the first section,
|
|
31273
|
+
// whether it's typed as "blockmath" or "paragraph"
|
|
31274
|
+
const firstSection = SimpleMarkdown.parse(mathString)[0];
|
|
31275
|
+
|
|
31276
|
+
// If it's blockMath, the outer level has the full math content.
|
|
31277
|
+
if (firstSection.type === "blockMath") {
|
|
31278
|
+
return firstSection.content;
|
|
31279
|
+
}
|
|
31280
|
+
|
|
31281
|
+
// If it's a paragraph, we need to iterate through the sections
|
|
31282
|
+
// to look for individual math blocks.
|
|
31283
|
+
const condensedNodes = condenseTextNodes(firstSection.content);
|
|
31284
|
+
let result = "";
|
|
31285
|
+
for (const piece of condensedNodes) {
|
|
31286
|
+
piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
|
|
31287
|
+
}
|
|
31288
|
+
return result;
|
|
31289
|
+
}
|
|
31290
|
+
// Helper function for replaceOutsideTeX()
|
|
31291
|
+
// Condense adjacent text nodes into a single text node
|
|
31292
|
+
function condenseTextNodes(nodes) {
|
|
31293
|
+
const result = [];
|
|
31294
|
+
if (!nodes) {
|
|
31295
|
+
return result;
|
|
31296
|
+
}
|
|
31297
|
+
let currentText = "";
|
|
31298
|
+
for (const node of nodes) {
|
|
31299
|
+
if (node.type === "math") {
|
|
31300
|
+
if (currentText) {
|
|
31301
|
+
result.push({
|
|
31302
|
+
type: "text",
|
|
31303
|
+
content: currentText
|
|
31304
|
+
});
|
|
31305
|
+
currentText = "";
|
|
31306
|
+
}
|
|
31307
|
+
result.push(node);
|
|
31308
|
+
} else {
|
|
31309
|
+
currentText += node.content;
|
|
31310
|
+
}
|
|
31311
|
+
}
|
|
31312
|
+
if (currentText) {
|
|
31313
|
+
result.push({
|
|
31314
|
+
type: "text",
|
|
31315
|
+
content: currentText
|
|
31316
|
+
});
|
|
31317
|
+
}
|
|
31318
|
+
return result;
|
|
31319
|
+
}
|
|
31320
|
+
|
|
31321
|
+
// Helper function for replaceOutsideTeX()
|
|
31322
|
+
function escapeSpecialChars(str) {
|
|
31323
|
+
// Escape $, \, {, and } characters
|
|
31324
|
+
return str.replace(/([$\\{}])/g, "\\$1");
|
|
31325
|
+
}
|
|
31326
|
+
|
|
31076
31327
|
function AxisLabels() {
|
|
31077
31328
|
const {
|
|
31078
31329
|
range,
|
|
@@ -31104,7 +31355,7 @@ function AxisLabels() {
|
|
|
31104
31355
|
fontSize: "14px",
|
|
31105
31356
|
transform: "translate(7px, -50%)"
|
|
31106
31357
|
}
|
|
31107
|
-
}, /*#__PURE__*/React__default.createElement(TeX, null, xAxisLabelText)), /*#__PURE__*/React__default.createElement("span", {
|
|
31358
|
+
}, /*#__PURE__*/React__default.createElement(TeX, null, replaceOutsideTeX(xAxisLabelText))), /*#__PURE__*/React__default.createElement("span", {
|
|
31108
31359
|
style: {
|
|
31109
31360
|
position: "absolute",
|
|
31110
31361
|
left: x2,
|
|
@@ -31112,7 +31363,7 @@ function AxisLabels() {
|
|
|
31112
31363
|
fontSize: "14px",
|
|
31113
31364
|
transform: "translate(-50%, 0px)"
|
|
31114
31365
|
}
|
|
31115
|
-
}, /*#__PURE__*/React__default.createElement(TeX, null, yAxisLabelText)));
|
|
31366
|
+
}, /*#__PURE__*/React__default.createElement(TeX, null, replaceOutsideTeX(yAxisLabelText))));
|
|
31116
31367
|
}
|
|
31117
31368
|
|
|
31118
31369
|
// The size of the ticks and labels in pixels
|
|
@@ -32046,107 +32297,6 @@ const lockedFigureFillStyles = {
|
|
|
32046
32297
|
|
|
32047
32298
|
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
32048
32299
|
|
|
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
32300
|
function LockedLabel(props) {
|
|
32151
32301
|
const {
|
|
32152
32302
|
coord,
|
|
@@ -33369,6 +33519,7 @@ function useControlPoint(params) {
|
|
|
33369
33519
|
} = useGraphConfig();
|
|
33370
33520
|
const {
|
|
33371
33521
|
point,
|
|
33522
|
+
sequenceNumber,
|
|
33372
33523
|
color,
|
|
33373
33524
|
cursor,
|
|
33374
33525
|
constrain = p => snap(snapStep, p),
|
|
@@ -33409,6 +33560,7 @@ function useControlPoint(params) {
|
|
|
33409
33560
|
ref: focusableHandleRef,
|
|
33410
33561
|
role: "button",
|
|
33411
33562
|
"aria-label": strings.srPointAtCoordinates({
|
|
33563
|
+
num: sequenceNumber,
|
|
33412
33564
|
x: srFormatNumber(point[X], locale),
|
|
33413
33565
|
y: srFormatNumber(point[Y], locale)
|
|
33414
33566
|
})
|
|
@@ -33487,6 +33639,7 @@ const MovableLine = props => {
|
|
|
33487
33639
|
focusableHandle: focusableHandle1
|
|
33488
33640
|
} = useControlPoint({
|
|
33489
33641
|
point: start,
|
|
33642
|
+
sequenceNumber: 1,
|
|
33490
33643
|
color,
|
|
33491
33644
|
onMove: p => onMovePoint(0, p)
|
|
33492
33645
|
});
|
|
@@ -33495,6 +33648,7 @@ const MovableLine = props => {
|
|
|
33495
33648
|
focusableHandle: focusableHandle2
|
|
33496
33649
|
} = useControlPoint({
|
|
33497
33650
|
point: end,
|
|
33651
|
+
sequenceNumber: 2,
|
|
33498
33652
|
color,
|
|
33499
33653
|
onMove: p => onMovePoint(1, p)
|
|
33500
33654
|
});
|
|
@@ -33674,14 +33828,17 @@ function AngleGraph(props) {
|
|
|
33674
33828
|
// Render the lines, angle, and then movable points
|
|
33675
33829
|
return /*#__PURE__*/React.createElement(React.Fragment, null, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33676
33830
|
point: coords[1],
|
|
33831
|
+
sequenceNumber: 1,
|
|
33677
33832
|
constrain: p => p,
|
|
33678
33833
|
onMove: destination => dispatch(actions.angle.movePoint(1, destination))
|
|
33679
33834
|
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33680
33835
|
point: coords[0],
|
|
33836
|
+
sequenceNumber: 2,
|
|
33681
33837
|
constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
|
|
33682
33838
|
onMove: destination => dispatch(actions.angle.movePoint(0, destination))
|
|
33683
33839
|
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33684
33840
|
point: coords[2],
|
|
33841
|
+
sequenceNumber: 3,
|
|
33685
33842
|
constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
|
|
33686
33843
|
onMove: destination => dispatch(actions.angle.movePoint(2, destination))
|
|
33687
33844
|
}));
|
|
@@ -33835,6 +33992,7 @@ function CircleGraph(props) {
|
|
|
33835
33992
|
onMove: c => dispatch(actions.circle.moveCenter(c))
|
|
33836
33993
|
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33837
33994
|
point: radiusPoint,
|
|
33995
|
+
sequenceNumber: 1,
|
|
33838
33996
|
cursor: "ew-resize",
|
|
33839
33997
|
onMove: newRadiusPoint => {
|
|
33840
33998
|
dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
|
|
@@ -34010,6 +34168,7 @@ function LimitedPointGraph(props) {
|
|
|
34010
34168
|
return /*#__PURE__*/React.createElement(React.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34011
34169
|
key: i,
|
|
34012
34170
|
point: point,
|
|
34171
|
+
sequenceNumber: i + 1,
|
|
34013
34172
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
|
|
34014
34173
|
})));
|
|
34015
34174
|
}
|
|
@@ -34052,6 +34211,7 @@ function UnlimitedPointGraph(props) {
|
|
|
34052
34211
|
}), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34053
34212
|
key: i,
|
|
34054
34213
|
point: point,
|
|
34214
|
+
sequenceNumber: i + 1,
|
|
34055
34215
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
34056
34216
|
ref: ref => {
|
|
34057
34217
|
itemsRef.current[i] = ref;
|
|
@@ -34082,7 +34242,8 @@ function describePointGraph(state, i18n) {
|
|
|
34082
34242
|
if (state.coords.length === 0) {
|
|
34083
34243
|
return strings.srNoInteractiveElements;
|
|
34084
34244
|
}
|
|
34085
|
-
const pointDescriptions = state.coords.map(([x, y]) => strings.srPointAtCoordinates({
|
|
34245
|
+
const pointDescriptions = state.coords.map(([x, y], index) => strings.srPointAtCoordinates({
|
|
34246
|
+
num: index + 1,
|
|
34086
34247
|
x: srFormatNumber(x, locale),
|
|
34087
34248
|
y: srFormatNumber(y, locale)
|
|
34088
34249
|
}));
|
|
@@ -34200,6 +34361,7 @@ const LimitedPolygonGraph = props => {
|
|
|
34200
34361
|
key: "point-" + i,
|
|
34201
34362
|
constrain: constrain,
|
|
34202
34363
|
point: point,
|
|
34364
|
+
sequenceNumber: i + 1,
|
|
34203
34365
|
onMove: destination => {
|
|
34204
34366
|
const now = Date.now();
|
|
34205
34367
|
const targetFPS = 40;
|
|
@@ -34346,6 +34508,7 @@ const UnlimitedPolygonGraph = props => {
|
|
|
34346
34508
|
}), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34347
34509
|
key: i,
|
|
34348
34510
|
point: point,
|
|
34511
|
+
sequenceNumber: i + 1,
|
|
34349
34512
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
34350
34513
|
ref: ref => {
|
|
34351
34514
|
pointRef.current[i] = ref;
|
|
@@ -34421,6 +34584,7 @@ function QuadraticGraph(props) {
|
|
|
34421
34584
|
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34422
34585
|
key: "point-" + i,
|
|
34423
34586
|
point: coord,
|
|
34587
|
+
sequenceNumber: i + 1,
|
|
34424
34588
|
onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
|
|
34425
34589
|
})));
|
|
34426
34590
|
}
|
|
@@ -34549,6 +34713,7 @@ function SinusoidGraph(props) {
|
|
|
34549
34713
|
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34550
34714
|
key: "point-" + i,
|
|
34551
34715
|
point: coord,
|
|
34716
|
+
sequenceNumber: i + 1,
|
|
34552
34717
|
onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
|
|
34553
34718
|
})));
|
|
34554
34719
|
}
|
|
@@ -44677,7 +44842,7 @@ const init = function init() {
|
|
|
44677
44842
|
|
|
44678
44843
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44679
44844
|
const libName = "@khanacademy/perseus";
|
|
44680
|
-
const libVersion = "
|
|
44845
|
+
const libVersion = "42.0.0";
|
|
44681
44846
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44682
44847
|
|
|
44683
44848
|
const apiVersion = {
|
|
@@ -45301,6 +45466,13 @@ class ServerItemRenderer extends React.Component {
|
|
|
45301
45466
|
this.props.onRendered(true);
|
|
45302
45467
|
}
|
|
45303
45468
|
}
|
|
45469
|
+
if (this.props.score && this.props.score !== prevProps.score) {
|
|
45470
|
+
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45471
|
+
this.setState({
|
|
45472
|
+
questionCompleted: this.props.score.correct,
|
|
45473
|
+
questionHighlightedWidgets: emptyQuestionAreaWidgets
|
|
45474
|
+
});
|
|
45475
|
+
}
|
|
45304
45476
|
}
|
|
45305
45477
|
componentWillUnmount() {
|
|
45306
45478
|
if (this.blurTimeoutID != null) {
|
|
@@ -45418,18 +45590,37 @@ class ServerItemRenderer extends React.Component {
|
|
|
45418
45590
|
return this.questionRenderer.getPromptJSON();
|
|
45419
45591
|
}
|
|
45420
45592
|
|
|
45593
|
+
/**
|
|
45594
|
+
* Returns an array of the widget `.getUserInput()` results
|
|
45595
|
+
*
|
|
45596
|
+
* TODO: can we remove this? Seems to be just for backwards
|
|
45597
|
+
* compatibility with old Perseus Chrome logging
|
|
45598
|
+
* @deprecated use getUserInput
|
|
45599
|
+
*/
|
|
45600
|
+
getUserInputLegacy() {
|
|
45601
|
+
return this.questionRenderer.getUserInput();
|
|
45602
|
+
}
|
|
45603
|
+
|
|
45604
|
+
/**
|
|
45605
|
+
* Returns an object of the widget `.getUserInput()` results
|
|
45606
|
+
*/
|
|
45607
|
+
getUserInput() {
|
|
45608
|
+
return this.questionRenderer.getUserInputMap();
|
|
45609
|
+
}
|
|
45610
|
+
|
|
45421
45611
|
/**
|
|
45422
45612
|
* Grades the item.
|
|
45613
|
+
*
|
|
45614
|
+
* @deprecated use scorePerseusItem
|
|
45423
45615
|
*/
|
|
45424
45616
|
scoreInput() {
|
|
45425
|
-
const
|
|
45426
|
-
const
|
|
45427
|
-
const score = guessAndScore[1];
|
|
45617
|
+
const guess = this.getUserInput();
|
|
45618
|
+
const score = scorePerseusItem(this.props.item.question, guess, this.context.strings, this.context.locale);
|
|
45428
45619
|
|
|
45429
45620
|
// Continue to include an empty guess for the now defunct answer area.
|
|
45430
45621
|
// TODO(alex): Check whether we rely on the format here for
|
|
45431
45622
|
// analyzing ProblemLogs. If not, remove this layer.
|
|
45432
|
-
const maxCompatGuess = [
|
|
45623
|
+
const maxCompatGuess = [this.questionRenderer.getUserInput(), []];
|
|
45433
45624
|
const keScore = Util.keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
|
|
45434
45625
|
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45435
45626
|
this.setState({
|
|
@@ -47277,140 +47468,6 @@ const registerAllWidgetsForTesting = () => {
|
|
|
47277
47468
|
replaceDeprecatedWidgets();
|
|
47278
47469
|
};
|
|
47279
47470
|
|
|
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
47471
|
/**
|
|
47415
47472
|
* This function extracts the answers from the widgets.
|
|
47416
47473
|
*
|
|
@@ -49355,5 +49412,5 @@ const GrapherUtil = {
|
|
|
49355
49412
|
typeToButton
|
|
49356
49413
|
};
|
|
49357
49414
|
|
|
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 };
|
|
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 };
|
|
49359
49416
|
//# sourceMappingURL=index.js.map
|