@khanacademy/perseus 45.0.0 → 46.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/text-input.d.ts +1 -0
- package/dist/es/index.css +169 -12
- package/dist/es/index.js +413 -290
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +169 -12
- package/dist/index.d.ts +5 -1
- package/dist/index.js +391 -264
- 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.d.ts +7 -2
- package/dist/types.d.ts +58 -17
- package/dist/util/parse-perseus-json/general-purpose-parsers/string-to-number.d.ts +2 -0
- package/dist/util/scoring.d.ts +20 -0
- package/dist/util.d.ts +0 -30
- package/dist/validation.types.d.ts +8 -3
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +1 -1
- package/dist/widgets/group/group.d.ts +2 -0
- package/dist/widgets/group/score-group.d.ts +5 -0
- package/dist/widgets/input-number/input-number.d.ts +1 -1
- 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 +7 -2
- package/package.json +1 -1
package/dist/es/index.js
CHANGED
|
@@ -2793,12 +2793,6 @@ const rWidgetRule = /^\[\[\u2603 (([a-z-]+) ([0-9]+))\]\]/;
|
|
|
2793
2793
|
const rTypeFromWidgetId = /^([a-z-]+) ([0-9]+)$/;
|
|
2794
2794
|
const rWidgetParts = new RegExp(rWidgetRule.source + "$");
|
|
2795
2795
|
const snowman = "\u2603";
|
|
2796
|
-
const noScore = {
|
|
2797
|
-
type: "points",
|
|
2798
|
-
earned: 0,
|
|
2799
|
-
total: 0,
|
|
2800
|
-
message: null
|
|
2801
|
-
};
|
|
2802
2796
|
const seededRNG$1 = function seededRNG(seed) {
|
|
2803
2797
|
let randomSeed = seed;
|
|
2804
2798
|
return function () {
|
|
@@ -2875,97 +2869,6 @@ const split = "x".split(/(.)/g).length ? function (str, r) {
|
|
|
2875
2869
|
return output;
|
|
2876
2870
|
};
|
|
2877
2871
|
|
|
2878
|
-
/**
|
|
2879
|
-
* Combine two score objects.
|
|
2880
|
-
*
|
|
2881
|
-
* Given two score objects for two different widgets, combine them so that
|
|
2882
|
-
* if one is wrong, the total score is wrong, etc.
|
|
2883
|
-
*/
|
|
2884
|
-
function combineScores(scoreA, scoreB) {
|
|
2885
|
-
let message;
|
|
2886
|
-
if (scoreA.type === "points" && scoreB.type === "points") {
|
|
2887
|
-
if (scoreA.message && scoreB.message && scoreA.message !== scoreB.message) {
|
|
2888
|
-
// TODO(alpert): Figure out how to combine messages usefully
|
|
2889
|
-
message = null;
|
|
2890
|
-
} else {
|
|
2891
|
-
message = scoreA.message || scoreB.message;
|
|
2892
|
-
}
|
|
2893
|
-
return {
|
|
2894
|
-
type: "points",
|
|
2895
|
-
earned: scoreA.earned + scoreB.earned,
|
|
2896
|
-
total: scoreA.total + scoreB.total,
|
|
2897
|
-
message: message
|
|
2898
|
-
};
|
|
2899
|
-
}
|
|
2900
|
-
if (scoreA.type === "points" && scoreB.type === "invalid") {
|
|
2901
|
-
return scoreB;
|
|
2902
|
-
}
|
|
2903
|
-
if (scoreA.type === "invalid" && scoreB.type === "points") {
|
|
2904
|
-
return scoreA;
|
|
2905
|
-
}
|
|
2906
|
-
if (scoreA.type === "invalid" && scoreB.type === "invalid") {
|
|
2907
|
-
if (scoreA.message && scoreB.message && scoreA.message !== scoreB.message) {
|
|
2908
|
-
// TODO(alpert): Figure out how to combine messages usefully
|
|
2909
|
-
message = null;
|
|
2910
|
-
} else {
|
|
2911
|
-
message = scoreA.message || scoreB.message;
|
|
2912
|
-
}
|
|
2913
|
-
return {
|
|
2914
|
-
type: "invalid",
|
|
2915
|
-
message: message
|
|
2916
|
-
};
|
|
2917
|
-
}
|
|
2918
|
-
|
|
2919
|
-
/**
|
|
2920
|
-
* The above checks cover all combinations of score type, so if we get here
|
|
2921
|
-
* then something is amiss with our inputs.
|
|
2922
|
-
*/
|
|
2923
|
-
throw new PerseusError("PerseusScore with unknown type encountered", Errors.InvalidInput, {
|
|
2924
|
-
metadata: {
|
|
2925
|
-
scoreA: JSON.stringify(scoreA),
|
|
2926
|
-
scoreB: JSON.stringify(scoreB)
|
|
2927
|
-
}
|
|
2928
|
-
});
|
|
2929
|
-
}
|
|
2930
|
-
function flattenScores(widgetScoreMap) {
|
|
2931
|
-
return Object.values(widgetScoreMap).reduce(combineScores, noScore);
|
|
2932
|
-
}
|
|
2933
|
-
function isCorrect(score) {
|
|
2934
|
-
return score.type === "points" && score.earned >= score.total;
|
|
2935
|
-
}
|
|
2936
|
-
function keScoreFromPerseusScore(score,
|
|
2937
|
-
// It's weird, but this is what we're passing it
|
|
2938
|
-
guess, state) {
|
|
2939
|
-
if (score.type === "points") {
|
|
2940
|
-
return {
|
|
2941
|
-
empty: false,
|
|
2942
|
-
correct: isCorrect(score),
|
|
2943
|
-
message: score.message,
|
|
2944
|
-
guess: guess,
|
|
2945
|
-
state: state
|
|
2946
|
-
};
|
|
2947
|
-
}
|
|
2948
|
-
if (score.type === "invalid") {
|
|
2949
|
-
return {
|
|
2950
|
-
empty: true,
|
|
2951
|
-
correct: false,
|
|
2952
|
-
message: score.message,
|
|
2953
|
-
suppressAlmostThere: score.suppressAlmostThere,
|
|
2954
|
-
guess: guess,
|
|
2955
|
-
state: state
|
|
2956
|
-
};
|
|
2957
|
-
}
|
|
2958
|
-
throw new PerseusError(
|
|
2959
|
-
// @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
|
|
2960
|
-
"Invalid score type: " + score.type, Errors.InvalidInput, {
|
|
2961
|
-
metadata: {
|
|
2962
|
-
score: JSON.stringify(score),
|
|
2963
|
-
guess: JSON.stringify(guess),
|
|
2964
|
-
state: JSON.stringify(state)
|
|
2965
|
-
}
|
|
2966
|
-
});
|
|
2967
|
-
}
|
|
2968
|
-
|
|
2969
2872
|
/**
|
|
2970
2873
|
* Return the first valid interpretation of 'text' as a number, in the form
|
|
2971
2874
|
* {value: 2.3, exact: true}.
|
|
@@ -3263,28 +3166,6 @@ function strongEncodeURIComponent(str) {
|
|
|
3263
3166
|
.replace(/\*/g, "%2A");
|
|
3264
3167
|
}
|
|
3265
3168
|
|
|
3266
|
-
/**
|
|
3267
|
-
* If a widget says that it is empty once it is graded.
|
|
3268
|
-
* Trying to encapsulate references to the score format.
|
|
3269
|
-
*/
|
|
3270
|
-
function scoreIsEmpty(score) {
|
|
3271
|
-
// HACK(benkomalo): ugh. this isn't great; the Perseus score objects
|
|
3272
|
-
// overload the type "invalid" for what should probably be three
|
|
3273
|
-
// distinct cases:
|
|
3274
|
-
// - truly empty or not fully filled out
|
|
3275
|
-
// - invalid or malformed inputs
|
|
3276
|
-
// - "almost correct" like inputs where the widget wants to give
|
|
3277
|
-
// feedback (e.g. a fraction needs to be reduced, or `pi` should
|
|
3278
|
-
// be used instead of 3.14)
|
|
3279
|
-
//
|
|
3280
|
-
// Unfortunately the coercion happens all over the place, as these
|
|
3281
|
-
// Perseus style score objects are created *everywhere* (basically
|
|
3282
|
-
// in every widget), so it's hard to change now. We assume that
|
|
3283
|
-
// anything with a "message" is not truly empty, and one of the
|
|
3284
|
-
// latter two cases for now.
|
|
3285
|
-
return score.type === "invalid" && (!score.message || score.message.length === 0);
|
|
3286
|
-
}
|
|
3287
|
-
|
|
3288
3169
|
/*
|
|
3289
3170
|
* The touchHandlers are used to track the current state of the touch
|
|
3290
3171
|
* event, such as whether or not the user is currently pressed down (either
|
|
@@ -3459,13 +3340,9 @@ const Util = {
|
|
|
3459
3340
|
rTypeFromWidgetId,
|
|
3460
3341
|
rWidgetParts,
|
|
3461
3342
|
snowman,
|
|
3462
|
-
noScore,
|
|
3463
3343
|
seededRNG: seededRNG$1,
|
|
3464
3344
|
shuffle: shuffle$3,
|
|
3465
3345
|
split,
|
|
3466
|
-
combineScores,
|
|
3467
|
-
flattenScores,
|
|
3468
|
-
keScoreFromPerseusScore,
|
|
3469
3346
|
firstNumericalParse: firstNumericalParse$1,
|
|
3470
3347
|
stringArrayOfSize: stringArrayOfSize$1,
|
|
3471
3348
|
gridDimensionConfig,
|
|
@@ -3481,7 +3358,6 @@ const Util = {
|
|
|
3481
3358
|
parseQueryString,
|
|
3482
3359
|
updateQueryString: updateQueryString$2,
|
|
3483
3360
|
strongEncodeURIComponent,
|
|
3484
|
-
scoreIsEmpty,
|
|
3485
3361
|
touchHandlers,
|
|
3486
3362
|
resetTouchHandlers,
|
|
3487
3363
|
extractPointerLocation,
|
|
@@ -3797,6 +3673,7 @@ Zoom.prototype.zoomImage = function () {
|
|
|
3797
3673
|
this._zoomOriginal();
|
|
3798
3674
|
}.bind(this);
|
|
3799
3675
|
img.src = this._targetImage.src;
|
|
3676
|
+
img.alt = this._targetImage.alt;
|
|
3800
3677
|
this.$zoomedImage = $zoomedImage;
|
|
3801
3678
|
};
|
|
3802
3679
|
Zoom.prototype._zoomOriginal = function () {
|
|
@@ -8872,7 +8749,10 @@ var Tex$1 = {
|
|
|
8872
8749
|
// We use createElement instead of JSX here because we can't name this file tex.tsx;
|
|
8873
8750
|
// that name is already taken.
|
|
8874
8751
|
// eslint-disable-next-line import/no-deprecated
|
|
8875
|
-
render(
|
|
8752
|
+
render(
|
|
8753
|
+
/*#__PURE__*/
|
|
8754
|
+
// eslint-disable-next-line react/no-children-prop
|
|
8755
|
+
React.createElement(TeX, {
|
|
8876
8756
|
children: text,
|
|
8877
8757
|
onRender: callback
|
|
8878
8758
|
}), $texHolder[0]);
|
|
@@ -14609,7 +14489,7 @@ const pluck = function pluck(table, subKey) {
|
|
|
14609
14489
|
* });
|
|
14610
14490
|
* {a: 2, b: 3}
|
|
14611
14491
|
*/
|
|
14612
|
-
const mapObject
|
|
14492
|
+
const mapObject = function mapObject(obj, lambda) {
|
|
14613
14493
|
const result = {};
|
|
14614
14494
|
Object.keys(obj).forEach(key => {
|
|
14615
14495
|
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
|
|
@@ -14618,27 +14498,6 @@ const mapObject$2 = function mapObject(obj, lambda) {
|
|
|
14618
14498
|
return result;
|
|
14619
14499
|
};
|
|
14620
14500
|
|
|
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
|
-
arr.forEach(elem => {
|
|
14632
|
-
result[elem] = lambda(elem);
|
|
14633
|
-
});
|
|
14634
|
-
return result;
|
|
14635
|
-
};
|
|
14636
|
-
var objective_ = {
|
|
14637
|
-
pluck: pluck,
|
|
14638
|
-
mapObject: mapObject$2,
|
|
14639
|
-
mapObjectFromArray: mapObjectFromArray
|
|
14640
|
-
};
|
|
14641
|
-
|
|
14642
14501
|
/**
|
|
14643
14502
|
* MovableLine
|
|
14644
14503
|
*/
|
|
@@ -14703,7 +14562,7 @@ _.extend(MovableLine$4.prototype, {
|
|
|
14703
14562
|
// - are objects, not primitives (and need a deeper copy)
|
|
14704
14563
|
// - they don't need getters created for them
|
|
14705
14564
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
14706
|
-
|
|
14565
|
+
pluck(MovableLineOptions, "standard")), DEFAULT_PROPS$2);
|
|
14707
14566
|
},
|
|
14708
14567
|
/**
|
|
14709
14568
|
* Resets the object to its state as if it were constructed with
|
|
@@ -15203,7 +15062,7 @@ class MovablePoint$5 {
|
|
|
15203
15062
|
// - are objects, not primitives (and need a deeper copy)
|
|
15204
15063
|
// - they don't need getters created for them
|
|
15205
15064
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
15206
|
-
|
|
15065
|
+
pluck(MovablePointOptions, "standard")
|
|
15207
15066
|
// We only update props here, because we want things on state to
|
|
15208
15067
|
// be persistent, and updated appropriately in modify()
|
|
15209
15068
|
), DEFAULT_PROPS$1);
|
|
@@ -16094,7 +15953,7 @@ _.extend(MovablePolygon.prototype, {
|
|
|
16094
15953
|
// - are objects, not primitives (and need a deeper copy)
|
|
16095
15954
|
// - they don't need getters created for them
|
|
16096
15955
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
16097
|
-
|
|
15956
|
+
pluck(MovablePolygonOptions, "standard")
|
|
16098
15957
|
|
|
16099
15958
|
// We only update props here, because we want things on state to
|
|
16100
15959
|
// be persistent, and updated appropriately in modify()
|
|
@@ -18719,6 +18578,126 @@ class QuestionParagraph extends React.Component {
|
|
|
18719
18578
|
}
|
|
18720
18579
|
}
|
|
18721
18580
|
|
|
18581
|
+
const noScore = {
|
|
18582
|
+
type: "points",
|
|
18583
|
+
earned: 0,
|
|
18584
|
+
total: 0,
|
|
18585
|
+
message: null
|
|
18586
|
+
};
|
|
18587
|
+
|
|
18588
|
+
/**
|
|
18589
|
+
* If a widget says that it is empty once it is graded.
|
|
18590
|
+
* Trying to encapsulate references to the score format.
|
|
18591
|
+
*/
|
|
18592
|
+
function scoreIsEmpty(score) {
|
|
18593
|
+
// HACK(benkomalo): ugh. this isn't great; the Perseus score objects
|
|
18594
|
+
// overload the type "invalid" for what should probably be three
|
|
18595
|
+
// distinct cases:
|
|
18596
|
+
// - truly empty or not fully filled out
|
|
18597
|
+
// - invalid or malformed inputs
|
|
18598
|
+
// - "almost correct" like inputs where the widget wants to give
|
|
18599
|
+
// feedback (e.g. a fraction needs to be reduced, or `pi` should
|
|
18600
|
+
// be used instead of 3.14)
|
|
18601
|
+
//
|
|
18602
|
+
// Unfortunately the coercion happens all over the place, as these
|
|
18603
|
+
// Perseus style score objects are created *everywhere* (basically
|
|
18604
|
+
// in every widget), so it's hard to change now. We assume that
|
|
18605
|
+
// anything with a "message" is not truly empty, and one of the
|
|
18606
|
+
// latter two cases for now.
|
|
18607
|
+
return score.type === "invalid" && (!score.message || score.message.length === 0);
|
|
18608
|
+
}
|
|
18609
|
+
|
|
18610
|
+
/**
|
|
18611
|
+
* Combine two score objects.
|
|
18612
|
+
*
|
|
18613
|
+
* Given two score objects for two different widgets, combine them so that
|
|
18614
|
+
* if one is wrong, the total score is wrong, etc.
|
|
18615
|
+
*/
|
|
18616
|
+
function combineScores(scoreA, scoreB) {
|
|
18617
|
+
let message;
|
|
18618
|
+
if (scoreA.type === "points" && scoreB.type === "points") {
|
|
18619
|
+
if (scoreA.message && scoreB.message && scoreA.message !== scoreB.message) {
|
|
18620
|
+
// TODO(alpert): Figure out how to combine messages usefully
|
|
18621
|
+
message = null;
|
|
18622
|
+
} else {
|
|
18623
|
+
message = scoreA.message || scoreB.message;
|
|
18624
|
+
}
|
|
18625
|
+
return {
|
|
18626
|
+
type: "points",
|
|
18627
|
+
earned: scoreA.earned + scoreB.earned,
|
|
18628
|
+
total: scoreA.total + scoreB.total,
|
|
18629
|
+
message: message
|
|
18630
|
+
};
|
|
18631
|
+
}
|
|
18632
|
+
if (scoreA.type === "points" && scoreB.type === "invalid") {
|
|
18633
|
+
return scoreB;
|
|
18634
|
+
}
|
|
18635
|
+
if (scoreA.type === "invalid" && scoreB.type === "points") {
|
|
18636
|
+
return scoreA;
|
|
18637
|
+
}
|
|
18638
|
+
if (scoreA.type === "invalid" && scoreB.type === "invalid") {
|
|
18639
|
+
if (scoreA.message && scoreB.message && scoreA.message !== scoreB.message) {
|
|
18640
|
+
// TODO(alpert): Figure out how to combine messages usefully
|
|
18641
|
+
message = null;
|
|
18642
|
+
} else {
|
|
18643
|
+
message = scoreA.message || scoreB.message;
|
|
18644
|
+
}
|
|
18645
|
+
return {
|
|
18646
|
+
type: "invalid",
|
|
18647
|
+
message: message
|
|
18648
|
+
};
|
|
18649
|
+
}
|
|
18650
|
+
|
|
18651
|
+
/**
|
|
18652
|
+
* The above checks cover all combinations of score type, so if we get here
|
|
18653
|
+
* then something is amiss with our inputs.
|
|
18654
|
+
*/
|
|
18655
|
+
throw new PerseusError("PerseusScore with unknown type encountered", Errors.InvalidInput, {
|
|
18656
|
+
metadata: {
|
|
18657
|
+
scoreA: JSON.stringify(scoreA),
|
|
18658
|
+
scoreB: JSON.stringify(scoreB)
|
|
18659
|
+
}
|
|
18660
|
+
});
|
|
18661
|
+
}
|
|
18662
|
+
function flattenScores(widgetScoreMap) {
|
|
18663
|
+
return Object.values(widgetScoreMap).reduce(combineScores, noScore);
|
|
18664
|
+
}
|
|
18665
|
+
function isCorrect(score) {
|
|
18666
|
+
return score.type === "points" && score.earned >= score.total;
|
|
18667
|
+
}
|
|
18668
|
+
function keScoreFromPerseusScore(score,
|
|
18669
|
+
// It's weird, but this is what we're passing it
|
|
18670
|
+
guess, state) {
|
|
18671
|
+
if (score.type === "points") {
|
|
18672
|
+
return {
|
|
18673
|
+
empty: false,
|
|
18674
|
+
correct: isCorrect(score),
|
|
18675
|
+
message: score.message,
|
|
18676
|
+
guess: guess,
|
|
18677
|
+
state: state
|
|
18678
|
+
};
|
|
18679
|
+
}
|
|
18680
|
+
if (score.type === "invalid") {
|
|
18681
|
+
return {
|
|
18682
|
+
empty: true,
|
|
18683
|
+
correct: false,
|
|
18684
|
+
message: score.message,
|
|
18685
|
+
suppressAlmostThere: score.suppressAlmostThere,
|
|
18686
|
+
guess: guess,
|
|
18687
|
+
state: state
|
|
18688
|
+
};
|
|
18689
|
+
}
|
|
18690
|
+
throw new PerseusError(
|
|
18691
|
+
// @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
|
|
18692
|
+
"Invalid score type: " + score.type, Errors.InvalidInput, {
|
|
18693
|
+
metadata: {
|
|
18694
|
+
score: JSON.stringify(score),
|
|
18695
|
+
guess: JSON.stringify(guess),
|
|
18696
|
+
state: JSON.stringify(state)
|
|
18697
|
+
}
|
|
18698
|
+
});
|
|
18699
|
+
}
|
|
18700
|
+
|
|
18722
18701
|
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
18723
18702
|
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
18724
18703
|
|
|
@@ -19284,12 +19263,9 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19284
19263
|
isLintable: isLintable
|
|
19285
19264
|
});
|
|
19286
19265
|
|
|
19287
|
-
const {
|
|
19288
|
-
mapObject: mapObject$1
|
|
19289
|
-
} = objective_;
|
|
19290
19266
|
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
19291
19267
|
// @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; }'.
|
|
19292
|
-
return mapObject
|
|
19268
|
+
return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
19293
19269
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19294
19270
|
const newValues = {};
|
|
19295
19271
|
if (!widgetInfo.type) {
|
|
@@ -19317,17 +19293,10 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19317
19293
|
// Static widgets shouldn't count as empty
|
|
19318
19294
|
return false;
|
|
19319
19295
|
}
|
|
19320
|
-
let score = null;
|
|
19321
|
-
const userInput = userInputMap[id];
|
|
19322
19296
|
const scorer = getWidgetScorer(widget.type);
|
|
19323
|
-
|
|
19324
|
-
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19325
|
-
score = Util.flattenScores(scores);
|
|
19326
|
-
} else if (scorer) {
|
|
19327
|
-
score = scorer(userInput, widget.options, strings, locale);
|
|
19328
|
-
}
|
|
19297
|
+
const score = scorer == null ? void 0 : scorer(userInputMap[id], widget.options, strings, locale);
|
|
19329
19298
|
if (score) {
|
|
19330
|
-
return
|
|
19299
|
+
return scoreIsEmpty(score);
|
|
19331
19300
|
}
|
|
19332
19301
|
});
|
|
19333
19302
|
}
|
|
@@ -19343,7 +19312,7 @@ strings, locale) {
|
|
|
19343
19312
|
// so this checks that the widgets are being used before scoring them
|
|
19344
19313
|
const usedWidgetIds = getWidgetIdsFromContent(perseusRenderData.content);
|
|
19345
19314
|
const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
|
|
19346
|
-
return
|
|
19315
|
+
return flattenScores(scores);
|
|
19347
19316
|
}
|
|
19348
19317
|
function scoreWidgetsFunctional(widgets,
|
|
19349
19318
|
// This is a port of old code, I'm not sure why
|
|
@@ -19365,11 +19334,9 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19365
19334
|
}
|
|
19366
19335
|
const userInput = userInputMap[id];
|
|
19367
19336
|
const scorer = getWidgetScorer(widget.type);
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
widgetScores[id] =
|
|
19371
|
-
} else if (scorer) {
|
|
19372
|
-
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
19337
|
+
const score = scorer == null ? void 0 : scorer(userInput, widget.options, strings, locale);
|
|
19338
|
+
if (score != null) {
|
|
19339
|
+
widgetScores[id] = score;
|
|
19373
19340
|
}
|
|
19374
19341
|
});
|
|
19375
19342
|
return widgetScores;
|
|
@@ -19675,9 +19642,6 @@ WidgetContainer.defaultProps = {
|
|
|
19675
19642
|
linterContext: linterContextDefault
|
|
19676
19643
|
};
|
|
19677
19644
|
|
|
19678
|
-
const {
|
|
19679
|
-
mapObject
|
|
19680
|
-
} = objective_;
|
|
19681
19645
|
const rContainsNonWhitespace = /\S/;
|
|
19682
19646
|
const rImageURL = /(web\+graphie|https):\/\/[^\s]*/;
|
|
19683
19647
|
const noopOnRender = () => {};
|
|
@@ -20603,7 +20567,7 @@ class Renderer extends React.Component {
|
|
|
20603
20567
|
}, 0);
|
|
20604
20568
|
});
|
|
20605
20569
|
};
|
|
20606
|
-
this.setInputValue = (path, newValue,
|
|
20570
|
+
this.setInputValue = (path, newValue, cb) => {
|
|
20607
20571
|
// @ts-expect-error - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
|
|
20608
20572
|
const widgetId = _.first(path);
|
|
20609
20573
|
// @ts-expect-error - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
|
|
@@ -20611,7 +20575,7 @@ class Renderer extends React.Component {
|
|
|
20611
20575
|
const widget = this.getWidgetInstance(widgetId);
|
|
20612
20576
|
|
|
20613
20577
|
// Widget handles parsing of the interWidgetPath.
|
|
20614
|
-
widget == null || widget.setInputValue == null || widget.setInputValue(interWidgetPath, newValue,
|
|
20578
|
+
widget == null || widget.setInputValue == null || widget.setInputValue(interWidgetPath, newValue, cb);
|
|
20615
20579
|
};
|
|
20616
20580
|
/**
|
|
20617
20581
|
* Returns an array of all widget IDs in the order they occur in
|
|
@@ -20628,28 +20592,6 @@ class Renderer extends React.Component {
|
|
|
20628
20592
|
const totalScore = this.score();
|
|
20629
20593
|
return [totalGuess, totalScore];
|
|
20630
20594
|
};
|
|
20631
|
-
this.examples = () => {
|
|
20632
|
-
const widgetIds = this.widgetIds;
|
|
20633
|
-
const examples = widgetIds.map(widgetId => {
|
|
20634
|
-
const widget = this.getWidgetInstance(widgetId);
|
|
20635
|
-
return widget != null && widget.examples ? widget.examples() : null;
|
|
20636
|
-
}).filter(Boolean);
|
|
20637
|
-
|
|
20638
|
-
// no widgets with examples
|
|
20639
|
-
if (!examples.length) {
|
|
20640
|
-
return null;
|
|
20641
|
-
}
|
|
20642
|
-
const allEqual = _.all(examples, function (example) {
|
|
20643
|
-
return _.isEqual(examples[0], example);
|
|
20644
|
-
});
|
|
20645
|
-
|
|
20646
|
-
// some widgets have different examples
|
|
20647
|
-
// TODO(alex): handle this better
|
|
20648
|
-
if (!allEqual) {
|
|
20649
|
-
return null;
|
|
20650
|
-
}
|
|
20651
|
-
return examples[0];
|
|
20652
|
-
};
|
|
20653
20595
|
// TranslationLinter callback
|
|
20654
20596
|
this.handletranslationLintErrors = lintErrors => {
|
|
20655
20597
|
if (!this._isMounted) {
|
|
@@ -20832,6 +20774,12 @@ class Renderer extends React.Component {
|
|
|
20832
20774
|
isLastUsedWidget: widgetId === this.state.lastUsedWidgetId
|
|
20833
20775
|
});
|
|
20834
20776
|
}
|
|
20777
|
+
/**
|
|
20778
|
+
* Returns an array of widget ids that are empty (meaning widgets where the
|
|
20779
|
+
* learner has not interacted with the widget yet or has not filled in all
|
|
20780
|
+
* fields). For example, the `interactive-graph` widget is considered
|
|
20781
|
+
* empty if the graph is in the starting state.
|
|
20782
|
+
*/
|
|
20835
20783
|
emptyWidgets() {
|
|
20836
20784
|
return emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20837
20785
|
}
|
|
@@ -20894,7 +20842,7 @@ class Renderer extends React.Component {
|
|
|
20894
20842
|
*/
|
|
20895
20843
|
score() {
|
|
20896
20844
|
const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20897
|
-
const combinedScore =
|
|
20845
|
+
const combinedScore = flattenScores(scores);
|
|
20898
20846
|
return combinedScore;
|
|
20899
20847
|
}
|
|
20900
20848
|
render() {
|
|
@@ -21096,6 +21044,7 @@ class TextInput extends React.Component {
|
|
|
21096
21044
|
value: formattedValue,
|
|
21097
21045
|
type: "text",
|
|
21098
21046
|
"aria-label": labelText,
|
|
21047
|
+
"aria-describedby": this.props["aria-describedby"],
|
|
21099
21048
|
onChange: value => this.props.onChange(value),
|
|
21100
21049
|
placeholder: placeholder,
|
|
21101
21050
|
onFocus: onFocus,
|
|
@@ -21362,6 +21311,7 @@ class Tooltip extends React.Component {
|
|
|
21362
21311
|
}, /*#__PURE__*/React.createElement("div", {
|
|
21363
21312
|
ref: this.tooltipContainerRef,
|
|
21364
21313
|
className: "tooltipContainer",
|
|
21314
|
+
role: "tooltip",
|
|
21365
21315
|
style: {
|
|
21366
21316
|
position: "absolute",
|
|
21367
21317
|
// height must start out undefined, not null, so that
|
|
@@ -21442,9 +21392,15 @@ class InputWithExamples extends React.Component {
|
|
|
21442
21392
|
};
|
|
21443
21393
|
this._renderInput = () => {
|
|
21444
21394
|
const id = this._getUniqueId();
|
|
21395
|
+
const ariaId = `aria-for-${id}`;
|
|
21396
|
+
// Generate text from a known set of format options that will read well in a screen reader
|
|
21397
|
+
const examplesAria = this.props.examples.length === 7 ? "" : `${this.props.examples[0]}
|
|
21398
|
+
${this.props.examples.slice(1).join(", or\n")}`
|
|
21399
|
+
// @ts-expect-error TS2550: Property replaceAll does not exist on type string.
|
|
21400
|
+
.replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ");
|
|
21445
21401
|
const inputProps = {
|
|
21446
21402
|
id: id,
|
|
21447
|
-
"aria-describedby":
|
|
21403
|
+
"aria-describedby": ariaId,
|
|
21448
21404
|
ref: "input",
|
|
21449
21405
|
className: this._getInputClassName(),
|
|
21450
21406
|
labelText: this.props.labelText,
|
|
@@ -21460,7 +21416,12 @@ class InputWithExamples extends React.Component {
|
|
|
21460
21416
|
autoCorrect: "off",
|
|
21461
21417
|
spellCheck: "false"
|
|
21462
21418
|
};
|
|
21463
|
-
return /*#__PURE__*/React.createElement(TextInput, inputProps)
|
|
21419
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextInput, inputProps), /*#__PURE__*/React.createElement("span", {
|
|
21420
|
+
id: ariaId,
|
|
21421
|
+
style: {
|
|
21422
|
+
display: "none"
|
|
21423
|
+
}
|
|
21424
|
+
}, examplesAria));
|
|
21464
21425
|
};
|
|
21465
21426
|
this._handleFocus = () => {
|
|
21466
21427
|
this.props.onFocus();
|
|
@@ -21502,8 +21463,12 @@ class InputWithExamples extends React.Component {
|
|
|
21502
21463
|
}
|
|
21503
21464
|
render() {
|
|
21504
21465
|
const input = this._renderInput();
|
|
21505
|
-
const examplesContent =
|
|
21506
|
-
|
|
21466
|
+
const examplesContent = this.props.examples.length <= 2 ? this.props.examples.join(" ") // A single item (with or without leading text) is not a "list"
|
|
21467
|
+
: this.props.examples // 2 or more items should display as a list
|
|
21468
|
+
.map((example, index) => {
|
|
21469
|
+
// If the first example is bold, then it is most likely a heading/leading text.
|
|
21470
|
+
// So, it shouldn't be part of the list.
|
|
21471
|
+
return index === 0 && example.startsWith("**") ? `${example}\n` : `- ${example}`;
|
|
21507
21472
|
}).join("\n");
|
|
21508
21473
|
const showExamples = this.props.shouldShowExamples && this.state.showExamples;
|
|
21509
21474
|
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
@@ -21938,15 +21903,6 @@ class InputNumber extends React.Component {
|
|
|
21938
21903
|
currentValue: newValue
|
|
21939
21904
|
}, cb);
|
|
21940
21905
|
};
|
|
21941
|
-
this.examples = () => {
|
|
21942
|
-
const {
|
|
21943
|
-
strings
|
|
21944
|
-
} = this.context;
|
|
21945
|
-
const type = this.props.answerType;
|
|
21946
|
-
const forms = answerTypes[type].forms.split(/\s*,\s*/);
|
|
21947
|
-
const examples = _.map(forms, form => formExamples$1[form](this.props, strings));
|
|
21948
|
-
return [strings.yourAnswer].concat(examples);
|
|
21949
|
-
};
|
|
21950
21906
|
}
|
|
21951
21907
|
static getUserInputFromProps(props) {
|
|
21952
21908
|
return {
|
|
@@ -21959,6 +21915,15 @@ class InputNumber extends React.Component {
|
|
|
21959
21915
|
getPromptJSON() {
|
|
21960
21916
|
return getPromptJSON$s(this.props, this.getUserInput());
|
|
21961
21917
|
}
|
|
21918
|
+
examples() {
|
|
21919
|
+
const {
|
|
21920
|
+
strings
|
|
21921
|
+
} = this.context;
|
|
21922
|
+
const type = this.props.answerType;
|
|
21923
|
+
const forms = answerTypes[type].forms.split(/\s*,\s*/);
|
|
21924
|
+
const examples = _.map(forms, form => formExamples$1[form](this.props, strings));
|
|
21925
|
+
return [strings.yourAnswer].concat(examples);
|
|
21926
|
+
}
|
|
21962
21927
|
render() {
|
|
21963
21928
|
if (this.props.apiOptions.customKeypad) {
|
|
21964
21929
|
// TODO(charlie): Support "Review Mode".
|
|
@@ -22177,7 +22142,7 @@ function scoreNumericInput(userInput, rubric, strings) {
|
|
|
22177
22142
|
// We may have received TeX; try to parse it before grading.
|
|
22178
22143
|
// If `currentValue` is not TeX, this should be a no-op.
|
|
22179
22144
|
const currentValue = ParseTex(userInput.currentValue);
|
|
22180
|
-
const normalizedAnswerExpected = rubric.answers.filter(answer => answer.status === "correct").every(answer => Math.abs(answer.value) <= 1);
|
|
22145
|
+
const normalizedAnswerExpected = rubric.answers.filter(answer => answer.status === "correct").every(answer => answer.value !== undefined && Math.abs(answer.value) <= 1);
|
|
22181
22146
|
|
|
22182
22147
|
// The coefficient is an attribute of the widget
|
|
22183
22148
|
let localValue = currentValue;
|
|
@@ -22238,33 +22203,16 @@ class NumericInput extends React.Component {
|
|
|
22238
22203
|
this.state = {
|
|
22239
22204
|
// keeps track of the other set of values when switching
|
|
22240
22205
|
// between 0 and finite solutions
|
|
22241
|
-
previousValues: [""]
|
|
22242
|
-
|
|
22243
|
-
// TODO(Nicole, Jeremy): This is maybe never used and should be removed
|
|
22244
|
-
this.examples = () => {
|
|
22245
|
-
var _this$props$answerFor;
|
|
22246
|
-
// if the set of specified forms are empty, allow all forms
|
|
22247
|
-
const forms = ((_this$props$answerFor = this.props.answerForms) == null ? void 0 : _this$props$answerFor.length) !== 0 ? this.props.answerForms : Object.keys(formExamples).map(name => {
|
|
22248
|
-
return {
|
|
22249
|
-
name: name,
|
|
22250
|
-
simplify: "required"
|
|
22251
|
-
};
|
|
22252
|
-
});
|
|
22253
|
-
let examples = _.map(forms, form => {
|
|
22254
|
-
return formExamples[form.name](form, this.context.strings);
|
|
22255
|
-
});
|
|
22256
|
-
// Ensure no duplicate tooltip text from simplified and unsimplified
|
|
22257
|
-
// versions of the same format
|
|
22258
|
-
examples = _.uniq(examples);
|
|
22259
|
-
return [this.context.strings.yourAnswer].concat(examples);
|
|
22206
|
+
previousValues: [""],
|
|
22207
|
+
isFocused: false
|
|
22260
22208
|
};
|
|
22261
22209
|
this.shouldShowExamples = () => {
|
|
22262
|
-
var _this$props$
|
|
22263
|
-
const noFormsAccepted = ((_this$props$
|
|
22210
|
+
var _this$props$answerFor, _this$props$answerFor2;
|
|
22211
|
+
const noFormsAccepted = ((_this$props$answerFor = this.props.answerForms) == null ? void 0 : _this$props$answerFor.length) === 0;
|
|
22264
22212
|
// To check if all answer forms are accepted, we must first
|
|
22265
22213
|
// find the *names* of all accepted forms, and see if they are
|
|
22266
22214
|
// all present, ignoring duplicates
|
|
22267
|
-
const answerFormNames = _.uniq((_this$props$
|
|
22215
|
+
const answerFormNames = _.uniq((_this$props$answerFor2 = this.props.answerForms) == null ? void 0 : _this$props$answerFor2.map(form => form.name));
|
|
22268
22216
|
const allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
|
|
22269
22217
|
return !noFormsAccepted && !allFormsAccepted;
|
|
22270
22218
|
};
|
|
@@ -22301,9 +22249,19 @@ class NumericInput extends React.Component {
|
|
|
22301
22249
|
};
|
|
22302
22250
|
this._handleFocus = () => {
|
|
22303
22251
|
this.props.onFocus([]);
|
|
22252
|
+
this.setState(currentState => {
|
|
22253
|
+
return _extends({}, currentState, {
|
|
22254
|
+
isFocused: true
|
|
22255
|
+
});
|
|
22256
|
+
});
|
|
22304
22257
|
};
|
|
22305
22258
|
this._handleBlur = () => {
|
|
22306
22259
|
this.props.onBlur([]);
|
|
22260
|
+
this.setState(currentState => {
|
|
22261
|
+
return _extends({}, currentState, {
|
|
22262
|
+
isFocused: false
|
|
22263
|
+
});
|
|
22264
|
+
});
|
|
22307
22265
|
};
|
|
22308
22266
|
}
|
|
22309
22267
|
static getUserInputFromProps(props) {
|
|
@@ -22311,6 +22269,27 @@ class NumericInput extends React.Component {
|
|
|
22311
22269
|
currentValue: props.currentValue
|
|
22312
22270
|
};
|
|
22313
22271
|
}
|
|
22272
|
+
/**
|
|
22273
|
+
* Generates a string that demonstrates how to input the various supported
|
|
22274
|
+
* answer forms.
|
|
22275
|
+
*/
|
|
22276
|
+
examples() {
|
|
22277
|
+
var _this$props$answerFor3;
|
|
22278
|
+
// if the set of specified forms are empty, allow all forms
|
|
22279
|
+
const forms = ((_this$props$answerFor3 = this.props.answerForms) == null ? void 0 : _this$props$answerFor3.length) !== 0 ? this.props.answerForms : Object.keys(formExamples).map(name => {
|
|
22280
|
+
return {
|
|
22281
|
+
name: name,
|
|
22282
|
+
simplify: "required"
|
|
22283
|
+
};
|
|
22284
|
+
});
|
|
22285
|
+
let examples = _.map(forms, form => {
|
|
22286
|
+
return formExamples[form.name](form, this.context.strings);
|
|
22287
|
+
});
|
|
22288
|
+
// Ensure no duplicate tooltip text from simplified and unsimplified
|
|
22289
|
+
// versions of the same format
|
|
22290
|
+
examples = _.uniq(examples);
|
|
22291
|
+
return [this.context.strings.yourAnswer].concat(examples);
|
|
22292
|
+
}
|
|
22314
22293
|
getUserInput() {
|
|
22315
22294
|
return NumericInput.getUserInputFromProps(this.props);
|
|
22316
22295
|
}
|
|
@@ -22346,13 +22325,20 @@ class NumericInput extends React.Component {
|
|
|
22346
22325
|
// component.
|
|
22347
22326
|
const styles = StyleSheet.create({
|
|
22348
22327
|
input: {
|
|
22328
|
+
borderRadius: "3px",
|
|
22329
|
+
borderWidth: this.state.isFocused ? "2px" : "1px",
|
|
22330
|
+
display: "inline-block",
|
|
22331
|
+
fontFamily: `Symbola, "Times New Roman", serif`,
|
|
22332
|
+
fontSize: "18px",
|
|
22333
|
+
height: "32px",
|
|
22334
|
+
lineHeight: "18px",
|
|
22335
|
+
padding: this.state.isFocused ? "4px" : "4px 5px",
|
|
22336
|
+
// account for added focus border thickness
|
|
22349
22337
|
textAlign: this.props.rightAlign ? "right" : "left",
|
|
22350
|
-
width: this.props.size === "small" ? 40 : 80
|
|
22351
|
-
padding: 0,
|
|
22352
|
-
height: "auto"
|
|
22338
|
+
width: this.props.size === "small" ? 40 : 80
|
|
22353
22339
|
}
|
|
22354
22340
|
});
|
|
22355
|
-
return /*#__PURE__*/React.createElement(
|
|
22341
|
+
return /*#__PURE__*/React.createElement(InputWithExamples, {
|
|
22356
22342
|
ref: ref => this.inputRef = ref,
|
|
22357
22343
|
value: this.props.currentValue,
|
|
22358
22344
|
onChange: this.handleChange,
|
|
@@ -22364,7 +22350,7 @@ class NumericInput extends React.Component {
|
|
|
22364
22350
|
id: this.props.widgetId,
|
|
22365
22351
|
disabled: this.props.apiOptions.readOnly,
|
|
22366
22352
|
style: styles.input
|
|
22367
|
-
})
|
|
22353
|
+
});
|
|
22368
22354
|
}
|
|
22369
22355
|
}
|
|
22370
22356
|
|
|
@@ -27260,8 +27246,8 @@ var Definition$1 = {
|
|
|
27260
27246
|
scorer: () => scoreNoop()
|
|
27261
27247
|
};
|
|
27262
27248
|
|
|
27263
|
-
//
|
|
27264
|
-
//
|
|
27249
|
+
// There are no required props, but this component can receive props from any
|
|
27250
|
+
// deprecated widget
|
|
27265
27251
|
class DeprecatedStandin extends React__default.Component {
|
|
27266
27252
|
constructor(...args) {
|
|
27267
27253
|
super(...args);
|
|
@@ -29371,6 +29357,13 @@ const getPromptJSON$f = rendererJSON => {
|
|
|
29371
29357
|
});
|
|
29372
29358
|
};
|
|
29373
29359
|
|
|
29360
|
+
// The `group` widget is basically a widget hosting a full Perseus system in
|
|
29361
|
+
// it. As such, scoring a group means scoring all widgets it contains.
|
|
29362
|
+
function scoreGroup(userInput, options, strings, locale) {
|
|
29363
|
+
const scores = scoreWidgetsFunctional(options.widgets, Object.keys(options.widgets), userInput, strings, locale);
|
|
29364
|
+
return flattenScores(scores);
|
|
29365
|
+
}
|
|
29366
|
+
|
|
29374
29367
|
// exports has no 'transform'
|
|
29375
29368
|
|
|
29376
29369
|
class Group extends React.Component {
|
|
@@ -29525,6 +29518,9 @@ var Group$1 = {
|
|
|
29525
29518
|
displayName: "Group (SAT only)",
|
|
29526
29519
|
widget: Group,
|
|
29527
29520
|
traverseChildWidgets: traverseChildWidgets,
|
|
29521
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
29522
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
29523
|
+
scorer: scoreGroup,
|
|
29528
29524
|
hidden: true,
|
|
29529
29525
|
isLintable: true
|
|
29530
29526
|
};
|
|
@@ -32234,6 +32230,61 @@ const LegacyGrid = ({
|
|
|
32234
32230
|
|
|
32235
32231
|
// Range is replaced within this file with Interval, but it is used elsewhere
|
|
32236
32232
|
// and exported from the package, so we need to keep it around.
|
|
32233
|
+
/**
|
|
32234
|
+
* Our core set of Perseus widgets.
|
|
32235
|
+
*
|
|
32236
|
+
* This interface is the basis for "registering" all Perseus widget types.
|
|
32237
|
+
* There should be one key/value pair for each supported widget. If you create
|
|
32238
|
+
* a new widget, an entry should be added to this interface. Note that this
|
|
32239
|
+
* only registers the widget options type, you'll also need to register the
|
|
32240
|
+
* widget so that it's available at runtime (@see
|
|
32241
|
+
* {@link file://./widgets.ts#registerWidget}).
|
|
32242
|
+
*
|
|
32243
|
+
* Importantly, the key should be the name that is used in widget IDs. For most
|
|
32244
|
+
* widgets that is the same as the widget option's `type` field. In cases where
|
|
32245
|
+
* a widget has been deprecated and replaced with the deprecated-standin
|
|
32246
|
+
* widget, it should be the original widget type!
|
|
32247
|
+
*
|
|
32248
|
+
* If you define the widget outside of this package, you can still add the new
|
|
32249
|
+
* widget to this interface by writing the following in that package that
|
|
32250
|
+
* contains the widget. TypeScript will merge that definition of the
|
|
32251
|
+
* `PerseusWidgets` with the one defined below.
|
|
32252
|
+
*
|
|
32253
|
+
* ```typescript
|
|
32254
|
+
* declare module "@khanacademy/perseus" {
|
|
32255
|
+
* interface PerseusWidgetTypes {
|
|
32256
|
+
* // A new widget
|
|
32257
|
+
* "new-awesomeness": MyAwesomeNewWidget;
|
|
32258
|
+
*
|
|
32259
|
+
* // A deprecated widget
|
|
32260
|
+
* "super-old-widget": AutoCorrectWidget;
|
|
32261
|
+
* }
|
|
32262
|
+
* }
|
|
32263
|
+
*
|
|
32264
|
+
* // The new widget's options definition
|
|
32265
|
+
* type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
|
|
32266
|
+
*
|
|
32267
|
+
* // The deprecated widget's options definition
|
|
32268
|
+
* type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
|
|
32269
|
+
* ```
|
|
32270
|
+
*
|
|
32271
|
+
* This interface can be extended through the magic of TypeScript "Declaration
|
|
32272
|
+
* merging". Specifically, we augment this module and extend this interface.
|
|
32273
|
+
*
|
|
32274
|
+
* @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
|
|
32275
|
+
*/
|
|
32276
|
+
/**
|
|
32277
|
+
* A map of widget IDs to widget options. This is most often used as the type
|
|
32278
|
+
* for a set of widgets defined in a `PerseusItem` but can also be useful to
|
|
32279
|
+
* represent a function parameter where only `widgets` from a `PerseusItem` are
|
|
32280
|
+
* needed. Today Widget IDs are made up of the widget type and an incrementing
|
|
32281
|
+
* integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
|
|
32282
|
+
* reading/parsing the widget id to derive any information from it, except in
|
|
32283
|
+
* the case of this map.
|
|
32284
|
+
*
|
|
32285
|
+
* @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
|
|
32286
|
+
* by augmenting the PerseusWidgetTypes with new widget types!
|
|
32287
|
+
*/
|
|
32237
32288
|
/**
|
|
32238
32289
|
* A "PerseusItem" is a classic Perseus item. It is rendered by the
|
|
32239
32290
|
* `ServerItemRenderer` and the layout is pre-set.
|
|
@@ -32249,6 +32300,9 @@ const LegacyGrid = ({
|
|
|
32249
32300
|
* A "MultiItem" is an advanced Perseus item. It is rendered by the
|
|
32250
32301
|
* `MultiRenderer` and you can control the layout of individual parts of the
|
|
32251
32302
|
* item.
|
|
32303
|
+
*
|
|
32304
|
+
* @deprecated MultiItem support is slated for removal in a future Perseus
|
|
32305
|
+
* release.
|
|
32252
32306
|
*/
|
|
32253
32307
|
const ItemExtras = [
|
|
32254
32308
|
// The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
|
|
@@ -32270,6 +32324,11 @@ const ItemExtras = [
|
|
|
32270
32324
|
// The user might benefit from using a statistics Z Table like https://www.ztable.net/
|
|
32271
32325
|
"zTable"];
|
|
32272
32326
|
|
|
32327
|
+
/**
|
|
32328
|
+
* The type representing the common structure of all widget's options. The
|
|
32329
|
+
* `Options` generic type represents the widget-specific option data.
|
|
32330
|
+
*/
|
|
32331
|
+
|
|
32273
32332
|
// prettier-ignore
|
|
32274
32333
|
|
|
32275
32334
|
// prettier-ignore
|
|
@@ -32338,7 +32397,9 @@ const ItemExtras = [
|
|
|
32338
32397
|
|
|
32339
32398
|
//prettier-ignore
|
|
32340
32399
|
|
|
32341
|
-
|
|
32400
|
+
/**
|
|
32401
|
+
* A background image applied to various widgets.
|
|
32402
|
+
*/
|
|
32342
32403
|
|
|
32343
32404
|
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
32344
32405
|
|
|
@@ -36906,8 +36967,6 @@ const makeInvalidTypeError = (functionName, graphType) => {
|
|
|
36906
36967
|
});
|
|
36907
36968
|
};
|
|
36908
36969
|
|
|
36909
|
-
// There's no transform function in exports
|
|
36910
|
-
|
|
36911
36970
|
// TODO: there's another, very similar getSinusoidCoefficients function
|
|
36912
36971
|
// they should probably be merged
|
|
36913
36972
|
function getSinusoidCoefficients(coords) {
|
|
@@ -42043,23 +42102,37 @@ const getPromptJSON$6 = (renderProps, userInput) => {
|
|
|
42043
42102
|
};
|
|
42044
42103
|
};
|
|
42045
42104
|
|
|
42046
|
-
|
|
42047
|
-
|
|
42048
|
-
|
|
42049
|
-
|
|
42050
|
-
|
|
42051
|
-
|
|
42052
|
-
|
|
42053
|
-
const
|
|
42105
|
+
/**
|
|
42106
|
+
* Checks user input is within the allowed range and not the same as the initial
|
|
42107
|
+
* state.
|
|
42108
|
+
* @param userInput
|
|
42109
|
+
* @see 'scoreNumberLine' for the scoring logic.
|
|
42110
|
+
*/
|
|
42111
|
+
function validateNumberLine(userInput) {
|
|
42112
|
+
const divisionRange = userInput.divisionRange;
|
|
42113
|
+
const outsideAllowedRange = userInput.numDivisions > divisionRange[1] || userInput.numDivisions < divisionRange[0];
|
|
42054
42114
|
|
|
42055
42115
|
// TODO: I don't think isTickCrtl is a thing anymore
|
|
42056
|
-
if (
|
|
42116
|
+
if (userInput.isTickCrtl && outsideAllowedRange) {
|
|
42057
42117
|
return {
|
|
42058
42118
|
type: "invalid",
|
|
42059
42119
|
message: "Number of divisions is outside the allowed range."
|
|
42060
42120
|
};
|
|
42061
42121
|
}
|
|
42062
|
-
|
|
42122
|
+
return null;
|
|
42123
|
+
}
|
|
42124
|
+
|
|
42125
|
+
function scoreNumberLine(userInput, scoringData) {
|
|
42126
|
+
const validationError = validateNumberLine(userInput);
|
|
42127
|
+
if (validationError) {
|
|
42128
|
+
return validationError;
|
|
42129
|
+
}
|
|
42130
|
+
const range = scoringData.range;
|
|
42131
|
+
const start = scoringData.initialX != null ? scoringData.initialX : range[0];
|
|
42132
|
+
const startRel = scoringData.isInequality ? "ge" : "eq";
|
|
42133
|
+
const correctRel = scoringData.correctRel || "eq";
|
|
42134
|
+
const correctPos = number$1.equal(userInput.numLinePosition, scoringData.correctX || 0);
|
|
42135
|
+
if (correctPos && correctRel === userInput.rel) {
|
|
42063
42136
|
return {
|
|
42064
42137
|
type: "points",
|
|
42065
42138
|
earned: 1,
|
|
@@ -42067,7 +42140,7 @@ function scoreNumberLine(state, rubric) {
|
|
|
42067
42140
|
message: null
|
|
42068
42141
|
};
|
|
42069
42142
|
}
|
|
42070
|
-
if (
|
|
42143
|
+
if (userInput.numLinePosition === start && userInput.rel === startRel) {
|
|
42071
42144
|
// We're where we started.
|
|
42072
42145
|
return {
|
|
42073
42146
|
type: "invalid",
|
|
@@ -45013,7 +45086,7 @@ const init = function init() {
|
|
|
45013
45086
|
|
|
45014
45087
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45015
45088
|
const libName = "@khanacademy/perseus";
|
|
45016
|
-
const libVersion = "
|
|
45089
|
+
const libVersion = "46.0.0";
|
|
45017
45090
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45018
45091
|
|
|
45019
45092
|
const apiVersion = {
|
|
@@ -45621,14 +45694,10 @@ class ServerItemRenderer extends React.Component {
|
|
|
45621
45694
|
});
|
|
45622
45695
|
}
|
|
45623
45696
|
componentDidUpdate(prevProps, prevState) {
|
|
45624
|
-
|
|
45697
|
+
const answerableCallback = this.props.apiOptions.answerableCallback;
|
|
45698
|
+
if (answerableCallback != null) {
|
|
45625
45699
|
const isAnswerable = this.questionRenderer.emptyWidgets().length === 0;
|
|
45626
|
-
|
|
45627
|
-
answerableCallback
|
|
45628
|
-
} = this.props.apiOptions;
|
|
45629
|
-
if (answerableCallback) {
|
|
45630
|
-
answerableCallback(isAnswerable);
|
|
45631
|
-
}
|
|
45700
|
+
answerableCallback(isAnswerable);
|
|
45632
45701
|
}
|
|
45633
45702
|
if (!this._fullyRendered) {
|
|
45634
45703
|
const assetsLoaded = Object.values(this.state.assetStatuses).every(Boolean);
|
|
@@ -45792,7 +45861,7 @@ class ServerItemRenderer extends React.Component {
|
|
|
45792
45861
|
// TODO(alex): Check whether we rely on the format here for
|
|
45793
45862
|
// analyzing ProblemLogs. If not, remove this layer.
|
|
45794
45863
|
const maxCompatGuess = [this.questionRenderer.getUserInput(), []];
|
|
45795
|
-
const keScore =
|
|
45864
|
+
const keScore = keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
|
|
45796
45865
|
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45797
45866
|
this.setState({
|
|
45798
45867
|
questionCompleted: keScore.correct,
|
|
@@ -45971,7 +46040,7 @@ const traverseRenderer$1 = function traverseRenderer(rendererOptions, contentCal
|
|
|
45971
46040
|
newContent = modifiedContent;
|
|
45972
46041
|
}
|
|
45973
46042
|
}
|
|
45974
|
-
const newWidgets =
|
|
46043
|
+
const newWidgets = mapObject(rendererOptions.widgets || {}, function (widgetInfo, widgetId) {
|
|
45975
46044
|
// Widgets without info or a type are empty widgets, and
|
|
45976
46045
|
// should always be renderable. It's also annoying to write
|
|
45977
46046
|
// checks for this everywhere, so we just filter them out once and
|
|
@@ -46871,7 +46940,7 @@ class MultiRenderer extends React.Component {
|
|
|
46871
46940
|
if (ref.getSerializedState) {
|
|
46872
46941
|
state = ref.getSerializedState();
|
|
46873
46942
|
}
|
|
46874
|
-
return
|
|
46943
|
+
return keScoreFromPerseusScore(score, guess, state);
|
|
46875
46944
|
}
|
|
46876
46945
|
|
|
46877
46946
|
/**
|
|
@@ -46901,8 +46970,8 @@ class MultiRenderer extends React.Component {
|
|
|
46901
46970
|
scores.push(data.ref.score());
|
|
46902
46971
|
return (_data$ref = data.ref) == null ? void 0 : _data$ref.getUserInput();
|
|
46903
46972
|
});
|
|
46904
|
-
const combinedScore = scores.reduce(
|
|
46905
|
-
return
|
|
46973
|
+
const combinedScore = scores.reduce(combineScores);
|
|
46974
|
+
return keScoreFromPerseusScore(combinedScore, guess, state);
|
|
46906
46975
|
}
|
|
46907
46976
|
|
|
46908
46977
|
/**
|
|
@@ -48570,7 +48639,7 @@ const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
|
48570
48639
|
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
48571
48640
|
togglePrompt: string,
|
|
48572
48641
|
definition: string,
|
|
48573
|
-
static: boolean
|
|
48642
|
+
static: defaulted(boolean, () => false)
|
|
48574
48643
|
}));
|
|
48575
48644
|
|
|
48576
48645
|
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
@@ -48772,15 +48841,26 @@ const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
|
48772
48841
|
static: defaulted(boolean, () => false)
|
|
48773
48842
|
}));
|
|
48774
48843
|
|
|
48844
|
+
const stringToNumber = (rawValue, ctx) => {
|
|
48845
|
+
if (typeof rawValue === "number") {
|
|
48846
|
+
return ctx.success(rawValue);
|
|
48847
|
+
}
|
|
48848
|
+
const parsedNumber = +rawValue;
|
|
48849
|
+
if (rawValue === "" || isNaN(parsedNumber)) {
|
|
48850
|
+
return ctx.failure("a number or numeric string", rawValue);
|
|
48851
|
+
}
|
|
48852
|
+
return ctx.success(parsedNumber);
|
|
48853
|
+
};
|
|
48854
|
+
|
|
48855
|
+
const numericToNumber = pipeParsers(union(number).or(string).parser).then(stringToNumber).parser;
|
|
48775
48856
|
const parsePerseusImageBackground = object({
|
|
48776
48857
|
url: optional(nullable(string)),
|
|
48777
|
-
width: optional(
|
|
48778
|
-
height: optional(
|
|
48779
|
-
top: optional(
|
|
48780
|
-
left: optional(
|
|
48781
|
-
bottom: optional(
|
|
48782
|
-
|
|
48783
|
-
scale: optional(union(number).or(string).parser)
|
|
48858
|
+
width: optional(numericToNumber),
|
|
48859
|
+
height: optional(numericToNumber),
|
|
48860
|
+
top: optional(numericToNumber),
|
|
48861
|
+
left: optional(numericToNumber),
|
|
48862
|
+
bottom: optional(numericToNumber),
|
|
48863
|
+
scale: optional(numericToNumber)
|
|
48784
48864
|
});
|
|
48785
48865
|
|
|
48786
48866
|
const pairOfNumbers$2 = pair(number, number);
|
|
@@ -48799,6 +48879,12 @@ const parseImageWidget = parseWidget(constant("image"), object({
|
|
|
48799
48879
|
box: optional(pairOfNumbers$2)
|
|
48800
48880
|
}));
|
|
48801
48881
|
|
|
48882
|
+
const booleanToString = (rawValue, ctx) => {
|
|
48883
|
+
if (typeof rawValue === "boolean") {
|
|
48884
|
+
return ctx.success(String(rawValue));
|
|
48885
|
+
}
|
|
48886
|
+
return ctx.failure("boolean", rawValue);
|
|
48887
|
+
};
|
|
48802
48888
|
const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
48803
48889
|
answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
|
|
48804
48890
|
inexact: optional(boolean),
|
|
@@ -48806,7 +48892,11 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
|
48806
48892
|
rightAlign: optional(boolean),
|
|
48807
48893
|
simplify: enumeration("required", "optional", "enforced"),
|
|
48808
48894
|
size: enumeration("normal", "small"),
|
|
48809
|
-
|
|
48895
|
+
// TODO(benchristel): there are some content items where value is a
|
|
48896
|
+
// boolean, even though that makes no sense. We should figure out if
|
|
48897
|
+
// those content items are actually published anywhere, and consider
|
|
48898
|
+
// updating them.
|
|
48899
|
+
value: union(number).or(string).or(booleanToString).parser,
|
|
48810
48900
|
customKeypad: optional(boolean)
|
|
48811
48901
|
}));
|
|
48812
48902
|
|
|
@@ -49104,18 +49194,27 @@ const parseLockedFunctionType = object({
|
|
|
49104
49194
|
const parseLockedFigure = union(parseLockedPointType).or(parseLockedLineType).or(parseLockedVectorType).or(parseLockedEllipseType).or(parseLockedPolygonType).or(parseLockedFunctionType).or(parseLockedLabelType).parser;
|
|
49105
49195
|
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
49106
49196
|
step: pairOfNumbers,
|
|
49107
|
-
|
|
49108
|
-
|
|
49197
|
+
// TODO(benchristel): rather than making gridStep and snapStep
|
|
49198
|
+
// optional, we should duplicate the defaulting logic from the
|
|
49199
|
+
// InteractiveGraph component. See parse-perseus-json/README.md for
|
|
49200
|
+
// why.
|
|
49201
|
+
gridStep: optional(pairOfNumbers),
|
|
49202
|
+
snapStep: optional(pairOfNumbers),
|
|
49109
49203
|
backgroundImage: optional(parsePerseusImageBackground),
|
|
49110
49204
|
markings: enumeration("graph", "grid", "none"),
|
|
49111
|
-
labels: array(string),
|
|
49205
|
+
labels: optional(array(string)),
|
|
49112
49206
|
showProtractor: boolean,
|
|
49113
49207
|
showRuler: optional(boolean),
|
|
49114
49208
|
showTooltips: optional(boolean),
|
|
49115
49209
|
rulerLabel: optional(string),
|
|
49116
49210
|
rulerTicks: optional(number),
|
|
49117
49211
|
range: pair(pairOfNumbers, pairOfNumbers),
|
|
49118
|
-
|
|
49212
|
+
// NOTE(benchristel): I copied the default graph from
|
|
49213
|
+
// interactive-graph.tsx. See the parse-perseus-json/README.md for
|
|
49214
|
+
// an explanation of why we want to duplicate the default here.
|
|
49215
|
+
graph: defaulted(parsePerseusGraphType, () => ({
|
|
49216
|
+
type: "linear"
|
|
49217
|
+
})),
|
|
49119
49218
|
correct: parsePerseusGraphType,
|
|
49120
49219
|
// TODO(benchristel): default lockedFigures to empty array
|
|
49121
49220
|
lockedFigures: optional(array(parseLockedFigure)),
|
|
@@ -49183,7 +49282,10 @@ const parseNumberLineWidget = parseWidget(constant("number-line"), object({
|
|
|
49183
49282
|
isTickCtrl: optional(nullable(boolean)),
|
|
49184
49283
|
divisionRange: array(number),
|
|
49185
49284
|
numDivisions: optional(nullable(number)),
|
|
49186
|
-
|
|
49285
|
+
// NOTE(benchristel): I copied the default snapDivisions from
|
|
49286
|
+
// number-line.tsx. See the parse-perseus-json/README.md for
|
|
49287
|
+
// an explanation of why we want to duplicate the default here.
|
|
49288
|
+
snapDivisions: defaulted(number, () => 2),
|
|
49187
49289
|
tickStep: optional(nullable(number)),
|
|
49188
49290
|
correctRel: optional(nullable(string)),
|
|
49189
49291
|
correctX: number,
|
|
@@ -49196,18 +49298,18 @@ const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "d
|
|
|
49196
49298
|
const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
|
|
49197
49299
|
answers: array(object({
|
|
49198
49300
|
message: string,
|
|
49199
|
-
value: number,
|
|
49301
|
+
value: optional(number),
|
|
49200
49302
|
status: string,
|
|
49201
49303
|
answerForms: optional(array(parseMathFormat)),
|
|
49202
49304
|
strict: boolean,
|
|
49203
49305
|
maxError: optional(nullable(number)),
|
|
49204
49306
|
simplify: optional(nullable(string))
|
|
49205
49307
|
})),
|
|
49206
|
-
labelText: string,
|
|
49308
|
+
labelText: optional(string),
|
|
49207
49309
|
size: string,
|
|
49208
49310
|
coefficient: boolean,
|
|
49209
49311
|
rightAlign: optional(boolean),
|
|
49210
|
-
static: boolean,
|
|
49312
|
+
static: defaulted(boolean, () => false),
|
|
49211
49313
|
answerForms: optional(array(object({
|
|
49212
49314
|
name: parseMathFormat,
|
|
49213
49315
|
simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
|
|
@@ -49220,12 +49322,18 @@ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
|
|
|
49220
49322
|
function parseRenderer(rawValue, ctx) {
|
|
49221
49323
|
return parsePerseusRenderer(rawValue, ctx);
|
|
49222
49324
|
}
|
|
49325
|
+
const largeToAuto = (height, ctx) => {
|
|
49326
|
+
if (height === "large") {
|
|
49327
|
+
return ctx.success("auto");
|
|
49328
|
+
}
|
|
49329
|
+
return ctx.success(height);
|
|
49330
|
+
};
|
|
49223
49331
|
const parseOrdererWidget = parseWidget(constant("orderer"), object({
|
|
49224
|
-
options: array(parseRenderer),
|
|
49332
|
+
options: defaulted(array(parseRenderer), () => []),
|
|
49225
49333
|
correctOptions: array(parseRenderer),
|
|
49226
49334
|
otherOptions: array(parseRenderer),
|
|
49227
|
-
height: enumeration("normal", "auto"),
|
|
49228
|
-
layout: enumeration("horizontal", "vertical")
|
|
49335
|
+
height: pipeParsers(enumeration("normal", "auto", "large")).then(largeToAuto).parser,
|
|
49336
|
+
layout: defaulted(enumeration("horizontal", "vertical"), () => "horizontal")
|
|
49229
49337
|
}));
|
|
49230
49338
|
|
|
49231
49339
|
const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
|
|
@@ -49239,7 +49347,7 @@ const parsePassageWidget = parseWidget(constant("passage"), object({
|
|
|
49239
49347
|
passageText: string,
|
|
49240
49348
|
passageTitle: string,
|
|
49241
49349
|
showLineNumbers: boolean,
|
|
49242
|
-
static: boolean
|
|
49350
|
+
static: defaulted(boolean, () => false)
|
|
49243
49351
|
}));
|
|
49244
49352
|
|
|
49245
49353
|
const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
|
|
@@ -49260,7 +49368,10 @@ const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
|
49260
49368
|
picUrl: optional(nullable(string)),
|
|
49261
49369
|
picSize: optional(nullable(number)),
|
|
49262
49370
|
picBoxHeight: optional(nullable(number)),
|
|
49263
|
-
|
|
49371
|
+
// NOTE(benchristel): I copied the default plotDimensions from
|
|
49372
|
+
// plotter.tsx. See the parse-perseus-json/README.md for an explanation
|
|
49373
|
+
// of why we want to duplicate the defaults here.
|
|
49374
|
+
plotDimensions: defaulted(array(number), () => [380, 300])
|
|
49264
49375
|
}));
|
|
49265
49376
|
|
|
49266
49377
|
const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
|
|
@@ -49412,6 +49523,10 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
|
|
|
49412
49523
|
return parseAndAssign(`table ${id}`, parseTableWidget);
|
|
49413
49524
|
case "video":
|
|
49414
49525
|
return parseAndAssign(`video ${id}`, parseVideoWidget);
|
|
49526
|
+
case "sequence":
|
|
49527
|
+
// sequence is a deprecated widget type, and the corresponding
|
|
49528
|
+
// widget component no longer exists.
|
|
49529
|
+
return parseAndAssign(`sequence ${id}`, parseDeprecatedWidget);
|
|
49415
49530
|
default:
|
|
49416
49531
|
if (getWidget(type)) {
|
|
49417
49532
|
// @ts-expect-error - 'type' is not a valid widget type
|
|
@@ -49420,6 +49535,11 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
|
|
|
49420
49535
|
return ctx.failure("a valid widget type", type);
|
|
49421
49536
|
}
|
|
49422
49537
|
};
|
|
49538
|
+
const parseDeprecatedWidget = parseWidget(
|
|
49539
|
+
// Ignore the incoming widget type and hardcode "deprecated-standin"
|
|
49540
|
+
(_, ctx) => ctx.success("deprecated-standin"),
|
|
49541
|
+
// Allow any widget options
|
|
49542
|
+
object({}));
|
|
49423
49543
|
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
49424
49544
|
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
49425
49545
|
return ctx.failure("numeric string", rawValue);
|
|
@@ -49694,6 +49814,9 @@ const GrapherUtil = {
|
|
|
49694
49814
|
getEquationString,
|
|
49695
49815
|
typeToButton
|
|
49696
49816
|
};
|
|
49817
|
+
const ScoringUtil = {
|
|
49818
|
+
keScoreFromPerseusScore
|
|
49819
|
+
};
|
|
49697
49820
|
|
|
49698
|
-
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 };
|
|
49821
|
+
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, ScoringUtil, 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 };
|
|
49699
49822
|
//# sourceMappingURL=index.js.map
|