@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/index.js CHANGED
@@ -4,7 +4,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var KAS = require('@khanacademy/kas');
6
6
  var mathInput = require('@khanacademy/math-input');
7
- var perseusCore = require('@khanacademy/perseus-core');
8
7
  var PerseusLinter = require('@khanacademy/perseus-linter');
9
8
  var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
10
9
  var Tooltip$1 = require('@khanacademy/wonder-blocks-tooltip');
@@ -19,6 +18,7 @@ var wonderBlocksPopover = require('@khanacademy/wonder-blocks-popover');
19
18
  var wonderBlocksTokens = require('@khanacademy/wonder-blocks-tokens');
20
19
  var $ = require('jquery');
21
20
  var uuid = require('uuid');
21
+ var perseusCore = require('@khanacademy/perseus-core');
22
22
  var PropTypes = require('prop-types');
23
23
  var kmath = require('@khanacademy/kmath');
24
24
  var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
@@ -845,23 +845,6 @@ var dependencies = /*#__PURE__*/Object.freeze({
845
845
  useDependencies: useDependencies
846
846
  });
847
847
 
848
- // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
849
- // getDependencies() internally... simplify usage to just using a "singleton"
850
- // which get's the injected logger.
851
- /**
852
- * Provides logging infrastructure for Perseus
853
- */
854
- const Log = {
855
- // Logs a message
856
- log: (message, extra) => {
857
- getDependencies().Log.log(message, extra);
858
- },
859
- // Logs an error with a message
860
- error: (message, kind, extra) => {
861
- getDependencies().Log.error(message, kind, extra);
862
- }
863
- };
864
-
865
848
  /**
866
849
  * These are things that widgets should exclude when serializing themselves.
867
850
  *
@@ -1304,6 +1287,23 @@ const ClassNames = {
1304
1287
  MOBILE: "perseus-mobile"
1305
1288
  };
1306
1289
 
1290
+ // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
1291
+ // getDependencies() internally... simplify usage to just using a "singleton"
1292
+ // which get's the injected logger.
1293
+ /**
1294
+ * Provides logging infrastructure for Perseus
1295
+ */
1296
+ const Log = {
1297
+ // Logs a message
1298
+ log: (message, extra) => {
1299
+ getDependencies().Log.log(message, extra);
1300
+ },
1301
+ // Logs an error with a message
1302
+ error: (message, kind, extra) => {
1303
+ getDependencies().Log.error(message, kind, extra);
1304
+ }
1305
+ };
1306
+
1307
1307
  const KhanMath = {
1308
1308
  // Simplify formulas before display
1309
1309
  cleanMath: function (expr) {
@@ -2219,6 +2219,176 @@ const KhanAnswerTypes = {
2219
2219
  }
2220
2220
  };
2221
2221
 
2222
+ /**
2223
+ * Get the character used for separating decimals.
2224
+ */
2225
+ const getDecimalSeparator = locale => {
2226
+ switch (locale) {
2227
+ // TODO(somewhatabstract): Remove this when Chrome supports the `ka`
2228
+ // locale properly.
2229
+ // https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
2230
+ //
2231
+ // Supported locales in Chrome:
2232
+ // https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
2233
+ case "ka":
2234
+ return ",";
2235
+ default:
2236
+ const numberWithDecimalSeparator = 1.1;
2237
+ // TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
2238
+ // support Safari 12.
2239
+ const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
2240
+ // 0x661 is ARABIC-INDIC DIGIT ONE
2241
+ // 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
2242
+ .match(/[^\d\u0661\u06F1]/);
2243
+ return match?.[0] ?? ".";
2244
+ }
2245
+ };
2246
+
2247
+ /* Content creators input a list of answers which are matched from top to
2248
+ * bottom. The intent is that they can include spcific solutions which should
2249
+ * be graded as correct or incorrect (or ungraded!) first, then get more
2250
+ * general.
2251
+ *
2252
+ * We iterate through each answer, trying to match it with the user's input
2253
+ * using the following angorithm:
2254
+ * - Try to parse the user's input. If it doesn't parse then return "not
2255
+ * graded".
2256
+ * - For each answer:
2257
+ * ~ Try to validate the user's input against the answer. The answer is
2258
+ * expected to parse.
2259
+ * ~ If the user's input validates (the validator judges it "correct"), we've
2260
+ * matched and can stop considering answers.
2261
+ * - If there were no matches or the matching answer is considered "ungraded",
2262
+ * show the user an error. TODO(joel) - what error?
2263
+ * - Otherwise, pass through the resulting points and message.
2264
+ */
2265
+ function expressionValidator(userInput, rubric) {
2266
+ let onInputError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
2267
+ let strings = arguments.length > 3 ? arguments[3] : undefined;
2268
+ let locale = arguments.length > 4 ? arguments[4] : undefined;
2269
+ const options = ___default["default"].clone(rubric);
2270
+ ___default["default"].extend(options, {
2271
+ decimal_separator: getDecimalSeparator(locale)
2272
+ });
2273
+ const createValidator = answer => {
2274
+ // We give options to KAS.parse here because it is parsing the
2275
+ // solution answer, not the student answer, and we don't want a
2276
+ // solution to work if the student is using a different language
2277
+ // (different from the content creation language, ie. English).
2278
+ const expression = KAS__namespace.parse(answer.value, rubric);
2279
+ // An answer may not be parsed if the expression was defined
2280
+ // incorrectly. For example if the answer is using a symbol defined
2281
+ // in the function variables list for the expression.
2282
+ if (!expression.parsed) {
2283
+ /* c8 ignore next */
2284
+ Log.error("Unable to parse solution answer for expression", perseusCore.Errors.InvalidInput, {
2285
+ loggedMetadata: {
2286
+ rubric: JSON.stringify(rubric)
2287
+ }
2288
+ });
2289
+ return null;
2290
+ }
2291
+ return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
2292
+ simplify: answer.simplify,
2293
+ form: answer.form
2294
+ }), strings);
2295
+ };
2296
+
2297
+ // Find the first answer form that matches the user's input and that
2298
+ // is considered correct. Also, track whether the input is
2299
+ // considered "empty" for all answer forms, and keep the validation
2300
+ // result for the first answer form for which the user's input was
2301
+ // considered "ungraded".
2302
+ // (Terminology reminder: the answer forms are provided by the
2303
+ // assessment items; they are not the user's input. Each one might
2304
+ // represent a correct answer, an incorrect one (if the exercise
2305
+ // creator has predicted certain common wrong answers and wants to
2306
+ // provide guidance via a message), or an ungraded one (same idea,
2307
+ // but without giving the user an incorrect mark for the question).
2308
+ let matchingAnswerForm;
2309
+ let matchMessage;
2310
+ let allEmpty = true;
2311
+ let firstUngradedResult;
2312
+ for (const answerForm of rubric.answerForms || []) {
2313
+ const validator = createValidator(answerForm);
2314
+ if (!validator) {
2315
+ continue;
2316
+ }
2317
+ const result = validator(userInput);
2318
+
2319
+ // Short-circuit as soon as the user's input matches some answer
2320
+ // (independently of whether the answer is correct)
2321
+ if (result.correct) {
2322
+ matchingAnswerForm = answerForm;
2323
+ matchMessage = result.message || "";
2324
+ break;
2325
+ }
2326
+ allEmpty = allEmpty && result.empty;
2327
+ // If this answer form is correct and the user's input is considered
2328
+ // "ungraded" for it, we'll want to keep the evaluation result for
2329
+ // later. If the user's input doesn't match any answer forms, we'll
2330
+ // show the message from this validation.
2331
+ if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
2332
+ firstUngradedResult = result;
2333
+ }
2334
+ }
2335
+
2336
+ // Now check to see if we matched any answer form at all, and if
2337
+ // we did, whether it's considered correct, incorrect, or ungraded
2338
+ if (!matchingAnswerForm) {
2339
+ if (firstUngradedResult) {
2340
+ // While we didn't directly match with any answer form, we
2341
+ // did at some point get an "ungraded" validation result,
2342
+ // which might indicate e.g. a mismatch in variable casing.
2343
+ // We'll return "invalid", which will let the user try again
2344
+ // with no penalty, and the hopefully helpful validation
2345
+ // message.
2346
+ return {
2347
+ type: "invalid",
2348
+ message: firstUngradedResult.message,
2349
+ suppressAlmostThere: firstUngradedResult.suppressAlmostThere
2350
+ };
2351
+ }
2352
+ if (allEmpty) {
2353
+ // If everything graded as empty, it's invalid.
2354
+ return {
2355
+ type: "invalid",
2356
+ message: null
2357
+ };
2358
+ }
2359
+ // We fell through all the possibilities and we're not empty,
2360
+ // so the answer is considered incorrect.
2361
+ return {
2362
+ type: "points",
2363
+ earned: 0,
2364
+ total: 1
2365
+ };
2366
+ }
2367
+ if (matchingAnswerForm.considered === "ungraded") {
2368
+ // We matched an ungraded answer form - return "invalid", which
2369
+ // will let the user try again with no penalty
2370
+ const apiResult = onInputError(null,
2371
+ // Reserved for some widget identifier
2372
+ userInput, matchMessage);
2373
+ return {
2374
+ type: "invalid",
2375
+ message: apiResult === false ? null : matchMessage
2376
+ };
2377
+ }
2378
+ // We matched a graded answer form, so we can now tell the user
2379
+ // whether their input was correct or incorrect, and hand out
2380
+ // points accordingly
2381
+ // TODO(eater): Seems silly to translate result to this
2382
+ // invalid/points thing and immediately translate it back in
2383
+ // ItemRenderer.scoreInput()
2384
+ return {
2385
+ type: "points",
2386
+ earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
2387
+ total: 1,
2388
+ message: matchMessage
2389
+ };
2390
+ }
2391
+
2222
2392
  // Map of international operator names to their English equivalents
2223
2393
  const englishOperators = {
2224
2394
  arctg: "arctan",
@@ -2245,181 +2415,18 @@ const deriveKeypadVersion = apiOptions => {
2245
2415
  // can remove this `virtualKeypadVersion` field entirely.
2246
2416
  return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : "MATH_INPUT_KEYPAD_V2";
2247
2417
  };
2248
- /**
2249
- * Get the character used for separating decimals.
2250
- */
2251
- const getDecimalSeparator = locale => {
2252
- switch (locale) {
2253
- // TODO(somewhatabstract): Remove this when Chrome supports the `ka`
2254
- // locale properly.
2255
- // https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
2256
- //
2257
- // Supported locales in Chrome:
2258
- // https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
2259
- case "ka":
2260
- return ",";
2261
- default:
2262
- const numberWithDecimalSeparator = 1.1;
2263
- // TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
2264
- // support Safari 12.
2265
- const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
2266
- // 0x661 is ARABIC-INDIC DIGIT ONE
2267
- // 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
2268
- .match(/[^\d\u0661\u06F1]/);
2269
- return match?.[0] ?? ".";
2270
- }
2271
- };
2272
-
2273
2418
  // The new, MathQuill input expression widget
2274
2419
  class Expression extends React__namespace.Component {
2275
2420
  static contextType = PerseusI18nContext;
2276
2421
  _textareaId = `expression_textarea_${Date.now()}`;
2277
2422
  _isMounted = false;
2278
2423
 
2279
- //#region Previously a class extension
2280
- /* Content creators input a list of answers which are matched from top to
2281
- * bottom. The intent is that they can include spcific solutions which should
2282
- * be graded as correct or incorrect (or ungraded!) first, then get more
2283
- * general.
2284
- *
2285
- * We iterate through each answer, trying to match it with the user's input
2286
- * using the following angorithm:
2287
- * - Try to parse the user's input. If it doesn't parse then return "not
2288
- * graded".
2289
- * - For each answer:
2290
- * ~ Try to validate the user's input against the answer. The answer is
2291
- * expected to parse.
2292
- * ~ If the user's input validates (the validator judges it "correct"), we've
2293
- * matched and can stop considering answers.
2294
- * - If there were no matches or the matching answer is considered "ungraded",
2295
- * show the user an error. TODO(joel) - what error?
2296
- * - Otherwise, pass through the resulting points and message.
2297
- */
2424
+ // TODO remove this in favor of just using expressionValidator
2298
2425
  static validate(userInput, rubric) {
2299
2426
  let onInputError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
2300
2427
  let strings = arguments.length > 3 ? arguments[3] : undefined;
2301
2428
  let locale = arguments.length > 4 ? arguments[4] : undefined;
2302
- const options = ___default["default"].clone(rubric);
2303
- ___default["default"].extend(options, {
2304
- decimal_separator: getDecimalSeparator(locale)
2305
- });
2306
- const createValidator = answer => {
2307
- // We give options to KAS.parse here because it is parsing the
2308
- // solution answer, not the student answer, and we don't want a
2309
- // solution to work if the student is using a different language
2310
- // (different from the content creation language, ie. English).
2311
- const expression = KAS__namespace.parse(answer.value, rubric);
2312
- // An answer may not be parsed if the expression was defined
2313
- // incorrectly. For example if the answer is using a symbol defined
2314
- // in the function variables list for the expression.
2315
- if (!expression.parsed) {
2316
- /* c8 ignore next */
2317
- Log.error("Unable to parse solution answer for expression", perseusCore.Errors.InvalidInput, {
2318
- loggedMetadata: {
2319
- rubric: JSON.stringify(rubric)
2320
- }
2321
- });
2322
- return null;
2323
- }
2324
- return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
2325
- simplify: answer.simplify,
2326
- form: answer.form
2327
- }), strings);
2328
- };
2329
-
2330
- // Find the first answer form that matches the user's input and that
2331
- // is considered correct. Also, track whether the input is
2332
- // considered "empty" for all answer forms, and keep the validation
2333
- // result for the first answer form for which the user's input was
2334
- // considered "ungraded".
2335
- // (Terminology reminder: the answer forms are provided by the
2336
- // assessment items; they are not the user's input. Each one might
2337
- // represent a correct answer, an incorrect one (if the exercise
2338
- // creator has predicted certain common wrong answers and wants to
2339
- // provide guidance via a message), or an ungraded one (same idea,
2340
- // but without giving the user an incorrect mark for the question).
2341
- let matchingAnswerForm;
2342
- let matchMessage;
2343
- let allEmpty = true;
2344
- let firstUngradedResult;
2345
- for (const answerForm of rubric.answerForms || []) {
2346
- const validate = createValidator(answerForm);
2347
- if (!validate) {
2348
- continue;
2349
- }
2350
- const result = validate(userInput);
2351
-
2352
- // Short-circuit as soon as the user's input matches some answer
2353
- // (independently of whether the answer is correct)
2354
- if (result.correct) {
2355
- matchingAnswerForm = answerForm;
2356
- matchMessage = result.message || "";
2357
- break;
2358
- }
2359
- allEmpty = allEmpty && result.empty;
2360
- // If this answer form is correct and the user's input is considered
2361
- // "ungraded" for it, we'll want to keep the evaluation result for
2362
- // later. If the user's input doesn't match any answer forms, we'll
2363
- // show the message from this validation.
2364
- if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
2365
- firstUngradedResult = result;
2366
- }
2367
- }
2368
-
2369
- // Now check to see if we matched any answer form at all, and if
2370
- // we did, whether it's considered correct, incorrect, or ungraded
2371
- if (!matchingAnswerForm) {
2372
- if (firstUngradedResult) {
2373
- // While we didn't directly match with any answer form, we
2374
- // did at some point get an "ungraded" validation result,
2375
- // which might indicate e.g. a mismatch in variable casing.
2376
- // We'll return "invalid", which will let the user try again
2377
- // with no penalty, and the hopefully helpful validation
2378
- // message.
2379
- return {
2380
- type: "invalid",
2381
- message: firstUngradedResult.message,
2382
- suppressAlmostThere: firstUngradedResult.suppressAlmostThere
2383
- };
2384
- }
2385
- if (allEmpty) {
2386
- // If everything graded as empty, it's invalid.
2387
- return {
2388
- type: "invalid",
2389
- message: null
2390
- };
2391
- }
2392
- // We fell through all the possibilities and we're not empty,
2393
- // so the answer is considered incorrect.
2394
- return {
2395
- type: "points",
2396
- earned: 0,
2397
- total: 1
2398
- };
2399
- }
2400
- if (matchingAnswerForm.considered === "ungraded") {
2401
- // We matched an ungraded answer form - return "invalid", which
2402
- // will let the user try again with no penalty
2403
- const apiResult = onInputError(null,
2404
- // Reserved for some widget identifier
2405
- userInput, matchMessage);
2406
- return {
2407
- type: "invalid",
2408
- message: apiResult === false ? null : matchMessage
2409
- };
2410
- }
2411
- // We matched a graded answer form, so we can now tell the user
2412
- // whether their input was correct or incorrect, and hand out
2413
- // points accordingly
2414
- // TODO(eater): Seems silly to translate result to this
2415
- // invalid/points thing and immediately translate it back in
2416
- // ItemRenderer.scoreInput()
2417
- return {
2418
- type: "points",
2419
- earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
2420
- total: 1,
2421
- message: matchMessage
2422
- };
2429
+ return expressionValidator(userInput, rubric, onInputError, strings, locale);
2423
2430
  }
2424
2431
  static getUserInputFromProps(props) {
2425
2432
  return normalizeTex(props.value);
@@ -2506,7 +2513,7 @@ class Expression extends React__namespace.Component {
2506
2513
  scoring,
2507
2514
  ...rubric
2508
2515
  } = _ref;
2509
- const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {}, this.context.strings, this.context.locale);
2516
+ const score = expressionValidator(this.getUserInput(), rubric, onInputError || function () {}, this.context.strings, this.context.locale);
2510
2517
 
2511
2518
  // "scoring" is a flag that indicates when we are checking answers.
2512
2519
  // otherwise, we may just be checking validity after changes.
@@ -2780,10 +2787,10 @@ const ExpressionWithDependencies = /*#__PURE__*/React__namespace.forwardRef((pro
2780
2787
 
2781
2788
  // HACK: Propogate "static" methods onto our wrapper component.
2782
2789
  // In the future we should adjust client apps to not depend on these static
2783
- // methods and instead adjust Peresus to provide these facilities through
2790
+ // methods and instead adjust Perseus to provide these facilities through
2784
2791
  // instance methods on our Renderers.
2785
2792
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
2786
- ExpressionWithDependencies.validate = Expression.validate;
2793
+ ExpressionWithDependencies.validate = expressionValidator;
2787
2794
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
2788
2795
  ExpressionWithDependencies.getUserInputFromProps = Expression.getUserInputFromProps;
2789
2796
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
@@ -19812,9 +19819,7 @@ const isIdPathPrefix = function (prefixArray, wholeArray) {
19812
19819
 
19813
19820
  // The return type for getUserInput. Widgets have full control of what is
19814
19821
  // returned so it's not easily typed (some widgets return a scalar (string),
19815
- // some return a custom-built object, some delegate to
19816
- // `WidgetJsonifyDeprecated` which returns an object containing widget props
19817
- // (filtered by deprecated keys)).
19822
+ // some return a custom-built object
19818
19823
  /**
19819
19824
  * The Widget type represents the common API that the Renderer uses to interact
19820
19825
  * with all widgets. All widgets must implement the methods in this API, unless
@@ -21967,6 +21972,48 @@ const answerTypes = {
21967
21972
  forms: "pi"
21968
21973
  }
21969
21974
  };
21975
+ function inputNumberValidator(state, rubric, strings) {
21976
+ let onInputError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : () => {};
21977
+ if (rubric.answerType == null) {
21978
+ rubric.answerType = "number";
21979
+ }
21980
+
21981
+ // note(matthewc): this will get immediately parsed again by
21982
+ // `KhanAnswerTypes.number.convertToPredicate`, but a string is
21983
+ // expected here
21984
+ const stringValue = `${rubric.value}`;
21985
+ const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
21986
+ simplify: rubric.simplify,
21987
+ inexact: rubric.inexact || undefined,
21988
+ maxError: rubric.maxError,
21989
+ forms: answerTypes[rubric.answerType].forms
21990
+ }, strings);
21991
+
21992
+ // We may have received TeX; try to parse it before grading.
21993
+ // If `currentValue` is not TeX, this should be a no-op.
21994
+ const currentValue = ParseTex$1(state.currentValue);
21995
+ const result = val(currentValue);
21996
+
21997
+ // TODO(eater): Seems silly to translate result to this invalid/points
21998
+ // thing and immediately translate it back in ItemRenderer.scoreInput()
21999
+ if (result.empty) {
22000
+ // TODO(FEI-3867): remove null-check once we have APIOptionsInternal
22001
+ const apiResult = onInputError?.(null,
22002
+ // reserved for some widget identifier
22003
+ state.currentValue, result.message);
22004
+ return {
22005
+ type: "invalid",
22006
+ message: apiResult === false ? null : result.message
22007
+ };
22008
+ }
22009
+ return {
22010
+ type: "points",
22011
+ earned: result.correct ? 1 : 0,
22012
+ total: 1,
22013
+ message: result.message
22014
+ };
22015
+ }
22016
+
21970
22017
  const formExamples$1 = {
21971
22018
  integer: function (options, strings) {
21972
22019
  return strings.integerExample;
@@ -22010,44 +22057,7 @@ class InputNumber extends React__namespace.Component {
22010
22057
  };
22011
22058
  static validate(state, rubric, strings) {
22012
22059
  let onInputError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : () => {};
22013
- if (rubric.answerType == null) {
22014
- rubric.answerType = "number";
22015
- }
22016
-
22017
- // note(matthewc): this will get immediately parsed again by
22018
- // `KhanAnswerTypes.number.convertToPredicate`, but a string is
22019
- // expected here
22020
- const stringValue = `${rubric.value}`;
22021
- const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
22022
- simplify: rubric.simplify,
22023
- inexact: rubric.inexact || undefined,
22024
- maxError: rubric.maxError,
22025
- forms: answerTypes[rubric.answerType].forms
22026
- }, strings);
22027
-
22028
- // We may have received TeX; try to parse it before grading.
22029
- // If `currentValue` is not TeX, this should be a no-op.
22030
- const currentValue = ParseTex$1(state.currentValue);
22031
- const result = val(currentValue);
22032
-
22033
- // TODO(eater): Seems silly to translate result to this invalid/points
22034
- // thing and immediately translate it back in ItemRenderer.scoreInput()
22035
- if (result.empty) {
22036
- // TODO(FEI-3867): remove null-check once we have APIOptionsInternal
22037
- const apiResult = onInputError?.(null,
22038
- // reserved for some widget identifier
22039
- state.currentValue, result.message);
22040
- return {
22041
- type: "invalid",
22042
- message: apiResult === false ? null : result.message
22043
- };
22044
- }
22045
- return {
22046
- type: "points",
22047
- earned: result.correct ? 1 : 0,
22048
- total: 1,
22049
- message: result.message
22050
- };
22060
+ return inputNumberValidator(state, rubric, strings, onInputError);
22051
22061
  }
22052
22062
  static getUserInputFromProps(props) {
22053
22063
  return {
@@ -22121,7 +22131,7 @@ class InputNumber extends React__namespace.Component {
22121
22131
  };
22122
22132
  simpleValidate = (rubric, onInputError) => {
22123
22133
  onInputError = onInputError || function () {};
22124
- return InputNumber.validate(this.getUserInput(), rubric, this.context.strings, onInputError);
22134
+ return inputNumberValidator(this.getUserInput(), rubric, this.context.strings, onInputError);
22125
22135
  };
22126
22136
  examples = () => {
22127
22137
  const {
@@ -22254,6 +22264,130 @@ const answerFormButtons = [{
22254
22264
  value: "pi",
22255
22265
  content: "\u03C0"
22256
22266
  }];
22267
+
22268
+ // This function checks if the user inputted a percent value, parsing
22269
+ // it as a number (and maybe scaling) so that it can be graded.
22270
+ // NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
22271
+ // can accept several input forms with or without "%", the decision
22272
+ // to parse based on the presence of "%" in the input, is so that we
22273
+ // don't accidently scale the user typed value before grading, CP-930.
22274
+ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
22275
+ // If the input value is not a string ending with "%", then there's
22276
+ // nothing more to do. The value will be graded as inputted by user.
22277
+ if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
22278
+ return inputValue;
22279
+ }
22280
+ const value = parseFloat(inputValue.slice(0, -1));
22281
+ // If the input value stripped of the "%" cannot be parsed as a
22282
+ // number (the slice is not really necessary for parseFloat to work
22283
+ // if the string starts with a number) then return the original
22284
+ // input for grading.
22285
+ if (isNaN(value)) {
22286
+ return inputValue;
22287
+ }
22288
+
22289
+ // Next, if all correct answers are in the range of |0,1| then we
22290
+ // scale the user typed value. We assume this is the correct thing
22291
+ // to do since the input value ends with "%".
22292
+ if (normalizedAnswerExpected) {
22293
+ return value / 100;
22294
+ }
22295
+
22296
+ // Otherwise, we return input valuåe (number) stripped of the "%".
22297
+ return value;
22298
+ }
22299
+ function numericInputValidator(userInput, rubric, strings) {
22300
+ const defaultAnswerForms = answerFormButtons.map(e => e["value"])
22301
+ // Don't default to validating the answer as a pi answer
22302
+ // if answerForm isn't set on the answer
22303
+ // https://khanacademy.atlassian.net/browse/LC-691
22304
+ .filter(e => e !== "pi");
22305
+ const createValidator = answer => {
22306
+ const stringAnswer = `${answer.value}`;
22307
+
22308
+ // Always validate against the provided answer forms (pi, decimal, etc.)
22309
+ const validatorForms = [...(answer.answerForms ?? [])];
22310
+
22311
+ // When an answer is set to strict, we validate using ONLY
22312
+ // the provided answerForms. If strict is false, or if there
22313
+ // were no provided answer forms, we will include all
22314
+ // of the default answer forms in our validator.
22315
+ if (!answer.strict || validatorForms.length === 0) {
22316
+ validatorForms.push(...defaultAnswerForms);
22317
+ }
22318
+ return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
22319
+ message: answer.message,
22320
+ simplify: answer.status === "correct" ? answer.simplify : "optional",
22321
+ inexact: true,
22322
+ // TODO(merlob) backfill / delete
22323
+ maxError: answer.maxError,
22324
+ forms: validatorForms
22325
+ }, strings);
22326
+ };
22327
+
22328
+ // We may have received TeX; try to parse it before grading.
22329
+ // If `currentValue` is not TeX, this should be a no-op.
22330
+ const currentValue = ParseTex(userInput.currentValue);
22331
+ const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
22332
+ const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1);
22333
+
22334
+ // Look through all correct answers for one that matches either
22335
+ // precisely or approximately and return the appropriate message:
22336
+ // - if precise, return the message that the answer came with
22337
+ // - if it needs to be simplified, etc., show that message
22338
+ let result = correctAnswers.map(answer => {
22339
+ // The coefficient is an attribute of the widget
22340
+ let localValue = currentValue;
22341
+ if (rubric.coefficient) {
22342
+ if (!localValue) {
22343
+ localValue = 1;
22344
+ } else if (localValue === "-") {
22345
+ localValue = -1;
22346
+ }
22347
+ }
22348
+ const validate = createValidator(answer);
22349
+ return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
22350
+ }).find(match => match.correct || match.empty);
22351
+ if (!result) {
22352
+ // Otherwise, if the guess is not correct
22353
+ const otherAnswers = [].concat(
22354
+ // @ts-expect-error - TS2769 - No overload matches this call.
22355
+ rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong"));
22356
+
22357
+ // Look through all other answers and if one matches either
22358
+ // precisely or approximately return the answer's message
22359
+ const match = otherAnswers.find(answer => {
22360
+ const validate = createValidator(answer);
22361
+ return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
22362
+ });
22363
+ result = {
22364
+ // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
22365
+ empty: match ? match.status === "ungraded" : false,
22366
+ // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
22367
+ correct: match ? match.status === "correct" : false,
22368
+ // @ts-expect-error - TS2339 - Property 'message' does not exist on type 'never'.
22369
+ message: match ? match.message : null,
22370
+ guess: currentValue
22371
+ };
22372
+ }
22373
+
22374
+ // TODO(eater): Seems silly to translate result to this
22375
+ // invalid/points thing and immediately translate it
22376
+ // back in ItemRenderer.scoreInput()
22377
+ if (result.empty) {
22378
+ return {
22379
+ type: "invalid",
22380
+ message: result.message
22381
+ };
22382
+ }
22383
+ return {
22384
+ type: "points",
22385
+ earned: result.correct ? 1 : 0,
22386
+ total: 1,
22387
+ message: result.message
22388
+ };
22389
+ }
22390
+
22257
22391
  const formExamples = {
22258
22392
  integer: (form, strings) => strings.integerExample,
22259
22393
  proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
@@ -22309,97 +22443,7 @@ class NumericInput extends React__namespace.Component {
22309
22443
  return answerStrings[0];
22310
22444
  }
22311
22445
  static validate(userInput, rubric, strings) {
22312
- const defaultAnswerForms = answerFormButtons.map(e => e["value"])
22313
- // Don't default to validating the answer as a pi answer
22314
- // if answerForm isn't set on the answer
22315
- // https://khanacademy.atlassian.net/browse/LC-691
22316
- .filter(e => e !== "pi");
22317
- const createValidator = answer => {
22318
- const stringAnswer = `${answer.value}`;
22319
-
22320
- // Always validate against the provided answer forms (pi, decimal, etc.)
22321
- const validatorForms = [...(answer.answerForms ?? [])];
22322
-
22323
- // When an answer is set to strict, we validate using ONLY
22324
- // the provided answerForms. If strict is false, or if there
22325
- // were no provided answer forms, we will include all
22326
- // of the default answer forms in our validator.
22327
- if (!answer.strict || validatorForms.length === 0) {
22328
- validatorForms.push(...defaultAnswerForms);
22329
- }
22330
- return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
22331
- message: answer.message,
22332
- simplify: answer.status === "correct" ? answer.simplify : "optional",
22333
- inexact: true,
22334
- // TODO(merlob) backfill / delete
22335
- maxError: answer.maxError,
22336
- forms: validatorForms
22337
- }, strings);
22338
- };
22339
-
22340
- // We may have received TeX; try to parse it before grading.
22341
- // If `currentValue` is not TeX, this should be a no-op.
22342
- const currentValue = ParseTex(userInput.currentValue);
22343
- const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
22344
- const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1);
22345
-
22346
- // Look through all correct answers for one that matches either
22347
- // precisely or approximately and return the appropriate message:
22348
- // - if precise, return the message that the answer came with
22349
- // - if it needs to be simplified, etc., show that message
22350
- let result = correctAnswers.map(answer => {
22351
- // The coefficient is an attribute of the widget
22352
- let localValue = currentValue;
22353
- if (rubric.coefficient) {
22354
- if (!localValue) {
22355
- // @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'string'.
22356
- localValue = 1;
22357
- } else if (localValue === "-") {
22358
- // @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'string'.
22359
- localValue = -1;
22360
- }
22361
- }
22362
- const validate = createValidator(answer);
22363
- return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
22364
- }).find(match => match.correct || match.empty);
22365
- if (!result) {
22366
- // Otherwise, if the guess is not correct
22367
- const otherAnswers = [].concat(
22368
- // @ts-expect-error - TS2769 - No overload matches this call.
22369
- rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong"));
22370
-
22371
- // Look through all other answers and if one matches either
22372
- // precisely or approximately return the answer's message
22373
- const match = otherAnswers.find(answer => {
22374
- const validate = createValidator(answer);
22375
- return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
22376
- });
22377
- result = {
22378
- // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
22379
- empty: match ? match.status === "ungraded" : false,
22380
- // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
22381
- correct: match ? match.status === "correct" : false,
22382
- // @ts-expect-error - TS2339 - Property 'message' does not exist on type 'never'.
22383
- message: match ? match.message : null,
22384
- guess: currentValue
22385
- };
22386
- }
22387
-
22388
- // TODO(eater): Seems silly to translate result to this
22389
- // invalid/points thing and immediately translate it
22390
- // back in ItemRenderer.scoreInput()
22391
- if (result.empty) {
22392
- return {
22393
- type: "invalid",
22394
- message: result.message
22395
- };
22396
- }
22397
- return {
22398
- type: "points",
22399
- earned: result.correct ? 1 : 0,
22400
- total: 1,
22401
- message: result.message
22402
- };
22446
+ return numericInputValidator(userInput, rubric, strings);
22403
22447
  }
22404
22448
  state = {
22405
22449
  // keeps track of the other set of values when switching
@@ -22434,7 +22478,7 @@ class NumericInput extends React__namespace.Component {
22434
22478
  return !noFormsAccepted && !allFormsAccepted;
22435
22479
  };
22436
22480
  simpleValidate = rubric => {
22437
- return NumericInput.validate(this.getUserInput(), rubric, this.context.strings);
22481
+ return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
22438
22482
  };
22439
22483
  focus = () => {
22440
22484
  this.inputRef?.focus();
@@ -22565,37 +22609,6 @@ const unionAnswerForms = function (answerFormsList) {
22565
22609
  return formExampleKeys.indexOf(form.name);
22566
22610
  });
22567
22611
  };
22568
- // This function checks if the user inputted a percent value, parsing
22569
- // it as a number (and maybe scaling) so that it can be graded.
22570
- // NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
22571
- // can accept several input forms with or without "%", the decision
22572
- // to parse based on the presence of "%" in the input, is so that we
22573
- // don't accidently scale the user typed value before grading, CP-930.
22574
- function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
22575
- // If the input value is not a string ending with "%", then there's
22576
- // nothing more to do. The value will be graded as inputted by user.
22577
- if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
22578
- return inputValue;
22579
- }
22580
- const value = parseFloat(inputValue.slice(0, -1));
22581
- // If the input value stripped of the "%" cannot be parsed as a
22582
- // number (the slice is not really necessary for parseFloat to work
22583
- // if the string starts with a number) then return the original
22584
- // input for grading.
22585
- if (isNaN(value)) {
22586
- return inputValue;
22587
- }
22588
-
22589
- // Next, if all correct answers are in the range of |0,1| then we
22590
- // scale the user typed value. We assume this is the correct thing
22591
- // to do since the input value ends with "%".
22592
- if (normalizedAnswerExpected) {
22593
- return value / 100;
22594
- }
22595
-
22596
- // Otherwise, we return input valuåe (number) stripped of the "%".
22597
- return value;
22598
- }
22599
22612
  const propsTransform = function (widgetOptions) {
22600
22613
  const rendererProps = ___default["default"].extend(___default["default"].omit(widgetOptions, "answers"), {
22601
22614
  answerForms: unionAnswerForms(
@@ -22623,6 +22636,24 @@ var NumericInput$1 = {
22623
22636
  isLintable: true
22624
22637
  };
22625
22638
 
22639
+ /**
22640
+ * Several widgets don't have "right"/"wrong" validation logic,
22641
+ * so this just says to move on past those widgets
22642
+ *
22643
+ * TODO(LEMS-2396) widgets that use this probably shouldn't have any
22644
+ * validation logic and the thing validating an exercise
22645
+ * should just know to skip these
22646
+ */
22647
+ function noopValidator() {
22648
+ let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
22649
+ return {
22650
+ type: "points",
22651
+ earned: points,
22652
+ total: points,
22653
+ message: null
22654
+ };
22655
+ }
22656
+
22626
22657
  const EN_DASH$1 = "\u2013";
22627
22658
  class PassageRef extends React__namespace.Component {
22628
22659
  static contextType = PerseusI18nContext;
@@ -22640,13 +22671,10 @@ class PassageRef extends React__namespace.Component {
22640
22671
  lineRange: null,
22641
22672
  content: null
22642
22673
  };
22643
- static validate(userInput, rubric) {
22644
- return {
22645
- type: "points",
22646
- earned: 0,
22647
- total: 0,
22648
- message: null
22649
- };
22674
+
22675
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
22676
+ static validate() {
22677
+ return noopValidator();
22650
22678
  }
22651
22679
  componentDidMount() {
22652
22680
  // TODO(scottgrant): This is a hack to remove the deprecated call to
@@ -22707,9 +22735,11 @@ class PassageRef extends React__namespace.Component {
22707
22735
  }
22708
22736
  }
22709
22737
  };
22710
- simpleValidate = rubric => {
22711
- return PassageRef.validate(this.getUserInput(), rubric);
22712
- };
22738
+
22739
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
22740
+ simpleValidate() {
22741
+ return noopValidator();
22742
+ }
22713
22743
  render() {
22714
22744
  const {
22715
22745
  strings
@@ -22747,6 +22777,7 @@ class PassageRef extends React__namespace.Component {
22747
22777
  var PassageRef$1 = {
22748
22778
  name: "passage-ref",
22749
22779
  displayName: "PassageRef (SAT only)",
22780
+ hidden: true,
22750
22781
  defaultAlignment: "inline",
22751
22782
  widget: PassageRef,
22752
22783
  transform: widgetOptions => ({
@@ -24655,22 +24686,30 @@ var Radio = {
24655
24686
  /* eslint-disable import/no-named-as-default */
24656
24687
  var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
24657
24688
 
24658
- /* Free implementation of getUserInput. This should be used sparingly, since it
24659
- * just returns all the widget's props rather than picking out those which were
24660
- * input by the user.
24661
- */
24662
- const WidgetJsonifyDeprecated = {
24663
- getUserInput: function () {
24664
- // Omit props that get passed to all widgets
24665
- // @ts-expect-error - TS2339 - Property 'props' does not exist on type '{ readonly getUserInput: () => any; readonly getUserInputFromProps: (props: any) => any; }'.
24666
- return ___default["default"].omit(this.props, denylist);
24667
- },
24668
- // Static version of `WidgetJsonifyDeprecated.getUserInput`
24669
- getUserInputFromProps: function (props) {
24670
- // Omit props that get passed to all widgets
24671
- return ___default["default"].omit(props, denylist);
24689
+ function categorizerValidator(userInput, rubric, strings) {
24690
+ let completed = true;
24691
+ let allCorrect = true;
24692
+ rubric.values.forEach((value, i) => {
24693
+ if (userInput.values[i] == null) {
24694
+ completed = false;
24695
+ }
24696
+ if (userInput.values[i] !== value) {
24697
+ allCorrect = false;
24698
+ }
24699
+ });
24700
+ if (!completed) {
24701
+ return {
24702
+ type: "invalid",
24703
+ message: strings.invalidSelection
24704
+ };
24672
24705
  }
24673
- };
24706
+ return {
24707
+ type: "points",
24708
+ earned: allCorrect ? 1 : 0,
24709
+ total: 1,
24710
+ message: null
24711
+ };
24712
+ }
24674
24713
 
24675
24714
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
24676
24715
  class Categorizer extends React__namespace.Component {
@@ -24685,31 +24724,12 @@ class Categorizer extends React__namespace.Component {
24685
24724
  uniqueId: ___default["default"].uniqueId("perseus_radio_")
24686
24725
  };
24687
24726
  static validate(userInput, rubric, strings) {
24688
- let completed = true;
24689
- let allCorrect = true;
24690
- rubric.values.forEach((value, i) => {
24691
- if (userInput.values[i] == null) {
24692
- completed = false;
24693
- }
24694
- if (userInput.values[i] !== value) {
24695
- allCorrect = false;
24696
- }
24697
- });
24698
- if (!completed) {
24699
- return {
24700
- type: "invalid",
24701
- message: strings.invalidSelection
24702
- };
24703
- }
24704
- return {
24705
- type: "points",
24706
- earned: allCorrect ? 1 : 0,
24707
- total: 1,
24708
- message: null
24709
- };
24727
+ return categorizerValidator(userInput, rubric, strings);
24710
24728
  }
24711
24729
  static getUserInputFromProps(props) {
24712
- return WidgetJsonifyDeprecated.getUserInputFromProps(props);
24730
+ return {
24731
+ values: props.values
24732
+ };
24713
24733
  }
24714
24734
  change = (() => {
24715
24735
  var _this = this;
@@ -24732,7 +24752,7 @@ class Categorizer extends React__namespace.Component {
24732
24752
  this.props.trackInteraction();
24733
24753
  }
24734
24754
  simpleValidate = rubric => {
24735
- return Categorizer.validate(this.getUserInput(), rubric, this.context.strings);
24755
+ return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
24736
24756
  };
24737
24757
  render() {
24738
24758
  const self = this;
@@ -24864,6 +24884,7 @@ const styles$s = aphrodite.StyleSheet.create({
24864
24884
  var Categorizer$1 = {
24865
24885
  name: "categorizer",
24866
24886
  displayName: "Categorizer",
24887
+ hidden: true,
24867
24888
  widget: Categorizer,
24868
24889
  transform: widgetOptions => {
24869
24890
  return ___default["default"].pick(widgetOptions, "items", "categories", "randomizeItems");
@@ -24944,18 +24965,12 @@ function getUrlFromProgramID$1(programID) {
24944
24965
  /* This renders the scratchpad in an iframe and handles validation via
24945
24966
  * window.postMessage */
24946
24967
  class CSProgram extends React__namespace.Component {
24947
- static propTypes = {
24948
- ...propTypes,
24949
- programID: PropTypes__default["default"].string,
24950
- programType: PropTypes__default["default"].oneOf(["pjs", "sql", "webpage"]),
24951
- width: PropTypes__default["default"].number,
24952
- height: PropTypes__default["default"].number,
24953
- // eslint-disable-next-line react/forbid-prop-types
24954
- settings: PropTypes__default["default"].array,
24955
- showEditor: PropTypes__default["default"].bool,
24956
- showButtons: PropTypes__default["default"].bool,
24957
- status: PropTypes__default["default"].oneOf(["incomplete", "incorrect", "correct"]),
24958
- message: PropTypes__default["default"].string
24968
+ static defaultProps = {
24969
+ showEditor: false,
24970
+ showButtons: false,
24971
+ status: "incomplete",
24972
+ // optional message
24973
+ message: null
24959
24974
  };
24960
24975
 
24961
24976
  // The widget's grading function
@@ -24983,13 +24998,6 @@ class CSProgram extends React__namespace.Component {
24983
24998
  message: "Keep going, you're not there yet!"
24984
24999
  };
24985
25000
  }
24986
- static defaultProps = {
24987
- showEditor: false,
24988
- showButtons: false,
24989
- status: "incomplete",
24990
- // optional message
24991
- message: null
24992
- };
24993
25001
  componentDidMount() {
24994
25002
  $__default["default"](window).on("message", this.handleMessageEvent);
24995
25003
  }
@@ -25025,12 +25033,12 @@ class CSProgram extends React__namespace.Component {
25025
25033
  return change.apply(_this, args);
25026
25034
  };
25027
25035
  })();
25028
- simpleValidate = rubric => {
25036
+ simpleValidate(rubric) {
25029
25037
  return CSProgram.validate({
25030
25038
  status: this.props.status,
25031
25039
  message: this.props.message
25032
25040
  }, rubric);
25033
- };
25041
+ }
25034
25042
  render() {
25035
25043
  if (!this.props.programID) {
25036
25044
  return /*#__PURE__*/React__namespace.createElement("div", null);
@@ -25114,20 +25122,19 @@ class Definition extends React__namespace.Component {
25114
25122
  togglePrompt: "define me",
25115
25123
  definition: "definition goes here"
25116
25124
  };
25117
- static validate(userInput, rubric) {
25118
- return {
25119
- type: "points",
25120
- earned: 0,
25121
- total: 0,
25122
- message: null
25123
- };
25125
+
25126
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
25127
+ static validate() {
25128
+ return noopValidator();
25124
25129
  }
25125
25130
  getUserInput = () => {
25126
25131
  return {};
25127
25132
  };
25128
- simpleValidate = rubric => {
25129
- return Definition.validate(this.getUserInput(), rubric);
25130
- };
25133
+
25134
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
25135
+ simpleValidate() {
25136
+ return noopValidator();
25137
+ }
25131
25138
  render() {
25132
25139
  return /*#__PURE__*/React__namespace.createElement(DefinitionConsumer, null, _ref => {
25133
25140
  let {
@@ -25193,18 +25200,13 @@ class DeprecatedStandin extends React__default["default"].Component {
25193
25200
  static validate(userInput, rubric) {
25194
25201
  // Since this mean to replace an existing widget the learner
25195
25202
  // WILL earn points for this widget
25196
- return {
25197
- type: "points",
25198
- earned: 1,
25199
- total: 1,
25200
- message: null
25201
- };
25203
+ return noopValidator(1);
25202
25204
  }
25203
25205
  getUserInput = () => {
25204
25206
  return {};
25205
25207
  };
25206
25208
  simpleValidate = rubric => {
25207
- return DeprecatedStandin.validate(this.getUserInput(), rubric);
25209
+ return noopValidator(1);
25208
25210
  };
25209
25211
  render() {
25210
25212
  return /*#__PURE__*/React__default["default"].createElement("div", {
@@ -25226,6 +25228,23 @@ var DeprecatedStandin$1 = {
25226
25228
  hidden: true
25227
25229
  };
25228
25230
 
25231
+ function dropdownValidator(userInput, rubric) {
25232
+ const selected = userInput.value;
25233
+ if (selected === 0) {
25234
+ return {
25235
+ type: "invalid",
25236
+ message: null
25237
+ };
25238
+ }
25239
+ const correct = rubric.choices[selected - 1].correct;
25240
+ return {
25241
+ type: "points",
25242
+ earned: correct ? 1 : 0,
25243
+ total: 1,
25244
+ message: null
25245
+ };
25246
+ }
25247
+
25229
25248
  class Dropdown extends React__namespace.Component {
25230
25249
  static defaultProps = {
25231
25250
  choices: [],
@@ -25234,20 +25253,7 @@ class Dropdown extends React__namespace.Component {
25234
25253
  apiOptions: ApiOptions.defaults
25235
25254
  };
25236
25255
  static validate(userInput, rubric) {
25237
- const selected = userInput.value;
25238
- if (selected === 0) {
25239
- return {
25240
- type: "invalid",
25241
- message: null
25242
- };
25243
- }
25244
- const correct = rubric.choices[selected - 1].correct;
25245
- return {
25246
- type: "points",
25247
- earned: correct ? 1 : 0,
25248
- total: 1,
25249
- message: null
25250
- };
25256
+ return dropdownValidator(userInput, rubric);
25251
25257
  }
25252
25258
  focus = () => {
25253
25259
  // TODO(LP-10797): This focus() call doesn't do anything because our
@@ -25272,7 +25278,7 @@ class Dropdown extends React__namespace.Component {
25272
25278
  };
25273
25279
  };
25274
25280
  simpleValidate = rubric => {
25275
- return Dropdown.validate(this.getUserInput(), rubric);
25281
+ return dropdownValidator(this.getUserInput(), rubric);
25276
25282
  };
25277
25283
  render() {
25278
25284
  const children = [/*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
@@ -25338,13 +25344,10 @@ class Explanation extends React__namespace.Component {
25338
25344
  widgets: {},
25339
25345
  linterContext: PerseusLinter.linterContextDefault
25340
25346
  };
25341
- static validate(userInput, rubric) {
25342
- return {
25343
- type: "points",
25344
- earned: 0,
25345
- total: 0,
25346
- message: null
25347
- };
25347
+
25348
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
25349
+ static validate() {
25350
+ return noopValidator();
25348
25351
  }
25349
25352
  componentDidMount() {
25350
25353
  this._mounted = true;
@@ -25370,9 +25373,11 @@ class Explanation extends React__namespace.Component {
25370
25373
  getUserInput = () => {
25371
25374
  return {};
25372
25375
  };
25373
- simpleValidate = rubric => {
25374
- return Explanation.validate(this.getUserInput(), rubric);
25375
- };
25376
+
25377
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
25378
+ simpleValidate() {
25379
+ return noopValidator();
25380
+ }
25376
25381
  render() {
25377
25382
  const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
25378
25383
  const caretIcon = this.state.expanded ? caretUp__default["default"] : caretDown__default["default"];
@@ -27208,6 +27213,7 @@ const staticTransform$4 = editorProps => {
27208
27213
  var Grapher$1 = {
27209
27214
  name: "grapher",
27210
27215
  displayName: "Grapher",
27216
+ hidden: true,
27211
27217
  widget: Grapher,
27212
27218
  transform: propTransform$2,
27213
27219
  staticTransform: staticTransform$4
@@ -27355,28 +27361,23 @@ var Group$1 = {
27355
27361
  displayName: "Group (SAT only)",
27356
27362
  widget: Group,
27357
27363
  traverseChildWidgets: traverseChildWidgets,
27358
- hidden: false,
27364
+ hidden: true,
27359
27365
  isLintable: true
27360
27366
  };
27361
27367
 
27362
- /* eslint-disable react/forbid-prop-types */
27368
+ /**
27369
+ * This is an iframe widget. It is used for rendering an iframe that
27370
+ * then communicates its state via window.postMessage
27371
+ * This is useful for embedding arbitrary visualizations/simulations with
27372
+ * completed conditions, such as the mazes and games in Algorithms.
27373
+ * It's particularly well suited for embedding our ProcessingJS programs,
27374
+ * but could also be used for embedding viz's hosted elsewhere.
27375
+ */
27363
27376
  const {
27364
27377
  updateQueryString
27365
27378
  } = Util;
27366
-
27367
27379
  /* This renders the iframe and handles validation via window.postMessage */
27368
27380
  class Iframe extends React__namespace.Component {
27369
- static propTypes = {
27370
- ...propTypes,
27371
- width: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number]),
27372
- height: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number]),
27373
- url: PropTypes__default["default"].string,
27374
- settings: PropTypes__default["default"].array,
27375
- status: PropTypes__default["default"].oneOf(["incomplete", "incorrect", "correct"]),
27376
- message: PropTypes__default["default"].string,
27377
- allowFullScreen: PropTypes__default["default"].bool,
27378
- allowTopNavigation: PropTypes__default["default"].bool
27379
- };
27380
27381
  static defaultProps = {
27381
27382
  status: "incomplete",
27382
27383
  // optional message
@@ -27390,9 +27391,12 @@ class Iframe extends React__namespace.Component {
27390
27391
  componentWillUnmount() {
27391
27392
  $__default["default"](window).off("message", this.handleMessageEvent);
27392
27393
  }
27393
- getUserInput = () => {
27394
- return WidgetJsonifyDeprecated.getUserInput.call(this);
27395
- };
27394
+ getUserInput() {
27395
+ return {
27396
+ status: this.props.status,
27397
+ message: this.props.message
27398
+ };
27399
+ }
27396
27400
  handleMessageEvent = e => {
27397
27401
  // We receive data from the iframe that contains {passed: true/false}
27398
27402
  // and use that to set the status
@@ -27551,13 +27555,10 @@ class ImageWidget extends React__namespace.Component {
27551
27555
  caption: "",
27552
27556
  linterContext: PerseusLinter.linterContextDefault
27553
27557
  };
27554
- static validate(userInput, rubric) {
27555
- return {
27556
- type: "points",
27557
- earned: 0,
27558
- total: 0,
27559
- message: null
27560
- };
27558
+
27559
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27560
+ static validate() {
27561
+ return noopValidator();
27561
27562
  }
27562
27563
  change = (() => {
27563
27564
  var _this = this;
@@ -27571,9 +27572,11 @@ class ImageWidget extends React__namespace.Component {
27571
27572
  getUserInput = () => {
27572
27573
  return null;
27573
27574
  };
27574
- simpleValidate = rubric => {
27575
- return ImageWidget.validate(this.getUserInput(), rubric);
27576
- };
27575
+
27576
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27577
+ simpleValidate() {
27578
+ return noopValidator();
27579
+ }
27577
27580
  focus = () => {}; // no-op
27578
27581
 
27579
27582
  render() {
@@ -27799,13 +27802,10 @@ class Interaction extends React__namespace.Component {
27799
27802
  },
27800
27803
  elements: []
27801
27804
  };
27802
- static validate(state, rubric) {
27803
- return {
27804
- type: "points",
27805
- earned: 0,
27806
- total: 0,
27807
- message: null
27808
- };
27805
+
27806
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27807
+ static validate() {
27808
+ return noopValidator();
27809
27809
  }
27810
27810
  state = {
27811
27811
  variables: _getInitialVariables(this.props.elements),
@@ -27921,9 +27921,11 @@ class Interaction extends React__namespace.Component {
27921
27921
  // anything.
27922
27922
  return {};
27923
27923
  };
27924
- simpleValidate = rubric => {
27925
- return Interaction.validate(this.getUserInput(), rubric);
27926
- };
27924
+
27925
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27926
+ simpleValidate() {
27927
+ return noopValidator();
27928
+ }
27927
27929
  render() {
27928
27930
  const range = this.props.graph.range;
27929
27931
  let labels = this.props.graph.labels;
@@ -29977,6 +29979,7 @@ function LockedLabel(props) {
29977
29979
 
29978
29980
  function GraphLockedLabelsLayer(props) {
29979
29981
  const {
29982
+ flags,
29980
29983
  lockedFigures
29981
29984
  } = props;
29982
29985
  return lockedFigures.map((figure, i) => {
@@ -29985,9 +29988,21 @@ function GraphLockedLabelsLayer(props) {
29985
29988
  key: `label-${i}`
29986
29989
  }, figure));
29987
29990
  }
29988
-
29989
- // TODO(LEMS-2271): Add support for labels within
29990
- // other locked figure types
29991
+ if (
29992
+ // Point flag + point type
29993
+ flags?.["mafs"]?.["locked-point-labels"] && figure.type === "point" ||
29994
+ // Line flag + line type
29995
+ flags?.["mafs"]?.["locked-line-labels"] && figure.type === "line") {
29996
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, {
29997
+ key: i
29998
+ }, figure.labels.map((label, j) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29999
+ key: `${i}-label-${j}`
30000
+ }, label))), figure.type === "line" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, figure.points[0].labels.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
30001
+ key: `locked-figure-${i}-point-1-label-${k}`
30002
+ }, label))), figure.points[1].labels.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
30003
+ key: `locked-figure-${i}-point-2-label-${k}`
30004
+ }, label)))));
30005
+ }
29991
30006
  return null;
29992
30007
  });
29993
30008
  }
@@ -30326,7 +30341,9 @@ const GraphLockedLayer = props => {
30326
30341
 
30327
30342
  const actions = {
30328
30343
  global: {
30329
- deleteIntent
30344
+ deleteIntent,
30345
+ changeInteractionMode,
30346
+ changeKeyboardInvitationVisibility
30330
30347
  },
30331
30348
  angle: {
30332
30349
  movePoint
@@ -30418,6 +30435,20 @@ function clickPoint(index) {
30418
30435
  index
30419
30436
  };
30420
30437
  }
30438
+ const CHANGE_INTERACTION_MODE = "point-change-interaction-mode";
30439
+ function changeInteractionMode(mode) {
30440
+ return {
30441
+ type: CHANGE_INTERACTION_MODE,
30442
+ mode
30443
+ };
30444
+ }
30445
+ const CHANGE_KEYBOARD_INVITATION_VISIBILITY = "change-keyboard-interaction-invitation-visibility";
30446
+ function changeKeyboardInvitationVisibility(shouldShow) {
30447
+ return {
30448
+ type: CHANGE_KEYBOARD_INVITATION_VISIBILITY,
30449
+ shouldShow
30450
+ };
30451
+ }
30421
30452
  const MOVE_ALL = "move-all";
30422
30453
  function moveAll(delta) {
30423
30454
  return {
@@ -32275,15 +32306,18 @@ const MafsGraph = props => {
32275
32306
  height
32276
32307
  },
32277
32308
  onKeyUp: event => {
32278
- if (event.key === "Backspace") {
32279
- dispatch(actions.global.deleteIntent());
32280
- graphRef.current?.focus();
32281
- }
32309
+ handleKeyboardEvent(event, state, dispatch);
32282
32310
  },
32283
32311
  "aria-label": fullGraphAriaLabel,
32284
32312
  "aria-describedby": fullGraphAriaDescription ? descriptionId : undefined,
32285
32313
  ref: graphRef,
32286
- tabIndex: 0
32314
+ tabIndex: 0,
32315
+ onFocus: event => {
32316
+ handleFocusEvent(event, state, dispatch);
32317
+ },
32318
+ onBlur: event => {
32319
+ handleBlurEvent(event, state, dispatch);
32320
+ }
32287
32321
  }, fullGraphAriaDescription && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32288
32322
  id: descriptionId,
32289
32323
  tabIndex: -1,
@@ -32324,7 +32358,8 @@ const MafsGraph = props => {
32324
32358
  props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
32325
32359
  lockedFigures: props.lockedFigures,
32326
32360
  range: state.range
32327
- }))), props.showLabelsFlag && props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
32361
+ }))), props.flags?.["mafs"]?.["interactive-graph-locked-features-labels"] && props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
32362
+ flags: props.flags,
32328
32363
  lockedFigures: props.lockedFigures
32329
32364
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32330
32365
  style: {
@@ -32344,30 +32379,63 @@ const MafsGraph = props => {
32344
32379
  }, /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
32345
32380
  state,
32346
32381
  dispatch
32347
- })))))), renderGraphControls({
32382
+ }))))), state.type === "point" && state.showKeyboardInteractionInvitation && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32383
+ style: {
32384
+ textAlign: "center",
32385
+ backgroundColor: "white",
32386
+ border: "1px solid #21242C52",
32387
+ padding: "16px 0",
32388
+ boxShadow: "0px 8px 8px 0px #21242C14",
32389
+ // This translates the box to the center of the
32390
+ // graph Then backs it off by half of its
32391
+ // overall height so it's perfectly centered
32392
+ top: "50%",
32393
+ transform: "translateY(-50%)"
32394
+ }
32395
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, null, "Press ", /*#__PURE__*/React__namespace.createElement("strong", null, "Shift + Enter"), " to interact with the graph"))), renderGraphControls({
32348
32396
  state,
32349
32397
  dispatch,
32350
32398
  width
32351
32399
  })));
32352
32400
  };
32353
- const renderPointGraphControls = props => /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32354
- style: {
32355
- flexDirection: "row",
32356
- width: props.width
32357
- }
32358
- }, props.state.showRemovePointButton && props.state.focusedPointIndex !== null && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
32359
- id: REMOVE_BUTTON_ID,
32360
- kind: "secondary",
32361
- color: "destructive",
32362
- tabIndex: -1,
32363
- style: {
32364
- width: "100%",
32365
- marginLeft: "20px"
32366
- },
32367
- onClick: event => {
32368
- props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));
32369
- }
32370
- }, "Remove Point"));
32401
+ const renderPointGraphControls = props => {
32402
+ const {
32403
+ interactionMode,
32404
+ showRemovePointButton,
32405
+ focusedPointIndex
32406
+ } = props.state;
32407
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32408
+ style: {
32409
+ flexDirection: "row",
32410
+ width: props.width
32411
+ }
32412
+ }, interactionMode === "keyboard" && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
32413
+ kind: "secondary",
32414
+ style: {
32415
+ width: "100%",
32416
+ marginLeft: "20px"
32417
+ },
32418
+ tabIndex: 0,
32419
+ onClick: () => {
32420
+ props.dispatch(actions.pointGraph.addPoint([0, 0]));
32421
+ }
32422
+ }, "Add Point"), interactionMode === "mouse" && showRemovePointButton && focusedPointIndex !== null && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
32423
+ id: REMOVE_BUTTON_ID,
32424
+ kind: "secondary",
32425
+ color: "destructive"
32426
+ // This button is meant to be interacted with by the mouse only
32427
+ // Never allow learners to tab to this button
32428
+ ,
32429
+ tabIndex: -1,
32430
+ style: {
32431
+ width: "100%",
32432
+ marginLeft: "20px"
32433
+ },
32434
+ onClick: event => {
32435
+ props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));
32436
+ }
32437
+ }, "Remove Point"));
32438
+ };
32371
32439
  const renderGraphControls = props => {
32372
32440
  const {
32373
32441
  state,
@@ -32391,6 +32459,34 @@ const renderGraphControls = props => {
32391
32459
  return null;
32392
32460
  }
32393
32461
  };
32462
+ function handleFocusEvent(event, state, dispatch) {
32463
+ if (state.type === "point" && state.numPoints === "unlimited") {
32464
+ if (event.target.classList.contains("mafs-graph") && state.interactionMode === "mouse") {
32465
+ dispatch(actions.global.changeKeyboardInvitationVisibility(true));
32466
+ }
32467
+ }
32468
+ }
32469
+ function handleBlurEvent(event, state, dispatch) {
32470
+ if (state.type === "point" && state.numPoints === "unlimited") {
32471
+ dispatch(actions.global.changeKeyboardInvitationVisibility(false));
32472
+ }
32473
+ }
32474
+ function handleKeyboardEvent(event, state, dispatch) {
32475
+ if (state.type === "point" && state.numPoints === "unlimited") {
32476
+ if (event.key === "Backspace") {
32477
+ dispatch(actions.global.deleteIntent());
32478
+
32479
+ // After removing a point blur
32480
+ // It would be nice if this could focus on the graph but doing so
32481
+ // would trigger the message to prompt a learner to enter keyboard mode
32482
+ document.activeElement.blur();
32483
+ } else if (event.shiftKey && event.key === "Enter") {
32484
+ dispatch(actions.global.changeInteractionMode("keyboard"));
32485
+ } else if (state.interactionMode === "keyboard" && event.key === "a") {
32486
+ dispatch(actions.pointGraph.addPoint([0, 0]));
32487
+ }
32488
+ }
32489
+ }
32394
32490
 
32395
32491
  // Calculate the difference between the min and max values of a range
32396
32492
  const getRangeDiff = range => {
@@ -32560,7 +32656,9 @@ function initializeGraphState(params) {
32560
32656
  coords: getPointCoords(graph, range, step),
32561
32657
  numPoints: graph.numPoints || 0,
32562
32658
  focusedPointIndex: null,
32563
- showRemovePointButton: false
32659
+ showRemovePointButton: false,
32660
+ interactionMode: "mouse",
32661
+ showKeyboardInteractionInvitation: false
32564
32662
  };
32565
32663
  case "circle":
32566
32664
  return {
@@ -32833,6 +32931,10 @@ function interactiveGraphReducer(state, action) {
32833
32931
  return doDeleteIntent(state);
32834
32932
  case CLICK_POINT:
32835
32933
  return doClickPoint(state, action);
32934
+ case CHANGE_INTERACTION_MODE:
32935
+ return doChangeInteractionMode(state, action);
32936
+ case CHANGE_KEYBOARD_INVITATION_VISIBILITY:
32937
+ return doChangeKeyboardInvitationVisibility(state, action);
32836
32938
  default:
32837
32939
  throw new wonderStuffCore.UnreachableCaseError(action);
32838
32940
  }
@@ -32884,6 +32986,33 @@ function doClickPoint(state, action) {
32884
32986
  }
32885
32987
  return state;
32886
32988
  }
32989
+ function doChangeInteractionMode(state, action) {
32990
+ if (state.type !== "point") {
32991
+ return state;
32992
+ }
32993
+ if (state.numPoints === "unlimited") {
32994
+ const nextKeyboardInvitation = action.mode === "keyboard" ? false : state.showKeyboardInteractionInvitation;
32995
+ return {
32996
+ ...state,
32997
+ interactionMode: action.mode,
32998
+ showKeyboardInteractionInvitation: nextKeyboardInvitation
32999
+ };
33000
+ }
33001
+ return state;
33002
+ }
33003
+ function doChangeKeyboardInvitationVisibility(state, action) {
33004
+ if (state.type !== "point") {
33005
+ return state;
33006
+ }
33007
+ if (state.numPoints === "unlimited") {
33008
+ return {
33009
+ ...state,
33010
+ showKeyboardInteractionInvitation: action.shouldShow,
33011
+ hasBeenInteractedWith: true
33012
+ };
33013
+ }
33014
+ return state;
33015
+ }
32887
33016
  function doMovePointInFigure(state, action) {
32888
33017
  switch (state.type) {
32889
33018
  case "segment":
@@ -33652,7 +33781,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
33652
33781
  const snapTo = graph.type === "polygon" ? graph.snapTo : null;
33653
33782
  const showAngles = graph.type === "polygon" ? graph.showAngles : null;
33654
33783
  const showSides = graph.type === "polygon" ? graph.showSides : null;
33655
- const startCoords = "startCoords" in graph ? graph.startCoords : null;
33784
+ const startCoords = "startCoords" in graph ? graph.startCoords : undefined;
33656
33785
  const originalPropsRef = React.useRef(props);
33657
33786
  const latestPropsRef = wonderBlocksCore.useLatestRef(props);
33658
33787
  React.useEffect(() => {
@@ -34960,7 +35089,7 @@ class InteractiveGraph extends React__namespace.Component {
34960
35089
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
34961
35090
  const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
34962
35091
  return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends({}, this.props, {
34963
- showLabelsFlag: this.props.apiOptions?.flags?.["mafs"]?.["interactive-graph-locked-features-labels"],
35092
+ flags: this.props.apiOptions?.flags,
34964
35093
  ref: this.mafsRef,
34965
35094
  gridStep: gridStep,
34966
35095
  snapStep: snapStep,
@@ -35557,7 +35686,7 @@ function shouldUseMafs(mafsFlags, graph) {
35557
35686
  const staticTransform$3 = ___default["default"].identity;
35558
35687
  var InteractiveGraph$1 = {
35559
35688
  name: "interactive-graph",
35560
- displayName: "Interactive graph",
35689
+ displayName: "Interactive graph (Assessments only)",
35561
35690
  widget: InteractiveGraph,
35562
35691
  staticTransform: staticTransform$3
35563
35692
  };
@@ -37511,11 +37640,6 @@ var LabelImage$1 = {
37511
37640
  };
37512
37641
 
37513
37642
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
37514
- let Layout = /*#__PURE__*/function (Layout) {
37515
- Layout["HORIZONTAL"] = "horizontal";
37516
- Layout["VERTICAL"] = "vertical";
37517
- return Layout;
37518
- }({});
37519
37643
  var ItemState = /*#__PURE__*/function (ItemState) {
37520
37644
  ItemState["STATIC"] = "static";
37521
37645
  ItemState["DRAGGING"] = "dragging";
@@ -37542,7 +37666,7 @@ class Placeholder extends React__namespace.Component {
37542
37666
  const {
37543
37667
  layout
37544
37668
  } = this.props;
37545
- const className = aphrodite.css(styles$d.card, styles$d.placeholder, layout === Layout.HORIZONTAL && styles$d.horizontalCard);
37669
+ const className = aphrodite.css(styles$d.card, styles$d.placeholder, layout === "horizontal" && styles$d.horizontalCard);
37546
37670
  const style = {
37547
37671
  width: this.props.width,
37548
37672
  height: this.props.height
@@ -37749,7 +37873,7 @@ class Draggable extends React__namespace.Component {
37749
37873
  // We need to keep backwards compatbility with rules specified directly
37750
37874
  // in CSS. Hence the hacky tacking on of manual classNames.
37751
37875
  // See sortable.less for details.
37752
- let className = aphrodite.css(styles$d.card, styles$d.draggable, layout === 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";
37876
+ let className = aphrodite.css(styles$d.card, styles$d.draggable, layout === "horizontal" && styles$d.horizontalCard, layout === "vertical" && styles$d.verticalCard, type === ItemState.DRAGGING && styles$d.dragging, type === ItemState.DISABLED && styles$d.disabled, !includePadding && styles$d.unpaddedCard) + " " + ClassNames.INTERACTIVE + " perseus-sortable-draggable";
37753
37877
  if (!includePadding) {
37754
37878
  className += " perseus-sortable-draggable-unpadded";
37755
37879
  }
@@ -37789,7 +37913,7 @@ class Draggable extends React__namespace.Component {
37789
37913
  }
37790
37914
  class Sortable extends React__namespace.Component {
37791
37915
  static defaultProps = {
37792
- layout: Layout.HORIZONTAL,
37916
+ layout: "horizontal",
37793
37917
  padding: true,
37794
37918
  disabled: false,
37795
37919
  constraints: {},
@@ -37884,7 +38008,7 @@ class Sortable extends React__namespace.Component {
37884
38008
  if (constraints?.width) {
37885
38009
  // Items must be at least as wide as the specified constraint
37886
38010
  syncWidth = ___default["default"].max(widths.concat(constraints.width));
37887
- } else if (layout === Layout.VERTICAL) {
38011
+ } else if (layout === "vertical") {
37888
38012
  // Sync widths to get a clean column
37889
38013
  syncWidth = ___default["default"].max(widths);
37890
38014
  }
@@ -37892,7 +38016,7 @@ class Sortable extends React__namespace.Component {
37892
38016
  if (constraints?.height) {
37893
38017
  // Items must be at least as high as the specified constraint
37894
38018
  syncHeight = ___default["default"].max(heights.concat(constraints.height));
37895
- } else if (layout === Layout.HORIZONTAL) {
38019
+ } else if (layout === "horizontal") {
37896
38020
  // Sync widths to get a clean row
37897
38021
  syncHeight = ___default["default"].max(heights);
37898
38022
  }
@@ -37974,7 +38098,7 @@ class Sortable extends React__namespace.Component {
37974
38098
 
37975
38099
  // @ts-expect-error - TS2551 - Property 'splice' does not exist on type 'readonly SortableItem[]'. Did you mean 'slice'?
37976
38100
  items.splice(currentIndex, 1);
37977
- if (this.props.layout === Layout.HORIZONTAL) {
38101
+ if (this.props.layout === "horizontal") {
37978
38102
  // @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'. | TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
37979
38103
  const midWidth = $draggable.offset().left - $sortable.offset().left;
37980
38104
  let sumWidth = 0;
@@ -38088,19 +38212,19 @@ class Sortable extends React__namespace.Component {
38088
38212
  // We need to keep backwards compatbility with rules specified directly
38089
38213
  // in CSS. See sortable.less for details.
38090
38214
  const className = aphrodite.css(styles$d.sortable) + " perseus-sortable";
38091
- const syncWidth = this.props.constraints?.width || layout === Layout.VERTICAL;
38092
- const syncHeight = this.props.constraints?.height || layout === Layout.HORIZONTAL;
38215
+ const syncWidth = this.props.constraints?.width || layout === "vertical";
38216
+ const syncHeight = this.props.constraints?.height || layout === "horizontal";
38093
38217
  ___default["default"].each(this.state.items, function (item, i, items) {
38094
38218
  const isLast = i === items.length - 1;
38095
38219
  const isStatic = item.state === ItemState.STATIC || item.state === ItemState.DISABLED;
38096
38220
  let margin;
38097
38221
 
38098
38222
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
38099
- if (this.props.layout === Layout.HORIZONTAL) {
38223
+ if (this.props.layout === "horizontal") {
38100
38224
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
38101
38225
  margin = "0 " + this.props.margin + "px 0 0"; // right
38102
38226
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
38103
- } else if (this.props.layout === Layout.VERTICAL) {
38227
+ } else if (this.props.layout === "vertical") {
38104
38228
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
38105
38229
  margin = "0 0 " + this.props.margin + "px 0"; // bottom
38106
38230
  }
@@ -38222,7 +38346,6 @@ const styles$d = aphrodite.StyleSheet.create({
38222
38346
  }
38223
38347
  });
38224
38348
 
38225
- /* eslint-disable @khanacademy/ts-no-error-suppressions */
38226
38349
  const {
38227
38350
  shuffle: shuffle$1,
38228
38351
  seededRNG
@@ -38230,18 +38353,6 @@ const {
38230
38353
  const HACKY_CSS_CLASSNAME = "perseus-widget-matcher";
38231
38354
  class Matcher extends React__namespace.Component {
38232
38355
  static contextType = PerseusI18nContext;
38233
- static propTypes = {
38234
- apiOptions: ApiOptions.propTypes,
38235
- labels: PropTypes__default["default"].array,
38236
- left: PropTypes__default["default"].array,
38237
- onChange: PropTypes__default["default"].func,
38238
- orderMatters: PropTypes__default["default"].bool,
38239
- padding: PropTypes__default["default"].bool,
38240
- problemNum: PropTypes__default["default"].number,
38241
- right: PropTypes__default["default"].array,
38242
- trackInteraction: PropTypes__default["default"].func.isRequired,
38243
- linterContext: PerseusLinter.linterContextProps
38244
- };
38245
38356
  static defaultProps = {
38246
38357
  left: [],
38247
38358
  right: [],
@@ -38378,7 +38489,7 @@ class Matcher extends React__namespace.Component {
38378
38489
  className: aphrodite.css(styles$c.column)
38379
38490
  }, /*#__PURE__*/React__namespace.createElement(Sortable, {
38380
38491
  options: left,
38381
- layout: Layout.VERTICAL,
38492
+ layout: "vertical",
38382
38493
  padding: this.props.padding,
38383
38494
  disabled: !this.props.orderMatters,
38384
38495
  constraints: constraints,
@@ -38391,11 +38502,9 @@ class Matcher extends React__namespace.Component {
38391
38502
  ref: "left"
38392
38503
  })), /*#__PURE__*/React__namespace.createElement("td", {
38393
38504
  className: aphrodite.css(styles$c.column, styles$c.columnRight)
38394
- }, /*#__PURE__*/React__namespace.createElement(Sortable
38395
- // @ts-expect-error - TS2322 - Type 'readonly unknown[]' is not assignable to type 'readonly string[]'.
38396
- , {
38505
+ }, /*#__PURE__*/React__namespace.createElement(Sortable, {
38397
38506
  options: right,
38398
- layout: Layout.VERTICAL,
38507
+ layout: "vertical",
38399
38508
  padding: this.props.padding,
38400
38509
  constraints: constraints,
38401
38510
  onMeasure: this.onMeasureRight,
@@ -38916,6 +39025,7 @@ const staticTransform$2 = editorProps => {
38916
39025
  var Matrix$1 = {
38917
39026
  name: "matrix",
38918
39027
  displayName: "Matrix",
39028
+ hidden: true,
38919
39029
  widget: Matrix,
38920
39030
  transform: propTransform$1,
38921
39031
  staticTransform: staticTransform$2,
@@ -39017,9 +39127,8 @@ const Measurer = createReactClass__default["default"]({
39017
39127
  getUserInput: function () {
39018
39128
  return {};
39019
39129
  },
39020
- simpleValidate: function (rubric) {
39021
- // TODO(joel) - I don't understand how this is useful!
39022
- return Measurer.validate(this.getUserInput(), rubric);
39130
+ simpleValidate: function () {
39131
+ return noopValidator(1);
39023
39132
  },
39024
39133
  focus: $__default["default"].noop,
39025
39134
  render: function () {
@@ -39048,14 +39157,7 @@ const Measurer = createReactClass__default["default"]({
39048
39157
  }
39049
39158
  });
39050
39159
  ___default["default"].extend(Measurer, {
39051
- validate: function (state, rubric) {
39052
- return {
39053
- type: "points",
39054
- earned: 1,
39055
- total: 1,
39056
- message: null
39057
- };
39058
- }
39160
+ validate: noopValidator(1)
39059
39161
  });
39060
39162
  const propUpgrades = {
39061
39163
  "1": v0props => {
@@ -39072,6 +39174,7 @@ const propUpgrades = {
39072
39174
  var Measurer$1 = {
39073
39175
  name: "measurer",
39074
39176
  displayName: "Measurer",
39177
+ hidden: true,
39075
39178
  widget: Measurer,
39076
39179
  version: {
39077
39180
  major: 1,
@@ -39949,28 +40052,19 @@ class MoleculeWidget extends React__namespace.Component {
39949
40052
  static defaultProps = {
39950
40053
  rotationAngle: 0
39951
40054
  };
39952
- simpleValidate = () => {
39953
- return {
39954
- type: "points",
39955
- earned: 0,
39956
- total: 0,
39957
- message: null
39958
- };
39959
- };
40055
+
40056
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
40057
+ simpleValidate() {
40058
+ return noopValidator();
40059
+ }
39960
40060
  getUserInput = () => {
39961
40061
  return [];
39962
40062
  };
39963
- validate = (state, rubric) => {
39964
- // TODO(colin): this is here as part of the interface for a component.
39965
- // Figure out if there is something more appropriate that this should
39966
- // return.
39967
- return {
39968
- type: "points",
39969
- earned: 0,
39970
- total: 0,
39971
- message: null
39972
- };
39973
- };
40063
+
40064
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
40065
+ validate() {
40066
+ return noopValidator();
40067
+ }
39974
40068
  render() {
39975
40069
  return /*#__PURE__*/React__namespace.createElement(Molecule, {
39976
40070
  widgetId: this.props.widgetId,
@@ -40569,10 +40663,6 @@ var NumberLine$1 = {
40569
40663
  };
40570
40664
 
40571
40665
  class PlaceholderCard extends React__namespace.Component {
40572
- static propTypes = {
40573
- width: PropTypes__default["default"].number.isRequired,
40574
- height: PropTypes__default["default"].number.isRequired
40575
- };
40576
40666
  render() {
40577
40667
  return /*#__PURE__*/React__namespace.createElement("div", {
40578
40668
  className: "card-wrap " + ClassNames.INTERACTIVE,
@@ -40582,7 +40672,7 @@ class PlaceholderCard extends React__namespace.Component {
40582
40672
  }, /*#__PURE__*/React__namespace.createElement("div", {
40583
40673
  className: "card placeholder",
40584
40674
  style: {
40585
- height: this.props.height
40675
+ height: this.props.width
40586
40676
  }
40587
40677
  }));
40588
40678
  }
@@ -40596,29 +40686,10 @@ class DragHintCard extends React__namespace.Component {
40596
40686
  }));
40597
40687
  }
40598
40688
  }
40599
- const PropTypePosition = PropTypes__default["default"].shape({
40600
- left: PropTypes__default["default"].number,
40601
- top: PropTypes__default["default"].number
40602
- });
40603
40689
  class Card extends React__namespace.Component {
40604
40690
  static contextType = PerseusI18nContext;
40605
40691
  // @ts-expect-error - TS2564 - Property 'mouseMoveUpBound' has no initializer and is not definitely assigned in the constructor.
40606
40692
  mouseMoveUpBound;
40607
- static propTypes = {
40608
- floating: PropTypes__default["default"].bool.isRequired,
40609
- animating: PropTypes__default["default"].bool,
40610
- width: PropTypes__default["default"].number,
40611
- stack: PropTypes__default["default"].bool,
40612
- onMouseDown: PropTypes__default["default"].func,
40613
- onMouseMove: PropTypes__default["default"].func,
40614
- onMouseUp: PropTypes__default["default"].func,
40615
- // Used only for floating/animating cards
40616
- startMouse: PropTypePosition,
40617
- startOffset: PropTypePosition,
40618
- animateTo: PropTypePosition,
40619
- onAnimationEnd: PropTypes__default["default"].func,
40620
- linterContext: PerseusLinter.linterContextProps
40621
- };
40622
40693
  static defaultProps = {
40623
40694
  stack: false,
40624
40695
  animating: false,
@@ -40656,7 +40727,7 @@ class Card extends React__namespace.Component {
40656
40727
  this.props.fakeRef !== nextProps.fakeRef;
40657
40728
  }
40658
40729
  componentDidUpdate(prevProps, prevState) {
40659
- if (this.props.animating && !prevProps.animating) {
40730
+ if (this.props.animating && !prevProps.animating && this.props.animateTo && this.props.startOffset) {
40660
40731
  // If we just were changed into animating, start the animation.
40661
40732
  // We pick the animation speed based on the distance that the card
40662
40733
  // needs to travel. (Why sqrt? Just because it looks nice -- with a
@@ -40723,8 +40794,8 @@ class Card extends React__namespace.Component {
40723
40794
  if (this.props.floating) {
40724
40795
  style = {
40725
40796
  position: "absolute",
40726
- left: this.props.startOffset.left,
40727
- top: this.props.startOffset.top
40797
+ left: this.props.startOffset?.left,
40798
+ top: this.props.startOffset?.top
40728
40799
  };
40729
40800
  }
40730
40801
  if (this.props.width) {
@@ -40734,7 +40805,7 @@ class Card extends React__namespace.Component {
40734
40805
  if (this.props.stack) {
40735
40806
  className.push("stack");
40736
40807
  }
40737
- if (this.props.floating && !this.props.animating) {
40808
+ if (this.props.floating && !this.props.animating && this.props.mouse && this.props.startMouse) {
40738
40809
  className.push("dragging");
40739
40810
  style.left += this.props.mouse.left - this.props.startMouse.left;
40740
40811
  style.top += this.props.mouse.top - this.props.startMouse.top;
@@ -40759,14 +40830,14 @@ class Card extends React__namespace.Component {
40759
40830
  }
40760
40831
  }
40761
40832
  const NORMAL = "normal",
40762
- HORIZONTAL$1 = "horizontal";
40833
+ HORIZONTAL = "horizontal";
40763
40834
  class Orderer extends React__namespace.Component {
40764
40835
  static defaultProps = {
40765
40836
  current: [],
40766
40837
  options: [],
40767
40838
  correctOptions: [],
40768
40839
  height: NORMAL,
40769
- layout: HORIZONTAL$1,
40840
+ layout: HORIZONTAL,
40770
40841
  linterContext: PerseusLinter.linterContextDefault
40771
40842
  };
40772
40843
  state = {
@@ -40919,7 +40990,7 @@ class Orderer extends React__namespace.Component {
40919
40990
  };
40920
40991
  findCorrectIndex = (draggable, list) => {
40921
40992
  // Find the correct index for a card given the current cards.
40922
- const isHorizontal = this.props.layout === HORIZONTAL$1;
40993
+ const isHorizontal = this.props.layout === HORIZONTAL;
40923
40994
  // eslint-disable-next-line react/no-string-refs
40924
40995
  // @ts-expect-error - TS2769 - No overload matches this call.
40925
40996
  const $dragList = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.dragList));
@@ -40969,7 +41040,7 @@ class Orderer extends React__namespace.Component {
40969
41040
  if (draggable == null) {
40970
41041
  return false;
40971
41042
  }
40972
- const isHorizontal = this.props.layout === HORIZONTAL$1,
41043
+ const isHorizontal = this.props.layout === HORIZONTAL,
40973
41044
  // @ts-expect-error - TS2769 - No overload matches this call.
40974
41045
  $draggable = $__default["default"](ReactDOM__default["default"].findDOMNode(draggable)),
40975
41046
  // eslint-disable-next-line react/no-string-refs
@@ -41149,6 +41220,7 @@ ___default["default"].extend(Orderer, {
41149
41220
  var Orderer$1 = {
41150
41221
  name: "orderer",
41151
41222
  displayName: "Orderer",
41223
+ hidden: true,
41152
41224
  widget: Orderer,
41153
41225
  isLintable: true
41154
41226
  };
@@ -42958,13 +43030,10 @@ class Passage extends React__namespace.Component {
42958
43030
  startLineNumbersAfter: 0,
42959
43031
  stylesAreApplied: false
42960
43032
  };
42961
- static validate(state, rubric) {
42962
- return {
42963
- type: "points",
42964
- earned: 0,
42965
- total: 0,
42966
- message: null
42967
- };
43033
+
43034
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43035
+ static validate() {
43036
+ return noopValidator();
42968
43037
  }
42969
43038
  componentDidMount() {
42970
43039
  this._updateState();
@@ -43183,8 +43252,10 @@ class Passage extends React__namespace.Component {
43183
43252
  getUserInput() {
43184
43253
  return null;
43185
43254
  }
43186
- simpleValidate(rubric) {
43187
- return Passage.validate(this.getUserInput(), rubric);
43255
+
43256
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43257
+ simpleValidate() {
43258
+ return noopValidator();
43188
43259
  }
43189
43260
 
43190
43261
  /**
@@ -43325,6 +43396,7 @@ class Passage extends React__namespace.Component {
43325
43396
  var Passage$1 = {
43326
43397
  name: "passage",
43327
43398
  displayName: "Passage (SAT only)",
43399
+ hidden: true,
43328
43400
  widget: Passage,
43329
43401
  transform: editorProps => {
43330
43402
  return ___default["default"].pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
@@ -43339,17 +43411,18 @@ class PassageRefTarget extends React__namespace.Component {
43339
43411
  content: "",
43340
43412
  linterContext: PerseusLinter.linterContextDefault
43341
43413
  };
43342
- static validate(state, rubric) {
43343
- return {
43344
- type: "points",
43345
- earned: 0,
43346
- total: 0,
43347
- message: null
43348
- };
43414
+
43415
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43416
+ static validate() {
43417
+ return noopValidator();
43349
43418
  }
43419
+
43420
+ // TODO passage-ref-target isn't interactive; remove
43350
43421
  getUserInput = () => {
43351
- return WidgetJsonifyDeprecated.getUserInput.call(this);
43422
+ return;
43352
43423
  };
43424
+
43425
+ // TODO passage-ref-target isn't interactive; remove
43353
43426
  change = (() => {
43354
43427
  var _this = this;
43355
43428
  return function () {
@@ -43359,9 +43432,11 @@ class PassageRefTarget extends React__namespace.Component {
43359
43432
  return change.apply(_this, args);
43360
43433
  };
43361
43434
  })();
43362
- simpleValidate = rubric => {
43363
- return PassageRefTarget.validate(this.getUserInput(), rubric);
43364
- };
43435
+
43436
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43437
+ simpleValidate() {
43438
+ return noopValidator();
43439
+ }
43365
43440
  render() {
43366
43441
  return /*#__PURE__*/React__namespace.createElement(Renderer, {
43367
43442
  content: this.props.content
@@ -44417,6 +44492,7 @@ const staticTransform = ___default["default"].identity;
44417
44492
  var Plotter$1 = {
44418
44493
  name: "plotter",
44419
44494
  displayName: "Plotter",
44495
+ hidden: true,
44420
44496
  widget: Plotter,
44421
44497
  staticTransform: staticTransform
44422
44498
  };
@@ -44467,29 +44543,14 @@ var PythonProgram$1 = {
44467
44543
  widget: PythonProgram
44468
44544
  };
44469
44545
 
44470
- /* eslint-disable @khanacademy/ts-no-error-suppressions */
44471
44546
  const {
44472
44547
  shuffle
44473
44548
  } = Util;
44474
- const HORIZONTAL = "horizontal";
44475
- const VERTICAL = "vertical";
44476
44549
  class Sorter extends React__namespace.Component {
44477
- // @ts-expect-error - TS2564 - Property '_isMounted' has no initializer and is not definitely assigned in the constructor.
44478
- _isMounted;
44479
- static propTypes = {
44480
- apiOptions: ApiOptions.propTypes,
44481
- // eslint-disable-next-line react/forbid-prop-types
44482
- correct: PropTypes__default["default"].array,
44483
- layout: PropTypes__default["default"].oneOf([HORIZONTAL, VERTICAL]),
44484
- onChange: PropTypes__default["default"].func,
44485
- padding: PropTypes__default["default"].bool,
44486
- problemNum: PropTypes__default["default"].number,
44487
- trackInteraction: PropTypes__default["default"].func.isRequired,
44488
- linterContext: PerseusLinter.linterContextProps
44489
- };
44550
+ _isMounted = false;
44490
44551
  static defaultProps = {
44491
44552
  correct: [],
44492
- layout: HORIZONTAL,
44553
+ layout: "horizontal",
44493
44554
  padding: true,
44494
44555
  problemNum: 0,
44495
44556
  onChange: function () {},
@@ -44567,9 +44628,7 @@ class Sorter extends React__namespace.Component {
44567
44628
  const marginPx = apiOptions.isMobile ? 8 : 5;
44568
44629
  return /*#__PURE__*/React__namespace.createElement("div", {
44569
44630
  className: "perseus-widget-sorter perseus-clearfix"
44570
- }, /*#__PURE__*/React__namespace.createElement(Sortable
44571
- // @ts-expect-error - TS2322 - Type 'readonly unknown[]' is not assignable to type 'readonly string[]'.
44572
- , {
44631
+ }, /*#__PURE__*/React__namespace.createElement(Sortable, {
44573
44632
  options: options,
44574
44633
  layout: this.props.layout,
44575
44634
  margin: marginPx,
@@ -44592,7 +44651,6 @@ var Sorter$1 = {
44592
44651
  const {
44593
44652
  assert
44594
44653
  } = InteractiveUtil;
44595
-
44596
44654
  /* Input handling: Maps a (row, column) pair to a unique ref used by React,
44597
44655
  * and extracts (row, column) pairs from input paths, used to allow outsiders
44598
44656
  * to focus, blur, set input values, etc. */
@@ -44619,16 +44677,6 @@ const getRefForPath = function (path) {
44619
44677
  };
44620
44678
  class Table extends React__namespace.Component {
44621
44679
  static contextType = PerseusI18nContext;
44622
- static propTypes = {
44623
- answers: PropTypes__default["default"].arrayOf(PropTypes__default["default"].arrayOf(PropTypes__default["default"].string)),
44624
- editableHeaders: PropTypes__default["default"].bool,
44625
- // The editor to use when editableHeaders is enabled
44626
- Editor: PropTypes__default["default"].func,
44627
- headers: PropTypes__default["default"].arrayOf(PropTypes__default["default"].string),
44628
- keypadElement: mathInput.keypadElementPropType,
44629
- trackInteraction: PropTypes__default["default"].func.isRequired,
44630
- linterContext: PerseusLinter.linterContextProps
44631
- };
44632
44680
  static defaultProps = function () {
44633
44681
  const defaultRows = 4;
44634
44682
  const defaultColumns = 1;
@@ -44972,21 +45020,18 @@ class Video extends React__namespace.Component {
44972
45020
  * This is the widget's grading function.
44973
45021
  * Points for videos are tallied by the embedded video itself, in the case
44974
45022
  * of Khan Academy videos.
44975
- */
44976
- static validate(userInput, rubric) {
44977
- return {
44978
- type: "points",
44979
- earned: 0,
44980
- total: 0,
44981
- message: null
44982
- };
45023
+ */ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
45024
+ static validate() {
45025
+ return noopValidator();
44983
45026
  }
44984
45027
  getUserInput = () => {
44985
45028
  return null;
44986
45029
  };
44987
- simpleValidate = rubric => {
44988
- return Video.validate(null, rubric);
44989
- };
45030
+
45031
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
45032
+ simpleValidate() {
45033
+ return noopValidator();
45034
+ }
44990
45035
  change = (() => {
44991
45036
  var _this = this;
44992
45037
  return function () {
@@ -45099,7 +45144,7 @@ const init = function (options) {
45099
45144
 
45100
45145
  // This file is processed by a Rollup plugin (replace) to inject the production
45101
45146
  const libName = "@khanacademy/perseus";
45102
- const libVersion = "33.3.0";
45147
+ const libVersion = "34.1.0";
45103
45148
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45104
45149
 
45105
45150
  const apiVersion = {
@@ -48614,7 +48659,6 @@ exports.SizingUtils = sizingUtils;
48614
48659
  exports.TableWidget = Table$1;
48615
48660
  exports.Util = Util;
48616
48661
  exports.WIDGET_PROP_DENYLIST = denylist;
48617
- exports.WidgetJsonifyDeprecated = WidgetJsonifyDeprecated;
48618
48662
  exports.Widgets = widgets$1;
48619
48663
  exports.addWidget = addWidget;
48620
48664
  exports.apiVersion = apiVersion;