@khanacademy/perseus 33.2.1 → 34.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/sortable.d.ts +1 -4
- package/dist/es/index.js +541 -585
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +540 -587
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +2 -0
- package/dist/perseus-types.d.ts +11 -6
- package/dist/server-item-renderer.d.ts +2 -0
- package/dist/types.d.ts +2 -2
- package/dist/util/answer-types.d.ts +1 -1
- package/dist/util.d.ts +2 -1
- package/dist/widgets/__shared__/noop-validator.d.ts +11 -0
- package/dist/widgets/categorizer/categorizer.d.ts +3 -1
- package/dist/widgets/cs-program/cs-program.d.ts +23 -17
- package/dist/widgets/dropdown/dropdown-validator.d.ts +4 -0
- package/dist/widgets/dropdown/dropdown.d.ts +1 -5
- package/dist/widgets/dropdown/dropdown.types.d.ts +5 -0
- package/dist/widgets/expression/expression-validator.d.ts +5 -0
- package/dist/widgets/expression/expression.d.ts +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/interactive-graph.d.ts +3 -0
- package/dist/widgets/interactive-graphs/graph-locked-labels-layer.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/components/movable-point-view.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/components/movable-point.d.ts +2 -1
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +15 -4
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +3 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +8 -1
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +2 -1
- package/dist/widgets/interactive-graphs/types.d.ts +5 -2
- package/dist/widgets/label-image/label-image.d.ts +4 -0
- package/dist/widgets/matcher/matcher.d.ts +17 -46
- package/dist/widgets/numeric-input/numeric-input-validator.d.ts +6 -0
- package/dist/widgets/numeric-input/numeric-input.d.ts +1 -5
- package/dist/widgets/numeric-input/numeric-input.types.d.ts +5 -0
- package/dist/widgets/orderer/orderer.d.ts +6 -5
- package/dist/widgets/sorter/sorter.d.ts +18 -44
- package/dist/widgets/table/table.d.ts +20 -24
- package/package.json +2 -1
- package/dist/mixins/widget-jsonify-deprecated.d.ts +0 -5
package/dist/index.js
CHANGED
|
@@ -4,7 +4,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var KAS = require('@khanacademy/kas');
|
|
6
6
|
var mathInput = require('@khanacademy/math-input');
|
|
7
|
-
var perseusCore = require('@khanacademy/perseus-core');
|
|
8
7
|
var PerseusLinter = require('@khanacademy/perseus-linter');
|
|
9
8
|
var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
|
|
10
9
|
var Tooltip$1 = require('@khanacademy/wonder-blocks-tooltip');
|
|
@@ -19,6 +18,7 @@ var wonderBlocksPopover = require('@khanacademy/wonder-blocks-popover');
|
|
|
19
18
|
var wonderBlocksTokens = require('@khanacademy/wonder-blocks-tokens');
|
|
20
19
|
var $ = require('jquery');
|
|
21
20
|
var uuid = require('uuid');
|
|
21
|
+
var perseusCore = require('@khanacademy/perseus-core');
|
|
22
22
|
var PropTypes = require('prop-types');
|
|
23
23
|
var kmath = require('@khanacademy/kmath');
|
|
24
24
|
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
@@ -845,23 +845,6 @@ var dependencies = /*#__PURE__*/Object.freeze({
|
|
|
845
845
|
useDependencies: useDependencies
|
|
846
846
|
});
|
|
847
847
|
|
|
848
|
-
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
849
|
-
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
850
|
-
// which get's the injected logger.
|
|
851
|
-
/**
|
|
852
|
-
* Provides logging infrastructure for Perseus
|
|
853
|
-
*/
|
|
854
|
-
const Log = {
|
|
855
|
-
// Logs a message
|
|
856
|
-
log: (message, extra) => {
|
|
857
|
-
getDependencies().Log.log(message, extra);
|
|
858
|
-
},
|
|
859
|
-
// Logs an error with a message
|
|
860
|
-
error: (message, kind, extra) => {
|
|
861
|
-
getDependencies().Log.error(message, kind, extra);
|
|
862
|
-
}
|
|
863
|
-
};
|
|
864
|
-
|
|
865
848
|
/**
|
|
866
849
|
* These are things that widgets should exclude when serializing themselves.
|
|
867
850
|
*
|
|
@@ -1304,6 +1287,23 @@ const ClassNames = {
|
|
|
1304
1287
|
MOBILE: "perseus-mobile"
|
|
1305
1288
|
};
|
|
1306
1289
|
|
|
1290
|
+
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
1291
|
+
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
1292
|
+
// which get's the injected logger.
|
|
1293
|
+
/**
|
|
1294
|
+
* Provides logging infrastructure for Perseus
|
|
1295
|
+
*/
|
|
1296
|
+
const Log = {
|
|
1297
|
+
// Logs a message
|
|
1298
|
+
log: (message, extra) => {
|
|
1299
|
+
getDependencies().Log.log(message, extra);
|
|
1300
|
+
},
|
|
1301
|
+
// Logs an error with a message
|
|
1302
|
+
error: (message, kind, extra) => {
|
|
1303
|
+
getDependencies().Log.error(message, kind, extra);
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
1307
|
const KhanMath = {
|
|
1308
1308
|
// Simplify formulas before display
|
|
1309
1309
|
cleanMath: function (expr) {
|
|
@@ -2219,6 +2219,176 @@ const KhanAnswerTypes = {
|
|
|
2219
2219
|
}
|
|
2220
2220
|
};
|
|
2221
2221
|
|
|
2222
|
+
/**
|
|
2223
|
+
* Get the character used for separating decimals.
|
|
2224
|
+
*/
|
|
2225
|
+
const getDecimalSeparator = locale => {
|
|
2226
|
+
switch (locale) {
|
|
2227
|
+
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
2228
|
+
// locale properly.
|
|
2229
|
+
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
2230
|
+
//
|
|
2231
|
+
// Supported locales in Chrome:
|
|
2232
|
+
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
2233
|
+
case "ka":
|
|
2234
|
+
return ",";
|
|
2235
|
+
default:
|
|
2236
|
+
const numberWithDecimalSeparator = 1.1;
|
|
2237
|
+
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
2238
|
+
// support Safari 12.
|
|
2239
|
+
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
2240
|
+
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
2241
|
+
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
2242
|
+
.match(/[^\d\u0661\u06F1]/);
|
|
2243
|
+
return match?.[0] ?? ".";
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
|
|
2247
|
+
/* Content creators input a list of answers which are matched from top to
|
|
2248
|
+
* bottom. The intent is that they can include spcific solutions which should
|
|
2249
|
+
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
2250
|
+
* general.
|
|
2251
|
+
*
|
|
2252
|
+
* We iterate through each answer, trying to match it with the user's input
|
|
2253
|
+
* using the following angorithm:
|
|
2254
|
+
* - Try to parse the user's input. If it doesn't parse then return "not
|
|
2255
|
+
* graded".
|
|
2256
|
+
* - For each answer:
|
|
2257
|
+
* ~ Try to validate the user's input against the answer. The answer is
|
|
2258
|
+
* expected to parse.
|
|
2259
|
+
* ~ If the user's input validates (the validator judges it "correct"), we've
|
|
2260
|
+
* matched and can stop considering answers.
|
|
2261
|
+
* - If there were no matches or the matching answer is considered "ungraded",
|
|
2262
|
+
* show the user an error. TODO(joel) - what error?
|
|
2263
|
+
* - Otherwise, pass through the resulting points and message.
|
|
2264
|
+
*/
|
|
2265
|
+
function expressionValidator(userInput, rubric) {
|
|
2266
|
+
let onInputError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
|
|
2267
|
+
let strings = arguments.length > 3 ? arguments[3] : undefined;
|
|
2268
|
+
let locale = arguments.length > 4 ? arguments[4] : undefined;
|
|
2269
|
+
const options = ___default["default"].clone(rubric);
|
|
2270
|
+
___default["default"].extend(options, {
|
|
2271
|
+
decimal_separator: getDecimalSeparator(locale)
|
|
2272
|
+
});
|
|
2273
|
+
const createValidator = answer => {
|
|
2274
|
+
// We give options to KAS.parse here because it is parsing the
|
|
2275
|
+
// solution answer, not the student answer, and we don't want a
|
|
2276
|
+
// solution to work if the student is using a different language
|
|
2277
|
+
// (different from the content creation language, ie. English).
|
|
2278
|
+
const expression = KAS__namespace.parse(answer.value, rubric);
|
|
2279
|
+
// An answer may not be parsed if the expression was defined
|
|
2280
|
+
// incorrectly. For example if the answer is using a symbol defined
|
|
2281
|
+
// in the function variables list for the expression.
|
|
2282
|
+
if (!expression.parsed) {
|
|
2283
|
+
/* c8 ignore next */
|
|
2284
|
+
Log.error("Unable to parse solution answer for expression", perseusCore.Errors.InvalidInput, {
|
|
2285
|
+
loggedMetadata: {
|
|
2286
|
+
rubric: JSON.stringify(rubric)
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2289
|
+
return null;
|
|
2290
|
+
}
|
|
2291
|
+
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
|
|
2292
|
+
simplify: answer.simplify,
|
|
2293
|
+
form: answer.form
|
|
2294
|
+
}), strings);
|
|
2295
|
+
};
|
|
2296
|
+
|
|
2297
|
+
// Find the first answer form that matches the user's input and that
|
|
2298
|
+
// is considered correct. Also, track whether the input is
|
|
2299
|
+
// considered "empty" for all answer forms, and keep the validation
|
|
2300
|
+
// result for the first answer form for which the user's input was
|
|
2301
|
+
// considered "ungraded".
|
|
2302
|
+
// (Terminology reminder: the answer forms are provided by the
|
|
2303
|
+
// assessment items; they are not the user's input. Each one might
|
|
2304
|
+
// represent a correct answer, an incorrect one (if the exercise
|
|
2305
|
+
// creator has predicted certain common wrong answers and wants to
|
|
2306
|
+
// provide guidance via a message), or an ungraded one (same idea,
|
|
2307
|
+
// but without giving the user an incorrect mark for the question).
|
|
2308
|
+
let matchingAnswerForm;
|
|
2309
|
+
let matchMessage;
|
|
2310
|
+
let allEmpty = true;
|
|
2311
|
+
let firstUngradedResult;
|
|
2312
|
+
for (const answerForm of rubric.answerForms || []) {
|
|
2313
|
+
const validator = createValidator(answerForm);
|
|
2314
|
+
if (!validator) {
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
const result = validator(userInput);
|
|
2318
|
+
|
|
2319
|
+
// Short-circuit as soon as the user's input matches some answer
|
|
2320
|
+
// (independently of whether the answer is correct)
|
|
2321
|
+
if (result.correct) {
|
|
2322
|
+
matchingAnswerForm = answerForm;
|
|
2323
|
+
matchMessage = result.message || "";
|
|
2324
|
+
break;
|
|
2325
|
+
}
|
|
2326
|
+
allEmpty = allEmpty && result.empty;
|
|
2327
|
+
// If this answer form is correct and the user's input is considered
|
|
2328
|
+
// "ungraded" for it, we'll want to keep the evaluation result for
|
|
2329
|
+
// later. If the user's input doesn't match any answer forms, we'll
|
|
2330
|
+
// show the message from this validation.
|
|
2331
|
+
if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
|
|
2332
|
+
firstUngradedResult = result;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// Now check to see if we matched any answer form at all, and if
|
|
2337
|
+
// we did, whether it's considered correct, incorrect, or ungraded
|
|
2338
|
+
if (!matchingAnswerForm) {
|
|
2339
|
+
if (firstUngradedResult) {
|
|
2340
|
+
// While we didn't directly match with any answer form, we
|
|
2341
|
+
// did at some point get an "ungraded" validation result,
|
|
2342
|
+
// which might indicate e.g. a mismatch in variable casing.
|
|
2343
|
+
// We'll return "invalid", which will let the user try again
|
|
2344
|
+
// with no penalty, and the hopefully helpful validation
|
|
2345
|
+
// message.
|
|
2346
|
+
return {
|
|
2347
|
+
type: "invalid",
|
|
2348
|
+
message: firstUngradedResult.message,
|
|
2349
|
+
suppressAlmostThere: firstUngradedResult.suppressAlmostThere
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
if (allEmpty) {
|
|
2353
|
+
// If everything graded as empty, it's invalid.
|
|
2354
|
+
return {
|
|
2355
|
+
type: "invalid",
|
|
2356
|
+
message: null
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
// We fell through all the possibilities and we're not empty,
|
|
2360
|
+
// so the answer is considered incorrect.
|
|
2361
|
+
return {
|
|
2362
|
+
type: "points",
|
|
2363
|
+
earned: 0,
|
|
2364
|
+
total: 1
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
if (matchingAnswerForm.considered === "ungraded") {
|
|
2368
|
+
// We matched an ungraded answer form - return "invalid", which
|
|
2369
|
+
// will let the user try again with no penalty
|
|
2370
|
+
const apiResult = onInputError(null,
|
|
2371
|
+
// Reserved for some widget identifier
|
|
2372
|
+
userInput, matchMessage);
|
|
2373
|
+
return {
|
|
2374
|
+
type: "invalid",
|
|
2375
|
+
message: apiResult === false ? null : matchMessage
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
// We matched a graded answer form, so we can now tell the user
|
|
2379
|
+
// whether their input was correct or incorrect, and hand out
|
|
2380
|
+
// points accordingly
|
|
2381
|
+
// TODO(eater): Seems silly to translate result to this
|
|
2382
|
+
// invalid/points thing and immediately translate it back in
|
|
2383
|
+
// ItemRenderer.scoreInput()
|
|
2384
|
+
return {
|
|
2385
|
+
type: "points",
|
|
2386
|
+
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
2387
|
+
total: 1,
|
|
2388
|
+
message: matchMessage
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2222
2392
|
// Map of international operator names to their English equivalents
|
|
2223
2393
|
const englishOperators = {
|
|
2224
2394
|
arctg: "arctan",
|
|
@@ -2245,181 +2415,18 @@ const deriveKeypadVersion = apiOptions => {
|
|
|
2245
2415
|
// can remove this `virtualKeypadVersion` field entirely.
|
|
2246
2416
|
return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : "MATH_INPUT_KEYPAD_V2";
|
|
2247
2417
|
};
|
|
2248
|
-
/**
|
|
2249
|
-
* Get the character used for separating decimals.
|
|
2250
|
-
*/
|
|
2251
|
-
const getDecimalSeparator = locale => {
|
|
2252
|
-
switch (locale) {
|
|
2253
|
-
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
2254
|
-
// locale properly.
|
|
2255
|
-
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
2256
|
-
//
|
|
2257
|
-
// Supported locales in Chrome:
|
|
2258
|
-
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
2259
|
-
case "ka":
|
|
2260
|
-
return ",";
|
|
2261
|
-
default:
|
|
2262
|
-
const numberWithDecimalSeparator = 1.1;
|
|
2263
|
-
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
2264
|
-
// support Safari 12.
|
|
2265
|
-
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
2266
|
-
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
2267
|
-
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
2268
|
-
.match(/[^\d\u0661\u06F1]/);
|
|
2269
|
-
return match?.[0] ?? ".";
|
|
2270
|
-
}
|
|
2271
|
-
};
|
|
2272
|
-
|
|
2273
2418
|
// The new, MathQuill input expression widget
|
|
2274
2419
|
class Expression extends React__namespace.Component {
|
|
2275
2420
|
static contextType = PerseusI18nContext;
|
|
2276
2421
|
_textareaId = `expression_textarea_${Date.now()}`;
|
|
2277
2422
|
_isMounted = false;
|
|
2278
2423
|
|
|
2279
|
-
|
|
2280
|
-
/* Content creators input a list of answers which are matched from top to
|
|
2281
|
-
* bottom. The intent is that they can include spcific solutions which should
|
|
2282
|
-
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
2283
|
-
* general.
|
|
2284
|
-
*
|
|
2285
|
-
* We iterate through each answer, trying to match it with the user's input
|
|
2286
|
-
* using the following angorithm:
|
|
2287
|
-
* - Try to parse the user's input. If it doesn't parse then return "not
|
|
2288
|
-
* graded".
|
|
2289
|
-
* - For each answer:
|
|
2290
|
-
* ~ Try to validate the user's input against the answer. The answer is
|
|
2291
|
-
* expected to parse.
|
|
2292
|
-
* ~ If the user's input validates (the validator judges it "correct"), we've
|
|
2293
|
-
* matched and can stop considering answers.
|
|
2294
|
-
* - If there were no matches or the matching answer is considered "ungraded",
|
|
2295
|
-
* show the user an error. TODO(joel) - what error?
|
|
2296
|
-
* - Otherwise, pass through the resulting points and message.
|
|
2297
|
-
*/
|
|
2424
|
+
// TODO remove this in favor of just using expressionValidator
|
|
2298
2425
|
static validate(userInput, rubric) {
|
|
2299
2426
|
let onInputError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
|
|
2300
2427
|
let strings = arguments.length > 3 ? arguments[3] : undefined;
|
|
2301
2428
|
let locale = arguments.length > 4 ? arguments[4] : undefined;
|
|
2302
|
-
|
|
2303
|
-
___default["default"].extend(options, {
|
|
2304
|
-
decimal_separator: getDecimalSeparator(locale)
|
|
2305
|
-
});
|
|
2306
|
-
const createValidator = answer => {
|
|
2307
|
-
// We give options to KAS.parse here because it is parsing the
|
|
2308
|
-
// solution answer, not the student answer, and we don't want a
|
|
2309
|
-
// solution to work if the student is using a different language
|
|
2310
|
-
// (different from the content creation language, ie. English).
|
|
2311
|
-
const expression = KAS__namespace.parse(answer.value, rubric);
|
|
2312
|
-
// An answer may not be parsed if the expression was defined
|
|
2313
|
-
// incorrectly. For example if the answer is using a symbol defined
|
|
2314
|
-
// in the function variables list for the expression.
|
|
2315
|
-
if (!expression.parsed) {
|
|
2316
|
-
/* c8 ignore next */
|
|
2317
|
-
Log.error("Unable to parse solution answer for expression", perseusCore.Errors.InvalidInput, {
|
|
2318
|
-
loggedMetadata: {
|
|
2319
|
-
rubric: JSON.stringify(rubric)
|
|
2320
|
-
}
|
|
2321
|
-
});
|
|
2322
|
-
return null;
|
|
2323
|
-
}
|
|
2324
|
-
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
|
|
2325
|
-
simplify: answer.simplify,
|
|
2326
|
-
form: answer.form
|
|
2327
|
-
}), strings);
|
|
2328
|
-
};
|
|
2329
|
-
|
|
2330
|
-
// Find the first answer form that matches the user's input and that
|
|
2331
|
-
// is considered correct. Also, track whether the input is
|
|
2332
|
-
// considered "empty" for all answer forms, and keep the validation
|
|
2333
|
-
// result for the first answer form for which the user's input was
|
|
2334
|
-
// considered "ungraded".
|
|
2335
|
-
// (Terminology reminder: the answer forms are provided by the
|
|
2336
|
-
// assessment items; they are not the user's input. Each one might
|
|
2337
|
-
// represent a correct answer, an incorrect one (if the exercise
|
|
2338
|
-
// creator has predicted certain common wrong answers and wants to
|
|
2339
|
-
// provide guidance via a message), or an ungraded one (same idea,
|
|
2340
|
-
// but without giving the user an incorrect mark for the question).
|
|
2341
|
-
let matchingAnswerForm;
|
|
2342
|
-
let matchMessage;
|
|
2343
|
-
let allEmpty = true;
|
|
2344
|
-
let firstUngradedResult;
|
|
2345
|
-
for (const answerForm of rubric.answerForms || []) {
|
|
2346
|
-
const validate = createValidator(answerForm);
|
|
2347
|
-
if (!validate) {
|
|
2348
|
-
continue;
|
|
2349
|
-
}
|
|
2350
|
-
const result = validate(userInput);
|
|
2351
|
-
|
|
2352
|
-
// Short-circuit as soon as the user's input matches some answer
|
|
2353
|
-
// (independently of whether the answer is correct)
|
|
2354
|
-
if (result.correct) {
|
|
2355
|
-
matchingAnswerForm = answerForm;
|
|
2356
|
-
matchMessage = result.message || "";
|
|
2357
|
-
break;
|
|
2358
|
-
}
|
|
2359
|
-
allEmpty = allEmpty && result.empty;
|
|
2360
|
-
// If this answer form is correct and the user's input is considered
|
|
2361
|
-
// "ungraded" for it, we'll want to keep the evaluation result for
|
|
2362
|
-
// later. If the user's input doesn't match any answer forms, we'll
|
|
2363
|
-
// show the message from this validation.
|
|
2364
|
-
if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
|
|
2365
|
-
firstUngradedResult = result;
|
|
2366
|
-
}
|
|
2367
|
-
}
|
|
2368
|
-
|
|
2369
|
-
// Now check to see if we matched any answer form at all, and if
|
|
2370
|
-
// we did, whether it's considered correct, incorrect, or ungraded
|
|
2371
|
-
if (!matchingAnswerForm) {
|
|
2372
|
-
if (firstUngradedResult) {
|
|
2373
|
-
// While we didn't directly match with any answer form, we
|
|
2374
|
-
// did at some point get an "ungraded" validation result,
|
|
2375
|
-
// which might indicate e.g. a mismatch in variable casing.
|
|
2376
|
-
// We'll return "invalid", which will let the user try again
|
|
2377
|
-
// with no penalty, and the hopefully helpful validation
|
|
2378
|
-
// message.
|
|
2379
|
-
return {
|
|
2380
|
-
type: "invalid",
|
|
2381
|
-
message: firstUngradedResult.message,
|
|
2382
|
-
suppressAlmostThere: firstUngradedResult.suppressAlmostThere
|
|
2383
|
-
};
|
|
2384
|
-
}
|
|
2385
|
-
if (allEmpty) {
|
|
2386
|
-
// If everything graded as empty, it's invalid.
|
|
2387
|
-
return {
|
|
2388
|
-
type: "invalid",
|
|
2389
|
-
message: null
|
|
2390
|
-
};
|
|
2391
|
-
}
|
|
2392
|
-
// We fell through all the possibilities and we're not empty,
|
|
2393
|
-
// so the answer is considered incorrect.
|
|
2394
|
-
return {
|
|
2395
|
-
type: "points",
|
|
2396
|
-
earned: 0,
|
|
2397
|
-
total: 1
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
if (matchingAnswerForm.considered === "ungraded") {
|
|
2401
|
-
// We matched an ungraded answer form - return "invalid", which
|
|
2402
|
-
// will let the user try again with no penalty
|
|
2403
|
-
const apiResult = onInputError(null,
|
|
2404
|
-
// Reserved for some widget identifier
|
|
2405
|
-
userInput, matchMessage);
|
|
2406
|
-
return {
|
|
2407
|
-
type: "invalid",
|
|
2408
|
-
message: apiResult === false ? null : matchMessage
|
|
2409
|
-
};
|
|
2410
|
-
}
|
|
2411
|
-
// We matched a graded answer form, so we can now tell the user
|
|
2412
|
-
// whether their input was correct or incorrect, and hand out
|
|
2413
|
-
// points accordingly
|
|
2414
|
-
// TODO(eater): Seems silly to translate result to this
|
|
2415
|
-
// invalid/points thing and immediately translate it back in
|
|
2416
|
-
// ItemRenderer.scoreInput()
|
|
2417
|
-
return {
|
|
2418
|
-
type: "points",
|
|
2419
|
-
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
2420
|
-
total: 1,
|
|
2421
|
-
message: matchMessage
|
|
2422
|
-
};
|
|
2429
|
+
return expressionValidator(userInput, rubric, onInputError, strings, locale);
|
|
2423
2430
|
}
|
|
2424
2431
|
static getUserInputFromProps(props) {
|
|
2425
2432
|
return normalizeTex(props.value);
|
|
@@ -2506,7 +2513,7 @@ class Expression extends React__namespace.Component {
|
|
|
2506
2513
|
scoring,
|
|
2507
2514
|
...rubric
|
|
2508
2515
|
} = _ref;
|
|
2509
|
-
const score =
|
|
2516
|
+
const score = expressionValidator(this.getUserInput(), rubric, onInputError || function () {}, this.context.strings, this.context.locale);
|
|
2510
2517
|
|
|
2511
2518
|
// "scoring" is a flag that indicates when we are checking answers.
|
|
2512
2519
|
// otherwise, we may just be checking validity after changes.
|
|
@@ -2780,10 +2787,10 @@ const ExpressionWithDependencies = /*#__PURE__*/React__namespace.forwardRef((pro
|
|
|
2780
2787
|
|
|
2781
2788
|
// HACK: Propogate "static" methods onto our wrapper component.
|
|
2782
2789
|
// In the future we should adjust client apps to not depend on these static
|
|
2783
|
-
// methods and instead adjust
|
|
2790
|
+
// methods and instead adjust Perseus to provide these facilities through
|
|
2784
2791
|
// instance methods on our Renderers.
|
|
2785
2792
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
2786
|
-
ExpressionWithDependencies.validate =
|
|
2793
|
+
ExpressionWithDependencies.validate = expressionValidator;
|
|
2787
2794
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
2788
2795
|
ExpressionWithDependencies.getUserInputFromProps = Expression.getUserInputFromProps;
|
|
2789
2796
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
@@ -19812,9 +19819,7 @@ const isIdPathPrefix = function (prefixArray, wholeArray) {
|
|
|
19812
19819
|
|
|
19813
19820
|
// The return type for getUserInput. Widgets have full control of what is
|
|
19814
19821
|
// returned so it's not easily typed (some widgets return a scalar (string),
|
|
19815
|
-
// some return a custom-built object
|
|
19816
|
-
// `WidgetJsonifyDeprecated` which returns an object containing widget props
|
|
19817
|
-
// (filtered by deprecated keys)).
|
|
19822
|
+
// some return a custom-built object
|
|
19818
19823
|
/**
|
|
19819
19824
|
* The Widget type represents the common API that the Renderer uses to interact
|
|
19820
19825
|
* with all widgets. All widgets must implement the methods in this API, unless
|
|
@@ -22254,6 +22259,130 @@ const answerFormButtons = [{
|
|
|
22254
22259
|
value: "pi",
|
|
22255
22260
|
content: "\u03C0"
|
|
22256
22261
|
}];
|
|
22262
|
+
|
|
22263
|
+
// This function checks if the user inputted a percent value, parsing
|
|
22264
|
+
// it as a number (and maybe scaling) so that it can be graded.
|
|
22265
|
+
// NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
|
|
22266
|
+
// can accept several input forms with or without "%", the decision
|
|
22267
|
+
// to parse based on the presence of "%" in the input, is so that we
|
|
22268
|
+
// don't accidently scale the user typed value before grading, CP-930.
|
|
22269
|
+
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
22270
|
+
// If the input value is not a string ending with "%", then there's
|
|
22271
|
+
// nothing more to do. The value will be graded as inputted by user.
|
|
22272
|
+
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
22273
|
+
return inputValue;
|
|
22274
|
+
}
|
|
22275
|
+
const value = parseFloat(inputValue.slice(0, -1));
|
|
22276
|
+
// If the input value stripped of the "%" cannot be parsed as a
|
|
22277
|
+
// number (the slice is not really necessary for parseFloat to work
|
|
22278
|
+
// if the string starts with a number) then return the original
|
|
22279
|
+
// input for grading.
|
|
22280
|
+
if (isNaN(value)) {
|
|
22281
|
+
return inputValue;
|
|
22282
|
+
}
|
|
22283
|
+
|
|
22284
|
+
// Next, if all correct answers are in the range of |0,1| then we
|
|
22285
|
+
// scale the user typed value. We assume this is the correct thing
|
|
22286
|
+
// to do since the input value ends with "%".
|
|
22287
|
+
if (normalizedAnswerExpected) {
|
|
22288
|
+
return value / 100;
|
|
22289
|
+
}
|
|
22290
|
+
|
|
22291
|
+
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
22292
|
+
return value;
|
|
22293
|
+
}
|
|
22294
|
+
function numericInputValidator(userInput, rubric, strings) {
|
|
22295
|
+
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
22296
|
+
// Don't default to validating the answer as a pi answer
|
|
22297
|
+
// if answerForm isn't set on the answer
|
|
22298
|
+
// https://khanacademy.atlassian.net/browse/LC-691
|
|
22299
|
+
.filter(e => e !== "pi");
|
|
22300
|
+
const createValidator = answer => {
|
|
22301
|
+
const stringAnswer = `${answer.value}`;
|
|
22302
|
+
|
|
22303
|
+
// Always validate against the provided answer forms (pi, decimal, etc.)
|
|
22304
|
+
const validatorForms = [...(answer.answerForms ?? [])];
|
|
22305
|
+
|
|
22306
|
+
// When an answer is set to strict, we validate using ONLY
|
|
22307
|
+
// the provided answerForms. If strict is false, or if there
|
|
22308
|
+
// were no provided answer forms, we will include all
|
|
22309
|
+
// of the default answer forms in our validator.
|
|
22310
|
+
if (!answer.strict || validatorForms.length === 0) {
|
|
22311
|
+
validatorForms.push(...defaultAnswerForms);
|
|
22312
|
+
}
|
|
22313
|
+
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
22314
|
+
message: answer.message,
|
|
22315
|
+
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
22316
|
+
inexact: true,
|
|
22317
|
+
// TODO(merlob) backfill / delete
|
|
22318
|
+
maxError: answer.maxError,
|
|
22319
|
+
forms: validatorForms
|
|
22320
|
+
}, strings);
|
|
22321
|
+
};
|
|
22322
|
+
|
|
22323
|
+
// We may have received TeX; try to parse it before grading.
|
|
22324
|
+
// If `currentValue` is not TeX, this should be a no-op.
|
|
22325
|
+
const currentValue = ParseTex(userInput.currentValue);
|
|
22326
|
+
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22327
|
+
const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1);
|
|
22328
|
+
|
|
22329
|
+
// Look through all correct answers for one that matches either
|
|
22330
|
+
// precisely or approximately and return the appropriate message:
|
|
22331
|
+
// - if precise, return the message that the answer came with
|
|
22332
|
+
// - if it needs to be simplified, etc., show that message
|
|
22333
|
+
let result = correctAnswers.map(answer => {
|
|
22334
|
+
// The coefficient is an attribute of the widget
|
|
22335
|
+
let localValue = currentValue;
|
|
22336
|
+
if (rubric.coefficient) {
|
|
22337
|
+
if (!localValue) {
|
|
22338
|
+
localValue = 1;
|
|
22339
|
+
} else if (localValue === "-") {
|
|
22340
|
+
localValue = -1;
|
|
22341
|
+
}
|
|
22342
|
+
}
|
|
22343
|
+
const validate = createValidator(answer);
|
|
22344
|
+
return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
22345
|
+
}).find(match => match.correct || match.empty);
|
|
22346
|
+
if (!result) {
|
|
22347
|
+
// Otherwise, if the guess is not correct
|
|
22348
|
+
const otherAnswers = [].concat(
|
|
22349
|
+
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
22350
|
+
rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong"));
|
|
22351
|
+
|
|
22352
|
+
// Look through all other answers and if one matches either
|
|
22353
|
+
// precisely or approximately return the answer's message
|
|
22354
|
+
const match = otherAnswers.find(answer => {
|
|
22355
|
+
const validate = createValidator(answer);
|
|
22356
|
+
return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
|
|
22357
|
+
});
|
|
22358
|
+
result = {
|
|
22359
|
+
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22360
|
+
empty: match ? match.status === "ungraded" : false,
|
|
22361
|
+
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22362
|
+
correct: match ? match.status === "correct" : false,
|
|
22363
|
+
// @ts-expect-error - TS2339 - Property 'message' does not exist on type 'never'.
|
|
22364
|
+
message: match ? match.message : null,
|
|
22365
|
+
guess: currentValue
|
|
22366
|
+
};
|
|
22367
|
+
}
|
|
22368
|
+
|
|
22369
|
+
// TODO(eater): Seems silly to translate result to this
|
|
22370
|
+
// invalid/points thing and immediately translate it
|
|
22371
|
+
// back in ItemRenderer.scoreInput()
|
|
22372
|
+
if (result.empty) {
|
|
22373
|
+
return {
|
|
22374
|
+
type: "invalid",
|
|
22375
|
+
message: result.message
|
|
22376
|
+
};
|
|
22377
|
+
}
|
|
22378
|
+
return {
|
|
22379
|
+
type: "points",
|
|
22380
|
+
earned: result.correct ? 1 : 0,
|
|
22381
|
+
total: 1,
|
|
22382
|
+
message: result.message
|
|
22383
|
+
};
|
|
22384
|
+
}
|
|
22385
|
+
|
|
22257
22386
|
const formExamples = {
|
|
22258
22387
|
integer: (form, strings) => strings.integerExample,
|
|
22259
22388
|
proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
|
|
@@ -22309,97 +22438,7 @@ class NumericInput extends React__namespace.Component {
|
|
|
22309
22438
|
return answerStrings[0];
|
|
22310
22439
|
}
|
|
22311
22440
|
static validate(userInput, rubric, strings) {
|
|
22312
|
-
|
|
22313
|
-
// Don't default to validating the answer as a pi answer
|
|
22314
|
-
// if answerForm isn't set on the answer
|
|
22315
|
-
// https://khanacademy.atlassian.net/browse/LC-691
|
|
22316
|
-
.filter(e => e !== "pi");
|
|
22317
|
-
const createValidator = answer => {
|
|
22318
|
-
const stringAnswer = `${answer.value}`;
|
|
22319
|
-
|
|
22320
|
-
// Always validate against the provided answer forms (pi, decimal, etc.)
|
|
22321
|
-
const validatorForms = [...(answer.answerForms ?? [])];
|
|
22322
|
-
|
|
22323
|
-
// When an answer is set to strict, we validate using ONLY
|
|
22324
|
-
// the provided answerForms. If strict is false, or if there
|
|
22325
|
-
// were no provided answer forms, we will include all
|
|
22326
|
-
// of the default answer forms in our validator.
|
|
22327
|
-
if (!answer.strict || validatorForms.length === 0) {
|
|
22328
|
-
validatorForms.push(...defaultAnswerForms);
|
|
22329
|
-
}
|
|
22330
|
-
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
22331
|
-
message: answer.message,
|
|
22332
|
-
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
22333
|
-
inexact: true,
|
|
22334
|
-
// TODO(merlob) backfill / delete
|
|
22335
|
-
maxError: answer.maxError,
|
|
22336
|
-
forms: validatorForms
|
|
22337
|
-
}, strings);
|
|
22338
|
-
};
|
|
22339
|
-
|
|
22340
|
-
// We may have received TeX; try to parse it before grading.
|
|
22341
|
-
// If `currentValue` is not TeX, this should be a no-op.
|
|
22342
|
-
const currentValue = ParseTex(userInput.currentValue);
|
|
22343
|
-
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22344
|
-
const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1);
|
|
22345
|
-
|
|
22346
|
-
// Look through all correct answers for one that matches either
|
|
22347
|
-
// precisely or approximately and return the appropriate message:
|
|
22348
|
-
// - if precise, return the message that the answer came with
|
|
22349
|
-
// - if it needs to be simplified, etc., show that message
|
|
22350
|
-
let result = correctAnswers.map(answer => {
|
|
22351
|
-
// The coefficient is an attribute of the widget
|
|
22352
|
-
let localValue = currentValue;
|
|
22353
|
-
if (rubric.coefficient) {
|
|
22354
|
-
if (!localValue) {
|
|
22355
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'string'.
|
|
22356
|
-
localValue = 1;
|
|
22357
|
-
} else if (localValue === "-") {
|
|
22358
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'string'.
|
|
22359
|
-
localValue = -1;
|
|
22360
|
-
}
|
|
22361
|
-
}
|
|
22362
|
-
const validate = createValidator(answer);
|
|
22363
|
-
return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
22364
|
-
}).find(match => match.correct || match.empty);
|
|
22365
|
-
if (!result) {
|
|
22366
|
-
// Otherwise, if the guess is not correct
|
|
22367
|
-
const otherAnswers = [].concat(
|
|
22368
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
22369
|
-
rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong"));
|
|
22370
|
-
|
|
22371
|
-
// Look through all other answers and if one matches either
|
|
22372
|
-
// precisely or approximately return the answer's message
|
|
22373
|
-
const match = otherAnswers.find(answer => {
|
|
22374
|
-
const validate = createValidator(answer);
|
|
22375
|
-
return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
|
|
22376
|
-
});
|
|
22377
|
-
result = {
|
|
22378
|
-
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22379
|
-
empty: match ? match.status === "ungraded" : false,
|
|
22380
|
-
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22381
|
-
correct: match ? match.status === "correct" : false,
|
|
22382
|
-
// @ts-expect-error - TS2339 - Property 'message' does not exist on type 'never'.
|
|
22383
|
-
message: match ? match.message : null,
|
|
22384
|
-
guess: currentValue
|
|
22385
|
-
};
|
|
22386
|
-
}
|
|
22387
|
-
|
|
22388
|
-
// TODO(eater): Seems silly to translate result to this
|
|
22389
|
-
// invalid/points thing and immediately translate it
|
|
22390
|
-
// back in ItemRenderer.scoreInput()
|
|
22391
|
-
if (result.empty) {
|
|
22392
|
-
return {
|
|
22393
|
-
type: "invalid",
|
|
22394
|
-
message: result.message
|
|
22395
|
-
};
|
|
22396
|
-
}
|
|
22397
|
-
return {
|
|
22398
|
-
type: "points",
|
|
22399
|
-
earned: result.correct ? 1 : 0,
|
|
22400
|
-
total: 1,
|
|
22401
|
-
message: result.message
|
|
22402
|
-
};
|
|
22441
|
+
return numericInputValidator(userInput, rubric, strings);
|
|
22403
22442
|
}
|
|
22404
22443
|
state = {
|
|
22405
22444
|
// keeps track of the other set of values when switching
|
|
@@ -22434,7 +22473,7 @@ class NumericInput extends React__namespace.Component {
|
|
|
22434
22473
|
return !noFormsAccepted && !allFormsAccepted;
|
|
22435
22474
|
};
|
|
22436
22475
|
simpleValidate = rubric => {
|
|
22437
|
-
return
|
|
22476
|
+
return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
|
|
22438
22477
|
};
|
|
22439
22478
|
focus = () => {
|
|
22440
22479
|
this.inputRef?.focus();
|
|
@@ -22565,37 +22604,6 @@ const unionAnswerForms = function (answerFormsList) {
|
|
|
22565
22604
|
return formExampleKeys.indexOf(form.name);
|
|
22566
22605
|
});
|
|
22567
22606
|
};
|
|
22568
|
-
// This function checks if the user inputted a percent value, parsing
|
|
22569
|
-
// it as a number (and maybe scaling) so that it can be graded.
|
|
22570
|
-
// NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
|
|
22571
|
-
// can accept several input forms with or without "%", the decision
|
|
22572
|
-
// to parse based on the presence of "%" in the input, is so that we
|
|
22573
|
-
// don't accidently scale the user typed value before grading, CP-930.
|
|
22574
|
-
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
22575
|
-
// If the input value is not a string ending with "%", then there's
|
|
22576
|
-
// nothing more to do. The value will be graded as inputted by user.
|
|
22577
|
-
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
22578
|
-
return inputValue;
|
|
22579
|
-
}
|
|
22580
|
-
const value = parseFloat(inputValue.slice(0, -1));
|
|
22581
|
-
// If the input value stripped of the "%" cannot be parsed as a
|
|
22582
|
-
// number (the slice is not really necessary for parseFloat to work
|
|
22583
|
-
// if the string starts with a number) then return the original
|
|
22584
|
-
// input for grading.
|
|
22585
|
-
if (isNaN(value)) {
|
|
22586
|
-
return inputValue;
|
|
22587
|
-
}
|
|
22588
|
-
|
|
22589
|
-
// Next, if all correct answers are in the range of |0,1| then we
|
|
22590
|
-
// scale the user typed value. We assume this is the correct thing
|
|
22591
|
-
// to do since the input value ends with "%".
|
|
22592
|
-
if (normalizedAnswerExpected) {
|
|
22593
|
-
return value / 100;
|
|
22594
|
-
}
|
|
22595
|
-
|
|
22596
|
-
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
22597
|
-
return value;
|
|
22598
|
-
}
|
|
22599
22607
|
const propsTransform = function (widgetOptions) {
|
|
22600
22608
|
const rendererProps = ___default["default"].extend(___default["default"].omit(widgetOptions, "answers"), {
|
|
22601
22609
|
answerForms: unionAnswerForms(
|
|
@@ -22623,6 +22631,23 @@ var NumericInput$1 = {
|
|
|
22623
22631
|
isLintable: true
|
|
22624
22632
|
};
|
|
22625
22633
|
|
|
22634
|
+
/**
|
|
22635
|
+
* Several widgets don't have "right"/"wrong" validation logic,
|
|
22636
|
+
* so this just says to move on past those widgets
|
|
22637
|
+
*
|
|
22638
|
+
* TODO: widgets that use this probably shouldn't have any
|
|
22639
|
+
* validation logic and the thing validating an exercise
|
|
22640
|
+
* should just know to skip these
|
|
22641
|
+
*/
|
|
22642
|
+
function noopValidator() {
|
|
22643
|
+
return {
|
|
22644
|
+
type: "points",
|
|
22645
|
+
earned: 0,
|
|
22646
|
+
total: 0,
|
|
22647
|
+
message: null
|
|
22648
|
+
};
|
|
22649
|
+
}
|
|
22650
|
+
|
|
22626
22651
|
const EN_DASH$1 = "\u2013";
|
|
22627
22652
|
class PassageRef extends React__namespace.Component {
|
|
22628
22653
|
static contextType = PerseusI18nContext;
|
|
@@ -22641,12 +22666,7 @@ class PassageRef extends React__namespace.Component {
|
|
|
22641
22666
|
content: null
|
|
22642
22667
|
};
|
|
22643
22668
|
static validate(userInput, rubric) {
|
|
22644
|
-
return
|
|
22645
|
-
type: "points",
|
|
22646
|
-
earned: 0,
|
|
22647
|
-
total: 0,
|
|
22648
|
-
message: null
|
|
22649
|
-
};
|
|
22669
|
+
return noopValidator();
|
|
22650
22670
|
}
|
|
22651
22671
|
componentDidMount() {
|
|
22652
22672
|
// TODO(scottgrant): This is a hack to remove the deprecated call to
|
|
@@ -22747,6 +22767,7 @@ class PassageRef extends React__namespace.Component {
|
|
|
22747
22767
|
var PassageRef$1 = {
|
|
22748
22768
|
name: "passage-ref",
|
|
22749
22769
|
displayName: "PassageRef (SAT only)",
|
|
22770
|
+
hidden: true,
|
|
22750
22771
|
defaultAlignment: "inline",
|
|
22751
22772
|
widget: PassageRef,
|
|
22752
22773
|
transform: widgetOptions => ({
|
|
@@ -23977,9 +23998,8 @@ const BaseRadio = function (props) {
|
|
|
23977
23998
|
// element) We have to add some extra behavior to make
|
|
23978
23999
|
// sure that we can still check the choice.
|
|
23979
24000
|
let listElem = null;
|
|
23980
|
-
let clickHandler
|
|
24001
|
+
let clickHandler;
|
|
23981
24002
|
if (editMode) {
|
|
23982
|
-
// @ts-expect-error - TS2322 - Type '(e: any) => void' is not assignable to type 'null'.
|
|
23983
24003
|
clickHandler = e => {
|
|
23984
24004
|
// Traverse the parent nodes of the clicked
|
|
23985
24005
|
// element.
|
|
@@ -24006,19 +24026,11 @@ const BaseRadio = function (props) {
|
|
|
24006
24026
|
// somehow break something happening inside a choice's
|
|
24007
24027
|
// child Renderers, by changing when we mount/unmount?
|
|
24008
24028
|
return /*#__PURE__*/React__namespace.createElement("li", {
|
|
24009
|
-
key: i
|
|
24010
|
-
|
|
24011
|
-
,
|
|
24012
|
-
|
|
24013
|
-
|
|
24014
|
-
,
|
|
24015
|
-
className: className
|
|
24016
|
-
// @ts-expect-error - TS2322 - Type 'null' is not assignable to type 'MouseEventHandler<HTMLLIElement> | undefined'.
|
|
24017
|
-
,
|
|
24018
|
-
onClick: clickHandler
|
|
24019
|
-
// @ts-expect-error - TS2322 - Type '((e: TouchEvent) => void) | null' is not assignable to type 'TouchEventHandler<HTMLLIElement> | undefined'.
|
|
24020
|
-
,
|
|
24021
|
-
onTouchStart: !labelWrap ? null : captureScratchpadTouchStart$1
|
|
24029
|
+
key: i,
|
|
24030
|
+
ref: e => listElem = e,
|
|
24031
|
+
className: className,
|
|
24032
|
+
onClick: clickHandler,
|
|
24033
|
+
onTouchStart: labelWrap ? undefined : captureScratchpadTouchStart$1
|
|
24022
24034
|
}, /*#__PURE__*/React__namespace.createElement(Element, _extends({}, elementProps, {
|
|
24023
24035
|
ref: ref
|
|
24024
24036
|
})));
|
|
@@ -24664,23 +24676,6 @@ var Radio = {
|
|
|
24664
24676
|
/* eslint-disable import/no-named-as-default */
|
|
24665
24677
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
24666
24678
|
|
|
24667
|
-
/* Free implementation of getUserInput. This should be used sparingly, since it
|
|
24668
|
-
* just returns all the widget's props rather than picking out those which were
|
|
24669
|
-
* input by the user.
|
|
24670
|
-
*/
|
|
24671
|
-
const WidgetJsonifyDeprecated = {
|
|
24672
|
-
getUserInput: function () {
|
|
24673
|
-
// Omit props that get passed to all widgets
|
|
24674
|
-
// @ts-expect-error - TS2339 - Property 'props' does not exist on type '{ readonly getUserInput: () => any; readonly getUserInputFromProps: (props: any) => any; }'.
|
|
24675
|
-
return ___default["default"].omit(this.props, denylist);
|
|
24676
|
-
},
|
|
24677
|
-
// Static version of `WidgetJsonifyDeprecated.getUserInput`
|
|
24678
|
-
getUserInputFromProps: function (props) {
|
|
24679
|
-
// Omit props that get passed to all widgets
|
|
24680
|
-
return ___default["default"].omit(props, denylist);
|
|
24681
|
-
}
|
|
24682
|
-
};
|
|
24683
|
-
|
|
24684
24679
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
24685
24680
|
class Categorizer extends React__namespace.Component {
|
|
24686
24681
|
static contextType = PerseusI18nContext;
|
|
@@ -24718,7 +24713,9 @@ class Categorizer extends React__namespace.Component {
|
|
|
24718
24713
|
};
|
|
24719
24714
|
}
|
|
24720
24715
|
static getUserInputFromProps(props) {
|
|
24721
|
-
return
|
|
24716
|
+
return {
|
|
24717
|
+
values: props.values
|
|
24718
|
+
};
|
|
24722
24719
|
}
|
|
24723
24720
|
change = (() => {
|
|
24724
24721
|
var _this = this;
|
|
@@ -24873,6 +24870,7 @@ const styles$s = aphrodite.StyleSheet.create({
|
|
|
24873
24870
|
var Categorizer$1 = {
|
|
24874
24871
|
name: "categorizer",
|
|
24875
24872
|
displayName: "Categorizer",
|
|
24873
|
+
hidden: true,
|
|
24876
24874
|
widget: Categorizer,
|
|
24877
24875
|
transform: widgetOptions => {
|
|
24878
24876
|
return ___default["default"].pick(widgetOptions, "items", "categories", "randomizeItems");
|
|
@@ -24953,18 +24951,12 @@ function getUrlFromProgramID$1(programID) {
|
|
|
24953
24951
|
/* This renders the scratchpad in an iframe and handles validation via
|
|
24954
24952
|
* window.postMessage */
|
|
24955
24953
|
class CSProgram extends React__namespace.Component {
|
|
24956
|
-
static
|
|
24957
|
-
|
|
24958
|
-
|
|
24959
|
-
|
|
24960
|
-
|
|
24961
|
-
|
|
24962
|
-
// eslint-disable-next-line react/forbid-prop-types
|
|
24963
|
-
settings: PropTypes__default["default"].array,
|
|
24964
|
-
showEditor: PropTypes__default["default"].bool,
|
|
24965
|
-
showButtons: PropTypes__default["default"].bool,
|
|
24966
|
-
status: PropTypes__default["default"].oneOf(["incomplete", "incorrect", "correct"]),
|
|
24967
|
-
message: PropTypes__default["default"].string
|
|
24954
|
+
static defaultProps = {
|
|
24955
|
+
showEditor: false,
|
|
24956
|
+
showButtons: false,
|
|
24957
|
+
status: "incomplete",
|
|
24958
|
+
// optional message
|
|
24959
|
+
message: null
|
|
24968
24960
|
};
|
|
24969
24961
|
|
|
24970
24962
|
// The widget's grading function
|
|
@@ -24992,13 +24984,6 @@ class CSProgram extends React__namespace.Component {
|
|
|
24992
24984
|
message: "Keep going, you're not there yet!"
|
|
24993
24985
|
};
|
|
24994
24986
|
}
|
|
24995
|
-
static defaultProps = {
|
|
24996
|
-
showEditor: false,
|
|
24997
|
-
showButtons: false,
|
|
24998
|
-
status: "incomplete",
|
|
24999
|
-
// optional message
|
|
25000
|
-
message: null
|
|
25001
|
-
};
|
|
25002
24987
|
componentDidMount() {
|
|
25003
24988
|
$__default["default"](window).on("message", this.handleMessageEvent);
|
|
25004
24989
|
}
|
|
@@ -25034,12 +25019,12 @@ class CSProgram extends React__namespace.Component {
|
|
|
25034
25019
|
return change.apply(_this, args);
|
|
25035
25020
|
};
|
|
25036
25021
|
})();
|
|
25037
|
-
simpleValidate
|
|
25022
|
+
simpleValidate(rubric) {
|
|
25038
25023
|
return CSProgram.validate({
|
|
25039
25024
|
status: this.props.status,
|
|
25040
25025
|
message: this.props.message
|
|
25041
25026
|
}, rubric);
|
|
25042
|
-
}
|
|
25027
|
+
}
|
|
25043
25028
|
render() {
|
|
25044
25029
|
if (!this.props.programID) {
|
|
25045
25030
|
return /*#__PURE__*/React__namespace.createElement("div", null);
|
|
@@ -25124,12 +25109,7 @@ class Definition extends React__namespace.Component {
|
|
|
25124
25109
|
definition: "definition goes here"
|
|
25125
25110
|
};
|
|
25126
25111
|
static validate(userInput, rubric) {
|
|
25127
|
-
return
|
|
25128
|
-
type: "points",
|
|
25129
|
-
earned: 0,
|
|
25130
|
-
total: 0,
|
|
25131
|
-
message: null
|
|
25132
|
-
};
|
|
25112
|
+
return noopValidator();
|
|
25133
25113
|
}
|
|
25134
25114
|
getUserInput = () => {
|
|
25135
25115
|
return {};
|
|
@@ -25235,6 +25215,23 @@ var DeprecatedStandin$1 = {
|
|
|
25235
25215
|
hidden: true
|
|
25236
25216
|
};
|
|
25237
25217
|
|
|
25218
|
+
function dropdownValidator(userInput, rubric) {
|
|
25219
|
+
const selected = userInput.value;
|
|
25220
|
+
if (selected === 0) {
|
|
25221
|
+
return {
|
|
25222
|
+
type: "invalid",
|
|
25223
|
+
message: null
|
|
25224
|
+
};
|
|
25225
|
+
}
|
|
25226
|
+
const correct = rubric.choices[selected - 1].correct;
|
|
25227
|
+
return {
|
|
25228
|
+
type: "points",
|
|
25229
|
+
earned: correct ? 1 : 0,
|
|
25230
|
+
total: 1,
|
|
25231
|
+
message: null
|
|
25232
|
+
};
|
|
25233
|
+
}
|
|
25234
|
+
|
|
25238
25235
|
class Dropdown extends React__namespace.Component {
|
|
25239
25236
|
static defaultProps = {
|
|
25240
25237
|
choices: [],
|
|
@@ -25243,20 +25240,7 @@ class Dropdown extends React__namespace.Component {
|
|
|
25243
25240
|
apiOptions: ApiOptions.defaults
|
|
25244
25241
|
};
|
|
25245
25242
|
static validate(userInput, rubric) {
|
|
25246
|
-
|
|
25247
|
-
if (selected === 0) {
|
|
25248
|
-
return {
|
|
25249
|
-
type: "invalid",
|
|
25250
|
-
message: null
|
|
25251
|
-
};
|
|
25252
|
-
}
|
|
25253
|
-
const correct = rubric.choices[selected - 1].correct;
|
|
25254
|
-
return {
|
|
25255
|
-
type: "points",
|
|
25256
|
-
earned: correct ? 1 : 0,
|
|
25257
|
-
total: 1,
|
|
25258
|
-
message: null
|
|
25259
|
-
};
|
|
25243
|
+
return dropdownValidator(userInput, rubric);
|
|
25260
25244
|
}
|
|
25261
25245
|
focus = () => {
|
|
25262
25246
|
// TODO(LP-10797): This focus() call doesn't do anything because our
|
|
@@ -25281,7 +25265,7 @@ class Dropdown extends React__namespace.Component {
|
|
|
25281
25265
|
};
|
|
25282
25266
|
};
|
|
25283
25267
|
simpleValidate = rubric => {
|
|
25284
|
-
return
|
|
25268
|
+
return dropdownValidator(this.getUserInput(), rubric);
|
|
25285
25269
|
};
|
|
25286
25270
|
render() {
|
|
25287
25271
|
const children = [/*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
|
|
@@ -25348,12 +25332,7 @@ class Explanation extends React__namespace.Component {
|
|
|
25348
25332
|
linterContext: PerseusLinter.linterContextDefault
|
|
25349
25333
|
};
|
|
25350
25334
|
static validate(userInput, rubric) {
|
|
25351
|
-
return
|
|
25352
|
-
type: "points",
|
|
25353
|
-
earned: 0,
|
|
25354
|
-
total: 0,
|
|
25355
|
-
message: null
|
|
25356
|
-
};
|
|
25335
|
+
return noopValidator();
|
|
25357
25336
|
}
|
|
25358
25337
|
componentDidMount() {
|
|
25359
25338
|
this._mounted = true;
|
|
@@ -27217,6 +27196,7 @@ const staticTransform$4 = editorProps => {
|
|
|
27217
27196
|
var Grapher$1 = {
|
|
27218
27197
|
name: "grapher",
|
|
27219
27198
|
displayName: "Grapher",
|
|
27199
|
+
hidden: true,
|
|
27220
27200
|
widget: Grapher,
|
|
27221
27201
|
transform: propTransform$2,
|
|
27222
27202
|
staticTransform: staticTransform$4
|
|
@@ -27364,28 +27344,23 @@ var Group$1 = {
|
|
|
27364
27344
|
displayName: "Group (SAT only)",
|
|
27365
27345
|
widget: Group,
|
|
27366
27346
|
traverseChildWidgets: traverseChildWidgets,
|
|
27367
|
-
hidden:
|
|
27347
|
+
hidden: true,
|
|
27368
27348
|
isLintable: true
|
|
27369
27349
|
};
|
|
27370
27350
|
|
|
27371
|
-
|
|
27351
|
+
/**
|
|
27352
|
+
* This is an iframe widget. It is used for rendering an iframe that
|
|
27353
|
+
* then communicates its state via window.postMessage
|
|
27354
|
+
* This is useful for embedding arbitrary visualizations/simulations with
|
|
27355
|
+
* completed conditions, such as the mazes and games in Algorithms.
|
|
27356
|
+
* It's particularly well suited for embedding our ProcessingJS programs,
|
|
27357
|
+
* but could also be used for embedding viz's hosted elsewhere.
|
|
27358
|
+
*/
|
|
27372
27359
|
const {
|
|
27373
27360
|
updateQueryString
|
|
27374
27361
|
} = Util;
|
|
27375
|
-
|
|
27376
27362
|
/* This renders the iframe and handles validation via window.postMessage */
|
|
27377
27363
|
class Iframe extends React__namespace.Component {
|
|
27378
|
-
static propTypes = {
|
|
27379
|
-
...propTypes,
|
|
27380
|
-
width: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number]),
|
|
27381
|
-
height: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number]),
|
|
27382
|
-
url: PropTypes__default["default"].string,
|
|
27383
|
-
settings: PropTypes__default["default"].array,
|
|
27384
|
-
status: PropTypes__default["default"].oneOf(["incomplete", "incorrect", "correct"]),
|
|
27385
|
-
message: PropTypes__default["default"].string,
|
|
27386
|
-
allowFullScreen: PropTypes__default["default"].bool,
|
|
27387
|
-
allowTopNavigation: PropTypes__default["default"].bool
|
|
27388
|
-
};
|
|
27389
27364
|
static defaultProps = {
|
|
27390
27365
|
status: "incomplete",
|
|
27391
27366
|
// optional message
|
|
@@ -27399,9 +27374,12 @@ class Iframe extends React__namespace.Component {
|
|
|
27399
27374
|
componentWillUnmount() {
|
|
27400
27375
|
$__default["default"](window).off("message", this.handleMessageEvent);
|
|
27401
27376
|
}
|
|
27402
|
-
getUserInput
|
|
27403
|
-
return
|
|
27404
|
-
|
|
27377
|
+
getUserInput() {
|
|
27378
|
+
return {
|
|
27379
|
+
status: this.props.status,
|
|
27380
|
+
message: this.props.message
|
|
27381
|
+
};
|
|
27382
|
+
}
|
|
27405
27383
|
handleMessageEvent = e => {
|
|
27406
27384
|
// We receive data from the iframe that contains {passed: true/false}
|
|
27407
27385
|
// and use that to set the status
|
|
@@ -27561,12 +27539,7 @@ class ImageWidget extends React__namespace.Component {
|
|
|
27561
27539
|
linterContext: PerseusLinter.linterContextDefault
|
|
27562
27540
|
};
|
|
27563
27541
|
static validate(userInput, rubric) {
|
|
27564
|
-
return
|
|
27565
|
-
type: "points",
|
|
27566
|
-
earned: 0,
|
|
27567
|
-
total: 0,
|
|
27568
|
-
message: null
|
|
27569
|
-
};
|
|
27542
|
+
return noopValidator();
|
|
27570
27543
|
}
|
|
27571
27544
|
change = (() => {
|
|
27572
27545
|
var _this = this;
|
|
@@ -27809,12 +27782,7 @@ class Interaction extends React__namespace.Component {
|
|
|
27809
27782
|
elements: []
|
|
27810
27783
|
};
|
|
27811
27784
|
static validate(state, rubric) {
|
|
27812
|
-
return
|
|
27813
|
-
type: "points",
|
|
27814
|
-
earned: 0,
|
|
27815
|
-
total: 0,
|
|
27816
|
-
message: null
|
|
27817
|
-
};
|
|
27785
|
+
return noopValidator();
|
|
27818
27786
|
}
|
|
27819
27787
|
state = {
|
|
27820
27788
|
variables: _getInitialVariables(this.props.elements),
|
|
@@ -29718,9 +29686,7 @@ class NumberInput extends React__namespace.Component {
|
|
|
29718
29686
|
onFocus: this._handleFocus,
|
|
29719
29687
|
onBlur: this._handleBlur,
|
|
29720
29688
|
onKeyPress: this._handleBlur,
|
|
29721
|
-
onKeyDown: this._onKeyDown
|
|
29722
|
-
// @ts-expect-error - TS2322 - Type '(e: TouchEvent) => void' is not assignable to type 'TouchEventHandler<HTMLInputElement>'.
|
|
29723
|
-
,
|
|
29689
|
+
onKeyDown: this._onKeyDown,
|
|
29724
29690
|
onTouchStart: captureScratchpadTouchStart,
|
|
29725
29691
|
defaultValue: toNumericString(this.props.value, this.state.format),
|
|
29726
29692
|
value: undefined
|
|
@@ -29988,6 +29954,7 @@ function LockedLabel(props) {
|
|
|
29988
29954
|
|
|
29989
29955
|
function GraphLockedLabelsLayer(props) {
|
|
29990
29956
|
const {
|
|
29957
|
+
flags,
|
|
29991
29958
|
lockedFigures
|
|
29992
29959
|
} = props;
|
|
29993
29960
|
return lockedFigures.map((figure, i) => {
|
|
@@ -29996,9 +29963,13 @@ function GraphLockedLabelsLayer(props) {
|
|
|
29996
29963
|
key: `label-${i}`
|
|
29997
29964
|
}, figure));
|
|
29998
29965
|
}
|
|
29999
|
-
|
|
30000
|
-
|
|
30001
|
-
|
|
29966
|
+
if (flags?.["mafs"]?.["locked-point-labels"] && figure.type === "point") {
|
|
29967
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, {
|
|
29968
|
+
key: `point-${i}`
|
|
29969
|
+
}, figure.labels.map((label, j) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
|
|
29970
|
+
key: `locked-figure-${i}-label-${j}`
|
|
29971
|
+
}, label))));
|
|
29972
|
+
}
|
|
30002
29973
|
return null;
|
|
30003
29974
|
});
|
|
30004
29975
|
}
|
|
@@ -30359,7 +30330,8 @@ const actions = {
|
|
|
30359
30330
|
addPoint,
|
|
30360
30331
|
removePoint,
|
|
30361
30332
|
focusPoint,
|
|
30362
|
-
blurPoint
|
|
30333
|
+
blurPoint,
|
|
30334
|
+
clickPoint
|
|
30363
30335
|
},
|
|
30364
30336
|
polygon: {
|
|
30365
30337
|
movePoint,
|
|
@@ -30421,6 +30393,13 @@ function blurPoint() {
|
|
|
30421
30393
|
type: BLUR_POINT
|
|
30422
30394
|
};
|
|
30423
30395
|
}
|
|
30396
|
+
const CLICK_POINT = "click-point";
|
|
30397
|
+
function clickPoint(index) {
|
|
30398
|
+
return {
|
|
30399
|
+
type: CLICK_POINT,
|
|
30400
|
+
index
|
|
30401
|
+
};
|
|
30402
|
+
}
|
|
30424
30403
|
const MOVE_ALL = "move-all";
|
|
30425
30404
|
function moveAll(delta) {
|
|
30426
30405
|
return {
|
|
@@ -30749,10 +30728,14 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
|
|
|
30749
30728
|
},
|
|
30750
30729
|
"data-testid": "movable-point",
|
|
30751
30730
|
tabIndex: disableKeyboardInteraction ? -1 : tabIndex(focusBehavior),
|
|
30752
|
-
onFocus:
|
|
30753
|
-
|
|
30731
|
+
onFocus: event => {
|
|
30732
|
+
const callback = getOnFocusChangeCallback(focusBehavior);
|
|
30733
|
+
callback && callback(event, true);
|
|
30734
|
+
},
|
|
30735
|
+
onBlur: event => {
|
|
30736
|
+
const callback = getOnFocusChangeCallback(focusBehavior);
|
|
30737
|
+
callback && callback(event, false);
|
|
30754
30738
|
},
|
|
30755
|
-
onBlur: () => getOnFocusChangeCallback(focusBehavior)(false),
|
|
30756
30739
|
onClick: onClick
|
|
30757
30740
|
}, /*#__PURE__*/React__namespace.createElement("circle", {
|
|
30758
30741
|
className: "movable-point-hitbox",
|
|
@@ -31406,6 +31389,7 @@ const MovablePoint$1 = props => {
|
|
|
31406
31389
|
point,
|
|
31407
31390
|
onMove = () => {},
|
|
31408
31391
|
onFocusChange = () => {},
|
|
31392
|
+
onClick = () => {},
|
|
31409
31393
|
cursor,
|
|
31410
31394
|
color = wonderBlocksTokens.color.blue,
|
|
31411
31395
|
constrain = p => snap(snapStep, p)
|
|
@@ -31428,7 +31412,10 @@ const MovablePoint$1 = props => {
|
|
|
31428
31412
|
tabIndex: 0,
|
|
31429
31413
|
onFocusChange
|
|
31430
31414
|
},
|
|
31431
|
-
onClick: () =>
|
|
31415
|
+
onClick: () => {
|
|
31416
|
+
onClick && onClick();
|
|
31417
|
+
elementRef.current?.focus();
|
|
31418
|
+
},
|
|
31432
31419
|
cursor: cursor
|
|
31433
31420
|
});
|
|
31434
31421
|
};
|
|
@@ -31639,6 +31626,11 @@ function getGradableGraph(state, initialGraph) {
|
|
|
31639
31626
|
allowReflexAngles: state.allowReflexAngles
|
|
31640
31627
|
};
|
|
31641
31628
|
}
|
|
31629
|
+
if (state.type === "none" && initialGraph.type === "none") {
|
|
31630
|
+
return {
|
|
31631
|
+
type: "none"
|
|
31632
|
+
};
|
|
31633
|
+
}
|
|
31642
31634
|
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
31643
31635
|
}
|
|
31644
31636
|
|
|
@@ -32036,12 +32028,18 @@ function UnlimitedPointGraph(props) {
|
|
|
32036
32028
|
key: i,
|
|
32037
32029
|
point: point,
|
|
32038
32030
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
32039
|
-
onFocusChange: isFocused => {
|
|
32031
|
+
onFocusChange: (event, isFocused) => {
|
|
32040
32032
|
if (isFocused) {
|
|
32041
32033
|
dispatch(actions.pointGraph.focusPoint(i));
|
|
32042
32034
|
} else {
|
|
32035
|
+
if (event.relatedTarget?.id === REMOVE_BUTTON_ID) {
|
|
32036
|
+
return;
|
|
32037
|
+
}
|
|
32043
32038
|
dispatch(actions.pointGraph.blurPoint());
|
|
32044
32039
|
}
|
|
32040
|
+
},
|
|
32041
|
+
onClick: () => {
|
|
32042
|
+
dispatch(actions.pointGraph.clickPoint(i));
|
|
32045
32043
|
}
|
|
32046
32044
|
})));
|
|
32047
32045
|
}
|
|
@@ -32200,6 +32198,7 @@ function useDraggablePx(args) {
|
|
|
32200
32198
|
* - Protractor
|
|
32201
32199
|
* - Interactive Graph Elements
|
|
32202
32200
|
*/
|
|
32201
|
+
const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
|
|
32203
32202
|
const MafsGraph = props => {
|
|
32204
32203
|
const {
|
|
32205
32204
|
state,
|
|
@@ -32307,7 +32306,8 @@ const MafsGraph = props => {
|
|
|
32307
32306
|
props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
|
|
32308
32307
|
lockedFigures: props.lockedFigures,
|
|
32309
32308
|
range: state.range
|
|
32310
|
-
}))), props.
|
|
32309
|
+
}))), props.flags?.["mafs"]?.["interactive-graph-locked-features-labels"] && props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
|
|
32310
|
+
flags: props.flags,
|
|
32311
32311
|
lockedFigures: props.lockedFigures
|
|
32312
32312
|
}), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
32313
32313
|
style: {
|
|
@@ -32324,10 +32324,10 @@ const MafsGraph = props => {
|
|
|
32324
32324
|
zoom: false,
|
|
32325
32325
|
width: width,
|
|
32326
32326
|
height: height
|
|
32327
|
-
}, props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
|
|
32327
|
+
}, /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
|
|
32328
32328
|
state,
|
|
32329
32329
|
dispatch
|
|
32330
|
-
}))))), renderGraphControls({
|
|
32330
|
+
})))))), renderGraphControls({
|
|
32331
32331
|
state,
|
|
32332
32332
|
dispatch,
|
|
32333
32333
|
width
|
|
@@ -32338,26 +32338,17 @@ const renderPointGraphControls = props => /*#__PURE__*/React__namespace.createEl
|
|
|
32338
32338
|
flexDirection: "row",
|
|
32339
32339
|
width: props.width
|
|
32340
32340
|
}
|
|
32341
|
-
}, /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
32342
|
-
|
|
32343
|
-
style: {
|
|
32344
|
-
width: "100%",
|
|
32345
|
-
marginLeft: "20px"
|
|
32346
|
-
},
|
|
32347
|
-
tabIndex: 0,
|
|
32348
|
-
onClick: () => {
|
|
32349
|
-
props.dispatch(actions.pointGraph.addPoint([0, 0]));
|
|
32350
|
-
}
|
|
32351
|
-
}, "Add Point"), (props.state.focusedPointIndex !== null || props.state.previouslyFocusedPointIndex !== null) && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
32341
|
+
}, props.state.showRemovePointButton && props.state.focusedPointIndex !== null && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
32342
|
+
id: REMOVE_BUTTON_ID,
|
|
32352
32343
|
kind: "secondary",
|
|
32353
32344
|
color: "destructive",
|
|
32354
|
-
tabIndex:
|
|
32345
|
+
tabIndex: -1,
|
|
32355
32346
|
style: {
|
|
32356
32347
|
width: "100%",
|
|
32357
32348
|
marginLeft: "20px"
|
|
32358
32349
|
},
|
|
32359
32350
|
onClick: event => {
|
|
32360
|
-
props.dispatch(actions.pointGraph.removePoint(props.state.
|
|
32351
|
+
props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));
|
|
32361
32352
|
}
|
|
32362
32353
|
}, "Remove Point"));
|
|
32363
32354
|
const renderGraphControls = props => {
|
|
@@ -32492,6 +32483,8 @@ const renderGraph = props => {
|
|
|
32492
32483
|
graphState: state,
|
|
32493
32484
|
dispatch: dispatch
|
|
32494
32485
|
});
|
|
32486
|
+
case "none":
|
|
32487
|
+
return null;
|
|
32495
32488
|
default:
|
|
32496
32489
|
throw new wonderStuffCore.UnreachableCaseError(type);
|
|
32497
32490
|
}
|
|
@@ -32550,7 +32543,7 @@ function initializeGraphState(params) {
|
|
|
32550
32543
|
coords: getPointCoords(graph, range, step),
|
|
32551
32544
|
numPoints: graph.numPoints || 0,
|
|
32552
32545
|
focusedPointIndex: null,
|
|
32553
|
-
|
|
32546
|
+
showRemovePointButton: false
|
|
32554
32547
|
};
|
|
32555
32548
|
case "circle":
|
|
32556
32549
|
return {
|
|
@@ -32584,6 +32577,11 @@ function initializeGraphState(params) {
|
|
|
32584
32577
|
allowReflexAngles: Boolean(graph.allowReflexAngles),
|
|
32585
32578
|
snapDegrees: Number(graph.snapDegrees)
|
|
32586
32579
|
};
|
|
32580
|
+
case "none":
|
|
32581
|
+
return {
|
|
32582
|
+
...shared,
|
|
32583
|
+
type: "none"
|
|
32584
|
+
};
|
|
32587
32585
|
default:
|
|
32588
32586
|
throw new wonderStuffCore.UnreachableCaseError(graph);
|
|
32589
32587
|
}
|
|
@@ -32816,6 +32814,8 @@ function interactiveGraphReducer(state, action) {
|
|
|
32816
32814
|
return doBlurPoint(state);
|
|
32817
32815
|
case DELETE_INTENT:
|
|
32818
32816
|
return doDeleteIntent(state);
|
|
32817
|
+
case CLICK_POINT:
|
|
32818
|
+
return doClickPoint(state, action);
|
|
32819
32819
|
default:
|
|
32820
32820
|
throw new wonderStuffCore.UnreachableCaseError(action);
|
|
32821
32821
|
}
|
|
@@ -32847,13 +32847,26 @@ function doBlurPoint(state, action) {
|
|
|
32847
32847
|
case "point":
|
|
32848
32848
|
return {
|
|
32849
32849
|
...state,
|
|
32850
|
-
|
|
32851
|
-
|
|
32850
|
+
focusedPointIndex: null,
|
|
32851
|
+
showRemovePointButton: false
|
|
32852
32852
|
};
|
|
32853
32853
|
default:
|
|
32854
32854
|
return state;
|
|
32855
32855
|
}
|
|
32856
32856
|
}
|
|
32857
|
+
function doClickPoint(state, action) {
|
|
32858
|
+
if (state.type !== "point") {
|
|
32859
|
+
return state;
|
|
32860
|
+
}
|
|
32861
|
+
if (state.numPoints === "unlimited") {
|
|
32862
|
+
return {
|
|
32863
|
+
...state,
|
|
32864
|
+
focusedPointIndex: action.index,
|
|
32865
|
+
showRemovePointButton: true
|
|
32866
|
+
};
|
|
32867
|
+
}
|
|
32868
|
+
return state;
|
|
32869
|
+
}
|
|
32857
32870
|
function doMovePointInFigure(state, action) {
|
|
32858
32871
|
switch (state.type) {
|
|
32859
32872
|
case "segment":
|
|
@@ -32894,6 +32907,8 @@ function doMovePointInFigure(state, action) {
|
|
|
32894
32907
|
}
|
|
32895
32908
|
case "angle":
|
|
32896
32909
|
case "circle":
|
|
32910
|
+
throw new Error("FIXME implement circle reducer");
|
|
32911
|
+
case "none":
|
|
32897
32912
|
case "point":
|
|
32898
32913
|
case "polygon":
|
|
32899
32914
|
case "quadratic":
|
|
@@ -33209,7 +33224,8 @@ function doAddPoint(state, action) {
|
|
|
33209
33224
|
return {
|
|
33210
33225
|
...state,
|
|
33211
33226
|
hasBeenInteractedWith: true,
|
|
33212
|
-
coords: [...state.coords, snappedPoint]
|
|
33227
|
+
coords: [...state.coords, snappedPoint],
|
|
33228
|
+
showRemovePointButton: false
|
|
33213
33229
|
};
|
|
33214
33230
|
}
|
|
33215
33231
|
function doRemovePoint(state, action) {
|
|
@@ -33220,7 +33236,7 @@ function doRemovePoint(state, action) {
|
|
|
33220
33236
|
...state,
|
|
33221
33237
|
coords: state.coords.filter((_, i) => i !== action.index),
|
|
33222
33238
|
focusedPointIndex: null,
|
|
33223
|
-
|
|
33239
|
+
showRemovePointButton: false
|
|
33224
33240
|
};
|
|
33225
33241
|
}
|
|
33226
33242
|
const getDeltaVertex = (maxMoves, minMoves, delta) => {
|
|
@@ -33619,6 +33635,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
33619
33635
|
const snapTo = graph.type === "polygon" ? graph.snapTo : null;
|
|
33620
33636
|
const showAngles = graph.type === "polygon" ? graph.showAngles : null;
|
|
33621
33637
|
const showSides = graph.type === "polygon" ? graph.showSides : null;
|
|
33638
|
+
const startCoords = "startCoords" in graph ? graph.startCoords : null;
|
|
33622
33639
|
const originalPropsRef = React.useRef(props);
|
|
33623
33640
|
const latestPropsRef = wonderBlocksCore.useLatestRef(props);
|
|
33624
33641
|
React.useEffect(() => {
|
|
@@ -33629,7 +33646,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
33629
33646
|
if (latestPropsRef.current !== originalPropsRef.current) {
|
|
33630
33647
|
dispatch(reinitialize(latestPropsRef.current));
|
|
33631
33648
|
}
|
|
33632
|
-
}, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef,
|
|
33649
|
+
}, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, startCoords]);
|
|
33633
33650
|
|
|
33634
33651
|
// If the graph is static, it always displays the correct answer. This is
|
|
33635
33652
|
// standard behavior for Perseus widgets (e.g. compare the Radio widget).
|
|
@@ -33955,6 +33972,8 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
33955
33972
|
___default["default"].invoke(this.points, "remove");
|
|
33956
33973
|
this.line?.remove();
|
|
33957
33974
|
};
|
|
33975
|
+
addNoneControls = () => {};
|
|
33976
|
+
removeNoneControls = () => {};
|
|
33958
33977
|
addLinearControls = () => {
|
|
33959
33978
|
this.addLine("line");
|
|
33960
33979
|
};
|
|
@@ -34924,7 +34943,7 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
34924
34943
|
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
34925
34944
|
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
34926
34945
|
return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends({}, this.props, {
|
|
34927
|
-
|
|
34946
|
+
flags: this.props.apiOptions?.flags,
|
|
34928
34947
|
ref: this.mafsRef,
|
|
34929
34948
|
gridStep: gridStep,
|
|
34930
34949
|
snapStep: snapStep,
|
|
@@ -35157,6 +35176,9 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
35157
35176
|
});
|
|
35158
35177
|
});
|
|
35159
35178
|
}
|
|
35179
|
+
static getNoneEquationString() {
|
|
35180
|
+
return "";
|
|
35181
|
+
}
|
|
35160
35182
|
static getLinearEquationString(props) {
|
|
35161
35183
|
const coords = InteractiveGraph.getLineCoords(props.graph, props);
|
|
35162
35184
|
if (eq(coords[0][0], coords[1][0])) {
|
|
@@ -35270,6 +35292,16 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
35270
35292
|
return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
|
|
35271
35293
|
}
|
|
35272
35294
|
static validate(userInput, rubric, component) {
|
|
35295
|
+
// None-type graphs are not graded
|
|
35296
|
+
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
35297
|
+
return {
|
|
35298
|
+
type: "points",
|
|
35299
|
+
earned: 0,
|
|
35300
|
+
total: 0,
|
|
35301
|
+
message: null
|
|
35302
|
+
};
|
|
35303
|
+
}
|
|
35304
|
+
|
|
35273
35305
|
// When nothing has moved, there will neither be coords nor the
|
|
35274
35306
|
// circle's center/radius fields. When those fields are absent, skip
|
|
35275
35307
|
// all these checks; just go mark the answer as empty.
|
|
@@ -35485,6 +35517,8 @@ function shouldUseMafs(mafsFlags, graph) {
|
|
|
35485
35517
|
return false;
|
|
35486
35518
|
}
|
|
35487
35519
|
switch (graph.type) {
|
|
35520
|
+
case "none":
|
|
35521
|
+
return true;
|
|
35488
35522
|
case "point":
|
|
35489
35523
|
if (graph.numPoints === UNLIMITED) {
|
|
35490
35524
|
return Boolean(mafsFlags["unlimited-point"]);
|
|
@@ -35506,7 +35540,7 @@ function shouldUseMafs(mafsFlags, graph) {
|
|
|
35506
35540
|
const staticTransform$3 = ___default["default"].identity;
|
|
35507
35541
|
var InteractiveGraph$1 = {
|
|
35508
35542
|
name: "interactive-graph",
|
|
35509
|
-
displayName: "Interactive graph",
|
|
35543
|
+
displayName: "Interactive graph (Assessments only)",
|
|
35510
35544
|
widget: InteractiveGraph,
|
|
35511
35545
|
staticTransform: staticTransform$3
|
|
35512
35546
|
};
|
|
@@ -37460,11 +37494,6 @@ var LabelImage$1 = {
|
|
|
37460
37494
|
};
|
|
37461
37495
|
|
|
37462
37496
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
37463
|
-
let Layout = /*#__PURE__*/function (Layout) {
|
|
37464
|
-
Layout["HORIZONTAL"] = "horizontal";
|
|
37465
|
-
Layout["VERTICAL"] = "vertical";
|
|
37466
|
-
return Layout;
|
|
37467
|
-
}({});
|
|
37468
37497
|
var ItemState = /*#__PURE__*/function (ItemState) {
|
|
37469
37498
|
ItemState["STATIC"] = "static";
|
|
37470
37499
|
ItemState["DRAGGING"] = "dragging";
|
|
@@ -37491,7 +37520,7 @@ class Placeholder extends React__namespace.Component {
|
|
|
37491
37520
|
const {
|
|
37492
37521
|
layout
|
|
37493
37522
|
} = this.props;
|
|
37494
|
-
const className = aphrodite.css(styles$d.card, styles$d.placeholder, layout ===
|
|
37523
|
+
const className = aphrodite.css(styles$d.card, styles$d.placeholder, layout === "horizontal" && styles$d.horizontalCard);
|
|
37495
37524
|
const style = {
|
|
37496
37525
|
width: this.props.width,
|
|
37497
37526
|
height: this.props.height
|
|
@@ -37698,7 +37727,7 @@ class Draggable extends React__namespace.Component {
|
|
|
37698
37727
|
// We need to keep backwards compatbility with rules specified directly
|
|
37699
37728
|
// in CSS. Hence the hacky tacking on of manual classNames.
|
|
37700
37729
|
// See sortable.less for details.
|
|
37701
|
-
let className = aphrodite.css(styles$d.card, styles$d.draggable, layout ===
|
|
37730
|
+
let className = aphrodite.css(styles$d.card, styles$d.draggable, layout === "horizontal" && styles$d.horizontalCard, layout === "vertical" && styles$d.verticalCard, type === ItemState.DRAGGING && styles$d.dragging, type === ItemState.DISABLED && styles$d.disabled, !includePadding && styles$d.unpaddedCard) + " " + ClassNames.INTERACTIVE + " perseus-sortable-draggable";
|
|
37702
37731
|
if (!includePadding) {
|
|
37703
37732
|
className += " perseus-sortable-draggable-unpadded";
|
|
37704
37733
|
}
|
|
@@ -37738,7 +37767,7 @@ class Draggable extends React__namespace.Component {
|
|
|
37738
37767
|
}
|
|
37739
37768
|
class Sortable extends React__namespace.Component {
|
|
37740
37769
|
static defaultProps = {
|
|
37741
|
-
layout:
|
|
37770
|
+
layout: "horizontal",
|
|
37742
37771
|
padding: true,
|
|
37743
37772
|
disabled: false,
|
|
37744
37773
|
constraints: {},
|
|
@@ -37833,7 +37862,7 @@ class Sortable extends React__namespace.Component {
|
|
|
37833
37862
|
if (constraints?.width) {
|
|
37834
37863
|
// Items must be at least as wide as the specified constraint
|
|
37835
37864
|
syncWidth = ___default["default"].max(widths.concat(constraints.width));
|
|
37836
|
-
} else if (layout ===
|
|
37865
|
+
} else if (layout === "vertical") {
|
|
37837
37866
|
// Sync widths to get a clean column
|
|
37838
37867
|
syncWidth = ___default["default"].max(widths);
|
|
37839
37868
|
}
|
|
@@ -37841,7 +37870,7 @@ class Sortable extends React__namespace.Component {
|
|
|
37841
37870
|
if (constraints?.height) {
|
|
37842
37871
|
// Items must be at least as high as the specified constraint
|
|
37843
37872
|
syncHeight = ___default["default"].max(heights.concat(constraints.height));
|
|
37844
|
-
} else if (layout ===
|
|
37873
|
+
} else if (layout === "horizontal") {
|
|
37845
37874
|
// Sync widths to get a clean row
|
|
37846
37875
|
syncHeight = ___default["default"].max(heights);
|
|
37847
37876
|
}
|
|
@@ -37923,7 +37952,7 @@ class Sortable extends React__namespace.Component {
|
|
|
37923
37952
|
|
|
37924
37953
|
// @ts-expect-error - TS2551 - Property 'splice' does not exist on type 'readonly SortableItem[]'. Did you mean 'slice'?
|
|
37925
37954
|
items.splice(currentIndex, 1);
|
|
37926
|
-
if (this.props.layout ===
|
|
37955
|
+
if (this.props.layout === "horizontal") {
|
|
37927
37956
|
// @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'. | TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
|
|
37928
37957
|
const midWidth = $draggable.offset().left - $sortable.offset().left;
|
|
37929
37958
|
let sumWidth = 0;
|
|
@@ -38037,19 +38066,19 @@ class Sortable extends React__namespace.Component {
|
|
|
38037
38066
|
// We need to keep backwards compatbility with rules specified directly
|
|
38038
38067
|
// in CSS. See sortable.less for details.
|
|
38039
38068
|
const className = aphrodite.css(styles$d.sortable) + " perseus-sortable";
|
|
38040
|
-
const syncWidth = this.props.constraints?.width || layout ===
|
|
38041
|
-
const syncHeight = this.props.constraints?.height || layout ===
|
|
38069
|
+
const syncWidth = this.props.constraints?.width || layout === "vertical";
|
|
38070
|
+
const syncHeight = this.props.constraints?.height || layout === "horizontal";
|
|
38042
38071
|
___default["default"].each(this.state.items, function (item, i, items) {
|
|
38043
38072
|
const isLast = i === items.length - 1;
|
|
38044
38073
|
const isStatic = item.state === ItemState.STATIC || item.state === ItemState.DISABLED;
|
|
38045
38074
|
let margin;
|
|
38046
38075
|
|
|
38047
38076
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38048
|
-
if (this.props.layout ===
|
|
38077
|
+
if (this.props.layout === "horizontal") {
|
|
38049
38078
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38050
38079
|
margin = "0 " + this.props.margin + "px 0 0"; // right
|
|
38051
38080
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38052
|
-
} else if (this.props.layout ===
|
|
38081
|
+
} else if (this.props.layout === "vertical") {
|
|
38053
38082
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
38054
38083
|
margin = "0 0 " + this.props.margin + "px 0"; // bottom
|
|
38055
38084
|
}
|
|
@@ -38171,7 +38200,6 @@ const styles$d = aphrodite.StyleSheet.create({
|
|
|
38171
38200
|
}
|
|
38172
38201
|
});
|
|
38173
38202
|
|
|
38174
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
38175
38203
|
const {
|
|
38176
38204
|
shuffle: shuffle$1,
|
|
38177
38205
|
seededRNG
|
|
@@ -38179,18 +38207,6 @@ const {
|
|
|
38179
38207
|
const HACKY_CSS_CLASSNAME = "perseus-widget-matcher";
|
|
38180
38208
|
class Matcher extends React__namespace.Component {
|
|
38181
38209
|
static contextType = PerseusI18nContext;
|
|
38182
|
-
static propTypes = {
|
|
38183
|
-
apiOptions: ApiOptions.propTypes,
|
|
38184
|
-
labels: PropTypes__default["default"].array,
|
|
38185
|
-
left: PropTypes__default["default"].array,
|
|
38186
|
-
onChange: PropTypes__default["default"].func,
|
|
38187
|
-
orderMatters: PropTypes__default["default"].bool,
|
|
38188
|
-
padding: PropTypes__default["default"].bool,
|
|
38189
|
-
problemNum: PropTypes__default["default"].number,
|
|
38190
|
-
right: PropTypes__default["default"].array,
|
|
38191
|
-
trackInteraction: PropTypes__default["default"].func.isRequired,
|
|
38192
|
-
linterContext: PerseusLinter.linterContextProps
|
|
38193
|
-
};
|
|
38194
38210
|
static defaultProps = {
|
|
38195
38211
|
left: [],
|
|
38196
38212
|
right: [],
|
|
@@ -38327,7 +38343,7 @@ class Matcher extends React__namespace.Component {
|
|
|
38327
38343
|
className: aphrodite.css(styles$c.column)
|
|
38328
38344
|
}, /*#__PURE__*/React__namespace.createElement(Sortable, {
|
|
38329
38345
|
options: left,
|
|
38330
|
-
layout:
|
|
38346
|
+
layout: "vertical",
|
|
38331
38347
|
padding: this.props.padding,
|
|
38332
38348
|
disabled: !this.props.orderMatters,
|
|
38333
38349
|
constraints: constraints,
|
|
@@ -38340,11 +38356,9 @@ class Matcher extends React__namespace.Component {
|
|
|
38340
38356
|
ref: "left"
|
|
38341
38357
|
})), /*#__PURE__*/React__namespace.createElement("td", {
|
|
38342
38358
|
className: aphrodite.css(styles$c.column, styles$c.columnRight)
|
|
38343
|
-
}, /*#__PURE__*/React__namespace.createElement(Sortable
|
|
38344
|
-
// @ts-expect-error - TS2322 - Type 'readonly unknown[]' is not assignable to type 'readonly string[]'.
|
|
38345
|
-
, {
|
|
38359
|
+
}, /*#__PURE__*/React__namespace.createElement(Sortable, {
|
|
38346
38360
|
options: right,
|
|
38347
|
-
layout:
|
|
38361
|
+
layout: "vertical",
|
|
38348
38362
|
padding: this.props.padding,
|
|
38349
38363
|
constraints: constraints,
|
|
38350
38364
|
onMeasure: this.onMeasureRight,
|
|
@@ -38865,6 +38879,7 @@ const staticTransform$2 = editorProps => {
|
|
|
38865
38879
|
var Matrix$1 = {
|
|
38866
38880
|
name: "matrix",
|
|
38867
38881
|
displayName: "Matrix",
|
|
38882
|
+
hidden: true,
|
|
38868
38883
|
widget: Matrix,
|
|
38869
38884
|
transform: propTransform$1,
|
|
38870
38885
|
staticTransform: staticTransform$2,
|
|
@@ -39021,6 +39036,7 @@ const propUpgrades = {
|
|
|
39021
39036
|
var Measurer$1 = {
|
|
39022
39037
|
name: "measurer",
|
|
39023
39038
|
displayName: "Measurer",
|
|
39039
|
+
hidden: true,
|
|
39024
39040
|
widget: Measurer,
|
|
39025
39041
|
version: {
|
|
39026
39042
|
major: 1,
|
|
@@ -39913,12 +39929,7 @@ class MoleculeWidget extends React__namespace.Component {
|
|
|
39913
39929
|
// TODO(colin): this is here as part of the interface for a component.
|
|
39914
39930
|
// Figure out if there is something more appropriate that this should
|
|
39915
39931
|
// return.
|
|
39916
|
-
return
|
|
39917
|
-
type: "points",
|
|
39918
|
-
earned: 0,
|
|
39919
|
-
total: 0,
|
|
39920
|
-
message: null
|
|
39921
|
-
};
|
|
39932
|
+
return noopValidator();
|
|
39922
39933
|
};
|
|
39923
39934
|
render() {
|
|
39924
39935
|
return /*#__PURE__*/React__namespace.createElement(Molecule, {
|
|
@@ -40518,10 +40529,6 @@ var NumberLine$1 = {
|
|
|
40518
40529
|
};
|
|
40519
40530
|
|
|
40520
40531
|
class PlaceholderCard extends React__namespace.Component {
|
|
40521
|
-
static propTypes = {
|
|
40522
|
-
width: PropTypes__default["default"].number.isRequired,
|
|
40523
|
-
height: PropTypes__default["default"].number.isRequired
|
|
40524
|
-
};
|
|
40525
40532
|
render() {
|
|
40526
40533
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
40527
40534
|
className: "card-wrap " + ClassNames.INTERACTIVE,
|
|
@@ -40531,7 +40538,7 @@ class PlaceholderCard extends React__namespace.Component {
|
|
|
40531
40538
|
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
40532
40539
|
className: "card placeholder",
|
|
40533
40540
|
style: {
|
|
40534
|
-
height: this.props.
|
|
40541
|
+
height: this.props.width
|
|
40535
40542
|
}
|
|
40536
40543
|
}));
|
|
40537
40544
|
}
|
|
@@ -40545,29 +40552,10 @@ class DragHintCard extends React__namespace.Component {
|
|
|
40545
40552
|
}));
|
|
40546
40553
|
}
|
|
40547
40554
|
}
|
|
40548
|
-
const PropTypePosition = PropTypes__default["default"].shape({
|
|
40549
|
-
left: PropTypes__default["default"].number,
|
|
40550
|
-
top: PropTypes__default["default"].number
|
|
40551
|
-
});
|
|
40552
40555
|
class Card extends React__namespace.Component {
|
|
40553
40556
|
static contextType = PerseusI18nContext;
|
|
40554
40557
|
// @ts-expect-error - TS2564 - Property 'mouseMoveUpBound' has no initializer and is not definitely assigned in the constructor.
|
|
40555
40558
|
mouseMoveUpBound;
|
|
40556
|
-
static propTypes = {
|
|
40557
|
-
floating: PropTypes__default["default"].bool.isRequired,
|
|
40558
|
-
animating: PropTypes__default["default"].bool,
|
|
40559
|
-
width: PropTypes__default["default"].number,
|
|
40560
|
-
stack: PropTypes__default["default"].bool,
|
|
40561
|
-
onMouseDown: PropTypes__default["default"].func,
|
|
40562
|
-
onMouseMove: PropTypes__default["default"].func,
|
|
40563
|
-
onMouseUp: PropTypes__default["default"].func,
|
|
40564
|
-
// Used only for floating/animating cards
|
|
40565
|
-
startMouse: PropTypePosition,
|
|
40566
|
-
startOffset: PropTypePosition,
|
|
40567
|
-
animateTo: PropTypePosition,
|
|
40568
|
-
onAnimationEnd: PropTypes__default["default"].func,
|
|
40569
|
-
linterContext: PerseusLinter.linterContextProps
|
|
40570
|
-
};
|
|
40571
40559
|
static defaultProps = {
|
|
40572
40560
|
stack: false,
|
|
40573
40561
|
animating: false,
|
|
@@ -40605,7 +40593,7 @@ class Card extends React__namespace.Component {
|
|
|
40605
40593
|
this.props.fakeRef !== nextProps.fakeRef;
|
|
40606
40594
|
}
|
|
40607
40595
|
componentDidUpdate(prevProps, prevState) {
|
|
40608
|
-
if (this.props.animating && !prevProps.animating) {
|
|
40596
|
+
if (this.props.animating && !prevProps.animating && this.props.animateTo && this.props.startOffset) {
|
|
40609
40597
|
// If we just were changed into animating, start the animation.
|
|
40610
40598
|
// We pick the animation speed based on the distance that the card
|
|
40611
40599
|
// needs to travel. (Why sqrt? Just because it looks nice -- with a
|
|
@@ -40672,8 +40660,8 @@ class Card extends React__namespace.Component {
|
|
|
40672
40660
|
if (this.props.floating) {
|
|
40673
40661
|
style = {
|
|
40674
40662
|
position: "absolute",
|
|
40675
|
-
left: this.props.startOffset
|
|
40676
|
-
top: this.props.startOffset
|
|
40663
|
+
left: this.props.startOffset?.left,
|
|
40664
|
+
top: this.props.startOffset?.top
|
|
40677
40665
|
};
|
|
40678
40666
|
}
|
|
40679
40667
|
if (this.props.width) {
|
|
@@ -40683,7 +40671,7 @@ class Card extends React__namespace.Component {
|
|
|
40683
40671
|
if (this.props.stack) {
|
|
40684
40672
|
className.push("stack");
|
|
40685
40673
|
}
|
|
40686
|
-
if (this.props.floating && !this.props.animating) {
|
|
40674
|
+
if (this.props.floating && !this.props.animating && this.props.mouse && this.props.startMouse) {
|
|
40687
40675
|
className.push("dragging");
|
|
40688
40676
|
style.left += this.props.mouse.left - this.props.startMouse.left;
|
|
40689
40677
|
style.top += this.props.mouse.top - this.props.startMouse.top;
|
|
@@ -40708,14 +40696,14 @@ class Card extends React__namespace.Component {
|
|
|
40708
40696
|
}
|
|
40709
40697
|
}
|
|
40710
40698
|
const NORMAL = "normal",
|
|
40711
|
-
HORIZONTAL
|
|
40699
|
+
HORIZONTAL = "horizontal";
|
|
40712
40700
|
class Orderer extends React__namespace.Component {
|
|
40713
40701
|
static defaultProps = {
|
|
40714
40702
|
current: [],
|
|
40715
40703
|
options: [],
|
|
40716
40704
|
correctOptions: [],
|
|
40717
40705
|
height: NORMAL,
|
|
40718
|
-
layout: HORIZONTAL
|
|
40706
|
+
layout: HORIZONTAL,
|
|
40719
40707
|
linterContext: PerseusLinter.linterContextDefault
|
|
40720
40708
|
};
|
|
40721
40709
|
state = {
|
|
@@ -40868,7 +40856,7 @@ class Orderer extends React__namespace.Component {
|
|
|
40868
40856
|
};
|
|
40869
40857
|
findCorrectIndex = (draggable, list) => {
|
|
40870
40858
|
// Find the correct index for a card given the current cards.
|
|
40871
|
-
const isHorizontal = this.props.layout === HORIZONTAL
|
|
40859
|
+
const isHorizontal = this.props.layout === HORIZONTAL;
|
|
40872
40860
|
// eslint-disable-next-line react/no-string-refs
|
|
40873
40861
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
40874
40862
|
const $dragList = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.dragList));
|
|
@@ -40918,7 +40906,7 @@ class Orderer extends React__namespace.Component {
|
|
|
40918
40906
|
if (draggable == null) {
|
|
40919
40907
|
return false;
|
|
40920
40908
|
}
|
|
40921
|
-
const isHorizontal = this.props.layout === HORIZONTAL
|
|
40909
|
+
const isHorizontal = this.props.layout === HORIZONTAL,
|
|
40922
40910
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
40923
40911
|
$draggable = $__default["default"](ReactDOM__default["default"].findDOMNode(draggable)),
|
|
40924
40912
|
// eslint-disable-next-line react/no-string-refs
|
|
@@ -41098,6 +41086,7 @@ ___default["default"].extend(Orderer, {
|
|
|
41098
41086
|
var Orderer$1 = {
|
|
41099
41087
|
name: "orderer",
|
|
41100
41088
|
displayName: "Orderer",
|
|
41089
|
+
hidden: true,
|
|
41101
41090
|
widget: Orderer,
|
|
41102
41091
|
isLintable: true
|
|
41103
41092
|
};
|
|
@@ -42908,12 +42897,7 @@ class Passage extends React__namespace.Component {
|
|
|
42908
42897
|
stylesAreApplied: false
|
|
42909
42898
|
};
|
|
42910
42899
|
static validate(state, rubric) {
|
|
42911
|
-
return
|
|
42912
|
-
type: "points",
|
|
42913
|
-
earned: 0,
|
|
42914
|
-
total: 0,
|
|
42915
|
-
message: null
|
|
42916
|
-
};
|
|
42900
|
+
return noopValidator();
|
|
42917
42901
|
}
|
|
42918
42902
|
componentDidMount() {
|
|
42919
42903
|
this._updateState();
|
|
@@ -43274,6 +43258,7 @@ class Passage extends React__namespace.Component {
|
|
|
43274
43258
|
var Passage$1 = {
|
|
43275
43259
|
name: "passage",
|
|
43276
43260
|
displayName: "Passage (SAT only)",
|
|
43261
|
+
hidden: true,
|
|
43277
43262
|
widget: Passage,
|
|
43278
43263
|
transform: editorProps => {
|
|
43279
43264
|
return ___default["default"].pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
@@ -43289,16 +43274,15 @@ class PassageRefTarget extends React__namespace.Component {
|
|
|
43289
43274
|
linterContext: PerseusLinter.linterContextDefault
|
|
43290
43275
|
};
|
|
43291
43276
|
static validate(state, rubric) {
|
|
43292
|
-
return
|
|
43293
|
-
type: "points",
|
|
43294
|
-
earned: 0,
|
|
43295
|
-
total: 0,
|
|
43296
|
-
message: null
|
|
43297
|
-
};
|
|
43277
|
+
return noopValidator();
|
|
43298
43278
|
}
|
|
43279
|
+
|
|
43280
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
43299
43281
|
getUserInput = () => {
|
|
43300
|
-
return
|
|
43282
|
+
return;
|
|
43301
43283
|
};
|
|
43284
|
+
|
|
43285
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
43302
43286
|
change = (() => {
|
|
43303
43287
|
var _this = this;
|
|
43304
43288
|
return function () {
|
|
@@ -43308,6 +43292,8 @@ class PassageRefTarget extends React__namespace.Component {
|
|
|
43308
43292
|
return change.apply(_this, args);
|
|
43309
43293
|
};
|
|
43310
43294
|
})();
|
|
43295
|
+
|
|
43296
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
43311
43297
|
simpleValidate = rubric => {
|
|
43312
43298
|
return PassageRefTarget.validate(this.getUserInput(), rubric);
|
|
43313
43299
|
};
|
|
@@ -44366,6 +44352,7 @@ const staticTransform = ___default["default"].identity;
|
|
|
44366
44352
|
var Plotter$1 = {
|
|
44367
44353
|
name: "plotter",
|
|
44368
44354
|
displayName: "Plotter",
|
|
44355
|
+
hidden: true,
|
|
44369
44356
|
widget: Plotter,
|
|
44370
44357
|
staticTransform: staticTransform
|
|
44371
44358
|
};
|
|
@@ -44416,29 +44403,14 @@ var PythonProgram$1 = {
|
|
|
44416
44403
|
widget: PythonProgram
|
|
44417
44404
|
};
|
|
44418
44405
|
|
|
44419
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
44420
44406
|
const {
|
|
44421
44407
|
shuffle
|
|
44422
44408
|
} = Util;
|
|
44423
|
-
const HORIZONTAL = "horizontal";
|
|
44424
|
-
const VERTICAL = "vertical";
|
|
44425
44409
|
class Sorter extends React__namespace.Component {
|
|
44426
|
-
|
|
44427
|
-
_isMounted;
|
|
44428
|
-
static propTypes = {
|
|
44429
|
-
apiOptions: ApiOptions.propTypes,
|
|
44430
|
-
// eslint-disable-next-line react/forbid-prop-types
|
|
44431
|
-
correct: PropTypes__default["default"].array,
|
|
44432
|
-
layout: PropTypes__default["default"].oneOf([HORIZONTAL, VERTICAL]),
|
|
44433
|
-
onChange: PropTypes__default["default"].func,
|
|
44434
|
-
padding: PropTypes__default["default"].bool,
|
|
44435
|
-
problemNum: PropTypes__default["default"].number,
|
|
44436
|
-
trackInteraction: PropTypes__default["default"].func.isRequired,
|
|
44437
|
-
linterContext: PerseusLinter.linterContextProps
|
|
44438
|
-
};
|
|
44410
|
+
_isMounted = false;
|
|
44439
44411
|
static defaultProps = {
|
|
44440
44412
|
correct: [],
|
|
44441
|
-
layout:
|
|
44413
|
+
layout: "horizontal",
|
|
44442
44414
|
padding: true,
|
|
44443
44415
|
problemNum: 0,
|
|
44444
44416
|
onChange: function () {},
|
|
@@ -44516,9 +44488,7 @@ class Sorter extends React__namespace.Component {
|
|
|
44516
44488
|
const marginPx = apiOptions.isMobile ? 8 : 5;
|
|
44517
44489
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
44518
44490
|
className: "perseus-widget-sorter perseus-clearfix"
|
|
44519
|
-
}, /*#__PURE__*/React__namespace.createElement(Sortable
|
|
44520
|
-
// @ts-expect-error - TS2322 - Type 'readonly unknown[]' is not assignable to type 'readonly string[]'.
|
|
44521
|
-
, {
|
|
44491
|
+
}, /*#__PURE__*/React__namespace.createElement(Sortable, {
|
|
44522
44492
|
options: options,
|
|
44523
44493
|
layout: this.props.layout,
|
|
44524
44494
|
margin: marginPx,
|
|
@@ -44541,7 +44511,6 @@ var Sorter$1 = {
|
|
|
44541
44511
|
const {
|
|
44542
44512
|
assert
|
|
44543
44513
|
} = InteractiveUtil;
|
|
44544
|
-
|
|
44545
44514
|
/* Input handling: Maps a (row, column) pair to a unique ref used by React,
|
|
44546
44515
|
* and extracts (row, column) pairs from input paths, used to allow outsiders
|
|
44547
44516
|
* to focus, blur, set input values, etc. */
|
|
@@ -44568,16 +44537,6 @@ const getRefForPath = function (path) {
|
|
|
44568
44537
|
};
|
|
44569
44538
|
class Table extends React__namespace.Component {
|
|
44570
44539
|
static contextType = PerseusI18nContext;
|
|
44571
|
-
static propTypes = {
|
|
44572
|
-
answers: PropTypes__default["default"].arrayOf(PropTypes__default["default"].arrayOf(PropTypes__default["default"].string)),
|
|
44573
|
-
editableHeaders: PropTypes__default["default"].bool,
|
|
44574
|
-
// The editor to use when editableHeaders is enabled
|
|
44575
|
-
Editor: PropTypes__default["default"].func,
|
|
44576
|
-
headers: PropTypes__default["default"].arrayOf(PropTypes__default["default"].string),
|
|
44577
|
-
keypadElement: mathInput.keypadElementPropType,
|
|
44578
|
-
trackInteraction: PropTypes__default["default"].func.isRequired,
|
|
44579
|
-
linterContext: PerseusLinter.linterContextProps
|
|
44580
|
-
};
|
|
44581
44540
|
static defaultProps = function () {
|
|
44582
44541
|
const defaultRows = 4;
|
|
44583
44542
|
const defaultColumns = 1;
|
|
@@ -44923,12 +44882,7 @@ class Video extends React__namespace.Component {
|
|
|
44923
44882
|
* of Khan Academy videos.
|
|
44924
44883
|
*/
|
|
44925
44884
|
static validate(userInput, rubric) {
|
|
44926
|
-
return
|
|
44927
|
-
type: "points",
|
|
44928
|
-
earned: 0,
|
|
44929
|
-
total: 0,
|
|
44930
|
-
message: null
|
|
44931
|
-
};
|
|
44885
|
+
return noopValidator();
|
|
44932
44886
|
}
|
|
44933
44887
|
getUserInput = () => {
|
|
44934
44888
|
return null;
|
|
@@ -45048,7 +45002,7 @@ const init = function (options) {
|
|
|
45048
45002
|
|
|
45049
45003
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45050
45004
|
const libName = "@khanacademy/perseus";
|
|
45051
|
-
const libVersion = "
|
|
45005
|
+
const libVersion = "34.0.0";
|
|
45052
45006
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45053
45007
|
|
|
45054
45008
|
const apiVersion = {
|
|
@@ -48563,7 +48517,6 @@ exports.SizingUtils = sizingUtils;
|
|
|
48563
48517
|
exports.TableWidget = Table$1;
|
|
48564
48518
|
exports.Util = Util;
|
|
48565
48519
|
exports.WIDGET_PROP_DENYLIST = denylist;
|
|
48566
|
-
exports.WidgetJsonifyDeprecated = WidgetJsonifyDeprecated;
|
|
48567
48520
|
exports.Widgets = widgets$1;
|
|
48568
48521
|
exports.addWidget = addWidget;
|
|
48569
48522
|
exports.apiVersion = apiVersion;
|