@khanacademy/perseus 44.0.0 → 45.1.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/components/text-input.d.ts +1 -0
- package/dist/es/index.css +169 -12
- package/dist/es/index.js +473 -286
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +169 -12
- package/dist/index.d.ts +3 -1
- package/dist/index.js +475 -281
- package/dist/index.js.map +1 -1
- package/dist/interactive2/objective_.d.ts +27 -6
- package/dist/perseus-types.d.ts +123 -72
- package/dist/renderer-util.d.ts +4 -2
- package/dist/renderer.d.ts +13 -11
- package/dist/server-item-renderer.d.ts +32 -1
- package/dist/types.d.ts +63 -16
- package/dist/util/parse-perseus-json/general-purpose-parsers/string-to-number.d.ts +2 -0
- package/dist/validation.types.d.ts +8 -3
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +1 -1
- package/dist/widgets/expression/expression.d.ts +1 -1
- package/dist/widgets/group/group.d.ts +3 -1
- package/dist/widgets/group/score-group.d.ts +5 -0
- package/dist/widgets/interactive-graph.d.ts +96 -3
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +2 -2
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +3 -3
- package/dist/widgets/number-line/score-number-line.d.ts +2 -2
- package/dist/widgets/number-line/validate-number-line.d.ts +12 -0
- package/dist/widgets/numeric-input/numeric-input.d.ts +2 -1
- package/package.json +1 -1
package/dist/es/index.js
CHANGED
|
@@ -3797,6 +3797,7 @@ Zoom.prototype.zoomImage = function () {
|
|
|
3797
3797
|
this._zoomOriginal();
|
|
3798
3798
|
}.bind(this);
|
|
3799
3799
|
img.src = this._targetImage.src;
|
|
3800
|
+
img.alt = this._targetImage.alt;
|
|
3800
3801
|
this.$zoomedImage = $zoomedImage;
|
|
3801
3802
|
};
|
|
3802
3803
|
Zoom.prototype._zoomOriginal = function () {
|
|
@@ -8872,7 +8873,10 @@ var Tex$1 = {
|
|
|
8872
8873
|
// We use createElement instead of JSX here because we can't name this file tex.tsx;
|
|
8873
8874
|
// that name is already taken.
|
|
8874
8875
|
// eslint-disable-next-line import/no-deprecated
|
|
8875
|
-
render(
|
|
8876
|
+
render(
|
|
8877
|
+
/*#__PURE__*/
|
|
8878
|
+
// eslint-disable-next-line react/no-children-prop
|
|
8879
|
+
React.createElement(TeX, {
|
|
8876
8880
|
children: text,
|
|
8877
8881
|
onRender: callback
|
|
8878
8882
|
}), $texHolder[0]);
|
|
@@ -14609,36 +14613,15 @@ const pluck = function pluck(table, subKey) {
|
|
|
14609
14613
|
* });
|
|
14610
14614
|
* {a: 2, b: 3}
|
|
14611
14615
|
*/
|
|
14612
|
-
const mapObject
|
|
14616
|
+
const mapObject = function mapObject(obj, lambda) {
|
|
14613
14617
|
const result = {};
|
|
14614
|
-
|
|
14618
|
+
Object.keys(obj).forEach(key => {
|
|
14615
14619
|
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
|
|
14616
14620
|
result[key] = lambda(obj[key], key);
|
|
14617
14621
|
});
|
|
14618
14622
|
return result;
|
|
14619
14623
|
};
|
|
14620
14624
|
|
|
14621
|
-
/**
|
|
14622
|
-
* Maps an array to an object
|
|
14623
|
-
*
|
|
14624
|
-
* > mapObjectFromArray(['a', 'b'], function(elem) {
|
|
14625
|
-
* return elem + elem;
|
|
14626
|
-
* });
|
|
14627
|
-
* {a: 'aa', b: 'bb'}
|
|
14628
|
-
*/
|
|
14629
|
-
const mapObjectFromArray = function mapObjectFromArray(arr, lambda) {
|
|
14630
|
-
const result = {};
|
|
14631
|
-
_.each(arr, function (elem) {
|
|
14632
|
-
result[elem] = lambda(elem);
|
|
14633
|
-
});
|
|
14634
|
-
return result;
|
|
14635
|
-
};
|
|
14636
|
-
var objective_ = {
|
|
14637
|
-
pluck: pluck,
|
|
14638
|
-
mapObject: mapObject$1,
|
|
14639
|
-
mapObjectFromArray: mapObjectFromArray
|
|
14640
|
-
};
|
|
14641
|
-
|
|
14642
14625
|
/**
|
|
14643
14626
|
* MovableLine
|
|
14644
14627
|
*/
|
|
@@ -14703,7 +14686,7 @@ _.extend(MovableLine$4.prototype, {
|
|
|
14703
14686
|
// - are objects, not primitives (and need a deeper copy)
|
|
14704
14687
|
// - they don't need getters created for them
|
|
14705
14688
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
14706
|
-
|
|
14689
|
+
pluck(MovableLineOptions, "standard")), DEFAULT_PROPS$2);
|
|
14707
14690
|
},
|
|
14708
14691
|
/**
|
|
14709
14692
|
* Resets the object to its state as if it were constructed with
|
|
@@ -15203,7 +15186,7 @@ class MovablePoint$5 {
|
|
|
15203
15186
|
// - are objects, not primitives (and need a deeper copy)
|
|
15204
15187
|
// - they don't need getters created for them
|
|
15205
15188
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
15206
|
-
|
|
15189
|
+
pluck(MovablePointOptions, "standard")
|
|
15207
15190
|
// We only update props here, because we want things on state to
|
|
15208
15191
|
// be persistent, and updated appropriately in modify()
|
|
15209
15192
|
), DEFAULT_PROPS$1);
|
|
@@ -16094,7 +16077,7 @@ _.extend(MovablePolygon.prototype, {
|
|
|
16094
16077
|
// - are objects, not primitives (and need a deeper copy)
|
|
16095
16078
|
// - they don't need getters created for them
|
|
16096
16079
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
16097
|
-
|
|
16080
|
+
pluck(MovablePolygonOptions, "standard")
|
|
16098
16081
|
|
|
16099
16082
|
// We only update props here, because we want things on state to
|
|
16100
16083
|
// be persistent, and updated appropriately in modify()
|
|
@@ -18719,6 +18702,140 @@ class QuestionParagraph extends React.Component {
|
|
|
18719
18702
|
}
|
|
18720
18703
|
}
|
|
18721
18704
|
|
|
18705
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
18706
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
18707
|
+
|
|
18708
|
+
/**
|
|
18709
|
+
* Regex for widget placeholders in a string.
|
|
18710
|
+
*
|
|
18711
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
18712
|
+
* Second capture group is the widget type (ex. "radio)
|
|
18713
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
18714
|
+
*/
|
|
18715
|
+
function getWidgetRegex() {
|
|
18716
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
18717
|
+
}
|
|
18718
|
+
|
|
18719
|
+
/**
|
|
18720
|
+
* Add a widget placeholder using the widget ID.
|
|
18721
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
18722
|
+
*
|
|
18723
|
+
* @param {string} id
|
|
18724
|
+
* @returns {string}
|
|
18725
|
+
*/
|
|
18726
|
+
function addWidget(id) {
|
|
18727
|
+
return `[[☃ ${id}]]`;
|
|
18728
|
+
}
|
|
18729
|
+
|
|
18730
|
+
/**
|
|
18731
|
+
* Get a widget type by a widget's ID
|
|
18732
|
+
* (please don't derive type from ID)
|
|
18733
|
+
*
|
|
18734
|
+
* @param {string} widgetId the ID of the widget
|
|
18735
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18736
|
+
* @returns {string} the widget type (ie "radio")
|
|
18737
|
+
*/
|
|
18738
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
18739
|
+
var _widget$type;
|
|
18740
|
+
const widget = widgetMap[WidgetId];
|
|
18741
|
+
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
18742
|
+
}
|
|
18743
|
+
|
|
18744
|
+
/**
|
|
18745
|
+
* Does the content have a specific type of widget?
|
|
18746
|
+
*
|
|
18747
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
18748
|
+
* @param {string} content the string to search through
|
|
18749
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18750
|
+
* @returns {boolean} if the content includes the widget type
|
|
18751
|
+
*/
|
|
18752
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
18753
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
18754
|
+
}
|
|
18755
|
+
|
|
18756
|
+
/**
|
|
18757
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
18758
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
18759
|
+
*
|
|
18760
|
+
* Content should contain Perseus widget placeholders,
|
|
18761
|
+
* which look like: '[[☃ radio 1]]'.
|
|
18762
|
+
*
|
|
18763
|
+
* @param {string} content
|
|
18764
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
18765
|
+
*/
|
|
18766
|
+
function getWidgetIdsFromContent(content) {
|
|
18767
|
+
const widgets = [];
|
|
18768
|
+
const localWidgetRegex = getWidgetRegex();
|
|
18769
|
+
let match = localWidgetRegex.exec(content);
|
|
18770
|
+
while (match !== null) {
|
|
18771
|
+
widgets.push(match[1]);
|
|
18772
|
+
match = localWidgetRegex.exec(content);
|
|
18773
|
+
}
|
|
18774
|
+
return widgets;
|
|
18775
|
+
}
|
|
18776
|
+
|
|
18777
|
+
/**
|
|
18778
|
+
* Get a list of widget IDs from content,
|
|
18779
|
+
* but only for specific widget types
|
|
18780
|
+
*
|
|
18781
|
+
* @param {string} type the type of widget (ie "radio")
|
|
18782
|
+
* @param {string} content the string to parse
|
|
18783
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18784
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
18785
|
+
*/
|
|
18786
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
18787
|
+
const rv = [];
|
|
18788
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
18789
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
18790
|
+
const widget = widgetMap[widgetId];
|
|
18791
|
+
if ((widget == null ? void 0 : widget.type) === type) {
|
|
18792
|
+
rv.push(widgetId);
|
|
18793
|
+
}
|
|
18794
|
+
});
|
|
18795
|
+
return rv;
|
|
18796
|
+
}
|
|
18797
|
+
|
|
18798
|
+
/**
|
|
18799
|
+
* Pull the widget map out of ItemData
|
|
18800
|
+
*
|
|
18801
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
18802
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
18803
|
+
*/
|
|
18804
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
18805
|
+
return itemData.question.widgets;
|
|
18806
|
+
}
|
|
18807
|
+
|
|
18808
|
+
/**
|
|
18809
|
+
* Get the widget information from a WidgetMap
|
|
18810
|
+
*
|
|
18811
|
+
* @param {string} widgetId the ID of the widget
|
|
18812
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
18813
|
+
*/
|
|
18814
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
18815
|
+
var _widgetMap$widgetId;
|
|
18816
|
+
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
18817
|
+
}
|
|
18818
|
+
|
|
18819
|
+
/**
|
|
18820
|
+
* Get select widgets from a widget map.
|
|
18821
|
+
* Useful for multi-items that needs to split
|
|
18822
|
+
* one PerseusItem into several
|
|
18823
|
+
*
|
|
18824
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
18825
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
18826
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
18827
|
+
*/
|
|
18828
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
18829
|
+
const widgets = {};
|
|
18830
|
+
widgetIds.forEach(widgetId => {
|
|
18831
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
18832
|
+
if (widget) {
|
|
18833
|
+
widgets[widgetId] = widget;
|
|
18834
|
+
}
|
|
18835
|
+
});
|
|
18836
|
+
return widgets;
|
|
18837
|
+
}
|
|
18838
|
+
|
|
18722
18839
|
const DEFAULT_ALIGNMENT$1 = "block";
|
|
18723
18840
|
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
18724
18841
|
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
@@ -19150,36 +19267,64 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19150
19267
|
isLintable: isLintable
|
|
19151
19268
|
});
|
|
19152
19269
|
|
|
19270
|
+
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
19271
|
+
// @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
|
|
19272
|
+
return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
19273
|
+
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19274
|
+
const newValues = {};
|
|
19275
|
+
if (!widgetInfo.type) {
|
|
19276
|
+
// TODO: why does widget have no type?
|
|
19277
|
+
// We don't want to derive type from widget ID
|
|
19278
|
+
// see: LEMS-1845
|
|
19279
|
+
newValues.type = widgetId.split(" ")[0];
|
|
19280
|
+
}
|
|
19281
|
+
if (!widgetInfo.alignment) {
|
|
19282
|
+
newValues.alignment = "default";
|
|
19283
|
+
}
|
|
19284
|
+
widgetInfo = _extends({}, widgetInfo, newValues);
|
|
19285
|
+
}
|
|
19286
|
+
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
19287
|
+
});
|
|
19288
|
+
}
|
|
19153
19289
|
function emptyWidgetsFunctional(widgets,
|
|
19154
19290
|
// This is a port of old code, I'm not sure why
|
|
19155
19291
|
// we need widgetIds vs the keys of the widgets object
|
|
19156
19292
|
widgetIds, userInputMap, strings, locale) {
|
|
19293
|
+
const upgradedWidgets = getUpgradedWidgetOptions(widgets);
|
|
19157
19294
|
return widgetIds.filter(id => {
|
|
19158
|
-
const widget =
|
|
19295
|
+
const widget = upgradedWidgets[id];
|
|
19159
19296
|
if (!widget || widget.static) {
|
|
19160
19297
|
// Static widgets shouldn't count as empty
|
|
19161
19298
|
return false;
|
|
19162
19299
|
}
|
|
19163
|
-
let score = null;
|
|
19164
|
-
const userInput = userInputMap[id];
|
|
19165
19300
|
const scorer = getWidgetScorer(widget.type);
|
|
19166
|
-
|
|
19167
|
-
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19168
|
-
score = Util.flattenScores(scores);
|
|
19169
|
-
} else if (scorer) {
|
|
19170
|
-
score = scorer(userInput, widget.options, strings, locale);
|
|
19171
|
-
}
|
|
19301
|
+
const score = scorer == null ? void 0 : scorer(userInputMap[id], widget.options, strings, locale);
|
|
19172
19302
|
if (score) {
|
|
19173
19303
|
return Util.scoreIsEmpty(score);
|
|
19174
19304
|
}
|
|
19175
19305
|
});
|
|
19176
19306
|
}
|
|
19307
|
+
|
|
19308
|
+
// TODO: combine scorePerseusItem with scoreWidgetsFunctional
|
|
19309
|
+
// once scorePerseusItem is the only one calling scoreWidgetsFunctional
|
|
19310
|
+
function scorePerseusItem(perseusRenderData, userInputMap,
|
|
19311
|
+
// TODO(LEMS-2461,LEMS-2391): these probably
|
|
19312
|
+
// need to be removed before we move this to the server
|
|
19313
|
+
strings, locale) {
|
|
19314
|
+
// There seems to be a chance that PerseusRenderer.widgets might include
|
|
19315
|
+
// widget data for widgets that are not in PerseusRenderer.content,
|
|
19316
|
+
// so this checks that the widgets are being used before scoring them
|
|
19317
|
+
const usedWidgetIds = getWidgetIdsFromContent(perseusRenderData.content);
|
|
19318
|
+
const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
|
|
19319
|
+
return Util.flattenScores(scores);
|
|
19320
|
+
}
|
|
19177
19321
|
function scoreWidgetsFunctional(widgets,
|
|
19178
19322
|
// This is a port of old code, I'm not sure why
|
|
19179
19323
|
// we need widgetIds vs the keys of the widgets object
|
|
19180
19324
|
widgetIds, userInputMap, strings, locale) {
|
|
19325
|
+
const upgradedWidgets = getUpgradedWidgetOptions(widgets);
|
|
19181
19326
|
const gradedWidgetIds = widgetIds.filter(id => {
|
|
19182
|
-
const props =
|
|
19327
|
+
const props = upgradedWidgets[id];
|
|
19183
19328
|
const widgetIsGraded = (props == null ? void 0 : props.graded) == null || props.graded;
|
|
19184
19329
|
const widgetIsStatic = !!(props != null && props.static);
|
|
19185
19330
|
// Ungraded widgets or widgets set to static shouldn't be graded.
|
|
@@ -19187,17 +19332,15 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19187
19332
|
});
|
|
19188
19333
|
const widgetScores = {};
|
|
19189
19334
|
gradedWidgetIds.forEach(id => {
|
|
19190
|
-
const widget =
|
|
19335
|
+
const widget = upgradedWidgets[id];
|
|
19191
19336
|
if (!widget) {
|
|
19192
19337
|
return;
|
|
19193
19338
|
}
|
|
19194
19339
|
const userInput = userInputMap[id];
|
|
19195
19340
|
const scorer = getWidgetScorer(widget.type);
|
|
19196
|
-
|
|
19197
|
-
|
|
19198
|
-
widgetScores[id] =
|
|
19199
|
-
} else if (scorer) {
|
|
19200
|
-
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
19341
|
+
const score = scorer == null ? void 0 : scorer(userInput, widget.options, strings, locale);
|
|
19342
|
+
if (score != null) {
|
|
19343
|
+
widgetScores[id] = score;
|
|
19201
19344
|
}
|
|
19202
19345
|
});
|
|
19203
19346
|
return widgetScores;
|
|
@@ -19503,9 +19646,6 @@ WidgetContainer.defaultProps = {
|
|
|
19503
19646
|
linterContext: linterContextDefault
|
|
19504
19647
|
};
|
|
19505
19648
|
|
|
19506
|
-
const {
|
|
19507
|
-
mapObject
|
|
19508
|
-
} = objective_;
|
|
19509
19649
|
const rContainsNonWhitespace = /\S/;
|
|
19510
19650
|
const rImageURL = /(web\+graphie|https):\/\/[^\s]*/;
|
|
19511
19651
|
const noopOnRender = () => {};
|
|
@@ -19556,31 +19696,12 @@ class Renderer extends React.Component {
|
|
|
19556
19696
|
return _extends({}, ApiOptions.defaults, this.props.apiOptions);
|
|
19557
19697
|
};
|
|
19558
19698
|
this._getInitialWidgetState = props => {
|
|
19559
|
-
const allWidgetInfo =
|
|
19699
|
+
const allWidgetInfo = getUpgradedWidgetOptions(props.widgets);
|
|
19560
19700
|
return {
|
|
19561
19701
|
widgetInfo: allWidgetInfo,
|
|
19562
19702
|
widgetProps: this._getAllWidgetsStartProps(allWidgetInfo, props)
|
|
19563
19703
|
};
|
|
19564
19704
|
};
|
|
19565
|
-
// @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
|
|
19566
|
-
this._getAllWidgetsInfo = props => {
|
|
19567
|
-
return mapObject(props.widgets, (widgetInfo, widgetId) => {
|
|
19568
|
-
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19569
|
-
const newValues = {};
|
|
19570
|
-
if (!widgetInfo.type) {
|
|
19571
|
-
// TODO: why does widget have no type?
|
|
19572
|
-
// We don't want to derive type from widget ID
|
|
19573
|
-
// see: LEMS-1845
|
|
19574
|
-
newValues.type = widgetId.split(" ")[0];
|
|
19575
|
-
}
|
|
19576
|
-
if (!widgetInfo.alignment) {
|
|
19577
|
-
newValues.alignment = "default";
|
|
19578
|
-
}
|
|
19579
|
-
widgetInfo = _.extend({}, widgetInfo, newValues);
|
|
19580
|
-
}
|
|
19581
|
-
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
19582
|
-
});
|
|
19583
|
-
};
|
|
19584
19705
|
this._getAllWidgetsStartProps = (allWidgetInfo, props) => {
|
|
19585
19706
|
const {
|
|
19586
19707
|
apiOptions,
|
|
@@ -20026,7 +20147,7 @@ class Renderer extends React.Component {
|
|
|
20026
20147
|
// Instead, we center them with css magic in styles.less
|
|
20027
20148
|
// /cry(aria)
|
|
20028
20149
|
this._foundTextNodes = true;
|
|
20029
|
-
if (
|
|
20150
|
+
if (this.widgetIds.includes(node.id)) {
|
|
20030
20151
|
// We don't want to render a duplicate widget key/ref,
|
|
20031
20152
|
// as this causes problems with react (for obvious
|
|
20032
20153
|
// reasons). Instead we just notify the
|
|
@@ -20330,7 +20451,7 @@ class Renderer extends React.Component {
|
|
|
20330
20451
|
};
|
|
20331
20452
|
this.getInputPaths = () => {
|
|
20332
20453
|
const inputPaths = [];
|
|
20333
|
-
|
|
20454
|
+
this.widgetIds.forEach(widgetId => {
|
|
20334
20455
|
const widget = this.getWidgetInstance(widgetId);
|
|
20335
20456
|
if (widget && widget.getInputPaths) {
|
|
20336
20457
|
// Grab all input paths and add widgetID to the front
|
|
@@ -20338,7 +20459,7 @@ class Renderer extends React.Component {
|
|
|
20338
20459
|
// Prefix paths with their widgetID and add to collective
|
|
20339
20460
|
// list of paths.
|
|
20340
20461
|
// @ts-expect-error - TS2345 - Argument of type '(inputPath: string) => void' is not assignable to parameter of type 'CollectionIterator<FocusPath, void, readonly FocusPath[]>'.
|
|
20341
|
-
|
|
20462
|
+
widgetInputPaths.forEach(inputPath => {
|
|
20342
20463
|
const relativeInputPath = [widgetId].concat(inputPath);
|
|
20343
20464
|
inputPaths.push(relativeInputPath);
|
|
20344
20465
|
});
|
|
@@ -20450,7 +20571,7 @@ class Renderer extends React.Component {
|
|
|
20450
20571
|
}, 0);
|
|
20451
20572
|
});
|
|
20452
20573
|
};
|
|
20453
|
-
this.setInputValue = (path, newValue,
|
|
20574
|
+
this.setInputValue = (path, newValue, cb) => {
|
|
20454
20575
|
// @ts-expect-error - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
|
|
20455
20576
|
const widgetId = _.first(path);
|
|
20456
20577
|
// @ts-expect-error - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
|
|
@@ -20458,7 +20579,7 @@ class Renderer extends React.Component {
|
|
|
20458
20579
|
const widget = this.getWidgetInstance(widgetId);
|
|
20459
20580
|
|
|
20460
20581
|
// Widget handles parsing of the interWidgetPath.
|
|
20461
|
-
widget == null || widget.setInputValue == null || widget.setInputValue(interWidgetPath, newValue,
|
|
20582
|
+
widget == null || widget.setInputValue == null || widget.setInputValue(interWidgetPath, newValue, cb);
|
|
20462
20583
|
};
|
|
20463
20584
|
/**
|
|
20464
20585
|
* Returns an array of all widget IDs in the order they occur in
|
|
@@ -20467,12 +20588,20 @@ class Renderer extends React.Component {
|
|
|
20467
20588
|
this.getWidgetIds = () => {
|
|
20468
20589
|
return this.widgetIds;
|
|
20469
20590
|
};
|
|
20591
|
+
/**
|
|
20592
|
+
* @deprecated use scorePerseusItem
|
|
20593
|
+
*/
|
|
20594
|
+
this.guessAndScore = () => {
|
|
20595
|
+
const totalGuess = this.getUserInput();
|
|
20596
|
+
const totalScore = this.score();
|
|
20597
|
+
return [totalGuess, totalScore];
|
|
20598
|
+
};
|
|
20470
20599
|
this.examples = () => {
|
|
20471
20600
|
const widgetIds = this.widgetIds;
|
|
20472
|
-
const examples =
|
|
20601
|
+
const examples = widgetIds.map(widgetId => {
|
|
20473
20602
|
const widget = this.getWidgetInstance(widgetId);
|
|
20474
20603
|
return widget != null && widget.examples ? widget.examples() : null;
|
|
20475
|
-
}));
|
|
20604
|
+
}).filter(Boolean);
|
|
20476
20605
|
|
|
20477
20606
|
// no widgets with examples
|
|
20478
20607
|
if (!examples.length) {
|
|
@@ -20671,6 +20800,12 @@ class Renderer extends React.Component {
|
|
|
20671
20800
|
isLastUsedWidget: widgetId === this.state.lastUsedWidgetId
|
|
20672
20801
|
});
|
|
20673
20802
|
}
|
|
20803
|
+
/**
|
|
20804
|
+
* Returns an array of widget ids that are empty (meaning widgets where the
|
|
20805
|
+
* learner has not interacted with the widget yet or has not filled in all
|
|
20806
|
+
* fields). For example, the `interactive-graph` widget is considered
|
|
20807
|
+
* empty if the graph is in the starting state.
|
|
20808
|
+
*/
|
|
20674
20809
|
emptyWidgets() {
|
|
20675
20810
|
return emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20676
20811
|
}
|
|
@@ -20727,27 +20862,15 @@ class Renderer extends React.Component {
|
|
|
20727
20862
|
}
|
|
20728
20863
|
|
|
20729
20864
|
/**
|
|
20730
|
-
*
|
|
20731
|
-
*
|
|
20732
|
-
*
|
|
20733
|
-
*/
|
|
20734
|
-
scoreWidgets() {
|
|
20735
|
-
return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20736
|
-
}
|
|
20737
|
-
|
|
20738
|
-
/**
|
|
20739
|
-
* Grades the content.
|
|
20865
|
+
* Scores the content.
|
|
20866
|
+
*
|
|
20867
|
+
* @deprecated use scorePerseusItem
|
|
20740
20868
|
*/
|
|
20741
20869
|
score() {
|
|
20742
|
-
const scores = this.
|
|
20870
|
+
const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20743
20871
|
const combinedScore = Util.flattenScores(scores);
|
|
20744
20872
|
return combinedScore;
|
|
20745
20873
|
}
|
|
20746
|
-
guessAndScore() {
|
|
20747
|
-
const totalGuess = this.getUserInput();
|
|
20748
|
-
const totalScore = this.score();
|
|
20749
|
-
return [totalGuess, totalScore];
|
|
20750
|
-
}
|
|
20751
20874
|
render() {
|
|
20752
20875
|
const apiOptions = this.getApiOptions();
|
|
20753
20876
|
if (this.reuseMarkdown) {
|
|
@@ -20947,6 +21070,7 @@ class TextInput extends React.Component {
|
|
|
20947
21070
|
value: formattedValue,
|
|
20948
21071
|
type: "text",
|
|
20949
21072
|
"aria-label": labelText,
|
|
21073
|
+
"aria-describedby": this.props["aria-describedby"],
|
|
20950
21074
|
onChange: value => this.props.onChange(value),
|
|
20951
21075
|
placeholder: placeholder,
|
|
20952
21076
|
onFocus: onFocus,
|
|
@@ -21213,6 +21337,7 @@ class Tooltip extends React.Component {
|
|
|
21213
21337
|
}, /*#__PURE__*/React.createElement("div", {
|
|
21214
21338
|
ref: this.tooltipContainerRef,
|
|
21215
21339
|
className: "tooltipContainer",
|
|
21340
|
+
role: "tooltip",
|
|
21216
21341
|
style: {
|
|
21217
21342
|
position: "absolute",
|
|
21218
21343
|
// height must start out undefined, not null, so that
|
|
@@ -21293,9 +21418,15 @@ class InputWithExamples extends React.Component {
|
|
|
21293
21418
|
};
|
|
21294
21419
|
this._renderInput = () => {
|
|
21295
21420
|
const id = this._getUniqueId();
|
|
21421
|
+
const ariaId = `aria-for-${id}`;
|
|
21422
|
+
// Generate text from a known set of format options that will read well in a screen reader
|
|
21423
|
+
const examplesAria = this.props.examples.length === 7 ? "" : `${this.props.examples[0]}
|
|
21424
|
+
${this.props.examples.slice(1).join(", or\n")}`
|
|
21425
|
+
// @ts-expect-error TS2550: Property replaceAll does not exist on type string.
|
|
21426
|
+
.replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ");
|
|
21296
21427
|
const inputProps = {
|
|
21297
21428
|
id: id,
|
|
21298
|
-
"aria-describedby":
|
|
21429
|
+
"aria-describedby": ariaId,
|
|
21299
21430
|
ref: "input",
|
|
21300
21431
|
className: this._getInputClassName(),
|
|
21301
21432
|
labelText: this.props.labelText,
|
|
@@ -21311,7 +21442,12 @@ class InputWithExamples extends React.Component {
|
|
|
21311
21442
|
autoCorrect: "off",
|
|
21312
21443
|
spellCheck: "false"
|
|
21313
21444
|
};
|
|
21314
|
-
return /*#__PURE__*/React.createElement(TextInput, inputProps)
|
|
21445
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextInput, inputProps), /*#__PURE__*/React.createElement("span", {
|
|
21446
|
+
id: ariaId,
|
|
21447
|
+
style: {
|
|
21448
|
+
display: "none"
|
|
21449
|
+
}
|
|
21450
|
+
}, examplesAria));
|
|
21315
21451
|
};
|
|
21316
21452
|
this._handleFocus = () => {
|
|
21317
21453
|
this.props.onFocus();
|
|
@@ -21353,8 +21489,12 @@ class InputWithExamples extends React.Component {
|
|
|
21353
21489
|
}
|
|
21354
21490
|
render() {
|
|
21355
21491
|
const input = this._renderInput();
|
|
21356
|
-
const examplesContent =
|
|
21357
|
-
|
|
21492
|
+
const examplesContent = this.props.examples.length <= 2 ? this.props.examples.join(" ") // A single item (with or without leading text) is not a "list"
|
|
21493
|
+
: this.props.examples // 2 or more items should display as a list
|
|
21494
|
+
.map((example, index) => {
|
|
21495
|
+
// If the first example is bold, then it is most likely a heading/leading text.
|
|
21496
|
+
// So, it shouldn't be part of the list.
|
|
21497
|
+
return index === 0 && example.startsWith("**") ? `${example}\n` : `- ${example}`;
|
|
21358
21498
|
}).join("\n");
|
|
21359
21499
|
const showExamples = this.props.shouldShowExamples && this.state.showExamples;
|
|
21360
21500
|
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
@@ -22028,7 +22168,7 @@ function scoreNumericInput(userInput, rubric, strings) {
|
|
|
22028
22168
|
// We may have received TeX; try to parse it before grading.
|
|
22029
22169
|
// If `currentValue` is not TeX, this should be a no-op.
|
|
22030
22170
|
const currentValue = ParseTex(userInput.currentValue);
|
|
22031
|
-
const normalizedAnswerExpected = rubric.answers.filter(answer => answer.status === "correct").every(answer => Math.abs(answer.value) <= 1);
|
|
22171
|
+
const normalizedAnswerExpected = rubric.answers.filter(answer => answer.status === "correct").every(answer => answer.value !== undefined && Math.abs(answer.value) <= 1);
|
|
22032
22172
|
|
|
22033
22173
|
// The coefficient is an attribute of the widget
|
|
22034
22174
|
let localValue = currentValue;
|
|
@@ -22089,7 +22229,8 @@ class NumericInput extends React.Component {
|
|
|
22089
22229
|
this.state = {
|
|
22090
22230
|
// keeps track of the other set of values when switching
|
|
22091
22231
|
// between 0 and finite solutions
|
|
22092
|
-
previousValues: [""]
|
|
22232
|
+
previousValues: [""],
|
|
22233
|
+
isFocused: false
|
|
22093
22234
|
};
|
|
22094
22235
|
// TODO(Nicole, Jeremy): This is maybe never used and should be removed
|
|
22095
22236
|
this.examples = () => {
|
|
@@ -22152,9 +22293,19 @@ class NumericInput extends React.Component {
|
|
|
22152
22293
|
};
|
|
22153
22294
|
this._handleFocus = () => {
|
|
22154
22295
|
this.props.onFocus([]);
|
|
22296
|
+
this.setState(currentState => {
|
|
22297
|
+
return _extends({}, currentState, {
|
|
22298
|
+
isFocused: true
|
|
22299
|
+
});
|
|
22300
|
+
});
|
|
22155
22301
|
};
|
|
22156
22302
|
this._handleBlur = () => {
|
|
22157
22303
|
this.props.onBlur([]);
|
|
22304
|
+
this.setState(currentState => {
|
|
22305
|
+
return _extends({}, currentState, {
|
|
22306
|
+
isFocused: false
|
|
22307
|
+
});
|
|
22308
|
+
});
|
|
22158
22309
|
};
|
|
22159
22310
|
}
|
|
22160
22311
|
static getUserInputFromProps(props) {
|
|
@@ -22197,13 +22348,20 @@ class NumericInput extends React.Component {
|
|
|
22197
22348
|
// component.
|
|
22198
22349
|
const styles = StyleSheet.create({
|
|
22199
22350
|
input: {
|
|
22351
|
+
borderRadius: "3px",
|
|
22352
|
+
borderWidth: this.state.isFocused ? "2px" : "1px",
|
|
22353
|
+
display: "inline-block",
|
|
22354
|
+
fontFamily: `Symbola, "Times New Roman", serif`,
|
|
22355
|
+
fontSize: "18px",
|
|
22356
|
+
height: "32px",
|
|
22357
|
+
lineHeight: "18px",
|
|
22358
|
+
padding: this.state.isFocused ? "4px" : "4px 5px",
|
|
22359
|
+
// account for added focus border thickness
|
|
22200
22360
|
textAlign: this.props.rightAlign ? "right" : "left",
|
|
22201
|
-
width: this.props.size === "small" ? 40 : 80
|
|
22202
|
-
padding: 0,
|
|
22203
|
-
height: "auto"
|
|
22361
|
+
width: this.props.size === "small" ? 40 : 80
|
|
22204
22362
|
}
|
|
22205
22363
|
});
|
|
22206
|
-
return /*#__PURE__*/React.createElement(
|
|
22364
|
+
return /*#__PURE__*/React.createElement(InputWithExamples, {
|
|
22207
22365
|
ref: ref => this.inputRef = ref,
|
|
22208
22366
|
value: this.props.currentValue,
|
|
22209
22367
|
onChange: this.handleChange,
|
|
@@ -22215,7 +22373,7 @@ class NumericInput extends React.Component {
|
|
|
22215
22373
|
id: this.props.widgetId,
|
|
22216
22374
|
disabled: this.props.apiOptions.readOnly,
|
|
22217
22375
|
style: styles.input
|
|
22218
|
-
})
|
|
22376
|
+
});
|
|
22219
22377
|
}
|
|
22220
22378
|
}
|
|
22221
22379
|
|
|
@@ -27111,8 +27269,8 @@ var Definition$1 = {
|
|
|
27111
27269
|
scorer: () => scoreNoop()
|
|
27112
27270
|
};
|
|
27113
27271
|
|
|
27114
|
-
//
|
|
27115
|
-
//
|
|
27272
|
+
// There are no required props, but this component can receive props from any
|
|
27273
|
+
// deprecated widget
|
|
27116
27274
|
class DeprecatedStandin extends React__default.Component {
|
|
27117
27275
|
constructor(...args) {
|
|
27118
27276
|
super(...args);
|
|
@@ -29222,6 +29380,13 @@ const getPromptJSON$f = rendererJSON => {
|
|
|
29222
29380
|
});
|
|
29223
29381
|
};
|
|
29224
29382
|
|
|
29383
|
+
// The `group` widget is basically a widget hosting a full Perseus system in
|
|
29384
|
+
// it. As such, scoring a group means scoring all widgets it contains.
|
|
29385
|
+
function scoreGroup(userInput, options, strings, locale) {
|
|
29386
|
+
const scores = scoreWidgetsFunctional(options.widgets, Object.keys(options.widgets), userInput, strings, locale);
|
|
29387
|
+
return Util.flattenScores(scores);
|
|
29388
|
+
}
|
|
29389
|
+
|
|
29225
29390
|
// exports has no 'transform'
|
|
29226
29391
|
|
|
29227
29392
|
class Group extends React.Component {
|
|
@@ -29327,6 +29492,9 @@ class Group extends React.Component {
|
|
|
29327
29492
|
this.change("widgets", this.rendererRef.props.widgets);
|
|
29328
29493
|
}
|
|
29329
29494
|
};
|
|
29495
|
+
|
|
29496
|
+
// TODO(LEMS-2391): replace this when there's a separate check
|
|
29497
|
+
// for valid/invalid state
|
|
29330
29498
|
const score = (_this$rendererRef12 = this.rendererRef) == null ? void 0 : _this$rendererRef12.score();
|
|
29331
29499
|
const isValid = score && score.type !== "invalid";
|
|
29332
29500
|
const isInvalid = score && score.type === "invalid";
|
|
@@ -29373,6 +29541,9 @@ var Group$1 = {
|
|
|
29373
29541
|
displayName: "Group (SAT only)",
|
|
29374
29542
|
widget: Group,
|
|
29375
29543
|
traverseChildWidgets: traverseChildWidgets,
|
|
29544
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
29545
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
29546
|
+
scorer: scoreGroup,
|
|
29376
29547
|
hidden: true,
|
|
29377
29548
|
isLintable: true
|
|
29378
29549
|
};
|
|
@@ -32082,6 +32253,61 @@ const LegacyGrid = ({
|
|
|
32082
32253
|
|
|
32083
32254
|
// Range is replaced within this file with Interval, but it is used elsewhere
|
|
32084
32255
|
// and exported from the package, so we need to keep it around.
|
|
32256
|
+
/**
|
|
32257
|
+
* Our core set of Perseus widgets.
|
|
32258
|
+
*
|
|
32259
|
+
* This interface is the basis for "registering" all Perseus widget types.
|
|
32260
|
+
* There should be one key/value pair for each supported widget. If you create
|
|
32261
|
+
* a new widget, an entry should be added to this interface. Note that this
|
|
32262
|
+
* only registers the widget options type, you'll also need to register the
|
|
32263
|
+
* widget so that it's available at runtime (@see
|
|
32264
|
+
* {@link file://./widgets.ts#registerWidget}).
|
|
32265
|
+
*
|
|
32266
|
+
* Importantly, the key should be the name that is used in widget IDs. For most
|
|
32267
|
+
* widgets that is the same as the widget option's `type` field. In cases where
|
|
32268
|
+
* a widget has been deprecated and replaced with the deprecated-standin
|
|
32269
|
+
* widget, it should be the original widget type!
|
|
32270
|
+
*
|
|
32271
|
+
* If you define the widget outside of this package, you can still add the new
|
|
32272
|
+
* widget to this interface by writing the following in that package that
|
|
32273
|
+
* contains the widget. TypeScript will merge that definition of the
|
|
32274
|
+
* `PerseusWidgets` with the one defined below.
|
|
32275
|
+
*
|
|
32276
|
+
* ```typescript
|
|
32277
|
+
* declare module "@khanacademy/perseus" {
|
|
32278
|
+
* interface PerseusWidgetTypes {
|
|
32279
|
+
* // A new widget
|
|
32280
|
+
* "new-awesomeness": MyAwesomeNewWidget;
|
|
32281
|
+
*
|
|
32282
|
+
* // A deprecated widget
|
|
32283
|
+
* "super-old-widget": AutoCorrectWidget;
|
|
32284
|
+
* }
|
|
32285
|
+
* }
|
|
32286
|
+
*
|
|
32287
|
+
* // The new widget's options definition
|
|
32288
|
+
* type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
|
|
32289
|
+
*
|
|
32290
|
+
* // The deprecated widget's options definition
|
|
32291
|
+
* type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
|
|
32292
|
+
* ```
|
|
32293
|
+
*
|
|
32294
|
+
* This interface can be extended through the magic of TypeScript "Declaration
|
|
32295
|
+
* merging". Specifically, we augment this module and extend this interface.
|
|
32296
|
+
*
|
|
32297
|
+
* @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
|
|
32298
|
+
*/
|
|
32299
|
+
/**
|
|
32300
|
+
* A map of widget IDs to widget options. This is most often used as the type
|
|
32301
|
+
* for a set of widgets defined in a `PerseusItem` but can also be useful to
|
|
32302
|
+
* represent a function parameter where only `widgets` from a `PerseusItem` are
|
|
32303
|
+
* needed. Today Widget IDs are made up of the widget type and an incrementing
|
|
32304
|
+
* integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
|
|
32305
|
+
* reading/parsing the widget id to derive any information from it, except in
|
|
32306
|
+
* the case of this map.
|
|
32307
|
+
*
|
|
32308
|
+
* @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
|
|
32309
|
+
* by augmenting the PerseusWidgetTypes with new widget types!
|
|
32310
|
+
*/
|
|
32085
32311
|
/**
|
|
32086
32312
|
* A "PerseusItem" is a classic Perseus item. It is rendered by the
|
|
32087
32313
|
* `ServerItemRenderer` and the layout is pre-set.
|
|
@@ -32097,6 +32323,9 @@ const LegacyGrid = ({
|
|
|
32097
32323
|
* A "MultiItem" is an advanced Perseus item. It is rendered by the
|
|
32098
32324
|
* `MultiRenderer` and you can control the layout of individual parts of the
|
|
32099
32325
|
* item.
|
|
32326
|
+
*
|
|
32327
|
+
* @deprecated MultiItem support is slated for removal in a future Perseus
|
|
32328
|
+
* release.
|
|
32100
32329
|
*/
|
|
32101
32330
|
const ItemExtras = [
|
|
32102
32331
|
// The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
|
|
@@ -32118,6 +32347,11 @@ const ItemExtras = [
|
|
|
32118
32347
|
// The user might benefit from using a statistics Z Table like https://www.ztable.net/
|
|
32119
32348
|
"zTable"];
|
|
32120
32349
|
|
|
32350
|
+
/**
|
|
32351
|
+
* The type representing the common structure of all widget's options. The
|
|
32352
|
+
* `Options` generic type represents the widget-specific option data.
|
|
32353
|
+
*/
|
|
32354
|
+
|
|
32121
32355
|
// prettier-ignore
|
|
32122
32356
|
|
|
32123
32357
|
// prettier-ignore
|
|
@@ -32186,7 +32420,9 @@ const ItemExtras = [
|
|
|
32186
32420
|
|
|
32187
32421
|
//prettier-ignore
|
|
32188
32422
|
|
|
32189
|
-
|
|
32423
|
+
/**
|
|
32424
|
+
* A background image applied to various widgets.
|
|
32425
|
+
*/
|
|
32190
32426
|
|
|
32191
32427
|
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
32192
32428
|
|
|
@@ -36754,8 +36990,6 @@ const makeInvalidTypeError = (functionName, graphType) => {
|
|
|
36754
36990
|
});
|
|
36755
36991
|
};
|
|
36756
36992
|
|
|
36757
|
-
// There's no transform function in exports
|
|
36758
|
-
|
|
36759
36993
|
// TODO: there's another, very similar getSinusoidCoefficients function
|
|
36760
36994
|
// they should probably be merged
|
|
36761
36995
|
function getSinusoidCoefficients(coords) {
|
|
@@ -41891,23 +42125,37 @@ const getPromptJSON$6 = (renderProps, userInput) => {
|
|
|
41891
42125
|
};
|
|
41892
42126
|
};
|
|
41893
42127
|
|
|
41894
|
-
|
|
41895
|
-
|
|
41896
|
-
|
|
41897
|
-
|
|
41898
|
-
|
|
41899
|
-
|
|
41900
|
-
|
|
41901
|
-
const
|
|
42128
|
+
/**
|
|
42129
|
+
* Checks user input is within the allowed range and not the same as the initial
|
|
42130
|
+
* state.
|
|
42131
|
+
* @param userInput
|
|
42132
|
+
* @see 'scoreNumberLine' for the scoring logic.
|
|
42133
|
+
*/
|
|
42134
|
+
function validateNumberLine(userInput) {
|
|
42135
|
+
const divisionRange = userInput.divisionRange;
|
|
42136
|
+
const outsideAllowedRange = userInput.numDivisions > divisionRange[1] || userInput.numDivisions < divisionRange[0];
|
|
41902
42137
|
|
|
41903
42138
|
// TODO: I don't think isTickCrtl is a thing anymore
|
|
41904
|
-
if (
|
|
42139
|
+
if (userInput.isTickCrtl && outsideAllowedRange) {
|
|
41905
42140
|
return {
|
|
41906
42141
|
type: "invalid",
|
|
41907
42142
|
message: "Number of divisions is outside the allowed range."
|
|
41908
42143
|
};
|
|
41909
42144
|
}
|
|
41910
|
-
|
|
42145
|
+
return null;
|
|
42146
|
+
}
|
|
42147
|
+
|
|
42148
|
+
function scoreNumberLine(userInput, scoringData) {
|
|
42149
|
+
const validationError = validateNumberLine(userInput);
|
|
42150
|
+
if (validationError) {
|
|
42151
|
+
return validationError;
|
|
42152
|
+
}
|
|
42153
|
+
const range = scoringData.range;
|
|
42154
|
+
const start = scoringData.initialX != null ? scoringData.initialX : range[0];
|
|
42155
|
+
const startRel = scoringData.isInequality ? "ge" : "eq";
|
|
42156
|
+
const correctRel = scoringData.correctRel || "eq";
|
|
42157
|
+
const correctPos = number$1.equal(userInput.numLinePosition, scoringData.correctX || 0);
|
|
42158
|
+
if (correctPos && correctRel === userInput.rel) {
|
|
41911
42159
|
return {
|
|
41912
42160
|
type: "points",
|
|
41913
42161
|
earned: 1,
|
|
@@ -41915,7 +42163,7 @@ function scoreNumberLine(state, rubric) {
|
|
|
41915
42163
|
message: null
|
|
41916
42164
|
};
|
|
41917
42165
|
}
|
|
41918
|
-
if (
|
|
42166
|
+
if (userInput.numLinePosition === start && userInput.rel === startRel) {
|
|
41919
42167
|
// We're where we started.
|
|
41920
42168
|
return {
|
|
41921
42169
|
type: "invalid",
|
|
@@ -44861,7 +45109,7 @@ const init = function init() {
|
|
|
44861
45109
|
|
|
44862
45110
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44863
45111
|
const libName = "@khanacademy/perseus";
|
|
44864
|
-
const libVersion = "
|
|
45112
|
+
const libVersion = "45.1.0";
|
|
44865
45113
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44866
45114
|
|
|
44867
45115
|
const apiVersion = {
|
|
@@ -45469,14 +45717,10 @@ class ServerItemRenderer extends React.Component {
|
|
|
45469
45717
|
});
|
|
45470
45718
|
}
|
|
45471
45719
|
componentDidUpdate(prevProps, prevState) {
|
|
45472
|
-
|
|
45720
|
+
const answerableCallback = this.props.apiOptions.answerableCallback;
|
|
45721
|
+
if (answerableCallback != null) {
|
|
45473
45722
|
const isAnswerable = this.questionRenderer.emptyWidgets().length === 0;
|
|
45474
|
-
|
|
45475
|
-
answerableCallback
|
|
45476
|
-
} = this.props.apiOptions;
|
|
45477
|
-
if (answerableCallback) {
|
|
45478
|
-
answerableCallback(isAnswerable);
|
|
45479
|
-
}
|
|
45723
|
+
answerableCallback(isAnswerable);
|
|
45480
45724
|
}
|
|
45481
45725
|
if (!this._fullyRendered) {
|
|
45482
45726
|
const assetsLoaded = Object.values(this.state.assetStatuses).every(Boolean);
|
|
@@ -45485,6 +45729,13 @@ class ServerItemRenderer extends React.Component {
|
|
|
45485
45729
|
this.props.onRendered(true);
|
|
45486
45730
|
}
|
|
45487
45731
|
}
|
|
45732
|
+
if (this.props.score && this.props.score !== prevProps.score) {
|
|
45733
|
+
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45734
|
+
this.setState({
|
|
45735
|
+
questionCompleted: this.props.score.correct,
|
|
45736
|
+
questionHighlightedWidgets: emptyQuestionAreaWidgets
|
|
45737
|
+
});
|
|
45738
|
+
}
|
|
45488
45739
|
}
|
|
45489
45740
|
componentWillUnmount() {
|
|
45490
45741
|
if (this.blurTimeoutID != null) {
|
|
@@ -45602,18 +45853,37 @@ class ServerItemRenderer extends React.Component {
|
|
|
45602
45853
|
return this.questionRenderer.getPromptJSON();
|
|
45603
45854
|
}
|
|
45604
45855
|
|
|
45856
|
+
/**
|
|
45857
|
+
* Returns an array of the widget `.getUserInput()` results
|
|
45858
|
+
*
|
|
45859
|
+
* TODO: can we remove this? Seems to be just for backwards
|
|
45860
|
+
* compatibility with old Perseus Chrome logging
|
|
45861
|
+
* @deprecated use getUserInput
|
|
45862
|
+
*/
|
|
45863
|
+
getUserInputLegacy() {
|
|
45864
|
+
return this.questionRenderer.getUserInput();
|
|
45865
|
+
}
|
|
45866
|
+
|
|
45867
|
+
/**
|
|
45868
|
+
* Returns an object of the widget `.getUserInput()` results
|
|
45869
|
+
*/
|
|
45870
|
+
getUserInput() {
|
|
45871
|
+
return this.questionRenderer.getUserInputMap();
|
|
45872
|
+
}
|
|
45873
|
+
|
|
45605
45874
|
/**
|
|
45606
45875
|
* Grades the item.
|
|
45876
|
+
*
|
|
45877
|
+
* @deprecated use scorePerseusItem
|
|
45607
45878
|
*/
|
|
45608
45879
|
scoreInput() {
|
|
45609
|
-
const
|
|
45610
|
-
const
|
|
45611
|
-
const score = guessAndScore[1];
|
|
45880
|
+
const guess = this.getUserInput();
|
|
45881
|
+
const score = scorePerseusItem(this.props.item.question, guess, this.context.strings, this.context.locale);
|
|
45612
45882
|
|
|
45613
45883
|
// Continue to include an empty guess for the now defunct answer area.
|
|
45614
45884
|
// TODO(alex): Check whether we rely on the format here for
|
|
45615
45885
|
// analyzing ProblemLogs. If not, remove this layer.
|
|
45616
|
-
const maxCompatGuess = [
|
|
45886
|
+
const maxCompatGuess = [this.questionRenderer.getUserInput(), []];
|
|
45617
45887
|
const keScore = Util.keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
|
|
45618
45888
|
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45619
45889
|
this.setState({
|
|
@@ -45793,7 +46063,7 @@ const traverseRenderer$1 = function traverseRenderer(rendererOptions, contentCal
|
|
|
45793
46063
|
newContent = modifiedContent;
|
|
45794
46064
|
}
|
|
45795
46065
|
}
|
|
45796
|
-
const newWidgets =
|
|
46066
|
+
const newWidgets = mapObject(rendererOptions.widgets || {}, function (widgetInfo, widgetId) {
|
|
45797
46067
|
// Widgets without info or a type are empty widgets, and
|
|
45798
46068
|
// should always be renderable. It's also annoying to write
|
|
45799
46069
|
// checks for this everywhere, so we just filter them out once and
|
|
@@ -47461,140 +47731,6 @@ const registerAllWidgetsForTesting = () => {
|
|
|
47461
47731
|
replaceDeprecatedWidgets();
|
|
47462
47732
|
};
|
|
47463
47733
|
|
|
47464
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
47465
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
47466
|
-
|
|
47467
|
-
/**
|
|
47468
|
-
* Regex for widget placeholders in a string.
|
|
47469
|
-
*
|
|
47470
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
47471
|
-
* Second capture group is the widget type (ex. "radio)
|
|
47472
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
47473
|
-
*/
|
|
47474
|
-
function getWidgetRegex() {
|
|
47475
|
-
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
47476
|
-
}
|
|
47477
|
-
|
|
47478
|
-
/**
|
|
47479
|
-
* Add a widget placeholder using the widget ID.
|
|
47480
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
47481
|
-
*
|
|
47482
|
-
* @param {string} id
|
|
47483
|
-
* @returns {string}
|
|
47484
|
-
*/
|
|
47485
|
-
function addWidget(id) {
|
|
47486
|
-
return `[[☃ ${id}]]`;
|
|
47487
|
-
}
|
|
47488
|
-
|
|
47489
|
-
/**
|
|
47490
|
-
* Get a widget type by a widget's ID
|
|
47491
|
-
* (please don't derive type from ID)
|
|
47492
|
-
*
|
|
47493
|
-
* @param {string} widgetId the ID of the widget
|
|
47494
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47495
|
-
* @returns {string} the widget type (ie "radio")
|
|
47496
|
-
*/
|
|
47497
|
-
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
47498
|
-
var _widget$type;
|
|
47499
|
-
const widget = widgetMap[WidgetId];
|
|
47500
|
-
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
47501
|
-
}
|
|
47502
|
-
|
|
47503
|
-
/**
|
|
47504
|
-
* Does the content have a specific type of widget?
|
|
47505
|
-
*
|
|
47506
|
-
* @param {string} type the type of the widget in question (ie "radio")
|
|
47507
|
-
* @param {string} content the string to search through
|
|
47508
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47509
|
-
* @returns {boolean} if the content includes the widget type
|
|
47510
|
-
*/
|
|
47511
|
-
function contentHasWidgetType(type, content, widgetMap) {
|
|
47512
|
-
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
47513
|
-
}
|
|
47514
|
-
|
|
47515
|
-
/**
|
|
47516
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
47517
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
47518
|
-
*
|
|
47519
|
-
* Content should contain Perseus widget placeholders,
|
|
47520
|
-
* which look like: '[[☃ radio 1]]'.
|
|
47521
|
-
*
|
|
47522
|
-
* @param {string} content
|
|
47523
|
-
* @returns {ReadonlyArray<string>} widgetIds
|
|
47524
|
-
*/
|
|
47525
|
-
function getWidgetIdsFromContent(content) {
|
|
47526
|
-
const widgets = [];
|
|
47527
|
-
const localWidgetRegex = getWidgetRegex();
|
|
47528
|
-
let match = localWidgetRegex.exec(content);
|
|
47529
|
-
while (match !== null) {
|
|
47530
|
-
widgets.push(match[1]);
|
|
47531
|
-
match = localWidgetRegex.exec(content);
|
|
47532
|
-
}
|
|
47533
|
-
return widgets;
|
|
47534
|
-
}
|
|
47535
|
-
|
|
47536
|
-
/**
|
|
47537
|
-
* Get a list of widget IDs from content,
|
|
47538
|
-
* but only for specific widget types
|
|
47539
|
-
*
|
|
47540
|
-
* @param {string} type the type of widget (ie "radio")
|
|
47541
|
-
* @param {string} content the string to parse
|
|
47542
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47543
|
-
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
47544
|
-
*/
|
|
47545
|
-
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
47546
|
-
const rv = [];
|
|
47547
|
-
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
47548
|
-
widgetIdsInContent.forEach(widgetId => {
|
|
47549
|
-
const widget = widgetMap[widgetId];
|
|
47550
|
-
if ((widget == null ? void 0 : widget.type) === type) {
|
|
47551
|
-
rv.push(widgetId);
|
|
47552
|
-
}
|
|
47553
|
-
});
|
|
47554
|
-
return rv;
|
|
47555
|
-
}
|
|
47556
|
-
|
|
47557
|
-
/**
|
|
47558
|
-
* Pull the widget map out of ItemData
|
|
47559
|
-
*
|
|
47560
|
-
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
47561
|
-
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
47562
|
-
*/
|
|
47563
|
-
function getWidgetsMapFromItemData(itemData) {
|
|
47564
|
-
return itemData.question.widgets;
|
|
47565
|
-
}
|
|
47566
|
-
|
|
47567
|
-
/**
|
|
47568
|
-
* Get the widget information from a WidgetMap
|
|
47569
|
-
*
|
|
47570
|
-
* @param {string} widgetId the ID of the widget
|
|
47571
|
-
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
47572
|
-
*/
|
|
47573
|
-
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
47574
|
-
var _widgetMap$widgetId;
|
|
47575
|
-
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
47576
|
-
}
|
|
47577
|
-
|
|
47578
|
-
/**
|
|
47579
|
-
* Get select widgets from a widget map.
|
|
47580
|
-
* Useful for multi-items that needs to split
|
|
47581
|
-
* one PerseusItem into several
|
|
47582
|
-
*
|
|
47583
|
-
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
47584
|
-
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
47585
|
-
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
47586
|
-
*/
|
|
47587
|
-
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
47588
|
-
const widgets = {};
|
|
47589
|
-
widgetIds.forEach(widgetId => {
|
|
47590
|
-
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
47591
|
-
if (widget) {
|
|
47592
|
-
widgets[widgetId] = widget;
|
|
47593
|
-
}
|
|
47594
|
-
});
|
|
47595
|
-
return widgets;
|
|
47596
|
-
}
|
|
47597
|
-
|
|
47598
47734
|
/**
|
|
47599
47735
|
* This function extracts the answers from the widgets.
|
|
47600
47736
|
*
|
|
@@ -48526,7 +48662,7 @@ const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
|
48526
48662
|
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
48527
48663
|
togglePrompt: string,
|
|
48528
48664
|
definition: string,
|
|
48529
|
-
static: boolean
|
|
48665
|
+
static: defaulted(boolean, () => false)
|
|
48530
48666
|
}));
|
|
48531
48667
|
|
|
48532
48668
|
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
@@ -48728,15 +48864,26 @@ const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
|
48728
48864
|
static: defaulted(boolean, () => false)
|
|
48729
48865
|
}));
|
|
48730
48866
|
|
|
48867
|
+
const stringToNumber = (rawValue, ctx) => {
|
|
48868
|
+
if (typeof rawValue === "number") {
|
|
48869
|
+
return ctx.success(rawValue);
|
|
48870
|
+
}
|
|
48871
|
+
const parsedNumber = +rawValue;
|
|
48872
|
+
if (rawValue === "" || isNaN(parsedNumber)) {
|
|
48873
|
+
return ctx.failure("a number or numeric string", rawValue);
|
|
48874
|
+
}
|
|
48875
|
+
return ctx.success(parsedNumber);
|
|
48876
|
+
};
|
|
48877
|
+
|
|
48878
|
+
const numericToNumber = pipeParsers(union(number).or(string).parser).then(stringToNumber).parser;
|
|
48731
48879
|
const parsePerseusImageBackground = object({
|
|
48732
48880
|
url: optional(nullable(string)),
|
|
48733
|
-
width: optional(
|
|
48734
|
-
height: optional(
|
|
48735
|
-
top: optional(
|
|
48736
|
-
left: optional(
|
|
48737
|
-
bottom: optional(
|
|
48738
|
-
|
|
48739
|
-
scale: optional(union(number).or(string).parser)
|
|
48881
|
+
width: optional(numericToNumber),
|
|
48882
|
+
height: optional(numericToNumber),
|
|
48883
|
+
top: optional(numericToNumber),
|
|
48884
|
+
left: optional(numericToNumber),
|
|
48885
|
+
bottom: optional(numericToNumber),
|
|
48886
|
+
scale: optional(numericToNumber)
|
|
48740
48887
|
});
|
|
48741
48888
|
|
|
48742
48889
|
const pairOfNumbers$2 = pair(number, number);
|
|
@@ -48755,6 +48902,12 @@ const parseImageWidget = parseWidget(constant("image"), object({
|
|
|
48755
48902
|
box: optional(pairOfNumbers$2)
|
|
48756
48903
|
}));
|
|
48757
48904
|
|
|
48905
|
+
const booleanToString = (rawValue, ctx) => {
|
|
48906
|
+
if (typeof rawValue === "boolean") {
|
|
48907
|
+
return ctx.success(String(rawValue));
|
|
48908
|
+
}
|
|
48909
|
+
return ctx.failure("boolean", rawValue);
|
|
48910
|
+
};
|
|
48758
48911
|
const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
48759
48912
|
answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
|
|
48760
48913
|
inexact: optional(boolean),
|
|
@@ -48762,7 +48915,11 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
|
48762
48915
|
rightAlign: optional(boolean),
|
|
48763
48916
|
simplify: enumeration("required", "optional", "enforced"),
|
|
48764
48917
|
size: enumeration("normal", "small"),
|
|
48765
|
-
|
|
48918
|
+
// TODO(benchristel): there are some content items where value is a
|
|
48919
|
+
// boolean, even though that makes no sense. We should figure out if
|
|
48920
|
+
// those content items are actually published anywhere, and consider
|
|
48921
|
+
// updating them.
|
|
48922
|
+
value: union(number).or(string).or(booleanToString).parser,
|
|
48766
48923
|
customKeypad: optional(boolean)
|
|
48767
48924
|
}));
|
|
48768
48925
|
|
|
@@ -49060,18 +49217,27 @@ const parseLockedFunctionType = object({
|
|
|
49060
49217
|
const parseLockedFigure = union(parseLockedPointType).or(parseLockedLineType).or(parseLockedVectorType).or(parseLockedEllipseType).or(parseLockedPolygonType).or(parseLockedFunctionType).or(parseLockedLabelType).parser;
|
|
49061
49218
|
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
49062
49219
|
step: pairOfNumbers,
|
|
49063
|
-
|
|
49064
|
-
|
|
49220
|
+
// TODO(benchristel): rather than making gridStep and snapStep
|
|
49221
|
+
// optional, we should duplicate the defaulting logic from the
|
|
49222
|
+
// InteractiveGraph component. See parse-perseus-json/README.md for
|
|
49223
|
+
// why.
|
|
49224
|
+
gridStep: optional(pairOfNumbers),
|
|
49225
|
+
snapStep: optional(pairOfNumbers),
|
|
49065
49226
|
backgroundImage: optional(parsePerseusImageBackground),
|
|
49066
49227
|
markings: enumeration("graph", "grid", "none"),
|
|
49067
|
-
labels: array(string),
|
|
49228
|
+
labels: optional(array(string)),
|
|
49068
49229
|
showProtractor: boolean,
|
|
49069
49230
|
showRuler: optional(boolean),
|
|
49070
49231
|
showTooltips: optional(boolean),
|
|
49071
49232
|
rulerLabel: optional(string),
|
|
49072
49233
|
rulerTicks: optional(number),
|
|
49073
49234
|
range: pair(pairOfNumbers, pairOfNumbers),
|
|
49074
|
-
|
|
49235
|
+
// NOTE(benchristel): I copied the default graph from
|
|
49236
|
+
// interactive-graph.tsx. See the parse-perseus-json/README.md for
|
|
49237
|
+
// an explanation of why we want to duplicate the default here.
|
|
49238
|
+
graph: defaulted(parsePerseusGraphType, () => ({
|
|
49239
|
+
type: "linear"
|
|
49240
|
+
})),
|
|
49075
49241
|
correct: parsePerseusGraphType,
|
|
49076
49242
|
// TODO(benchristel): default lockedFigures to empty array
|
|
49077
49243
|
lockedFigures: optional(array(parseLockedFigure)),
|
|
@@ -49139,7 +49305,10 @@ const parseNumberLineWidget = parseWidget(constant("number-line"), object({
|
|
|
49139
49305
|
isTickCtrl: optional(nullable(boolean)),
|
|
49140
49306
|
divisionRange: array(number),
|
|
49141
49307
|
numDivisions: optional(nullable(number)),
|
|
49142
|
-
|
|
49308
|
+
// NOTE(benchristel): I copied the default snapDivisions from
|
|
49309
|
+
// number-line.tsx. See the parse-perseus-json/README.md for
|
|
49310
|
+
// an explanation of why we want to duplicate the default here.
|
|
49311
|
+
snapDivisions: defaulted(number, () => 2),
|
|
49143
49312
|
tickStep: optional(nullable(number)),
|
|
49144
49313
|
correctRel: optional(nullable(string)),
|
|
49145
49314
|
correctX: number,
|
|
@@ -49152,18 +49321,18 @@ const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "d
|
|
|
49152
49321
|
const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
|
|
49153
49322
|
answers: array(object({
|
|
49154
49323
|
message: string,
|
|
49155
|
-
value: number,
|
|
49324
|
+
value: optional(number),
|
|
49156
49325
|
status: string,
|
|
49157
49326
|
answerForms: optional(array(parseMathFormat)),
|
|
49158
49327
|
strict: boolean,
|
|
49159
49328
|
maxError: optional(nullable(number)),
|
|
49160
49329
|
simplify: optional(nullable(string))
|
|
49161
49330
|
})),
|
|
49162
|
-
labelText: string,
|
|
49331
|
+
labelText: optional(string),
|
|
49163
49332
|
size: string,
|
|
49164
49333
|
coefficient: boolean,
|
|
49165
49334
|
rightAlign: optional(boolean),
|
|
49166
|
-
static: boolean,
|
|
49335
|
+
static: defaulted(boolean, () => false),
|
|
49167
49336
|
answerForms: optional(array(object({
|
|
49168
49337
|
name: parseMathFormat,
|
|
49169
49338
|
simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
|
|
@@ -49176,12 +49345,18 @@ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
|
|
|
49176
49345
|
function parseRenderer(rawValue, ctx) {
|
|
49177
49346
|
return parsePerseusRenderer(rawValue, ctx);
|
|
49178
49347
|
}
|
|
49348
|
+
const largeToAuto = (height, ctx) => {
|
|
49349
|
+
if (height === "large") {
|
|
49350
|
+
return ctx.success("auto");
|
|
49351
|
+
}
|
|
49352
|
+
return ctx.success(height);
|
|
49353
|
+
};
|
|
49179
49354
|
const parseOrdererWidget = parseWidget(constant("orderer"), object({
|
|
49180
|
-
options: array(parseRenderer),
|
|
49355
|
+
options: defaulted(array(parseRenderer), () => []),
|
|
49181
49356
|
correctOptions: array(parseRenderer),
|
|
49182
49357
|
otherOptions: array(parseRenderer),
|
|
49183
|
-
height: enumeration("normal", "auto"),
|
|
49184
|
-
layout: enumeration("horizontal", "vertical")
|
|
49358
|
+
height: pipeParsers(enumeration("normal", "auto", "large")).then(largeToAuto).parser,
|
|
49359
|
+
layout: defaulted(enumeration("horizontal", "vertical"), () => "horizontal")
|
|
49185
49360
|
}));
|
|
49186
49361
|
|
|
49187
49362
|
const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
|
|
@@ -49195,7 +49370,7 @@ const parsePassageWidget = parseWidget(constant("passage"), object({
|
|
|
49195
49370
|
passageText: string,
|
|
49196
49371
|
passageTitle: string,
|
|
49197
49372
|
showLineNumbers: boolean,
|
|
49198
|
-
static: boolean
|
|
49373
|
+
static: defaulted(boolean, () => false)
|
|
49199
49374
|
}));
|
|
49200
49375
|
|
|
49201
49376
|
const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
|
|
@@ -49216,7 +49391,10 @@ const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
|
49216
49391
|
picUrl: optional(nullable(string)),
|
|
49217
49392
|
picSize: optional(nullable(number)),
|
|
49218
49393
|
picBoxHeight: optional(nullable(number)),
|
|
49219
|
-
|
|
49394
|
+
// NOTE(benchristel): I copied the default plotDimensions from
|
|
49395
|
+
// plotter.tsx. See the parse-perseus-json/README.md for an explanation
|
|
49396
|
+
// of why we want to duplicate the defaults here.
|
|
49397
|
+
plotDimensions: defaulted(array(number), () => [380, 300])
|
|
49220
49398
|
}));
|
|
49221
49399
|
|
|
49222
49400
|
const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
|
|
@@ -49368,6 +49546,10 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
|
|
|
49368
49546
|
return parseAndAssign(`table ${id}`, parseTableWidget);
|
|
49369
49547
|
case "video":
|
|
49370
49548
|
return parseAndAssign(`video ${id}`, parseVideoWidget);
|
|
49549
|
+
case "sequence":
|
|
49550
|
+
// sequence is a deprecated widget type, and the corresponding
|
|
49551
|
+
// widget component no longer exists.
|
|
49552
|
+
return parseAndAssign(`sequence ${id}`, parseDeprecatedWidget);
|
|
49371
49553
|
default:
|
|
49372
49554
|
if (getWidget(type)) {
|
|
49373
49555
|
// @ts-expect-error - 'type' is not a valid widget type
|
|
@@ -49376,6 +49558,11 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
|
|
|
49376
49558
|
return ctx.failure("a valid widget type", type);
|
|
49377
49559
|
}
|
|
49378
49560
|
};
|
|
49561
|
+
const parseDeprecatedWidget = parseWidget(
|
|
49562
|
+
// Ignore the incoming widget type and hardcode "deprecated-standin"
|
|
49563
|
+
(_, ctx) => ctx.success("deprecated-standin"),
|
|
49564
|
+
// Allow any widget options
|
|
49565
|
+
object({}));
|
|
49379
49566
|
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
49380
49567
|
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
49381
49568
|
return ctx.failure("numeric string", rawValue);
|
|
@@ -49651,5 +49838,5 @@ const GrapherUtil = {
|
|
|
49651
49838
|
typeToButton
|
|
49652
49839
|
};
|
|
49653
49840
|
|
|
49654
|
-
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, mathOnlyParser, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
49841
|
+
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, mathOnlyParser, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
49655
49842
|
//# sourceMappingURL=index.js.map
|