@khanacademy/perseus 43.0.0 → 43.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/number-input.d.ts +5 -3
- package/dist/components/text-input.d.ts +4 -2
- package/dist/es/index.js +475 -306
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +7 -3
- package/dist/es/strings.js.map +1 -1
- package/dist/index.js +465 -296
- package/dist/index.js.map +1 -1
- package/dist/perseus-types.d.ts +14 -7
- package/dist/strings.js +7 -3
- package/dist/strings.js.map +1 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +1 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/index.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.d.ts +7 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.typetest.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/unknown.d.ts +2 -0
- package/dist/util/parse-perseus-json/perseus-parsers/images-map.d.ts +5 -0
- package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +4 -0
- package/dist/validation.types.d.ts +14 -6
- package/dist/widgets/categorizer/categorizer.d.ts +2 -2
- package/dist/widgets/categorizer/score-categorizer.d.ts +2 -2
- package/dist/widgets/categorizer/validate-categorizer.d.ts +15 -0
- package/dist/widgets/dropdown/validate-dropdown.d.ts +10 -0
- package/dist/widgets/interactive-graphs/graphs/components/movable-point.d.ts +11 -7
- package/dist/widgets/interactive-graphs/graphs/components/use-control-point.d.ts +7 -3
- package/dist/widgets/interactive-graphs/types.d.ts +1 -0
- package/dist/widgets/interactive-graphs/utils.d.ts +6 -6
- package/dist/widgets/matrix/matrix.d.ts +12 -5
- package/dist/widgets/measurer/measurer.d.ts +1 -1
- package/dist/widgets/orderer/validate-orderer.d.ts +12 -0
- package/dist/widgets/plotter/plotter.d.ts +2 -2
- package/dist/widgets/plotter/score-plotter.d.ts +2 -2
- package/dist/widgets/plotter/validate-plotter.d.ts +12 -0
- package/dist/widgets/radio/radio.d.ts +1 -1
- package/dist/widgets/radio/validate-radio.d.ts +14 -0
- package/dist/widgets/sorter/validate-sorter.d.ts +13 -0
- package/package.json +4 -4
- package/dist/util/parse-perseus-json/general-purpose-parsers/compose-parsers.d.ts +0 -2
package/dist/index.js
CHANGED
|
@@ -2882,8 +2882,6 @@ async function getImageSizeModern(url) {
|
|
|
2882
2882
|
});
|
|
2883
2883
|
}
|
|
2884
2884
|
|
|
2885
|
-
/* eslint-disable @babel/no-invalid-this, getter-return, one-var */
|
|
2886
|
-
|
|
2887
2885
|
// TODO: dedupe this with Coord in interactive2/types.js
|
|
2888
2886
|
|
|
2889
2887
|
let supportsPassive = false;
|
|
@@ -2960,8 +2958,8 @@ function shuffle$3(array, randomSeed) {
|
|
|
2960
2958
|
do {
|
|
2961
2959
|
// Fischer-Yates shuffle
|
|
2962
2960
|
for (let top = shuffled.length; top > 0; top--) {
|
|
2963
|
-
const newEnd = Math.floor(random() * top)
|
|
2964
|
-
|
|
2961
|
+
const newEnd = Math.floor(random() * top);
|
|
2962
|
+
const temp = shuffled[newEnd];
|
|
2965
2963
|
|
|
2966
2964
|
// @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
|
|
2967
2965
|
shuffled[newEnd] = shuffled[top - 1];
|
|
@@ -3293,13 +3291,13 @@ function eq$4(x, y) {
|
|
|
3293
3291
|
* Deep approximate equality on primitives, numbers, arrays, and objects.
|
|
3294
3292
|
* Recursive.
|
|
3295
3293
|
*/
|
|
3296
|
-
function deepEq$
|
|
3294
|
+
function deepEq$7(x, y) {
|
|
3297
3295
|
if (Array.isArray(x) && Array.isArray(y)) {
|
|
3298
3296
|
if (x.length !== y.length) {
|
|
3299
3297
|
return false;
|
|
3300
3298
|
}
|
|
3301
3299
|
for (let i = 0; i < x.length; i++) {
|
|
3302
|
-
if (!deepEq$
|
|
3300
|
+
if (!deepEq$7(x[i], y[i])) {
|
|
3303
3301
|
return false;
|
|
3304
3302
|
}
|
|
3305
3303
|
}
|
|
@@ -3317,10 +3315,10 @@ function deepEq$6(x, y) {
|
|
|
3317
3315
|
if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
|
|
3318
3316
|
return x === y || ___default["default"].all(x, function (v, k) {
|
|
3319
3317
|
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
3320
|
-
return deepEq$
|
|
3318
|
+
return deepEq$7(y[k], v);
|
|
3321
3319
|
}) && ___default["default"].all(y, function (v, k) {
|
|
3322
3320
|
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
3323
|
-
return deepEq$
|
|
3321
|
+
return deepEq$7(x[k], v);
|
|
3324
3322
|
});
|
|
3325
3323
|
}
|
|
3326
3324
|
if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
|
|
@@ -3599,7 +3597,7 @@ const Util = {
|
|
|
3599
3597
|
tickStepFromNumTicks,
|
|
3600
3598
|
constrainedTickStepsFromTickSteps,
|
|
3601
3599
|
eq: eq$4,
|
|
3602
|
-
deepEq: deepEq$
|
|
3600
|
+
deepEq: deepEq$7,
|
|
3603
3601
|
parseQueryString,
|
|
3604
3602
|
updateQueryString: updateQueryString$2,
|
|
3605
3603
|
strongEncodeURIComponent,
|
|
@@ -9002,7 +9000,6 @@ var Tex$1 = {
|
|
|
9002
9000
|
}
|
|
9003
9001
|
};
|
|
9004
9002
|
|
|
9005
|
-
/* eslint-disable @babel/no-invalid-this */
|
|
9006
9003
|
const {
|
|
9007
9004
|
processMath: processMath$1
|
|
9008
9005
|
} = Tex$1;
|
|
@@ -10632,7 +10629,9 @@ const getClipPoint = function (graph, coord, angle) {
|
|
|
10632
10629
|
return clipPoint;
|
|
10633
10630
|
};
|
|
10634
10631
|
|
|
10635
|
-
|
|
10632
|
+
/**
|
|
10633
|
+
* Default methods for a wrapped movable.
|
|
10634
|
+
*/
|
|
10636
10635
|
|
|
10637
10636
|
/*
|
|
10638
10637
|
* attr() and animate() simply pass the
|
|
@@ -10890,7 +10889,7 @@ class WrappedLine extends WrappedDrawing {
|
|
|
10890
10889
|
*/
|
|
10891
10890
|
const {
|
|
10892
10891
|
eq: eq$3,
|
|
10893
|
-
deepEq: deepEq$
|
|
10892
|
+
deepEq: deepEq$6
|
|
10894
10893
|
} = Util;
|
|
10895
10894
|
|
|
10896
10895
|
// This should really be a readonly tuple of [number, number]
|
|
@@ -11050,7 +11049,7 @@ function similar(coords1, coords2, tolerance) {
|
|
|
11050
11049
|
angles = rotate(angles, i);
|
|
11051
11050
|
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
11052
11051
|
sides = rotate(sides, i);
|
|
11053
|
-
if (deepEq$
|
|
11052
|
+
if (deepEq$6(angles1, angles)) {
|
|
11054
11053
|
const sidePairs = ___default["default"].zip(sides1, sides);
|
|
11055
11054
|
const factors = ___default["default"].map(sidePairs, function (pair) {
|
|
11056
11055
|
return pair[0] / pair[1];
|
|
@@ -13989,7 +13988,7 @@ ___default["default"].extend(MovableAngle.prototype, {
|
|
|
13989
13988
|
|
|
13990
13989
|
/* eslint-disable @babel/no-invalid-this */
|
|
13991
13990
|
const nestedMap$1 = Util.nestedMap;
|
|
13992
|
-
const deepEq$
|
|
13991
|
+
const deepEq$5 = Util.deepEq;
|
|
13993
13992
|
|
|
13994
13993
|
/**
|
|
13995
13994
|
* A base class for all Graphie Movables
|
|
@@ -14086,7 +14085,7 @@ const createSimpleClass = function (addFunction) {
|
|
|
14086
14085
|
this._prevProps = this.props;
|
|
14087
14086
|
},
|
|
14088
14087
|
modify: function (graphie) {
|
|
14089
|
-
if (!deepEq$
|
|
14088
|
+
if (!deepEq$5(this.props, this._prevProps)) {
|
|
14090
14089
|
this.remove();
|
|
14091
14090
|
this.add(graphie);
|
|
14092
14091
|
this._prevProps = this.props;
|
|
@@ -16744,7 +16743,7 @@ var Movables = {
|
|
|
16744
16743
|
const GraphieMovable = GraphieClasses.GraphieMovable;
|
|
16745
16744
|
const createGraphie = GraphUtils.createGraphie;
|
|
16746
16745
|
const {
|
|
16747
|
-
deepEq: deepEq$
|
|
16746
|
+
deepEq: deepEq$4,
|
|
16748
16747
|
nestedMap
|
|
16749
16748
|
} = Util;
|
|
16750
16749
|
const {
|
|
@@ -16782,7 +16781,7 @@ class Graphie extends React__namespace.Component {
|
|
|
16782
16781
|
if (this.props.setup !== prevProps.setup) {
|
|
16783
16782
|
Log.error("<Graphie> was given a new setup function. " + "This is a bad idea; please refactor your code to give " + "the same setup function reference to <Graphie> on " + "every render.", perseusCore.Errors.Internal);
|
|
16784
16783
|
}
|
|
16785
|
-
if (!deepEq$
|
|
16784
|
+
if (!deepEq$4(this.props.options, prevProps.options) || !deepEq$4(this.props.box, prevProps.box) || !deepEq$4(this.props.range, prevProps.range)) {
|
|
16786
16785
|
this._setupGraphie();
|
|
16787
16786
|
}
|
|
16788
16787
|
this._updateMovables();
|
|
@@ -18557,7 +18556,6 @@ const styles$C = aphrodite.StyleSheet.create({
|
|
|
18557
18556
|
}
|
|
18558
18557
|
});
|
|
18559
18558
|
|
|
18560
|
-
/* eslint-disable no-useless-escape, no-prototype-builtins */
|
|
18561
18559
|
const rules$1 = {
|
|
18562
18560
|
...pureMarkdown.pureMarkdownRules,
|
|
18563
18561
|
// NOTE: basically ignored by JIPT. wraps everything at the outer layer
|
|
@@ -18734,7 +18732,7 @@ const rules$1 = {
|
|
|
18734
18732
|
// The href prop should always be here, but just in case somehow the article's
|
|
18735
18733
|
// link was not put together properly, let's make sure it's there so we
|
|
18736
18734
|
// don't break the entire page.
|
|
18737
|
-
const isKAUrl = href ? href.match(/https?:\/\/[
|
|
18735
|
+
const isKAUrl = href ? href.match(/https?:\/\/[^/]*khanacademy.org|^\//) : false;
|
|
18738
18736
|
if (!isKAUrl) {
|
|
18739
18737
|
// Prevents "reverse tabnabbing" phishing attacks
|
|
18740
18738
|
// @ts-expect-error - TS2322 - Type '"noopener noreferrer"' is not assignable to type 'null'.
|
|
@@ -21144,6 +21142,7 @@ function uniqueIdForInput() {
|
|
|
21144
21142
|
return `${prefix}${lastId}`;
|
|
21145
21143
|
}
|
|
21146
21144
|
class TextInput extends React__namespace.Component {
|
|
21145
|
+
inputRef = /*#__PURE__*/React__namespace.createRef();
|
|
21147
21146
|
static defaultProps = {
|
|
21148
21147
|
value: "",
|
|
21149
21148
|
disabled: false
|
|
@@ -21157,33 +21156,32 @@ class TextInput extends React__namespace.Component {
|
|
|
21157
21156
|
this.id = uniqueIdForInput();
|
|
21158
21157
|
}
|
|
21159
21158
|
}
|
|
21159
|
+
_getInput = () => {
|
|
21160
|
+
if (!this.inputRef.current) {
|
|
21161
|
+
throw new perseusCore.PerseusError("Input ref accessed before set", perseusCore.Errors.Internal);
|
|
21162
|
+
}
|
|
21163
|
+
return this.inputRef.current;
|
|
21164
|
+
};
|
|
21160
21165
|
focus = () => {
|
|
21161
|
-
|
|
21162
|
-
ReactDOM__default["default"].findDOMNode(this).focus();
|
|
21166
|
+
this._getInput().focus();
|
|
21163
21167
|
};
|
|
21164
21168
|
blur = () => {
|
|
21165
|
-
|
|
21166
|
-
ReactDOM__default["default"].findDOMNode(this).blur();
|
|
21169
|
+
this._getInput().blur();
|
|
21167
21170
|
};
|
|
21168
21171
|
getValue = () => {
|
|
21169
|
-
|
|
21170
|
-
return ReactDOM__default["default"].findDOMNode(this)?.value;
|
|
21172
|
+
return this.inputRef.current?.value;
|
|
21171
21173
|
};
|
|
21172
21174
|
getStringValue = () => {
|
|
21173
|
-
|
|
21174
|
-
return ReactDOM__default["default"].findDOMNode(this)?.value.toString();
|
|
21175
|
+
return this.inputRef.current?.value.toString();
|
|
21175
21176
|
};
|
|
21176
21177
|
setSelectionRange = (selectionStart, selectionEnd) => {
|
|
21177
|
-
|
|
21178
|
-
ReactDOM__default["default"].findDOMNode(this).setSelectionRange(selectionStart, selectionEnd);
|
|
21178
|
+
this._getInput().setSelectionRange(selectionStart, selectionEnd);
|
|
21179
21179
|
};
|
|
21180
21180
|
getSelectionStart = () => {
|
|
21181
|
-
|
|
21182
|
-
return ReactDOM__default["default"].findDOMNode(this).selectionStart;
|
|
21181
|
+
return this._getInput().selectionStart;
|
|
21183
21182
|
};
|
|
21184
21183
|
getSelectionEnd = () => {
|
|
21185
|
-
|
|
21186
|
-
return ReactDOM__default["default"].findDOMNode(this).selectionEnd;
|
|
21184
|
+
return this._getInput().selectionEnd;
|
|
21187
21185
|
};
|
|
21188
21186
|
render() {
|
|
21189
21187
|
const {
|
|
@@ -21198,6 +21196,7 @@ class TextInput extends React__namespace.Component {
|
|
|
21198
21196
|
} = this.props;
|
|
21199
21197
|
const formattedValue = value === null ? "" : value.toString();
|
|
21200
21198
|
return /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
|
|
21199
|
+
ref: this.inputRef,
|
|
21201
21200
|
style: style,
|
|
21202
21201
|
disabled: disabled,
|
|
21203
21202
|
id: this.id,
|
|
@@ -22219,7 +22218,7 @@ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
|
22219
22218
|
return value / 100;
|
|
22220
22219
|
}
|
|
22221
22220
|
|
|
22222
|
-
// Otherwise, we return input
|
|
22221
|
+
// Otherwise, we return input value (number) stripped of the "%".
|
|
22223
22222
|
return value;
|
|
22224
22223
|
}
|
|
22225
22224
|
function scoreNumericInput(userInput, rubric, strings) {
|
|
@@ -26277,7 +26276,15 @@ const styles$p = aphrodite.StyleSheet.create({
|
|
|
26277
26276
|
}
|
|
26278
26277
|
});
|
|
26279
26278
|
|
|
26280
|
-
|
|
26279
|
+
/**
|
|
26280
|
+
* Checks if the user has selected at least one option. Additional validation
|
|
26281
|
+
* is done in scoreRadio to check if the number of selected options is correct
|
|
26282
|
+
* and if the user has selected both a correct option and the "none of the above"
|
|
26283
|
+
* option.
|
|
26284
|
+
* @param userInput
|
|
26285
|
+
* @see `scoreRadio` for the additional validation logic and the scoring logic.
|
|
26286
|
+
*/
|
|
26287
|
+
function validateRadio(userInput) {
|
|
26281
26288
|
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
26282
26289
|
return sum + (selected ? 1 : 0);
|
|
26283
26290
|
}, 0);
|
|
@@ -26287,6 +26294,17 @@ function scoreRadio(userInput, rubric, strings) {
|
|
|
26287
26294
|
message: null
|
|
26288
26295
|
};
|
|
26289
26296
|
}
|
|
26297
|
+
return null;
|
|
26298
|
+
}
|
|
26299
|
+
|
|
26300
|
+
function scoreRadio(userInput, rubric, strings) {
|
|
26301
|
+
const validationError = validateRadio(userInput);
|
|
26302
|
+
if (validationError) {
|
|
26303
|
+
return validationError;
|
|
26304
|
+
}
|
|
26305
|
+
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
26306
|
+
return sum + (selected ? 1 : 0);
|
|
26307
|
+
}, 0);
|
|
26290
26308
|
const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
|
|
26291
26309
|
return currentChoice.correct ? sum + 1 : sum;
|
|
26292
26310
|
}, 0);
|
|
@@ -26766,18 +26784,17 @@ const transform = (widgetOptions, strings, problemNum) => {
|
|
|
26766
26784
|
};
|
|
26767
26785
|
const propUpgrades$1 = {
|
|
26768
26786
|
"1": v0props => {
|
|
26769
|
-
|
|
26770
|
-
|
|
26771
|
-
|
|
26772
|
-
|
|
26773
|
-
|
|
26774
|
-
} else {
|
|
26787
|
+
const {
|
|
26788
|
+
noneOfTheAbove,
|
|
26789
|
+
...rest
|
|
26790
|
+
} = v0props;
|
|
26791
|
+
if (noneOfTheAbove) {
|
|
26775
26792
|
throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
|
|
26776
26793
|
}
|
|
26777
|
-
return
|
|
26778
|
-
|
|
26779
|
-
hasNoneOfTheAbove:
|
|
26780
|
-
}
|
|
26794
|
+
return {
|
|
26795
|
+
...rest,
|
|
26796
|
+
hasNoneOfTheAbove: false
|
|
26797
|
+
};
|
|
26781
26798
|
}
|
|
26782
26799
|
};
|
|
26783
26800
|
var Radio = {
|
|
@@ -26814,23 +26831,36 @@ const getPromptJSON$n = (renderProps, userInput) => {
|
|
|
26814
26831
|
};
|
|
26815
26832
|
};
|
|
26816
26833
|
|
|
26817
|
-
|
|
26818
|
-
|
|
26819
|
-
|
|
26820
|
-
|
|
26821
|
-
|
|
26822
|
-
|
|
26823
|
-
|
|
26824
|
-
|
|
26825
|
-
|
|
26826
|
-
|
|
26827
|
-
|
|
26828
|
-
if (!completed) {
|
|
26834
|
+
/**
|
|
26835
|
+
* Checks userInput from the categorizer widget to see if the user has selected
|
|
26836
|
+
* a category for each item.
|
|
26837
|
+
* @param userInput - The user's input corresponding to an array of indices that
|
|
26838
|
+
* represent the selected category for each row/item.
|
|
26839
|
+
* @param validationData - An array of strings corresponding to each row/item
|
|
26840
|
+
* @param strings - Used to provide a validation message
|
|
26841
|
+
*/
|
|
26842
|
+
function validateCategorizer(userInput, validationData, strings) {
|
|
26843
|
+
const incomplete = validationData.items.some((_, i) => userInput.values[i] == null);
|
|
26844
|
+
if (incomplete) {
|
|
26829
26845
|
return {
|
|
26830
26846
|
type: "invalid",
|
|
26831
26847
|
message: strings.invalidSelection
|
|
26832
26848
|
};
|
|
26833
26849
|
}
|
|
26850
|
+
return null;
|
|
26851
|
+
}
|
|
26852
|
+
|
|
26853
|
+
function scoreCategorizer(userInput, scoringData, strings) {
|
|
26854
|
+
const validationError = validateCategorizer(userInput, scoringData, strings);
|
|
26855
|
+
if (validationError) {
|
|
26856
|
+
return validationError;
|
|
26857
|
+
}
|
|
26858
|
+
let allCorrect = true;
|
|
26859
|
+
scoringData.values.forEach((value, i) => {
|
|
26860
|
+
if (userInput.values[i] !== value) {
|
|
26861
|
+
allCorrect = false;
|
|
26862
|
+
}
|
|
26863
|
+
});
|
|
26834
26864
|
return {
|
|
26835
26865
|
type: "points",
|
|
26836
26866
|
earned: allCorrect ? 1 : 0,
|
|
@@ -27384,15 +27414,26 @@ const getPromptJSON$k = (renderProps, userInput) => {
|
|
|
27384
27414
|
};
|
|
27385
27415
|
};
|
|
27386
27416
|
|
|
27387
|
-
|
|
27388
|
-
|
|
27389
|
-
|
|
27417
|
+
/**
|
|
27418
|
+
* Checks if the user has selected an item from the dropdown before scoring.
|
|
27419
|
+
* This is shown with a userInput value / index other than 0.
|
|
27420
|
+
*/
|
|
27421
|
+
function validateDropdown(userInput) {
|
|
27422
|
+
if (userInput.value === 0) {
|
|
27390
27423
|
return {
|
|
27391
27424
|
type: "invalid",
|
|
27392
27425
|
message: null
|
|
27393
27426
|
};
|
|
27394
27427
|
}
|
|
27395
|
-
|
|
27428
|
+
return null;
|
|
27429
|
+
}
|
|
27430
|
+
|
|
27431
|
+
function scoreDropdown(userInput, rubric) {
|
|
27432
|
+
const validationError = validateDropdown(userInput);
|
|
27433
|
+
if (validationError) {
|
|
27434
|
+
return validationError;
|
|
27435
|
+
}
|
|
27436
|
+
const correct = rubric.choices[userInput.value - 1].correct;
|
|
27396
27437
|
return {
|
|
27397
27438
|
type: "points",
|
|
27398
27439
|
earned: correct ? 1 : 0,
|
|
@@ -27782,8 +27823,6 @@ const getNextState = (currentState, answerable) => {
|
|
|
27782
27823
|
}
|
|
27783
27824
|
};
|
|
27784
27825
|
|
|
27785
|
-
// exports has no 'transform'
|
|
27786
|
-
|
|
27787
27826
|
// A Graded Group is more or less a Group widget that displays a check
|
|
27788
27827
|
// answer button below the rendered content. When clicked, the widget grades
|
|
27789
27828
|
// the stuff inside and displays feedback about whether the inputted answer was
|
|
@@ -28453,7 +28492,6 @@ const getPromptJSON$g = (renderProps, userInput) => {
|
|
|
28453
28492
|
};
|
|
28454
28493
|
};
|
|
28455
28494
|
|
|
28456
|
-
/* eslint-disable one-var */
|
|
28457
28495
|
// @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
|
|
28458
28496
|
const Plot$1 = Graphie.Plot;
|
|
28459
28497
|
const DEFAULT_BACKGROUND_IMAGE = {
|
|
@@ -28554,14 +28592,14 @@ const Linear = ___default["default"].extend({}, PlotDefaults, {
|
|
|
28554
28592
|
return [m, b];
|
|
28555
28593
|
},
|
|
28556
28594
|
getFunctionForCoeffs: function (coeffs, x) {
|
|
28557
|
-
const m = coeffs[0]
|
|
28558
|
-
|
|
28595
|
+
const m = coeffs[0];
|
|
28596
|
+
const b = coeffs[1];
|
|
28559
28597
|
return m * x + b;
|
|
28560
28598
|
},
|
|
28561
28599
|
getEquationString: function (coords) {
|
|
28562
28600
|
const coeffs = this.getCoefficients(coords);
|
|
28563
|
-
const m = coeffs[0]
|
|
28564
|
-
|
|
28601
|
+
const m = coeffs[0];
|
|
28602
|
+
const b = coeffs[1];
|
|
28565
28603
|
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
28566
28604
|
}
|
|
28567
28605
|
});
|
|
@@ -28585,9 +28623,9 @@ const Quadratic = ___default["default"].extend({}, PlotDefaults, {
|
|
|
28585
28623
|
return [a, b, c];
|
|
28586
28624
|
},
|
|
28587
28625
|
getFunctionForCoeffs: function (coeffs, x) {
|
|
28588
|
-
const a = coeffs[0]
|
|
28589
|
-
|
|
28590
|
-
|
|
28626
|
+
const a = coeffs[0];
|
|
28627
|
+
const b = coeffs[1];
|
|
28628
|
+
const c = coeffs[2];
|
|
28591
28629
|
return (a * x + b) * x + c;
|
|
28592
28630
|
},
|
|
28593
28631
|
getPropsForCoeffs: function (coeffs) {
|
|
@@ -28599,9 +28637,9 @@ const Quadratic = ___default["default"].extend({}, PlotDefaults, {
|
|
|
28599
28637
|
},
|
|
28600
28638
|
getEquationString: function (coords) {
|
|
28601
28639
|
const coeffs = this.getCoefficients(coords);
|
|
28602
|
-
const a = coeffs[0]
|
|
28603
|
-
|
|
28604
|
-
|
|
28640
|
+
const a = coeffs[0];
|
|
28641
|
+
const b = coeffs[1];
|
|
28642
|
+
const c = coeffs[2];
|
|
28605
28643
|
return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
|
|
28606
28644
|
}
|
|
28607
28645
|
});
|
|
@@ -28620,10 +28658,10 @@ const Sinusoid = ___default["default"].extend({}, PlotDefaults, {
|
|
|
28620
28658
|
return [a, b, c, d];
|
|
28621
28659
|
},
|
|
28622
28660
|
getFunctionForCoeffs: function (coeffs, x) {
|
|
28623
|
-
const a = coeffs[0]
|
|
28624
|
-
|
|
28625
|
-
|
|
28626
|
-
|
|
28661
|
+
const a = coeffs[0];
|
|
28662
|
+
const b = coeffs[1];
|
|
28663
|
+
const c = coeffs[2];
|
|
28664
|
+
const d = coeffs[3];
|
|
28627
28665
|
return a * Math.sin(b * x - c) + d;
|
|
28628
28666
|
},
|
|
28629
28667
|
getPropsForCoeffs: function (coeffs) {
|
|
@@ -28636,10 +28674,10 @@ const Sinusoid = ___default["default"].extend({}, PlotDefaults, {
|
|
|
28636
28674
|
},
|
|
28637
28675
|
getEquationString: function (coords) {
|
|
28638
28676
|
const coeffs = this.getCoefficients(coords);
|
|
28639
|
-
const a = coeffs[0]
|
|
28640
|
-
|
|
28641
|
-
|
|
28642
|
-
|
|
28677
|
+
const a = coeffs[0];
|
|
28678
|
+
const b = coeffs[1];
|
|
28679
|
+
const c = coeffs[2];
|
|
28680
|
+
const d = coeffs[3];
|
|
28643
28681
|
return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
|
|
28644
28682
|
},
|
|
28645
28683
|
areEqual: function (coeffs1, coeffs2) {
|
|
@@ -28659,18 +28697,18 @@ const Tangent = ___default["default"].extend({}, PlotDefaults, {
|
|
|
28659
28697
|
return [a, b, c, d];
|
|
28660
28698
|
},
|
|
28661
28699
|
getFunctionForCoeffs: function (coeffs, x) {
|
|
28662
|
-
const a = coeffs[0]
|
|
28663
|
-
|
|
28664
|
-
|
|
28665
|
-
|
|
28700
|
+
const a = coeffs[0];
|
|
28701
|
+
const b = coeffs[1];
|
|
28702
|
+
const c = coeffs[2];
|
|
28703
|
+
const d = coeffs[3];
|
|
28666
28704
|
return a * Math.tan(b * x - c) + d;
|
|
28667
28705
|
},
|
|
28668
28706
|
getEquationString: function (coords) {
|
|
28669
28707
|
const coeffs = this.getCoefficients(coords);
|
|
28670
|
-
const a = coeffs[0]
|
|
28671
|
-
|
|
28672
|
-
|
|
28673
|
-
|
|
28708
|
+
const a = coeffs[0];
|
|
28709
|
+
const b = coeffs[1];
|
|
28710
|
+
const c = coeffs[2];
|
|
28711
|
+
const d = coeffs[3];
|
|
28674
28712
|
return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
|
|
28675
28713
|
},
|
|
28676
28714
|
areEqual: function (coeffs1, coeffs2) {
|
|
@@ -28729,9 +28767,9 @@ const Exponential = ___default["default"].extend({}, PlotDefaults, {
|
|
|
28729
28767
|
return [a, b, c];
|
|
28730
28768
|
},
|
|
28731
28769
|
getFunctionForCoeffs: function (coeffs, x) {
|
|
28732
|
-
const a = coeffs[0]
|
|
28733
|
-
|
|
28734
|
-
|
|
28770
|
+
const a = coeffs[0];
|
|
28771
|
+
const b = coeffs[1];
|
|
28772
|
+
const c = coeffs[2];
|
|
28735
28773
|
return a * Math.exp(b * x) + c;
|
|
28736
28774
|
},
|
|
28737
28775
|
getEquationString: function (coords, asymptote) {
|
|
@@ -28739,9 +28777,9 @@ const Exponential = ___default["default"].extend({}, PlotDefaults, {
|
|
|
28739
28777
|
return null;
|
|
28740
28778
|
}
|
|
28741
28779
|
const coeffs = this.getCoefficients(coords, asymptote);
|
|
28742
|
-
const a = coeffs[0]
|
|
28743
|
-
|
|
28744
|
-
|
|
28780
|
+
const a = coeffs[0];
|
|
28781
|
+
const b = coeffs[1];
|
|
28782
|
+
const c = coeffs[2];
|
|
28745
28783
|
return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
|
|
28746
28784
|
}
|
|
28747
28785
|
});
|
|
@@ -28786,9 +28824,9 @@ const Logarithm = ___default["default"].extend({}, PlotDefaults, {
|
|
|
28786
28824
|
}
|
|
28787
28825
|
},
|
|
28788
28826
|
getFunctionForCoeffs: function (coeffs, x, asymptote) {
|
|
28789
|
-
const a = coeffs[0]
|
|
28790
|
-
|
|
28791
|
-
|
|
28827
|
+
const a = coeffs[0];
|
|
28828
|
+
const b = coeffs[1];
|
|
28829
|
+
const c = coeffs[2];
|
|
28792
28830
|
return a * Math.log(b * x + c);
|
|
28793
28831
|
},
|
|
28794
28832
|
getEquationString: function (coords, asymptote) {
|
|
@@ -28796,9 +28834,9 @@ const Logarithm = ___default["default"].extend({}, PlotDefaults, {
|
|
|
28796
28834
|
return null;
|
|
28797
28835
|
}
|
|
28798
28836
|
const coeffs = this.getCoefficients(coords, asymptote);
|
|
28799
|
-
const a = coeffs[0]
|
|
28800
|
-
|
|
28801
|
-
|
|
28837
|
+
const a = coeffs[0];
|
|
28838
|
+
const b = coeffs[1];
|
|
28839
|
+
const c = coeffs[2];
|
|
28802
28840
|
return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
|
|
28803
28841
|
}
|
|
28804
28842
|
});
|
|
@@ -28822,16 +28860,16 @@ const AbsoluteValue = ___default["default"].extend({}, PlotDefaults, {
|
|
|
28822
28860
|
return [m, horizontalOffset, verticalOffset];
|
|
28823
28861
|
},
|
|
28824
28862
|
getFunctionForCoeffs: function (coeffs, x) {
|
|
28825
|
-
const m = coeffs[0]
|
|
28826
|
-
|
|
28827
|
-
|
|
28863
|
+
const m = coeffs[0];
|
|
28864
|
+
const horizontalOffset = coeffs[1];
|
|
28865
|
+
const verticalOffset = coeffs[2];
|
|
28828
28866
|
return m * Math.abs(x - horizontalOffset) + verticalOffset;
|
|
28829
28867
|
},
|
|
28830
28868
|
getEquationString: function (coords) {
|
|
28831
28869
|
const coeffs = this.getCoefficients(coords);
|
|
28832
|
-
const m = coeffs[0]
|
|
28833
|
-
|
|
28834
|
-
|
|
28870
|
+
const m = coeffs[0];
|
|
28871
|
+
const horizontalOffset = coeffs[1];
|
|
28872
|
+
const verticalOffset = coeffs[2];
|
|
28835
28873
|
return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
|
|
28836
28874
|
}
|
|
28837
28875
|
});
|
|
@@ -31432,67 +31470,6 @@ function isUnlimitedGraphState(state) {
|
|
|
31432
31470
|
return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
|
|
31433
31471
|
}
|
|
31434
31472
|
|
|
31435
|
-
/**
|
|
31436
|
-
* Replace all text outside of the $ TeX blocks with `\\text{...}`
|
|
31437
|
-
* This way, the entire resulting string can be rendered within <TeX>
|
|
31438
|
-
* and the text outside of the $ blocks will be non-TeX text.
|
|
31439
|
-
*/
|
|
31440
|
-
function replaceOutsideTeX(mathString) {
|
|
31441
|
-
// All the information we need is in the first section,
|
|
31442
|
-
// whether it's typed as "blockmath" or "paragraph"
|
|
31443
|
-
const firstSection = pureMarkdown.parse(mathString)[0];
|
|
31444
|
-
|
|
31445
|
-
// If it's blockMath, the outer level has the full math content.
|
|
31446
|
-
if (firstSection.type === "blockMath") {
|
|
31447
|
-
return firstSection.content;
|
|
31448
|
-
}
|
|
31449
|
-
|
|
31450
|
-
// If it's a paragraph, we need to iterate through the sections
|
|
31451
|
-
// to look for individual math blocks.
|
|
31452
|
-
const condensedNodes = condenseTextNodes(firstSection.content);
|
|
31453
|
-
let result = "";
|
|
31454
|
-
for (const piece of condensedNodes) {
|
|
31455
|
-
piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
|
|
31456
|
-
}
|
|
31457
|
-
return result;
|
|
31458
|
-
}
|
|
31459
|
-
// Helper function for replaceOutsideTeX()
|
|
31460
|
-
// Condense adjacent text nodes into a single text node
|
|
31461
|
-
function condenseTextNodes(nodes) {
|
|
31462
|
-
const result = [];
|
|
31463
|
-
if (!nodes) {
|
|
31464
|
-
return result;
|
|
31465
|
-
}
|
|
31466
|
-
let currentText = "";
|
|
31467
|
-
for (const node of nodes) {
|
|
31468
|
-
if (node.type === "math") {
|
|
31469
|
-
if (currentText) {
|
|
31470
|
-
result.push({
|
|
31471
|
-
type: "text",
|
|
31472
|
-
content: currentText
|
|
31473
|
-
});
|
|
31474
|
-
currentText = "";
|
|
31475
|
-
}
|
|
31476
|
-
result.push(node);
|
|
31477
|
-
} else {
|
|
31478
|
-
currentText += node.content;
|
|
31479
|
-
}
|
|
31480
|
-
}
|
|
31481
|
-
if (currentText) {
|
|
31482
|
-
result.push({
|
|
31483
|
-
type: "text",
|
|
31484
|
-
content: currentText
|
|
31485
|
-
});
|
|
31486
|
-
}
|
|
31487
|
-
return result;
|
|
31488
|
-
}
|
|
31489
|
-
|
|
31490
|
-
// Helper function for replaceOutsideTeX()
|
|
31491
|
-
function escapeSpecialChars(str) {
|
|
31492
|
-
// Escape $, \, {, and } characters
|
|
31493
|
-
return str.replace(/([$\\{}])/g, "\\$1");
|
|
31494
|
-
}
|
|
31495
|
-
|
|
31496
31473
|
/**
|
|
31497
31474
|
* Parse a string of text and math into a list of objects with type and content
|
|
31498
31475
|
*
|
|
@@ -31525,6 +31502,29 @@ const mathOnlyParser = SimpleMarkdown__default["default"].parserFor({
|
|
|
31525
31502
|
inline: true
|
|
31526
31503
|
});
|
|
31527
31504
|
|
|
31505
|
+
/**
|
|
31506
|
+
* Replace all text outside of the $ TeX blocks with `\\text{...}`
|
|
31507
|
+
* This way, the entire resulting string can be rendered within <TeX>
|
|
31508
|
+
* and the text outside of the $ blocks will be non-TeX text.
|
|
31509
|
+
*/
|
|
31510
|
+
function replaceOutsideTeX(mathString) {
|
|
31511
|
+
// All the information we need is in the first section,
|
|
31512
|
+
// whether it's typed as "blockmath" or "paragraph"
|
|
31513
|
+
const parsed = mathOnlyParser(mathString);
|
|
31514
|
+
let result = "";
|
|
31515
|
+
for (const piece of parsed) {
|
|
31516
|
+
piece.type === "math" ? result += "$" + piece.content + "$" : piece.type === "specialCharacter" ? result += escapeIfUnescaped(piece.content) : result += piece.content;
|
|
31517
|
+
}
|
|
31518
|
+
return `\\text{${result}}`;
|
|
31519
|
+
}
|
|
31520
|
+
function escapeIfUnescaped(character) {
|
|
31521
|
+
if (character.length === 1) {
|
|
31522
|
+
return "\\" + character;
|
|
31523
|
+
} else {
|
|
31524
|
+
return character;
|
|
31525
|
+
}
|
|
31526
|
+
}
|
|
31527
|
+
|
|
31528
31528
|
function AxisLabels() {
|
|
31529
31529
|
const {
|
|
31530
31530
|
range,
|
|
@@ -32092,6 +32092,7 @@ const getNumericFormat = KhanMath.getNumericFormat;
|
|
|
32092
32092
|
*/
|
|
32093
32093
|
class NumberInput extends React__namespace.Component {
|
|
32094
32094
|
static contextType = PerseusI18nContext;
|
|
32095
|
+
inputRef = /*#__PURE__*/React__namespace.createRef();
|
|
32095
32096
|
static propTypes = {
|
|
32096
32097
|
value: PropTypes__default["default"].number,
|
|
32097
32098
|
format: PropTypes__default["default"].string,
|
|
@@ -32118,23 +32119,24 @@ class NumberInput extends React__namespace.Component {
|
|
|
32118
32119
|
this._setValue(this.props.value, this.state.format);
|
|
32119
32120
|
}
|
|
32120
32121
|
}
|
|
32122
|
+
_getInput = () => {
|
|
32123
|
+
if (!this.inputRef.current) {
|
|
32124
|
+
throw new perseusCore.PerseusError("Input ref accessed before set", perseusCore.Errors.Internal);
|
|
32125
|
+
}
|
|
32126
|
+
return this.inputRef.current;
|
|
32127
|
+
};
|
|
32121
32128
|
|
|
32122
32129
|
/* Return the current "value" of this input
|
|
32123
32130
|
* If empty, it returns the placeholder (if it is a number) or null
|
|
32124
32131
|
*/
|
|
32125
32132
|
getValue = () => {
|
|
32126
|
-
return this.parseInputValue(
|
|
32127
|
-
// @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'value' does not exist on type 'Element | Text'.
|
|
32128
|
-
ReactDOM__default["default"].findDOMNode(this.refs.input).value // eslint-disable-line react/no-string-refs
|
|
32129
|
-
);
|
|
32133
|
+
return this.parseInputValue(this._getInput().value);
|
|
32130
32134
|
};
|
|
32131
32135
|
|
|
32132
32136
|
/* Return the current string value of this input */
|
|
32133
32137
|
getStringValue = () => {
|
|
32134
|
-
|
|
32135
|
-
return ReactDOM__default["default"].findDOMNode(this.refs.input).value.toString(); // eslint-disable-line react/no-string-refs
|
|
32138
|
+
return this._getInput().toString();
|
|
32136
32139
|
};
|
|
32137
|
-
|
|
32138
32140
|
parseInputValue = value => {
|
|
32139
32141
|
if (value === "") {
|
|
32140
32142
|
const placeholder = this.props.placeholder;
|
|
@@ -32146,26 +32148,21 @@ class NumberInput extends React__namespace.Component {
|
|
|
32146
32148
|
|
|
32147
32149
|
/* Set text input focus to this input */
|
|
32148
32150
|
focus = () => {
|
|
32149
|
-
|
|
32150
|
-
ReactDOM__default["default"].findDOMNode(this.refs.input).focus(); // eslint-disable-line react/no-string-refs
|
|
32151
|
+
this._getInput().focus();
|
|
32151
32152
|
this._handleFocus();
|
|
32152
32153
|
};
|
|
32153
32154
|
blur = () => {
|
|
32154
|
-
|
|
32155
|
-
ReactDOM__default["default"].findDOMNode(this.refs.input).blur(); // eslint-disable-line react/no-string-refs
|
|
32155
|
+
this._getInput().blur();
|
|
32156
32156
|
this._handleBlur();
|
|
32157
32157
|
};
|
|
32158
32158
|
setSelectionRange = (selectionStart, selectionEnd) => {
|
|
32159
|
-
|
|
32160
|
-
ReactDOM__default["default"].findDOMNode(this).setSelectionRange(selectionStart, selectionEnd);
|
|
32159
|
+
this._getInput().setSelectionRange(selectionStart, selectionEnd);
|
|
32161
32160
|
};
|
|
32162
32161
|
getSelectionStart = () => {
|
|
32163
|
-
|
|
32164
|
-
return ReactDOM__default["default"].findDOMNode(this).selectionStart;
|
|
32162
|
+
return this._getInput().selectionStart;
|
|
32165
32163
|
};
|
|
32166
32164
|
getSelectionEnd = () => {
|
|
32167
|
-
|
|
32168
|
-
return ReactDOM__default["default"].findDOMNode(this).selectionEnd;
|
|
32165
|
+
return this._getInput().selectionEnd;
|
|
32169
32166
|
};
|
|
32170
32167
|
_checkValidity = value => {
|
|
32171
32168
|
if (value == null) {
|
|
@@ -32225,9 +32222,7 @@ class NumberInput extends React__namespace.Component {
|
|
|
32225
32222
|
}
|
|
32226
32223
|
};
|
|
32227
32224
|
_setValue = (val, format) => {
|
|
32228
|
-
|
|
32229
|
-
// @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'val' does not exist on type 'JQueryStatic'.
|
|
32230
|
-
$__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.input)).val(toNumericString(val, format));
|
|
32225
|
+
this._getInput().value = toNumericString(val, format);
|
|
32231
32226
|
};
|
|
32232
32227
|
render() {
|
|
32233
32228
|
let classes = classNames__default["default"]({
|
|
@@ -32251,10 +32246,8 @@ class NumberInput extends React__namespace.Component {
|
|
|
32251
32246
|
} = this.props;
|
|
32252
32247
|
return /*#__PURE__*/React__namespace.createElement("input", _extends({}, restProps, {
|
|
32253
32248
|
className: classes,
|
|
32254
|
-
type: "text"
|
|
32255
|
-
|
|
32256
|
-
,
|
|
32257
|
-
ref: "input",
|
|
32249
|
+
type: "text",
|
|
32250
|
+
ref: this.inputRef,
|
|
32258
32251
|
onChange: this._handleChange,
|
|
32259
32252
|
onFocus: this._handleFocus,
|
|
32260
32253
|
onBlur: this._handleBlur,
|
|
@@ -33749,15 +33742,18 @@ function useControlPoint(params) {
|
|
|
33749
33742
|
} = useGraphConfig();
|
|
33750
33743
|
const {
|
|
33751
33744
|
point,
|
|
33752
|
-
|
|
33745
|
+
ariaDescribedBy,
|
|
33746
|
+
ariaLabel,
|
|
33747
|
+
ariaLive = "polite",
|
|
33753
33748
|
color,
|
|
33754
|
-
cursor,
|
|
33755
33749
|
constrain = p => snap(snapStep, p),
|
|
33750
|
+
cursor,
|
|
33751
|
+
forwardedRef = noop$1,
|
|
33752
|
+
sequenceNumber = 1,
|
|
33756
33753
|
onMove = noop$1,
|
|
33757
33754
|
onClick = noop$1,
|
|
33758
33755
|
onFocus = noop$1,
|
|
33759
|
-
onBlur = noop$1
|
|
33760
|
-
forwardedRef = noop$1
|
|
33756
|
+
onBlur = noop$1
|
|
33761
33757
|
} = params;
|
|
33762
33758
|
const {
|
|
33763
33759
|
strings,
|
|
@@ -33780,6 +33776,13 @@ function useControlPoint(params) {
|
|
|
33780
33776
|
onMove,
|
|
33781
33777
|
constrainKeyboardMovement: constrain
|
|
33782
33778
|
});
|
|
33779
|
+
|
|
33780
|
+
// if custom aria label is not provided, will use default of sequence number and point coordinates
|
|
33781
|
+
const pointAriaLabel = ariaLabel || strings.srPointAtCoordinates({
|
|
33782
|
+
num: sequenceNumber,
|
|
33783
|
+
x: srFormatNumber(point[X], locale),
|
|
33784
|
+
y: srFormatNumber(point[Y], locale)
|
|
33785
|
+
});
|
|
33783
33786
|
React.useLayoutEffect(() => {
|
|
33784
33787
|
setForwardedRef(forwardedRef, focusableHandleRef.current);
|
|
33785
33788
|
}, [forwardedRef]);
|
|
@@ -33789,15 +33792,9 @@ function useControlPoint(params) {
|
|
|
33789
33792
|
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
33790
33793
|
ref: focusableHandleRef,
|
|
33791
33794
|
role: "button",
|
|
33792
|
-
"aria-
|
|
33793
|
-
|
|
33794
|
-
|
|
33795
|
-
y: srFormatNumber(point[Y], locale)
|
|
33796
|
-
})
|
|
33797
|
-
// aria-live="assertive" causes the new location of the point to be
|
|
33798
|
-
// announced immediately on move.
|
|
33799
|
-
,
|
|
33800
|
-
"aria-live": "assertive",
|
|
33795
|
+
"aria-describedby": ariaDescribedBy,
|
|
33796
|
+
"aria-label": pointAriaLabel,
|
|
33797
|
+
"aria-live": ariaLive,
|
|
33801
33798
|
onFocus: event => {
|
|
33802
33799
|
onFocus(event);
|
|
33803
33800
|
setFocused(true);
|
|
@@ -35095,6 +35092,7 @@ const MafsGraph = props => {
|
|
|
35095
35092
|
const uniqueId = React__namespace.useId();
|
|
35096
35093
|
const descriptionId = `interactive-graph-description-${uniqueId}`;
|
|
35097
35094
|
const interactiveElementsDescriptionId = `interactive-graph-interactive-elements-description-${uniqueId}`;
|
|
35095
|
+
const unlimitedGraphKeyboardPromptId = `unlimited-graph-keyboard-prompt-${uniqueId}`;
|
|
35098
35096
|
const graphRef = React__namespace.useRef(null);
|
|
35099
35097
|
const {
|
|
35100
35098
|
analytics
|
|
@@ -35167,7 +35165,7 @@ const MafsGraph = props => {
|
|
|
35167
35165
|
handleKeyboardEvent(event, state, dispatch);
|
|
35168
35166
|
},
|
|
35169
35167
|
"aria-label": fullGraphAriaLabel,
|
|
35170
|
-
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId),
|
|
35168
|
+
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && "unlimited-graph-keyboard-prompt"),
|
|
35171
35169
|
ref: graphRef,
|
|
35172
35170
|
tabIndex: 0,
|
|
35173
35171
|
onFocus: event => {
|
|
@@ -35237,6 +35235,7 @@ const MafsGraph = props => {
|
|
|
35237
35235
|
height: height
|
|
35238
35236
|
}, /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), graph)))), interactionPrompt && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
35239
35237
|
style: {
|
|
35238
|
+
display: interactionPrompt ? undefined : "hidden",
|
|
35240
35239
|
textAlign: "center",
|
|
35241
35240
|
backgroundColor: "white",
|
|
35242
35241
|
border: "1px solid #21242C52",
|
|
@@ -35248,7 +35247,9 @@ const MafsGraph = props => {
|
|
|
35248
35247
|
top: "50%",
|
|
35249
35248
|
transform: "translateY(-50%)"
|
|
35250
35249
|
}
|
|
35251
|
-
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium,
|
|
35250
|
+
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
|
|
35251
|
+
id: unlimitedGraphKeyboardPromptId
|
|
35252
|
+
}, strings.graphKeyboardPrompt))), renderGraphControls({
|
|
35252
35253
|
state,
|
|
35253
35254
|
dispatch,
|
|
35254
35255
|
width,
|
|
@@ -35311,6 +35312,10 @@ const renderPolygonGraphControls = props => {
|
|
|
35311
35312
|
} = props;
|
|
35312
35313
|
const shouldShowRemoveButton = showRemovePointButton && focusedPointIndex !== null;
|
|
35313
35314
|
|
|
35315
|
+
// We want to disable the closePolygon button when
|
|
35316
|
+
// there are not enough coords to make a polygon
|
|
35317
|
+
const disableCloseButton = coords.length < 3;
|
|
35318
|
+
|
|
35314
35319
|
// If polygon is closed, show open button.
|
|
35315
35320
|
// If polygon is open, show close button.
|
|
35316
35321
|
const polygonButton = closedPolygon ? /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
@@ -35328,12 +35333,12 @@ const renderPolygonGraphControls = props => {
|
|
|
35328
35333
|
// Conditional disable when there are less than 3 points in
|
|
35329
35334
|
// the graph
|
|
35330
35335
|
,
|
|
35331
|
-
disabled:
|
|
35336
|
+
disabled: disableCloseButton,
|
|
35332
35337
|
style: {
|
|
35333
35338
|
width: "100%",
|
|
35334
35339
|
marginLeft: "20px"
|
|
35335
35340
|
},
|
|
35336
|
-
tabIndex: 0,
|
|
35341
|
+
tabIndex: disableCloseButton ? -1 : 0,
|
|
35337
35342
|
onClick: () => {
|
|
35338
35343
|
props.dispatch(actions.polygon.closePolygon());
|
|
35339
35344
|
}
|
|
@@ -36257,8 +36262,12 @@ function doMovePoint(state, action) {
|
|
|
36257
36262
|
newValue: newValue
|
|
36258
36263
|
});
|
|
36259
36264
|
|
|
36260
|
-
//
|
|
36261
|
-
if
|
|
36265
|
+
// Boolean value to track whether we can let the polygon sides interact.
|
|
36266
|
+
// They can interact if it's an unlimited polygon that is open.
|
|
36267
|
+
const polygonSidesCanIntersect = state.numSides === "unlimited" && !state.closedPolygon;
|
|
36268
|
+
|
|
36269
|
+
// Reject the move if it would cause the sides of the polygon to cross.
|
|
36270
|
+
if (!polygonSidesCanIntersect && polygonSidesIntersect(newCoords)) {
|
|
36262
36271
|
return state;
|
|
36263
36272
|
}
|
|
36264
36273
|
return {
|
|
@@ -36861,7 +36870,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
36861
36870
|
});
|
|
36862
36871
|
|
|
36863
36872
|
const eq$1 = Util.eq;
|
|
36864
|
-
const deepEq$
|
|
36873
|
+
const deepEq$3 = Util.deepEq;
|
|
36865
36874
|
function scoreInteractiveGraph(userInput, rubric) {
|
|
36866
36875
|
// None-type graphs are not graded
|
|
36867
36876
|
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
@@ -36911,7 +36920,7 @@ function scoreInteractiveGraph(userInput, rubric) {
|
|
|
36911
36920
|
// If the parabola coefficients match, it's correct.
|
|
36912
36921
|
const guessCoeffs = getQuadraticCoefficients(userInput.coords);
|
|
36913
36922
|
const correctCoeffs = getQuadraticCoefficients(rubric.correct.coords);
|
|
36914
|
-
if (deepEq$
|
|
36923
|
+
if (deepEq$3(guessCoeffs, correctCoeffs)) {
|
|
36915
36924
|
return {
|
|
36916
36925
|
type: "points",
|
|
36917
36926
|
earned: 1,
|
|
@@ -36925,7 +36934,7 @@ function scoreInteractiveGraph(userInput, rubric) {
|
|
|
36925
36934
|
const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
|
|
36926
36935
|
const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
|
|
36927
36936
|
// If the canonical coefficients match, it's correct.
|
|
36928
|
-
if (deepEq$
|
|
36937
|
+
if (deepEq$3(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
|
|
36929
36938
|
return {
|
|
36930
36939
|
type: "points",
|
|
36931
36940
|
earned: 1,
|
|
@@ -36934,7 +36943,7 @@ function scoreInteractiveGraph(userInput, rubric) {
|
|
|
36934
36943
|
};
|
|
36935
36944
|
}
|
|
36936
36945
|
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
36937
|
-
if (deepEq$
|
|
36946
|
+
if (deepEq$3(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
|
|
36938
36947
|
return {
|
|
36939
36948
|
type: "points",
|
|
36940
36949
|
earned: 1,
|
|
@@ -36956,7 +36965,7 @@ function scoreInteractiveGraph(userInput, rubric) {
|
|
|
36956
36965
|
guess?.sort();
|
|
36957
36966
|
// @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
|
|
36958
36967
|
correct.sort();
|
|
36959
|
-
if (deepEq$
|
|
36968
|
+
if (deepEq$3(guess, correct)) {
|
|
36960
36969
|
return {
|
|
36961
36970
|
type: "points",
|
|
36962
36971
|
earned: 1,
|
|
@@ -36978,7 +36987,7 @@ function scoreInteractiveGraph(userInput, rubric) {
|
|
|
36978
36987
|
/* exact */
|
|
36979
36988
|
guess.sort();
|
|
36980
36989
|
correct.sort();
|
|
36981
|
-
match = deepEq$
|
|
36990
|
+
match = deepEq$3(guess, correct);
|
|
36982
36991
|
}
|
|
36983
36992
|
if (match) {
|
|
36984
36993
|
return {
|
|
@@ -36993,7 +37002,7 @@ function scoreInteractiveGraph(userInput, rubric) {
|
|
|
36993
37002
|
let correct = Util.deepClone(rubric.correct.coords);
|
|
36994
37003
|
guess = ___default["default"].invoke(guess, "sort").sort();
|
|
36995
37004
|
correct = ___default["default"].invoke(correct, "sort").sort();
|
|
36996
|
-
if (deepEq$
|
|
37005
|
+
if (deepEq$3(guess, correct)) {
|
|
36997
37006
|
return {
|
|
36998
37007
|
type: "points",
|
|
36999
37008
|
earned: 1,
|
|
@@ -37004,7 +37013,7 @@ function scoreInteractiveGraph(userInput, rubric) {
|
|
|
37004
37013
|
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
37005
37014
|
const guess = userInput.coords;
|
|
37006
37015
|
const correct = rubric.correct.coords;
|
|
37007
|
-
if (deepEq$
|
|
37016
|
+
if (deepEq$3(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
|
|
37008
37017
|
return {
|
|
37009
37018
|
type: "points",
|
|
37010
37019
|
earned: 1,
|
|
@@ -37031,7 +37040,7 @@ function scoreInteractiveGraph(userInput, rubric) {
|
|
|
37031
37040
|
/* exact */
|
|
37032
37041
|
match =
|
|
37033
37042
|
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
37034
|
-
deepEq$
|
|
37043
|
+
deepEq$3(guess[1], correct[1]) &&
|
|
37035
37044
|
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
37036
37045
|
collinear(correct[1], correct[0], guess[0]) &&
|
|
37037
37046
|
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
@@ -41293,14 +41302,20 @@ class Measurer extends React__namespace.Component {
|
|
|
41293
41302
|
}
|
|
41294
41303
|
const propUpgrades = {
|
|
41295
41304
|
"1": v0props => {
|
|
41296
|
-
const
|
|
41305
|
+
const {
|
|
41306
|
+
imageUrl,
|
|
41307
|
+
imageTop,
|
|
41308
|
+
imageLeft,
|
|
41309
|
+
...rest
|
|
41310
|
+
} = v0props;
|
|
41311
|
+
return {
|
|
41312
|
+
...rest,
|
|
41297
41313
|
image: {
|
|
41298
|
-
url:
|
|
41299
|
-
top:
|
|
41300
|
-
left:
|
|
41314
|
+
url: imageUrl,
|
|
41315
|
+
top: imageTop,
|
|
41316
|
+
left: imageLeft
|
|
41301
41317
|
}
|
|
41302
|
-
}
|
|
41303
|
-
return v1props;
|
|
41318
|
+
};
|
|
41304
41319
|
}
|
|
41305
41320
|
};
|
|
41306
41321
|
var Measurer$1 = {
|
|
@@ -42834,14 +42849,28 @@ const getPromptJSON$5 = (renderProps, userInput) => {
|
|
|
42834
42849
|
};
|
|
42835
42850
|
};
|
|
42836
42851
|
|
|
42837
|
-
|
|
42852
|
+
/**
|
|
42853
|
+
* Checks user input from the orderer widget to see if the user has started
|
|
42854
|
+
* ordering the options, making the widget scorable.
|
|
42855
|
+
* @param userInput
|
|
42856
|
+
* @see `scoreOrderer` for more details.
|
|
42857
|
+
*/
|
|
42858
|
+
function validateOrderer(userInput) {
|
|
42838
42859
|
if (userInput.current.length === 0) {
|
|
42839
42860
|
return {
|
|
42840
42861
|
type: "invalid",
|
|
42841
42862
|
message: null
|
|
42842
42863
|
};
|
|
42843
42864
|
}
|
|
42844
|
-
|
|
42865
|
+
return null;
|
|
42866
|
+
}
|
|
42867
|
+
|
|
42868
|
+
function scoreOrderer(userInput, rubric) {
|
|
42869
|
+
const validateError = validateOrderer(userInput);
|
|
42870
|
+
if (validateError) {
|
|
42871
|
+
return validateError;
|
|
42872
|
+
}
|
|
42873
|
+
const correct = ___default["default"].isEqual(userInput.current, rubric.correctOptions.map(option => option.content));
|
|
42845
42874
|
return {
|
|
42846
42875
|
type: "points",
|
|
42847
42876
|
earned: correct ? 1 : 0,
|
|
@@ -43017,15 +43046,13 @@ class Card extends React__namespace.Component {
|
|
|
43017
43046
|
}))));
|
|
43018
43047
|
}
|
|
43019
43048
|
}
|
|
43020
|
-
const NORMAL = "normal";
|
|
43021
|
-
const HORIZONTAL = "horizontal";
|
|
43022
43049
|
class Orderer extends React__namespace.Component {
|
|
43023
43050
|
static defaultProps = {
|
|
43024
43051
|
current: [],
|
|
43025
43052
|
options: [],
|
|
43026
43053
|
correctOptions: [],
|
|
43027
|
-
height:
|
|
43028
|
-
layout:
|
|
43054
|
+
height: "normal",
|
|
43055
|
+
layout: "horizontal",
|
|
43029
43056
|
linterContext: PerseusLinter.linterContextDefault
|
|
43030
43057
|
};
|
|
43031
43058
|
state = {
|
|
@@ -43178,7 +43205,7 @@ class Orderer extends React__namespace.Component {
|
|
|
43178
43205
|
};
|
|
43179
43206
|
findCorrectIndex = (draggable, list) => {
|
|
43180
43207
|
// Find the correct index for a card given the current cards.
|
|
43181
|
-
const isHorizontal = this.props.layout ===
|
|
43208
|
+
const isHorizontal = this.props.layout === "horizontal";
|
|
43182
43209
|
// eslint-disable-next-line react/no-string-refs
|
|
43183
43210
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
43184
43211
|
const $dragList = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.dragList));
|
|
@@ -43228,28 +43255,24 @@ class Orderer extends React__namespace.Component {
|
|
|
43228
43255
|
if (draggable == null) {
|
|
43229
43256
|
return false;
|
|
43230
43257
|
}
|
|
43231
|
-
const isHorizontal = this.props.layout ===
|
|
43232
|
-
|
|
43233
|
-
|
|
43234
|
-
|
|
43235
|
-
|
|
43236
|
-
|
|
43237
|
-
|
|
43238
|
-
|
|
43239
|
-
|
|
43240
|
-
|
|
43241
|
-
|
|
43242
|
-
|
|
43243
|
-
|
|
43244
|
-
|
|
43245
|
-
|
|
43246
|
-
|
|
43247
|
-
|
|
43248
|
-
|
|
43249
|
-
// @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'width' does not exist on type 'JQueryStatic'.
|
|
43250
|
-
$__default["default"](dragList).width();
|
|
43251
|
-
const // @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
|
|
43252
|
-
draggableWidth = $draggable.outerWidth(true);
|
|
43258
|
+
const isHorizontal = this.props.layout === "horizontal";
|
|
43259
|
+
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
43260
|
+
const $draggable = $__default["default"](ReactDOM__default["default"].findDOMNode(draggable));
|
|
43261
|
+
// eslint-disable-next-line react/no-string-refs
|
|
43262
|
+
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
43263
|
+
const $bank = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.bank));
|
|
43264
|
+
// @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
|
|
43265
|
+
const draggableOffset = $draggable.offset();
|
|
43266
|
+
// @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
|
|
43267
|
+
const bankOffset = $bank.offset();
|
|
43268
|
+
// @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
|
|
43269
|
+
const draggableHeight = $draggable.outerHeight(true);
|
|
43270
|
+
// @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
|
|
43271
|
+
const bankHeight = $bank.outerHeight(true);
|
|
43272
|
+
// @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
|
|
43273
|
+
const bankWidth = $bank.outerWidth(true);
|
|
43274
|
+
// @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
|
|
43275
|
+
const draggableWidth = $draggable.outerWidth(true);
|
|
43253
43276
|
if (isHorizontal) {
|
|
43254
43277
|
return draggableOffset.top + draggableHeight / 2 < bankOffset.top + bankHeight;
|
|
43255
43278
|
}
|
|
@@ -43668,18 +43691,36 @@ const getPromptJSON$3 = () => {
|
|
|
43668
43691
|
};
|
|
43669
43692
|
|
|
43670
43693
|
const {
|
|
43671
|
-
deepEq: deepEq$
|
|
43694
|
+
deepEq: deepEq$2
|
|
43672
43695
|
} = Util;
|
|
43673
|
-
|
|
43674
|
-
|
|
43696
|
+
|
|
43697
|
+
/**
|
|
43698
|
+
* Checks user input to confirm it is not the same as the starting values for the graph.
|
|
43699
|
+
* This means the user has modified the graph, and the question can be scored.
|
|
43700
|
+
*
|
|
43701
|
+
* @see 'scorePlotter' for more details on scoring.
|
|
43702
|
+
*/
|
|
43703
|
+
function validatePlotter(userInput, validationData) {
|
|
43704
|
+
if (deepEq$2(userInput, validationData.starting)) {
|
|
43675
43705
|
return {
|
|
43676
43706
|
type: "invalid",
|
|
43677
43707
|
message: null
|
|
43678
43708
|
};
|
|
43679
43709
|
}
|
|
43710
|
+
return null;
|
|
43711
|
+
}
|
|
43712
|
+
|
|
43713
|
+
const {
|
|
43714
|
+
deepEq: deepEq$1
|
|
43715
|
+
} = Util;
|
|
43716
|
+
function scorePlotter(userInput, scoringData) {
|
|
43717
|
+
const validationError = validatePlotter(userInput, scoringData);
|
|
43718
|
+
if (validationError) {
|
|
43719
|
+
return validationError;
|
|
43720
|
+
}
|
|
43680
43721
|
return {
|
|
43681
43722
|
type: "points",
|
|
43682
|
-
earned: deepEq$1(userInput,
|
|
43723
|
+
earned: deepEq$1(userInput, scoringData.correct) ? 1 : 0,
|
|
43683
43724
|
total: 1,
|
|
43684
43725
|
message: null
|
|
43685
43726
|
};
|
|
@@ -44566,7 +44607,14 @@ const getPromptJSON$1 = userInput => {
|
|
|
44566
44607
|
};
|
|
44567
44608
|
};
|
|
44568
44609
|
|
|
44569
|
-
|
|
44610
|
+
/**
|
|
44611
|
+
* Checks user input for the sorter widget to ensure that the user has made
|
|
44612
|
+
* changes before attempting to score the widget.
|
|
44613
|
+
* @param userInput
|
|
44614
|
+
* @see 'scoreSorter' in 'packages/perseus/src/widgets/sorter/score-sorter.ts'
|
|
44615
|
+
* for more details on how the sorter widget is scored.
|
|
44616
|
+
*/
|
|
44617
|
+
function validateSorter(userInput) {
|
|
44570
44618
|
// If the sorter widget hasn't been changed yet, we treat it as "empty" which
|
|
44571
44619
|
// prevents the "Check" button from becoming active. We want the user
|
|
44572
44620
|
// to make a change before trying to move forward. This makes an
|
|
@@ -44579,6 +44627,14 @@ function scoreSorter(userInput, rubric) {
|
|
|
44579
44627
|
message: null
|
|
44580
44628
|
};
|
|
44581
44629
|
}
|
|
44630
|
+
return null;
|
|
44631
|
+
}
|
|
44632
|
+
|
|
44633
|
+
function scoreSorter(userInput, rubric) {
|
|
44634
|
+
const validationError = validateSorter(userInput);
|
|
44635
|
+
if (validationError) {
|
|
44636
|
+
return validationError;
|
|
44637
|
+
}
|
|
44582
44638
|
const correct = Util.deepEq(userInput.options, rubric.correct);
|
|
44583
44639
|
return {
|
|
44584
44640
|
type: "points",
|
|
@@ -45153,7 +45209,7 @@ const init = function () {
|
|
|
45153
45209
|
|
|
45154
45210
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45155
45211
|
const libName = "@khanacademy/perseus";
|
|
45156
|
-
const libVersion = "43.
|
|
45212
|
+
const libVersion = "43.1.0";
|
|
45157
45213
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45158
45214
|
|
|
45159
45215
|
const apiVersion = {
|
|
@@ -45438,7 +45494,6 @@ const styles$3 = aphrodite.StyleSheet.create({
|
|
|
45438
45494
|
}
|
|
45439
45495
|
});
|
|
45440
45496
|
|
|
45441
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
45442
45497
|
// Use of UNSAFE_componentWillReceiveProps:
|
|
45443
45498
|
// eslint-disable-next-line react/no-unsafe
|
|
45444
45499
|
class HintsRenderer extends React__namespace.Component {
|
|
@@ -48664,6 +48719,27 @@ function pair(parseA, parseB) {
|
|
|
48664
48719
|
};
|
|
48665
48720
|
}
|
|
48666
48721
|
|
|
48722
|
+
function pipeParsers(p) {
|
|
48723
|
+
return new ParserPipeline(p);
|
|
48724
|
+
}
|
|
48725
|
+
class ParserPipeline {
|
|
48726
|
+
constructor(parser) {
|
|
48727
|
+
this.parser = parser;
|
|
48728
|
+
}
|
|
48729
|
+
then(nextParser) {
|
|
48730
|
+
return new ParserPipeline(composeParsers(this.parser, nextParser));
|
|
48731
|
+
}
|
|
48732
|
+
}
|
|
48733
|
+
function composeParsers(parserA, parserB) {
|
|
48734
|
+
return (rawValue, ctx) => {
|
|
48735
|
+
const partialResult = parserA(rawValue, ctx);
|
|
48736
|
+
if (isFailure(partialResult)) {
|
|
48737
|
+
return partialResult;
|
|
48738
|
+
}
|
|
48739
|
+
return parserB(partialResult.value, ctx);
|
|
48740
|
+
};
|
|
48741
|
+
}
|
|
48742
|
+
|
|
48667
48743
|
function record(parseKey, parseValue) {
|
|
48668
48744
|
return (rawValue, ctx) => {
|
|
48669
48745
|
if (!isObject(rawValue)) {
|
|
@@ -48744,6 +48820,20 @@ function either(parseA, parseB) {
|
|
|
48744
48820
|
};
|
|
48745
48821
|
}
|
|
48746
48822
|
|
|
48823
|
+
function defaulted(parser, fallback) {
|
|
48824
|
+
return (rawValue, ctx) => {
|
|
48825
|
+
if (rawValue == null) {
|
|
48826
|
+
return success(fallback(rawValue));
|
|
48827
|
+
}
|
|
48828
|
+
return parser(rawValue, ctx);
|
|
48829
|
+
};
|
|
48830
|
+
}
|
|
48831
|
+
|
|
48832
|
+
const parseImages = defaulted(record(string, object({
|
|
48833
|
+
width: number,
|
|
48834
|
+
height: number
|
|
48835
|
+
})), () => ({}));
|
|
48836
|
+
|
|
48747
48837
|
function parseWidget(parseType, parseOptions) {
|
|
48748
48838
|
return object({
|
|
48749
48839
|
type: parseType,
|
|
@@ -48752,10 +48842,21 @@ function parseWidget(parseType, parseOptions) {
|
|
|
48752
48842
|
alignment: optional(string),
|
|
48753
48843
|
options: parseOptions,
|
|
48754
48844
|
key: optional(number),
|
|
48755
|
-
version: object({
|
|
48845
|
+
version: optional(object({
|
|
48756
48846
|
major: number,
|
|
48757
48847
|
minor: number
|
|
48758
|
-
})
|
|
48848
|
+
}))
|
|
48849
|
+
});
|
|
48850
|
+
}
|
|
48851
|
+
function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
|
|
48852
|
+
return object({
|
|
48853
|
+
type: parseType,
|
|
48854
|
+
static: optional(boolean),
|
|
48855
|
+
graded: optional(boolean),
|
|
48856
|
+
alignment: optional(string),
|
|
48857
|
+
options: parseOptions,
|
|
48858
|
+
key: optional(number),
|
|
48859
|
+
version: parseVersion
|
|
48759
48860
|
});
|
|
48760
48861
|
}
|
|
48761
48862
|
|
|
@@ -48763,7 +48864,7 @@ const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
|
|
|
48763
48864
|
items: array(string),
|
|
48764
48865
|
categories: array(string),
|
|
48765
48866
|
randomizeItems: boolean,
|
|
48766
|
-
static: boolean,
|
|
48867
|
+
static: defaulted(boolean, () => false),
|
|
48767
48868
|
values: array(number),
|
|
48768
48869
|
highlightLint: optional(boolean),
|
|
48769
48870
|
linterContext: optional(object({
|
|
@@ -48795,7 +48896,7 @@ const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
|
48795
48896
|
|
|
48796
48897
|
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
48797
48898
|
placeholder: string,
|
|
48798
|
-
static: boolean,
|
|
48899
|
+
static: defaulted(boolean, () => false),
|
|
48799
48900
|
choices: array(object({
|
|
48800
48901
|
content: string,
|
|
48801
48902
|
correct: boolean
|
|
@@ -48818,9 +48919,12 @@ const parseAnswerForm = object({
|
|
|
48818
48919
|
form: boolean,
|
|
48819
48920
|
simplify: boolean,
|
|
48820
48921
|
considered: enumeration("correct", "wrong", "ungraded"),
|
|
48821
|
-
key: optional(string)
|
|
48922
|
+
key: pipeParsers(optional(union(string).or(number).parser)).then((key, ctx) => ctx.success(String(key))).parser
|
|
48822
48923
|
});
|
|
48823
|
-
const
|
|
48924
|
+
const parseExpressionWidgetV1 = parseWidgetWithVersion(object({
|
|
48925
|
+
major: constant(1),
|
|
48926
|
+
minor: number
|
|
48927
|
+
}), constant("expression"), object({
|
|
48824
48928
|
answerForms: array(parseAnswerForm),
|
|
48825
48929
|
functions: array(string),
|
|
48826
48930
|
times: boolean,
|
|
@@ -48829,23 +48933,69 @@ const parseExpressionWidget = parseWidget(constant("expression"), object({
|
|
|
48829
48933
|
buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
|
|
48830
48934
|
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
48831
48935
|
}));
|
|
48936
|
+
const parseExpressionWidgetV0 = parseWidgetWithVersion(optional(object({
|
|
48937
|
+
major: constant(0),
|
|
48938
|
+
minor: number
|
|
48939
|
+
})), constant("expression"), object({
|
|
48940
|
+
functions: array(string),
|
|
48941
|
+
times: boolean,
|
|
48942
|
+
visibleLabel: optional(string),
|
|
48943
|
+
ariaLabel: optional(string),
|
|
48944
|
+
form: boolean,
|
|
48945
|
+
simplify: boolean,
|
|
48946
|
+
value: string,
|
|
48947
|
+
buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
|
|
48948
|
+
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
48949
|
+
}));
|
|
48950
|
+
function migrateV0ToV1(widget, ctx) {
|
|
48951
|
+
const {
|
|
48952
|
+
options
|
|
48953
|
+
} = widget;
|
|
48954
|
+
return ctx.success({
|
|
48955
|
+
...widget,
|
|
48956
|
+
version: {
|
|
48957
|
+
major: 1,
|
|
48958
|
+
minor: 0
|
|
48959
|
+
},
|
|
48960
|
+
options: {
|
|
48961
|
+
times: options.times,
|
|
48962
|
+
buttonSets: options.buttonSets,
|
|
48963
|
+
functions: options.functions,
|
|
48964
|
+
buttonsVisible: options.buttonsVisible,
|
|
48965
|
+
visibleLabel: options.visibleLabel,
|
|
48966
|
+
ariaLabel: options.ariaLabel,
|
|
48967
|
+
answerForms: [{
|
|
48968
|
+
considered: "correct",
|
|
48969
|
+
form: options.form,
|
|
48970
|
+
simplify: options.simplify,
|
|
48971
|
+
value: options.value
|
|
48972
|
+
}]
|
|
48973
|
+
}
|
|
48974
|
+
});
|
|
48975
|
+
}
|
|
48976
|
+
const parseExpressionWidget = union(parseExpressionWidgetV1).or(pipeParsers(parseExpressionWidgetV0).then(migrateV0ToV1).parser).parser;
|
|
48832
48977
|
|
|
48833
|
-
const parsePerseusRenderer = object({
|
|
48834
|
-
|
|
48978
|
+
const parsePerseusRenderer = defaulted(object({
|
|
48979
|
+
// TODO(benchristel): content is also defaulted to empty string in
|
|
48980
|
+
// renderer.tsx. See if we can remove one default or the other.
|
|
48981
|
+
content: defaulted(string, () => ""),
|
|
48835
48982
|
// This module has an import cycle with parseWidgetsMap, because the
|
|
48836
48983
|
// `group` widget can contain another renderer.
|
|
48837
48984
|
// The anonymous function below ensures that we don't try to access
|
|
48838
48985
|
// parseWidgetsMap before it's defined.
|
|
48839
|
-
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
48986
|
+
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
48840
48987
|
metadata: optional(array(string)),
|
|
48841
|
-
images:
|
|
48842
|
-
|
|
48843
|
-
|
|
48844
|
-
|
|
48845
|
-
|
|
48988
|
+
images: parseImages
|
|
48989
|
+
}),
|
|
48990
|
+
// Default value
|
|
48991
|
+
() => ({
|
|
48992
|
+
content: "",
|
|
48993
|
+
widgets: {},
|
|
48994
|
+
images: {}
|
|
48995
|
+
}));
|
|
48846
48996
|
|
|
48847
48997
|
const parseGradedGroupWidgetOptions = object({
|
|
48848
|
-
title: string,
|
|
48998
|
+
title: defaulted(string, () => ""),
|
|
48849
48999
|
hasHint: optional(nullable(boolean)),
|
|
48850
49000
|
// This module has an import cycle with parsePerseusRenderer.
|
|
48851
49001
|
// The anonymous function below ensures that we don't try to access
|
|
@@ -48939,7 +49089,7 @@ const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
|
48939
49089
|
height: union(number).or(string).parser,
|
|
48940
49090
|
allowFullScreen: boolean,
|
|
48941
49091
|
allowTopNavigation: optional(boolean),
|
|
48942
|
-
static: boolean
|
|
49092
|
+
static: defaulted(boolean, () => false)
|
|
48943
49093
|
}));
|
|
48944
49094
|
|
|
48945
49095
|
const parsePerseusImageBackground = object({
|
|
@@ -49318,13 +49468,13 @@ const parseMatcherWidget = parseWidget(constant("matcher"), object({
|
|
|
49318
49468
|
padding: boolean
|
|
49319
49469
|
}));
|
|
49320
49470
|
|
|
49321
|
-
const parseMatrixWidget = parseWidget(constant("matrix"), object({
|
|
49322
|
-
prefix: string,
|
|
49323
|
-
suffix: string,
|
|
49471
|
+
const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
|
|
49472
|
+
prefix: optional(string),
|
|
49473
|
+
suffix: optional(string),
|
|
49324
49474
|
answers: array(array(number)),
|
|
49325
|
-
cursorPosition: array(number),
|
|
49475
|
+
cursorPosition: optional(array(number)),
|
|
49326
49476
|
matrixBoardSize: array(number),
|
|
49327
|
-
static: boolean
|
|
49477
|
+
static: optional(boolean)
|
|
49328
49478
|
}));
|
|
49329
49479
|
|
|
49330
49480
|
const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
@@ -49394,8 +49544,8 @@ const parseOrdererWidget = parseWidget(constant("orderer"), object({
|
|
|
49394
49544
|
options: array(parseRenderer),
|
|
49395
49545
|
correctOptions: array(parseRenderer),
|
|
49396
49546
|
otherOptions: array(parseRenderer),
|
|
49397
|
-
height:
|
|
49398
|
-
layout:
|
|
49547
|
+
height: enumeration("normal", "auto"),
|
|
49548
|
+
layout: enumeration("horizontal", "vertical")
|
|
49399
49549
|
}));
|
|
49400
49550
|
|
|
49401
49551
|
const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
|
|
@@ -49440,7 +49590,7 @@ const parsePythonProgramWidget = parseWidget(constant("python-program"), object(
|
|
|
49440
49590
|
|
|
49441
49591
|
const parseRadioWidget = parseWidget(constant("radio"), object({
|
|
49442
49592
|
choices: array(object({
|
|
49443
|
-
content: string,
|
|
49593
|
+
content: defaulted(string, () => ""),
|
|
49444
49594
|
clue: optional(string),
|
|
49445
49595
|
correct: optional(boolean),
|
|
49446
49596
|
isNoneOfTheAbove: optional(boolean),
|
|
@@ -49602,26 +49752,45 @@ const parseWidgetMapKeyComponents = pair(string, parseStringToPositiveInt);
|
|
|
49602
49752
|
const parseHint = object({
|
|
49603
49753
|
replace: optional(boolean),
|
|
49604
49754
|
content: string,
|
|
49605
|
-
widgets: parseWidgetsMap,
|
|
49755
|
+
widgets: defaulted(parseWidgetsMap, () => ({})),
|
|
49606
49756
|
metadata: optional(array(string)),
|
|
49607
|
-
images:
|
|
49608
|
-
width: number,
|
|
49609
|
-
height: number
|
|
49610
|
-
}))
|
|
49757
|
+
images: parseImages
|
|
49611
49758
|
});
|
|
49612
49759
|
|
|
49613
49760
|
object({
|
|
49614
49761
|
question: parsePerseusRenderer,
|
|
49615
|
-
hints: array(parseHint),
|
|
49616
|
-
answerArea: record(enumeration(...ItemExtras), boolean),
|
|
49617
|
-
itemDataVersion: object({
|
|
49762
|
+
hints: defaulted(array(parseHint), () => []),
|
|
49763
|
+
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
|
|
49764
|
+
itemDataVersion: optional(object({
|
|
49618
49765
|
major: number,
|
|
49619
49766
|
minor: number
|
|
49620
|
-
}),
|
|
49767
|
+
})),
|
|
49621
49768
|
// Deprecated field
|
|
49622
49769
|
answer: any
|
|
49623
49770
|
});
|
|
49624
49771
|
|
|
49772
|
+
// Some answerAreas have extra fields, like:
|
|
49773
|
+
//
|
|
49774
|
+
// "answerArea": {
|
|
49775
|
+
// "type": "multiple",
|
|
49776
|
+
// "options": {
|
|
49777
|
+
// "content": "",
|
|
49778
|
+
// "images": {},
|
|
49779
|
+
// "widgets": {}
|
|
49780
|
+
// }
|
|
49781
|
+
// }
|
|
49782
|
+
//
|
|
49783
|
+
// The "type" and "options" fields don't seem to be used anywhere. This
|
|
49784
|
+
// migration function removes them.
|
|
49785
|
+
function migrateAnswerArea(rawValue, ctx) {
|
|
49786
|
+
const {
|
|
49787
|
+
type: _,
|
|
49788
|
+
options: __,
|
|
49789
|
+
...rest
|
|
49790
|
+
} = rawValue;
|
|
49791
|
+
return ctx.success(rest);
|
|
49792
|
+
}
|
|
49793
|
+
|
|
49625
49794
|
/**
|
|
49626
49795
|
* Helper to parse PerseusItem JSON
|
|
49627
49796
|
* Why not just use JSON.parse? We want:
|