@khanacademy/perseus 33.3.0 → 34.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/sortable.d.ts +1 -4
- package/dist/es/index.js +721 -691
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +736 -692
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +2 -0
- package/dist/perseus-types.d.ts +3 -1
- package/dist/server-item-renderer.d.ts +2 -0
- package/dist/types.d.ts +1 -1
- package/dist/util/answer-types.d.ts +1 -1
- package/dist/widgets/__shared__/noop-validator.d.ts +11 -0
- package/dist/widgets/categorizer/categorizer-validator.d.ts +5 -0
- package/dist/widgets/categorizer/categorizer.d.ts +1 -2
- package/dist/widgets/categorizer/categorizer.types.d.ts +5 -0
- package/dist/widgets/cs-program/cs-program.d.ts +23 -17
- package/dist/widgets/definition/definition.d.ts +3 -3
- package/dist/widgets/dropdown/dropdown-validator.d.ts +4 -0
- package/dist/widgets/dropdown/dropdown.d.ts +1 -5
- package/dist/widgets/dropdown/dropdown.types.d.ts +5 -0
- package/dist/widgets/explanation/explanation.d.ts +3 -5
- package/dist/widgets/expression/expression-validator.d.ts +5 -0
- package/dist/widgets/expression/expression.d.ts +13 -6
- package/dist/widgets/expression/expression.types.d.ts +3 -0
- package/dist/widgets/expression/get-decimal-separator.d.ts +5 -0
- package/dist/widgets/iframe/iframe.d.ts +24 -15
- package/dist/widgets/image/image.d.ts +3 -3
- package/dist/widgets/input-number/input-number-validator.d.ts +41 -0
- package/dist/widgets/input-number/input-number.d.ts +1 -1
- package/dist/widgets/input-number/input-number.types.d.ts +2 -0
- package/dist/widgets/interaction/interaction.d.ts +3 -3
- package/dist/widgets/interactive-graphs/graph-locked-labels-layer.d.ts +2 -0
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +7 -0
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +2 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +16 -1
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +2 -1
- package/dist/widgets/interactive-graphs/types.d.ts +3 -0
- package/dist/widgets/label-image/label-image.d.ts +4 -0
- package/dist/widgets/matcher/matcher.d.ts +17 -46
- package/dist/widgets/molecule/molecule.d.ts +2 -2
- package/dist/widgets/numeric-input/numeric-input-validator.d.ts +6 -0
- package/dist/widgets/numeric-input/numeric-input.d.ts +1 -5
- package/dist/widgets/numeric-input/numeric-input.types.d.ts +5 -0
- package/dist/widgets/orderer/orderer.d.ts +6 -5
- package/dist/widgets/passage/passage.d.ts +3 -3
- package/dist/widgets/passage-ref/passage-ref.d.ts +3 -3
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +2 -2
- package/dist/widgets/sorter/sorter.d.ts +18 -44
- package/dist/widgets/table/table.d.ts +20 -24
- package/dist/widgets/video/video.d.ts +3 -4
- package/package.json +1 -1
- package/dist/mixins/widget-jsonify-deprecated.d.ts +0 -5
package/dist/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
|
|
@@ -21734,6 +21738,47 @@ const answerTypes = {
|
|
|
21734
21738
|
forms: "pi"
|
|
21735
21739
|
}
|
|
21736
21740
|
};
|
|
21741
|
+
function inputNumberValidator(state, rubric, strings, onInputError = () => {}) {
|
|
21742
|
+
if (rubric.answerType == null) {
|
|
21743
|
+
rubric.answerType = "number";
|
|
21744
|
+
}
|
|
21745
|
+
|
|
21746
|
+
// note(matthewc): this will get immediately parsed again by
|
|
21747
|
+
// `KhanAnswerTypes.number.convertToPredicate`, but a string is
|
|
21748
|
+
// expected here
|
|
21749
|
+
const stringValue = `${rubric.value}`;
|
|
21750
|
+
const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
21751
|
+
simplify: rubric.simplify,
|
|
21752
|
+
inexact: rubric.inexact || undefined,
|
|
21753
|
+
maxError: rubric.maxError,
|
|
21754
|
+
forms: answerTypes[rubric.answerType].forms
|
|
21755
|
+
}, strings);
|
|
21756
|
+
|
|
21757
|
+
// We may have received TeX; try to parse it before grading.
|
|
21758
|
+
// If `currentValue` is not TeX, this should be a no-op.
|
|
21759
|
+
const currentValue = ParseTex$1(state.currentValue);
|
|
21760
|
+
const result = val(currentValue);
|
|
21761
|
+
|
|
21762
|
+
// TODO(eater): Seems silly to translate result to this invalid/points
|
|
21763
|
+
// thing and immediately translate it back in ItemRenderer.scoreInput()
|
|
21764
|
+
if (result.empty) {
|
|
21765
|
+
// TODO(FEI-3867): remove null-check once we have APIOptionsInternal
|
|
21766
|
+
const apiResult = onInputError == null ? void 0 : onInputError(null,
|
|
21767
|
+
// reserved for some widget identifier
|
|
21768
|
+
state.currentValue, result.message);
|
|
21769
|
+
return {
|
|
21770
|
+
type: "invalid",
|
|
21771
|
+
message: apiResult === false ? null : result.message
|
|
21772
|
+
};
|
|
21773
|
+
}
|
|
21774
|
+
return {
|
|
21775
|
+
type: "points",
|
|
21776
|
+
earned: result.correct ? 1 : 0,
|
|
21777
|
+
total: 1,
|
|
21778
|
+
message: result.message
|
|
21779
|
+
};
|
|
21780
|
+
}
|
|
21781
|
+
|
|
21737
21782
|
const formExamples$1 = {
|
|
21738
21783
|
integer: function (options, strings) {
|
|
21739
21784
|
return strings.integerExample;
|
|
@@ -21824,7 +21869,7 @@ class InputNumber extends React.Component {
|
|
|
21824
21869
|
};
|
|
21825
21870
|
this.simpleValidate = (rubric, onInputError) => {
|
|
21826
21871
|
onInputError = onInputError || function () {};
|
|
21827
|
-
return
|
|
21872
|
+
return inputNumberValidator(this.getUserInput(), rubric, this.context.strings, onInputError);
|
|
21828
21873
|
};
|
|
21829
21874
|
this.examples = () => {
|
|
21830
21875
|
const {
|
|
@@ -21837,44 +21882,7 @@ class InputNumber extends React.Component {
|
|
|
21837
21882
|
};
|
|
21838
21883
|
}
|
|
21839
21884
|
static validate(state, rubric, strings, onInputError = () => {}) {
|
|
21840
|
-
|
|
21841
|
-
rubric.answerType = "number";
|
|
21842
|
-
}
|
|
21843
|
-
|
|
21844
|
-
// note(matthewc): this will get immediately parsed again by
|
|
21845
|
-
// `KhanAnswerTypes.number.convertToPredicate`, but a string is
|
|
21846
|
-
// expected here
|
|
21847
|
-
const stringValue = `${rubric.value}`;
|
|
21848
|
-
const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
21849
|
-
simplify: rubric.simplify,
|
|
21850
|
-
inexact: rubric.inexact || undefined,
|
|
21851
|
-
maxError: rubric.maxError,
|
|
21852
|
-
forms: answerTypes[rubric.answerType].forms
|
|
21853
|
-
}, strings);
|
|
21854
|
-
|
|
21855
|
-
// We may have received TeX; try to parse it before grading.
|
|
21856
|
-
// If `currentValue` is not TeX, this should be a no-op.
|
|
21857
|
-
const currentValue = ParseTex$1(state.currentValue);
|
|
21858
|
-
const result = val(currentValue);
|
|
21859
|
-
|
|
21860
|
-
// TODO(eater): Seems silly to translate result to this invalid/points
|
|
21861
|
-
// thing and immediately translate it back in ItemRenderer.scoreInput()
|
|
21862
|
-
if (result.empty) {
|
|
21863
|
-
// TODO(FEI-3867): remove null-check once we have APIOptionsInternal
|
|
21864
|
-
const apiResult = onInputError == null ? void 0 : onInputError(null,
|
|
21865
|
-
// reserved for some widget identifier
|
|
21866
|
-
state.currentValue, result.message);
|
|
21867
|
-
return {
|
|
21868
|
-
type: "invalid",
|
|
21869
|
-
message: apiResult === false ? null : result.message
|
|
21870
|
-
};
|
|
21871
|
-
}
|
|
21872
|
-
return {
|
|
21873
|
-
type: "points",
|
|
21874
|
-
earned: result.correct ? 1 : 0,
|
|
21875
|
-
total: 1,
|
|
21876
|
-
message: result.message
|
|
21877
|
-
};
|
|
21885
|
+
return inputNumberValidator(state, rubric, strings, onInputError);
|
|
21878
21886
|
}
|
|
21879
21887
|
static getUserInputFromProps(props) {
|
|
21880
21888
|
return {
|
|
@@ -22024,6 +22032,131 @@ const answerFormButtons = [{
|
|
|
22024
22032
|
value: "pi",
|
|
22025
22033
|
content: "\u03C0"
|
|
22026
22034
|
}];
|
|
22035
|
+
|
|
22036
|
+
// This function checks if the user inputted a percent value, parsing
|
|
22037
|
+
// it as a number (and maybe scaling) so that it can be graded.
|
|
22038
|
+
// NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
|
|
22039
|
+
// can accept several input forms with or without "%", the decision
|
|
22040
|
+
// to parse based on the presence of "%" in the input, is so that we
|
|
22041
|
+
// don't accidently scale the user typed value before grading, CP-930.
|
|
22042
|
+
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
22043
|
+
// If the input value is not a string ending with "%", then there's
|
|
22044
|
+
// nothing more to do. The value will be graded as inputted by user.
|
|
22045
|
+
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
22046
|
+
return inputValue;
|
|
22047
|
+
}
|
|
22048
|
+
const value = parseFloat(inputValue.slice(0, -1));
|
|
22049
|
+
// If the input value stripped of the "%" cannot be parsed as a
|
|
22050
|
+
// number (the slice is not really necessary for parseFloat to work
|
|
22051
|
+
// if the string starts with a number) then return the original
|
|
22052
|
+
// input for grading.
|
|
22053
|
+
if (isNaN(value)) {
|
|
22054
|
+
return inputValue;
|
|
22055
|
+
}
|
|
22056
|
+
|
|
22057
|
+
// Next, if all correct answers are in the range of |0,1| then we
|
|
22058
|
+
// scale the user typed value. We assume this is the correct thing
|
|
22059
|
+
// to do since the input value ends with "%".
|
|
22060
|
+
if (normalizedAnswerExpected) {
|
|
22061
|
+
return value / 100;
|
|
22062
|
+
}
|
|
22063
|
+
|
|
22064
|
+
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
22065
|
+
return value;
|
|
22066
|
+
}
|
|
22067
|
+
function numericInputValidator(userInput, rubric, strings) {
|
|
22068
|
+
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
22069
|
+
// Don't default to validating the answer as a pi answer
|
|
22070
|
+
// if answerForm isn't set on the answer
|
|
22071
|
+
// https://khanacademy.atlassian.net/browse/LC-691
|
|
22072
|
+
.filter(e => e !== "pi");
|
|
22073
|
+
const createValidator = answer => {
|
|
22074
|
+
var _answer$answerForms;
|
|
22075
|
+
const stringAnswer = `${answer.value}`;
|
|
22076
|
+
|
|
22077
|
+
// Always validate against the provided answer forms (pi, decimal, etc.)
|
|
22078
|
+
const validatorForms = [...((_answer$answerForms = answer.answerForms) != null ? _answer$answerForms : [])];
|
|
22079
|
+
|
|
22080
|
+
// When an answer is set to strict, we validate using ONLY
|
|
22081
|
+
// the provided answerForms. If strict is false, or if there
|
|
22082
|
+
// were no provided answer forms, we will include all
|
|
22083
|
+
// of the default answer forms in our validator.
|
|
22084
|
+
if (!answer.strict || validatorForms.length === 0) {
|
|
22085
|
+
validatorForms.push(...defaultAnswerForms);
|
|
22086
|
+
}
|
|
22087
|
+
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
22088
|
+
message: answer.message,
|
|
22089
|
+
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
22090
|
+
inexact: true,
|
|
22091
|
+
// TODO(merlob) backfill / delete
|
|
22092
|
+
maxError: answer.maxError,
|
|
22093
|
+
forms: validatorForms
|
|
22094
|
+
}, strings);
|
|
22095
|
+
};
|
|
22096
|
+
|
|
22097
|
+
// We may have received TeX; try to parse it before grading.
|
|
22098
|
+
// If `currentValue` is not TeX, this should be a no-op.
|
|
22099
|
+
const currentValue = ParseTex(userInput.currentValue);
|
|
22100
|
+
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22101
|
+
const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1);
|
|
22102
|
+
|
|
22103
|
+
// Look through all correct answers for one that matches either
|
|
22104
|
+
// precisely or approximately and return the appropriate message:
|
|
22105
|
+
// - if precise, return the message that the answer came with
|
|
22106
|
+
// - if it needs to be simplified, etc., show that message
|
|
22107
|
+
let result = correctAnswers.map(answer => {
|
|
22108
|
+
// The coefficient is an attribute of the widget
|
|
22109
|
+
let localValue = currentValue;
|
|
22110
|
+
if (rubric.coefficient) {
|
|
22111
|
+
if (!localValue) {
|
|
22112
|
+
localValue = 1;
|
|
22113
|
+
} else if (localValue === "-") {
|
|
22114
|
+
localValue = -1;
|
|
22115
|
+
}
|
|
22116
|
+
}
|
|
22117
|
+
const validate = createValidator(answer);
|
|
22118
|
+
return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
22119
|
+
}).find(match => match.correct || match.empty);
|
|
22120
|
+
if (!result) {
|
|
22121
|
+
// Otherwise, if the guess is not correct
|
|
22122
|
+
const otherAnswers = [].concat(
|
|
22123
|
+
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
22124
|
+
rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong"));
|
|
22125
|
+
|
|
22126
|
+
// Look through all other answers and if one matches either
|
|
22127
|
+
// precisely or approximately return the answer's message
|
|
22128
|
+
const match = otherAnswers.find(answer => {
|
|
22129
|
+
const validate = createValidator(answer);
|
|
22130
|
+
return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
|
|
22131
|
+
});
|
|
22132
|
+
result = {
|
|
22133
|
+
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22134
|
+
empty: match ? match.status === "ungraded" : false,
|
|
22135
|
+
// @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
|
|
22136
|
+
correct: match ? match.status === "correct" : false,
|
|
22137
|
+
// @ts-expect-error - TS2339 - Property 'message' does not exist on type 'never'.
|
|
22138
|
+
message: match ? match.message : null,
|
|
22139
|
+
guess: currentValue
|
|
22140
|
+
};
|
|
22141
|
+
}
|
|
22142
|
+
|
|
22143
|
+
// TODO(eater): Seems silly to translate result to this
|
|
22144
|
+
// invalid/points thing and immediately translate it
|
|
22145
|
+
// back in ItemRenderer.scoreInput()
|
|
22146
|
+
if (result.empty) {
|
|
22147
|
+
return {
|
|
22148
|
+
type: "invalid",
|
|
22149
|
+
message: result.message
|
|
22150
|
+
};
|
|
22151
|
+
}
|
|
22152
|
+
return {
|
|
22153
|
+
type: "points",
|
|
22154
|
+
earned: result.correct ? 1 : 0,
|
|
22155
|
+
total: 1,
|
|
22156
|
+
message: result.message
|
|
22157
|
+
};
|
|
22158
|
+
}
|
|
22159
|
+
|
|
22027
22160
|
const formExamples = {
|
|
22028
22161
|
integer: (form, strings) => strings.integerExample,
|
|
22029
22162
|
proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
|
|
@@ -22070,7 +22203,7 @@ class NumericInput extends React.Component {
|
|
|
22070
22203
|
return !noFormsAccepted && !allFormsAccepted;
|
|
22071
22204
|
};
|
|
22072
22205
|
this.simpleValidate = rubric => {
|
|
22073
|
-
return
|
|
22206
|
+
return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
|
|
22074
22207
|
};
|
|
22075
22208
|
this.focus = () => {
|
|
22076
22209
|
var _this$inputRef;
|
|
@@ -22151,98 +22284,7 @@ class NumericInput extends React.Component {
|
|
|
22151
22284
|
return answerStrings[0];
|
|
22152
22285
|
}
|
|
22153
22286
|
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
|
-
};
|
|
22287
|
+
return numericInputValidator(userInput, rubric, strings);
|
|
22246
22288
|
}
|
|
22247
22289
|
render() {
|
|
22248
22290
|
let labelText = this.props.labelText;
|
|
@@ -22343,37 +22385,6 @@ const unionAnswerForms = function unionAnswerForms(answerFormsList) {
|
|
|
22343
22385
|
return formExampleKeys.indexOf(form.name);
|
|
22344
22386
|
});
|
|
22345
22387
|
};
|
|
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
22388
|
const propsTransform = function propsTransform(widgetOptions) {
|
|
22378
22389
|
const rendererProps = _.extend(_.omit(widgetOptions, "answers"), {
|
|
22379
22390
|
answerForms: unionAnswerForms(
|
|
@@ -22401,6 +22412,23 @@ var NumericInput$1 = {
|
|
|
22401
22412
|
isLintable: true
|
|
22402
22413
|
};
|
|
22403
22414
|
|
|
22415
|
+
/**
|
|
22416
|
+
* Several widgets don't have "right"/"wrong" validation logic,
|
|
22417
|
+
* so this just says to move on past those widgets
|
|
22418
|
+
*
|
|
22419
|
+
* TODO(LEMS-2396) widgets that use this probably shouldn't have any
|
|
22420
|
+
* validation logic and the thing validating an exercise
|
|
22421
|
+
* should just know to skip these
|
|
22422
|
+
*/
|
|
22423
|
+
function noopValidator(points = 0) {
|
|
22424
|
+
return {
|
|
22425
|
+
type: "points",
|
|
22426
|
+
earned: points,
|
|
22427
|
+
total: points,
|
|
22428
|
+
message: null
|
|
22429
|
+
};
|
|
22430
|
+
}
|
|
22431
|
+
|
|
22404
22432
|
const EN_DASH$1 = "\u2013";
|
|
22405
22433
|
class PassageRef extends React.Component {
|
|
22406
22434
|
constructor(..._args) {
|
|
@@ -22448,17 +22476,10 @@ class PassageRef extends React.Component {
|
|
|
22448
22476
|
}
|
|
22449
22477
|
}
|
|
22450
22478
|
};
|
|
22451
|
-
this.simpleValidate = rubric => {
|
|
22452
|
-
return PassageRef.validate(this.getUserInput(), rubric);
|
|
22453
|
-
};
|
|
22454
22479
|
}
|
|
22455
|
-
|
|
22456
|
-
|
|
22457
|
-
|
|
22458
|
-
earned: 0,
|
|
22459
|
-
total: 0,
|
|
22460
|
-
message: null
|
|
22461
|
-
};
|
|
22480
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
22481
|
+
static validate() {
|
|
22482
|
+
return noopValidator();
|
|
22462
22483
|
}
|
|
22463
22484
|
componentDidMount() {
|
|
22464
22485
|
// TODO(scottgrant): This is a hack to remove the deprecated call to
|
|
@@ -22478,6 +22499,10 @@ class PassageRef extends React.Component {
|
|
|
22478
22499
|
window.removeEventListener("resize", this._throttledUpdateRange);
|
|
22479
22500
|
this._isMounted = false;
|
|
22480
22501
|
}
|
|
22502
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
22503
|
+
simpleValidate() {
|
|
22504
|
+
return noopValidator();
|
|
22505
|
+
}
|
|
22481
22506
|
render() {
|
|
22482
22507
|
const {
|
|
22483
22508
|
strings
|
|
@@ -22521,6 +22546,7 @@ PassageRef.defaultProps = {
|
|
|
22521
22546
|
var PassageRef$1 = {
|
|
22522
22547
|
name: "passage-ref",
|
|
22523
22548
|
displayName: "PassageRef (SAT only)",
|
|
22549
|
+
hidden: true,
|
|
22524
22550
|
defaultAlignment: "inline",
|
|
22525
22551
|
widget: PassageRef,
|
|
22526
22552
|
transform: widgetOptions => ({
|
|
@@ -24406,22 +24432,30 @@ var Radio = {
|
|
|
24406
24432
|
/* eslint-disable import/no-named-as-default */
|
|
24407
24433
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
24408
24434
|
|
|
24409
|
-
|
|
24410
|
-
|
|
24411
|
-
|
|
24412
|
-
|
|
24413
|
-
|
|
24414
|
-
|
|
24415
|
-
|
|
24416
|
-
|
|
24417
|
-
|
|
24418
|
-
|
|
24419
|
-
|
|
24420
|
-
|
|
24421
|
-
|
|
24422
|
-
|
|
24435
|
+
function categorizerValidator(userInput, rubric, strings) {
|
|
24436
|
+
let completed = true;
|
|
24437
|
+
let allCorrect = true;
|
|
24438
|
+
rubric.values.forEach((value, i) => {
|
|
24439
|
+
if (userInput.values[i] == null) {
|
|
24440
|
+
completed = false;
|
|
24441
|
+
}
|
|
24442
|
+
if (userInput.values[i] !== value) {
|
|
24443
|
+
allCorrect = false;
|
|
24444
|
+
}
|
|
24445
|
+
});
|
|
24446
|
+
if (!completed) {
|
|
24447
|
+
return {
|
|
24448
|
+
type: "invalid",
|
|
24449
|
+
message: strings.invalidSelection
|
|
24450
|
+
};
|
|
24423
24451
|
}
|
|
24424
|
-
|
|
24452
|
+
return {
|
|
24453
|
+
type: "points",
|
|
24454
|
+
earned: allCorrect ? 1 : 0,
|
|
24455
|
+
total: 1,
|
|
24456
|
+
message: null
|
|
24457
|
+
};
|
|
24458
|
+
}
|
|
24425
24459
|
|
|
24426
24460
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
24427
24461
|
class Categorizer extends React.Component {
|
|
@@ -24438,35 +24472,16 @@ class Categorizer extends React.Component {
|
|
|
24438
24472
|
return Categorizer.getUserInputFromProps(this.props);
|
|
24439
24473
|
};
|
|
24440
24474
|
this.simpleValidate = rubric => {
|
|
24441
|
-
return
|
|
24475
|
+
return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
|
|
24442
24476
|
};
|
|
24443
24477
|
}
|
|
24444
24478
|
static validate(userInput, rubric, strings) {
|
|
24445
|
-
|
|
24446
|
-
let allCorrect = true;
|
|
24447
|
-
rubric.values.forEach((value, i) => {
|
|
24448
|
-
if (userInput.values[i] == null) {
|
|
24449
|
-
completed = false;
|
|
24450
|
-
}
|
|
24451
|
-
if (userInput.values[i] !== value) {
|
|
24452
|
-
allCorrect = false;
|
|
24453
|
-
}
|
|
24454
|
-
});
|
|
24455
|
-
if (!completed) {
|
|
24456
|
-
return {
|
|
24457
|
-
type: "invalid",
|
|
24458
|
-
message: strings.invalidSelection
|
|
24459
|
-
};
|
|
24460
|
-
}
|
|
24461
|
-
return {
|
|
24462
|
-
type: "points",
|
|
24463
|
-
earned: allCorrect ? 1 : 0,
|
|
24464
|
-
total: 1,
|
|
24465
|
-
message: null
|
|
24466
|
-
};
|
|
24479
|
+
return categorizerValidator(userInput, rubric, strings);
|
|
24467
24480
|
}
|
|
24468
24481
|
static getUserInputFromProps(props) {
|
|
24469
|
-
return
|
|
24482
|
+
return {
|
|
24483
|
+
values: props.values
|
|
24484
|
+
};
|
|
24470
24485
|
}
|
|
24471
24486
|
onChange(itemNum, catNum) {
|
|
24472
24487
|
const values = [...this.props.values];
|
|
@@ -24612,6 +24627,7 @@ const styles$s = StyleSheet.create({
|
|
|
24612
24627
|
var Categorizer$1 = {
|
|
24613
24628
|
name: "categorizer",
|
|
24614
24629
|
displayName: "Categorizer",
|
|
24630
|
+
hidden: true,
|
|
24615
24631
|
widget: Categorizer,
|
|
24616
24632
|
transform: widgetOptions => {
|
|
24617
24633
|
return _.pick(widgetOptions, "items", "categories", "randomizeItems");
|
|
@@ -24669,6 +24685,9 @@ const parseUrl = url => new URL(url, location().origin);
|
|
|
24669
24685
|
*/
|
|
24670
24686
|
const toAbsoluteUrl = url => parseUrl(url).href;
|
|
24671
24687
|
|
|
24688
|
+
/**
|
|
24689
|
+
* This widget is for embedding Khan Academy CS programs.
|
|
24690
|
+
*/
|
|
24672
24691
|
const {
|
|
24673
24692
|
updateQueryString: updateQueryString$1
|
|
24674
24693
|
} = Util;
|
|
@@ -24714,12 +24733,6 @@ class CSProgram extends React.Component {
|
|
|
24714
24733
|
// @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
|
|
24715
24734
|
return change.apply(this, args);
|
|
24716
24735
|
};
|
|
24717
|
-
this.simpleValidate = rubric => {
|
|
24718
|
-
return CSProgram.validate({
|
|
24719
|
-
status: this.props.status,
|
|
24720
|
-
message: this.props.message
|
|
24721
|
-
}, rubric);
|
|
24722
|
-
};
|
|
24723
24736
|
}
|
|
24724
24737
|
// The widget's grading function
|
|
24725
24738
|
static validate(state, rubric) {
|
|
@@ -24752,6 +24765,12 @@ class CSProgram extends React.Component {
|
|
|
24752
24765
|
componentWillUnmount() {
|
|
24753
24766
|
$(window).off("message", this.handleMessageEvent);
|
|
24754
24767
|
}
|
|
24768
|
+
simpleValidate(rubric) {
|
|
24769
|
+
return CSProgram.validate({
|
|
24770
|
+
status: this.props.status,
|
|
24771
|
+
message: this.props.message
|
|
24772
|
+
}, rubric);
|
|
24773
|
+
}
|
|
24755
24774
|
render() {
|
|
24756
24775
|
if (!this.props.programID) {
|
|
24757
24776
|
return /*#__PURE__*/React.createElement("div", null);
|
|
@@ -24811,18 +24830,6 @@ class CSProgram extends React.Component {
|
|
|
24811
24830
|
}));
|
|
24812
24831
|
}
|
|
24813
24832
|
}
|
|
24814
|
-
CSProgram.propTypes = _extends({}, propTypes, {
|
|
24815
|
-
programID: PropTypes.string,
|
|
24816
|
-
programType: PropTypes.oneOf(["pjs", "sql", "webpage"]),
|
|
24817
|
-
width: PropTypes.number,
|
|
24818
|
-
height: PropTypes.number,
|
|
24819
|
-
// eslint-disable-next-line react/forbid-prop-types
|
|
24820
|
-
settings: PropTypes.array,
|
|
24821
|
-
showEditor: PropTypes.bool,
|
|
24822
|
-
showButtons: PropTypes.bool,
|
|
24823
|
-
status: PropTypes.oneOf(["incomplete", "incorrect", "correct"]),
|
|
24824
|
-
message: PropTypes.string
|
|
24825
|
-
});
|
|
24826
24833
|
CSProgram.defaultProps = {
|
|
24827
24834
|
showEditor: false,
|
|
24828
24835
|
showButtons: false,
|
|
@@ -24854,17 +24861,14 @@ class Definition extends React.Component {
|
|
|
24854
24861
|
this.getUserInput = () => {
|
|
24855
24862
|
return {};
|
|
24856
24863
|
};
|
|
24857
|
-
this.simpleValidate = rubric => {
|
|
24858
|
-
return Definition.validate(this.getUserInput(), rubric);
|
|
24859
|
-
};
|
|
24860
24864
|
}
|
|
24861
|
-
|
|
24862
|
-
|
|
24863
|
-
|
|
24864
|
-
|
|
24865
|
-
|
|
24866
|
-
|
|
24867
|
-
|
|
24865
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
24866
|
+
static validate() {
|
|
24867
|
+
return noopValidator();
|
|
24868
|
+
}
|
|
24869
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
24870
|
+
simpleValidate() {
|
|
24871
|
+
return noopValidator();
|
|
24868
24872
|
}
|
|
24869
24873
|
render() {
|
|
24870
24874
|
return /*#__PURE__*/React.createElement(DefinitionConsumer, null, ({
|
|
@@ -24932,18 +24936,13 @@ class DeprecatedStandin extends React__default.Component {
|
|
|
24932
24936
|
return {};
|
|
24933
24937
|
};
|
|
24934
24938
|
this.simpleValidate = rubric => {
|
|
24935
|
-
return
|
|
24939
|
+
return noopValidator(1);
|
|
24936
24940
|
};
|
|
24937
24941
|
}
|
|
24938
24942
|
static validate(userInput, rubric) {
|
|
24939
24943
|
// Since this mean to replace an existing widget the learner
|
|
24940
24944
|
// WILL earn points for this widget
|
|
24941
|
-
return
|
|
24942
|
-
type: "points",
|
|
24943
|
-
earned: 1,
|
|
24944
|
-
total: 1,
|
|
24945
|
-
message: null
|
|
24946
|
-
};
|
|
24945
|
+
return noopValidator(1);
|
|
24947
24946
|
}
|
|
24948
24947
|
render() {
|
|
24949
24948
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
@@ -24966,6 +24965,23 @@ var DeprecatedStandin$1 = {
|
|
|
24966
24965
|
hidden: true
|
|
24967
24966
|
};
|
|
24968
24967
|
|
|
24968
|
+
function dropdownValidator(userInput, rubric) {
|
|
24969
|
+
const selected = userInput.value;
|
|
24970
|
+
if (selected === 0) {
|
|
24971
|
+
return {
|
|
24972
|
+
type: "invalid",
|
|
24973
|
+
message: null
|
|
24974
|
+
};
|
|
24975
|
+
}
|
|
24976
|
+
const correct = rubric.choices[selected - 1].correct;
|
|
24977
|
+
return {
|
|
24978
|
+
type: "points",
|
|
24979
|
+
earned: correct ? 1 : 0,
|
|
24980
|
+
total: 1,
|
|
24981
|
+
message: null
|
|
24982
|
+
};
|
|
24983
|
+
}
|
|
24984
|
+
|
|
24969
24985
|
class Dropdown extends React.Component {
|
|
24970
24986
|
constructor(...args) {
|
|
24971
24987
|
super(...args);
|
|
@@ -24992,24 +25008,11 @@ class Dropdown extends React.Component {
|
|
|
24992
25008
|
};
|
|
24993
25009
|
};
|
|
24994
25010
|
this.simpleValidate = rubric => {
|
|
24995
|
-
return
|
|
25011
|
+
return dropdownValidator(this.getUserInput(), rubric);
|
|
24996
25012
|
};
|
|
24997
25013
|
}
|
|
24998
25014
|
static validate(userInput, rubric) {
|
|
24999
|
-
|
|
25000
|
-
if (selected === 0) {
|
|
25001
|
-
return {
|
|
25002
|
-
type: "invalid",
|
|
25003
|
-
message: null
|
|
25004
|
-
};
|
|
25005
|
-
}
|
|
25006
|
-
const correct = rubric.choices[selected - 1].correct;
|
|
25007
|
-
return {
|
|
25008
|
-
type: "points",
|
|
25009
|
-
earned: correct ? 1 : 0,
|
|
25010
|
-
total: 1,
|
|
25011
|
-
message: null
|
|
25012
|
-
};
|
|
25015
|
+
return dropdownValidator(userInput, rubric);
|
|
25013
25016
|
}
|
|
25014
25017
|
render() {
|
|
25015
25018
|
const children = [/*#__PURE__*/React.createElement(OptionItem, {
|
|
@@ -25087,17 +25090,10 @@ class Explanation extends React.Component {
|
|
|
25087
25090
|
this.getUserInput = () => {
|
|
25088
25091
|
return {};
|
|
25089
25092
|
};
|
|
25090
|
-
this.simpleValidate = rubric => {
|
|
25091
|
-
return Explanation.validate(this.getUserInput(), rubric);
|
|
25092
|
-
};
|
|
25093
25093
|
}
|
|
25094
|
-
|
|
25095
|
-
|
|
25096
|
-
|
|
25097
|
-
earned: 0,
|
|
25098
|
-
total: 0,
|
|
25099
|
-
message: null
|
|
25100
|
-
};
|
|
25094
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
25095
|
+
static validate() {
|
|
25096
|
+
return noopValidator();
|
|
25101
25097
|
}
|
|
25102
25098
|
componentDidMount() {
|
|
25103
25099
|
this._mounted = true;
|
|
@@ -25105,6 +25101,10 @@ class Explanation extends React.Component {
|
|
|
25105
25101
|
componentWillUnmount() {
|
|
25106
25102
|
this._mounted = false;
|
|
25107
25103
|
}
|
|
25104
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
25105
|
+
simpleValidate() {
|
|
25106
|
+
return noopValidator();
|
|
25107
|
+
}
|
|
25108
25108
|
render() {
|
|
25109
25109
|
const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
|
|
25110
25110
|
const caretIcon = this.state.expanded ? caretUp : caretDown;
|
|
@@ -26925,6 +26925,7 @@ const staticTransform$4 = editorProps => {
|
|
|
26925
26925
|
var Grapher$1 = {
|
|
26926
26926
|
name: "grapher",
|
|
26927
26927
|
displayName: "Grapher",
|
|
26928
|
+
hidden: true,
|
|
26928
26929
|
widget: Grapher,
|
|
26929
26930
|
transform: propTransform$2,
|
|
26930
26931
|
staticTransform: staticTransform$4
|
|
@@ -27074,21 +27075,25 @@ var Group$1 = {
|
|
|
27074
27075
|
displayName: "Group (SAT only)",
|
|
27075
27076
|
widget: Group,
|
|
27076
27077
|
traverseChildWidgets: traverseChildWidgets,
|
|
27077
|
-
hidden:
|
|
27078
|
+
hidden: true,
|
|
27078
27079
|
isLintable: true
|
|
27079
27080
|
};
|
|
27080
27081
|
|
|
27082
|
+
/**
|
|
27083
|
+
* This is an iframe widget. It is used for rendering an iframe that
|
|
27084
|
+
* then communicates its state via window.postMessage
|
|
27085
|
+
* This is useful for embedding arbitrary visualizations/simulations with
|
|
27086
|
+
* completed conditions, such as the mazes and games in Algorithms.
|
|
27087
|
+
* It's particularly well suited for embedding our ProcessingJS programs,
|
|
27088
|
+
* but could also be used for embedding viz's hosted elsewhere.
|
|
27089
|
+
*/
|
|
27081
27090
|
const {
|
|
27082
27091
|
updateQueryString
|
|
27083
27092
|
} = Util;
|
|
27084
|
-
|
|
27085
27093
|
/* This renders the iframe and handles validation via window.postMessage */
|
|
27086
27094
|
class Iframe extends React.Component {
|
|
27087
27095
|
constructor(..._args) {
|
|
27088
27096
|
super(..._args);
|
|
27089
|
-
this.getUserInput = () => {
|
|
27090
|
-
return WidgetJsonifyDeprecated.getUserInput.call(this);
|
|
27091
|
-
};
|
|
27092
27097
|
this.handleMessageEvent = e => {
|
|
27093
27098
|
// We receive data from the iframe that contains {passed: true/false}
|
|
27094
27099
|
// and use that to set the status
|
|
@@ -27123,6 +27128,12 @@ class Iframe extends React.Component {
|
|
|
27123
27128
|
componentWillUnmount() {
|
|
27124
27129
|
$(window).off("message", this.handleMessageEvent);
|
|
27125
27130
|
}
|
|
27131
|
+
getUserInput() {
|
|
27132
|
+
return {
|
|
27133
|
+
status: this.props.status,
|
|
27134
|
+
message: this.props.message
|
|
27135
|
+
};
|
|
27136
|
+
}
|
|
27126
27137
|
render() {
|
|
27127
27138
|
const style = {
|
|
27128
27139
|
width: String(this.props.width),
|
|
@@ -27188,16 +27199,6 @@ class Iframe extends React.Component {
|
|
|
27188
27199
|
/**
|
|
27189
27200
|
* This is the widget's grading function
|
|
27190
27201
|
*/
|
|
27191
|
-
Iframe.propTypes = _extends({}, propTypes, {
|
|
27192
|
-
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
27193
|
-
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
27194
|
-
url: PropTypes.string,
|
|
27195
|
-
settings: PropTypes.array,
|
|
27196
|
-
status: PropTypes.oneOf(["incomplete", "incorrect", "correct"]),
|
|
27197
|
-
message: PropTypes.string,
|
|
27198
|
-
allowFullScreen: PropTypes.bool,
|
|
27199
|
-
allowTopNavigation: PropTypes.bool
|
|
27200
|
-
});
|
|
27201
27202
|
Iframe.defaultProps = {
|
|
27202
27203
|
status: "incomplete",
|
|
27203
27204
|
// optional message
|
|
@@ -27260,18 +27261,15 @@ class ImageWidget extends React.Component {
|
|
|
27260
27261
|
this.getUserInput = () => {
|
|
27261
27262
|
return null;
|
|
27262
27263
|
};
|
|
27263
|
-
this.simpleValidate = rubric => {
|
|
27264
|
-
return ImageWidget.validate(this.getUserInput(), rubric);
|
|
27265
|
-
};
|
|
27266
27264
|
this.focus = () => {};
|
|
27267
27265
|
}
|
|
27268
|
-
|
|
27269
|
-
|
|
27270
|
-
|
|
27271
|
-
|
|
27272
|
-
|
|
27273
|
-
|
|
27274
|
-
|
|
27266
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27267
|
+
static validate() {
|
|
27268
|
+
return noopValidator();
|
|
27269
|
+
}
|
|
27270
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27271
|
+
simpleValidate() {
|
|
27272
|
+
return noopValidator();
|
|
27275
27273
|
}
|
|
27276
27274
|
// no-op
|
|
27277
27275
|
|
|
@@ -27587,17 +27585,10 @@ class Interaction extends React.Component {
|
|
|
27587
27585
|
// anything.
|
|
27588
27586
|
return {};
|
|
27589
27587
|
};
|
|
27590
|
-
this.simpleValidate = rubric => {
|
|
27591
|
-
return Interaction.validate(this.getUserInput(), rubric);
|
|
27592
|
-
};
|
|
27593
27588
|
}
|
|
27594
|
-
|
|
27595
|
-
|
|
27596
|
-
|
|
27597
|
-
earned: 0,
|
|
27598
|
-
total: 0,
|
|
27599
|
-
message: null
|
|
27600
|
-
};
|
|
27589
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27590
|
+
static validate() {
|
|
27591
|
+
return noopValidator();
|
|
27601
27592
|
}
|
|
27602
27593
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
27603
27594
|
// HACK(michaelpolyak): This widget is not wired to use the Perseus
|
|
@@ -27617,6 +27608,10 @@ class Interaction extends React.Component {
|
|
|
27617
27608
|
});
|
|
27618
27609
|
}
|
|
27619
27610
|
}
|
|
27611
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27612
|
+
simpleValidate() {
|
|
27613
|
+
return noopValidator();
|
|
27614
|
+
}
|
|
27620
27615
|
render() {
|
|
27621
27616
|
const range = this.props.graph.range;
|
|
27622
27617
|
let labels = this.props.graph.labels;
|
|
@@ -29663,17 +29658,31 @@ function LockedLabel(props) {
|
|
|
29663
29658
|
|
|
29664
29659
|
function GraphLockedLabelsLayer(props) {
|
|
29665
29660
|
const {
|
|
29661
|
+
flags,
|
|
29666
29662
|
lockedFigures
|
|
29667
29663
|
} = props;
|
|
29668
29664
|
return lockedFigures.map((figure, i) => {
|
|
29665
|
+
var _flags$mafs, _flags$mafs2;
|
|
29669
29666
|
if (figure.type === "label") {
|
|
29670
29667
|
return /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29671
29668
|
key: `label-${i}`
|
|
29672
29669
|
}, figure));
|
|
29673
29670
|
}
|
|
29674
|
-
|
|
29675
|
-
//
|
|
29676
|
-
|
|
29671
|
+
if (
|
|
29672
|
+
// Point flag + point type
|
|
29673
|
+
flags != null && (_flags$mafs = flags["mafs"]) != null && _flags$mafs["locked-point-labels"] && figure.type === "point" ||
|
|
29674
|
+
// Line flag + line type
|
|
29675
|
+
flags != null && (_flags$mafs2 = flags["mafs"]) != null && _flags$mafs2["locked-line-labels"] && figure.type === "line") {
|
|
29676
|
+
return /*#__PURE__*/React.createElement(React.Fragment, {
|
|
29677
|
+
key: i
|
|
29678
|
+
}, figure.labels.map((label, j) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29679
|
+
key: `${i}-label-${j}`
|
|
29680
|
+
}, label))), figure.type === "line" && /*#__PURE__*/React.createElement(React.Fragment, null, figure.points[0].labels.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29681
|
+
key: `locked-figure-${i}-point-1-label-${k}`
|
|
29682
|
+
}, label))), figure.points[1].labels.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29683
|
+
key: `locked-figure-${i}-point-2-label-${k}`
|
|
29684
|
+
}, label)))));
|
|
29685
|
+
}
|
|
29677
29686
|
return null;
|
|
29678
29687
|
});
|
|
29679
29688
|
}
|
|
@@ -30012,7 +30021,9 @@ const GraphLockedLayer = props => {
|
|
|
30012
30021
|
|
|
30013
30022
|
const actions = {
|
|
30014
30023
|
global: {
|
|
30015
|
-
deleteIntent
|
|
30024
|
+
deleteIntent,
|
|
30025
|
+
changeInteractionMode,
|
|
30026
|
+
changeKeyboardInvitationVisibility
|
|
30016
30027
|
},
|
|
30017
30028
|
angle: {
|
|
30018
30029
|
movePoint
|
|
@@ -30104,6 +30115,20 @@ function clickPoint(index) {
|
|
|
30104
30115
|
index
|
|
30105
30116
|
};
|
|
30106
30117
|
}
|
|
30118
|
+
const CHANGE_INTERACTION_MODE = "point-change-interaction-mode";
|
|
30119
|
+
function changeInteractionMode(mode) {
|
|
30120
|
+
return {
|
|
30121
|
+
type: CHANGE_INTERACTION_MODE,
|
|
30122
|
+
mode
|
|
30123
|
+
};
|
|
30124
|
+
}
|
|
30125
|
+
const CHANGE_KEYBOARD_INVITATION_VISIBILITY = "change-keyboard-interaction-invitation-visibility";
|
|
30126
|
+
function changeKeyboardInvitationVisibility(shouldShow) {
|
|
30127
|
+
return {
|
|
30128
|
+
type: CHANGE_KEYBOARD_INVITATION_VISIBILITY,
|
|
30129
|
+
shouldShow
|
|
30130
|
+
};
|
|
30131
|
+
}
|
|
30107
30132
|
const MOVE_ALL = "move-all";
|
|
30108
30133
|
function moveAll(delta) {
|
|
30109
30134
|
return {
|
|
@@ -31884,6 +31909,7 @@ function useDraggablePx(args) {
|
|
|
31884
31909
|
*/
|
|
31885
31910
|
const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
|
|
31886
31911
|
const MafsGraph = props => {
|
|
31912
|
+
var _props$flags;
|
|
31887
31913
|
const {
|
|
31888
31914
|
state,
|
|
31889
31915
|
dispatch,
|
|
@@ -31941,16 +31967,18 @@ const MafsGraph = props => {
|
|
|
31941
31967
|
height
|
|
31942
31968
|
},
|
|
31943
31969
|
onKeyUp: event => {
|
|
31944
|
-
|
|
31945
|
-
var _graphRef$current;
|
|
31946
|
-
dispatch(actions.global.deleteIntent());
|
|
31947
|
-
(_graphRef$current = graphRef.current) == null || _graphRef$current.focus();
|
|
31948
|
-
}
|
|
31970
|
+
handleKeyboardEvent(event, state, dispatch);
|
|
31949
31971
|
},
|
|
31950
31972
|
"aria-label": fullGraphAriaLabel,
|
|
31951
31973
|
"aria-describedby": fullGraphAriaDescription ? descriptionId : undefined,
|
|
31952
31974
|
ref: graphRef,
|
|
31953
|
-
tabIndex: 0
|
|
31975
|
+
tabIndex: 0,
|
|
31976
|
+
onFocus: event => {
|
|
31977
|
+
handleFocusEvent(event, state, dispatch);
|
|
31978
|
+
},
|
|
31979
|
+
onBlur: event => {
|
|
31980
|
+
handleBlurEvent(event, state, dispatch);
|
|
31981
|
+
}
|
|
31954
31982
|
}, fullGraphAriaDescription && /*#__PURE__*/React.createElement(View, {
|
|
31955
31983
|
id: descriptionId,
|
|
31956
31984
|
tabIndex: -1,
|
|
@@ -31991,7 +32019,8 @@ const MafsGraph = props => {
|
|
|
31991
32019
|
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, {
|
|
31992
32020
|
lockedFigures: props.lockedFigures,
|
|
31993
32021
|
range: state.range
|
|
31994
|
-
}))), props.
|
|
32022
|
+
}))), ((_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, {
|
|
32023
|
+
flags: props.flags,
|
|
31995
32024
|
lockedFigures: props.lockedFigures
|
|
31996
32025
|
}), /*#__PURE__*/React.createElement(View, {
|
|
31997
32026
|
style: {
|
|
@@ -32011,30 +32040,63 @@ const MafsGraph = props => {
|
|
|
32011
32040
|
}, /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.showProtractor && /*#__PURE__*/React.createElement(Protractor, null), renderGraph({
|
|
32012
32041
|
state,
|
|
32013
32042
|
dispatch
|
|
32014
|
-
})))))
|
|
32043
|
+
}))))), state.type === "point" && state.showKeyboardInteractionInvitation && /*#__PURE__*/React.createElement(View, {
|
|
32044
|
+
style: {
|
|
32045
|
+
textAlign: "center",
|
|
32046
|
+
backgroundColor: "white",
|
|
32047
|
+
border: "1px solid #21242C52",
|
|
32048
|
+
padding: "16px 0",
|
|
32049
|
+
boxShadow: "0px 8px 8px 0px #21242C14",
|
|
32050
|
+
// This translates the box to the center of the
|
|
32051
|
+
// graph Then backs it off by half of its
|
|
32052
|
+
// overall height so it's perfectly centered
|
|
32053
|
+
top: "50%",
|
|
32054
|
+
transform: "translateY(-50%)"
|
|
32055
|
+
}
|
|
32056
|
+
}, /*#__PURE__*/React.createElement(LabelMedium, null, "Press ", /*#__PURE__*/React.createElement("strong", null, "Shift + Enter"), " to interact with the graph"))), renderGraphControls({
|
|
32015
32057
|
state,
|
|
32016
32058
|
dispatch,
|
|
32017
32059
|
width
|
|
32018
32060
|
})));
|
|
32019
32061
|
};
|
|
32020
|
-
const renderPointGraphControls = props =>
|
|
32021
|
-
|
|
32022
|
-
|
|
32023
|
-
|
|
32024
|
-
|
|
32025
|
-
}
|
|
32026
|
-
|
|
32027
|
-
|
|
32028
|
-
|
|
32029
|
-
|
|
32030
|
-
|
|
32031
|
-
|
|
32032
|
-
|
|
32033
|
-
|
|
32034
|
-
|
|
32035
|
-
|
|
32036
|
-
|
|
32037
|
-
|
|
32062
|
+
const renderPointGraphControls = props => {
|
|
32063
|
+
const {
|
|
32064
|
+
interactionMode,
|
|
32065
|
+
showRemovePointButton,
|
|
32066
|
+
focusedPointIndex
|
|
32067
|
+
} = props.state;
|
|
32068
|
+
return /*#__PURE__*/React.createElement(View, {
|
|
32069
|
+
style: {
|
|
32070
|
+
flexDirection: "row",
|
|
32071
|
+
width: props.width
|
|
32072
|
+
}
|
|
32073
|
+
}, interactionMode === "keyboard" && /*#__PURE__*/React.createElement(Button, {
|
|
32074
|
+
kind: "secondary",
|
|
32075
|
+
style: {
|
|
32076
|
+
width: "100%",
|
|
32077
|
+
marginLeft: "20px"
|
|
32078
|
+
},
|
|
32079
|
+
tabIndex: 0,
|
|
32080
|
+
onClick: () => {
|
|
32081
|
+
props.dispatch(actions.pointGraph.addPoint([0, 0]));
|
|
32082
|
+
}
|
|
32083
|
+
}, "Add Point"), interactionMode === "mouse" && showRemovePointButton && focusedPointIndex !== null && /*#__PURE__*/React.createElement(Button, {
|
|
32084
|
+
id: REMOVE_BUTTON_ID,
|
|
32085
|
+
kind: "secondary",
|
|
32086
|
+
color: "destructive"
|
|
32087
|
+
// This button is meant to be interacted with by the mouse only
|
|
32088
|
+
// Never allow learners to tab to this button
|
|
32089
|
+
,
|
|
32090
|
+
tabIndex: -1,
|
|
32091
|
+
style: {
|
|
32092
|
+
width: "100%",
|
|
32093
|
+
marginLeft: "20px"
|
|
32094
|
+
},
|
|
32095
|
+
onClick: event => {
|
|
32096
|
+
props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));
|
|
32097
|
+
}
|
|
32098
|
+
}, "Remove Point"));
|
|
32099
|
+
};
|
|
32038
32100
|
const renderGraphControls = props => {
|
|
32039
32101
|
const {
|
|
32040
32102
|
state,
|
|
@@ -32058,6 +32120,34 @@ const renderGraphControls = props => {
|
|
|
32058
32120
|
return null;
|
|
32059
32121
|
}
|
|
32060
32122
|
};
|
|
32123
|
+
function handleFocusEvent(event, state, dispatch) {
|
|
32124
|
+
if (state.type === "point" && state.numPoints === "unlimited") {
|
|
32125
|
+
if (event.target.classList.contains("mafs-graph") && state.interactionMode === "mouse") {
|
|
32126
|
+
dispatch(actions.global.changeKeyboardInvitationVisibility(true));
|
|
32127
|
+
}
|
|
32128
|
+
}
|
|
32129
|
+
}
|
|
32130
|
+
function handleBlurEvent(event, state, dispatch) {
|
|
32131
|
+
if (state.type === "point" && state.numPoints === "unlimited") {
|
|
32132
|
+
dispatch(actions.global.changeKeyboardInvitationVisibility(false));
|
|
32133
|
+
}
|
|
32134
|
+
}
|
|
32135
|
+
function handleKeyboardEvent(event, state, dispatch) {
|
|
32136
|
+
if (state.type === "point" && state.numPoints === "unlimited") {
|
|
32137
|
+
if (event.key === "Backspace") {
|
|
32138
|
+
dispatch(actions.global.deleteIntent());
|
|
32139
|
+
|
|
32140
|
+
// After removing a point blur
|
|
32141
|
+
// It would be nice if this could focus on the graph but doing so
|
|
32142
|
+
// would trigger the message to prompt a learner to enter keyboard mode
|
|
32143
|
+
document.activeElement.blur();
|
|
32144
|
+
} else if (event.shiftKey && event.key === "Enter") {
|
|
32145
|
+
dispatch(actions.global.changeInteractionMode("keyboard"));
|
|
32146
|
+
} else if (state.interactionMode === "keyboard" && event.key === "a") {
|
|
32147
|
+
dispatch(actions.pointGraph.addPoint([0, 0]));
|
|
32148
|
+
}
|
|
32149
|
+
}
|
|
32150
|
+
}
|
|
32061
32151
|
|
|
32062
32152
|
// Calculate the difference between the min and max values of a range
|
|
32063
32153
|
const getRangeDiff = range => {
|
|
@@ -32222,7 +32312,9 @@ function initializeGraphState(params) {
|
|
|
32222
32312
|
coords: getPointCoords(graph, range, step),
|
|
32223
32313
|
numPoints: graph.numPoints || 0,
|
|
32224
32314
|
focusedPointIndex: null,
|
|
32225
|
-
showRemovePointButton: false
|
|
32315
|
+
showRemovePointButton: false,
|
|
32316
|
+
interactionMode: "mouse",
|
|
32317
|
+
showKeyboardInteractionInvitation: false
|
|
32226
32318
|
});
|
|
32227
32319
|
case "circle":
|
|
32228
32320
|
return _extends({}, shared, {
|
|
@@ -32492,6 +32584,10 @@ function interactiveGraphReducer(state, action) {
|
|
|
32492
32584
|
return doDeleteIntent(state);
|
|
32493
32585
|
case CLICK_POINT:
|
|
32494
32586
|
return doClickPoint(state, action);
|
|
32587
|
+
case CHANGE_INTERACTION_MODE:
|
|
32588
|
+
return doChangeInteractionMode(state, action);
|
|
32589
|
+
case CHANGE_KEYBOARD_INVITATION_VISIBILITY:
|
|
32590
|
+
return doChangeKeyboardInvitationVisibility(state, action);
|
|
32495
32591
|
default:
|
|
32496
32592
|
throw new UnreachableCaseError(action);
|
|
32497
32593
|
}
|
|
@@ -32540,6 +32636,31 @@ function doClickPoint(state, action) {
|
|
|
32540
32636
|
}
|
|
32541
32637
|
return state;
|
|
32542
32638
|
}
|
|
32639
|
+
function doChangeInteractionMode(state, action) {
|
|
32640
|
+
if (state.type !== "point") {
|
|
32641
|
+
return state;
|
|
32642
|
+
}
|
|
32643
|
+
if (state.numPoints === "unlimited") {
|
|
32644
|
+
const nextKeyboardInvitation = action.mode === "keyboard" ? false : state.showKeyboardInteractionInvitation;
|
|
32645
|
+
return _extends({}, state, {
|
|
32646
|
+
interactionMode: action.mode,
|
|
32647
|
+
showKeyboardInteractionInvitation: nextKeyboardInvitation
|
|
32648
|
+
});
|
|
32649
|
+
}
|
|
32650
|
+
return state;
|
|
32651
|
+
}
|
|
32652
|
+
function doChangeKeyboardInvitationVisibility(state, action) {
|
|
32653
|
+
if (state.type !== "point") {
|
|
32654
|
+
return state;
|
|
32655
|
+
}
|
|
32656
|
+
if (state.numPoints === "unlimited") {
|
|
32657
|
+
return _extends({}, state, {
|
|
32658
|
+
showKeyboardInteractionInvitation: action.shouldShow,
|
|
32659
|
+
hasBeenInteractedWith: true
|
|
32660
|
+
});
|
|
32661
|
+
}
|
|
32662
|
+
return state;
|
|
32663
|
+
}
|
|
32543
32664
|
function doMovePointInFigure(state, action) {
|
|
32544
32665
|
switch (state.type) {
|
|
32545
32666
|
case "segment":
|
|
@@ -33276,7 +33397,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
33276
33397
|
const snapTo = graph.type === "polygon" ? graph.snapTo : null;
|
|
33277
33398
|
const showAngles = graph.type === "polygon" ? graph.showAngles : null;
|
|
33278
33399
|
const showSides = graph.type === "polygon" ? graph.showSides : null;
|
|
33279
|
-
const startCoords = "startCoords" in graph ? graph.startCoords :
|
|
33400
|
+
const startCoords = "startCoords" in graph ? graph.startCoords : undefined;
|
|
33280
33401
|
const originalPropsRef = useRef(props);
|
|
33281
33402
|
const latestPropsRef = useLatestRef(props);
|
|
33282
33403
|
useEffect(() => {
|
|
@@ -34600,7 +34721,7 @@ class InteractiveGraph extends React.Component {
|
|
|
34600
34721
|
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
34601
34722
|
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
34602
34723
|
return /*#__PURE__*/React.createElement(StatefulMafsGraph, _extends({}, this.props, {
|
|
34603
|
-
|
|
34724
|
+
flags: (_this$props$apiOption2 = this.props.apiOptions) == null ? void 0 : _this$props$apiOption2.flags,
|
|
34604
34725
|
ref: this.mafsRef,
|
|
34605
34726
|
gridStep: gridStep,
|
|
34606
34727
|
snapStep: snapStep,
|
|
@@ -35211,7 +35332,7 @@ function shouldUseMafs(mafsFlags, graph) {
|
|
|
35211
35332
|
const staticTransform$3 = _.identity;
|
|
35212
35333
|
var InteractiveGraph$1 = {
|
|
35213
35334
|
name: "interactive-graph",
|
|
35214
|
-
displayName: "Interactive graph",
|
|
35335
|
+
displayName: "Interactive graph (Assessments only)",
|
|
35215
35336
|
widget: InteractiveGraph,
|
|
35216
35337
|
staticTransform: staticTransform$3
|
|
35217
35338
|
};
|
|
@@ -37147,11 +37268,6 @@ var LabelImage$1 = {
|
|
|
37147
37268
|
isLintable: true
|
|
37148
37269
|
};
|
|
37149
37270
|
|
|
37150
|
-
let Layout = /*#__PURE__*/function (Layout) {
|
|
37151
|
-
Layout["HORIZONTAL"] = "horizontal";
|
|
37152
|
-
Layout["VERTICAL"] = "vertical";
|
|
37153
|
-
return Layout;
|
|
37154
|
-
}({});
|
|
37155
37271
|
var ItemState = /*#__PURE__*/function (ItemState) {
|
|
37156
37272
|
ItemState["STATIC"] = "static";
|
|
37157
37273
|
ItemState["DRAGGING"] = "dragging";
|
|
@@ -37178,7 +37294,7 @@ class Placeholder extends React.Component {
|
|
|
37178
37294
|
const {
|
|
37179
37295
|
layout
|
|
37180
37296
|
} = this.props;
|
|
37181
|
-
const className = css(styles$d.card, styles$d.placeholder, layout ===
|
|
37297
|
+
const className = css(styles$d.card, styles$d.placeholder, layout === "horizontal" && styles$d.horizontalCard);
|
|
37182
37298
|
const style = {
|
|
37183
37299
|
width: this.props.width,
|
|
37184
37300
|
height: this.props.height
|
|
@@ -37382,7 +37498,7 @@ class Draggable extends React.Component {
|
|
|
37382
37498
|
// We need to keep backwards compatbility with rules specified directly
|
|
37383
37499
|
// in CSS. Hence the hacky tacking on of manual classNames.
|
|
37384
37500
|
// See sortable.less for details.
|
|
37385
|
-
let className = css(styles$d.card, styles$d.draggable, layout ===
|
|
37501
|
+
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";
|
|
37386
37502
|
if (!includePadding) {
|
|
37387
37503
|
className += " perseus-sortable-draggable-unpadded";
|
|
37388
37504
|
}
|
|
@@ -37511,7 +37627,7 @@ class Sortable extends React.Component {
|
|
|
37511
37627
|
if (constraints != null && constraints.width) {
|
|
37512
37628
|
// Items must be at least as wide as the specified constraint
|
|
37513
37629
|
syncWidth = _.max(widths.concat(constraints.width));
|
|
37514
|
-
} else if (layout ===
|
|
37630
|
+
} else if (layout === "vertical") {
|
|
37515
37631
|
// Sync widths to get a clean column
|
|
37516
37632
|
syncWidth = _.max(widths);
|
|
37517
37633
|
}
|
|
@@ -37519,7 +37635,7 @@ class Sortable extends React.Component {
|
|
|
37519
37635
|
if (constraints != null && constraints.height) {
|
|
37520
37636
|
// Items must be at least as high as the specified constraint
|
|
37521
37637
|
syncHeight = _.max(heights.concat(constraints.height));
|
|
37522
|
-
} else if (layout ===
|
|
37638
|
+
} else if (layout === "horizontal") {
|
|
37523
37639
|
// Sync widths to get a clean row
|
|
37524
37640
|
syncHeight = _.max(heights);
|
|
37525
37641
|
}
|
|
@@ -37601,7 +37717,7 @@ class Sortable extends React.Component {
|
|
|
37601
37717
|
|
|
37602
37718
|
// @ts-expect-error - TS2551 - Property 'splice' does not exist on type 'readonly SortableItem[]'. Did you mean 'slice'?
|
|
37603
37719
|
items.splice(currentIndex, 1);
|
|
37604
|
-
if (this.props.layout ===
|
|
37720
|
+
if (this.props.layout === "horizontal") {
|
|
37605
37721
|
// @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'. | TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
|
|
37606
37722
|
const midWidth = $draggable.offset().left - $sortable.offset().left;
|
|
37607
37723
|
let sumWidth = 0;
|
|
@@ -37716,19 +37832,19 @@ class Sortable extends React.Component {
|
|
|
37716
37832
|
// We need to keep backwards compatbility with rules specified directly
|
|
37717
37833
|
// in CSS. See sortable.less for details.
|
|
37718
37834
|
const className = css(styles$d.sortable) + " perseus-sortable";
|
|
37719
|
-
const syncWidth = ((_this$props$constrain = this.props.constraints) == null ? void 0 : _this$props$constrain.width) || layout ===
|
|
37720
|
-
const syncHeight = ((_this$props$constrain2 = this.props.constraints) == null ? void 0 : _this$props$constrain2.height) || layout ===
|
|
37835
|
+
const syncWidth = ((_this$props$constrain = this.props.constraints) == null ? void 0 : _this$props$constrain.width) || layout === "vertical";
|
|
37836
|
+
const syncHeight = ((_this$props$constrain2 = this.props.constraints) == null ? void 0 : _this$props$constrain2.height) || layout === "horizontal";
|
|
37721
37837
|
_.each(this.state.items, function (item, i, items) {
|
|
37722
37838
|
const isLast = i === items.length - 1;
|
|
37723
37839
|
const isStatic = item.state === ItemState.STATIC || item.state === ItemState.DISABLED;
|
|
37724
37840
|
let margin;
|
|
37725
37841
|
|
|
37726
37842
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
37727
|
-
if (this.props.layout ===
|
|
37843
|
+
if (this.props.layout === "horizontal") {
|
|
37728
37844
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
37729
37845
|
margin = "0 " + this.props.margin + "px 0 0"; // right
|
|
37730
37846
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
37731
|
-
} else if (this.props.layout ===
|
|
37847
|
+
} else if (this.props.layout === "vertical") {
|
|
37732
37848
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
37733
37849
|
margin = "0 0 " + this.props.margin + "px 0"; // bottom
|
|
37734
37850
|
}
|
|
@@ -37802,7 +37918,7 @@ class Sortable extends React.Component {
|
|
|
37802
37918
|
}
|
|
37803
37919
|
}
|
|
37804
37920
|
Sortable.defaultProps = {
|
|
37805
|
-
layout:
|
|
37921
|
+
layout: "horizontal",
|
|
37806
37922
|
padding: true,
|
|
37807
37923
|
disabled: false,
|
|
37808
37924
|
constraints: {},
|
|
@@ -37861,7 +37977,6 @@ const styles$d = StyleSheet.create({
|
|
|
37861
37977
|
}
|
|
37862
37978
|
});
|
|
37863
37979
|
|
|
37864
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
37865
37980
|
const {
|
|
37866
37981
|
shuffle: shuffle$1,
|
|
37867
37982
|
seededRNG
|
|
@@ -37995,7 +38110,7 @@ class Matcher extends React.Component {
|
|
|
37995
38110
|
className: css(styles$c.column)
|
|
37996
38111
|
}, /*#__PURE__*/React.createElement(Sortable, {
|
|
37997
38112
|
options: left,
|
|
37998
|
-
layout:
|
|
38113
|
+
layout: "vertical",
|
|
37999
38114
|
padding: this.props.padding,
|
|
38000
38115
|
disabled: !this.props.orderMatters,
|
|
38001
38116
|
constraints: constraints,
|
|
@@ -38008,11 +38123,9 @@ class Matcher extends React.Component {
|
|
|
38008
38123
|
ref: "left"
|
|
38009
38124
|
})), /*#__PURE__*/React.createElement("td", {
|
|
38010
38125
|
className: css(styles$c.column, styles$c.columnRight)
|
|
38011
|
-
}, /*#__PURE__*/React.createElement(Sortable
|
|
38012
|
-
// @ts-expect-error - TS2322 - Type 'readonly unknown[]' is not assignable to type 'readonly string[]'.
|
|
38013
|
-
, {
|
|
38126
|
+
}, /*#__PURE__*/React.createElement(Sortable, {
|
|
38014
38127
|
options: right,
|
|
38015
|
-
layout:
|
|
38128
|
+
layout: "vertical",
|
|
38016
38129
|
padding: this.props.padding,
|
|
38017
38130
|
constraints: constraints,
|
|
38018
38131
|
onMeasure: this.onMeasureRight,
|
|
@@ -38026,18 +38139,6 @@ class Matcher extends React.Component {
|
|
|
38026
38139
|
}
|
|
38027
38140
|
}
|
|
38028
38141
|
Matcher.contextType = PerseusI18nContext;
|
|
38029
|
-
Matcher.propTypes = {
|
|
38030
|
-
apiOptions: ApiOptions.propTypes,
|
|
38031
|
-
labels: PropTypes.array,
|
|
38032
|
-
left: PropTypes.array,
|
|
38033
|
-
onChange: PropTypes.func,
|
|
38034
|
-
orderMatters: PropTypes.bool,
|
|
38035
|
-
padding: PropTypes.bool,
|
|
38036
|
-
problemNum: PropTypes.number,
|
|
38037
|
-
right: PropTypes.array,
|
|
38038
|
-
trackInteraction: PropTypes.func.isRequired,
|
|
38039
|
-
linterContext: linterContextProps
|
|
38040
|
-
};
|
|
38041
38142
|
Matcher.defaultProps = {
|
|
38042
38143
|
left: [],
|
|
38043
38144
|
right: [],
|
|
@@ -38557,6 +38658,7 @@ const staticTransform$2 = editorProps => {
|
|
|
38557
38658
|
var Matrix$1 = {
|
|
38558
38659
|
name: "matrix",
|
|
38559
38660
|
displayName: "Matrix",
|
|
38661
|
+
hidden: true,
|
|
38560
38662
|
widget: Matrix,
|
|
38561
38663
|
transform: propTransform$1,
|
|
38562
38664
|
staticTransform: staticTransform$2,
|
|
@@ -38658,9 +38760,8 @@ const Measurer = createReactClass({
|
|
|
38658
38760
|
getUserInput: function () {
|
|
38659
38761
|
return {};
|
|
38660
38762
|
},
|
|
38661
|
-
simpleValidate: function (
|
|
38662
|
-
|
|
38663
|
-
return Measurer.validate(this.getUserInput(), rubric);
|
|
38763
|
+
simpleValidate: function () {
|
|
38764
|
+
return noopValidator(1);
|
|
38664
38765
|
},
|
|
38665
38766
|
focus: $.noop,
|
|
38666
38767
|
render: function () {
|
|
@@ -38689,14 +38790,7 @@ const Measurer = createReactClass({
|
|
|
38689
38790
|
}
|
|
38690
38791
|
});
|
|
38691
38792
|
_.extend(Measurer, {
|
|
38692
|
-
validate:
|
|
38693
|
-
return {
|
|
38694
|
-
type: "points",
|
|
38695
|
-
earned: 1,
|
|
38696
|
-
total: 1,
|
|
38697
|
-
message: null
|
|
38698
|
-
};
|
|
38699
|
-
}
|
|
38793
|
+
validate: noopValidator(1)
|
|
38700
38794
|
});
|
|
38701
38795
|
const propUpgrades = {
|
|
38702
38796
|
"1": v0props => {
|
|
@@ -38713,6 +38807,7 @@ const propUpgrades = {
|
|
|
38713
38807
|
var Measurer$1 = {
|
|
38714
38808
|
name: "measurer",
|
|
38715
38809
|
displayName: "Measurer",
|
|
38810
|
+
hidden: true,
|
|
38716
38811
|
widget: Measurer,
|
|
38717
38812
|
version: {
|
|
38718
38813
|
major: 1,
|
|
@@ -39587,28 +39682,17 @@ Molecule.contextType = PerseusI18nContext;
|
|
|
39587
39682
|
class MoleculeWidget extends React.Component {
|
|
39588
39683
|
constructor(...args) {
|
|
39589
39684
|
super(...args);
|
|
39590
|
-
this.simpleValidate = () => {
|
|
39591
|
-
return {
|
|
39592
|
-
type: "points",
|
|
39593
|
-
earned: 0,
|
|
39594
|
-
total: 0,
|
|
39595
|
-
message: null
|
|
39596
|
-
};
|
|
39597
|
-
};
|
|
39598
39685
|
this.getUserInput = () => {
|
|
39599
39686
|
return [];
|
|
39600
39687
|
};
|
|
39601
|
-
|
|
39602
|
-
|
|
39603
|
-
|
|
39604
|
-
|
|
39605
|
-
|
|
39606
|
-
|
|
39607
|
-
|
|
39608
|
-
|
|
39609
|
-
message: null
|
|
39610
|
-
};
|
|
39611
|
-
};
|
|
39688
|
+
}
|
|
39689
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
39690
|
+
simpleValidate() {
|
|
39691
|
+
return noopValidator();
|
|
39692
|
+
}
|
|
39693
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
39694
|
+
validate() {
|
|
39695
|
+
return noopValidator();
|
|
39612
39696
|
}
|
|
39613
39697
|
render() {
|
|
39614
39698
|
return /*#__PURE__*/React.createElement(Molecule, {
|
|
@@ -40214,15 +40298,11 @@ class PlaceholderCard extends React.Component {
|
|
|
40214
40298
|
}, /*#__PURE__*/React.createElement("div", {
|
|
40215
40299
|
className: "card placeholder",
|
|
40216
40300
|
style: {
|
|
40217
|
-
height: this.props.
|
|
40301
|
+
height: this.props.width
|
|
40218
40302
|
}
|
|
40219
40303
|
}));
|
|
40220
40304
|
}
|
|
40221
40305
|
}
|
|
40222
|
-
PlaceholderCard.propTypes = {
|
|
40223
|
-
width: PropTypes.number.isRequired,
|
|
40224
|
-
height: PropTypes.number.isRequired
|
|
40225
|
-
};
|
|
40226
40306
|
class DragHintCard extends React.Component {
|
|
40227
40307
|
render() {
|
|
40228
40308
|
return /*#__PURE__*/React.createElement("div", {
|
|
@@ -40232,10 +40312,6 @@ class DragHintCard extends React.Component {
|
|
|
40232
40312
|
}));
|
|
40233
40313
|
}
|
|
40234
40314
|
}
|
|
40235
|
-
const PropTypePosition = PropTypes.shape({
|
|
40236
|
-
left: PropTypes.number,
|
|
40237
|
-
top: PropTypes.number
|
|
40238
|
-
});
|
|
40239
40315
|
class Card extends React.Component {
|
|
40240
40316
|
constructor(...args) {
|
|
40241
40317
|
super(...args);
|
|
@@ -40316,7 +40392,7 @@ class Card extends React.Component {
|
|
|
40316
40392
|
this.props.fakeRef !== nextProps.fakeRef;
|
|
40317
40393
|
}
|
|
40318
40394
|
componentDidUpdate(prevProps, prevState) {
|
|
40319
|
-
if (this.props.animating && !prevProps.animating) {
|
|
40395
|
+
if (this.props.animating && !prevProps.animating && this.props.animateTo && this.props.startOffset) {
|
|
40320
40396
|
// If we just were changed into animating, start the animation.
|
|
40321
40397
|
// We pick the animation speed based on the distance that the card
|
|
40322
40398
|
// needs to travel. (Why sqrt? Just because it looks nice -- with a
|
|
@@ -40339,10 +40415,11 @@ class Card extends React.Component {
|
|
|
40339
40415
|
render() {
|
|
40340
40416
|
let style = {};
|
|
40341
40417
|
if (this.props.floating) {
|
|
40418
|
+
var _this$props$startOffs, _this$props$startOffs2;
|
|
40342
40419
|
style = {
|
|
40343
40420
|
position: "absolute",
|
|
40344
|
-
left: this.props.startOffset.left,
|
|
40345
|
-
top: this.props.startOffset.top
|
|
40421
|
+
left: (_this$props$startOffs = this.props.startOffset) == null ? void 0 : _this$props$startOffs.left,
|
|
40422
|
+
top: (_this$props$startOffs2 = this.props.startOffset) == null ? void 0 : _this$props$startOffs2.top
|
|
40346
40423
|
};
|
|
40347
40424
|
}
|
|
40348
40425
|
if (this.props.width) {
|
|
@@ -40352,7 +40429,7 @@ class Card extends React.Component {
|
|
|
40352
40429
|
if (this.props.stack) {
|
|
40353
40430
|
className.push("stack");
|
|
40354
40431
|
}
|
|
40355
|
-
if (this.props.floating && !this.props.animating) {
|
|
40432
|
+
if (this.props.floating && !this.props.animating && this.props.mouse && this.props.startMouse) {
|
|
40356
40433
|
className.push("dragging");
|
|
40357
40434
|
style.left += this.props.mouse.left - this.props.startMouse.left;
|
|
40358
40435
|
style.top += this.props.mouse.top - this.props.startMouse.top;
|
|
@@ -40377,28 +40454,13 @@ class Card extends React.Component {
|
|
|
40377
40454
|
}
|
|
40378
40455
|
}
|
|
40379
40456
|
Card.contextType = PerseusI18nContext;
|
|
40380
|
-
Card.propTypes = {
|
|
40381
|
-
floating: PropTypes.bool.isRequired,
|
|
40382
|
-
animating: PropTypes.bool,
|
|
40383
|
-
width: PropTypes.number,
|
|
40384
|
-
stack: PropTypes.bool,
|
|
40385
|
-
onMouseDown: PropTypes.func,
|
|
40386
|
-
onMouseMove: PropTypes.func,
|
|
40387
|
-
onMouseUp: PropTypes.func,
|
|
40388
|
-
// Used only for floating/animating cards
|
|
40389
|
-
startMouse: PropTypePosition,
|
|
40390
|
-
startOffset: PropTypePosition,
|
|
40391
|
-
animateTo: PropTypePosition,
|
|
40392
|
-
onAnimationEnd: PropTypes.func,
|
|
40393
|
-
linterContext: linterContextProps
|
|
40394
|
-
};
|
|
40395
40457
|
Card.defaultProps = {
|
|
40396
40458
|
stack: false,
|
|
40397
40459
|
animating: false,
|
|
40398
40460
|
linterContext: linterContextDefault
|
|
40399
40461
|
};
|
|
40400
40462
|
const NORMAL = "normal",
|
|
40401
|
-
HORIZONTAL
|
|
40463
|
+
HORIZONTAL = "horizontal";
|
|
40402
40464
|
class Orderer extends React.Component {
|
|
40403
40465
|
constructor(...args) {
|
|
40404
40466
|
super(...args);
|
|
@@ -40545,7 +40607,7 @@ class Orderer extends React.Component {
|
|
|
40545
40607
|
};
|
|
40546
40608
|
this.findCorrectIndex = (draggable, list) => {
|
|
40547
40609
|
// Find the correct index for a card given the current cards.
|
|
40548
|
-
const isHorizontal = this.props.layout === HORIZONTAL
|
|
40610
|
+
const isHorizontal = this.props.layout === HORIZONTAL;
|
|
40549
40611
|
// eslint-disable-next-line react/no-string-refs
|
|
40550
40612
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
40551
40613
|
const $dragList = $(ReactDOM__default.findDOMNode(this.refs.dragList));
|
|
@@ -40595,7 +40657,7 @@ class Orderer extends React.Component {
|
|
|
40595
40657
|
if (draggable == null) {
|
|
40596
40658
|
return false;
|
|
40597
40659
|
}
|
|
40598
|
-
const isHorizontal = this.props.layout === HORIZONTAL
|
|
40660
|
+
const isHorizontal = this.props.layout === HORIZONTAL,
|
|
40599
40661
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
40600
40662
|
$draggable = $(ReactDOM__default.findDOMNode(draggable)),
|
|
40601
40663
|
// eslint-disable-next-line react/no-string-refs
|
|
@@ -40767,7 +40829,7 @@ Orderer.defaultProps = {
|
|
|
40767
40829
|
options: [],
|
|
40768
40830
|
correctOptions: [],
|
|
40769
40831
|
height: NORMAL,
|
|
40770
|
-
layout: HORIZONTAL
|
|
40832
|
+
layout: HORIZONTAL,
|
|
40771
40833
|
linterContext: linterContextDefault
|
|
40772
40834
|
};
|
|
40773
40835
|
_.extend(Orderer, {
|
|
@@ -40790,6 +40852,7 @@ _.extend(Orderer, {
|
|
|
40790
40852
|
var Orderer$1 = {
|
|
40791
40853
|
name: "orderer",
|
|
40792
40854
|
displayName: "Orderer",
|
|
40855
|
+
hidden: true,
|
|
40793
40856
|
widget: Orderer,
|
|
40794
40857
|
isLintable: true
|
|
40795
40858
|
};
|
|
@@ -42593,13 +42656,9 @@ class Passage extends React.Component {
|
|
|
42593
42656
|
});
|
|
42594
42657
|
};
|
|
42595
42658
|
}
|
|
42596
|
-
|
|
42597
|
-
|
|
42598
|
-
|
|
42599
|
-
earned: 0,
|
|
42600
|
-
total: 0,
|
|
42601
|
-
message: null
|
|
42602
|
-
};
|
|
42659
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
42660
|
+
static validate() {
|
|
42661
|
+
return noopValidator();
|
|
42603
42662
|
}
|
|
42604
42663
|
componentDidMount() {
|
|
42605
42664
|
this._updateState();
|
|
@@ -42814,8 +42873,10 @@ class Passage extends React.Component {
|
|
|
42814
42873
|
getUserInput() {
|
|
42815
42874
|
return null;
|
|
42816
42875
|
}
|
|
42817
|
-
|
|
42818
|
-
|
|
42876
|
+
|
|
42877
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
42878
|
+
simpleValidate() {
|
|
42879
|
+
return noopValidator();
|
|
42819
42880
|
}
|
|
42820
42881
|
|
|
42821
42882
|
/**
|
|
@@ -42965,6 +43026,7 @@ Passage.defaultProps = {
|
|
|
42965
43026
|
var Passage$1 = {
|
|
42966
43027
|
name: "passage",
|
|
42967
43028
|
displayName: "Passage (SAT only)",
|
|
43029
|
+
hidden: true,
|
|
42968
43030
|
widget: Passage,
|
|
42969
43031
|
transform: editorProps => {
|
|
42970
43032
|
return _.pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
@@ -42976,23 +43038,22 @@ var Passage$1 = {
|
|
|
42976
43038
|
class PassageRefTarget extends React.Component {
|
|
42977
43039
|
constructor(..._args) {
|
|
42978
43040
|
super(..._args);
|
|
43041
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
42979
43042
|
this.getUserInput = () => {
|
|
42980
|
-
return
|
|
43043
|
+
return;
|
|
42981
43044
|
};
|
|
43045
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
42982
43046
|
this.change = (...args) => {
|
|
42983
43047
|
return change.apply(this, args);
|
|
42984
43048
|
};
|
|
42985
|
-
this.simpleValidate = rubric => {
|
|
42986
|
-
return PassageRefTarget.validate(this.getUserInput(), rubric);
|
|
42987
|
-
};
|
|
42988
43049
|
}
|
|
42989
|
-
|
|
42990
|
-
|
|
42991
|
-
|
|
42992
|
-
|
|
42993
|
-
|
|
42994
|
-
|
|
42995
|
-
|
|
43050
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
43051
|
+
static validate() {
|
|
43052
|
+
return noopValidator();
|
|
43053
|
+
}
|
|
43054
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
43055
|
+
simpleValidate() {
|
|
43056
|
+
return noopValidator();
|
|
42996
43057
|
}
|
|
42997
43058
|
render() {
|
|
42998
43059
|
return /*#__PURE__*/React.createElement(Renderer, {
|
|
@@ -44054,6 +44115,7 @@ const staticTransform = _.identity;
|
|
|
44054
44115
|
var Plotter$1 = {
|
|
44055
44116
|
name: "plotter",
|
|
44056
44117
|
displayName: "Plotter",
|
|
44118
|
+
hidden: true,
|
|
44057
44119
|
widget: Plotter,
|
|
44058
44120
|
staticTransform: staticTransform
|
|
44059
44121
|
};
|
|
@@ -44104,17 +44166,13 @@ var PythonProgram$1 = {
|
|
|
44104
44166
|
widget: PythonProgram
|
|
44105
44167
|
};
|
|
44106
44168
|
|
|
44107
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
44108
44169
|
const {
|
|
44109
44170
|
shuffle
|
|
44110
44171
|
} = Util;
|
|
44111
|
-
const HORIZONTAL = "horizontal";
|
|
44112
|
-
const VERTICAL = "vertical";
|
|
44113
44172
|
class Sorter extends React.Component {
|
|
44114
44173
|
constructor(...args) {
|
|
44115
44174
|
super(...args);
|
|
44116
|
-
|
|
44117
|
-
this._isMounted = void 0;
|
|
44175
|
+
this._isMounted = false;
|
|
44118
44176
|
this.state = {
|
|
44119
44177
|
changed: false
|
|
44120
44178
|
};
|
|
@@ -44188,9 +44246,7 @@ class Sorter extends React.Component {
|
|
|
44188
44246
|
const marginPx = apiOptions.isMobile ? 8 : 5;
|
|
44189
44247
|
return /*#__PURE__*/React.createElement("div", {
|
|
44190
44248
|
className: "perseus-widget-sorter perseus-clearfix"
|
|
44191
|
-
}, /*#__PURE__*/React.createElement(Sortable
|
|
44192
|
-
// @ts-expect-error - TS2322 - Type 'readonly unknown[]' is not assignable to type 'readonly string[]'.
|
|
44193
|
-
, {
|
|
44249
|
+
}, /*#__PURE__*/React.createElement(Sortable, {
|
|
44194
44250
|
options: options,
|
|
44195
44251
|
layout: this.props.layout,
|
|
44196
44252
|
margin: marginPx,
|
|
@@ -44203,20 +44259,9 @@ class Sorter extends React.Component {
|
|
|
44203
44259
|
}));
|
|
44204
44260
|
}
|
|
44205
44261
|
}
|
|
44206
|
-
Sorter.propTypes = {
|
|
44207
|
-
apiOptions: ApiOptions.propTypes,
|
|
44208
|
-
// eslint-disable-next-line react/forbid-prop-types
|
|
44209
|
-
correct: PropTypes.array,
|
|
44210
|
-
layout: PropTypes.oneOf([HORIZONTAL, VERTICAL]),
|
|
44211
|
-
onChange: PropTypes.func,
|
|
44212
|
-
padding: PropTypes.bool,
|
|
44213
|
-
problemNum: PropTypes.number,
|
|
44214
|
-
trackInteraction: PropTypes.func.isRequired,
|
|
44215
|
-
linterContext: linterContextProps
|
|
44216
|
-
};
|
|
44217
44262
|
Sorter.defaultProps = {
|
|
44218
44263
|
correct: [],
|
|
44219
|
-
layout:
|
|
44264
|
+
layout: "horizontal",
|
|
44220
44265
|
padding: true,
|
|
44221
44266
|
problemNum: 0,
|
|
44222
44267
|
onChange: function () {},
|
|
@@ -44232,7 +44277,6 @@ var Sorter$1 = {
|
|
|
44232
44277
|
const {
|
|
44233
44278
|
assert
|
|
44234
44279
|
} = InteractiveUtil;
|
|
44235
|
-
|
|
44236
44280
|
/* Input handling: Maps a (row, column) pair to a unique ref used by React,
|
|
44237
44281
|
* and extracts (row, column) pairs from input paths, used to allow outsiders
|
|
44238
44282
|
* to focus, blur, set input values, etc. */
|
|
@@ -44419,16 +44463,6 @@ class Table extends React.Component {
|
|
|
44419
44463
|
}
|
|
44420
44464
|
}
|
|
44421
44465
|
Table.contextType = PerseusI18nContext;
|
|
44422
|
-
Table.propTypes = {
|
|
44423
|
-
answers: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
|
|
44424
|
-
editableHeaders: PropTypes.bool,
|
|
44425
|
-
// The editor to use when editableHeaders is enabled
|
|
44426
|
-
Editor: PropTypes.func,
|
|
44427
|
-
headers: PropTypes.arrayOf(PropTypes.string),
|
|
44428
|
-
keypadElement: keypadElementPropType,
|
|
44429
|
-
trackInteraction: PropTypes.func.isRequired,
|
|
44430
|
-
linterContext: linterContextProps
|
|
44431
|
-
};
|
|
44432
44466
|
Table.defaultProps = function () {
|
|
44433
44467
|
const defaultRows = 4;
|
|
44434
44468
|
const defaultColumns = 1;
|
|
@@ -44616,9 +44650,6 @@ class Video extends React.Component {
|
|
|
44616
44650
|
this.getUserInput = () => {
|
|
44617
44651
|
return null;
|
|
44618
44652
|
};
|
|
44619
|
-
this.simpleValidate = rubric => {
|
|
44620
|
-
return Video.validate(null, rubric);
|
|
44621
|
-
};
|
|
44622
44653
|
this.change = (...args) => {
|
|
44623
44654
|
// @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
|
|
44624
44655
|
return change.apply(this, args);
|
|
@@ -44628,14 +44659,13 @@ class Video extends React.Component {
|
|
|
44628
44659
|
* This is the widget's grading function.
|
|
44629
44660
|
* Points for videos are tallied by the embedded video itself, in the case
|
|
44630
44661
|
* of Khan Academy videos.
|
|
44631
|
-
*/
|
|
44632
|
-
static validate(
|
|
44633
|
-
return
|
|
44634
|
-
|
|
44635
|
-
|
|
44636
|
-
|
|
44637
|
-
|
|
44638
|
-
};
|
|
44662
|
+
*/ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
44663
|
+
static validate() {
|
|
44664
|
+
return noopValidator();
|
|
44665
|
+
}
|
|
44666
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
44667
|
+
simpleValidate() {
|
|
44668
|
+
return noopValidator();
|
|
44639
44669
|
}
|
|
44640
44670
|
render() {
|
|
44641
44671
|
const {
|
|
@@ -44740,7 +44770,7 @@ const init = function init(options) {
|
|
|
44740
44770
|
|
|
44741
44771
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44742
44772
|
const libName = "@khanacademy/perseus";
|
|
44743
|
-
const libVersion = "
|
|
44773
|
+
const libVersion = "34.1.0";
|
|
44744
44774
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44745
44775
|
|
|
44746
44776
|
const apiVersion = {
|
|
@@ -48203,5 +48233,5 @@ const EditorJsonify = {
|
|
|
48203
48233
|
}
|
|
48204
48234
|
};
|
|
48205
48235
|
|
|
48206
|
-
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,
|
|
48236
|
+
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 };
|
|
48207
48237
|
//# sourceMappingURL=index.js.map
|