@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/es/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as KAS from '@khanacademy/kas';
|
|
2
2
|
import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
|
|
3
|
-
import { PerseusError, Errors, addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
|
|
4
3
|
import * as PerseusLinter from '@khanacademy/perseus-linter';
|
|
5
|
-
import { linterContextDefault, Rule
|
|
4
|
+
import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
|
|
6
5
|
import { View, RenderStateRoot, UniqueIDProvider, useLatestRef, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
|
|
7
6
|
import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
|
|
8
7
|
import { HeadingMedium, LabelSmall, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
@@ -18,6 +17,7 @@ import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder
|
|
|
18
17
|
import { color, spacing, font } from '@khanacademy/wonder-blocks-tokens';
|
|
19
18
|
import $ from 'jquery';
|
|
20
19
|
import { v4 } from 'uuid';
|
|
20
|
+
import { PerseusError, Errors, addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
|
|
21
21
|
import PropTypes from 'prop-types';
|
|
22
22
|
import { number, point, vector as vector$1, line } from '@khanacademy/kmath';
|
|
23
23
|
import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
|
|
@@ -732,23 +732,6 @@ var dependencies = /*#__PURE__*/Object.freeze({
|
|
|
732
732
|
useDependencies: useDependencies
|
|
733
733
|
});
|
|
734
734
|
|
|
735
|
-
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
736
|
-
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
737
|
-
// which get's the injected logger.
|
|
738
|
-
/**
|
|
739
|
-
* Provides logging infrastructure for Perseus
|
|
740
|
-
*/
|
|
741
|
-
const Log = {
|
|
742
|
-
// Logs a message
|
|
743
|
-
log: (message, extra) => {
|
|
744
|
-
getDependencies().Log.log(message, extra);
|
|
745
|
-
},
|
|
746
|
-
// Logs an error with a message
|
|
747
|
-
error: (message, kind, extra) => {
|
|
748
|
-
getDependencies().Log.error(message, kind, extra);
|
|
749
|
-
}
|
|
750
|
-
};
|
|
751
|
-
|
|
752
735
|
/**
|
|
753
736
|
* These are things that widgets should exclude when serializing themselves.
|
|
754
737
|
*
|
|
@@ -1192,6 +1175,23 @@ const ClassNames = {
|
|
|
1192
1175
|
MOBILE: "perseus-mobile"
|
|
1193
1176
|
};
|
|
1194
1177
|
|
|
1178
|
+
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
1179
|
+
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
1180
|
+
// which get's the injected logger.
|
|
1181
|
+
/**
|
|
1182
|
+
* Provides logging infrastructure for Perseus
|
|
1183
|
+
*/
|
|
1184
|
+
const Log = {
|
|
1185
|
+
// Logs a message
|
|
1186
|
+
log: (message, extra) => {
|
|
1187
|
+
getDependencies().Log.log(message, extra);
|
|
1188
|
+
},
|
|
1189
|
+
// Logs an error with a message
|
|
1190
|
+
error: (message, kind, extra) => {
|
|
1191
|
+
getDependencies().Log.error(message, kind, extra);
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1195
1195
|
const KhanMath = {
|
|
1196
1196
|
// Simplify formulas before display
|
|
1197
1197
|
cleanMath: function (expr) {
|
|
@@ -2106,6 +2106,176 @@ const KhanAnswerTypes = {
|
|
|
2106
2106
|
}
|
|
2107
2107
|
};
|
|
2108
2108
|
|
|
2109
|
+
/**
|
|
2110
|
+
* Get the character used for separating decimals.
|
|
2111
|
+
*/
|
|
2112
|
+
const getDecimalSeparator = locale => {
|
|
2113
|
+
var _match$;
|
|
2114
|
+
switch (locale) {
|
|
2115
|
+
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
2116
|
+
// locale properly.
|
|
2117
|
+
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
2118
|
+
//
|
|
2119
|
+
// Supported locales in Chrome:
|
|
2120
|
+
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
2121
|
+
case "ka":
|
|
2122
|
+
return ",";
|
|
2123
|
+
default:
|
|
2124
|
+
const numberWithDecimalSeparator = 1.1;
|
|
2125
|
+
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
2126
|
+
// support Safari 12.
|
|
2127
|
+
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
2128
|
+
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
2129
|
+
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
2130
|
+
.match(/[^\d\u0661\u06F1]/);
|
|
2131
|
+
return (_match$ = match == null ? void 0 : match[0]) != null ? _match$ : ".";
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
|
|
2135
|
+
/* Content creators input a list of answers which are matched from top to
|
|
2136
|
+
* bottom. The intent is that they can include spcific solutions which should
|
|
2137
|
+
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
2138
|
+
* general.
|
|
2139
|
+
*
|
|
2140
|
+
* We iterate through each answer, trying to match it with the user's input
|
|
2141
|
+
* using the following angorithm:
|
|
2142
|
+
* - Try to parse the user's input. If it doesn't parse then return "not
|
|
2143
|
+
* graded".
|
|
2144
|
+
* - For each answer:
|
|
2145
|
+
* ~ Try to validate the user's input against the answer. The answer is
|
|
2146
|
+
* expected to parse.
|
|
2147
|
+
* ~ If the user's input validates (the validator judges it "correct"), we've
|
|
2148
|
+
* matched and can stop considering answers.
|
|
2149
|
+
* - If there were no matches or the matching answer is considered "ungraded",
|
|
2150
|
+
* show the user an error. TODO(joel) - what error?
|
|
2151
|
+
* - Otherwise, pass through the resulting points and message.
|
|
2152
|
+
*/
|
|
2153
|
+
function expressionValidator(userInput, rubric,
|
|
2154
|
+
// @ts-expect-error - TS2322 - Type '() => void' is not assignable to type 'OnInputErrorFunctionType'.
|
|
2155
|
+
onInputError = function () {}, strings, locale) {
|
|
2156
|
+
const options = _.clone(rubric);
|
|
2157
|
+
_.extend(options, {
|
|
2158
|
+
decimal_separator: getDecimalSeparator(locale)
|
|
2159
|
+
});
|
|
2160
|
+
const createValidator = answer => {
|
|
2161
|
+
// We give options to KAS.parse here because it is parsing the
|
|
2162
|
+
// solution answer, not the student answer, and we don't want a
|
|
2163
|
+
// solution to work if the student is using a different language
|
|
2164
|
+
// (different from the content creation language, ie. English).
|
|
2165
|
+
const expression = KAS.parse(answer.value, rubric);
|
|
2166
|
+
// An answer may not be parsed if the expression was defined
|
|
2167
|
+
// incorrectly. For example if the answer is using a symbol defined
|
|
2168
|
+
// in the function variables list for the expression.
|
|
2169
|
+
if (!expression.parsed) {
|
|
2170
|
+
/* c8 ignore next */
|
|
2171
|
+
Log.error("Unable to parse solution answer for expression", Errors.InvalidInput, {
|
|
2172
|
+
loggedMetadata: {
|
|
2173
|
+
rubric: JSON.stringify(rubric)
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
return null;
|
|
2177
|
+
}
|
|
2178
|
+
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, _({}).extend(options, {
|
|
2179
|
+
simplify: answer.simplify,
|
|
2180
|
+
form: answer.form
|
|
2181
|
+
}), strings);
|
|
2182
|
+
};
|
|
2183
|
+
|
|
2184
|
+
// Find the first answer form that matches the user's input and that
|
|
2185
|
+
// is considered correct. Also, track whether the input is
|
|
2186
|
+
// considered "empty" for all answer forms, and keep the validation
|
|
2187
|
+
// result for the first answer form for which the user's input was
|
|
2188
|
+
// considered "ungraded".
|
|
2189
|
+
// (Terminology reminder: the answer forms are provided by the
|
|
2190
|
+
// assessment items; they are not the user's input. Each one might
|
|
2191
|
+
// represent a correct answer, an incorrect one (if the exercise
|
|
2192
|
+
// creator has predicted certain common wrong answers and wants to
|
|
2193
|
+
// provide guidance via a message), or an ungraded one (same idea,
|
|
2194
|
+
// but without giving the user an incorrect mark for the question).
|
|
2195
|
+
let matchingAnswerForm;
|
|
2196
|
+
let matchMessage;
|
|
2197
|
+
let allEmpty = true;
|
|
2198
|
+
let firstUngradedResult;
|
|
2199
|
+
for (const answerForm of rubric.answerForms || []) {
|
|
2200
|
+
const validator = createValidator(answerForm);
|
|
2201
|
+
if (!validator) {
|
|
2202
|
+
continue;
|
|
2203
|
+
}
|
|
2204
|
+
const result = validator(userInput);
|
|
2205
|
+
|
|
2206
|
+
// Short-circuit as soon as the user's input matches some answer
|
|
2207
|
+
// (independently of whether the answer is correct)
|
|
2208
|
+
if (result.correct) {
|
|
2209
|
+
matchingAnswerForm = answerForm;
|
|
2210
|
+
matchMessage = result.message || "";
|
|
2211
|
+
break;
|
|
2212
|
+
}
|
|
2213
|
+
allEmpty = allEmpty && result.empty;
|
|
2214
|
+
// If this answer form is correct and the user's input is considered
|
|
2215
|
+
// "ungraded" for it, we'll want to keep the evaluation result for
|
|
2216
|
+
// later. If the user's input doesn't match any answer forms, we'll
|
|
2217
|
+
// show the message from this validation.
|
|
2218
|
+
if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
|
|
2219
|
+
firstUngradedResult = result;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
// Now check to see if we matched any answer form at all, and if
|
|
2224
|
+
// we did, whether it's considered correct, incorrect, or ungraded
|
|
2225
|
+
if (!matchingAnswerForm) {
|
|
2226
|
+
if (firstUngradedResult) {
|
|
2227
|
+
// While we didn't directly match with any answer form, we
|
|
2228
|
+
// did at some point get an "ungraded" validation result,
|
|
2229
|
+
// which might indicate e.g. a mismatch in variable casing.
|
|
2230
|
+
// We'll return "invalid", which will let the user try again
|
|
2231
|
+
// with no penalty, and the hopefully helpful validation
|
|
2232
|
+
// message.
|
|
2233
|
+
return {
|
|
2234
|
+
type: "invalid",
|
|
2235
|
+
message: firstUngradedResult.message,
|
|
2236
|
+
suppressAlmostThere: firstUngradedResult.suppressAlmostThere
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
if (allEmpty) {
|
|
2240
|
+
// If everything graded as empty, it's invalid.
|
|
2241
|
+
return {
|
|
2242
|
+
type: "invalid",
|
|
2243
|
+
message: null
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
// We fell through all the possibilities and we're not empty,
|
|
2247
|
+
// so the answer is considered incorrect.
|
|
2248
|
+
return {
|
|
2249
|
+
type: "points",
|
|
2250
|
+
earned: 0,
|
|
2251
|
+
total: 1
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
if (matchingAnswerForm.considered === "ungraded") {
|
|
2255
|
+
// We matched an ungraded answer form - return "invalid", which
|
|
2256
|
+
// will let the user try again with no penalty
|
|
2257
|
+
const apiResult = onInputError(null,
|
|
2258
|
+
// Reserved for some widget identifier
|
|
2259
|
+
userInput, matchMessage);
|
|
2260
|
+
return {
|
|
2261
|
+
type: "invalid",
|
|
2262
|
+
message: apiResult === false ? null : matchMessage
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
// We matched a graded answer form, so we can now tell the user
|
|
2266
|
+
// whether their input was correct or incorrect, and hand out
|
|
2267
|
+
// points accordingly
|
|
2268
|
+
// TODO(eater): Seems silly to translate result to this
|
|
2269
|
+
// invalid/points thing and immediately translate it back in
|
|
2270
|
+
// ItemRenderer.scoreInput()
|
|
2271
|
+
return {
|
|
2272
|
+
type: "points",
|
|
2273
|
+
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
2274
|
+
total: 1,
|
|
2275
|
+
message: matchMessage
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2109
2279
|
const _excluded$5 = ["scoring"];
|
|
2110
2280
|
// Map of international operator names to their English equivalents
|
|
2111
2281
|
const englishOperators = {
|
|
@@ -2136,32 +2306,6 @@ const deriveKeypadVersion = apiOptions => {
|
|
|
2136
2306
|
// can remove this `virtualKeypadVersion` field entirely.
|
|
2137
2307
|
return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : "MATH_INPUT_KEYPAD_V2";
|
|
2138
2308
|
};
|
|
2139
|
-
/**
|
|
2140
|
-
* Get the character used for separating decimals.
|
|
2141
|
-
*/
|
|
2142
|
-
const getDecimalSeparator = locale => {
|
|
2143
|
-
var _match$;
|
|
2144
|
-
switch (locale) {
|
|
2145
|
-
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
2146
|
-
// locale properly.
|
|
2147
|
-
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
2148
|
-
//
|
|
2149
|
-
// Supported locales in Chrome:
|
|
2150
|
-
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
2151
|
-
case "ka":
|
|
2152
|
-
return ",";
|
|
2153
|
-
default:
|
|
2154
|
-
const numberWithDecimalSeparator = 1.1;
|
|
2155
|
-
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
2156
|
-
// support Safari 12.
|
|
2157
|
-
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
2158
|
-
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
2159
|
-
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
2160
|
-
.match(/[^\d\u0661\u06F1]/);
|
|
2161
|
-
return (_match$ = match == null ? void 0 : match[0]) != null ? _match$ : ".";
|
|
2162
|
-
}
|
|
2163
|
-
};
|
|
2164
|
-
|
|
2165
2309
|
// The new, MathQuill input expression widget
|
|
2166
2310
|
class Expression extends React.Component {
|
|
2167
2311
|
constructor(..._args) {
|
|
@@ -2230,7 +2374,7 @@ class Expression extends React.Component {
|
|
|
2230
2374
|
scoring
|
|
2231
2375
|
} = _ref,
|
|
2232
2376
|
rubric = _objectWithoutPropertiesLoose(_ref, _excluded$5);
|
|
2233
|
-
const score =
|
|
2377
|
+
const score = expressionValidator(this.getUserInput(), rubric, onInputError || function () {}, this.context.strings, this.context.locale);
|
|
2234
2378
|
|
|
2235
2379
|
// "scoring" is a flag that indicates when we are checking answers.
|
|
2236
2380
|
// otherwise, we may just be checking validity after changes.
|
|
@@ -2320,149 +2464,11 @@ class Expression extends React.Component {
|
|
|
2320
2464
|
}, cb);
|
|
2321
2465
|
};
|
|
2322
2466
|
}
|
|
2323
|
-
|
|
2324
|
-
/* Content creators input a list of answers which are matched from top to
|
|
2325
|
-
* bottom. The intent is that they can include spcific solutions which should
|
|
2326
|
-
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
2327
|
-
* general.
|
|
2328
|
-
*
|
|
2329
|
-
* We iterate through each answer, trying to match it with the user's input
|
|
2330
|
-
* using the following angorithm:
|
|
2331
|
-
* - Try to parse the user's input. If it doesn't parse then return "not
|
|
2332
|
-
* graded".
|
|
2333
|
-
* - For each answer:
|
|
2334
|
-
* ~ Try to validate the user's input against the answer. The answer is
|
|
2335
|
-
* expected to parse.
|
|
2336
|
-
* ~ If the user's input validates (the validator judges it "correct"), we've
|
|
2337
|
-
* matched and can stop considering answers.
|
|
2338
|
-
* - If there were no matches or the matching answer is considered "ungraded",
|
|
2339
|
-
* show the user an error. TODO(joel) - what error?
|
|
2340
|
-
* - Otherwise, pass through the resulting points and message.
|
|
2341
|
-
*/
|
|
2467
|
+
// TODO remove this in favor of just using expressionValidator
|
|
2342
2468
|
static validate(userInput, rubric,
|
|
2343
2469
|
// @ts-expect-error - TS2322 - Type '() => void' is not assignable to type 'OnInputErrorFunctionType'.
|
|
2344
2470
|
onInputError = function () {}, strings, locale) {
|
|
2345
|
-
|
|
2346
|
-
_.extend(options, {
|
|
2347
|
-
decimal_separator: getDecimalSeparator(locale)
|
|
2348
|
-
});
|
|
2349
|
-
const createValidator = answer => {
|
|
2350
|
-
// We give options to KAS.parse here because it is parsing the
|
|
2351
|
-
// solution answer, not the student answer, and we don't want a
|
|
2352
|
-
// solution to work if the student is using a different language
|
|
2353
|
-
// (different from the content creation language, ie. English).
|
|
2354
|
-
const expression = KAS.parse(answer.value, rubric);
|
|
2355
|
-
// An answer may not be parsed if the expression was defined
|
|
2356
|
-
// incorrectly. For example if the answer is using a symbol defined
|
|
2357
|
-
// in the function variables list for the expression.
|
|
2358
|
-
if (!expression.parsed) {
|
|
2359
|
-
/* c8 ignore next */
|
|
2360
|
-
Log.error("Unable to parse solution answer for expression", Errors.InvalidInput, {
|
|
2361
|
-
loggedMetadata: {
|
|
2362
|
-
rubric: JSON.stringify(rubric)
|
|
2363
|
-
}
|
|
2364
|
-
});
|
|
2365
|
-
return null;
|
|
2366
|
-
}
|
|
2367
|
-
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, _({}).extend(options, {
|
|
2368
|
-
simplify: answer.simplify,
|
|
2369
|
-
form: answer.form
|
|
2370
|
-
}), strings);
|
|
2371
|
-
};
|
|
2372
|
-
|
|
2373
|
-
// Find the first answer form that matches the user's input and that
|
|
2374
|
-
// is considered correct. Also, track whether the input is
|
|
2375
|
-
// considered "empty" for all answer forms, and keep the validation
|
|
2376
|
-
// result for the first answer form for which the user's input was
|
|
2377
|
-
// considered "ungraded".
|
|
2378
|
-
// (Terminology reminder: the answer forms are provided by the
|
|
2379
|
-
// assessment items; they are not the user's input. Each one might
|
|
2380
|
-
// represent a correct answer, an incorrect one (if the exercise
|
|
2381
|
-
// creator has predicted certain common wrong answers and wants to
|
|
2382
|
-
// provide guidance via a message), or an ungraded one (same idea,
|
|
2383
|
-
// but without giving the user an incorrect mark for the question).
|
|
2384
|
-
let matchingAnswerForm;
|
|
2385
|
-
let matchMessage;
|
|
2386
|
-
let allEmpty = true;
|
|
2387
|
-
let firstUngradedResult;
|
|
2388
|
-
for (const answerForm of rubric.answerForms || []) {
|
|
2389
|
-
const validate = createValidator(answerForm);
|
|
2390
|
-
if (!validate) {
|
|
2391
|
-
continue;
|
|
2392
|
-
}
|
|
2393
|
-
const result = validate(userInput);
|
|
2394
|
-
|
|
2395
|
-
// Short-circuit as soon as the user's input matches some answer
|
|
2396
|
-
// (independently of whether the answer is correct)
|
|
2397
|
-
if (result.correct) {
|
|
2398
|
-
matchingAnswerForm = answerForm;
|
|
2399
|
-
matchMessage = result.message || "";
|
|
2400
|
-
break;
|
|
2401
|
-
}
|
|
2402
|
-
allEmpty = allEmpty && result.empty;
|
|
2403
|
-
// If this answer form is correct and the user's input is considered
|
|
2404
|
-
// "ungraded" for it, we'll want to keep the evaluation result for
|
|
2405
|
-
// later. If the user's input doesn't match any answer forms, we'll
|
|
2406
|
-
// show the message from this validation.
|
|
2407
|
-
if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
|
|
2408
|
-
firstUngradedResult = result;
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
|
|
2412
|
-
// Now check to see if we matched any answer form at all, and if
|
|
2413
|
-
// we did, whether it's considered correct, incorrect, or ungraded
|
|
2414
|
-
if (!matchingAnswerForm) {
|
|
2415
|
-
if (firstUngradedResult) {
|
|
2416
|
-
// While we didn't directly match with any answer form, we
|
|
2417
|
-
// did at some point get an "ungraded" validation result,
|
|
2418
|
-
// which might indicate e.g. a mismatch in variable casing.
|
|
2419
|
-
// We'll return "invalid", which will let the user try again
|
|
2420
|
-
// with no penalty, and the hopefully helpful validation
|
|
2421
|
-
// message.
|
|
2422
|
-
return {
|
|
2423
|
-
type: "invalid",
|
|
2424
|
-
message: firstUngradedResult.message,
|
|
2425
|
-
suppressAlmostThere: firstUngradedResult.suppressAlmostThere
|
|
2426
|
-
};
|
|
2427
|
-
}
|
|
2428
|
-
if (allEmpty) {
|
|
2429
|
-
// If everything graded as empty, it's invalid.
|
|
2430
|
-
return {
|
|
2431
|
-
type: "invalid",
|
|
2432
|
-
message: null
|
|
2433
|
-
};
|
|
2434
|
-
}
|
|
2435
|
-
// We fell through all the possibilities and we're not empty,
|
|
2436
|
-
// so the answer is considered incorrect.
|
|
2437
|
-
return {
|
|
2438
|
-
type: "points",
|
|
2439
|
-
earned: 0,
|
|
2440
|
-
total: 1
|
|
2441
|
-
};
|
|
2442
|
-
}
|
|
2443
|
-
if (matchingAnswerForm.considered === "ungraded") {
|
|
2444
|
-
// We matched an ungraded answer form - return "invalid", which
|
|
2445
|
-
// will let the user try again with no penalty
|
|
2446
|
-
const apiResult = onInputError(null,
|
|
2447
|
-
// Reserved for some widget identifier
|
|
2448
|
-
userInput, matchMessage);
|
|
2449
|
-
return {
|
|
2450
|
-
type: "invalid",
|
|
2451
|
-
message: apiResult === false ? null : matchMessage
|
|
2452
|
-
};
|
|
2453
|
-
}
|
|
2454
|
-
// We matched a graded answer form, so we can now tell the user
|
|
2455
|
-
// whether their input was correct or incorrect, and hand out
|
|
2456
|
-
// points accordingly
|
|
2457
|
-
// TODO(eater): Seems silly to translate result to this
|
|
2458
|
-
// invalid/points thing and immediately translate it back in
|
|
2459
|
-
// ItemRenderer.scoreInput()
|
|
2460
|
-
return {
|
|
2461
|
-
type: "points",
|
|
2462
|
-
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
2463
|
-
total: 1,
|
|
2464
|
-
message: matchMessage
|
|
2465
|
-
};
|
|
2471
|
+
return expressionValidator(userInput, rubric, onInputError, strings, locale);
|
|
2466
2472
|
}
|
|
2467
2473
|
static getUserInputFromProps(props) {
|
|
2468
2474
|
return normalizeTex(props.value);
|
|
@@ -2670,10 +2676,10 @@ const ExpressionWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) =>
|
|
|
2670
2676
|
|
|
2671
2677
|
// HACK: Propogate "static" methods onto our wrapper component.
|
|
2672
2678
|
// In the future we should adjust client apps to not depend on these static
|
|
2673
|
-
// methods and instead adjust
|
|
2679
|
+
// methods and instead adjust Perseus to provide these facilities through
|
|
2674
2680
|
// instance methods on our Renderers.
|
|
2675
2681
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
2676
|
-
ExpressionWithDependencies.validate =
|
|
2682
|
+
ExpressionWithDependencies.validate = expressionValidator;
|
|
2677
2683
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
2678
2684
|
ExpressionWithDependencies.getUserInputFromProps = Expression.getUserInputFromProps;
|
|
2679
2685
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
@@ -19620,9 +19626,7 @@ const isIdPathPrefix = function isIdPathPrefix(prefixArray, wholeArray) {
|
|
|
19620
19626
|
|
|
19621
19627
|
// The return type for getUserInput. Widgets have full control of what is
|
|
19622
19628
|
// returned so it's not easily typed (some widgets return a scalar (string),
|
|
19623
|
-
// some return a custom-built object
|
|
19624
|
-
// `WidgetJsonifyDeprecated` which returns an object containing widget props
|
|
19625
|
-
// (filtered by deprecated keys)).
|
|
19629
|
+
// some return a custom-built object
|
|
19626
19630
|
/**
|
|
19627
19631
|
* The Widget type represents the common API that the Renderer uses to interact
|
|
19628
19632
|
* with all widgets. All widgets must implement the methods in this API, unless
|
|
@@ -22024,6 +22028,131 @@ const answerFormButtons = [{
|
|
|
22024
22028
|
value: "pi",
|
|
22025
22029
|
content: "\u03C0"
|
|
22026
22030
|
}];
|
|
22031
|
+
|
|
22032
|
+
// This function checks if the user inputted a percent value, parsing
|
|
22033
|
+
// it as a number (and maybe scaling) so that it can be graded.
|
|
22034
|
+
// NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
|
|
22035
|
+
// can accept several input forms with or without "%", the decision
|
|
22036
|
+
// to parse based on the presence of "%" in the input, is so that we
|
|
22037
|
+
// don't accidently scale the user typed value before grading, CP-930.
|
|
22038
|
+
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
22039
|
+
// If the input value is not a string ending with "%", then there's
|
|
22040
|
+
// nothing more to do. The value will be graded as inputted by user.
|
|
22041
|
+
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
22042
|
+
return inputValue;
|
|
22043
|
+
}
|
|
22044
|
+
const value = parseFloat(inputValue.slice(0, -1));
|
|
22045
|
+
// If the input value stripped of the "%" cannot be parsed as a
|
|
22046
|
+
// number (the slice is not really necessary for parseFloat to work
|
|
22047
|
+
// if the string starts with a number) then return the original
|
|
22048
|
+
// input for grading.
|
|
22049
|
+
if (isNaN(value)) {
|
|
22050
|
+
return inputValue;
|
|
22051
|
+
}
|
|
22052
|
+
|
|
22053
|
+
// Next, if all correct answers are in the range of |0,1| then we
|
|
22054
|
+
// scale the user typed value. We assume this is the correct thing
|
|
22055
|
+
// to do since the input value ends with "%".
|
|
22056
|
+
if (normalizedAnswerExpected) {
|
|
22057
|
+
return value / 100;
|
|
22058
|
+
}
|
|
22059
|
+
|
|
22060
|
+
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
22061
|
+
return value;
|
|
22062
|
+
}
|
|
22063
|
+
function numericInputValidator(userInput, rubric, strings) {
|
|
22064
|
+
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
22065
|
+
// Don't default to validating the answer as a pi answer
|
|
22066
|
+
// if answerForm isn't set on the answer
|
|
22067
|
+
// https://khanacademy.atlassian.net/browse/LC-691
|
|
22068
|
+
.filter(e => e !== "pi");
|
|
22069
|
+
const createValidator = answer => {
|
|
22070
|
+
var _answer$answerForms;
|
|
22071
|
+
const stringAnswer = `${answer.value}`;
|
|
22072
|
+
|
|
22073
|
+
// Always validate against the provided answer forms (pi, decimal, etc.)
|
|
22074
|
+
const validatorForms = [...((_answer$answerForms = answer.answerForms) != null ? _answer$answerForms : [])];
|
|
22075
|
+
|
|
22076
|
+
// When an answer is set to strict, we validate using ONLY
|
|
22077
|
+
// the provided answerForms. If strict is false, or if there
|
|
22078
|
+
// were no provided answer forms, we will include all
|
|
22079
|
+
// of the default answer forms in our validator.
|
|
22080
|
+
if (!answer.strict || validatorForms.length === 0) {
|
|
22081
|
+
validatorForms.push(...defaultAnswerForms);
|
|
22082
|
+
}
|
|
22083
|
+
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
22084
|
+
message: answer.message,
|
|
22085
|
+
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
22086
|
+
inexact: true,
|
|
22087
|
+
// TODO(merlob) backfill / delete
|
|
22088
|
+
maxError: answer.maxError,
|
|
22089
|
+
forms: validatorForms
|
|
22090
|
+
}, strings);
|
|
22091
|
+
};
|
|
22092
|
+
|
|
22093
|
+
// We may have received TeX; try to parse it before grading.
|
|
22094
|
+
// If `currentValue` is not TeX, this should be a no-op.
|
|
22095
|
+
const currentValue = ParseTex(userInput.currentValue);
|
|
22096
|
+
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22097
|
+
const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1);
|
|
22098
|
+
|
|
22099
|
+
// Look through all correct answers for one that matches either
|
|
22100
|
+
// precisely or approximately and return the appropriate message:
|
|
22101
|
+
// - if precise, return the message that the answer came with
|
|
22102
|
+
// - if it needs to be simplified, etc., show that message
|
|
22103
|
+
let result = correctAnswers.map(answer => {
|
|
22104
|
+
// The coefficient is an attribute of the widget
|
|
22105
|
+
let localValue = currentValue;
|
|
22106
|
+
if (rubric.coefficient) {
|
|
22107
|
+
if (!localValue) {
|
|
22108
|
+
localValue = 1;
|
|
22109
|
+
} else if (localValue === "-") {
|
|
22110
|
+
localValue = -1;
|
|
22111
|
+
}
|
|
22112
|
+
}
|
|
22113
|
+
const validate = createValidator(answer);
|
|
22114
|
+
return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
22115
|
+
}).find(match => match.correct || match.empty);
|
|
22116
|
+
if (!result) {
|
|
22117
|
+
// Otherwise, if the guess is not correct
|
|
22118
|
+
const otherAnswers = [].concat(
|
|
22119
|
+
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
22120
|
+
rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong"));
|
|
22121
|
+
|
|
22122
|
+
// Look through all other answers and if one matches either
|
|
22123
|
+
// precisely or approximately return the answer's message
|
|
22124
|
+
const match = otherAnswers.find(answer => {
|
|
22125
|
+
const validate = createValidator(answer);
|
|
22126
|
+
return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
|
|
22127
|
+
});
|
|
22128
|
+
result = {
|
|
22129
|
+
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22130
|
+
empty: match ? match.status === "ungraded" : false,
|
|
22131
|
+
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22132
|
+
correct: match ? match.status === "correct" : false,
|
|
22133
|
+
// @ts-expect-error - TS2339 - Property 'message' does not exist on type 'never'.
|
|
22134
|
+
message: match ? match.message : null,
|
|
22135
|
+
guess: currentValue
|
|
22136
|
+
};
|
|
22137
|
+
}
|
|
22138
|
+
|
|
22139
|
+
// TODO(eater): Seems silly to translate result to this
|
|
22140
|
+
// invalid/points thing and immediately translate it
|
|
22141
|
+
// back in ItemRenderer.scoreInput()
|
|
22142
|
+
if (result.empty) {
|
|
22143
|
+
return {
|
|
22144
|
+
type: "invalid",
|
|
22145
|
+
message: result.message
|
|
22146
|
+
};
|
|
22147
|
+
}
|
|
22148
|
+
return {
|
|
22149
|
+
type: "points",
|
|
22150
|
+
earned: result.correct ? 1 : 0,
|
|
22151
|
+
total: 1,
|
|
22152
|
+
message: result.message
|
|
22153
|
+
};
|
|
22154
|
+
}
|
|
22155
|
+
|
|
22027
22156
|
const formExamples = {
|
|
22028
22157
|
integer: (form, strings) => strings.integerExample,
|
|
22029
22158
|
proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
|
|
@@ -22070,7 +22199,7 @@ class NumericInput extends React.Component {
|
|
|
22070
22199
|
return !noFormsAccepted && !allFormsAccepted;
|
|
22071
22200
|
};
|
|
22072
22201
|
this.simpleValidate = rubric => {
|
|
22073
|
-
return
|
|
22202
|
+
return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
|
|
22074
22203
|
};
|
|
22075
22204
|
this.focus = () => {
|
|
22076
22205
|
var _this$inputRef;
|
|
@@ -22151,98 +22280,7 @@ class NumericInput extends React.Component {
|
|
|
22151
22280
|
return answerStrings[0];
|
|
22152
22281
|
}
|
|
22153
22282
|
static validate(userInput, rubric, strings) {
|
|
22154
|
-
|
|
22155
|
-
// Don't default to validating the answer as a pi answer
|
|
22156
|
-
// if answerForm isn't set on the answer
|
|
22157
|
-
// https://khanacademy.atlassian.net/browse/LC-691
|
|
22158
|
-
.filter(e => e !== "pi");
|
|
22159
|
-
const createValidator = answer => {
|
|
22160
|
-
var _answer$answerForms;
|
|
22161
|
-
const stringAnswer = `${answer.value}`;
|
|
22162
|
-
|
|
22163
|
-
// Always validate against the provided answer forms (pi, decimal, etc.)
|
|
22164
|
-
const validatorForms = [...((_answer$answerForms = answer.answerForms) != null ? _answer$answerForms : [])];
|
|
22165
|
-
|
|
22166
|
-
// When an answer is set to strict, we validate using ONLY
|
|
22167
|
-
// the provided answerForms. If strict is false, or if there
|
|
22168
|
-
// were no provided answer forms, we will include all
|
|
22169
|
-
// of the default answer forms in our validator.
|
|
22170
|
-
if (!answer.strict || validatorForms.length === 0) {
|
|
22171
|
-
validatorForms.push(...defaultAnswerForms);
|
|
22172
|
-
}
|
|
22173
|
-
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
22174
|
-
message: answer.message,
|
|
22175
|
-
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
22176
|
-
inexact: true,
|
|
22177
|
-
// TODO(merlob) backfill / delete
|
|
22178
|
-
maxError: answer.maxError,
|
|
22179
|
-
forms: validatorForms
|
|
22180
|
-
}, strings);
|
|
22181
|
-
};
|
|
22182
|
-
|
|
22183
|
-
// We may have received TeX; try to parse it before grading.
|
|
22184
|
-
// If `currentValue` is not TeX, this should be a no-op.
|
|
22185
|
-
const currentValue = ParseTex(userInput.currentValue);
|
|
22186
|
-
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22187
|
-
const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1);
|
|
22188
|
-
|
|
22189
|
-
// Look through all correct answers for one that matches either
|
|
22190
|
-
// precisely or approximately and return the appropriate message:
|
|
22191
|
-
// - if precise, return the message that the answer came with
|
|
22192
|
-
// - if it needs to be simplified, etc., show that message
|
|
22193
|
-
let result = correctAnswers.map(answer => {
|
|
22194
|
-
// The coefficient is an attribute of the widget
|
|
22195
|
-
let localValue = currentValue;
|
|
22196
|
-
if (rubric.coefficient) {
|
|
22197
|
-
if (!localValue) {
|
|
22198
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'string'.
|
|
22199
|
-
localValue = 1;
|
|
22200
|
-
} else if (localValue === "-") {
|
|
22201
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'string'.
|
|
22202
|
-
localValue = -1;
|
|
22203
|
-
}
|
|
22204
|
-
}
|
|
22205
|
-
const validate = createValidator(answer);
|
|
22206
|
-
return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
22207
|
-
}).find(match => match.correct || match.empty);
|
|
22208
|
-
if (!result) {
|
|
22209
|
-
// Otherwise, if the guess is not correct
|
|
22210
|
-
const otherAnswers = [].concat(
|
|
22211
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
22212
|
-
rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong"));
|
|
22213
|
-
|
|
22214
|
-
// Look through all other answers and if one matches either
|
|
22215
|
-
// precisely or approximately return the answer's message
|
|
22216
|
-
const match = otherAnswers.find(answer => {
|
|
22217
|
-
const validate = createValidator(answer);
|
|
22218
|
-
return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
|
|
22219
|
-
});
|
|
22220
|
-
result = {
|
|
22221
|
-
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22222
|
-
empty: match ? match.status === "ungraded" : false,
|
|
22223
|
-
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22224
|
-
correct: match ? match.status === "correct" : false,
|
|
22225
|
-
// @ts-expect-error - TS2339 - Property 'message' does not exist on type 'never'.
|
|
22226
|
-
message: match ? match.message : null,
|
|
22227
|
-
guess: currentValue
|
|
22228
|
-
};
|
|
22229
|
-
}
|
|
22230
|
-
|
|
22231
|
-
// TODO(eater): Seems silly to translate result to this
|
|
22232
|
-
// invalid/points thing and immediately translate it
|
|
22233
|
-
// back in ItemRenderer.scoreInput()
|
|
22234
|
-
if (result.empty) {
|
|
22235
|
-
return {
|
|
22236
|
-
type: "invalid",
|
|
22237
|
-
message: result.message
|
|
22238
|
-
};
|
|
22239
|
-
}
|
|
22240
|
-
return {
|
|
22241
|
-
type: "points",
|
|
22242
|
-
earned: result.correct ? 1 : 0,
|
|
22243
|
-
total: 1,
|
|
22244
|
-
message: result.message
|
|
22245
|
-
};
|
|
22283
|
+
return numericInputValidator(userInput, rubric, strings);
|
|
22246
22284
|
}
|
|
22247
22285
|
render() {
|
|
22248
22286
|
let labelText = this.props.labelText;
|
|
@@ -22343,37 +22381,6 @@ const unionAnswerForms = function unionAnswerForms(answerFormsList) {
|
|
|
22343
22381
|
return formExampleKeys.indexOf(form.name);
|
|
22344
22382
|
});
|
|
22345
22383
|
};
|
|
22346
|
-
// This function checks if the user inputted a percent value, parsing
|
|
22347
|
-
// it as a number (and maybe scaling) so that it can be graded.
|
|
22348
|
-
// NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
|
|
22349
|
-
// can accept several input forms with or without "%", the decision
|
|
22350
|
-
// to parse based on the presence of "%" in the input, is so that we
|
|
22351
|
-
// don't accidently scale the user typed value before grading, CP-930.
|
|
22352
|
-
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
22353
|
-
// If the input value is not a string ending with "%", then there's
|
|
22354
|
-
// nothing more to do. The value will be graded as inputted by user.
|
|
22355
|
-
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
22356
|
-
return inputValue;
|
|
22357
|
-
}
|
|
22358
|
-
const value = parseFloat(inputValue.slice(0, -1));
|
|
22359
|
-
// If the input value stripped of the "%" cannot be parsed as a
|
|
22360
|
-
// number (the slice is not really necessary for parseFloat to work
|
|
22361
|
-
// if the string starts with a number) then return the original
|
|
22362
|
-
// input for grading.
|
|
22363
|
-
if (isNaN(value)) {
|
|
22364
|
-
return inputValue;
|
|
22365
|
-
}
|
|
22366
|
-
|
|
22367
|
-
// Next, if all correct answers are in the range of |0,1| then we
|
|
22368
|
-
// scale the user typed value. We assume this is the correct thing
|
|
22369
|
-
// to do since the input value ends with "%".
|
|
22370
|
-
if (normalizedAnswerExpected) {
|
|
22371
|
-
return value / 100;
|
|
22372
|
-
}
|
|
22373
|
-
|
|
22374
|
-
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
22375
|
-
return value;
|
|
22376
|
-
}
|
|
22377
22384
|
const propsTransform = function propsTransform(widgetOptions) {
|
|
22378
22385
|
const rendererProps = _.extend(_.omit(widgetOptions, "answers"), {
|
|
22379
22386
|
answerForms: unionAnswerForms(
|
|
@@ -22401,6 +22408,23 @@ var NumericInput$1 = {
|
|
|
22401
22408
|
isLintable: true
|
|
22402
22409
|
};
|
|
22403
22410
|
|
|
22411
|
+
/**
|
|
22412
|
+
* Several widgets don't have "right"/"wrong" validation logic,
|
|
22413
|
+
* so this just says to move on past those widgets
|
|
22414
|
+
*
|
|
22415
|
+
* TODO: widgets that use this probably shouldn't have any
|
|
22416
|
+
* validation logic and the thing validating an exercise
|
|
22417
|
+
* should just know to skip these
|
|
22418
|
+
*/
|
|
22419
|
+
function noopValidator() {
|
|
22420
|
+
return {
|
|
22421
|
+
type: "points",
|
|
22422
|
+
earned: 0,
|
|
22423
|
+
total: 0,
|
|
22424
|
+
message: null
|
|
22425
|
+
};
|
|
22426
|
+
}
|
|
22427
|
+
|
|
22404
22428
|
const EN_DASH$1 = "\u2013";
|
|
22405
22429
|
class PassageRef extends React.Component {
|
|
22406
22430
|
constructor(..._args) {
|
|
@@ -22453,12 +22477,7 @@ class PassageRef extends React.Component {
|
|
|
22453
22477
|
};
|
|
22454
22478
|
}
|
|
22455
22479
|
static validate(userInput, rubric) {
|
|
22456
|
-
return
|
|
22457
|
-
type: "points",
|
|
22458
|
-
earned: 0,
|
|
22459
|
-
total: 0,
|
|
22460
|
-
message: null
|
|
22461
|
-
};
|
|
22480
|
+
return noopValidator();
|
|
22462
22481
|
}
|
|
22463
22482
|
componentDidMount() {
|
|
22464
22483
|
// TODO(scottgrant): This is a hack to remove the deprecated call to
|
|
@@ -22521,6 +22540,7 @@ PassageRef.defaultProps = {
|
|
|
22521
22540
|
var PassageRef$1 = {
|
|
22522
22541
|
name: "passage-ref",
|
|
22523
22542
|
displayName: "PassageRef (SAT only)",
|
|
22543
|
+
hidden: true,
|
|
22524
22544
|
defaultAlignment: "inline",
|
|
22525
22545
|
widget: PassageRef,
|
|
22526
22546
|
transform: widgetOptions => ({
|
|
@@ -23743,9 +23763,8 @@ const BaseRadio = function BaseRadio(props) {
|
|
|
23743
23763
|
// element) We have to add some extra behavior to make
|
|
23744
23764
|
// sure that we can still check the choice.
|
|
23745
23765
|
let listElem = null;
|
|
23746
|
-
let clickHandler
|
|
23766
|
+
let clickHandler;
|
|
23747
23767
|
if (editMode) {
|
|
23748
|
-
// @ts-expect-error - TS2322 - Type '(e: any) => void' is not assignable to type 'null'.
|
|
23749
23768
|
clickHandler = e => {
|
|
23750
23769
|
// Traverse the parent nodes of the clicked
|
|
23751
23770
|
// element.
|
|
@@ -23772,19 +23791,11 @@ const BaseRadio = function BaseRadio(props) {
|
|
|
23772
23791
|
// somehow break something happening inside a choice's
|
|
23773
23792
|
// child Renderers, by changing when we mount/unmount?
|
|
23774
23793
|
return /*#__PURE__*/React.createElement("li", {
|
|
23775
|
-
key: i
|
|
23776
|
-
|
|
23777
|
-
,
|
|
23778
|
-
|
|
23779
|
-
|
|
23780
|
-
,
|
|
23781
|
-
className: className
|
|
23782
|
-
// @ts-expect-error - TS2322 - Type 'null' is not assignable to type 'MouseEventHandler<HTMLLIElement> | undefined'.
|
|
23783
|
-
,
|
|
23784
|
-
onClick: clickHandler
|
|
23785
|
-
// @ts-expect-error - TS2322 - Type '((e: TouchEvent) => void) | null' is not assignable to type 'TouchEventHandler<HTMLLIElement> | undefined'.
|
|
23786
|
-
,
|
|
23787
|
-
onTouchStart: !labelWrap ? null : captureScratchpadTouchStart$1
|
|
23794
|
+
key: i,
|
|
23795
|
+
ref: e => listElem = e,
|
|
23796
|
+
className: className,
|
|
23797
|
+
onClick: clickHandler,
|
|
23798
|
+
onTouchStart: labelWrap ? undefined : captureScratchpadTouchStart$1
|
|
23788
23799
|
}, /*#__PURE__*/React.createElement(Element, _extends({}, elementProps, {
|
|
23789
23800
|
ref: ref
|
|
23790
23801
|
})));
|
|
@@ -24415,23 +24426,6 @@ var Radio = {
|
|
|
24415
24426
|
/* eslint-disable import/no-named-as-default */
|
|
24416
24427
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
24417
24428
|
|
|
24418
|
-
/* Free implementation of getUserInput. This should be used sparingly, since it
|
|
24419
|
-
* just returns all the widget's props rather than picking out those which were
|
|
24420
|
-
* input by the user.
|
|
24421
|
-
*/
|
|
24422
|
-
const WidgetJsonifyDeprecated = {
|
|
24423
|
-
getUserInput: function () {
|
|
24424
|
-
// Omit props that get passed to all widgets
|
|
24425
|
-
// @ts-expect-error - TS2339 - Property 'props' does not exist on type '{ readonly getUserInput: () => any; readonly getUserInputFromProps: (props: any) => any; }'.
|
|
24426
|
-
return _.omit(this.props, denylist);
|
|
24427
|
-
},
|
|
24428
|
-
// Static version of `WidgetJsonifyDeprecated.getUserInput`
|
|
24429
|
-
getUserInputFromProps: function (props) {
|
|
24430
|
-
// Omit props that get passed to all widgets
|
|
24431
|
-
return _.omit(props, denylist);
|
|
24432
|
-
}
|
|
24433
|
-
};
|
|
24434
|
-
|
|
24435
24429
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
24436
24430
|
class Categorizer extends React.Component {
|
|
24437
24431
|
constructor(..._args) {
|
|
@@ -24475,7 +24469,9 @@ class Categorizer extends React.Component {
|
|
|
24475
24469
|
};
|
|
24476
24470
|
}
|
|
24477
24471
|
static getUserInputFromProps(props) {
|
|
24478
|
-
return
|
|
24472
|
+
return {
|
|
24473
|
+
values: props.values
|
|
24474
|
+
};
|
|
24479
24475
|
}
|
|
24480
24476
|
onChange(itemNum, catNum) {
|
|
24481
24477
|
const values = [...this.props.values];
|
|
@@ -24621,6 +24617,7 @@ const styles$s = StyleSheet.create({
|
|
|
24621
24617
|
var Categorizer$1 = {
|
|
24622
24618
|
name: "categorizer",
|
|
24623
24619
|
displayName: "Categorizer",
|
|
24620
|
+
hidden: true,
|
|
24624
24621
|
widget: Categorizer,
|
|
24625
24622
|
transform: widgetOptions => {
|
|
24626
24623
|
return _.pick(widgetOptions, "items", "categories", "randomizeItems");
|
|
@@ -24678,6 +24675,9 @@ const parseUrl = url => new URL(url, location().origin);
|
|
|
24678
24675
|
*/
|
|
24679
24676
|
const toAbsoluteUrl = url => parseUrl(url).href;
|
|
24680
24677
|
|
|
24678
|
+
/**
|
|
24679
|
+
* This widget is for embedding Khan Academy CS programs.
|
|
24680
|
+
*/
|
|
24681
24681
|
const {
|
|
24682
24682
|
updateQueryString: updateQueryString$1
|
|
24683
24683
|
} = Util;
|
|
@@ -24723,12 +24723,6 @@ class CSProgram extends React.Component {
|
|
|
24723
24723
|
// @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
|
|
24724
24724
|
return change.apply(this, args);
|
|
24725
24725
|
};
|
|
24726
|
-
this.simpleValidate = rubric => {
|
|
24727
|
-
return CSProgram.validate({
|
|
24728
|
-
status: this.props.status,
|
|
24729
|
-
message: this.props.message
|
|
24730
|
-
}, rubric);
|
|
24731
|
-
};
|
|
24732
24726
|
}
|
|
24733
24727
|
// The widget's grading function
|
|
24734
24728
|
static validate(state, rubric) {
|
|
@@ -24761,6 +24755,12 @@ class CSProgram extends React.Component {
|
|
|
24761
24755
|
componentWillUnmount() {
|
|
24762
24756
|
$(window).off("message", this.handleMessageEvent);
|
|
24763
24757
|
}
|
|
24758
|
+
simpleValidate(rubric) {
|
|
24759
|
+
return CSProgram.validate({
|
|
24760
|
+
status: this.props.status,
|
|
24761
|
+
message: this.props.message
|
|
24762
|
+
}, rubric);
|
|
24763
|
+
}
|
|
24764
24764
|
render() {
|
|
24765
24765
|
if (!this.props.programID) {
|
|
24766
24766
|
return /*#__PURE__*/React.createElement("div", null);
|
|
@@ -24820,18 +24820,6 @@ class CSProgram extends React.Component {
|
|
|
24820
24820
|
}));
|
|
24821
24821
|
}
|
|
24822
24822
|
}
|
|
24823
|
-
CSProgram.propTypes = _extends({}, propTypes, {
|
|
24824
|
-
programID: PropTypes.string,
|
|
24825
|
-
programType: PropTypes.oneOf(["pjs", "sql", "webpage"]),
|
|
24826
|
-
width: PropTypes.number,
|
|
24827
|
-
height: PropTypes.number,
|
|
24828
|
-
// eslint-disable-next-line react/forbid-prop-types
|
|
24829
|
-
settings: PropTypes.array,
|
|
24830
|
-
showEditor: PropTypes.bool,
|
|
24831
|
-
showButtons: PropTypes.bool,
|
|
24832
|
-
status: PropTypes.oneOf(["incomplete", "incorrect", "correct"]),
|
|
24833
|
-
message: PropTypes.string
|
|
24834
|
-
});
|
|
24835
24823
|
CSProgram.defaultProps = {
|
|
24836
24824
|
showEditor: false,
|
|
24837
24825
|
showButtons: false,
|
|
@@ -24868,12 +24856,7 @@ class Definition extends React.Component {
|
|
|
24868
24856
|
};
|
|
24869
24857
|
}
|
|
24870
24858
|
static validate(userInput, rubric) {
|
|
24871
|
-
return
|
|
24872
|
-
type: "points",
|
|
24873
|
-
earned: 0,
|
|
24874
|
-
total: 0,
|
|
24875
|
-
message: null
|
|
24876
|
-
};
|
|
24859
|
+
return noopValidator();
|
|
24877
24860
|
}
|
|
24878
24861
|
render() {
|
|
24879
24862
|
return /*#__PURE__*/React.createElement(DefinitionConsumer, null, ({
|
|
@@ -24975,6 +24958,23 @@ var DeprecatedStandin$1 = {
|
|
|
24975
24958
|
hidden: true
|
|
24976
24959
|
};
|
|
24977
24960
|
|
|
24961
|
+
function dropdownValidator(userInput, rubric) {
|
|
24962
|
+
const selected = userInput.value;
|
|
24963
|
+
if (selected === 0) {
|
|
24964
|
+
return {
|
|
24965
|
+
type: "invalid",
|
|
24966
|
+
message: null
|
|
24967
|
+
};
|
|
24968
|
+
}
|
|
24969
|
+
const correct = rubric.choices[selected - 1].correct;
|
|
24970
|
+
return {
|
|
24971
|
+
type: "points",
|
|
24972
|
+
earned: correct ? 1 : 0,
|
|
24973
|
+
total: 1,
|
|
24974
|
+
message: null
|
|
24975
|
+
};
|
|
24976
|
+
}
|
|
24977
|
+
|
|
24978
24978
|
class Dropdown extends React.Component {
|
|
24979
24979
|
constructor(...args) {
|
|
24980
24980
|
super(...args);
|
|
@@ -25001,24 +25001,11 @@ class Dropdown extends React.Component {
|
|
|
25001
25001
|
};
|
|
25002
25002
|
};
|
|
25003
25003
|
this.simpleValidate = rubric => {
|
|
25004
|
-
return
|
|
25004
|
+
return dropdownValidator(this.getUserInput(), rubric);
|
|
25005
25005
|
};
|
|
25006
25006
|
}
|
|
25007
25007
|
static validate(userInput, rubric) {
|
|
25008
|
-
|
|
25009
|
-
if (selected === 0) {
|
|
25010
|
-
return {
|
|
25011
|
-
type: "invalid",
|
|
25012
|
-
message: null
|
|
25013
|
-
};
|
|
25014
|
-
}
|
|
25015
|
-
const correct = rubric.choices[selected - 1].correct;
|
|
25016
|
-
return {
|
|
25017
|
-
type: "points",
|
|
25018
|
-
earned: correct ? 1 : 0,
|
|
25019
|
-
total: 1,
|
|
25020
|
-
message: null
|
|
25021
|
-
};
|
|
25008
|
+
return dropdownValidator(userInput, rubric);
|
|
25022
25009
|
}
|
|
25023
25010
|
render() {
|
|
25024
25011
|
const children = [/*#__PURE__*/React.createElement(OptionItem, {
|
|
@@ -25101,12 +25088,7 @@ class Explanation extends React.Component {
|
|
|
25101
25088
|
};
|
|
25102
25089
|
}
|
|
25103
25090
|
static validate(userInput, rubric) {
|
|
25104
|
-
return
|
|
25105
|
-
type: "points",
|
|
25106
|
-
earned: 0,
|
|
25107
|
-
total: 0,
|
|
25108
|
-
message: null
|
|
25109
|
-
};
|
|
25091
|
+
return noopValidator();
|
|
25110
25092
|
}
|
|
25111
25093
|
componentDidMount() {
|
|
25112
25094
|
this._mounted = true;
|
|
@@ -26934,6 +26916,7 @@ const staticTransform$4 = editorProps => {
|
|
|
26934
26916
|
var Grapher$1 = {
|
|
26935
26917
|
name: "grapher",
|
|
26936
26918
|
displayName: "Grapher",
|
|
26919
|
+
hidden: true,
|
|
26937
26920
|
widget: Grapher,
|
|
26938
26921
|
transform: propTransform$2,
|
|
26939
26922
|
staticTransform: staticTransform$4
|
|
@@ -27083,21 +27066,25 @@ var Group$1 = {
|
|
|
27083
27066
|
displayName: "Group (SAT only)",
|
|
27084
27067
|
widget: Group,
|
|
27085
27068
|
traverseChildWidgets: traverseChildWidgets,
|
|
27086
|
-
hidden:
|
|
27069
|
+
hidden: true,
|
|
27087
27070
|
isLintable: true
|
|
27088
27071
|
};
|
|
27089
27072
|
|
|
27073
|
+
/**
|
|
27074
|
+
* This is an iframe widget. It is used for rendering an iframe that
|
|
27075
|
+
* then communicates its state via window.postMessage
|
|
27076
|
+
* This is useful for embedding arbitrary visualizations/simulations with
|
|
27077
|
+
* completed conditions, such as the mazes and games in Algorithms.
|
|
27078
|
+
* It's particularly well suited for embedding our ProcessingJS programs,
|
|
27079
|
+
* but could also be used for embedding viz's hosted elsewhere.
|
|
27080
|
+
*/
|
|
27090
27081
|
const {
|
|
27091
27082
|
updateQueryString
|
|
27092
27083
|
} = Util;
|
|
27093
|
-
|
|
27094
27084
|
/* This renders the iframe and handles validation via window.postMessage */
|
|
27095
27085
|
class Iframe extends React.Component {
|
|
27096
27086
|
constructor(..._args) {
|
|
27097
27087
|
super(..._args);
|
|
27098
|
-
this.getUserInput = () => {
|
|
27099
|
-
return WidgetJsonifyDeprecated.getUserInput.call(this);
|
|
27100
|
-
};
|
|
27101
27088
|
this.handleMessageEvent = e => {
|
|
27102
27089
|
// We receive data from the iframe that contains {passed: true/false}
|
|
27103
27090
|
// and use that to set the status
|
|
@@ -27132,6 +27119,12 @@ class Iframe extends React.Component {
|
|
|
27132
27119
|
componentWillUnmount() {
|
|
27133
27120
|
$(window).off("message", this.handleMessageEvent);
|
|
27134
27121
|
}
|
|
27122
|
+
getUserInput() {
|
|
27123
|
+
return {
|
|
27124
|
+
status: this.props.status,
|
|
27125
|
+
message: this.props.message
|
|
27126
|
+
};
|
|
27127
|
+
}
|
|
27135
27128
|
render() {
|
|
27136
27129
|
const style = {
|
|
27137
27130
|
width: String(this.props.width),
|
|
@@ -27197,16 +27190,6 @@ class Iframe extends React.Component {
|
|
|
27197
27190
|
/**
|
|
27198
27191
|
* This is the widget's grading function
|
|
27199
27192
|
*/
|
|
27200
|
-
Iframe.propTypes = _extends({}, propTypes, {
|
|
27201
|
-
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
27202
|
-
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
27203
|
-
url: PropTypes.string,
|
|
27204
|
-
settings: PropTypes.array,
|
|
27205
|
-
status: PropTypes.oneOf(["incomplete", "incorrect", "correct"]),
|
|
27206
|
-
message: PropTypes.string,
|
|
27207
|
-
allowFullScreen: PropTypes.bool,
|
|
27208
|
-
allowTopNavigation: PropTypes.bool
|
|
27209
|
-
});
|
|
27210
27193
|
Iframe.defaultProps = {
|
|
27211
27194
|
status: "incomplete",
|
|
27212
27195
|
// optional message
|
|
@@ -27275,12 +27258,7 @@ class ImageWidget extends React.Component {
|
|
|
27275
27258
|
this.focus = () => {};
|
|
27276
27259
|
}
|
|
27277
27260
|
static validate(userInput, rubric) {
|
|
27278
|
-
return
|
|
27279
|
-
type: "points",
|
|
27280
|
-
earned: 0,
|
|
27281
|
-
total: 0,
|
|
27282
|
-
message: null
|
|
27283
|
-
};
|
|
27261
|
+
return noopValidator();
|
|
27284
27262
|
}
|
|
27285
27263
|
// no-op
|
|
27286
27264
|
|
|
@@ -27601,12 +27579,7 @@ class Interaction extends React.Component {
|
|
|
27601
27579
|
};
|
|
27602
27580
|
}
|
|
27603
27581
|
static validate(state, rubric) {
|
|
27604
|
-
return
|
|
27605
|
-
type: "points",
|
|
27606
|
-
earned: 0,
|
|
27607
|
-
total: 0,
|
|
27608
|
-
message: null
|
|
27609
|
-
};
|
|
27582
|
+
return noopValidator();
|
|
27610
27583
|
}
|
|
27611
27584
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
27612
27585
|
// HACK(michaelpolyak): This widget is not wired to use the Perseus
|
|
@@ -29386,9 +29359,7 @@ class NumberInput extends React.Component {
|
|
|
29386
29359
|
onFocus: this._handleFocus,
|
|
29387
29360
|
onBlur: this._handleBlur,
|
|
29388
29361
|
onKeyPress: this._handleBlur,
|
|
29389
|
-
onKeyDown: this._onKeyDown
|
|
29390
|
-
// @ts-expect-error - TS2322 - Type '(e: TouchEvent) => void' is not assignable to type 'TouchEventHandler<HTMLInputElement>'.
|
|
29391
|
-
,
|
|
29362
|
+
onKeyDown: this._onKeyDown,
|
|
29392
29363
|
onTouchStart: captureScratchpadTouchStart,
|
|
29393
29364
|
defaultValue: toNumericString(this.props.value, this.state.format),
|
|
29394
29365
|
value: undefined
|
|
@@ -29674,17 +29645,23 @@ function LockedLabel(props) {
|
|
|
29674
29645
|
|
|
29675
29646
|
function GraphLockedLabelsLayer(props) {
|
|
29676
29647
|
const {
|
|
29648
|
+
flags,
|
|
29677
29649
|
lockedFigures
|
|
29678
29650
|
} = props;
|
|
29679
29651
|
return lockedFigures.map((figure, i) => {
|
|
29652
|
+
var _flags$mafs;
|
|
29680
29653
|
if (figure.type === "label") {
|
|
29681
29654
|
return /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29682
29655
|
key: `label-${i}`
|
|
29683
29656
|
}, figure));
|
|
29684
29657
|
}
|
|
29685
|
-
|
|
29686
|
-
|
|
29687
|
-
|
|
29658
|
+
if (flags != null && (_flags$mafs = flags["mafs"]) != null && _flags$mafs["locked-point-labels"] && figure.type === "point") {
|
|
29659
|
+
return /*#__PURE__*/React.createElement(React.Fragment, {
|
|
29660
|
+
key: `point-${i}`
|
|
29661
|
+
}, figure.labels.map((label, j) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29662
|
+
key: `locked-figure-${i}-label-${j}`
|
|
29663
|
+
}, label))));
|
|
29664
|
+
}
|
|
29688
29665
|
return null;
|
|
29689
29666
|
});
|
|
29690
29667
|
}
|
|
@@ -30045,7 +30022,8 @@ const actions = {
|
|
|
30045
30022
|
addPoint,
|
|
30046
30023
|
removePoint,
|
|
30047
30024
|
focusPoint,
|
|
30048
|
-
blurPoint
|
|
30025
|
+
blurPoint,
|
|
30026
|
+
clickPoint
|
|
30049
30027
|
},
|
|
30050
30028
|
polygon: {
|
|
30051
30029
|
movePoint,
|
|
@@ -30107,6 +30085,13 @@ function blurPoint() {
|
|
|
30107
30085
|
type: BLUR_POINT
|
|
30108
30086
|
};
|
|
30109
30087
|
}
|
|
30088
|
+
const CLICK_POINT = "click-point";
|
|
30089
|
+
function clickPoint(index) {
|
|
30090
|
+
return {
|
|
30091
|
+
type: CLICK_POINT,
|
|
30092
|
+
index
|
|
30093
|
+
};
|
|
30094
|
+
}
|
|
30110
30095
|
const MOVE_ALL = "move-all";
|
|
30111
30096
|
function moveAll(delta) {
|
|
30112
30097
|
return {
|
|
@@ -30431,10 +30416,14 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
|
|
|
30431
30416
|
},
|
|
30432
30417
|
"data-testid": "movable-point",
|
|
30433
30418
|
tabIndex: disableKeyboardInteraction ? -1 : tabIndex(focusBehavior),
|
|
30434
|
-
onFocus:
|
|
30435
|
-
|
|
30419
|
+
onFocus: event => {
|
|
30420
|
+
const callback = getOnFocusChangeCallback(focusBehavior);
|
|
30421
|
+
callback && callback(event, true);
|
|
30422
|
+
},
|
|
30423
|
+
onBlur: event => {
|
|
30424
|
+
const callback = getOnFocusChangeCallback(focusBehavior);
|
|
30425
|
+
callback && callback(event, false);
|
|
30436
30426
|
},
|
|
30437
|
-
onBlur: () => getOnFocusChangeCallback(focusBehavior)(false),
|
|
30438
30427
|
onClick: onClick
|
|
30439
30428
|
}, /*#__PURE__*/React.createElement("circle", {
|
|
30440
30429
|
className: "movable-point-hitbox",
|
|
@@ -31080,6 +31069,7 @@ const MovablePoint$1 = props => {
|
|
|
31080
31069
|
point,
|
|
31081
31070
|
onMove = () => {},
|
|
31082
31071
|
onFocusChange = () => {},
|
|
31072
|
+
onClick = () => {},
|
|
31083
31073
|
cursor,
|
|
31084
31074
|
color: color$1 = color.blue,
|
|
31085
31075
|
constrain = p => snap(snapStep, p)
|
|
@@ -31104,7 +31094,8 @@ const MovablePoint$1 = props => {
|
|
|
31104
31094
|
},
|
|
31105
31095
|
onClick: () => {
|
|
31106
31096
|
var _elementRef$current;
|
|
31107
|
-
|
|
31097
|
+
onClick && onClick();
|
|
31098
|
+
(_elementRef$current = elementRef.current) == null || _elementRef$current.focus();
|
|
31108
31099
|
},
|
|
31109
31100
|
cursor: cursor
|
|
31110
31101
|
});
|
|
@@ -31306,6 +31297,11 @@ function getGradableGraph(state, initialGraph) {
|
|
|
31306
31297
|
allowReflexAngles: state.allowReflexAngles
|
|
31307
31298
|
});
|
|
31308
31299
|
}
|
|
31300
|
+
if (state.type === "none" && initialGraph.type === "none") {
|
|
31301
|
+
return {
|
|
31302
|
+
type: "none"
|
|
31303
|
+
};
|
|
31304
|
+
}
|
|
31309
31305
|
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
31310
31306
|
}
|
|
31311
31307
|
|
|
@@ -31703,12 +31699,19 @@ function UnlimitedPointGraph(props) {
|
|
|
31703
31699
|
key: i,
|
|
31704
31700
|
point: point,
|
|
31705
31701
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
31706
|
-
onFocusChange: isFocused => {
|
|
31702
|
+
onFocusChange: (event, isFocused) => {
|
|
31707
31703
|
if (isFocused) {
|
|
31708
31704
|
dispatch(actions.pointGraph.focusPoint(i));
|
|
31709
31705
|
} else {
|
|
31706
|
+
var _event$relatedTarget;
|
|
31707
|
+
if (((_event$relatedTarget = event.relatedTarget) == null ? void 0 : _event$relatedTarget.id) === REMOVE_BUTTON_ID) {
|
|
31708
|
+
return;
|
|
31709
|
+
}
|
|
31710
31710
|
dispatch(actions.pointGraph.blurPoint());
|
|
31711
31711
|
}
|
|
31712
|
+
},
|
|
31713
|
+
onClick: () => {
|
|
31714
|
+
dispatch(actions.pointGraph.clickPoint(i));
|
|
31712
31715
|
}
|
|
31713
31716
|
})));
|
|
31714
31717
|
}
|
|
@@ -31867,7 +31870,9 @@ function useDraggablePx(args) {
|
|
|
31867
31870
|
* - Protractor
|
|
31868
31871
|
* - Interactive Graph Elements
|
|
31869
31872
|
*/
|
|
31873
|
+
const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
|
|
31870
31874
|
const MafsGraph = props => {
|
|
31875
|
+
var _props$flags;
|
|
31871
31876
|
const {
|
|
31872
31877
|
state,
|
|
31873
31878
|
dispatch,
|
|
@@ -31975,7 +31980,8 @@ const MafsGraph = props => {
|
|
|
31975
31980
|
props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
31976
31981
|
lockedFigures: props.lockedFigures,
|
|
31977
31982
|
range: state.range
|
|
31978
|
-
}))), props.
|
|
31983
|
+
}))), ((_props$flags = props.flags) == null || (_props$flags = _props$flags["mafs"]) == null ? void 0 : _props$flags["interactive-graph-locked-features-labels"]) && props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
|
|
31984
|
+
flags: props.flags,
|
|
31979
31985
|
lockedFigures: props.lockedFigures
|
|
31980
31986
|
}), /*#__PURE__*/React.createElement(View, {
|
|
31981
31987
|
style: {
|
|
@@ -31992,10 +31998,10 @@ const MafsGraph = props => {
|
|
|
31992
31998
|
zoom: false,
|
|
31993
31999
|
width: width,
|
|
31994
32000
|
height: height
|
|
31995
|
-
}, props.showProtractor && /*#__PURE__*/React.createElement(Protractor, null), renderGraph({
|
|
32001
|
+
}, /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.showProtractor && /*#__PURE__*/React.createElement(Protractor, null), renderGraph({
|
|
31996
32002
|
state,
|
|
31997
32003
|
dispatch
|
|
31998
|
-
}))))), renderGraphControls({
|
|
32004
|
+
})))))), renderGraphControls({
|
|
31999
32005
|
state,
|
|
32000
32006
|
dispatch,
|
|
32001
32007
|
width
|
|
@@ -32006,26 +32012,17 @@ const renderPointGraphControls = props => /*#__PURE__*/React.createElement(View,
|
|
|
32006
32012
|
flexDirection: "row",
|
|
32007
32013
|
width: props.width
|
|
32008
32014
|
}
|
|
32009
|
-
}, /*#__PURE__*/React.createElement(Button, {
|
|
32010
|
-
|
|
32011
|
-
style: {
|
|
32012
|
-
width: "100%",
|
|
32013
|
-
marginLeft: "20px"
|
|
32014
|
-
},
|
|
32015
|
-
tabIndex: 0,
|
|
32016
|
-
onClick: () => {
|
|
32017
|
-
props.dispatch(actions.pointGraph.addPoint([0, 0]));
|
|
32018
|
-
}
|
|
32019
|
-
}, "Add Point"), (props.state.focusedPointIndex !== null || props.state.previouslyFocusedPointIndex !== null) && /*#__PURE__*/React.createElement(Button, {
|
|
32015
|
+
}, props.state.showRemovePointButton && props.state.focusedPointIndex !== null && /*#__PURE__*/React.createElement(Button, {
|
|
32016
|
+
id: REMOVE_BUTTON_ID,
|
|
32020
32017
|
kind: "secondary",
|
|
32021
32018
|
color: "destructive",
|
|
32022
|
-
tabIndex:
|
|
32019
|
+
tabIndex: -1,
|
|
32023
32020
|
style: {
|
|
32024
32021
|
width: "100%",
|
|
32025
32022
|
marginLeft: "20px"
|
|
32026
32023
|
},
|
|
32027
32024
|
onClick: event => {
|
|
32028
|
-
props.dispatch(actions.pointGraph.removePoint(props.state.
|
|
32025
|
+
props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));
|
|
32029
32026
|
}
|
|
32030
32027
|
}, "Remove Point"));
|
|
32031
32028
|
const renderGraphControls = props => {
|
|
@@ -32160,6 +32157,8 @@ const renderGraph = props => {
|
|
|
32160
32157
|
graphState: state,
|
|
32161
32158
|
dispatch: dispatch
|
|
32162
32159
|
});
|
|
32160
|
+
case "none":
|
|
32161
|
+
return null;
|
|
32163
32162
|
default:
|
|
32164
32163
|
throw new UnreachableCaseError(type);
|
|
32165
32164
|
}
|
|
@@ -32213,7 +32212,7 @@ function initializeGraphState(params) {
|
|
|
32213
32212
|
coords: getPointCoords(graph, range, step),
|
|
32214
32213
|
numPoints: graph.numPoints || 0,
|
|
32215
32214
|
focusedPointIndex: null,
|
|
32216
|
-
|
|
32215
|
+
showRemovePointButton: false
|
|
32217
32216
|
});
|
|
32218
32217
|
case "circle":
|
|
32219
32218
|
return _extends({}, shared, {
|
|
@@ -32242,6 +32241,10 @@ function initializeGraphState(params) {
|
|
|
32242
32241
|
allowReflexAngles: Boolean(graph.allowReflexAngles),
|
|
32243
32242
|
snapDegrees: Number(graph.snapDegrees)
|
|
32244
32243
|
});
|
|
32244
|
+
case "none":
|
|
32245
|
+
return _extends({}, shared, {
|
|
32246
|
+
type: "none"
|
|
32247
|
+
});
|
|
32245
32248
|
default:
|
|
32246
32249
|
throw new UnreachableCaseError(graph);
|
|
32247
32250
|
}
|
|
@@ -32477,6 +32480,8 @@ function interactiveGraphReducer(state, action) {
|
|
|
32477
32480
|
return doBlurPoint(state);
|
|
32478
32481
|
case DELETE_INTENT:
|
|
32479
32482
|
return doDeleteIntent(state);
|
|
32483
|
+
case CLICK_POINT:
|
|
32484
|
+
return doClickPoint(state, action);
|
|
32480
32485
|
default:
|
|
32481
32486
|
throw new UnreachableCaseError(action);
|
|
32482
32487
|
}
|
|
@@ -32506,13 +32511,25 @@ function doBlurPoint(state, action) {
|
|
|
32506
32511
|
switch (state.type) {
|
|
32507
32512
|
case "point":
|
|
32508
32513
|
return _extends({}, state, {
|
|
32509
|
-
|
|
32510
|
-
|
|
32514
|
+
focusedPointIndex: null,
|
|
32515
|
+
showRemovePointButton: false
|
|
32511
32516
|
});
|
|
32512
32517
|
default:
|
|
32513
32518
|
return state;
|
|
32514
32519
|
}
|
|
32515
32520
|
}
|
|
32521
|
+
function doClickPoint(state, action) {
|
|
32522
|
+
if (state.type !== "point") {
|
|
32523
|
+
return state;
|
|
32524
|
+
}
|
|
32525
|
+
if (state.numPoints === "unlimited") {
|
|
32526
|
+
return _extends({}, state, {
|
|
32527
|
+
focusedPointIndex: action.index,
|
|
32528
|
+
showRemovePointButton: true
|
|
32529
|
+
});
|
|
32530
|
+
}
|
|
32531
|
+
return state;
|
|
32532
|
+
}
|
|
32516
32533
|
function doMovePointInFigure(state, action) {
|
|
32517
32534
|
switch (state.type) {
|
|
32518
32535
|
case "segment":
|
|
@@ -32551,6 +32568,8 @@ function doMovePointInFigure(state, action) {
|
|
|
32551
32568
|
}
|
|
32552
32569
|
case "angle":
|
|
32553
32570
|
case "circle":
|
|
32571
|
+
throw new Error("FIXME implement circle reducer");
|
|
32572
|
+
case "none":
|
|
32554
32573
|
case "point":
|
|
32555
32574
|
case "polygon":
|
|
32556
32575
|
case "quadratic":
|
|
@@ -32852,7 +32871,8 @@ function doAddPoint(state, action) {
|
|
|
32852
32871
|
// If there's no point in spot where we want the new point to go we add it there
|
|
32853
32872
|
return _extends({}, state, {
|
|
32854
32873
|
hasBeenInteractedWith: true,
|
|
32855
|
-
coords: [...state.coords, snappedPoint]
|
|
32874
|
+
coords: [...state.coords, snappedPoint],
|
|
32875
|
+
showRemovePointButton: false
|
|
32856
32876
|
});
|
|
32857
32877
|
}
|
|
32858
32878
|
function doRemovePoint(state, action) {
|
|
@@ -32862,7 +32882,7 @@ function doRemovePoint(state, action) {
|
|
|
32862
32882
|
return _extends({}, state, {
|
|
32863
32883
|
coords: state.coords.filter((_, i) => i !== action.index),
|
|
32864
32884
|
focusedPointIndex: null,
|
|
32865
|
-
|
|
32885
|
+
showRemovePointButton: false
|
|
32866
32886
|
});
|
|
32867
32887
|
}
|
|
32868
32888
|
const getDeltaVertex = (maxMoves, minMoves, delta) => {
|
|
@@ -33246,6 +33266,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
33246
33266
|
const snapTo = graph.type === "polygon" ? graph.snapTo : null;
|
|
33247
33267
|
const showAngles = graph.type === "polygon" ? graph.showAngles : null;
|
|
33248
33268
|
const showSides = graph.type === "polygon" ? graph.showSides : null;
|
|
33269
|
+
const startCoords = "startCoords" in graph ? graph.startCoords : null;
|
|
33249
33270
|
const originalPropsRef = useRef(props);
|
|
33250
33271
|
const latestPropsRef = useLatestRef(props);
|
|
33251
33272
|
useEffect(() => {
|
|
@@ -33256,7 +33277,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
33256
33277
|
if (latestPropsRef.current !== originalPropsRef.current) {
|
|
33257
33278
|
dispatch(reinitialize(latestPropsRef.current));
|
|
33258
33279
|
}
|
|
33259
|
-
}, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef,
|
|
33280
|
+
}, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, startCoords]);
|
|
33260
33281
|
|
|
33261
33282
|
// If the graph is static, it always displays the correct answer. This is
|
|
33262
33283
|
// standard behavior for Perseus widgets (e.g. compare the Radio widget).
|
|
@@ -33533,6 +33554,8 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
33533
33554
|
_.invoke(this.points, "remove");
|
|
33534
33555
|
(_this$line = this.line) == null || _this$line.remove();
|
|
33535
33556
|
};
|
|
33557
|
+
this.addNoneControls = () => {};
|
|
33558
|
+
this.removeNoneControls = () => {};
|
|
33536
33559
|
this.addLinearControls = () => {
|
|
33537
33560
|
this.addLine("line");
|
|
33538
33561
|
};
|
|
@@ -34567,7 +34590,7 @@ class InteractiveGraph extends React.Component {
|
|
|
34567
34590
|
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
34568
34591
|
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
34569
34592
|
return /*#__PURE__*/React.createElement(StatefulMafsGraph, _extends({}, this.props, {
|
|
34570
|
-
|
|
34593
|
+
flags: (_this$props$apiOption2 = this.props.apiOptions) == null ? void 0 : _this$props$apiOption2.flags,
|
|
34571
34594
|
ref: this.mafsRef,
|
|
34572
34595
|
gridStep: gridStep,
|
|
34573
34596
|
snapStep: snapStep,
|
|
@@ -34800,6 +34823,9 @@ class InteractiveGraph extends React.Component {
|
|
|
34800
34823
|
});
|
|
34801
34824
|
});
|
|
34802
34825
|
}
|
|
34826
|
+
static getNoneEquationString() {
|
|
34827
|
+
return "";
|
|
34828
|
+
}
|
|
34803
34829
|
static getLinearEquationString(props) {
|
|
34804
34830
|
const coords = InteractiveGraph.getLineCoords(props.graph, props);
|
|
34805
34831
|
if (eq(coords[0][0], coords[1][0])) {
|
|
@@ -34913,6 +34939,16 @@ class InteractiveGraph extends React.Component {
|
|
|
34913
34939
|
return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
|
|
34914
34940
|
}
|
|
34915
34941
|
static validate(userInput, rubric, component) {
|
|
34942
|
+
// None-type graphs are not graded
|
|
34943
|
+
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
34944
|
+
return {
|
|
34945
|
+
type: "points",
|
|
34946
|
+
earned: 0,
|
|
34947
|
+
total: 0,
|
|
34948
|
+
message: null
|
|
34949
|
+
};
|
|
34950
|
+
}
|
|
34951
|
+
|
|
34916
34952
|
// When nothing has moved, there will neither be coords nor the
|
|
34917
34953
|
// circle's center/radius fields. When those fields are absent, skip
|
|
34918
34954
|
// all these checks; just go mark the answer as empty.
|
|
@@ -35142,6 +35178,8 @@ function shouldUseMafs(mafsFlags, graph) {
|
|
|
35142
35178
|
return false;
|
|
35143
35179
|
}
|
|
35144
35180
|
switch (graph.type) {
|
|
35181
|
+
case "none":
|
|
35182
|
+
return true;
|
|
35145
35183
|
case "point":
|
|
35146
35184
|
if (graph.numPoints === UNLIMITED) {
|
|
35147
35185
|
return Boolean(mafsFlags["unlimited-point"]);
|
|
@@ -35163,7 +35201,7 @@ function shouldUseMafs(mafsFlags, graph) {
|
|
|
35163
35201
|
const staticTransform$3 = _.identity;
|
|
35164
35202
|
var InteractiveGraph$1 = {
|
|
35165
35203
|
name: "interactive-graph",
|
|
35166
|
-
displayName: "Interactive graph",
|
|
35204
|
+
displayName: "Interactive graph (Assessments only)",
|
|
35167
35205
|
widget: InteractiveGraph,
|
|
35168
35206
|
staticTransform: staticTransform$3
|
|
35169
35207
|
};
|
|
@@ -37099,11 +37137,6 @@ var LabelImage$1 = {
|
|
|
37099
37137
|
isLintable: true
|
|
37100
37138
|
};
|
|
37101
37139
|
|
|
37102
|
-
let Layout = /*#__PURE__*/function (Layout) {
|
|
37103
|
-
Layout["HORIZONTAL"] = "horizontal";
|
|
37104
|
-
Layout["VERTICAL"] = "vertical";
|
|
37105
|
-
return Layout;
|
|
37106
|
-
}({});
|
|
37107
37140
|
var ItemState = /*#__PURE__*/function (ItemState) {
|
|
37108
37141
|
ItemState["STATIC"] = "static";
|
|
37109
37142
|
ItemState["DRAGGING"] = "dragging";
|
|
@@ -37130,7 +37163,7 @@ class Placeholder extends React.Component {
|
|
|
37130
37163
|
const {
|
|
37131
37164
|
layout
|
|
37132
37165
|
} = this.props;
|
|
37133
|
-
const className = css(styles$d.card, styles$d.placeholder, layout ===
|
|
37166
|
+
const className = css(styles$d.card, styles$d.placeholder, layout === "horizontal" && styles$d.horizontalCard);
|
|
37134
37167
|
const style = {
|
|
37135
37168
|
width: this.props.width,
|
|
37136
37169
|
height: this.props.height
|
|
@@ -37334,7 +37367,7 @@ class Draggable extends React.Component {
|
|
|
37334
37367
|
// We need to keep backwards compatbility with rules specified directly
|
|
37335
37368
|
// in CSS. Hence the hacky tacking on of manual classNames.
|
|
37336
37369
|
// See sortable.less for details.
|
|
37337
|
-
let className = css(styles$d.card, styles$d.draggable, layout ===
|
|
37370
|
+
let className = 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";
|
|
37338
37371
|
if (!includePadding) {
|
|
37339
37372
|
className += " perseus-sortable-draggable-unpadded";
|
|
37340
37373
|
}
|
|
@@ -37463,7 +37496,7 @@ class Sortable extends React.Component {
|
|
|
37463
37496
|
if (constraints != null && constraints.width) {
|
|
37464
37497
|
// Items must be at least as wide as the specified constraint
|
|
37465
37498
|
syncWidth = _.max(widths.concat(constraints.width));
|
|
37466
|
-
} else if (layout ===
|
|
37499
|
+
} else if (layout === "vertical") {
|
|
37467
37500
|
// Sync widths to get a clean column
|
|
37468
37501
|
syncWidth = _.max(widths);
|
|
37469
37502
|
}
|
|
@@ -37471,7 +37504,7 @@ class Sortable extends React.Component {
|
|
|
37471
37504
|
if (constraints != null && constraints.height) {
|
|
37472
37505
|
// Items must be at least as high as the specified constraint
|
|
37473
37506
|
syncHeight = _.max(heights.concat(constraints.height));
|
|
37474
|
-
} else if (layout ===
|
|
37507
|
+
} else if (layout === "horizontal") {
|
|
37475
37508
|
// Sync widths to get a clean row
|
|
37476
37509
|
syncHeight = _.max(heights);
|
|
37477
37510
|
}
|
|
@@ -37553,7 +37586,7 @@ class Sortable extends React.Component {
|
|
|
37553
37586
|
|
|
37554
37587
|
// @ts-expect-error - TS2551 - Property 'splice' does not exist on type 'readonly SortableItem[]'. Did you mean 'slice'?
|
|
37555
37588
|
items.splice(currentIndex, 1);
|
|
37556
|
-
if (this.props.layout ===
|
|
37589
|
+
if (this.props.layout === "horizontal") {
|
|
37557
37590
|
// @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'. | TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
|
|
37558
37591
|
const midWidth = $draggable.offset().left - $sortable.offset().left;
|
|
37559
37592
|
let sumWidth = 0;
|
|
@@ -37668,19 +37701,19 @@ class Sortable extends React.Component {
|
|
|
37668
37701
|
// We need to keep backwards compatbility with rules specified directly
|
|
37669
37702
|
// in CSS. See sortable.less for details.
|
|
37670
37703
|
const className = css(styles$d.sortable) + " perseus-sortable";
|
|
37671
|
-
const syncWidth = ((_this$props$constrain = this.props.constraints) == null ? void 0 : _this$props$constrain.width) || layout ===
|
|
37672
|
-
const syncHeight = ((_this$props$constrain2 = this.props.constraints) == null ? void 0 : _this$props$constrain2.height) || layout ===
|
|
37704
|
+
const syncWidth = ((_this$props$constrain = this.props.constraints) == null ? void 0 : _this$props$constrain.width) || layout === "vertical";
|
|
37705
|
+
const syncHeight = ((_this$props$constrain2 = this.props.constraints) == null ? void 0 : _this$props$constrain2.height) || layout === "horizontal";
|
|
37673
37706
|
_.each(this.state.items, function (item, i, items) {
|
|
37674
37707
|
const isLast = i === items.length - 1;
|
|
37675
37708
|
const isStatic = item.state === ItemState.STATIC || item.state === ItemState.DISABLED;
|
|
37676
37709
|
let margin;
|
|
37677
37710
|
|
|
37678
37711
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
37679
|
-
if (this.props.layout ===
|
|
37712
|
+
if (this.props.layout === "horizontal") {
|
|
37680
37713
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
37681
37714
|
margin = "0 " + this.props.margin + "px 0 0"; // right
|
|
37682
37715
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
37683
|
-
} else if (this.props.layout ===
|
|
37716
|
+
} else if (this.props.layout === "vertical") {
|
|
37684
37717
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
37685
37718
|
margin = "0 0 " + this.props.margin + "px 0"; // bottom
|
|
37686
37719
|
}
|
|
@@ -37754,7 +37787,7 @@ class Sortable extends React.Component {
|
|
|
37754
37787
|
}
|
|
37755
37788
|
}
|
|
37756
37789
|
Sortable.defaultProps = {
|
|
37757
|
-
layout:
|
|
37790
|
+
layout: "horizontal",
|
|
37758
37791
|
padding: true,
|
|
37759
37792
|
disabled: false,
|
|
37760
37793
|
constraints: {},
|
|
@@ -37813,7 +37846,6 @@ const styles$d = StyleSheet.create({
|
|
|
37813
37846
|
}
|
|
37814
37847
|
});
|
|
37815
37848
|
|
|
37816
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
37817
37849
|
const {
|
|
37818
37850
|
shuffle: shuffle$1,
|
|
37819
37851
|
seededRNG
|
|
@@ -37947,7 +37979,7 @@ class Matcher extends React.Component {
|
|
|
37947
37979
|
className: css(styles$c.column)
|
|
37948
37980
|
}, /*#__PURE__*/React.createElement(Sortable, {
|
|
37949
37981
|
options: left,
|
|
37950
|
-
layout:
|
|
37982
|
+
layout: "vertical",
|
|
37951
37983
|
padding: this.props.padding,
|
|
37952
37984
|
disabled: !this.props.orderMatters,
|
|
37953
37985
|
constraints: constraints,
|
|
@@ -37960,11 +37992,9 @@ class Matcher extends React.Component {
|
|
|
37960
37992
|
ref: "left"
|
|
37961
37993
|
})), /*#__PURE__*/React.createElement("td", {
|
|
37962
37994
|
className: css(styles$c.column, styles$c.columnRight)
|
|
37963
|
-
}, /*#__PURE__*/React.createElement(Sortable
|
|
37964
|
-
// @ts-expect-error - TS2322 - Type 'readonly unknown[]' is not assignable to type 'readonly string[]'.
|
|
37965
|
-
, {
|
|
37995
|
+
}, /*#__PURE__*/React.createElement(Sortable, {
|
|
37966
37996
|
options: right,
|
|
37967
|
-
layout:
|
|
37997
|
+
layout: "vertical",
|
|
37968
37998
|
padding: this.props.padding,
|
|
37969
37999
|
constraints: constraints,
|
|
37970
38000
|
onMeasure: this.onMeasureRight,
|
|
@@ -37978,18 +38008,6 @@ class Matcher extends React.Component {
|
|
|
37978
38008
|
}
|
|
37979
38009
|
}
|
|
37980
38010
|
Matcher.contextType = PerseusI18nContext;
|
|
37981
|
-
Matcher.propTypes = {
|
|
37982
|
-
apiOptions: ApiOptions.propTypes,
|
|
37983
|
-
labels: PropTypes.array,
|
|
37984
|
-
left: PropTypes.array,
|
|
37985
|
-
onChange: PropTypes.func,
|
|
37986
|
-
orderMatters: PropTypes.bool,
|
|
37987
|
-
padding: PropTypes.bool,
|
|
37988
|
-
problemNum: PropTypes.number,
|
|
37989
|
-
right: PropTypes.array,
|
|
37990
|
-
trackInteraction: PropTypes.func.isRequired,
|
|
37991
|
-
linterContext: linterContextProps
|
|
37992
|
-
};
|
|
37993
38011
|
Matcher.defaultProps = {
|
|
37994
38012
|
left: [],
|
|
37995
38013
|
right: [],
|
|
@@ -38509,6 +38527,7 @@ const staticTransform$2 = editorProps => {
|
|
|
38509
38527
|
var Matrix$1 = {
|
|
38510
38528
|
name: "matrix",
|
|
38511
38529
|
displayName: "Matrix",
|
|
38530
|
+
hidden: true,
|
|
38512
38531
|
widget: Matrix,
|
|
38513
38532
|
transform: propTransform$1,
|
|
38514
38533
|
staticTransform: staticTransform$2,
|
|
@@ -38665,6 +38684,7 @@ const propUpgrades = {
|
|
|
38665
38684
|
var Measurer$1 = {
|
|
38666
38685
|
name: "measurer",
|
|
38667
38686
|
displayName: "Measurer",
|
|
38687
|
+
hidden: true,
|
|
38668
38688
|
widget: Measurer,
|
|
38669
38689
|
version: {
|
|
38670
38690
|
major: 1,
|
|
@@ -39554,12 +39574,7 @@ class MoleculeWidget extends React.Component {
|
|
|
39554
39574
|
// TODO(colin): this is here as part of the interface for a component.
|
|
39555
39575
|
// Figure out if there is something more appropriate that this should
|
|
39556
39576
|
// return.
|
|
39557
|
-
return
|
|
39558
|
-
type: "points",
|
|
39559
|
-
earned: 0,
|
|
39560
|
-
total: 0,
|
|
39561
|
-
message: null
|
|
39562
|
-
};
|
|
39577
|
+
return noopValidator();
|
|
39563
39578
|
};
|
|
39564
39579
|
}
|
|
39565
39580
|
render() {
|
|
@@ -40166,15 +40181,11 @@ class PlaceholderCard extends React.Component {
|
|
|
40166
40181
|
}, /*#__PURE__*/React.createElement("div", {
|
|
40167
40182
|
className: "card placeholder",
|
|
40168
40183
|
style: {
|
|
40169
|
-
height: this.props.
|
|
40184
|
+
height: this.props.width
|
|
40170
40185
|
}
|
|
40171
40186
|
}));
|
|
40172
40187
|
}
|
|
40173
40188
|
}
|
|
40174
|
-
PlaceholderCard.propTypes = {
|
|
40175
|
-
width: PropTypes.number.isRequired,
|
|
40176
|
-
height: PropTypes.number.isRequired
|
|
40177
|
-
};
|
|
40178
40189
|
class DragHintCard extends React.Component {
|
|
40179
40190
|
render() {
|
|
40180
40191
|
return /*#__PURE__*/React.createElement("div", {
|
|
@@ -40184,10 +40195,6 @@ class DragHintCard extends React.Component {
|
|
|
40184
40195
|
}));
|
|
40185
40196
|
}
|
|
40186
40197
|
}
|
|
40187
|
-
const PropTypePosition = PropTypes.shape({
|
|
40188
|
-
left: PropTypes.number,
|
|
40189
|
-
top: PropTypes.number
|
|
40190
|
-
});
|
|
40191
40198
|
class Card extends React.Component {
|
|
40192
40199
|
constructor(...args) {
|
|
40193
40200
|
super(...args);
|
|
@@ -40268,7 +40275,7 @@ class Card extends React.Component {
|
|
|
40268
40275
|
this.props.fakeRef !== nextProps.fakeRef;
|
|
40269
40276
|
}
|
|
40270
40277
|
componentDidUpdate(prevProps, prevState) {
|
|
40271
|
-
if (this.props.animating && !prevProps.animating) {
|
|
40278
|
+
if (this.props.animating && !prevProps.animating && this.props.animateTo && this.props.startOffset) {
|
|
40272
40279
|
// If we just were changed into animating, start the animation.
|
|
40273
40280
|
// We pick the animation speed based on the distance that the card
|
|
40274
40281
|
// needs to travel. (Why sqrt? Just because it looks nice -- with a
|
|
@@ -40291,10 +40298,11 @@ class Card extends React.Component {
|
|
|
40291
40298
|
render() {
|
|
40292
40299
|
let style = {};
|
|
40293
40300
|
if (this.props.floating) {
|
|
40301
|
+
var _this$props$startOffs, _this$props$startOffs2;
|
|
40294
40302
|
style = {
|
|
40295
40303
|
position: "absolute",
|
|
40296
|
-
left: this.props.startOffset.left,
|
|
40297
|
-
top: this.props.startOffset.top
|
|
40304
|
+
left: (_this$props$startOffs = this.props.startOffset) == null ? void 0 : _this$props$startOffs.left,
|
|
40305
|
+
top: (_this$props$startOffs2 = this.props.startOffset) == null ? void 0 : _this$props$startOffs2.top
|
|
40298
40306
|
};
|
|
40299
40307
|
}
|
|
40300
40308
|
if (this.props.width) {
|
|
@@ -40304,7 +40312,7 @@ class Card extends React.Component {
|
|
|
40304
40312
|
if (this.props.stack) {
|
|
40305
40313
|
className.push("stack");
|
|
40306
40314
|
}
|
|
40307
|
-
if (this.props.floating && !this.props.animating) {
|
|
40315
|
+
if (this.props.floating && !this.props.animating && this.props.mouse && this.props.startMouse) {
|
|
40308
40316
|
className.push("dragging");
|
|
40309
40317
|
style.left += this.props.mouse.left - this.props.startMouse.left;
|
|
40310
40318
|
style.top += this.props.mouse.top - this.props.startMouse.top;
|
|
@@ -40329,28 +40337,13 @@ class Card extends React.Component {
|
|
|
40329
40337
|
}
|
|
40330
40338
|
}
|
|
40331
40339
|
Card.contextType = PerseusI18nContext;
|
|
40332
|
-
Card.propTypes = {
|
|
40333
|
-
floating: PropTypes.bool.isRequired,
|
|
40334
|
-
animating: PropTypes.bool,
|
|
40335
|
-
width: PropTypes.number,
|
|
40336
|
-
stack: PropTypes.bool,
|
|
40337
|
-
onMouseDown: PropTypes.func,
|
|
40338
|
-
onMouseMove: PropTypes.func,
|
|
40339
|
-
onMouseUp: PropTypes.func,
|
|
40340
|
-
// Used only for floating/animating cards
|
|
40341
|
-
startMouse: PropTypePosition,
|
|
40342
|
-
startOffset: PropTypePosition,
|
|
40343
|
-
animateTo: PropTypePosition,
|
|
40344
|
-
onAnimationEnd: PropTypes.func,
|
|
40345
|
-
linterContext: linterContextProps
|
|
40346
|
-
};
|
|
40347
40340
|
Card.defaultProps = {
|
|
40348
40341
|
stack: false,
|
|
40349
40342
|
animating: false,
|
|
40350
40343
|
linterContext: linterContextDefault
|
|
40351
40344
|
};
|
|
40352
40345
|
const NORMAL = "normal",
|
|
40353
|
-
HORIZONTAL
|
|
40346
|
+
HORIZONTAL = "horizontal";
|
|
40354
40347
|
class Orderer extends React.Component {
|
|
40355
40348
|
constructor(...args) {
|
|
40356
40349
|
super(...args);
|
|
@@ -40497,7 +40490,7 @@ class Orderer extends React.Component {
|
|
|
40497
40490
|
};
|
|
40498
40491
|
this.findCorrectIndex = (draggable, list) => {
|
|
40499
40492
|
// Find the correct index for a card given the current cards.
|
|
40500
|
-
const isHorizontal = this.props.layout === HORIZONTAL
|
|
40493
|
+
const isHorizontal = this.props.layout === HORIZONTAL;
|
|
40501
40494
|
// eslint-disable-next-line react/no-string-refs
|
|
40502
40495
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
40503
40496
|
const $dragList = $(ReactDOM__default.findDOMNode(this.refs.dragList));
|
|
@@ -40547,7 +40540,7 @@ class Orderer extends React.Component {
|
|
|
40547
40540
|
if (draggable == null) {
|
|
40548
40541
|
return false;
|
|
40549
40542
|
}
|
|
40550
|
-
const isHorizontal = this.props.layout === HORIZONTAL
|
|
40543
|
+
const isHorizontal = this.props.layout === HORIZONTAL,
|
|
40551
40544
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
40552
40545
|
$draggable = $(ReactDOM__default.findDOMNode(draggable)),
|
|
40553
40546
|
// eslint-disable-next-line react/no-string-refs
|
|
@@ -40719,7 +40712,7 @@ Orderer.defaultProps = {
|
|
|
40719
40712
|
options: [],
|
|
40720
40713
|
correctOptions: [],
|
|
40721
40714
|
height: NORMAL,
|
|
40722
|
-
layout: HORIZONTAL
|
|
40715
|
+
layout: HORIZONTAL,
|
|
40723
40716
|
linterContext: linterContextDefault
|
|
40724
40717
|
};
|
|
40725
40718
|
_.extend(Orderer, {
|
|
@@ -40742,6 +40735,7 @@ _.extend(Orderer, {
|
|
|
40742
40735
|
var Orderer$1 = {
|
|
40743
40736
|
name: "orderer",
|
|
40744
40737
|
displayName: "Orderer",
|
|
40738
|
+
hidden: true,
|
|
40745
40739
|
widget: Orderer,
|
|
40746
40740
|
isLintable: true
|
|
40747
40741
|
};
|
|
@@ -42546,12 +42540,7 @@ class Passage extends React.Component {
|
|
|
42546
42540
|
};
|
|
42547
42541
|
}
|
|
42548
42542
|
static validate(state, rubric) {
|
|
42549
|
-
return
|
|
42550
|
-
type: "points",
|
|
42551
|
-
earned: 0,
|
|
42552
|
-
total: 0,
|
|
42553
|
-
message: null
|
|
42554
|
-
};
|
|
42543
|
+
return noopValidator();
|
|
42555
42544
|
}
|
|
42556
42545
|
componentDidMount() {
|
|
42557
42546
|
this._updateState();
|
|
@@ -42917,6 +42906,7 @@ Passage.defaultProps = {
|
|
|
42917
42906
|
var Passage$1 = {
|
|
42918
42907
|
name: "passage",
|
|
42919
42908
|
displayName: "Passage (SAT only)",
|
|
42909
|
+
hidden: true,
|
|
42920
42910
|
widget: Passage,
|
|
42921
42911
|
transform: editorProps => {
|
|
42922
42912
|
return _.pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
@@ -42928,23 +42918,21 @@ var Passage$1 = {
|
|
|
42928
42918
|
class PassageRefTarget extends React.Component {
|
|
42929
42919
|
constructor(..._args) {
|
|
42930
42920
|
super(..._args);
|
|
42921
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
42931
42922
|
this.getUserInput = () => {
|
|
42932
|
-
return
|
|
42923
|
+
return;
|
|
42933
42924
|
};
|
|
42925
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
42934
42926
|
this.change = (...args) => {
|
|
42935
42927
|
return change.apply(this, args);
|
|
42936
42928
|
};
|
|
42929
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
42937
42930
|
this.simpleValidate = rubric => {
|
|
42938
42931
|
return PassageRefTarget.validate(this.getUserInput(), rubric);
|
|
42939
42932
|
};
|
|
42940
42933
|
}
|
|
42941
42934
|
static validate(state, rubric) {
|
|
42942
|
-
return
|
|
42943
|
-
type: "points",
|
|
42944
|
-
earned: 0,
|
|
42945
|
-
total: 0,
|
|
42946
|
-
message: null
|
|
42947
|
-
};
|
|
42935
|
+
return noopValidator();
|
|
42948
42936
|
}
|
|
42949
42937
|
render() {
|
|
42950
42938
|
return /*#__PURE__*/React.createElement(Renderer, {
|
|
@@ -44006,6 +43994,7 @@ const staticTransform = _.identity;
|
|
|
44006
43994
|
var Plotter$1 = {
|
|
44007
43995
|
name: "plotter",
|
|
44008
43996
|
displayName: "Plotter",
|
|
43997
|
+
hidden: true,
|
|
44009
43998
|
widget: Plotter,
|
|
44010
43999
|
staticTransform: staticTransform
|
|
44011
44000
|
};
|
|
@@ -44056,17 +44045,13 @@ var PythonProgram$1 = {
|
|
|
44056
44045
|
widget: PythonProgram
|
|
44057
44046
|
};
|
|
44058
44047
|
|
|
44059
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
44060
44048
|
const {
|
|
44061
44049
|
shuffle
|
|
44062
44050
|
} = Util;
|
|
44063
|
-
const HORIZONTAL = "horizontal";
|
|
44064
|
-
const VERTICAL = "vertical";
|
|
44065
44051
|
class Sorter extends React.Component {
|
|
44066
44052
|
constructor(...args) {
|
|
44067
44053
|
super(...args);
|
|
44068
|
-
|
|
44069
|
-
this._isMounted = void 0;
|
|
44054
|
+
this._isMounted = false;
|
|
44070
44055
|
this.state = {
|
|
44071
44056
|
changed: false
|
|
44072
44057
|
};
|
|
@@ -44140,9 +44125,7 @@ class Sorter extends React.Component {
|
|
|
44140
44125
|
const marginPx = apiOptions.isMobile ? 8 : 5;
|
|
44141
44126
|
return /*#__PURE__*/React.createElement("div", {
|
|
44142
44127
|
className: "perseus-widget-sorter perseus-clearfix"
|
|
44143
|
-
}, /*#__PURE__*/React.createElement(Sortable
|
|
44144
|
-
// @ts-expect-error - TS2322 - Type 'readonly unknown[]' is not assignable to type 'readonly string[]'.
|
|
44145
|
-
, {
|
|
44128
|
+
}, /*#__PURE__*/React.createElement(Sortable, {
|
|
44146
44129
|
options: options,
|
|
44147
44130
|
layout: this.props.layout,
|
|
44148
44131
|
margin: marginPx,
|
|
@@ -44155,20 +44138,9 @@ class Sorter extends React.Component {
|
|
|
44155
44138
|
}));
|
|
44156
44139
|
}
|
|
44157
44140
|
}
|
|
44158
|
-
Sorter.propTypes = {
|
|
44159
|
-
apiOptions: ApiOptions.propTypes,
|
|
44160
|
-
// eslint-disable-next-line react/forbid-prop-types
|
|
44161
|
-
correct: PropTypes.array,
|
|
44162
|
-
layout: PropTypes.oneOf([HORIZONTAL, VERTICAL]),
|
|
44163
|
-
onChange: PropTypes.func,
|
|
44164
|
-
padding: PropTypes.bool,
|
|
44165
|
-
problemNum: PropTypes.number,
|
|
44166
|
-
trackInteraction: PropTypes.func.isRequired,
|
|
44167
|
-
linterContext: linterContextProps
|
|
44168
|
-
};
|
|
44169
44141
|
Sorter.defaultProps = {
|
|
44170
44142
|
correct: [],
|
|
44171
|
-
layout:
|
|
44143
|
+
layout: "horizontal",
|
|
44172
44144
|
padding: true,
|
|
44173
44145
|
problemNum: 0,
|
|
44174
44146
|
onChange: function () {},
|
|
@@ -44184,7 +44156,6 @@ var Sorter$1 = {
|
|
|
44184
44156
|
const {
|
|
44185
44157
|
assert
|
|
44186
44158
|
} = InteractiveUtil;
|
|
44187
|
-
|
|
44188
44159
|
/* Input handling: Maps a (row, column) pair to a unique ref used by React,
|
|
44189
44160
|
* and extracts (row, column) pairs from input paths, used to allow outsiders
|
|
44190
44161
|
* to focus, blur, set input values, etc. */
|
|
@@ -44371,16 +44342,6 @@ class Table extends React.Component {
|
|
|
44371
44342
|
}
|
|
44372
44343
|
}
|
|
44373
44344
|
Table.contextType = PerseusI18nContext;
|
|
44374
|
-
Table.propTypes = {
|
|
44375
|
-
answers: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
|
|
44376
|
-
editableHeaders: PropTypes.bool,
|
|
44377
|
-
// The editor to use when editableHeaders is enabled
|
|
44378
|
-
Editor: PropTypes.func,
|
|
44379
|
-
headers: PropTypes.arrayOf(PropTypes.string),
|
|
44380
|
-
keypadElement: keypadElementPropType,
|
|
44381
|
-
trackInteraction: PropTypes.func.isRequired,
|
|
44382
|
-
linterContext: linterContextProps
|
|
44383
|
-
};
|
|
44384
44345
|
Table.defaultProps = function () {
|
|
44385
44346
|
const defaultRows = 4;
|
|
44386
44347
|
const defaultColumns = 1;
|
|
@@ -44582,12 +44543,7 @@ class Video extends React.Component {
|
|
|
44582
44543
|
* of Khan Academy videos.
|
|
44583
44544
|
*/
|
|
44584
44545
|
static validate(userInput, rubric) {
|
|
44585
|
-
return
|
|
44586
|
-
type: "points",
|
|
44587
|
-
earned: 0,
|
|
44588
|
-
total: 0,
|
|
44589
|
-
message: null
|
|
44590
|
-
};
|
|
44546
|
+
return noopValidator();
|
|
44591
44547
|
}
|
|
44592
44548
|
render() {
|
|
44593
44549
|
const {
|
|
@@ -44692,7 +44648,7 @@ const init = function init(options) {
|
|
|
44692
44648
|
|
|
44693
44649
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44694
44650
|
const libName = "@khanacademy/perseus";
|
|
44695
|
-
const libVersion = "
|
|
44651
|
+
const libVersion = "34.0.0";
|
|
44696
44652
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44697
44653
|
|
|
44698
44654
|
const apiVersion = {
|
|
@@ -48155,5 +48111,5 @@ const EditorJsonify = {
|
|
|
48155
48111
|
}
|
|
48156
48112
|
};
|
|
48157
48113
|
|
|
48158
|
-
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST,
|
|
48114
|
+
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
48159
48115
|
//# sourceMappingURL=index.js.map
|