@khanacademy/perseus 33.3.0 → 34.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/sortable.d.ts +1 -4
- package/dist/es/index.js +721 -691
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +736 -692
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +2 -0
- package/dist/perseus-types.d.ts +3 -1
- package/dist/server-item-renderer.d.ts +2 -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-validator.d.ts +5 -0
- package/dist/widgets/categorizer/categorizer.d.ts +1 -2
- package/dist/widgets/categorizer/categorizer.types.d.ts +5 -0
- package/dist/widgets/cs-program/cs-program.d.ts +23 -17
- package/dist/widgets/definition/definition.d.ts +3 -3
- 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/explanation/explanation.d.ts +3 -5
- package/dist/widgets/expression/expression-validator.d.ts +5 -0
- package/dist/widgets/expression/expression.d.ts +13 -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/image/image.d.ts +3 -3
- package/dist/widgets/input-number/input-number-validator.d.ts +41 -0
- package/dist/widgets/input-number/input-number.d.ts +1 -1
- package/dist/widgets/input-number/input-number.types.d.ts +2 -0
- package/dist/widgets/interaction/interaction.d.ts +3 -3
- package/dist/widgets/interactive-graphs/graph-locked-labels-layer.d.ts +2 -0
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +7 -0
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +2 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +16 -1
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +2 -1
- package/dist/widgets/interactive-graphs/types.d.ts +3 -0
- package/dist/widgets/label-image/label-image.d.ts +4 -0
- package/dist/widgets/matcher/matcher.d.ts +17 -46
- package/dist/widgets/molecule/molecule.d.ts +2 -2
- 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/passage/passage.d.ts +3 -3
- package/dist/widgets/passage-ref/passage-ref.d.ts +3 -3
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +2 -2
- package/dist/widgets/sorter/sorter.d.ts +18 -44
- package/dist/widgets/table/table.d.ts +20 -24
- package/dist/widgets/video/video.d.ts +3 -4
- 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
|
|
@@ -21967,6 +21972,48 @@ const answerTypes = {
|
|
|
21967
21972
|
forms: "pi"
|
|
21968
21973
|
}
|
|
21969
21974
|
};
|
|
21975
|
+
function inputNumberValidator(state, rubric, strings) {
|
|
21976
|
+
let onInputError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : () => {};
|
|
21977
|
+
if (rubric.answerType == null) {
|
|
21978
|
+
rubric.answerType = "number";
|
|
21979
|
+
}
|
|
21980
|
+
|
|
21981
|
+
// note(matthewc): this will get immediately parsed again by
|
|
21982
|
+
// `KhanAnswerTypes.number.convertToPredicate`, but a string is
|
|
21983
|
+
// expected here
|
|
21984
|
+
const stringValue = `${rubric.value}`;
|
|
21985
|
+
const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
21986
|
+
simplify: rubric.simplify,
|
|
21987
|
+
inexact: rubric.inexact || undefined,
|
|
21988
|
+
maxError: rubric.maxError,
|
|
21989
|
+
forms: answerTypes[rubric.answerType].forms
|
|
21990
|
+
}, strings);
|
|
21991
|
+
|
|
21992
|
+
// We may have received TeX; try to parse it before grading.
|
|
21993
|
+
// If `currentValue` is not TeX, this should be a no-op.
|
|
21994
|
+
const currentValue = ParseTex$1(state.currentValue);
|
|
21995
|
+
const result = val(currentValue);
|
|
21996
|
+
|
|
21997
|
+
// TODO(eater): Seems silly to translate result to this invalid/points
|
|
21998
|
+
// thing and immediately translate it back in ItemRenderer.scoreInput()
|
|
21999
|
+
if (result.empty) {
|
|
22000
|
+
// TODO(FEI-3867): remove null-check once we have APIOptionsInternal
|
|
22001
|
+
const apiResult = onInputError?.(null,
|
|
22002
|
+
// reserved for some widget identifier
|
|
22003
|
+
state.currentValue, result.message);
|
|
22004
|
+
return {
|
|
22005
|
+
type: "invalid",
|
|
22006
|
+
message: apiResult === false ? null : result.message
|
|
22007
|
+
};
|
|
22008
|
+
}
|
|
22009
|
+
return {
|
|
22010
|
+
type: "points",
|
|
22011
|
+
earned: result.correct ? 1 : 0,
|
|
22012
|
+
total: 1,
|
|
22013
|
+
message: result.message
|
|
22014
|
+
};
|
|
22015
|
+
}
|
|
22016
|
+
|
|
21970
22017
|
const formExamples$1 = {
|
|
21971
22018
|
integer: function (options, strings) {
|
|
21972
22019
|
return strings.integerExample;
|
|
@@ -22010,44 +22057,7 @@ class InputNumber extends React__namespace.Component {
|
|
|
22010
22057
|
};
|
|
22011
22058
|
static validate(state, rubric, strings) {
|
|
22012
22059
|
let onInputError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : () => {};
|
|
22013
|
-
|
|
22014
|
-
rubric.answerType = "number";
|
|
22015
|
-
}
|
|
22016
|
-
|
|
22017
|
-
// note(matthewc): this will get immediately parsed again by
|
|
22018
|
-
// `KhanAnswerTypes.number.convertToPredicate`, but a string is
|
|
22019
|
-
// expected here
|
|
22020
|
-
const stringValue = `${rubric.value}`;
|
|
22021
|
-
const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
22022
|
-
simplify: rubric.simplify,
|
|
22023
|
-
inexact: rubric.inexact || undefined,
|
|
22024
|
-
maxError: rubric.maxError,
|
|
22025
|
-
forms: answerTypes[rubric.answerType].forms
|
|
22026
|
-
}, strings);
|
|
22027
|
-
|
|
22028
|
-
// We may have received TeX; try to parse it before grading.
|
|
22029
|
-
// If `currentValue` is not TeX, this should be a no-op.
|
|
22030
|
-
const currentValue = ParseTex$1(state.currentValue);
|
|
22031
|
-
const result = val(currentValue);
|
|
22032
|
-
|
|
22033
|
-
// TODO(eater): Seems silly to translate result to this invalid/points
|
|
22034
|
-
// thing and immediately translate it back in ItemRenderer.scoreInput()
|
|
22035
|
-
if (result.empty) {
|
|
22036
|
-
// TODO(FEI-3867): remove null-check once we have APIOptionsInternal
|
|
22037
|
-
const apiResult = onInputError?.(null,
|
|
22038
|
-
// reserved for some widget identifier
|
|
22039
|
-
state.currentValue, result.message);
|
|
22040
|
-
return {
|
|
22041
|
-
type: "invalid",
|
|
22042
|
-
message: apiResult === false ? null : result.message
|
|
22043
|
-
};
|
|
22044
|
-
}
|
|
22045
|
-
return {
|
|
22046
|
-
type: "points",
|
|
22047
|
-
earned: result.correct ? 1 : 0,
|
|
22048
|
-
total: 1,
|
|
22049
|
-
message: result.message
|
|
22050
|
-
};
|
|
22060
|
+
return inputNumberValidator(state, rubric, strings, onInputError);
|
|
22051
22061
|
}
|
|
22052
22062
|
static getUserInputFromProps(props) {
|
|
22053
22063
|
return {
|
|
@@ -22121,7 +22131,7 @@ class InputNumber extends React__namespace.Component {
|
|
|
22121
22131
|
};
|
|
22122
22132
|
simpleValidate = (rubric, onInputError) => {
|
|
22123
22133
|
onInputError = onInputError || function () {};
|
|
22124
|
-
return
|
|
22134
|
+
return inputNumberValidator(this.getUserInput(), rubric, this.context.strings, onInputError);
|
|
22125
22135
|
};
|
|
22126
22136
|
examples = () => {
|
|
22127
22137
|
const {
|
|
@@ -22254,6 +22264,130 @@ const answerFormButtons = [{
|
|
|
22254
22264
|
value: "pi",
|
|
22255
22265
|
content: "\u03C0"
|
|
22256
22266
|
}];
|
|
22267
|
+
|
|
22268
|
+
// This function checks if the user inputted a percent value, parsing
|
|
22269
|
+
// it as a number (and maybe scaling) so that it can be graded.
|
|
22270
|
+
// NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
|
|
22271
|
+
// can accept several input forms with or without "%", the decision
|
|
22272
|
+
// to parse based on the presence of "%" in the input, is so that we
|
|
22273
|
+
// don't accidently scale the user typed value before grading, CP-930.
|
|
22274
|
+
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
22275
|
+
// If the input value is not a string ending with "%", then there's
|
|
22276
|
+
// nothing more to do. The value will be graded as inputted by user.
|
|
22277
|
+
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
22278
|
+
return inputValue;
|
|
22279
|
+
}
|
|
22280
|
+
const value = parseFloat(inputValue.slice(0, -1));
|
|
22281
|
+
// If the input value stripped of the "%" cannot be parsed as a
|
|
22282
|
+
// number (the slice is not really necessary for parseFloat to work
|
|
22283
|
+
// if the string starts with a number) then return the original
|
|
22284
|
+
// input for grading.
|
|
22285
|
+
if (isNaN(value)) {
|
|
22286
|
+
return inputValue;
|
|
22287
|
+
}
|
|
22288
|
+
|
|
22289
|
+
// Next, if all correct answers are in the range of |0,1| then we
|
|
22290
|
+
// scale the user typed value. We assume this is the correct thing
|
|
22291
|
+
// to do since the input value ends with "%".
|
|
22292
|
+
if (normalizedAnswerExpected) {
|
|
22293
|
+
return value / 100;
|
|
22294
|
+
}
|
|
22295
|
+
|
|
22296
|
+
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
22297
|
+
return value;
|
|
22298
|
+
}
|
|
22299
|
+
function numericInputValidator(userInput, rubric, strings) {
|
|
22300
|
+
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
22301
|
+
// Don't default to validating the answer as a pi answer
|
|
22302
|
+
// if answerForm isn't set on the answer
|
|
22303
|
+
// https://khanacademy.atlassian.net/browse/LC-691
|
|
22304
|
+
.filter(e => e !== "pi");
|
|
22305
|
+
const createValidator = answer => {
|
|
22306
|
+
const stringAnswer = `${answer.value}`;
|
|
22307
|
+
|
|
22308
|
+
// Always validate against the provided answer forms (pi, decimal, etc.)
|
|
22309
|
+
const validatorForms = [...(answer.answerForms ?? [])];
|
|
22310
|
+
|
|
22311
|
+
// When an answer is set to strict, we validate using ONLY
|
|
22312
|
+
// the provided answerForms. If strict is false, or if there
|
|
22313
|
+
// were no provided answer forms, we will include all
|
|
22314
|
+
// of the default answer forms in our validator.
|
|
22315
|
+
if (!answer.strict || validatorForms.length === 0) {
|
|
22316
|
+
validatorForms.push(...defaultAnswerForms);
|
|
22317
|
+
}
|
|
22318
|
+
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
22319
|
+
message: answer.message,
|
|
22320
|
+
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
22321
|
+
inexact: true,
|
|
22322
|
+
// TODO(merlob) backfill / delete
|
|
22323
|
+
maxError: answer.maxError,
|
|
22324
|
+
forms: validatorForms
|
|
22325
|
+
}, strings);
|
|
22326
|
+
};
|
|
22327
|
+
|
|
22328
|
+
// We may have received TeX; try to parse it before grading.
|
|
22329
|
+
// If `currentValue` is not TeX, this should be a no-op.
|
|
22330
|
+
const currentValue = ParseTex(userInput.currentValue);
|
|
22331
|
+
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22332
|
+
const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1);
|
|
22333
|
+
|
|
22334
|
+
// Look through all correct answers for one that matches either
|
|
22335
|
+
// precisely or approximately and return the appropriate message:
|
|
22336
|
+
// - if precise, return the message that the answer came with
|
|
22337
|
+
// - if it needs to be simplified, etc., show that message
|
|
22338
|
+
let result = correctAnswers.map(answer => {
|
|
22339
|
+
// The coefficient is an attribute of the widget
|
|
22340
|
+
let localValue = currentValue;
|
|
22341
|
+
if (rubric.coefficient) {
|
|
22342
|
+
if (!localValue) {
|
|
22343
|
+
localValue = 1;
|
|
22344
|
+
} else if (localValue === "-") {
|
|
22345
|
+
localValue = -1;
|
|
22346
|
+
}
|
|
22347
|
+
}
|
|
22348
|
+
const validate = createValidator(answer);
|
|
22349
|
+
return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
22350
|
+
}).find(match => match.correct || match.empty);
|
|
22351
|
+
if (!result) {
|
|
22352
|
+
// Otherwise, if the guess is not correct
|
|
22353
|
+
const otherAnswers = [].concat(
|
|
22354
|
+
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
22355
|
+
rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong"));
|
|
22356
|
+
|
|
22357
|
+
// Look through all other answers and if one matches either
|
|
22358
|
+
// precisely or approximately return the answer's message
|
|
22359
|
+
const match = otherAnswers.find(answer => {
|
|
22360
|
+
const validate = createValidator(answer);
|
|
22361
|
+
return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
|
|
22362
|
+
});
|
|
22363
|
+
result = {
|
|
22364
|
+
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22365
|
+
empty: match ? match.status === "ungraded" : false,
|
|
22366
|
+
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22367
|
+
correct: match ? match.status === "correct" : false,
|
|
22368
|
+
// @ts-expect-error - TS2339 - Property 'message' does not exist on type 'never'.
|
|
22369
|
+
message: match ? match.message : null,
|
|
22370
|
+
guess: currentValue
|
|
22371
|
+
};
|
|
22372
|
+
}
|
|
22373
|
+
|
|
22374
|
+
// TODO(eater): Seems silly to translate result to this
|
|
22375
|
+
// invalid/points thing and immediately translate it
|
|
22376
|
+
// back in ItemRenderer.scoreInput()
|
|
22377
|
+
if (result.empty) {
|
|
22378
|
+
return {
|
|
22379
|
+
type: "invalid",
|
|
22380
|
+
message: result.message
|
|
22381
|
+
};
|
|
22382
|
+
}
|
|
22383
|
+
return {
|
|
22384
|
+
type: "points",
|
|
22385
|
+
earned: result.correct ? 1 : 0,
|
|
22386
|
+
total: 1,
|
|
22387
|
+
message: result.message
|
|
22388
|
+
};
|
|
22389
|
+
}
|
|
22390
|
+
|
|
22257
22391
|
const formExamples = {
|
|
22258
22392
|
integer: (form, strings) => strings.integerExample,
|
|
22259
22393
|
proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
|
|
@@ -22309,97 +22443,7 @@ class NumericInput extends React__namespace.Component {
|
|
|
22309
22443
|
return answerStrings[0];
|
|
22310
22444
|
}
|
|
22311
22445
|
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
|
-
};
|
|
22446
|
+
return numericInputValidator(userInput, rubric, strings);
|
|
22403
22447
|
}
|
|
22404
22448
|
state = {
|
|
22405
22449
|
// keeps track of the other set of values when switching
|
|
@@ -22434,7 +22478,7 @@ class NumericInput extends React__namespace.Component {
|
|
|
22434
22478
|
return !noFormsAccepted && !allFormsAccepted;
|
|
22435
22479
|
};
|
|
22436
22480
|
simpleValidate = rubric => {
|
|
22437
|
-
return
|
|
22481
|
+
return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
|
|
22438
22482
|
};
|
|
22439
22483
|
focus = () => {
|
|
22440
22484
|
this.inputRef?.focus();
|
|
@@ -22565,37 +22609,6 @@ const unionAnswerForms = function (answerFormsList) {
|
|
|
22565
22609
|
return formExampleKeys.indexOf(form.name);
|
|
22566
22610
|
});
|
|
22567
22611
|
};
|
|
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
22612
|
const propsTransform = function (widgetOptions) {
|
|
22600
22613
|
const rendererProps = ___default["default"].extend(___default["default"].omit(widgetOptions, "answers"), {
|
|
22601
22614
|
answerForms: unionAnswerForms(
|
|
@@ -22623,6 +22636,24 @@ var NumericInput$1 = {
|
|
|
22623
22636
|
isLintable: true
|
|
22624
22637
|
};
|
|
22625
22638
|
|
|
22639
|
+
/**
|
|
22640
|
+
* Several widgets don't have "right"/"wrong" validation logic,
|
|
22641
|
+
* so this just says to move on past those widgets
|
|
22642
|
+
*
|
|
22643
|
+
* TODO(LEMS-2396) widgets that use this probably shouldn't have any
|
|
22644
|
+
* validation logic and the thing validating an exercise
|
|
22645
|
+
* should just know to skip these
|
|
22646
|
+
*/
|
|
22647
|
+
function noopValidator() {
|
|
22648
|
+
let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
|
22649
|
+
return {
|
|
22650
|
+
type: "points",
|
|
22651
|
+
earned: points,
|
|
22652
|
+
total: points,
|
|
22653
|
+
message: null
|
|
22654
|
+
};
|
|
22655
|
+
}
|
|
22656
|
+
|
|
22626
22657
|
const EN_DASH$1 = "\u2013";
|
|
22627
22658
|
class PassageRef extends React__namespace.Component {
|
|
22628
22659
|
static contextType = PerseusI18nContext;
|
|
@@ -22640,13 +22671,10 @@ class PassageRef extends React__namespace.Component {
|
|
|
22640
22671
|
lineRange: null,
|
|
22641
22672
|
content: null
|
|
22642
22673
|
};
|
|
22643
|
-
|
|
22644
|
-
|
|
22645
|
-
|
|
22646
|
-
|
|
22647
|
-
total: 0,
|
|
22648
|
-
message: null
|
|
22649
|
-
};
|
|
22674
|
+
|
|
22675
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
22676
|
+
static validate() {
|
|
22677
|
+
return noopValidator();
|
|
22650
22678
|
}
|
|
22651
22679
|
componentDidMount() {
|
|
22652
22680
|
// TODO(scottgrant): This is a hack to remove the deprecated call to
|
|
@@ -22707,9 +22735,11 @@ class PassageRef extends React__namespace.Component {
|
|
|
22707
22735
|
}
|
|
22708
22736
|
}
|
|
22709
22737
|
};
|
|
22710
|
-
|
|
22711
|
-
|
|
22712
|
-
|
|
22738
|
+
|
|
22739
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
22740
|
+
simpleValidate() {
|
|
22741
|
+
return noopValidator();
|
|
22742
|
+
}
|
|
22713
22743
|
render() {
|
|
22714
22744
|
const {
|
|
22715
22745
|
strings
|
|
@@ -22747,6 +22777,7 @@ class PassageRef extends React__namespace.Component {
|
|
|
22747
22777
|
var PassageRef$1 = {
|
|
22748
22778
|
name: "passage-ref",
|
|
22749
22779
|
displayName: "PassageRef (SAT only)",
|
|
22780
|
+
hidden: true,
|
|
22750
22781
|
defaultAlignment: "inline",
|
|
22751
22782
|
widget: PassageRef,
|
|
22752
22783
|
transform: widgetOptions => ({
|
|
@@ -24655,22 +24686,30 @@ var Radio = {
|
|
|
24655
24686
|
/* eslint-disable import/no-named-as-default */
|
|
24656
24687
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
24657
24688
|
|
|
24658
|
-
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24662
|
-
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
|
|
24669
|
-
|
|
24670
|
-
|
|
24671
|
-
|
|
24689
|
+
function categorizerValidator(userInput, rubric, strings) {
|
|
24690
|
+
let completed = true;
|
|
24691
|
+
let allCorrect = true;
|
|
24692
|
+
rubric.values.forEach((value, i) => {
|
|
24693
|
+
if (userInput.values[i] == null) {
|
|
24694
|
+
completed = false;
|
|
24695
|
+
}
|
|
24696
|
+
if (userInput.values[i] !== value) {
|
|
24697
|
+
allCorrect = false;
|
|
24698
|
+
}
|
|
24699
|
+
});
|
|
24700
|
+
if (!completed) {
|
|
24701
|
+
return {
|
|
24702
|
+
type: "invalid",
|
|
24703
|
+
message: strings.invalidSelection
|
|
24704
|
+
};
|
|
24672
24705
|
}
|
|
24673
|
-
|
|
24706
|
+
return {
|
|
24707
|
+
type: "points",
|
|
24708
|
+
earned: allCorrect ? 1 : 0,
|
|
24709
|
+
total: 1,
|
|
24710
|
+
message: null
|
|
24711
|
+
};
|
|
24712
|
+
}
|
|
24674
24713
|
|
|
24675
24714
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
24676
24715
|
class Categorizer extends React__namespace.Component {
|
|
@@ -24685,31 +24724,12 @@ class Categorizer extends React__namespace.Component {
|
|
|
24685
24724
|
uniqueId: ___default["default"].uniqueId("perseus_radio_")
|
|
24686
24725
|
};
|
|
24687
24726
|
static validate(userInput, rubric, strings) {
|
|
24688
|
-
|
|
24689
|
-
let allCorrect = true;
|
|
24690
|
-
rubric.values.forEach((value, i) => {
|
|
24691
|
-
if (userInput.values[i] == null) {
|
|
24692
|
-
completed = false;
|
|
24693
|
-
}
|
|
24694
|
-
if (userInput.values[i] !== value) {
|
|
24695
|
-
allCorrect = false;
|
|
24696
|
-
}
|
|
24697
|
-
});
|
|
24698
|
-
if (!completed) {
|
|
24699
|
-
return {
|
|
24700
|
-
type: "invalid",
|
|
24701
|
-
message: strings.invalidSelection
|
|
24702
|
-
};
|
|
24703
|
-
}
|
|
24704
|
-
return {
|
|
24705
|
-
type: "points",
|
|
24706
|
-
earned: allCorrect ? 1 : 0,
|
|
24707
|
-
total: 1,
|
|
24708
|
-
message: null
|
|
24709
|
-
};
|
|
24727
|
+
return categorizerValidator(userInput, rubric, strings);
|
|
24710
24728
|
}
|
|
24711
24729
|
static getUserInputFromProps(props) {
|
|
24712
|
-
return
|
|
24730
|
+
return {
|
|
24731
|
+
values: props.values
|
|
24732
|
+
};
|
|
24713
24733
|
}
|
|
24714
24734
|
change = (() => {
|
|
24715
24735
|
var _this = this;
|
|
@@ -24732,7 +24752,7 @@ class Categorizer extends React__namespace.Component {
|
|
|
24732
24752
|
this.props.trackInteraction();
|
|
24733
24753
|
}
|
|
24734
24754
|
simpleValidate = rubric => {
|
|
24735
|
-
return
|
|
24755
|
+
return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
|
|
24736
24756
|
};
|
|
24737
24757
|
render() {
|
|
24738
24758
|
const self = this;
|
|
@@ -24864,6 +24884,7 @@ const styles$s = aphrodite.StyleSheet.create({
|
|
|
24864
24884
|
var Categorizer$1 = {
|
|
24865
24885
|
name: "categorizer",
|
|
24866
24886
|
displayName: "Categorizer",
|
|
24887
|
+
hidden: true,
|
|
24867
24888
|
widget: Categorizer,
|
|
24868
24889
|
transform: widgetOptions => {
|
|
24869
24890
|
return ___default["default"].pick(widgetOptions, "items", "categories", "randomizeItems");
|
|
@@ -24944,18 +24965,12 @@ function getUrlFromProgramID$1(programID) {
|
|
|
24944
24965
|
/* This renders the scratchpad in an iframe and handles validation via
|
|
24945
24966
|
* window.postMessage */
|
|
24946
24967
|
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
|
|
24968
|
+
static defaultProps = {
|
|
24969
|
+
showEditor: false,
|
|
24970
|
+
showButtons: false,
|
|
24971
|
+
status: "incomplete",
|
|
24972
|
+
// optional message
|
|
24973
|
+
message: null
|
|
24959
24974
|
};
|
|
24960
24975
|
|
|
24961
24976
|
// The widget's grading function
|
|
@@ -24983,13 +24998,6 @@ class CSProgram extends React__namespace.Component {
|
|
|
24983
24998
|
message: "Keep going, you're not there yet!"
|
|
24984
24999
|
};
|
|
24985
25000
|
}
|
|
24986
|
-
static defaultProps = {
|
|
24987
|
-
showEditor: false,
|
|
24988
|
-
showButtons: false,
|
|
24989
|
-
status: "incomplete",
|
|
24990
|
-
// optional message
|
|
24991
|
-
message: null
|
|
24992
|
-
};
|
|
24993
25001
|
componentDidMount() {
|
|
24994
25002
|
$__default["default"](window).on("message", this.handleMessageEvent);
|
|
24995
25003
|
}
|
|
@@ -25025,12 +25033,12 @@ class CSProgram extends React__namespace.Component {
|
|
|
25025
25033
|
return change.apply(_this, args);
|
|
25026
25034
|
};
|
|
25027
25035
|
})();
|
|
25028
|
-
simpleValidate
|
|
25036
|
+
simpleValidate(rubric) {
|
|
25029
25037
|
return CSProgram.validate({
|
|
25030
25038
|
status: this.props.status,
|
|
25031
25039
|
message: this.props.message
|
|
25032
25040
|
}, rubric);
|
|
25033
|
-
}
|
|
25041
|
+
}
|
|
25034
25042
|
render() {
|
|
25035
25043
|
if (!this.props.programID) {
|
|
25036
25044
|
return /*#__PURE__*/React__namespace.createElement("div", null);
|
|
@@ -25114,20 +25122,19 @@ class Definition extends React__namespace.Component {
|
|
|
25114
25122
|
togglePrompt: "define me",
|
|
25115
25123
|
definition: "definition goes here"
|
|
25116
25124
|
};
|
|
25117
|
-
|
|
25118
|
-
|
|
25119
|
-
|
|
25120
|
-
|
|
25121
|
-
total: 0,
|
|
25122
|
-
message: null
|
|
25123
|
-
};
|
|
25125
|
+
|
|
25126
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
25127
|
+
static validate() {
|
|
25128
|
+
return noopValidator();
|
|
25124
25129
|
}
|
|
25125
25130
|
getUserInput = () => {
|
|
25126
25131
|
return {};
|
|
25127
25132
|
};
|
|
25128
|
-
|
|
25129
|
-
|
|
25130
|
-
|
|
25133
|
+
|
|
25134
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
25135
|
+
simpleValidate() {
|
|
25136
|
+
return noopValidator();
|
|
25137
|
+
}
|
|
25131
25138
|
render() {
|
|
25132
25139
|
return /*#__PURE__*/React__namespace.createElement(DefinitionConsumer, null, _ref => {
|
|
25133
25140
|
let {
|
|
@@ -25193,18 +25200,13 @@ class DeprecatedStandin extends React__default["default"].Component {
|
|
|
25193
25200
|
static validate(userInput, rubric) {
|
|
25194
25201
|
// Since this mean to replace an existing widget the learner
|
|
25195
25202
|
// WILL earn points for this widget
|
|
25196
|
-
return
|
|
25197
|
-
type: "points",
|
|
25198
|
-
earned: 1,
|
|
25199
|
-
total: 1,
|
|
25200
|
-
message: null
|
|
25201
|
-
};
|
|
25203
|
+
return noopValidator(1);
|
|
25202
25204
|
}
|
|
25203
25205
|
getUserInput = () => {
|
|
25204
25206
|
return {};
|
|
25205
25207
|
};
|
|
25206
25208
|
simpleValidate = rubric => {
|
|
25207
|
-
return
|
|
25209
|
+
return noopValidator(1);
|
|
25208
25210
|
};
|
|
25209
25211
|
render() {
|
|
25210
25212
|
return /*#__PURE__*/React__default["default"].createElement("div", {
|
|
@@ -25226,6 +25228,23 @@ var DeprecatedStandin$1 = {
|
|
|
25226
25228
|
hidden: true
|
|
25227
25229
|
};
|
|
25228
25230
|
|
|
25231
|
+
function dropdownValidator(userInput, rubric) {
|
|
25232
|
+
const selected = userInput.value;
|
|
25233
|
+
if (selected === 0) {
|
|
25234
|
+
return {
|
|
25235
|
+
type: "invalid",
|
|
25236
|
+
message: null
|
|
25237
|
+
};
|
|
25238
|
+
}
|
|
25239
|
+
const correct = rubric.choices[selected - 1].correct;
|
|
25240
|
+
return {
|
|
25241
|
+
type: "points",
|
|
25242
|
+
earned: correct ? 1 : 0,
|
|
25243
|
+
total: 1,
|
|
25244
|
+
message: null
|
|
25245
|
+
};
|
|
25246
|
+
}
|
|
25247
|
+
|
|
25229
25248
|
class Dropdown extends React__namespace.Component {
|
|
25230
25249
|
static defaultProps = {
|
|
25231
25250
|
choices: [],
|
|
@@ -25234,20 +25253,7 @@ class Dropdown extends React__namespace.Component {
|
|
|
25234
25253
|
apiOptions: ApiOptions.defaults
|
|
25235
25254
|
};
|
|
25236
25255
|
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
|
-
};
|
|
25256
|
+
return dropdownValidator(userInput, rubric);
|
|
25251
25257
|
}
|
|
25252
25258
|
focus = () => {
|
|
25253
25259
|
// TODO(LP-10797): This focus() call doesn't do anything because our
|
|
@@ -25272,7 +25278,7 @@ class Dropdown extends React__namespace.Component {
|
|
|
25272
25278
|
};
|
|
25273
25279
|
};
|
|
25274
25280
|
simpleValidate = rubric => {
|
|
25275
|
-
return
|
|
25281
|
+
return dropdownValidator(this.getUserInput(), rubric);
|
|
25276
25282
|
};
|
|
25277
25283
|
render() {
|
|
25278
25284
|
const children = [/*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
|
|
@@ -25338,13 +25344,10 @@ class Explanation extends React__namespace.Component {
|
|
|
25338
25344
|
widgets: {},
|
|
25339
25345
|
linterContext: PerseusLinter.linterContextDefault
|
|
25340
25346
|
};
|
|
25341
|
-
|
|
25342
|
-
|
|
25343
|
-
|
|
25344
|
-
|
|
25345
|
-
total: 0,
|
|
25346
|
-
message: null
|
|
25347
|
-
};
|
|
25347
|
+
|
|
25348
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
25349
|
+
static validate() {
|
|
25350
|
+
return noopValidator();
|
|
25348
25351
|
}
|
|
25349
25352
|
componentDidMount() {
|
|
25350
25353
|
this._mounted = true;
|
|
@@ -25370,9 +25373,11 @@ class Explanation extends React__namespace.Component {
|
|
|
25370
25373
|
getUserInput = () => {
|
|
25371
25374
|
return {};
|
|
25372
25375
|
};
|
|
25373
|
-
|
|
25374
|
-
|
|
25375
|
-
|
|
25376
|
+
|
|
25377
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
25378
|
+
simpleValidate() {
|
|
25379
|
+
return noopValidator();
|
|
25380
|
+
}
|
|
25376
25381
|
render() {
|
|
25377
25382
|
const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
|
|
25378
25383
|
const caretIcon = this.state.expanded ? caretUp__default["default"] : caretDown__default["default"];
|
|
@@ -27208,6 +27213,7 @@ const staticTransform$4 = editorProps => {
|
|
|
27208
27213
|
var Grapher$1 = {
|
|
27209
27214
|
name: "grapher",
|
|
27210
27215
|
displayName: "Grapher",
|
|
27216
|
+
hidden: true,
|
|
27211
27217
|
widget: Grapher,
|
|
27212
27218
|
transform: propTransform$2,
|
|
27213
27219
|
staticTransform: staticTransform$4
|
|
@@ -27355,28 +27361,23 @@ var Group$1 = {
|
|
|
27355
27361
|
displayName: "Group (SAT only)",
|
|
27356
27362
|
widget: Group,
|
|
27357
27363
|
traverseChildWidgets: traverseChildWidgets,
|
|
27358
|
-
hidden:
|
|
27364
|
+
hidden: true,
|
|
27359
27365
|
isLintable: true
|
|
27360
27366
|
};
|
|
27361
27367
|
|
|
27362
|
-
|
|
27368
|
+
/**
|
|
27369
|
+
* This is an iframe widget. It is used for rendering an iframe that
|
|
27370
|
+
* then communicates its state via window.postMessage
|
|
27371
|
+
* This is useful for embedding arbitrary visualizations/simulations with
|
|
27372
|
+
* completed conditions, such as the mazes and games in Algorithms.
|
|
27373
|
+
* It's particularly well suited for embedding our ProcessingJS programs,
|
|
27374
|
+
* but could also be used for embedding viz's hosted elsewhere.
|
|
27375
|
+
*/
|
|
27363
27376
|
const {
|
|
27364
27377
|
updateQueryString
|
|
27365
27378
|
} = Util;
|
|
27366
|
-
|
|
27367
27379
|
/* This renders the iframe and handles validation via window.postMessage */
|
|
27368
27380
|
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
27381
|
static defaultProps = {
|
|
27381
27382
|
status: "incomplete",
|
|
27382
27383
|
// optional message
|
|
@@ -27390,9 +27391,12 @@ class Iframe extends React__namespace.Component {
|
|
|
27390
27391
|
componentWillUnmount() {
|
|
27391
27392
|
$__default["default"](window).off("message", this.handleMessageEvent);
|
|
27392
27393
|
}
|
|
27393
|
-
getUserInput
|
|
27394
|
-
return
|
|
27395
|
-
|
|
27394
|
+
getUserInput() {
|
|
27395
|
+
return {
|
|
27396
|
+
status: this.props.status,
|
|
27397
|
+
message: this.props.message
|
|
27398
|
+
};
|
|
27399
|
+
}
|
|
27396
27400
|
handleMessageEvent = e => {
|
|
27397
27401
|
// We receive data from the iframe that contains {passed: true/false}
|
|
27398
27402
|
// and use that to set the status
|
|
@@ -27551,13 +27555,10 @@ class ImageWidget extends React__namespace.Component {
|
|
|
27551
27555
|
caption: "",
|
|
27552
27556
|
linterContext: PerseusLinter.linterContextDefault
|
|
27553
27557
|
};
|
|
27554
|
-
|
|
27555
|
-
|
|
27556
|
-
|
|
27557
|
-
|
|
27558
|
-
total: 0,
|
|
27559
|
-
message: null
|
|
27560
|
-
};
|
|
27558
|
+
|
|
27559
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27560
|
+
static validate() {
|
|
27561
|
+
return noopValidator();
|
|
27561
27562
|
}
|
|
27562
27563
|
change = (() => {
|
|
27563
27564
|
var _this = this;
|
|
@@ -27571,9 +27572,11 @@ class ImageWidget extends React__namespace.Component {
|
|
|
27571
27572
|
getUserInput = () => {
|
|
27572
27573
|
return null;
|
|
27573
27574
|
};
|
|
27574
|
-
|
|
27575
|
-
|
|
27576
|
-
|
|
27575
|
+
|
|
27576
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27577
|
+
simpleValidate() {
|
|
27578
|
+
return noopValidator();
|
|
27579
|
+
}
|
|
27577
27580
|
focus = () => {}; // no-op
|
|
27578
27581
|
|
|
27579
27582
|
render() {
|
|
@@ -27799,13 +27802,10 @@ class Interaction extends React__namespace.Component {
|
|
|
27799
27802
|
},
|
|
27800
27803
|
elements: []
|
|
27801
27804
|
};
|
|
27802
|
-
|
|
27803
|
-
|
|
27804
|
-
|
|
27805
|
-
|
|
27806
|
-
total: 0,
|
|
27807
|
-
message: null
|
|
27808
|
-
};
|
|
27805
|
+
|
|
27806
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27807
|
+
static validate() {
|
|
27808
|
+
return noopValidator();
|
|
27809
27809
|
}
|
|
27810
27810
|
state = {
|
|
27811
27811
|
variables: _getInitialVariables(this.props.elements),
|
|
@@ -27921,9 +27921,11 @@ class Interaction extends React__namespace.Component {
|
|
|
27921
27921
|
// anything.
|
|
27922
27922
|
return {};
|
|
27923
27923
|
};
|
|
27924
|
-
|
|
27925
|
-
|
|
27926
|
-
|
|
27924
|
+
|
|
27925
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27926
|
+
simpleValidate() {
|
|
27927
|
+
return noopValidator();
|
|
27928
|
+
}
|
|
27927
27929
|
render() {
|
|
27928
27930
|
const range = this.props.graph.range;
|
|
27929
27931
|
let labels = this.props.graph.labels;
|
|
@@ -29977,6 +29979,7 @@ function LockedLabel(props) {
|
|
|
29977
29979
|
|
|
29978
29980
|
function GraphLockedLabelsLayer(props) {
|
|
29979
29981
|
const {
|
|
29982
|
+
flags,
|
|
29980
29983
|
lockedFigures
|
|
29981
29984
|
} = props;
|
|
29982
29985
|
return lockedFigures.map((figure, i) => {
|
|
@@ -29985,9 +29988,21 @@ function GraphLockedLabelsLayer(props) {
|
|
|
29985
29988
|
key: `label-${i}`
|
|
29986
29989
|
}, figure));
|
|
29987
29990
|
}
|
|
29988
|
-
|
|
29989
|
-
//
|
|
29990
|
-
|
|
29991
|
+
if (
|
|
29992
|
+
// Point flag + point type
|
|
29993
|
+
flags?.["mafs"]?.["locked-point-labels"] && figure.type === "point" ||
|
|
29994
|
+
// Line flag + line type
|
|
29995
|
+
flags?.["mafs"]?.["locked-line-labels"] && figure.type === "line") {
|
|
29996
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, {
|
|
29997
|
+
key: i
|
|
29998
|
+
}, figure.labels.map((label, j) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
|
|
29999
|
+
key: `${i}-label-${j}`
|
|
30000
|
+
}, label))), figure.type === "line" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, figure.points[0].labels.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
|
|
30001
|
+
key: `locked-figure-${i}-point-1-label-${k}`
|
|
30002
|
+
}, label))), figure.points[1].labels.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
|
|
30003
|
+
key: `locked-figure-${i}-point-2-label-${k}`
|
|
30004
|
+
}, label)))));
|
|
30005
|
+
}
|
|
29991
30006
|
return null;
|
|
29992
30007
|
});
|
|
29993
30008
|
}
|
|
@@ -30326,7 +30341,9 @@ const GraphLockedLayer = props => {
|
|
|
30326
30341
|
|
|
30327
30342
|
const actions = {
|
|
30328
30343
|
global: {
|
|
30329
|
-
deleteIntent
|
|
30344
|
+
deleteIntent,
|
|
30345
|
+
changeInteractionMode,
|
|
30346
|
+
changeKeyboardInvitationVisibility
|
|
30330
30347
|
},
|
|
30331
30348
|
angle: {
|
|
30332
30349
|
movePoint
|
|
@@ -30418,6 +30435,20 @@ function clickPoint(index) {
|
|
|
30418
30435
|
index
|
|
30419
30436
|
};
|
|
30420
30437
|
}
|
|
30438
|
+
const CHANGE_INTERACTION_MODE = "point-change-interaction-mode";
|
|
30439
|
+
function changeInteractionMode(mode) {
|
|
30440
|
+
return {
|
|
30441
|
+
type: CHANGE_INTERACTION_MODE,
|
|
30442
|
+
mode
|
|
30443
|
+
};
|
|
30444
|
+
}
|
|
30445
|
+
const CHANGE_KEYBOARD_INVITATION_VISIBILITY = "change-keyboard-interaction-invitation-visibility";
|
|
30446
|
+
function changeKeyboardInvitationVisibility(shouldShow) {
|
|
30447
|
+
return {
|
|
30448
|
+
type: CHANGE_KEYBOARD_INVITATION_VISIBILITY,
|
|
30449
|
+
shouldShow
|
|
30450
|
+
};
|
|
30451
|
+
}
|
|
30421
30452
|
const MOVE_ALL = "move-all";
|
|
30422
30453
|
function moveAll(delta) {
|
|
30423
30454
|
return {
|
|
@@ -32275,15 +32306,18 @@ const MafsGraph = props => {
|
|
|
32275
32306
|
height
|
|
32276
32307
|
},
|
|
32277
32308
|
onKeyUp: event => {
|
|
32278
|
-
|
|
32279
|
-
dispatch(actions.global.deleteIntent());
|
|
32280
|
-
graphRef.current?.focus();
|
|
32281
|
-
}
|
|
32309
|
+
handleKeyboardEvent(event, state, dispatch);
|
|
32282
32310
|
},
|
|
32283
32311
|
"aria-label": fullGraphAriaLabel,
|
|
32284
32312
|
"aria-describedby": fullGraphAriaDescription ? descriptionId : undefined,
|
|
32285
32313
|
ref: graphRef,
|
|
32286
|
-
tabIndex: 0
|
|
32314
|
+
tabIndex: 0,
|
|
32315
|
+
onFocus: event => {
|
|
32316
|
+
handleFocusEvent(event, state, dispatch);
|
|
32317
|
+
},
|
|
32318
|
+
onBlur: event => {
|
|
32319
|
+
handleBlurEvent(event, state, dispatch);
|
|
32320
|
+
}
|
|
32287
32321
|
}, fullGraphAriaDescription && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
32288
32322
|
id: descriptionId,
|
|
32289
32323
|
tabIndex: -1,
|
|
@@ -32324,7 +32358,8 @@ const MafsGraph = props => {
|
|
|
32324
32358
|
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
32359
|
lockedFigures: props.lockedFigures,
|
|
32326
32360
|
range: state.range
|
|
32327
|
-
}))), props.
|
|
32361
|
+
}))), props.flags?.["mafs"]?.["interactive-graph-locked-features-labels"] && props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
|
|
32362
|
+
flags: props.flags,
|
|
32328
32363
|
lockedFigures: props.lockedFigures
|
|
32329
32364
|
}), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
32330
32365
|
style: {
|
|
@@ -32344,30 +32379,63 @@ const MafsGraph = props => {
|
|
|
32344
32379
|
}, /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
|
|
32345
32380
|
state,
|
|
32346
32381
|
dispatch
|
|
32347
|
-
})))))
|
|
32382
|
+
}))))), state.type === "point" && state.showKeyboardInteractionInvitation && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
32383
|
+
style: {
|
|
32384
|
+
textAlign: "center",
|
|
32385
|
+
backgroundColor: "white",
|
|
32386
|
+
border: "1px solid #21242C52",
|
|
32387
|
+
padding: "16px 0",
|
|
32388
|
+
boxShadow: "0px 8px 8px 0px #21242C14",
|
|
32389
|
+
// This translates the box to the center of the
|
|
32390
|
+
// graph Then backs it off by half of its
|
|
32391
|
+
// overall height so it's perfectly centered
|
|
32392
|
+
top: "50%",
|
|
32393
|
+
transform: "translateY(-50%)"
|
|
32394
|
+
}
|
|
32395
|
+
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, null, "Press ", /*#__PURE__*/React__namespace.createElement("strong", null, "Shift + Enter"), " to interact with the graph"))), renderGraphControls({
|
|
32348
32396
|
state,
|
|
32349
32397
|
dispatch,
|
|
32350
32398
|
width
|
|
32351
32399
|
})));
|
|
32352
32400
|
};
|
|
32353
|
-
const renderPointGraphControls = props =>
|
|
32354
|
-
|
|
32355
|
-
|
|
32356
|
-
|
|
32357
|
-
|
|
32358
|
-
}
|
|
32359
|
-
|
|
32360
|
-
|
|
32361
|
-
|
|
32362
|
-
|
|
32363
|
-
|
|
32364
|
-
|
|
32365
|
-
|
|
32366
|
-
|
|
32367
|
-
|
|
32368
|
-
|
|
32369
|
-
|
|
32370
|
-
|
|
32401
|
+
const renderPointGraphControls = props => {
|
|
32402
|
+
const {
|
|
32403
|
+
interactionMode,
|
|
32404
|
+
showRemovePointButton,
|
|
32405
|
+
focusedPointIndex
|
|
32406
|
+
} = props.state;
|
|
32407
|
+
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
32408
|
+
style: {
|
|
32409
|
+
flexDirection: "row",
|
|
32410
|
+
width: props.width
|
|
32411
|
+
}
|
|
32412
|
+
}, interactionMode === "keyboard" && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
32413
|
+
kind: "secondary",
|
|
32414
|
+
style: {
|
|
32415
|
+
width: "100%",
|
|
32416
|
+
marginLeft: "20px"
|
|
32417
|
+
},
|
|
32418
|
+
tabIndex: 0,
|
|
32419
|
+
onClick: () => {
|
|
32420
|
+
props.dispatch(actions.pointGraph.addPoint([0, 0]));
|
|
32421
|
+
}
|
|
32422
|
+
}, "Add Point"), interactionMode === "mouse" && showRemovePointButton && focusedPointIndex !== null && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
32423
|
+
id: REMOVE_BUTTON_ID,
|
|
32424
|
+
kind: "secondary",
|
|
32425
|
+
color: "destructive"
|
|
32426
|
+
// This button is meant to be interacted with by the mouse only
|
|
32427
|
+
// Never allow learners to tab to this button
|
|
32428
|
+
,
|
|
32429
|
+
tabIndex: -1,
|
|
32430
|
+
style: {
|
|
32431
|
+
width: "100%",
|
|
32432
|
+
marginLeft: "20px"
|
|
32433
|
+
},
|
|
32434
|
+
onClick: event => {
|
|
32435
|
+
props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));
|
|
32436
|
+
}
|
|
32437
|
+
}, "Remove Point"));
|
|
32438
|
+
};
|
|
32371
32439
|
const renderGraphControls = props => {
|
|
32372
32440
|
const {
|
|
32373
32441
|
state,
|
|
@@ -32391,6 +32459,34 @@ const renderGraphControls = props => {
|
|
|
32391
32459
|
return null;
|
|
32392
32460
|
}
|
|
32393
32461
|
};
|
|
32462
|
+
function handleFocusEvent(event, state, dispatch) {
|
|
32463
|
+
if (state.type === "point" && state.numPoints === "unlimited") {
|
|
32464
|
+
if (event.target.classList.contains("mafs-graph") && state.interactionMode === "mouse") {
|
|
32465
|
+
dispatch(actions.global.changeKeyboardInvitationVisibility(true));
|
|
32466
|
+
}
|
|
32467
|
+
}
|
|
32468
|
+
}
|
|
32469
|
+
function handleBlurEvent(event, state, dispatch) {
|
|
32470
|
+
if (state.type === "point" && state.numPoints === "unlimited") {
|
|
32471
|
+
dispatch(actions.global.changeKeyboardInvitationVisibility(false));
|
|
32472
|
+
}
|
|
32473
|
+
}
|
|
32474
|
+
function handleKeyboardEvent(event, state, dispatch) {
|
|
32475
|
+
if (state.type === "point" && state.numPoints === "unlimited") {
|
|
32476
|
+
if (event.key === "Backspace") {
|
|
32477
|
+
dispatch(actions.global.deleteIntent());
|
|
32478
|
+
|
|
32479
|
+
// After removing a point blur
|
|
32480
|
+
// It would be nice if this could focus on the graph but doing so
|
|
32481
|
+
// would trigger the message to prompt a learner to enter keyboard mode
|
|
32482
|
+
document.activeElement.blur();
|
|
32483
|
+
} else if (event.shiftKey && event.key === "Enter") {
|
|
32484
|
+
dispatch(actions.global.changeInteractionMode("keyboard"));
|
|
32485
|
+
} else if (state.interactionMode === "keyboard" && event.key === "a") {
|
|
32486
|
+
dispatch(actions.pointGraph.addPoint([0, 0]));
|
|
32487
|
+
}
|
|
32488
|
+
}
|
|
32489
|
+
}
|
|
32394
32490
|
|
|
32395
32491
|
// Calculate the difference between the min and max values of a range
|
|
32396
32492
|
const getRangeDiff = range => {
|
|
@@ -32560,7 +32656,9 @@ function initializeGraphState(params) {
|
|
|
32560
32656
|
coords: getPointCoords(graph, range, step),
|
|
32561
32657
|
numPoints: graph.numPoints || 0,
|
|
32562
32658
|
focusedPointIndex: null,
|
|
32563
|
-
showRemovePointButton: false
|
|
32659
|
+
showRemovePointButton: false,
|
|
32660
|
+
interactionMode: "mouse",
|
|
32661
|
+
showKeyboardInteractionInvitation: false
|
|
32564
32662
|
};
|
|
32565
32663
|
case "circle":
|
|
32566
32664
|
return {
|
|
@@ -32833,6 +32931,10 @@ function interactiveGraphReducer(state, action) {
|
|
|
32833
32931
|
return doDeleteIntent(state);
|
|
32834
32932
|
case CLICK_POINT:
|
|
32835
32933
|
return doClickPoint(state, action);
|
|
32934
|
+
case CHANGE_INTERACTION_MODE:
|
|
32935
|
+
return doChangeInteractionMode(state, action);
|
|
32936
|
+
case CHANGE_KEYBOARD_INVITATION_VISIBILITY:
|
|
32937
|
+
return doChangeKeyboardInvitationVisibility(state, action);
|
|
32836
32938
|
default:
|
|
32837
32939
|
throw new wonderStuffCore.UnreachableCaseError(action);
|
|
32838
32940
|
}
|
|
@@ -32884,6 +32986,33 @@ function doClickPoint(state, action) {
|
|
|
32884
32986
|
}
|
|
32885
32987
|
return state;
|
|
32886
32988
|
}
|
|
32989
|
+
function doChangeInteractionMode(state, action) {
|
|
32990
|
+
if (state.type !== "point") {
|
|
32991
|
+
return state;
|
|
32992
|
+
}
|
|
32993
|
+
if (state.numPoints === "unlimited") {
|
|
32994
|
+
const nextKeyboardInvitation = action.mode === "keyboard" ? false : state.showKeyboardInteractionInvitation;
|
|
32995
|
+
return {
|
|
32996
|
+
...state,
|
|
32997
|
+
interactionMode: action.mode,
|
|
32998
|
+
showKeyboardInteractionInvitation: nextKeyboardInvitation
|
|
32999
|
+
};
|
|
33000
|
+
}
|
|
33001
|
+
return state;
|
|
33002
|
+
}
|
|
33003
|
+
function doChangeKeyboardInvitationVisibility(state, action) {
|
|
33004
|
+
if (state.type !== "point") {
|
|
33005
|
+
return state;
|
|
33006
|
+
}
|
|
33007
|
+
if (state.numPoints === "unlimited") {
|
|
33008
|
+
return {
|
|
33009
|
+
...state,
|
|
33010
|
+
showKeyboardInteractionInvitation: action.shouldShow,
|
|
33011
|
+
hasBeenInteractedWith: true
|
|
33012
|
+
};
|
|
33013
|
+
}
|
|
33014
|
+
return state;
|
|
33015
|
+
}
|
|
32887
33016
|
function doMovePointInFigure(state, action) {
|
|
32888
33017
|
switch (state.type) {
|
|
32889
33018
|
case "segment":
|
|
@@ -33652,7 +33781,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
33652
33781
|
const snapTo = graph.type === "polygon" ? graph.snapTo : null;
|
|
33653
33782
|
const showAngles = graph.type === "polygon" ? graph.showAngles : null;
|
|
33654
33783
|
const showSides = graph.type === "polygon" ? graph.showSides : null;
|
|
33655
|
-
const startCoords = "startCoords" in graph ? graph.startCoords :
|
|
33784
|
+
const startCoords = "startCoords" in graph ? graph.startCoords : undefined;
|
|
33656
33785
|
const originalPropsRef = React.useRef(props);
|
|
33657
33786
|
const latestPropsRef = wonderBlocksCore.useLatestRef(props);
|
|
33658
33787
|
React.useEffect(() => {
|
|
@@ -34960,7 +35089,7 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
34960
35089
|
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
34961
35090
|
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
34962
35091
|
return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends({}, this.props, {
|
|
34963
|
-
|
|
35092
|
+
flags: this.props.apiOptions?.flags,
|
|
34964
35093
|
ref: this.mafsRef,
|
|
34965
35094
|
gridStep: gridStep,
|
|
34966
35095
|
snapStep: snapStep,
|
|
@@ -35557,7 +35686,7 @@ function shouldUseMafs(mafsFlags, graph) {
|
|
|
35557
35686
|
const staticTransform$3 = ___default["default"].identity;
|
|
35558
35687
|
var InteractiveGraph$1 = {
|
|
35559
35688
|
name: "interactive-graph",
|
|
35560
|
-
displayName: "Interactive graph",
|
|
35689
|
+
displayName: "Interactive graph (Assessments only)",
|
|
35561
35690
|
widget: InteractiveGraph,
|
|
35562
35691
|
staticTransform: staticTransform$3
|
|
35563
35692
|
};
|
|
@@ -37511,11 +37640,6 @@ var LabelImage$1 = {
|
|
|
37511
37640
|
};
|
|
37512
37641
|
|
|
37513
37642
|
/* 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
37643
|
var ItemState = /*#__PURE__*/function (ItemState) {
|
|
37520
37644
|
ItemState["STATIC"] = "static";
|
|
37521
37645
|
ItemState["DRAGGING"] = "dragging";
|
|
@@ -37542,7 +37666,7 @@ class Placeholder extends React__namespace.Component {
|
|
|
37542
37666
|
const {
|
|
37543
37667
|
layout
|
|
37544
37668
|
} = this.props;
|
|
37545
|
-
const className = aphrodite.css(styles$d.card, styles$d.placeholder, layout ===
|
|
37669
|
+
const className = aphrodite.css(styles$d.card, styles$d.placeholder, layout === "horizontal" && styles$d.horizontalCard);
|
|
37546
37670
|
const style = {
|
|
37547
37671
|
width: this.props.width,
|
|
37548
37672
|
height: this.props.height
|
|
@@ -37749,7 +37873,7 @@ class Draggable extends React__namespace.Component {
|
|
|
37749
37873
|
// We need to keep backwards compatbility with rules specified directly
|
|
37750
37874
|
// in CSS. Hence the hacky tacking on of manual classNames.
|
|
37751
37875
|
// See sortable.less for details.
|
|
37752
|
-
let className = aphrodite.css(styles$d.card, styles$d.draggable, layout ===
|
|
37876
|
+
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
37877
|
if (!includePadding) {
|
|
37754
37878
|
className += " perseus-sortable-draggable-unpadded";
|
|
37755
37879
|
}
|
|
@@ -37789,7 +37913,7 @@ class Draggable extends React__namespace.Component {
|
|
|
37789
37913
|
}
|
|
37790
37914
|
class Sortable extends React__namespace.Component {
|
|
37791
37915
|
static defaultProps = {
|
|
37792
|
-
layout:
|
|
37916
|
+
layout: "horizontal",
|
|
37793
37917
|
padding: true,
|
|
37794
37918
|
disabled: false,
|
|
37795
37919
|
constraints: {},
|
|
@@ -37884,7 +38008,7 @@ class Sortable extends React__namespace.Component {
|
|
|
37884
38008
|
if (constraints?.width) {
|
|
37885
38009
|
// Items must be at least as wide as the specified constraint
|
|
37886
38010
|
syncWidth = ___default["default"].max(widths.concat(constraints.width));
|
|
37887
|
-
} else if (layout ===
|
|
38011
|
+
} else if (layout === "vertical") {
|
|
37888
38012
|
// Sync widths to get a clean column
|
|
37889
38013
|
syncWidth = ___default["default"].max(widths);
|
|
37890
38014
|
}
|
|
@@ -37892,7 +38016,7 @@ class Sortable extends React__namespace.Component {
|
|
|
37892
38016
|
if (constraints?.height) {
|
|
37893
38017
|
// Items must be at least as high as the specified constraint
|
|
37894
38018
|
syncHeight = ___default["default"].max(heights.concat(constraints.height));
|
|
37895
|
-
} else if (layout ===
|
|
38019
|
+
} else if (layout === "horizontal") {
|
|
37896
38020
|
// Sync widths to get a clean row
|
|
37897
38021
|
syncHeight = ___default["default"].max(heights);
|
|
37898
38022
|
}
|
|
@@ -37974,7 +38098,7 @@ class Sortable extends React__namespace.Component {
|
|
|
37974
38098
|
|
|
37975
38099
|
// @ts-expect-error - TS2551 - Property 'splice' does not exist on type 'readonly SortableItem[]'. Did you mean 'slice'?
|
|
37976
38100
|
items.splice(currentIndex, 1);
|
|
37977
|
-
if (this.props.layout ===
|
|
38101
|
+
if (this.props.layout === "horizontal") {
|
|
37978
38102
|
// @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'. | TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
|
|
37979
38103
|
const midWidth = $draggable.offset().left - $sortable.offset().left;
|
|
37980
38104
|
let sumWidth = 0;
|
|
@@ -38088,19 +38212,19 @@ class Sortable extends React__namespace.Component {
|
|
|
38088
38212
|
// We need to keep backwards compatbility with rules specified directly
|
|
38089
38213
|
// in CSS. See sortable.less for details.
|
|
38090
38214
|
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 ===
|
|
38215
|
+
const syncWidth = this.props.constraints?.width || layout === "vertical";
|
|
38216
|
+
const syncHeight = this.props.constraints?.height || layout === "horizontal";
|
|
38093
38217
|
___default["default"].each(this.state.items, function (item, i, items) {
|
|
38094
38218
|
const isLast = i === items.length - 1;
|
|
38095
38219
|
const isStatic = item.state === ItemState.STATIC || item.state === ItemState.DISABLED;
|
|
38096
38220
|
let margin;
|
|
38097
38221
|
|
|
38098
38222
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38099
|
-
if (this.props.layout ===
|
|
38223
|
+
if (this.props.layout === "horizontal") {
|
|
38100
38224
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38101
38225
|
margin = "0 " + this.props.margin + "px 0 0"; // right
|
|
38102
38226
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38103
|
-
} else if (this.props.layout ===
|
|
38227
|
+
} else if (this.props.layout === "vertical") {
|
|
38104
38228
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38105
38229
|
margin = "0 0 " + this.props.margin + "px 0"; // bottom
|
|
38106
38230
|
}
|
|
@@ -38222,7 +38346,6 @@ const styles$d = aphrodite.StyleSheet.create({
|
|
|
38222
38346
|
}
|
|
38223
38347
|
});
|
|
38224
38348
|
|
|
38225
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
38226
38349
|
const {
|
|
38227
38350
|
shuffle: shuffle$1,
|
|
38228
38351
|
seededRNG
|
|
@@ -38230,18 +38353,6 @@ const {
|
|
|
38230
38353
|
const HACKY_CSS_CLASSNAME = "perseus-widget-matcher";
|
|
38231
38354
|
class Matcher extends React__namespace.Component {
|
|
38232
38355
|
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
38356
|
static defaultProps = {
|
|
38246
38357
|
left: [],
|
|
38247
38358
|
right: [],
|
|
@@ -38378,7 +38489,7 @@ class Matcher extends React__namespace.Component {
|
|
|
38378
38489
|
className: aphrodite.css(styles$c.column)
|
|
38379
38490
|
}, /*#__PURE__*/React__namespace.createElement(Sortable, {
|
|
38380
38491
|
options: left,
|
|
38381
|
-
layout:
|
|
38492
|
+
layout: "vertical",
|
|
38382
38493
|
padding: this.props.padding,
|
|
38383
38494
|
disabled: !this.props.orderMatters,
|
|
38384
38495
|
constraints: constraints,
|
|
@@ -38391,11 +38502,9 @@ class Matcher extends React__namespace.Component {
|
|
|
38391
38502
|
ref: "left"
|
|
38392
38503
|
})), /*#__PURE__*/React__namespace.createElement("td", {
|
|
38393
38504
|
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
|
-
, {
|
|
38505
|
+
}, /*#__PURE__*/React__namespace.createElement(Sortable, {
|
|
38397
38506
|
options: right,
|
|
38398
|
-
layout:
|
|
38507
|
+
layout: "vertical",
|
|
38399
38508
|
padding: this.props.padding,
|
|
38400
38509
|
constraints: constraints,
|
|
38401
38510
|
onMeasure: this.onMeasureRight,
|
|
@@ -38916,6 +39025,7 @@ const staticTransform$2 = editorProps => {
|
|
|
38916
39025
|
var Matrix$1 = {
|
|
38917
39026
|
name: "matrix",
|
|
38918
39027
|
displayName: "Matrix",
|
|
39028
|
+
hidden: true,
|
|
38919
39029
|
widget: Matrix,
|
|
38920
39030
|
transform: propTransform$1,
|
|
38921
39031
|
staticTransform: staticTransform$2,
|
|
@@ -39017,9 +39127,8 @@ const Measurer = createReactClass__default["default"]({
|
|
|
39017
39127
|
getUserInput: function () {
|
|
39018
39128
|
return {};
|
|
39019
39129
|
},
|
|
39020
|
-
simpleValidate: function (
|
|
39021
|
-
|
|
39022
|
-
return Measurer.validate(this.getUserInput(), rubric);
|
|
39130
|
+
simpleValidate: function () {
|
|
39131
|
+
return noopValidator(1);
|
|
39023
39132
|
},
|
|
39024
39133
|
focus: $__default["default"].noop,
|
|
39025
39134
|
render: function () {
|
|
@@ -39048,14 +39157,7 @@ const Measurer = createReactClass__default["default"]({
|
|
|
39048
39157
|
}
|
|
39049
39158
|
});
|
|
39050
39159
|
___default["default"].extend(Measurer, {
|
|
39051
|
-
validate:
|
|
39052
|
-
return {
|
|
39053
|
-
type: "points",
|
|
39054
|
-
earned: 1,
|
|
39055
|
-
total: 1,
|
|
39056
|
-
message: null
|
|
39057
|
-
};
|
|
39058
|
-
}
|
|
39160
|
+
validate: noopValidator(1)
|
|
39059
39161
|
});
|
|
39060
39162
|
const propUpgrades = {
|
|
39061
39163
|
"1": v0props => {
|
|
@@ -39072,6 +39174,7 @@ const propUpgrades = {
|
|
|
39072
39174
|
var Measurer$1 = {
|
|
39073
39175
|
name: "measurer",
|
|
39074
39176
|
displayName: "Measurer",
|
|
39177
|
+
hidden: true,
|
|
39075
39178
|
widget: Measurer,
|
|
39076
39179
|
version: {
|
|
39077
39180
|
major: 1,
|
|
@@ -39949,28 +40052,19 @@ class MoleculeWidget extends React__namespace.Component {
|
|
|
39949
40052
|
static defaultProps = {
|
|
39950
40053
|
rotationAngle: 0
|
|
39951
40054
|
};
|
|
39952
|
-
|
|
39953
|
-
|
|
39954
|
-
|
|
39955
|
-
|
|
39956
|
-
|
|
39957
|
-
message: null
|
|
39958
|
-
};
|
|
39959
|
-
};
|
|
40055
|
+
|
|
40056
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
40057
|
+
simpleValidate() {
|
|
40058
|
+
return noopValidator();
|
|
40059
|
+
}
|
|
39960
40060
|
getUserInput = () => {
|
|
39961
40061
|
return [];
|
|
39962
40062
|
};
|
|
39963
|
-
|
|
39964
|
-
|
|
39965
|
-
|
|
39966
|
-
|
|
39967
|
-
|
|
39968
|
-
type: "points",
|
|
39969
|
-
earned: 0,
|
|
39970
|
-
total: 0,
|
|
39971
|
-
message: null
|
|
39972
|
-
};
|
|
39973
|
-
};
|
|
40063
|
+
|
|
40064
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
40065
|
+
validate() {
|
|
40066
|
+
return noopValidator();
|
|
40067
|
+
}
|
|
39974
40068
|
render() {
|
|
39975
40069
|
return /*#__PURE__*/React__namespace.createElement(Molecule, {
|
|
39976
40070
|
widgetId: this.props.widgetId,
|
|
@@ -40569,10 +40663,6 @@ var NumberLine$1 = {
|
|
|
40569
40663
|
};
|
|
40570
40664
|
|
|
40571
40665
|
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
40666
|
render() {
|
|
40577
40667
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
40578
40668
|
className: "card-wrap " + ClassNames.INTERACTIVE,
|
|
@@ -40582,7 +40672,7 @@ class PlaceholderCard extends React__namespace.Component {
|
|
|
40582
40672
|
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
40583
40673
|
className: "card placeholder",
|
|
40584
40674
|
style: {
|
|
40585
|
-
height: this.props.
|
|
40675
|
+
height: this.props.width
|
|
40586
40676
|
}
|
|
40587
40677
|
}));
|
|
40588
40678
|
}
|
|
@@ -40596,29 +40686,10 @@ class DragHintCard extends React__namespace.Component {
|
|
|
40596
40686
|
}));
|
|
40597
40687
|
}
|
|
40598
40688
|
}
|
|
40599
|
-
const PropTypePosition = PropTypes__default["default"].shape({
|
|
40600
|
-
left: PropTypes__default["default"].number,
|
|
40601
|
-
top: PropTypes__default["default"].number
|
|
40602
|
-
});
|
|
40603
40689
|
class Card extends React__namespace.Component {
|
|
40604
40690
|
static contextType = PerseusI18nContext;
|
|
40605
40691
|
// @ts-expect-error - TS2564 - Property 'mouseMoveUpBound' has no initializer and is not definitely assigned in the constructor.
|
|
40606
40692
|
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
40693
|
static defaultProps = {
|
|
40623
40694
|
stack: false,
|
|
40624
40695
|
animating: false,
|
|
@@ -40656,7 +40727,7 @@ class Card extends React__namespace.Component {
|
|
|
40656
40727
|
this.props.fakeRef !== nextProps.fakeRef;
|
|
40657
40728
|
}
|
|
40658
40729
|
componentDidUpdate(prevProps, prevState) {
|
|
40659
|
-
if (this.props.animating && !prevProps.animating) {
|
|
40730
|
+
if (this.props.animating && !prevProps.animating && this.props.animateTo && this.props.startOffset) {
|
|
40660
40731
|
// If we just were changed into animating, start the animation.
|
|
40661
40732
|
// We pick the animation speed based on the distance that the card
|
|
40662
40733
|
// needs to travel. (Why sqrt? Just because it looks nice -- with a
|
|
@@ -40723,8 +40794,8 @@ class Card extends React__namespace.Component {
|
|
|
40723
40794
|
if (this.props.floating) {
|
|
40724
40795
|
style = {
|
|
40725
40796
|
position: "absolute",
|
|
40726
|
-
left: this.props.startOffset
|
|
40727
|
-
top: this.props.startOffset
|
|
40797
|
+
left: this.props.startOffset?.left,
|
|
40798
|
+
top: this.props.startOffset?.top
|
|
40728
40799
|
};
|
|
40729
40800
|
}
|
|
40730
40801
|
if (this.props.width) {
|
|
@@ -40734,7 +40805,7 @@ class Card extends React__namespace.Component {
|
|
|
40734
40805
|
if (this.props.stack) {
|
|
40735
40806
|
className.push("stack");
|
|
40736
40807
|
}
|
|
40737
|
-
if (this.props.floating && !this.props.animating) {
|
|
40808
|
+
if (this.props.floating && !this.props.animating && this.props.mouse && this.props.startMouse) {
|
|
40738
40809
|
className.push("dragging");
|
|
40739
40810
|
style.left += this.props.mouse.left - this.props.startMouse.left;
|
|
40740
40811
|
style.top += this.props.mouse.top - this.props.startMouse.top;
|
|
@@ -40759,14 +40830,14 @@ class Card extends React__namespace.Component {
|
|
|
40759
40830
|
}
|
|
40760
40831
|
}
|
|
40761
40832
|
const NORMAL = "normal",
|
|
40762
|
-
HORIZONTAL
|
|
40833
|
+
HORIZONTAL = "horizontal";
|
|
40763
40834
|
class Orderer extends React__namespace.Component {
|
|
40764
40835
|
static defaultProps = {
|
|
40765
40836
|
current: [],
|
|
40766
40837
|
options: [],
|
|
40767
40838
|
correctOptions: [],
|
|
40768
40839
|
height: NORMAL,
|
|
40769
|
-
layout: HORIZONTAL
|
|
40840
|
+
layout: HORIZONTAL,
|
|
40770
40841
|
linterContext: PerseusLinter.linterContextDefault
|
|
40771
40842
|
};
|
|
40772
40843
|
state = {
|
|
@@ -40919,7 +40990,7 @@ class Orderer extends React__namespace.Component {
|
|
|
40919
40990
|
};
|
|
40920
40991
|
findCorrectIndex = (draggable, list) => {
|
|
40921
40992
|
// Find the correct index for a card given the current cards.
|
|
40922
|
-
const isHorizontal = this.props.layout === HORIZONTAL
|
|
40993
|
+
const isHorizontal = this.props.layout === HORIZONTAL;
|
|
40923
40994
|
// eslint-disable-next-line react/no-string-refs
|
|
40924
40995
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
40925
40996
|
const $dragList = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.dragList));
|
|
@@ -40969,7 +41040,7 @@ class Orderer extends React__namespace.Component {
|
|
|
40969
41040
|
if (draggable == null) {
|
|
40970
41041
|
return false;
|
|
40971
41042
|
}
|
|
40972
|
-
const isHorizontal = this.props.layout === HORIZONTAL
|
|
41043
|
+
const isHorizontal = this.props.layout === HORIZONTAL,
|
|
40973
41044
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
40974
41045
|
$draggable = $__default["default"](ReactDOM__default["default"].findDOMNode(draggable)),
|
|
40975
41046
|
// eslint-disable-next-line react/no-string-refs
|
|
@@ -41149,6 +41220,7 @@ ___default["default"].extend(Orderer, {
|
|
|
41149
41220
|
var Orderer$1 = {
|
|
41150
41221
|
name: "orderer",
|
|
41151
41222
|
displayName: "Orderer",
|
|
41223
|
+
hidden: true,
|
|
41152
41224
|
widget: Orderer,
|
|
41153
41225
|
isLintable: true
|
|
41154
41226
|
};
|
|
@@ -42958,13 +43030,10 @@ class Passage extends React__namespace.Component {
|
|
|
42958
43030
|
startLineNumbersAfter: 0,
|
|
42959
43031
|
stylesAreApplied: false
|
|
42960
43032
|
};
|
|
42961
|
-
|
|
42962
|
-
|
|
42963
|
-
|
|
42964
|
-
|
|
42965
|
-
total: 0,
|
|
42966
|
-
message: null
|
|
42967
|
-
};
|
|
43033
|
+
|
|
43034
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
43035
|
+
static validate() {
|
|
43036
|
+
return noopValidator();
|
|
42968
43037
|
}
|
|
42969
43038
|
componentDidMount() {
|
|
42970
43039
|
this._updateState();
|
|
@@ -43183,8 +43252,10 @@ class Passage extends React__namespace.Component {
|
|
|
43183
43252
|
getUserInput() {
|
|
43184
43253
|
return null;
|
|
43185
43254
|
}
|
|
43186
|
-
|
|
43187
|
-
|
|
43255
|
+
|
|
43256
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
43257
|
+
simpleValidate() {
|
|
43258
|
+
return noopValidator();
|
|
43188
43259
|
}
|
|
43189
43260
|
|
|
43190
43261
|
/**
|
|
@@ -43325,6 +43396,7 @@ class Passage extends React__namespace.Component {
|
|
|
43325
43396
|
var Passage$1 = {
|
|
43326
43397
|
name: "passage",
|
|
43327
43398
|
displayName: "Passage (SAT only)",
|
|
43399
|
+
hidden: true,
|
|
43328
43400
|
widget: Passage,
|
|
43329
43401
|
transform: editorProps => {
|
|
43330
43402
|
return ___default["default"].pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
@@ -43339,17 +43411,18 @@ class PassageRefTarget extends React__namespace.Component {
|
|
|
43339
43411
|
content: "",
|
|
43340
43412
|
linterContext: PerseusLinter.linterContextDefault
|
|
43341
43413
|
};
|
|
43342
|
-
|
|
43343
|
-
|
|
43344
|
-
|
|
43345
|
-
|
|
43346
|
-
total: 0,
|
|
43347
|
-
message: null
|
|
43348
|
-
};
|
|
43414
|
+
|
|
43415
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
43416
|
+
static validate() {
|
|
43417
|
+
return noopValidator();
|
|
43349
43418
|
}
|
|
43419
|
+
|
|
43420
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
43350
43421
|
getUserInput = () => {
|
|
43351
|
-
return
|
|
43422
|
+
return;
|
|
43352
43423
|
};
|
|
43424
|
+
|
|
43425
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
43353
43426
|
change = (() => {
|
|
43354
43427
|
var _this = this;
|
|
43355
43428
|
return function () {
|
|
@@ -43359,9 +43432,11 @@ class PassageRefTarget extends React__namespace.Component {
|
|
|
43359
43432
|
return change.apply(_this, args);
|
|
43360
43433
|
};
|
|
43361
43434
|
})();
|
|
43362
|
-
|
|
43363
|
-
|
|
43364
|
-
|
|
43435
|
+
|
|
43436
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
43437
|
+
simpleValidate() {
|
|
43438
|
+
return noopValidator();
|
|
43439
|
+
}
|
|
43365
43440
|
render() {
|
|
43366
43441
|
return /*#__PURE__*/React__namespace.createElement(Renderer, {
|
|
43367
43442
|
content: this.props.content
|
|
@@ -44417,6 +44492,7 @@ const staticTransform = ___default["default"].identity;
|
|
|
44417
44492
|
var Plotter$1 = {
|
|
44418
44493
|
name: "plotter",
|
|
44419
44494
|
displayName: "Plotter",
|
|
44495
|
+
hidden: true,
|
|
44420
44496
|
widget: Plotter,
|
|
44421
44497
|
staticTransform: staticTransform
|
|
44422
44498
|
};
|
|
@@ -44467,29 +44543,14 @@ var PythonProgram$1 = {
|
|
|
44467
44543
|
widget: PythonProgram
|
|
44468
44544
|
};
|
|
44469
44545
|
|
|
44470
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
44471
44546
|
const {
|
|
44472
44547
|
shuffle
|
|
44473
44548
|
} = Util;
|
|
44474
|
-
const HORIZONTAL = "horizontal";
|
|
44475
|
-
const VERTICAL = "vertical";
|
|
44476
44549
|
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
|
-
};
|
|
44550
|
+
_isMounted = false;
|
|
44490
44551
|
static defaultProps = {
|
|
44491
44552
|
correct: [],
|
|
44492
|
-
layout:
|
|
44553
|
+
layout: "horizontal",
|
|
44493
44554
|
padding: true,
|
|
44494
44555
|
problemNum: 0,
|
|
44495
44556
|
onChange: function () {},
|
|
@@ -44567,9 +44628,7 @@ class Sorter extends React__namespace.Component {
|
|
|
44567
44628
|
const marginPx = apiOptions.isMobile ? 8 : 5;
|
|
44568
44629
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
44569
44630
|
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
|
-
, {
|
|
44631
|
+
}, /*#__PURE__*/React__namespace.createElement(Sortable, {
|
|
44573
44632
|
options: options,
|
|
44574
44633
|
layout: this.props.layout,
|
|
44575
44634
|
margin: marginPx,
|
|
@@ -44592,7 +44651,6 @@ var Sorter$1 = {
|
|
|
44592
44651
|
const {
|
|
44593
44652
|
assert
|
|
44594
44653
|
} = InteractiveUtil;
|
|
44595
|
-
|
|
44596
44654
|
/* Input handling: Maps a (row, column) pair to a unique ref used by React,
|
|
44597
44655
|
* and extracts (row, column) pairs from input paths, used to allow outsiders
|
|
44598
44656
|
* to focus, blur, set input values, etc. */
|
|
@@ -44619,16 +44677,6 @@ const getRefForPath = function (path) {
|
|
|
44619
44677
|
};
|
|
44620
44678
|
class Table extends React__namespace.Component {
|
|
44621
44679
|
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
44680
|
static defaultProps = function () {
|
|
44633
44681
|
const defaultRows = 4;
|
|
44634
44682
|
const defaultColumns = 1;
|
|
@@ -44972,21 +45020,18 @@ class Video extends React__namespace.Component {
|
|
|
44972
45020
|
* This is the widget's grading function.
|
|
44973
45021
|
* Points for videos are tallied by the embedded video itself, in the case
|
|
44974
45022
|
* of Khan Academy videos.
|
|
44975
|
-
*/
|
|
44976
|
-
static validate(
|
|
44977
|
-
return
|
|
44978
|
-
type: "points",
|
|
44979
|
-
earned: 0,
|
|
44980
|
-
total: 0,
|
|
44981
|
-
message: null
|
|
44982
|
-
};
|
|
45023
|
+
*/ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
45024
|
+
static validate() {
|
|
45025
|
+
return noopValidator();
|
|
44983
45026
|
}
|
|
44984
45027
|
getUserInput = () => {
|
|
44985
45028
|
return null;
|
|
44986
45029
|
};
|
|
44987
|
-
|
|
44988
|
-
|
|
44989
|
-
|
|
45030
|
+
|
|
45031
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
45032
|
+
simpleValidate() {
|
|
45033
|
+
return noopValidator();
|
|
45034
|
+
}
|
|
44990
45035
|
change = (() => {
|
|
44991
45036
|
var _this = this;
|
|
44992
45037
|
return function () {
|
|
@@ -45099,7 +45144,7 @@ const init = function (options) {
|
|
|
45099
45144
|
|
|
45100
45145
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45101
45146
|
const libName = "@khanacademy/perseus";
|
|
45102
|
-
const libVersion = "
|
|
45147
|
+
const libVersion = "34.1.0";
|
|
45103
45148
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45104
45149
|
|
|
45105
45150
|
const apiVersion = {
|
|
@@ -48614,7 +48659,6 @@ exports.SizingUtils = sizingUtils;
|
|
|
48614
48659
|
exports.TableWidget = Table$1;
|
|
48615
48660
|
exports.Util = Util;
|
|
48616
48661
|
exports.WIDGET_PROP_DENYLIST = denylist;
|
|
48617
|
-
exports.WidgetJsonifyDeprecated = WidgetJsonifyDeprecated;
|
|
48618
48662
|
exports.Widgets = widgets$1;
|
|
48619
48663
|
exports.addWidget = addWidget;
|
|
48620
48664
|
exports.apiVersion = apiVersion;
|