@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.
Files changed (52) hide show
  1. package/dist/components/sortable.d.ts +1 -4
  2. package/dist/es/index.js +721 -691
  3. package/dist/es/index.js.map +1 -1
  4. package/dist/index.d.ts +0 -1
  5. package/dist/index.js +736 -692
  6. package/dist/index.js.map +1 -1
  7. package/dist/perseus-api.d.ts +2 -0
  8. package/dist/perseus-types.d.ts +3 -1
  9. package/dist/server-item-renderer.d.ts +2 -0
  10. package/dist/types.d.ts +1 -1
  11. package/dist/util/answer-types.d.ts +1 -1
  12. package/dist/widgets/__shared__/noop-validator.d.ts +11 -0
  13. package/dist/widgets/categorizer/categorizer-validator.d.ts +5 -0
  14. package/dist/widgets/categorizer/categorizer.d.ts +1 -2
  15. package/dist/widgets/categorizer/categorizer.types.d.ts +5 -0
  16. package/dist/widgets/cs-program/cs-program.d.ts +23 -17
  17. package/dist/widgets/definition/definition.d.ts +3 -3
  18. package/dist/widgets/dropdown/dropdown-validator.d.ts +4 -0
  19. package/dist/widgets/dropdown/dropdown.d.ts +1 -5
  20. package/dist/widgets/dropdown/dropdown.types.d.ts +5 -0
  21. package/dist/widgets/explanation/explanation.d.ts +3 -5
  22. package/dist/widgets/expression/expression-validator.d.ts +5 -0
  23. package/dist/widgets/expression/expression.d.ts +13 -6
  24. package/dist/widgets/expression/expression.types.d.ts +3 -0
  25. package/dist/widgets/expression/get-decimal-separator.d.ts +5 -0
  26. package/dist/widgets/iframe/iframe.d.ts +24 -15
  27. package/dist/widgets/image/image.d.ts +3 -3
  28. package/dist/widgets/input-number/input-number-validator.d.ts +41 -0
  29. package/dist/widgets/input-number/input-number.d.ts +1 -1
  30. package/dist/widgets/input-number/input-number.types.d.ts +2 -0
  31. package/dist/widgets/interaction/interaction.d.ts +3 -3
  32. package/dist/widgets/interactive-graphs/graph-locked-labels-layer.d.ts +2 -0
  33. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +7 -0
  34. package/dist/widgets/interactive-graphs/mafs-graph.d.ts +2 -1
  35. package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +16 -1
  36. package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +2 -1
  37. package/dist/widgets/interactive-graphs/types.d.ts +3 -0
  38. package/dist/widgets/label-image/label-image.d.ts +4 -0
  39. package/dist/widgets/matcher/matcher.d.ts +17 -46
  40. package/dist/widgets/molecule/molecule.d.ts +2 -2
  41. package/dist/widgets/numeric-input/numeric-input-validator.d.ts +6 -0
  42. package/dist/widgets/numeric-input/numeric-input.d.ts +1 -5
  43. package/dist/widgets/numeric-input/numeric-input.types.d.ts +5 -0
  44. package/dist/widgets/orderer/orderer.d.ts +6 -5
  45. package/dist/widgets/passage/passage.d.ts +3 -3
  46. package/dist/widgets/passage-ref/passage-ref.d.ts +3 -3
  47. package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +2 -2
  48. package/dist/widgets/sorter/sorter.d.ts +18 -44
  49. package/dist/widgets/table/table.d.ts +20 -24
  50. package/dist/widgets/video/video.d.ts +3 -4
  51. package/package.json +1 -1
  52. 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, linterContextProps } from '@khanacademy/perseus-linter';
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 = Expression.validate(this.getUserInput(), rubric, onInputError || function () {}, this.context.strings, this.context.locale);
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
- //#region Previously a class extension
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
- const options = _.clone(rubric);
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 Peresus to provide these facilities through
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 = Expression.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, some delegate to
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 InputNumber.validate(this.getUserInput(), rubric, this.context.strings, onInputError);
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
- if (rubric.answerType == null) {
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 NumericInput.validate(this.getUserInput(), rubric, this.context.strings);
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
- const defaultAnswerForms = answerFormButtons.map(e => e["value"])
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
- static validate(userInput, rubric) {
22456
- return {
22457
- type: "points",
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
- /* Free implementation of getUserInput. This should be used sparingly, since it
24410
- * just returns all the widget's props rather than picking out those which were
24411
- * input by the user.
24412
- */
24413
- const WidgetJsonifyDeprecated = {
24414
- getUserInput: function () {
24415
- // Omit props that get passed to all widgets
24416
- // @ts-expect-error - TS2339 - Property 'props' does not exist on type '{ readonly getUserInput: () => any; readonly getUserInputFromProps: (props: any) => any; }'.
24417
- return _.omit(this.props, denylist);
24418
- },
24419
- // Static version of `WidgetJsonifyDeprecated.getUserInput`
24420
- getUserInputFromProps: function (props) {
24421
- // Omit props that get passed to all widgets
24422
- return _.omit(props, denylist);
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 Categorizer.validate(this.getUserInput(), rubric, this.context.strings);
24475
+ return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
24442
24476
  };
24443
24477
  }
24444
24478
  static validate(userInput, rubric, strings) {
24445
- let completed = true;
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 WidgetJsonifyDeprecated.getUserInputFromProps(props);
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
- static validate(userInput, rubric) {
24862
- return {
24863
- type: "points",
24864
- earned: 0,
24865
- total: 0,
24866
- message: null
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 DeprecatedStandin.validate(this.getUserInput(), rubric);
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 Dropdown.validate(this.getUserInput(), rubric);
25011
+ return dropdownValidator(this.getUserInput(), rubric);
24996
25012
  };
24997
25013
  }
24998
25014
  static validate(userInput, rubric) {
24999
- const selected = userInput.value;
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
- static validate(userInput, rubric) {
25095
- return {
25096
- type: "points",
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: false,
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
- static validate(userInput, rubric) {
27269
- return {
27270
- type: "points",
27271
- earned: 0,
27272
- total: 0,
27273
- message: null
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
- static validate(state, rubric) {
27595
- return {
27596
- type: "points",
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
- // TODO(LEMS-2271): Add support for labels within
29676
- // other locked figure types
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
- if (event.key === "Backspace") {
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.showLabelsFlag && props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
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
- })))))), renderGraphControls({
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 => /*#__PURE__*/React.createElement(View, {
32021
- style: {
32022
- flexDirection: "row",
32023
- width: props.width
32024
- }
32025
- }, props.state.showRemovePointButton && props.state.focusedPointIndex !== null && /*#__PURE__*/React.createElement(Button, {
32026
- id: REMOVE_BUTTON_ID,
32027
- kind: "secondary",
32028
- color: "destructive",
32029
- tabIndex: -1,
32030
- style: {
32031
- width: "100%",
32032
- marginLeft: "20px"
32033
- },
32034
- onClick: event => {
32035
- props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));
32036
- }
32037
- }, "Remove Point"));
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 : null;
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
- showLabelsFlag: (_this$props$apiOption2 = this.props.apiOptions) == null || (_this$props$apiOption2 = _this$props$apiOption2.flags) == null || (_this$props$apiOption2 = _this$props$apiOption2["mafs"]) == null ? void 0 : _this$props$apiOption2["interactive-graph-locked-features-labels"],
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 === Layout.HORIZONTAL && styles$d.horizontalCard);
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 === Layout.HORIZONTAL && styles$d.horizontalCard, layout === 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";
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 === Layout.VERTICAL) {
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 === Layout.HORIZONTAL) {
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 === Layout.HORIZONTAL) {
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 === Layout.VERTICAL;
37720
- const syncHeight = ((_this$props$constrain2 = this.props.constraints) == null ? void 0 : _this$props$constrain2.height) || layout === Layout.HORIZONTAL;
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 === Layout.HORIZONTAL) {
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 === Layout.VERTICAL) {
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: Layout.HORIZONTAL,
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: Layout.VERTICAL,
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: Layout.VERTICAL,
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 (rubric) {
38662
- // TODO(joel) - I don't understand how this is useful!
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: function (state, rubric) {
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
- this.validate = (state, rubric) => {
39602
- // TODO(colin): this is here as part of the interface for a component.
39603
- // Figure out if there is something more appropriate that this should
39604
- // return.
39605
- return {
39606
- type: "points",
39607
- earned: 0,
39608
- total: 0,
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.height
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$1 = "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$1;
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$1,
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$1,
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
- static validate(state, rubric) {
42597
- return {
42598
- type: "points",
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
- simpleValidate(rubric) {
42818
- return Passage.validate(this.getUserInput(), rubric);
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 WidgetJsonifyDeprecated.getUserInput.call(this);
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
- static validate(state, rubric) {
42990
- return {
42991
- type: "points",
42992
- earned: 0,
42993
- total: 0,
42994
- message: null
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
- // @ts-expect-error - TS2564 - Property '_isMounted' has no initializer and is not definitely assigned in the constructor.
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: HORIZONTAL,
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(userInput, rubric) {
44633
- return {
44634
- type: "points",
44635
- earned: 0,
44636
- total: 0,
44637
- message: null
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 = "33.3.0";
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, WidgetJsonifyDeprecated, 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 };
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