@khanacademy/perseus 33.3.0 → 34.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/sortable.d.ts +1 -4
- package/dist/es/index.js +448 -540
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +444 -542
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +1 -0
- package/dist/perseus-types.d.ts +2 -1
- package/dist/server-item-renderer.d.ts +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/util/answer-types.d.ts +1 -1
- package/dist/widgets/__shared__/noop-validator.d.ts +11 -0
- package/dist/widgets/categorizer/categorizer.d.ts +3 -1
- package/dist/widgets/cs-program/cs-program.d.ts +23 -17
- package/dist/widgets/dropdown/dropdown-validator.d.ts +4 -0
- package/dist/widgets/dropdown/dropdown.d.ts +1 -5
- package/dist/widgets/dropdown/dropdown.types.d.ts +5 -0
- package/dist/widgets/expression/expression-validator.d.ts +5 -0
- package/dist/widgets/expression/expression.d.ts +7 -6
- package/dist/widgets/expression/expression.types.d.ts +3 -0
- package/dist/widgets/expression/get-decimal-separator.d.ts +5 -0
- package/dist/widgets/iframe/iframe.d.ts +24 -15
- package/dist/widgets/interactive-graphs/graph-locked-labels-layer.d.ts +2 -0
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +5 -0
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +2 -1
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +2 -1
- package/dist/widgets/label-image/label-image.d.ts +2 -0
- package/dist/widgets/matcher/matcher.d.ts +17 -46
- package/dist/widgets/numeric-input/numeric-input-validator.d.ts +6 -0
- package/dist/widgets/numeric-input/numeric-input.d.ts +1 -5
- package/dist/widgets/numeric-input/numeric-input.types.d.ts +5 -0
- package/dist/widgets/orderer/orderer.d.ts +6 -5
- package/dist/widgets/sorter/sorter.d.ts +18 -44
- package/dist/widgets/table/table.d.ts +20 -24
- package/package.json +1 -1
- package/dist/mixins/widget-jsonify-deprecated.d.ts +0 -5
package/dist/index.js
CHANGED
|
@@ -4,7 +4,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var KAS = require('@khanacademy/kas');
|
|
6
6
|
var mathInput = require('@khanacademy/math-input');
|
|
7
|
-
var perseusCore = require('@khanacademy/perseus-core');
|
|
8
7
|
var PerseusLinter = require('@khanacademy/perseus-linter');
|
|
9
8
|
var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
|
|
10
9
|
var Tooltip$1 = require('@khanacademy/wonder-blocks-tooltip');
|
|
@@ -19,6 +18,7 @@ var wonderBlocksPopover = require('@khanacademy/wonder-blocks-popover');
|
|
|
19
18
|
var wonderBlocksTokens = require('@khanacademy/wonder-blocks-tokens');
|
|
20
19
|
var $ = require('jquery');
|
|
21
20
|
var uuid = require('uuid');
|
|
21
|
+
var perseusCore = require('@khanacademy/perseus-core');
|
|
22
22
|
var PropTypes = require('prop-types');
|
|
23
23
|
var kmath = require('@khanacademy/kmath');
|
|
24
24
|
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
@@ -845,23 +845,6 @@ var dependencies = /*#__PURE__*/Object.freeze({
|
|
|
845
845
|
useDependencies: useDependencies
|
|
846
846
|
});
|
|
847
847
|
|
|
848
|
-
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
849
|
-
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
850
|
-
// which get's the injected logger.
|
|
851
|
-
/**
|
|
852
|
-
* Provides logging infrastructure for Perseus
|
|
853
|
-
*/
|
|
854
|
-
const Log = {
|
|
855
|
-
// Logs a message
|
|
856
|
-
log: (message, extra) => {
|
|
857
|
-
getDependencies().Log.log(message, extra);
|
|
858
|
-
},
|
|
859
|
-
// Logs an error with a message
|
|
860
|
-
error: (message, kind, extra) => {
|
|
861
|
-
getDependencies().Log.error(message, kind, extra);
|
|
862
|
-
}
|
|
863
|
-
};
|
|
864
|
-
|
|
865
848
|
/**
|
|
866
849
|
* These are things that widgets should exclude when serializing themselves.
|
|
867
850
|
*
|
|
@@ -1304,6 +1287,23 @@ const ClassNames = {
|
|
|
1304
1287
|
MOBILE: "perseus-mobile"
|
|
1305
1288
|
};
|
|
1306
1289
|
|
|
1290
|
+
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
1291
|
+
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
1292
|
+
// which get's the injected logger.
|
|
1293
|
+
/**
|
|
1294
|
+
* Provides logging infrastructure for Perseus
|
|
1295
|
+
*/
|
|
1296
|
+
const Log = {
|
|
1297
|
+
// Logs a message
|
|
1298
|
+
log: (message, extra) => {
|
|
1299
|
+
getDependencies().Log.log(message, extra);
|
|
1300
|
+
},
|
|
1301
|
+
// Logs an error with a message
|
|
1302
|
+
error: (message, kind, extra) => {
|
|
1303
|
+
getDependencies().Log.error(message, kind, extra);
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
1307
|
const KhanMath = {
|
|
1308
1308
|
// Simplify formulas before display
|
|
1309
1309
|
cleanMath: function (expr) {
|
|
@@ -2219,6 +2219,176 @@ const KhanAnswerTypes = {
|
|
|
2219
2219
|
}
|
|
2220
2220
|
};
|
|
2221
2221
|
|
|
2222
|
+
/**
|
|
2223
|
+
* Get the character used for separating decimals.
|
|
2224
|
+
*/
|
|
2225
|
+
const getDecimalSeparator = locale => {
|
|
2226
|
+
switch (locale) {
|
|
2227
|
+
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
2228
|
+
// locale properly.
|
|
2229
|
+
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
2230
|
+
//
|
|
2231
|
+
// Supported locales in Chrome:
|
|
2232
|
+
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
2233
|
+
case "ka":
|
|
2234
|
+
return ",";
|
|
2235
|
+
default:
|
|
2236
|
+
const numberWithDecimalSeparator = 1.1;
|
|
2237
|
+
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
2238
|
+
// support Safari 12.
|
|
2239
|
+
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
2240
|
+
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
2241
|
+
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
2242
|
+
.match(/[^\d\u0661\u06F1]/);
|
|
2243
|
+
return match?.[0] ?? ".";
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
|
|
2247
|
+
/* Content creators input a list of answers which are matched from top to
|
|
2248
|
+
* bottom. The intent is that they can include spcific solutions which should
|
|
2249
|
+
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
2250
|
+
* general.
|
|
2251
|
+
*
|
|
2252
|
+
* We iterate through each answer, trying to match it with the user's input
|
|
2253
|
+
* using the following angorithm:
|
|
2254
|
+
* - Try to parse the user's input. If it doesn't parse then return "not
|
|
2255
|
+
* graded".
|
|
2256
|
+
* - For each answer:
|
|
2257
|
+
* ~ Try to validate the user's input against the answer. The answer is
|
|
2258
|
+
* expected to parse.
|
|
2259
|
+
* ~ If the user's input validates (the validator judges it "correct"), we've
|
|
2260
|
+
* matched and can stop considering answers.
|
|
2261
|
+
* - If there were no matches or the matching answer is considered "ungraded",
|
|
2262
|
+
* show the user an error. TODO(joel) - what error?
|
|
2263
|
+
* - Otherwise, pass through the resulting points and message.
|
|
2264
|
+
*/
|
|
2265
|
+
function expressionValidator(userInput, rubric) {
|
|
2266
|
+
let onInputError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
|
|
2267
|
+
let strings = arguments.length > 3 ? arguments[3] : undefined;
|
|
2268
|
+
let locale = arguments.length > 4 ? arguments[4] : undefined;
|
|
2269
|
+
const options = ___default["default"].clone(rubric);
|
|
2270
|
+
___default["default"].extend(options, {
|
|
2271
|
+
decimal_separator: getDecimalSeparator(locale)
|
|
2272
|
+
});
|
|
2273
|
+
const createValidator = answer => {
|
|
2274
|
+
// We give options to KAS.parse here because it is parsing the
|
|
2275
|
+
// solution answer, not the student answer, and we don't want a
|
|
2276
|
+
// solution to work if the student is using a different language
|
|
2277
|
+
// (different from the content creation language, ie. English).
|
|
2278
|
+
const expression = KAS__namespace.parse(answer.value, rubric);
|
|
2279
|
+
// An answer may not be parsed if the expression was defined
|
|
2280
|
+
// incorrectly. For example if the answer is using a symbol defined
|
|
2281
|
+
// in the function variables list for the expression.
|
|
2282
|
+
if (!expression.parsed) {
|
|
2283
|
+
/* c8 ignore next */
|
|
2284
|
+
Log.error("Unable to parse solution answer for expression", perseusCore.Errors.InvalidInput, {
|
|
2285
|
+
loggedMetadata: {
|
|
2286
|
+
rubric: JSON.stringify(rubric)
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2289
|
+
return null;
|
|
2290
|
+
}
|
|
2291
|
+
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
|
|
2292
|
+
simplify: answer.simplify,
|
|
2293
|
+
form: answer.form
|
|
2294
|
+
}), strings);
|
|
2295
|
+
};
|
|
2296
|
+
|
|
2297
|
+
// Find the first answer form that matches the user's input and that
|
|
2298
|
+
// is considered correct. Also, track whether the input is
|
|
2299
|
+
// considered "empty" for all answer forms, and keep the validation
|
|
2300
|
+
// result for the first answer form for which the user's input was
|
|
2301
|
+
// considered "ungraded".
|
|
2302
|
+
// (Terminology reminder: the answer forms are provided by the
|
|
2303
|
+
// assessment items; they are not the user's input. Each one might
|
|
2304
|
+
// represent a correct answer, an incorrect one (if the exercise
|
|
2305
|
+
// creator has predicted certain common wrong answers and wants to
|
|
2306
|
+
// provide guidance via a message), or an ungraded one (same idea,
|
|
2307
|
+
// but without giving the user an incorrect mark for the question).
|
|
2308
|
+
let matchingAnswerForm;
|
|
2309
|
+
let matchMessage;
|
|
2310
|
+
let allEmpty = true;
|
|
2311
|
+
let firstUngradedResult;
|
|
2312
|
+
for (const answerForm of rubric.answerForms || []) {
|
|
2313
|
+
const validator = createValidator(answerForm);
|
|
2314
|
+
if (!validator) {
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
const result = validator(userInput);
|
|
2318
|
+
|
|
2319
|
+
// Short-circuit as soon as the user's input matches some answer
|
|
2320
|
+
// (independently of whether the answer is correct)
|
|
2321
|
+
if (result.correct) {
|
|
2322
|
+
matchingAnswerForm = answerForm;
|
|
2323
|
+
matchMessage = result.message || "";
|
|
2324
|
+
break;
|
|
2325
|
+
}
|
|
2326
|
+
allEmpty = allEmpty && result.empty;
|
|
2327
|
+
// If this answer form is correct and the user's input is considered
|
|
2328
|
+
// "ungraded" for it, we'll want to keep the evaluation result for
|
|
2329
|
+
// later. If the user's input doesn't match any answer forms, we'll
|
|
2330
|
+
// show the message from this validation.
|
|
2331
|
+
if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
|
|
2332
|
+
firstUngradedResult = result;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// Now check to see if we matched any answer form at all, and if
|
|
2337
|
+
// we did, whether it's considered correct, incorrect, or ungraded
|
|
2338
|
+
if (!matchingAnswerForm) {
|
|
2339
|
+
if (firstUngradedResult) {
|
|
2340
|
+
// While we didn't directly match with any answer form, we
|
|
2341
|
+
// did at some point get an "ungraded" validation result,
|
|
2342
|
+
// which might indicate e.g. a mismatch in variable casing.
|
|
2343
|
+
// We'll return "invalid", which will let the user try again
|
|
2344
|
+
// with no penalty, and the hopefully helpful validation
|
|
2345
|
+
// message.
|
|
2346
|
+
return {
|
|
2347
|
+
type: "invalid",
|
|
2348
|
+
message: firstUngradedResult.message,
|
|
2349
|
+
suppressAlmostThere: firstUngradedResult.suppressAlmostThere
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
if (allEmpty) {
|
|
2353
|
+
// If everything graded as empty, it's invalid.
|
|
2354
|
+
return {
|
|
2355
|
+
type: "invalid",
|
|
2356
|
+
message: null
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
// We fell through all the possibilities and we're not empty,
|
|
2360
|
+
// so the answer is considered incorrect.
|
|
2361
|
+
return {
|
|
2362
|
+
type: "points",
|
|
2363
|
+
earned: 0,
|
|
2364
|
+
total: 1
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
if (matchingAnswerForm.considered === "ungraded") {
|
|
2368
|
+
// We matched an ungraded answer form - return "invalid", which
|
|
2369
|
+
// will let the user try again with no penalty
|
|
2370
|
+
const apiResult = onInputError(null,
|
|
2371
|
+
// Reserved for some widget identifier
|
|
2372
|
+
userInput, matchMessage);
|
|
2373
|
+
return {
|
|
2374
|
+
type: "invalid",
|
|
2375
|
+
message: apiResult === false ? null : matchMessage
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
// We matched a graded answer form, so we can now tell the user
|
|
2379
|
+
// whether their input was correct or incorrect, and hand out
|
|
2380
|
+
// points accordingly
|
|
2381
|
+
// TODO(eater): Seems silly to translate result to this
|
|
2382
|
+
// invalid/points thing and immediately translate it back in
|
|
2383
|
+
// ItemRenderer.scoreInput()
|
|
2384
|
+
return {
|
|
2385
|
+
type: "points",
|
|
2386
|
+
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
2387
|
+
total: 1,
|
|
2388
|
+
message: matchMessage
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2222
2392
|
// Map of international operator names to their English equivalents
|
|
2223
2393
|
const englishOperators = {
|
|
2224
2394
|
arctg: "arctan",
|
|
@@ -2245,181 +2415,18 @@ const deriveKeypadVersion = apiOptions => {
|
|
|
2245
2415
|
// can remove this `virtualKeypadVersion` field entirely.
|
|
2246
2416
|
return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : "MATH_INPUT_KEYPAD_V2";
|
|
2247
2417
|
};
|
|
2248
|
-
/**
|
|
2249
|
-
* Get the character used for separating decimals.
|
|
2250
|
-
*/
|
|
2251
|
-
const getDecimalSeparator = locale => {
|
|
2252
|
-
switch (locale) {
|
|
2253
|
-
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
2254
|
-
// locale properly.
|
|
2255
|
-
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
2256
|
-
//
|
|
2257
|
-
// Supported locales in Chrome:
|
|
2258
|
-
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
2259
|
-
case "ka":
|
|
2260
|
-
return ",";
|
|
2261
|
-
default:
|
|
2262
|
-
const numberWithDecimalSeparator = 1.1;
|
|
2263
|
-
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
2264
|
-
// support Safari 12.
|
|
2265
|
-
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
2266
|
-
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
2267
|
-
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
2268
|
-
.match(/[^\d\u0661\u06F1]/);
|
|
2269
|
-
return match?.[0] ?? ".";
|
|
2270
|
-
}
|
|
2271
|
-
};
|
|
2272
|
-
|
|
2273
2418
|
// The new, MathQuill input expression widget
|
|
2274
2419
|
class Expression extends React__namespace.Component {
|
|
2275
2420
|
static contextType = PerseusI18nContext;
|
|
2276
2421
|
_textareaId = `expression_textarea_${Date.now()}`;
|
|
2277
2422
|
_isMounted = false;
|
|
2278
2423
|
|
|
2279
|
-
|
|
2280
|
-
/* Content creators input a list of answers which are matched from top to
|
|
2281
|
-
* bottom. The intent is that they can include spcific solutions which should
|
|
2282
|
-
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
2283
|
-
* general.
|
|
2284
|
-
*
|
|
2285
|
-
* We iterate through each answer, trying to match it with the user's input
|
|
2286
|
-
* using the following angorithm:
|
|
2287
|
-
* - Try to parse the user's input. If it doesn't parse then return "not
|
|
2288
|
-
* graded".
|
|
2289
|
-
* - For each answer:
|
|
2290
|
-
* ~ Try to validate the user's input against the answer. The answer is
|
|
2291
|
-
* expected to parse.
|
|
2292
|
-
* ~ If the user's input validates (the validator judges it "correct"), we've
|
|
2293
|
-
* matched and can stop considering answers.
|
|
2294
|
-
* - If there were no matches or the matching answer is considered "ungraded",
|
|
2295
|
-
* show the user an error. TODO(joel) - what error?
|
|
2296
|
-
* - Otherwise, pass through the resulting points and message.
|
|
2297
|
-
*/
|
|
2424
|
+
// TODO remove this in favor of just using expressionValidator
|
|
2298
2425
|
static validate(userInput, rubric) {
|
|
2299
2426
|
let onInputError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
|
|
2300
2427
|
let strings = arguments.length > 3 ? arguments[3] : undefined;
|
|
2301
2428
|
let locale = arguments.length > 4 ? arguments[4] : undefined;
|
|
2302
|
-
|
|
2303
|
-
___default["default"].extend(options, {
|
|
2304
|
-
decimal_separator: getDecimalSeparator(locale)
|
|
2305
|
-
});
|
|
2306
|
-
const createValidator = answer => {
|
|
2307
|
-
// We give options to KAS.parse here because it is parsing the
|
|
2308
|
-
// solution answer, not the student answer, and we don't want a
|
|
2309
|
-
// solution to work if the student is using a different language
|
|
2310
|
-
// (different from the content creation language, ie. English).
|
|
2311
|
-
const expression = KAS__namespace.parse(answer.value, rubric);
|
|
2312
|
-
// An answer may not be parsed if the expression was defined
|
|
2313
|
-
// incorrectly. For example if the answer is using a symbol defined
|
|
2314
|
-
// in the function variables list for the expression.
|
|
2315
|
-
if (!expression.parsed) {
|
|
2316
|
-
/* c8 ignore next */
|
|
2317
|
-
Log.error("Unable to parse solution answer for expression", perseusCore.Errors.InvalidInput, {
|
|
2318
|
-
loggedMetadata: {
|
|
2319
|
-
rubric: JSON.stringify(rubric)
|
|
2320
|
-
}
|
|
2321
|
-
});
|
|
2322
|
-
return null;
|
|
2323
|
-
}
|
|
2324
|
-
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
|
|
2325
|
-
simplify: answer.simplify,
|
|
2326
|
-
form: answer.form
|
|
2327
|
-
}), strings);
|
|
2328
|
-
};
|
|
2329
|
-
|
|
2330
|
-
// Find the first answer form that matches the user's input and that
|
|
2331
|
-
// is considered correct. Also, track whether the input is
|
|
2332
|
-
// considered "empty" for all answer forms, and keep the validation
|
|
2333
|
-
// result for the first answer form for which the user's input was
|
|
2334
|
-
// considered "ungraded".
|
|
2335
|
-
// (Terminology reminder: the answer forms are provided by the
|
|
2336
|
-
// assessment items; they are not the user's input. Each one might
|
|
2337
|
-
// represent a correct answer, an incorrect one (if the exercise
|
|
2338
|
-
// creator has predicted certain common wrong answers and wants to
|
|
2339
|
-
// provide guidance via a message), or an ungraded one (same idea,
|
|
2340
|
-
// but without giving the user an incorrect mark for the question).
|
|
2341
|
-
let matchingAnswerForm;
|
|
2342
|
-
let matchMessage;
|
|
2343
|
-
let allEmpty = true;
|
|
2344
|
-
let firstUngradedResult;
|
|
2345
|
-
for (const answerForm of rubric.answerForms || []) {
|
|
2346
|
-
const validate = createValidator(answerForm);
|
|
2347
|
-
if (!validate) {
|
|
2348
|
-
continue;
|
|
2349
|
-
}
|
|
2350
|
-
const result = validate(userInput);
|
|
2351
|
-
|
|
2352
|
-
// Short-circuit as soon as the user's input matches some answer
|
|
2353
|
-
// (independently of whether the answer is correct)
|
|
2354
|
-
if (result.correct) {
|
|
2355
|
-
matchingAnswerForm = answerForm;
|
|
2356
|
-
matchMessage = result.message || "";
|
|
2357
|
-
break;
|
|
2358
|
-
}
|
|
2359
|
-
allEmpty = allEmpty && result.empty;
|
|
2360
|
-
// If this answer form is correct and the user's input is considered
|
|
2361
|
-
// "ungraded" for it, we'll want to keep the evaluation result for
|
|
2362
|
-
// later. If the user's input doesn't match any answer forms, we'll
|
|
2363
|
-
// show the message from this validation.
|
|
2364
|
-
if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
|
|
2365
|
-
firstUngradedResult = result;
|
|
2366
|
-
}
|
|
2367
|
-
}
|
|
2368
|
-
|
|
2369
|
-
// Now check to see if we matched any answer form at all, and if
|
|
2370
|
-
// we did, whether it's considered correct, incorrect, or ungraded
|
|
2371
|
-
if (!matchingAnswerForm) {
|
|
2372
|
-
if (firstUngradedResult) {
|
|
2373
|
-
// While we didn't directly match with any answer form, we
|
|
2374
|
-
// did at some point get an "ungraded" validation result,
|
|
2375
|
-
// which might indicate e.g. a mismatch in variable casing.
|
|
2376
|
-
// We'll return "invalid", which will let the user try again
|
|
2377
|
-
// with no penalty, and the hopefully helpful validation
|
|
2378
|
-
// message.
|
|
2379
|
-
return {
|
|
2380
|
-
type: "invalid",
|
|
2381
|
-
message: firstUngradedResult.message,
|
|
2382
|
-
suppressAlmostThere: firstUngradedResult.suppressAlmostThere
|
|
2383
|
-
};
|
|
2384
|
-
}
|
|
2385
|
-
if (allEmpty) {
|
|
2386
|
-
// If everything graded as empty, it's invalid.
|
|
2387
|
-
return {
|
|
2388
|
-
type: "invalid",
|
|
2389
|
-
message: null
|
|
2390
|
-
};
|
|
2391
|
-
}
|
|
2392
|
-
// We fell through all the possibilities and we're not empty,
|
|
2393
|
-
// so the answer is considered incorrect.
|
|
2394
|
-
return {
|
|
2395
|
-
type: "points",
|
|
2396
|
-
earned: 0,
|
|
2397
|
-
total: 1
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
if (matchingAnswerForm.considered === "ungraded") {
|
|
2401
|
-
// We matched an ungraded answer form - return "invalid", which
|
|
2402
|
-
// will let the user try again with no penalty
|
|
2403
|
-
const apiResult = onInputError(null,
|
|
2404
|
-
// Reserved for some widget identifier
|
|
2405
|
-
userInput, matchMessage);
|
|
2406
|
-
return {
|
|
2407
|
-
type: "invalid",
|
|
2408
|
-
message: apiResult === false ? null : matchMessage
|
|
2409
|
-
};
|
|
2410
|
-
}
|
|
2411
|
-
// We matched a graded answer form, so we can now tell the user
|
|
2412
|
-
// whether their input was correct or incorrect, and hand out
|
|
2413
|
-
// points accordingly
|
|
2414
|
-
// TODO(eater): Seems silly to translate result to this
|
|
2415
|
-
// invalid/points thing and immediately translate it back in
|
|
2416
|
-
// ItemRenderer.scoreInput()
|
|
2417
|
-
return {
|
|
2418
|
-
type: "points",
|
|
2419
|
-
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
2420
|
-
total: 1,
|
|
2421
|
-
message: matchMessage
|
|
2422
|
-
};
|
|
2429
|
+
return expressionValidator(userInput, rubric, onInputError, strings, locale);
|
|
2423
2430
|
}
|
|
2424
2431
|
static getUserInputFromProps(props) {
|
|
2425
2432
|
return normalizeTex(props.value);
|
|
@@ -2506,7 +2513,7 @@ class Expression extends React__namespace.Component {
|
|
|
2506
2513
|
scoring,
|
|
2507
2514
|
...rubric
|
|
2508
2515
|
} = _ref;
|
|
2509
|
-
const score =
|
|
2516
|
+
const score = expressionValidator(this.getUserInput(), rubric, onInputError || function () {}, this.context.strings, this.context.locale);
|
|
2510
2517
|
|
|
2511
2518
|
// "scoring" is a flag that indicates when we are checking answers.
|
|
2512
2519
|
// otherwise, we may just be checking validity after changes.
|
|
@@ -2780,10 +2787,10 @@ const ExpressionWithDependencies = /*#__PURE__*/React__namespace.forwardRef((pro
|
|
|
2780
2787
|
|
|
2781
2788
|
// HACK: Propogate "static" methods onto our wrapper component.
|
|
2782
2789
|
// In the future we should adjust client apps to not depend on these static
|
|
2783
|
-
// methods and instead adjust
|
|
2790
|
+
// methods and instead adjust Perseus to provide these facilities through
|
|
2784
2791
|
// instance methods on our Renderers.
|
|
2785
2792
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
2786
|
-
ExpressionWithDependencies.validate =
|
|
2793
|
+
ExpressionWithDependencies.validate = expressionValidator;
|
|
2787
2794
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
2788
2795
|
ExpressionWithDependencies.getUserInputFromProps = Expression.getUserInputFromProps;
|
|
2789
2796
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
@@ -19812,9 +19819,7 @@ const isIdPathPrefix = function (prefixArray, wholeArray) {
|
|
|
19812
19819
|
|
|
19813
19820
|
// The return type for getUserInput. Widgets have full control of what is
|
|
19814
19821
|
// returned so it's not easily typed (some widgets return a scalar (string),
|
|
19815
|
-
// some return a custom-built object
|
|
19816
|
-
// `WidgetJsonifyDeprecated` which returns an object containing widget props
|
|
19817
|
-
// (filtered by deprecated keys)).
|
|
19822
|
+
// some return a custom-built object
|
|
19818
19823
|
/**
|
|
19819
19824
|
* The Widget type represents the common API that the Renderer uses to interact
|
|
19820
19825
|
* with all widgets. All widgets must implement the methods in this API, unless
|
|
@@ -22254,6 +22259,130 @@ const answerFormButtons = [{
|
|
|
22254
22259
|
value: "pi",
|
|
22255
22260
|
content: "\u03C0"
|
|
22256
22261
|
}];
|
|
22262
|
+
|
|
22263
|
+
// This function checks if the user inputted a percent value, parsing
|
|
22264
|
+
// it as a number (and maybe scaling) so that it can be graded.
|
|
22265
|
+
// NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
|
|
22266
|
+
// can accept several input forms with or without "%", the decision
|
|
22267
|
+
// to parse based on the presence of "%" in the input, is so that we
|
|
22268
|
+
// don't accidently scale the user typed value before grading, CP-930.
|
|
22269
|
+
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
22270
|
+
// If the input value is not a string ending with "%", then there's
|
|
22271
|
+
// nothing more to do. The value will be graded as inputted by user.
|
|
22272
|
+
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
22273
|
+
return inputValue;
|
|
22274
|
+
}
|
|
22275
|
+
const value = parseFloat(inputValue.slice(0, -1));
|
|
22276
|
+
// If the input value stripped of the "%" cannot be parsed as a
|
|
22277
|
+
// number (the slice is not really necessary for parseFloat to work
|
|
22278
|
+
// if the string starts with a number) then return the original
|
|
22279
|
+
// input for grading.
|
|
22280
|
+
if (isNaN(value)) {
|
|
22281
|
+
return inputValue;
|
|
22282
|
+
}
|
|
22283
|
+
|
|
22284
|
+
// Next, if all correct answers are in the range of |0,1| then we
|
|
22285
|
+
// scale the user typed value. We assume this is the correct thing
|
|
22286
|
+
// to do since the input value ends with "%".
|
|
22287
|
+
if (normalizedAnswerExpected) {
|
|
22288
|
+
return value / 100;
|
|
22289
|
+
}
|
|
22290
|
+
|
|
22291
|
+
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
22292
|
+
return value;
|
|
22293
|
+
}
|
|
22294
|
+
function numericInputValidator(userInput, rubric, strings) {
|
|
22295
|
+
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
22296
|
+
// Don't default to validating the answer as a pi answer
|
|
22297
|
+
// if answerForm isn't set on the answer
|
|
22298
|
+
// https://khanacademy.atlassian.net/browse/LC-691
|
|
22299
|
+
.filter(e => e !== "pi");
|
|
22300
|
+
const createValidator = answer => {
|
|
22301
|
+
const stringAnswer = `${answer.value}`;
|
|
22302
|
+
|
|
22303
|
+
// Always validate against the provided answer forms (pi, decimal, etc.)
|
|
22304
|
+
const validatorForms = [...(answer.answerForms ?? [])];
|
|
22305
|
+
|
|
22306
|
+
// When an answer is set to strict, we validate using ONLY
|
|
22307
|
+
// the provided answerForms. If strict is false, or if there
|
|
22308
|
+
// were no provided answer forms, we will include all
|
|
22309
|
+
// of the default answer forms in our validator.
|
|
22310
|
+
if (!answer.strict || validatorForms.length === 0) {
|
|
22311
|
+
validatorForms.push(...defaultAnswerForms);
|
|
22312
|
+
}
|
|
22313
|
+
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
22314
|
+
message: answer.message,
|
|
22315
|
+
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
22316
|
+
inexact: true,
|
|
22317
|
+
// TODO(merlob) backfill / delete
|
|
22318
|
+
maxError: answer.maxError,
|
|
22319
|
+
forms: validatorForms
|
|
22320
|
+
}, strings);
|
|
22321
|
+
};
|
|
22322
|
+
|
|
22323
|
+
// We may have received TeX; try to parse it before grading.
|
|
22324
|
+
// If `currentValue` is not TeX, this should be a no-op.
|
|
22325
|
+
const currentValue = ParseTex(userInput.currentValue);
|
|
22326
|
+
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22327
|
+
const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1);
|
|
22328
|
+
|
|
22329
|
+
// Look through all correct answers for one that matches either
|
|
22330
|
+
// precisely or approximately and return the appropriate message:
|
|
22331
|
+
// - if precise, return the message that the answer came with
|
|
22332
|
+
// - if it needs to be simplified, etc., show that message
|
|
22333
|
+
let result = correctAnswers.map(answer => {
|
|
22334
|
+
// The coefficient is an attribute of the widget
|
|
22335
|
+
let localValue = currentValue;
|
|
22336
|
+
if (rubric.coefficient) {
|
|
22337
|
+
if (!localValue) {
|
|
22338
|
+
localValue = 1;
|
|
22339
|
+
} else if (localValue === "-") {
|
|
22340
|
+
localValue = -1;
|
|
22341
|
+
}
|
|
22342
|
+
}
|
|
22343
|
+
const validate = createValidator(answer);
|
|
22344
|
+
return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
22345
|
+
}).find(match => match.correct || match.empty);
|
|
22346
|
+
if (!result) {
|
|
22347
|
+
// Otherwise, if the guess is not correct
|
|
22348
|
+
const otherAnswers = [].concat(
|
|
22349
|
+
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
22350
|
+
rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong"));
|
|
22351
|
+
|
|
22352
|
+
// Look through all other answers and if one matches either
|
|
22353
|
+
// precisely or approximately return the answer's message
|
|
22354
|
+
const match = otherAnswers.find(answer => {
|
|
22355
|
+
const validate = createValidator(answer);
|
|
22356
|
+
return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
|
|
22357
|
+
});
|
|
22358
|
+
result = {
|
|
22359
|
+
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22360
|
+
empty: match ? match.status === "ungraded" : false,
|
|
22361
|
+
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22362
|
+
correct: match ? match.status === "correct" : false,
|
|
22363
|
+
// @ts-expect-error - TS2339 - Property 'message' does not exist on type 'never'.
|
|
22364
|
+
message: match ? match.message : null,
|
|
22365
|
+
guess: currentValue
|
|
22366
|
+
};
|
|
22367
|
+
}
|
|
22368
|
+
|
|
22369
|
+
// TODO(eater): Seems silly to translate result to this
|
|
22370
|
+
// invalid/points thing and immediately translate it
|
|
22371
|
+
// back in ItemRenderer.scoreInput()
|
|
22372
|
+
if (result.empty) {
|
|
22373
|
+
return {
|
|
22374
|
+
type: "invalid",
|
|
22375
|
+
message: result.message
|
|
22376
|
+
};
|
|
22377
|
+
}
|
|
22378
|
+
return {
|
|
22379
|
+
type: "points",
|
|
22380
|
+
earned: result.correct ? 1 : 0,
|
|
22381
|
+
total: 1,
|
|
22382
|
+
message: result.message
|
|
22383
|
+
};
|
|
22384
|
+
}
|
|
22385
|
+
|
|
22257
22386
|
const formExamples = {
|
|
22258
22387
|
integer: (form, strings) => strings.integerExample,
|
|
22259
22388
|
proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
|
|
@@ -22309,97 +22438,7 @@ class NumericInput extends React__namespace.Component {
|
|
|
22309
22438
|
return answerStrings[0];
|
|
22310
22439
|
}
|
|
22311
22440
|
static validate(userInput, rubric, strings) {
|
|
22312
|
-
|
|
22313
|
-
// Don't default to validating the answer as a pi answer
|
|
22314
|
-
// if answerForm isn't set on the answer
|
|
22315
|
-
// https://khanacademy.atlassian.net/browse/LC-691
|
|
22316
|
-
.filter(e => e !== "pi");
|
|
22317
|
-
const createValidator = answer => {
|
|
22318
|
-
const stringAnswer = `${answer.value}`;
|
|
22319
|
-
|
|
22320
|
-
// Always validate against the provided answer forms (pi, decimal, etc.)
|
|
22321
|
-
const validatorForms = [...(answer.answerForms ?? [])];
|
|
22322
|
-
|
|
22323
|
-
// When an answer is set to strict, we validate using ONLY
|
|
22324
|
-
// the provided answerForms. If strict is false, or if there
|
|
22325
|
-
// were no provided answer forms, we will include all
|
|
22326
|
-
// of the default answer forms in our validator.
|
|
22327
|
-
if (!answer.strict || validatorForms.length === 0) {
|
|
22328
|
-
validatorForms.push(...defaultAnswerForms);
|
|
22329
|
-
}
|
|
22330
|
-
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
22331
|
-
message: answer.message,
|
|
22332
|
-
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
22333
|
-
inexact: true,
|
|
22334
|
-
// TODO(merlob) backfill / delete
|
|
22335
|
-
maxError: answer.maxError,
|
|
22336
|
-
forms: validatorForms
|
|
22337
|
-
}, strings);
|
|
22338
|
-
};
|
|
22339
|
-
|
|
22340
|
-
// We may have received TeX; try to parse it before grading.
|
|
22341
|
-
// If `currentValue` is not TeX, this should be a no-op.
|
|
22342
|
-
const currentValue = ParseTex(userInput.currentValue);
|
|
22343
|
-
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22344
|
-
const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1);
|
|
22345
|
-
|
|
22346
|
-
// Look through all correct answers for one that matches either
|
|
22347
|
-
// precisely or approximately and return the appropriate message:
|
|
22348
|
-
// - if precise, return the message that the answer came with
|
|
22349
|
-
// - if it needs to be simplified, etc., show that message
|
|
22350
|
-
let result = correctAnswers.map(answer => {
|
|
22351
|
-
// The coefficient is an attribute of the widget
|
|
22352
|
-
let localValue = currentValue;
|
|
22353
|
-
if (rubric.coefficient) {
|
|
22354
|
-
if (!localValue) {
|
|
22355
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'string'.
|
|
22356
|
-
localValue = 1;
|
|
22357
|
-
} else if (localValue === "-") {
|
|
22358
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'string'.
|
|
22359
|
-
localValue = -1;
|
|
22360
|
-
}
|
|
22361
|
-
}
|
|
22362
|
-
const validate = createValidator(answer);
|
|
22363
|
-
return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
22364
|
-
}).find(match => match.correct || match.empty);
|
|
22365
|
-
if (!result) {
|
|
22366
|
-
// Otherwise, if the guess is not correct
|
|
22367
|
-
const otherAnswers = [].concat(
|
|
22368
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
22369
|
-
rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong"));
|
|
22370
|
-
|
|
22371
|
-
// Look through all other answers and if one matches either
|
|
22372
|
-
// precisely or approximately return the answer's message
|
|
22373
|
-
const match = otherAnswers.find(answer => {
|
|
22374
|
-
const validate = createValidator(answer);
|
|
22375
|
-
return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
|
|
22376
|
-
});
|
|
22377
|
-
result = {
|
|
22378
|
-
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22379
|
-
empty: match ? match.status === "ungraded" : false,
|
|
22380
|
-
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22381
|
-
correct: match ? match.status === "correct" : false,
|
|
22382
|
-
// @ts-expect-error - TS2339 - Property 'message' does not exist on type 'never'.
|
|
22383
|
-
message: match ? match.message : null,
|
|
22384
|
-
guess: currentValue
|
|
22385
|
-
};
|
|
22386
|
-
}
|
|
22387
|
-
|
|
22388
|
-
// TODO(eater): Seems silly to translate result to this
|
|
22389
|
-
// invalid/points thing and immediately translate it
|
|
22390
|
-
// back in ItemRenderer.scoreInput()
|
|
22391
|
-
if (result.empty) {
|
|
22392
|
-
return {
|
|
22393
|
-
type: "invalid",
|
|
22394
|
-
message: result.message
|
|
22395
|
-
};
|
|
22396
|
-
}
|
|
22397
|
-
return {
|
|
22398
|
-
type: "points",
|
|
22399
|
-
earned: result.correct ? 1 : 0,
|
|
22400
|
-
total: 1,
|
|
22401
|
-
message: result.message
|
|
22402
|
-
};
|
|
22441
|
+
return numericInputValidator(userInput, rubric, strings);
|
|
22403
22442
|
}
|
|
22404
22443
|
state = {
|
|
22405
22444
|
// keeps track of the other set of values when switching
|
|
@@ -22434,7 +22473,7 @@ class NumericInput extends React__namespace.Component {
|
|
|
22434
22473
|
return !noFormsAccepted && !allFormsAccepted;
|
|
22435
22474
|
};
|
|
22436
22475
|
simpleValidate = rubric => {
|
|
22437
|
-
return
|
|
22476
|
+
return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
|
|
22438
22477
|
};
|
|
22439
22478
|
focus = () => {
|
|
22440
22479
|
this.inputRef?.focus();
|
|
@@ -22565,37 +22604,6 @@ const unionAnswerForms = function (answerFormsList) {
|
|
|
22565
22604
|
return formExampleKeys.indexOf(form.name);
|
|
22566
22605
|
});
|
|
22567
22606
|
};
|
|
22568
|
-
// This function checks if the user inputted a percent value, parsing
|
|
22569
|
-
// it as a number (and maybe scaling) so that it can be graded.
|
|
22570
|
-
// NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
|
|
22571
|
-
// can accept several input forms with or without "%", the decision
|
|
22572
|
-
// to parse based on the presence of "%" in the input, is so that we
|
|
22573
|
-
// don't accidently scale the user typed value before grading, CP-930.
|
|
22574
|
-
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
22575
|
-
// If the input value is not a string ending with "%", then there's
|
|
22576
|
-
// nothing more to do. The value will be graded as inputted by user.
|
|
22577
|
-
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
22578
|
-
return inputValue;
|
|
22579
|
-
}
|
|
22580
|
-
const value = parseFloat(inputValue.slice(0, -1));
|
|
22581
|
-
// If the input value stripped of the "%" cannot be parsed as a
|
|
22582
|
-
// number (the slice is not really necessary for parseFloat to work
|
|
22583
|
-
// if the string starts with a number) then return the original
|
|
22584
|
-
// input for grading.
|
|
22585
|
-
if (isNaN(value)) {
|
|
22586
|
-
return inputValue;
|
|
22587
|
-
}
|
|
22588
|
-
|
|
22589
|
-
// Next, if all correct answers are in the range of |0,1| then we
|
|
22590
|
-
// scale the user typed value. We assume this is the correct thing
|
|
22591
|
-
// to do since the input value ends with "%".
|
|
22592
|
-
if (normalizedAnswerExpected) {
|
|
22593
|
-
return value / 100;
|
|
22594
|
-
}
|
|
22595
|
-
|
|
22596
|
-
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
22597
|
-
return value;
|
|
22598
|
-
}
|
|
22599
22607
|
const propsTransform = function (widgetOptions) {
|
|
22600
22608
|
const rendererProps = ___default["default"].extend(___default["default"].omit(widgetOptions, "answers"), {
|
|
22601
22609
|
answerForms: unionAnswerForms(
|
|
@@ -22623,6 +22631,23 @@ var NumericInput$1 = {
|
|
|
22623
22631
|
isLintable: true
|
|
22624
22632
|
};
|
|
22625
22633
|
|
|
22634
|
+
/**
|
|
22635
|
+
* Several widgets don't have "right"/"wrong" validation logic,
|
|
22636
|
+
* so this just says to move on past those widgets
|
|
22637
|
+
*
|
|
22638
|
+
* TODO: widgets that use this probably shouldn't have any
|
|
22639
|
+
* validation logic and the thing validating an exercise
|
|
22640
|
+
* should just know to skip these
|
|
22641
|
+
*/
|
|
22642
|
+
function noopValidator() {
|
|
22643
|
+
return {
|
|
22644
|
+
type: "points",
|
|
22645
|
+
earned: 0,
|
|
22646
|
+
total: 0,
|
|
22647
|
+
message: null
|
|
22648
|
+
};
|
|
22649
|
+
}
|
|
22650
|
+
|
|
22626
22651
|
const EN_DASH$1 = "\u2013";
|
|
22627
22652
|
class PassageRef extends React__namespace.Component {
|
|
22628
22653
|
static contextType = PerseusI18nContext;
|
|
@@ -22641,12 +22666,7 @@ class PassageRef extends React__namespace.Component {
|
|
|
22641
22666
|
content: null
|
|
22642
22667
|
};
|
|
22643
22668
|
static validate(userInput, rubric) {
|
|
22644
|
-
return
|
|
22645
|
-
type: "points",
|
|
22646
|
-
earned: 0,
|
|
22647
|
-
total: 0,
|
|
22648
|
-
message: null
|
|
22649
|
-
};
|
|
22669
|
+
return noopValidator();
|
|
22650
22670
|
}
|
|
22651
22671
|
componentDidMount() {
|
|
22652
22672
|
// TODO(scottgrant): This is a hack to remove the deprecated call to
|
|
@@ -22747,6 +22767,7 @@ class PassageRef extends React__namespace.Component {
|
|
|
22747
22767
|
var PassageRef$1 = {
|
|
22748
22768
|
name: "passage-ref",
|
|
22749
22769
|
displayName: "PassageRef (SAT only)",
|
|
22770
|
+
hidden: true,
|
|
22750
22771
|
defaultAlignment: "inline",
|
|
22751
22772
|
widget: PassageRef,
|
|
22752
22773
|
transform: widgetOptions => ({
|
|
@@ -24655,23 +24676,6 @@ var Radio = {
|
|
|
24655
24676
|
/* eslint-disable import/no-named-as-default */
|
|
24656
24677
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
24657
24678
|
|
|
24658
|
-
/* Free implementation of getUserInput. This should be used sparingly, since it
|
|
24659
|
-
* just returns all the widget's props rather than picking out those which were
|
|
24660
|
-
* input by the user.
|
|
24661
|
-
*/
|
|
24662
|
-
const WidgetJsonifyDeprecated = {
|
|
24663
|
-
getUserInput: function () {
|
|
24664
|
-
// Omit props that get passed to all widgets
|
|
24665
|
-
// @ts-expect-error - TS2339 - Property 'props' does not exist on type '{ readonly getUserInput: () => any; readonly getUserInputFromProps: (props: any) => any; }'.
|
|
24666
|
-
return ___default["default"].omit(this.props, denylist);
|
|
24667
|
-
},
|
|
24668
|
-
// Static version of `WidgetJsonifyDeprecated.getUserInput`
|
|
24669
|
-
getUserInputFromProps: function (props) {
|
|
24670
|
-
// Omit props that get passed to all widgets
|
|
24671
|
-
return ___default["default"].omit(props, denylist);
|
|
24672
|
-
}
|
|
24673
|
-
};
|
|
24674
|
-
|
|
24675
24679
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
24676
24680
|
class Categorizer extends React__namespace.Component {
|
|
24677
24681
|
static contextType = PerseusI18nContext;
|
|
@@ -24709,7 +24713,9 @@ class Categorizer extends React__namespace.Component {
|
|
|
24709
24713
|
};
|
|
24710
24714
|
}
|
|
24711
24715
|
static getUserInputFromProps(props) {
|
|
24712
|
-
return
|
|
24716
|
+
return {
|
|
24717
|
+
values: props.values
|
|
24718
|
+
};
|
|
24713
24719
|
}
|
|
24714
24720
|
change = (() => {
|
|
24715
24721
|
var _this = this;
|
|
@@ -24864,6 +24870,7 @@ const styles$s = aphrodite.StyleSheet.create({
|
|
|
24864
24870
|
var Categorizer$1 = {
|
|
24865
24871
|
name: "categorizer",
|
|
24866
24872
|
displayName: "Categorizer",
|
|
24873
|
+
hidden: true,
|
|
24867
24874
|
widget: Categorizer,
|
|
24868
24875
|
transform: widgetOptions => {
|
|
24869
24876
|
return ___default["default"].pick(widgetOptions, "items", "categories", "randomizeItems");
|
|
@@ -24944,18 +24951,12 @@ function getUrlFromProgramID$1(programID) {
|
|
|
24944
24951
|
/* This renders the scratchpad in an iframe and handles validation via
|
|
24945
24952
|
* window.postMessage */
|
|
24946
24953
|
class CSProgram extends React__namespace.Component {
|
|
24947
|
-
static
|
|
24948
|
-
|
|
24949
|
-
|
|
24950
|
-
|
|
24951
|
-
|
|
24952
|
-
|
|
24953
|
-
// eslint-disable-next-line react/forbid-prop-types
|
|
24954
|
-
settings: PropTypes__default["default"].array,
|
|
24955
|
-
showEditor: PropTypes__default["default"].bool,
|
|
24956
|
-
showButtons: PropTypes__default["default"].bool,
|
|
24957
|
-
status: PropTypes__default["default"].oneOf(["incomplete", "incorrect", "correct"]),
|
|
24958
|
-
message: PropTypes__default["default"].string
|
|
24954
|
+
static defaultProps = {
|
|
24955
|
+
showEditor: false,
|
|
24956
|
+
showButtons: false,
|
|
24957
|
+
status: "incomplete",
|
|
24958
|
+
// optional message
|
|
24959
|
+
message: null
|
|
24959
24960
|
};
|
|
24960
24961
|
|
|
24961
24962
|
// The widget's grading function
|
|
@@ -24983,13 +24984,6 @@ class CSProgram extends React__namespace.Component {
|
|
|
24983
24984
|
message: "Keep going, you're not there yet!"
|
|
24984
24985
|
};
|
|
24985
24986
|
}
|
|
24986
|
-
static defaultProps = {
|
|
24987
|
-
showEditor: false,
|
|
24988
|
-
showButtons: false,
|
|
24989
|
-
status: "incomplete",
|
|
24990
|
-
// optional message
|
|
24991
|
-
message: null
|
|
24992
|
-
};
|
|
24993
24987
|
componentDidMount() {
|
|
24994
24988
|
$__default["default"](window).on("message", this.handleMessageEvent);
|
|
24995
24989
|
}
|
|
@@ -25025,12 +25019,12 @@ class CSProgram extends React__namespace.Component {
|
|
|
25025
25019
|
return change.apply(_this, args);
|
|
25026
25020
|
};
|
|
25027
25021
|
})();
|
|
25028
|
-
simpleValidate
|
|
25022
|
+
simpleValidate(rubric) {
|
|
25029
25023
|
return CSProgram.validate({
|
|
25030
25024
|
status: this.props.status,
|
|
25031
25025
|
message: this.props.message
|
|
25032
25026
|
}, rubric);
|
|
25033
|
-
}
|
|
25027
|
+
}
|
|
25034
25028
|
render() {
|
|
25035
25029
|
if (!this.props.programID) {
|
|
25036
25030
|
return /*#__PURE__*/React__namespace.createElement("div", null);
|
|
@@ -25115,12 +25109,7 @@ class Definition extends React__namespace.Component {
|
|
|
25115
25109
|
definition: "definition goes here"
|
|
25116
25110
|
};
|
|
25117
25111
|
static validate(userInput, rubric) {
|
|
25118
|
-
return
|
|
25119
|
-
type: "points",
|
|
25120
|
-
earned: 0,
|
|
25121
|
-
total: 0,
|
|
25122
|
-
message: null
|
|
25123
|
-
};
|
|
25112
|
+
return noopValidator();
|
|
25124
25113
|
}
|
|
25125
25114
|
getUserInput = () => {
|
|
25126
25115
|
return {};
|
|
@@ -25226,6 +25215,23 @@ var DeprecatedStandin$1 = {
|
|
|
25226
25215
|
hidden: true
|
|
25227
25216
|
};
|
|
25228
25217
|
|
|
25218
|
+
function dropdownValidator(userInput, rubric) {
|
|
25219
|
+
const selected = userInput.value;
|
|
25220
|
+
if (selected === 0) {
|
|
25221
|
+
return {
|
|
25222
|
+
type: "invalid",
|
|
25223
|
+
message: null
|
|
25224
|
+
};
|
|
25225
|
+
}
|
|
25226
|
+
const correct = rubric.choices[selected - 1].correct;
|
|
25227
|
+
return {
|
|
25228
|
+
type: "points",
|
|
25229
|
+
earned: correct ? 1 : 0,
|
|
25230
|
+
total: 1,
|
|
25231
|
+
message: null
|
|
25232
|
+
};
|
|
25233
|
+
}
|
|
25234
|
+
|
|
25229
25235
|
class Dropdown extends React__namespace.Component {
|
|
25230
25236
|
static defaultProps = {
|
|
25231
25237
|
choices: [],
|
|
@@ -25234,20 +25240,7 @@ class Dropdown extends React__namespace.Component {
|
|
|
25234
25240
|
apiOptions: ApiOptions.defaults
|
|
25235
25241
|
};
|
|
25236
25242
|
static validate(userInput, rubric) {
|
|
25237
|
-
|
|
25238
|
-
if (selected === 0) {
|
|
25239
|
-
return {
|
|
25240
|
-
type: "invalid",
|
|
25241
|
-
message: null
|
|
25242
|
-
};
|
|
25243
|
-
}
|
|
25244
|
-
const correct = rubric.choices[selected - 1].correct;
|
|
25245
|
-
return {
|
|
25246
|
-
type: "points",
|
|
25247
|
-
earned: correct ? 1 : 0,
|
|
25248
|
-
total: 1,
|
|
25249
|
-
message: null
|
|
25250
|
-
};
|
|
25243
|
+
return dropdownValidator(userInput, rubric);
|
|
25251
25244
|
}
|
|
25252
25245
|
focus = () => {
|
|
25253
25246
|
// TODO(LP-10797): This focus() call doesn't do anything because our
|
|
@@ -25272,7 +25265,7 @@ class Dropdown extends React__namespace.Component {
|
|
|
25272
25265
|
};
|
|
25273
25266
|
};
|
|
25274
25267
|
simpleValidate = rubric => {
|
|
25275
|
-
return
|
|
25268
|
+
return dropdownValidator(this.getUserInput(), rubric);
|
|
25276
25269
|
};
|
|
25277
25270
|
render() {
|
|
25278
25271
|
const children = [/*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
|
|
@@ -25339,12 +25332,7 @@ class Explanation extends React__namespace.Component {
|
|
|
25339
25332
|
linterContext: PerseusLinter.linterContextDefault
|
|
25340
25333
|
};
|
|
25341
25334
|
static validate(userInput, rubric) {
|
|
25342
|
-
return
|
|
25343
|
-
type: "points",
|
|
25344
|
-
earned: 0,
|
|
25345
|
-
total: 0,
|
|
25346
|
-
message: null
|
|
25347
|
-
};
|
|
25335
|
+
return noopValidator();
|
|
25348
25336
|
}
|
|
25349
25337
|
componentDidMount() {
|
|
25350
25338
|
this._mounted = true;
|
|
@@ -27208,6 +27196,7 @@ const staticTransform$4 = editorProps => {
|
|
|
27208
27196
|
var Grapher$1 = {
|
|
27209
27197
|
name: "grapher",
|
|
27210
27198
|
displayName: "Grapher",
|
|
27199
|
+
hidden: true,
|
|
27211
27200
|
widget: Grapher,
|
|
27212
27201
|
transform: propTransform$2,
|
|
27213
27202
|
staticTransform: staticTransform$4
|
|
@@ -27355,28 +27344,23 @@ var Group$1 = {
|
|
|
27355
27344
|
displayName: "Group (SAT only)",
|
|
27356
27345
|
widget: Group,
|
|
27357
27346
|
traverseChildWidgets: traverseChildWidgets,
|
|
27358
|
-
hidden:
|
|
27347
|
+
hidden: true,
|
|
27359
27348
|
isLintable: true
|
|
27360
27349
|
};
|
|
27361
27350
|
|
|
27362
|
-
|
|
27351
|
+
/**
|
|
27352
|
+
* This is an iframe widget. It is used for rendering an iframe that
|
|
27353
|
+
* then communicates its state via window.postMessage
|
|
27354
|
+
* This is useful for embedding arbitrary visualizations/simulations with
|
|
27355
|
+
* completed conditions, such as the mazes and games in Algorithms.
|
|
27356
|
+
* It's particularly well suited for embedding our ProcessingJS programs,
|
|
27357
|
+
* but could also be used for embedding viz's hosted elsewhere.
|
|
27358
|
+
*/
|
|
27363
27359
|
const {
|
|
27364
27360
|
updateQueryString
|
|
27365
27361
|
} = Util;
|
|
27366
|
-
|
|
27367
27362
|
/* This renders the iframe and handles validation via window.postMessage */
|
|
27368
27363
|
class Iframe extends React__namespace.Component {
|
|
27369
|
-
static propTypes = {
|
|
27370
|
-
...propTypes,
|
|
27371
|
-
width: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number]),
|
|
27372
|
-
height: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number]),
|
|
27373
|
-
url: PropTypes__default["default"].string,
|
|
27374
|
-
settings: PropTypes__default["default"].array,
|
|
27375
|
-
status: PropTypes__default["default"].oneOf(["incomplete", "incorrect", "correct"]),
|
|
27376
|
-
message: PropTypes__default["default"].string,
|
|
27377
|
-
allowFullScreen: PropTypes__default["default"].bool,
|
|
27378
|
-
allowTopNavigation: PropTypes__default["default"].bool
|
|
27379
|
-
};
|
|
27380
27364
|
static defaultProps = {
|
|
27381
27365
|
status: "incomplete",
|
|
27382
27366
|
// optional message
|
|
@@ -27390,9 +27374,12 @@ class Iframe extends React__namespace.Component {
|
|
|
27390
27374
|
componentWillUnmount() {
|
|
27391
27375
|
$__default["default"](window).off("message", this.handleMessageEvent);
|
|
27392
27376
|
}
|
|
27393
|
-
getUserInput
|
|
27394
|
-
return
|
|
27395
|
-
|
|
27377
|
+
getUserInput() {
|
|
27378
|
+
return {
|
|
27379
|
+
status: this.props.status,
|
|
27380
|
+
message: this.props.message
|
|
27381
|
+
};
|
|
27382
|
+
}
|
|
27396
27383
|
handleMessageEvent = e => {
|
|
27397
27384
|
// We receive data from the iframe that contains {passed: true/false}
|
|
27398
27385
|
// and use that to set the status
|
|
@@ -27552,12 +27539,7 @@ class ImageWidget extends React__namespace.Component {
|
|
|
27552
27539
|
linterContext: PerseusLinter.linterContextDefault
|
|
27553
27540
|
};
|
|
27554
27541
|
static validate(userInput, rubric) {
|
|
27555
|
-
return
|
|
27556
|
-
type: "points",
|
|
27557
|
-
earned: 0,
|
|
27558
|
-
total: 0,
|
|
27559
|
-
message: null
|
|
27560
|
-
};
|
|
27542
|
+
return noopValidator();
|
|
27561
27543
|
}
|
|
27562
27544
|
change = (() => {
|
|
27563
27545
|
var _this = this;
|
|
@@ -27800,12 +27782,7 @@ class Interaction extends React__namespace.Component {
|
|
|
27800
27782
|
elements: []
|
|
27801
27783
|
};
|
|
27802
27784
|
static validate(state, rubric) {
|
|
27803
|
-
return
|
|
27804
|
-
type: "points",
|
|
27805
|
-
earned: 0,
|
|
27806
|
-
total: 0,
|
|
27807
|
-
message: null
|
|
27808
|
-
};
|
|
27785
|
+
return noopValidator();
|
|
27809
27786
|
}
|
|
27810
27787
|
state = {
|
|
27811
27788
|
variables: _getInitialVariables(this.props.elements),
|
|
@@ -29977,6 +29954,7 @@ function LockedLabel(props) {
|
|
|
29977
29954
|
|
|
29978
29955
|
function GraphLockedLabelsLayer(props) {
|
|
29979
29956
|
const {
|
|
29957
|
+
flags,
|
|
29980
29958
|
lockedFigures
|
|
29981
29959
|
} = props;
|
|
29982
29960
|
return lockedFigures.map((figure, i) => {
|
|
@@ -29985,9 +29963,13 @@ function GraphLockedLabelsLayer(props) {
|
|
|
29985
29963
|
key: `label-${i}`
|
|
29986
29964
|
}, figure));
|
|
29987
29965
|
}
|
|
29988
|
-
|
|
29989
|
-
|
|
29990
|
-
|
|
29966
|
+
if (flags?.["mafs"]?.["locked-point-labels"] && figure.type === "point") {
|
|
29967
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, {
|
|
29968
|
+
key: `point-${i}`
|
|
29969
|
+
}, figure.labels.map((label, j) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
|
|
29970
|
+
key: `locked-figure-${i}-label-${j}`
|
|
29971
|
+
}, label))));
|
|
29972
|
+
}
|
|
29991
29973
|
return null;
|
|
29992
29974
|
});
|
|
29993
29975
|
}
|
|
@@ -32324,7 +32306,8 @@ const MafsGraph = props => {
|
|
|
32324
32306
|
props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
|
|
32325
32307
|
lockedFigures: props.lockedFigures,
|
|
32326
32308
|
range: state.range
|
|
32327
|
-
}))), props.
|
|
32309
|
+
}))), props.flags?.["mafs"]?.["interactive-graph-locked-features-labels"] && props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
|
|
32310
|
+
flags: props.flags,
|
|
32328
32311
|
lockedFigures: props.lockedFigures
|
|
32329
32312
|
}), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
32330
32313
|
style: {
|
|
@@ -34960,7 +34943,7 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
34960
34943
|
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
34961
34944
|
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
34962
34945
|
return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends({}, this.props, {
|
|
34963
|
-
|
|
34946
|
+
flags: this.props.apiOptions?.flags,
|
|
34964
34947
|
ref: this.mafsRef,
|
|
34965
34948
|
gridStep: gridStep,
|
|
34966
34949
|
snapStep: snapStep,
|
|
@@ -35557,7 +35540,7 @@ function shouldUseMafs(mafsFlags, graph) {
|
|
|
35557
35540
|
const staticTransform$3 = ___default["default"].identity;
|
|
35558
35541
|
var InteractiveGraph$1 = {
|
|
35559
35542
|
name: "interactive-graph",
|
|
35560
|
-
displayName: "Interactive graph",
|
|
35543
|
+
displayName: "Interactive graph (Assessments only)",
|
|
35561
35544
|
widget: InteractiveGraph,
|
|
35562
35545
|
staticTransform: staticTransform$3
|
|
35563
35546
|
};
|
|
@@ -37511,11 +37494,6 @@ var LabelImage$1 = {
|
|
|
37511
37494
|
};
|
|
37512
37495
|
|
|
37513
37496
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
37514
|
-
let Layout = /*#__PURE__*/function (Layout) {
|
|
37515
|
-
Layout["HORIZONTAL"] = "horizontal";
|
|
37516
|
-
Layout["VERTICAL"] = "vertical";
|
|
37517
|
-
return Layout;
|
|
37518
|
-
}({});
|
|
37519
37497
|
var ItemState = /*#__PURE__*/function (ItemState) {
|
|
37520
37498
|
ItemState["STATIC"] = "static";
|
|
37521
37499
|
ItemState["DRAGGING"] = "dragging";
|
|
@@ -37542,7 +37520,7 @@ class Placeholder extends React__namespace.Component {
|
|
|
37542
37520
|
const {
|
|
37543
37521
|
layout
|
|
37544
37522
|
} = this.props;
|
|
37545
|
-
const className = aphrodite.css(styles$d.card, styles$d.placeholder, layout ===
|
|
37523
|
+
const className = aphrodite.css(styles$d.card, styles$d.placeholder, layout === "horizontal" && styles$d.horizontalCard);
|
|
37546
37524
|
const style = {
|
|
37547
37525
|
width: this.props.width,
|
|
37548
37526
|
height: this.props.height
|
|
@@ -37749,7 +37727,7 @@ class Draggable extends React__namespace.Component {
|
|
|
37749
37727
|
// We need to keep backwards compatbility with rules specified directly
|
|
37750
37728
|
// in CSS. Hence the hacky tacking on of manual classNames.
|
|
37751
37729
|
// See sortable.less for details.
|
|
37752
|
-
let className = aphrodite.css(styles$d.card, styles$d.draggable, layout ===
|
|
37730
|
+
let className = aphrodite.css(styles$d.card, styles$d.draggable, layout === "horizontal" && styles$d.horizontalCard, layout === "vertical" && styles$d.verticalCard, type === ItemState.DRAGGING && styles$d.dragging, type === ItemState.DISABLED && styles$d.disabled, !includePadding && styles$d.unpaddedCard) + " " + ClassNames.INTERACTIVE + " perseus-sortable-draggable";
|
|
37753
37731
|
if (!includePadding) {
|
|
37754
37732
|
className += " perseus-sortable-draggable-unpadded";
|
|
37755
37733
|
}
|
|
@@ -37789,7 +37767,7 @@ class Draggable extends React__namespace.Component {
|
|
|
37789
37767
|
}
|
|
37790
37768
|
class Sortable extends React__namespace.Component {
|
|
37791
37769
|
static defaultProps = {
|
|
37792
|
-
layout:
|
|
37770
|
+
layout: "horizontal",
|
|
37793
37771
|
padding: true,
|
|
37794
37772
|
disabled: false,
|
|
37795
37773
|
constraints: {},
|
|
@@ -37884,7 +37862,7 @@ class Sortable extends React__namespace.Component {
|
|
|
37884
37862
|
if (constraints?.width) {
|
|
37885
37863
|
// Items must be at least as wide as the specified constraint
|
|
37886
37864
|
syncWidth = ___default["default"].max(widths.concat(constraints.width));
|
|
37887
|
-
} else if (layout ===
|
|
37865
|
+
} else if (layout === "vertical") {
|
|
37888
37866
|
// Sync widths to get a clean column
|
|
37889
37867
|
syncWidth = ___default["default"].max(widths);
|
|
37890
37868
|
}
|
|
@@ -37892,7 +37870,7 @@ class Sortable extends React__namespace.Component {
|
|
|
37892
37870
|
if (constraints?.height) {
|
|
37893
37871
|
// Items must be at least as high as the specified constraint
|
|
37894
37872
|
syncHeight = ___default["default"].max(heights.concat(constraints.height));
|
|
37895
|
-
} else if (layout ===
|
|
37873
|
+
} else if (layout === "horizontal") {
|
|
37896
37874
|
// Sync widths to get a clean row
|
|
37897
37875
|
syncHeight = ___default["default"].max(heights);
|
|
37898
37876
|
}
|
|
@@ -37974,7 +37952,7 @@ class Sortable extends React__namespace.Component {
|
|
|
37974
37952
|
|
|
37975
37953
|
// @ts-expect-error - TS2551 - Property 'splice' does not exist on type 'readonly SortableItem[]'. Did you mean 'slice'?
|
|
37976
37954
|
items.splice(currentIndex, 1);
|
|
37977
|
-
if (this.props.layout ===
|
|
37955
|
+
if (this.props.layout === "horizontal") {
|
|
37978
37956
|
// @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'. | TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
|
|
37979
37957
|
const midWidth = $draggable.offset().left - $sortable.offset().left;
|
|
37980
37958
|
let sumWidth = 0;
|
|
@@ -38088,19 +38066,19 @@ class Sortable extends React__namespace.Component {
|
|
|
38088
38066
|
// We need to keep backwards compatbility with rules specified directly
|
|
38089
38067
|
// in CSS. See sortable.less for details.
|
|
38090
38068
|
const className = aphrodite.css(styles$d.sortable) + " perseus-sortable";
|
|
38091
|
-
const syncWidth = this.props.constraints?.width || layout ===
|
|
38092
|
-
const syncHeight = this.props.constraints?.height || layout ===
|
|
38069
|
+
const syncWidth = this.props.constraints?.width || layout === "vertical";
|
|
38070
|
+
const syncHeight = this.props.constraints?.height || layout === "horizontal";
|
|
38093
38071
|
___default["default"].each(this.state.items, function (item, i, items) {
|
|
38094
38072
|
const isLast = i === items.length - 1;
|
|
38095
38073
|
const isStatic = item.state === ItemState.STATIC || item.state === ItemState.DISABLED;
|
|
38096
38074
|
let margin;
|
|
38097
38075
|
|
|
38098
38076
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38099
|
-
if (this.props.layout ===
|
|
38077
|
+
if (this.props.layout === "horizontal") {
|
|
38100
38078
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38101
38079
|
margin = "0 " + this.props.margin + "px 0 0"; // right
|
|
38102
38080
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38103
|
-
} else if (this.props.layout ===
|
|
38081
|
+
} else if (this.props.layout === "vertical") {
|
|
38104
38082
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38105
38083
|
margin = "0 0 " + this.props.margin + "px 0"; // bottom
|
|
38106
38084
|
}
|
|
@@ -38222,7 +38200,6 @@ const styles$d = aphrodite.StyleSheet.create({
|
|
|
38222
38200
|
}
|
|
38223
38201
|
});
|
|
38224
38202
|
|
|
38225
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
38226
38203
|
const {
|
|
38227
38204
|
shuffle: shuffle$1,
|
|
38228
38205
|
seededRNG
|
|
@@ -38230,18 +38207,6 @@ const {
|
|
|
38230
38207
|
const HACKY_CSS_CLASSNAME = "perseus-widget-matcher";
|
|
38231
38208
|
class Matcher extends React__namespace.Component {
|
|
38232
38209
|
static contextType = PerseusI18nContext;
|
|
38233
|
-
static propTypes = {
|
|
38234
|
-
apiOptions: ApiOptions.propTypes,
|
|
38235
|
-
labels: PropTypes__default["default"].array,
|
|
38236
|
-
left: PropTypes__default["default"].array,
|
|
38237
|
-
onChange: PropTypes__default["default"].func,
|
|
38238
|
-
orderMatters: PropTypes__default["default"].bool,
|
|
38239
|
-
padding: PropTypes__default["default"].bool,
|
|
38240
|
-
problemNum: PropTypes__default["default"].number,
|
|
38241
|
-
right: PropTypes__default["default"].array,
|
|
38242
|
-
trackInteraction: PropTypes__default["default"].func.isRequired,
|
|
38243
|
-
linterContext: PerseusLinter.linterContextProps
|
|
38244
|
-
};
|
|
38245
38210
|
static defaultProps = {
|
|
38246
38211
|
left: [],
|
|
38247
38212
|
right: [],
|
|
@@ -38378,7 +38343,7 @@ class Matcher extends React__namespace.Component {
|
|
|
38378
38343
|
className: aphrodite.css(styles$c.column)
|
|
38379
38344
|
}, /*#__PURE__*/React__namespace.createElement(Sortable, {
|
|
38380
38345
|
options: left,
|
|
38381
|
-
layout:
|
|
38346
|
+
layout: "vertical",
|
|
38382
38347
|
padding: this.props.padding,
|
|
38383
38348
|
disabled: !this.props.orderMatters,
|
|
38384
38349
|
constraints: constraints,
|
|
@@ -38391,11 +38356,9 @@ class Matcher extends React__namespace.Component {
|
|
|
38391
38356
|
ref: "left"
|
|
38392
38357
|
})), /*#__PURE__*/React__namespace.createElement("td", {
|
|
38393
38358
|
className: aphrodite.css(styles$c.column, styles$c.columnRight)
|
|
38394
|
-
}, /*#__PURE__*/React__namespace.createElement(Sortable
|
|
38395
|
-
// @ts-expect-error - TS2322 - Type 'readonly unknown[]' is not assignable to type 'readonly string[]'.
|
|
38396
|
-
, {
|
|
38359
|
+
}, /*#__PURE__*/React__namespace.createElement(Sortable, {
|
|
38397
38360
|
options: right,
|
|
38398
|
-
layout:
|
|
38361
|
+
layout: "vertical",
|
|
38399
38362
|
padding: this.props.padding,
|
|
38400
38363
|
constraints: constraints,
|
|
38401
38364
|
onMeasure: this.onMeasureRight,
|
|
@@ -38916,6 +38879,7 @@ const staticTransform$2 = editorProps => {
|
|
|
38916
38879
|
var Matrix$1 = {
|
|
38917
38880
|
name: "matrix",
|
|
38918
38881
|
displayName: "Matrix",
|
|
38882
|
+
hidden: true,
|
|
38919
38883
|
widget: Matrix,
|
|
38920
38884
|
transform: propTransform$1,
|
|
38921
38885
|
staticTransform: staticTransform$2,
|
|
@@ -39072,6 +39036,7 @@ const propUpgrades = {
|
|
|
39072
39036
|
var Measurer$1 = {
|
|
39073
39037
|
name: "measurer",
|
|
39074
39038
|
displayName: "Measurer",
|
|
39039
|
+
hidden: true,
|
|
39075
39040
|
widget: Measurer,
|
|
39076
39041
|
version: {
|
|
39077
39042
|
major: 1,
|
|
@@ -39964,12 +39929,7 @@ class MoleculeWidget extends React__namespace.Component {
|
|
|
39964
39929
|
// TODO(colin): this is here as part of the interface for a component.
|
|
39965
39930
|
// Figure out if there is something more appropriate that this should
|
|
39966
39931
|
// return.
|
|
39967
|
-
return
|
|
39968
|
-
type: "points",
|
|
39969
|
-
earned: 0,
|
|
39970
|
-
total: 0,
|
|
39971
|
-
message: null
|
|
39972
|
-
};
|
|
39932
|
+
return noopValidator();
|
|
39973
39933
|
};
|
|
39974
39934
|
render() {
|
|
39975
39935
|
return /*#__PURE__*/React__namespace.createElement(Molecule, {
|
|
@@ -40569,10 +40529,6 @@ var NumberLine$1 = {
|
|
|
40569
40529
|
};
|
|
40570
40530
|
|
|
40571
40531
|
class PlaceholderCard extends React__namespace.Component {
|
|
40572
|
-
static propTypes = {
|
|
40573
|
-
width: PropTypes__default["default"].number.isRequired,
|
|
40574
|
-
height: PropTypes__default["default"].number.isRequired
|
|
40575
|
-
};
|
|
40576
40532
|
render() {
|
|
40577
40533
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
40578
40534
|
className: "card-wrap " + ClassNames.INTERACTIVE,
|
|
@@ -40582,7 +40538,7 @@ class PlaceholderCard extends React__namespace.Component {
|
|
|
40582
40538
|
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
40583
40539
|
className: "card placeholder",
|
|
40584
40540
|
style: {
|
|
40585
|
-
height: this.props.
|
|
40541
|
+
height: this.props.width
|
|
40586
40542
|
}
|
|
40587
40543
|
}));
|
|
40588
40544
|
}
|
|
@@ -40596,29 +40552,10 @@ class DragHintCard extends React__namespace.Component {
|
|
|
40596
40552
|
}));
|
|
40597
40553
|
}
|
|
40598
40554
|
}
|
|
40599
|
-
const PropTypePosition = PropTypes__default["default"].shape({
|
|
40600
|
-
left: PropTypes__default["default"].number,
|
|
40601
|
-
top: PropTypes__default["default"].number
|
|
40602
|
-
});
|
|
40603
40555
|
class Card extends React__namespace.Component {
|
|
40604
40556
|
static contextType = PerseusI18nContext;
|
|
40605
40557
|
// @ts-expect-error - TS2564 - Property 'mouseMoveUpBound' has no initializer and is not definitely assigned in the constructor.
|
|
40606
40558
|
mouseMoveUpBound;
|
|
40607
|
-
static propTypes = {
|
|
40608
|
-
floating: PropTypes__default["default"].bool.isRequired,
|
|
40609
|
-
animating: PropTypes__default["default"].bool,
|
|
40610
|
-
width: PropTypes__default["default"].number,
|
|
40611
|
-
stack: PropTypes__default["default"].bool,
|
|
40612
|
-
onMouseDown: PropTypes__default["default"].func,
|
|
40613
|
-
onMouseMove: PropTypes__default["default"].func,
|
|
40614
|
-
onMouseUp: PropTypes__default["default"].func,
|
|
40615
|
-
// Used only for floating/animating cards
|
|
40616
|
-
startMouse: PropTypePosition,
|
|
40617
|
-
startOffset: PropTypePosition,
|
|
40618
|
-
animateTo: PropTypePosition,
|
|
40619
|
-
onAnimationEnd: PropTypes__default["default"].func,
|
|
40620
|
-
linterContext: PerseusLinter.linterContextProps
|
|
40621
|
-
};
|
|
40622
40559
|
static defaultProps = {
|
|
40623
40560
|
stack: false,
|
|
40624
40561
|
animating: false,
|
|
@@ -40656,7 +40593,7 @@ class Card extends React__namespace.Component {
|
|
|
40656
40593
|
this.props.fakeRef !== nextProps.fakeRef;
|
|
40657
40594
|
}
|
|
40658
40595
|
componentDidUpdate(prevProps, prevState) {
|
|
40659
|
-
if (this.props.animating && !prevProps.animating) {
|
|
40596
|
+
if (this.props.animating && !prevProps.animating && this.props.animateTo && this.props.startOffset) {
|
|
40660
40597
|
// If we just were changed into animating, start the animation.
|
|
40661
40598
|
// We pick the animation speed based on the distance that the card
|
|
40662
40599
|
// needs to travel. (Why sqrt? Just because it looks nice -- with a
|
|
@@ -40723,8 +40660,8 @@ class Card extends React__namespace.Component {
|
|
|
40723
40660
|
if (this.props.floating) {
|
|
40724
40661
|
style = {
|
|
40725
40662
|
position: "absolute",
|
|
40726
|
-
left: this.props.startOffset
|
|
40727
|
-
top: this.props.startOffset
|
|
40663
|
+
left: this.props.startOffset?.left,
|
|
40664
|
+
top: this.props.startOffset?.top
|
|
40728
40665
|
};
|
|
40729
40666
|
}
|
|
40730
40667
|
if (this.props.width) {
|
|
@@ -40734,7 +40671,7 @@ class Card extends React__namespace.Component {
|
|
|
40734
40671
|
if (this.props.stack) {
|
|
40735
40672
|
className.push("stack");
|
|
40736
40673
|
}
|
|
40737
|
-
if (this.props.floating && !this.props.animating) {
|
|
40674
|
+
if (this.props.floating && !this.props.animating && this.props.mouse && this.props.startMouse) {
|
|
40738
40675
|
className.push("dragging");
|
|
40739
40676
|
style.left += this.props.mouse.left - this.props.startMouse.left;
|
|
40740
40677
|
style.top += this.props.mouse.top - this.props.startMouse.top;
|
|
@@ -40759,14 +40696,14 @@ class Card extends React__namespace.Component {
|
|
|
40759
40696
|
}
|
|
40760
40697
|
}
|
|
40761
40698
|
const NORMAL = "normal",
|
|
40762
|
-
HORIZONTAL
|
|
40699
|
+
HORIZONTAL = "horizontal";
|
|
40763
40700
|
class Orderer extends React__namespace.Component {
|
|
40764
40701
|
static defaultProps = {
|
|
40765
40702
|
current: [],
|
|
40766
40703
|
options: [],
|
|
40767
40704
|
correctOptions: [],
|
|
40768
40705
|
height: NORMAL,
|
|
40769
|
-
layout: HORIZONTAL
|
|
40706
|
+
layout: HORIZONTAL,
|
|
40770
40707
|
linterContext: PerseusLinter.linterContextDefault
|
|
40771
40708
|
};
|
|
40772
40709
|
state = {
|
|
@@ -40919,7 +40856,7 @@ class Orderer extends React__namespace.Component {
|
|
|
40919
40856
|
};
|
|
40920
40857
|
findCorrectIndex = (draggable, list) => {
|
|
40921
40858
|
// Find the correct index for a card given the current cards.
|
|
40922
|
-
const isHorizontal = this.props.layout === HORIZONTAL
|
|
40859
|
+
const isHorizontal = this.props.layout === HORIZONTAL;
|
|
40923
40860
|
// eslint-disable-next-line react/no-string-refs
|
|
40924
40861
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
40925
40862
|
const $dragList = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.dragList));
|
|
@@ -40969,7 +40906,7 @@ class Orderer extends React__namespace.Component {
|
|
|
40969
40906
|
if (draggable == null) {
|
|
40970
40907
|
return false;
|
|
40971
40908
|
}
|
|
40972
|
-
const isHorizontal = this.props.layout === HORIZONTAL
|
|
40909
|
+
const isHorizontal = this.props.layout === HORIZONTAL,
|
|
40973
40910
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
40974
40911
|
$draggable = $__default["default"](ReactDOM__default["default"].findDOMNode(draggable)),
|
|
40975
40912
|
// eslint-disable-next-line react/no-string-refs
|
|
@@ -41149,6 +41086,7 @@ ___default["default"].extend(Orderer, {
|
|
|
41149
41086
|
var Orderer$1 = {
|
|
41150
41087
|
name: "orderer",
|
|
41151
41088
|
displayName: "Orderer",
|
|
41089
|
+
hidden: true,
|
|
41152
41090
|
widget: Orderer,
|
|
41153
41091
|
isLintable: true
|
|
41154
41092
|
};
|
|
@@ -42959,12 +42897,7 @@ class Passage extends React__namespace.Component {
|
|
|
42959
42897
|
stylesAreApplied: false
|
|
42960
42898
|
};
|
|
42961
42899
|
static validate(state, rubric) {
|
|
42962
|
-
return
|
|
42963
|
-
type: "points",
|
|
42964
|
-
earned: 0,
|
|
42965
|
-
total: 0,
|
|
42966
|
-
message: null
|
|
42967
|
-
};
|
|
42900
|
+
return noopValidator();
|
|
42968
42901
|
}
|
|
42969
42902
|
componentDidMount() {
|
|
42970
42903
|
this._updateState();
|
|
@@ -43325,6 +43258,7 @@ class Passage extends React__namespace.Component {
|
|
|
43325
43258
|
var Passage$1 = {
|
|
43326
43259
|
name: "passage",
|
|
43327
43260
|
displayName: "Passage (SAT only)",
|
|
43261
|
+
hidden: true,
|
|
43328
43262
|
widget: Passage,
|
|
43329
43263
|
transform: editorProps => {
|
|
43330
43264
|
return ___default["default"].pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
@@ -43340,16 +43274,15 @@ class PassageRefTarget extends React__namespace.Component {
|
|
|
43340
43274
|
linterContext: PerseusLinter.linterContextDefault
|
|
43341
43275
|
};
|
|
43342
43276
|
static validate(state, rubric) {
|
|
43343
|
-
return
|
|
43344
|
-
type: "points",
|
|
43345
|
-
earned: 0,
|
|
43346
|
-
total: 0,
|
|
43347
|
-
message: null
|
|
43348
|
-
};
|
|
43277
|
+
return noopValidator();
|
|
43349
43278
|
}
|
|
43279
|
+
|
|
43280
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
43350
43281
|
getUserInput = () => {
|
|
43351
|
-
return
|
|
43282
|
+
return;
|
|
43352
43283
|
};
|
|
43284
|
+
|
|
43285
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
43353
43286
|
change = (() => {
|
|
43354
43287
|
var _this = this;
|
|
43355
43288
|
return function () {
|
|
@@ -43359,6 +43292,8 @@ class PassageRefTarget extends React__namespace.Component {
|
|
|
43359
43292
|
return change.apply(_this, args);
|
|
43360
43293
|
};
|
|
43361
43294
|
})();
|
|
43295
|
+
|
|
43296
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
43362
43297
|
simpleValidate = rubric => {
|
|
43363
43298
|
return PassageRefTarget.validate(this.getUserInput(), rubric);
|
|
43364
43299
|
};
|
|
@@ -44417,6 +44352,7 @@ const staticTransform = ___default["default"].identity;
|
|
|
44417
44352
|
var Plotter$1 = {
|
|
44418
44353
|
name: "plotter",
|
|
44419
44354
|
displayName: "Plotter",
|
|
44355
|
+
hidden: true,
|
|
44420
44356
|
widget: Plotter,
|
|
44421
44357
|
staticTransform: staticTransform
|
|
44422
44358
|
};
|
|
@@ -44467,29 +44403,14 @@ var PythonProgram$1 = {
|
|
|
44467
44403
|
widget: PythonProgram
|
|
44468
44404
|
};
|
|
44469
44405
|
|
|
44470
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
44471
44406
|
const {
|
|
44472
44407
|
shuffle
|
|
44473
44408
|
} = Util;
|
|
44474
|
-
const HORIZONTAL = "horizontal";
|
|
44475
|
-
const VERTICAL = "vertical";
|
|
44476
44409
|
class Sorter extends React__namespace.Component {
|
|
44477
|
-
|
|
44478
|
-
_isMounted;
|
|
44479
|
-
static propTypes = {
|
|
44480
|
-
apiOptions: ApiOptions.propTypes,
|
|
44481
|
-
// eslint-disable-next-line react/forbid-prop-types
|
|
44482
|
-
correct: PropTypes__default["default"].array,
|
|
44483
|
-
layout: PropTypes__default["default"].oneOf([HORIZONTAL, VERTICAL]),
|
|
44484
|
-
onChange: PropTypes__default["default"].func,
|
|
44485
|
-
padding: PropTypes__default["default"].bool,
|
|
44486
|
-
problemNum: PropTypes__default["default"].number,
|
|
44487
|
-
trackInteraction: PropTypes__default["default"].func.isRequired,
|
|
44488
|
-
linterContext: PerseusLinter.linterContextProps
|
|
44489
|
-
};
|
|
44410
|
+
_isMounted = false;
|
|
44490
44411
|
static defaultProps = {
|
|
44491
44412
|
correct: [],
|
|
44492
|
-
layout:
|
|
44413
|
+
layout: "horizontal",
|
|
44493
44414
|
padding: true,
|
|
44494
44415
|
problemNum: 0,
|
|
44495
44416
|
onChange: function () {},
|
|
@@ -44567,9 +44488,7 @@ class Sorter extends React__namespace.Component {
|
|
|
44567
44488
|
const marginPx = apiOptions.isMobile ? 8 : 5;
|
|
44568
44489
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
44569
44490
|
className: "perseus-widget-sorter perseus-clearfix"
|
|
44570
|
-
}, /*#__PURE__*/React__namespace.createElement(Sortable
|
|
44571
|
-
// @ts-expect-error - TS2322 - Type 'readonly unknown[]' is not assignable to type 'readonly string[]'.
|
|
44572
|
-
, {
|
|
44491
|
+
}, /*#__PURE__*/React__namespace.createElement(Sortable, {
|
|
44573
44492
|
options: options,
|
|
44574
44493
|
layout: this.props.layout,
|
|
44575
44494
|
margin: marginPx,
|
|
@@ -44592,7 +44511,6 @@ var Sorter$1 = {
|
|
|
44592
44511
|
const {
|
|
44593
44512
|
assert
|
|
44594
44513
|
} = InteractiveUtil;
|
|
44595
|
-
|
|
44596
44514
|
/* Input handling: Maps a (row, column) pair to a unique ref used by React,
|
|
44597
44515
|
* and extracts (row, column) pairs from input paths, used to allow outsiders
|
|
44598
44516
|
* to focus, blur, set input values, etc. */
|
|
@@ -44619,16 +44537,6 @@ const getRefForPath = function (path) {
|
|
|
44619
44537
|
};
|
|
44620
44538
|
class Table extends React__namespace.Component {
|
|
44621
44539
|
static contextType = PerseusI18nContext;
|
|
44622
|
-
static propTypes = {
|
|
44623
|
-
answers: PropTypes__default["default"].arrayOf(PropTypes__default["default"].arrayOf(PropTypes__default["default"].string)),
|
|
44624
|
-
editableHeaders: PropTypes__default["default"].bool,
|
|
44625
|
-
// The editor to use when editableHeaders is enabled
|
|
44626
|
-
Editor: PropTypes__default["default"].func,
|
|
44627
|
-
headers: PropTypes__default["default"].arrayOf(PropTypes__default["default"].string),
|
|
44628
|
-
keypadElement: mathInput.keypadElementPropType,
|
|
44629
|
-
trackInteraction: PropTypes__default["default"].func.isRequired,
|
|
44630
|
-
linterContext: PerseusLinter.linterContextProps
|
|
44631
|
-
};
|
|
44632
44540
|
static defaultProps = function () {
|
|
44633
44541
|
const defaultRows = 4;
|
|
44634
44542
|
const defaultColumns = 1;
|
|
@@ -44974,12 +44882,7 @@ class Video extends React__namespace.Component {
|
|
|
44974
44882
|
* of Khan Academy videos.
|
|
44975
44883
|
*/
|
|
44976
44884
|
static validate(userInput, rubric) {
|
|
44977
|
-
return
|
|
44978
|
-
type: "points",
|
|
44979
|
-
earned: 0,
|
|
44980
|
-
total: 0,
|
|
44981
|
-
message: null
|
|
44982
|
-
};
|
|
44885
|
+
return noopValidator();
|
|
44983
44886
|
}
|
|
44984
44887
|
getUserInput = () => {
|
|
44985
44888
|
return null;
|
|
@@ -45099,7 +45002,7 @@ const init = function (options) {
|
|
|
45099
45002
|
|
|
45100
45003
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45101
45004
|
const libName = "@khanacademy/perseus";
|
|
45102
|
-
const libVersion = "
|
|
45005
|
+
const libVersion = "34.0.0";
|
|
45103
45006
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45104
45007
|
|
|
45105
45008
|
const apiVersion = {
|
|
@@ -48614,7 +48517,6 @@ exports.SizingUtils = sizingUtils;
|
|
|
48614
48517
|
exports.TableWidget = Table$1;
|
|
48615
48518
|
exports.Util = Util;
|
|
48616
48519
|
exports.WIDGET_PROP_DENYLIST = denylist;
|
|
48617
|
-
exports.WidgetJsonifyDeprecated = WidgetJsonifyDeprecated;
|
|
48618
48520
|
exports.Widgets = widgets$1;
|
|
48619
48521
|
exports.addWidget = addWidget;
|
|
48620
48522
|
exports.apiVersion = apiVersion;
|