@khanacademy/perseus 33.2.1 → 34.0.0

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