@khanacademy/perseus 54.0.0 → 55.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 (43) hide show
  1. package/LICENSE +18 -0
  2. package/dist/components/number-input.d.ts +9 -10
  3. package/dist/components/range-input.d.ts +5 -6
  4. package/dist/components/simple-keypad-input.d.ts +5 -0
  5. package/dist/components/stub-tag-editor.d.ts +3 -4
  6. package/dist/components/text-list-editor.d.ts +3 -4
  7. package/dist/es/index.css +16 -6
  8. package/dist/es/index.js +919 -1066
  9. package/dist/es/index.js.map +1 -1
  10. package/dist/es/strings.js +40 -10
  11. package/dist/es/strings.js.map +1 -1
  12. package/dist/index.css +16 -6
  13. package/dist/index.js +1055 -1170
  14. package/dist/index.js.map +1 -1
  15. package/dist/mixins/changeable.d.ts +1 -2
  16. package/dist/perseus-api.d.ts +1 -56
  17. package/dist/shared-utils/add-library-version-to-perseus-debug.d.ts +9 -0
  18. package/dist/strings.d.ts +34 -4
  19. package/dist/strings.js +142 -97
  20. package/dist/strings.js.map +1 -1
  21. package/dist/types.d.ts +3 -1
  22. package/dist/util.d.ts +0 -5
  23. package/dist/widget-ai-utils/numeric-input/prompt-utils.d.ts +1 -1
  24. package/dist/widgets/categorizer/categorizer.d.ts +1 -1
  25. package/dist/widgets/dropdown/dropdown.d.ts +5 -4
  26. package/dist/widgets/expression/expression.d.ts +0 -2
  27. package/dist/widgets/interactive-graph.d.ts +4 -2
  28. package/dist/widgets/interactive-graphs/graphs/circle.d.ts +7 -0
  29. package/dist/widgets/interactive-graphs/graphs/components/movable-line.d.ts +6 -0
  30. package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +8 -0
  31. package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +7 -0
  32. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +1 -1
  33. package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +6 -1
  34. package/dist/widgets/numeric-input/index.d.ts +1 -1
  35. package/dist/widgets/numeric-input/input-with-examples.d.ts +8 -0
  36. package/dist/widgets/numeric-input/numeric-input.class.d.ts +66 -0
  37. package/dist/widgets/numeric-input/numeric-input.d.ts +123 -77
  38. package/dist/widgets/numeric-input/utils.d.ts +25 -0
  39. package/dist/widgets/radio/radio.d.ts +1 -1
  40. package/dist/widgets/sorter/sorter.d.ts +1 -1
  41. package/package.json +120 -124
  42. package/dist/components/input-with-examples.d.ts +0 -51
  43. package/dist/components/tooltip.d.ts +0 -85
package/dist/index.js CHANGED
@@ -2,12 +2,13 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var _extends = require('@babel/runtime/helpers/extends');
5
6
  var KAS = require('@khanacademy/kas');
6
7
  var mathInput = require('@khanacademy/math-input');
7
8
  var perseusCore = require('@khanacademy/perseus-core');
8
9
  var PerseusLinter = require('@khanacademy/perseus-linter');
9
10
  var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
10
- var Tooltip$1 = require('@khanacademy/wonder-blocks-tooltip');
11
+ var Tooltip = require('@khanacademy/wonder-blocks-tooltip');
11
12
  var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
12
13
  var aphrodite = require('aphrodite');
13
14
  var classNames$1 = require('classnames');
@@ -21,12 +22,13 @@ var $ = require('jquery');
21
22
  var uuid = require('uuid');
22
23
  var PropTypes = require('prop-types');
23
24
  var perseusScore = require('@khanacademy/perseus-score');
25
+ var keypadContext = require('@khanacademy/keypad-context');
26
+ var wonderBlocksForm = require('@khanacademy/wonder-blocks-form');
24
27
  var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
25
28
  var wonderBlocksProgressSpinner = require('@khanacademy/wonder-blocks-progress-spinner');
26
29
  var kmath = require('@khanacademy/kmath');
27
30
  var SimpleMarkdown = require('@khanacademy/simple-markdown');
28
31
  var pureMarkdown = require('@khanacademy/pure-markdown');
29
- var wonderBlocksForm = require('@khanacademy/wonder-blocks-form');
30
32
  var Button = require('@khanacademy/wonder-blocks-button');
31
33
  var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
32
34
  var Banner = require('@khanacademy/wonder-blocks-banner');
@@ -47,26 +49,27 @@ var Link = require('@khanacademy/wonder-blocks-link');
47
49
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
48
50
 
49
51
  function _interopNamespace(e) {
50
- if (e && e.__esModule) return e;
51
- var n = Object.create(null);
52
- if (e) {
53
- Object.keys(e).forEach(function (k) {
54
- if (k !== 'default') {
55
- var d = Object.getOwnPropertyDescriptor(e, k);
56
- Object.defineProperty(n, k, d.get ? d : {
57
- enumerable: true,
58
- get: function () { return e[k]; }
52
+ if (e && e.__esModule) return e;
53
+ var n = Object.create(null);
54
+ if (e) {
55
+ Object.keys(e).forEach(function (k) {
56
+ if (k !== 'default') {
57
+ var d = Object.getOwnPropertyDescriptor(e, k);
58
+ Object.defineProperty(n, k, d.get ? d : {
59
+ enumerable: true,
60
+ get: function () { return e[k]; }
61
+ });
62
+ }
59
63
  });
60
- }
61
- });
62
- }
63
- n["default"] = e;
64
- return Object.freeze(n);
64
+ }
65
+ n["default"] = e;
66
+ return Object.freeze(n);
65
67
  }
66
68
 
69
+ var _extends__default = /*#__PURE__*/_interopDefaultLegacy(_extends);
67
70
  var KAS__namespace = /*#__PURE__*/_interopNamespace(KAS);
68
71
  var PerseusLinter__namespace = /*#__PURE__*/_interopNamespace(PerseusLinter);
69
- var Tooltip__default = /*#__PURE__*/_interopDefaultLegacy(Tooltip$1);
72
+ var Tooltip__default = /*#__PURE__*/_interopDefaultLegacy(Tooltip);
70
73
  var classNames__default = /*#__PURE__*/_interopDefaultLegacy(classNames$1);
71
74
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
72
75
  var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
@@ -88,21 +91,6 @@ var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
88
91
  var cornersOutIcon__default = /*#__PURE__*/_interopDefaultLegacy(cornersOutIcon);
89
92
  var Link__default = /*#__PURE__*/_interopDefaultLegacy(Link);
90
93
 
91
- function _extends() {
92
- _extends = Object.assign ? Object.assign.bind() : function (target) {
93
- for (var i = 1; i < arguments.length; i++) {
94
- var source = arguments[i];
95
- for (var key in source) {
96
- if (Object.prototype.hasOwnProperty.call(source, key)) {
97
- target[key] = source[key];
98
- }
99
- }
100
- }
101
- return target;
102
- };
103
- return _extends.apply(this, arguments);
104
- }
105
-
106
94
  /**
107
95
  * The translated strings that are used to render Perseus.
108
96
  */
@@ -398,20 +386,27 @@ const mockStrings = {
398
386
  } = _ref29;
399
387
  return `Circle. The center point is at ${centerX} comma ${centerY}.`;
400
388
  },
401
- srCircleRadiusPoint: _ref30 => {
389
+ srCircleRadiusPointRight: _ref30 => {
402
390
  let {
403
391
  radiusPointX,
404
392
  radiusPointY
405
393
  } = _ref30;
406
- return `Radius point at ${radiusPointX} comma ${radiusPointY}.`;
394
+ return `Right radius endpoint at ${radiusPointX} comma ${radiusPointY}.`;
407
395
  },
408
- srCircleRadius: _ref31 => {
396
+ srCircleRadiusPointLeft: _ref31 => {
409
397
  let {
410
- radius
398
+ radiusPointX,
399
+ radiusPointY
411
400
  } = _ref31;
401
+ return `Left radius endpoint at ${radiusPointX} comma ${radiusPointY}.`;
402
+ },
403
+ srCircleRadius: _ref32 => {
404
+ let {
405
+ radius
406
+ } = _ref32;
412
407
  return `Circle radius is ${radius}.`;
413
408
  },
414
- srCircleOuterPoints: _ref32 => {
409
+ srCircleOuterPoints: _ref33 => {
415
410
  let {
416
411
  point1X,
417
412
  point1Y,
@@ -421,83 +416,83 @@ const mockStrings = {
421
416
  point3Y,
422
417
  point4X,
423
418
  point4Y
424
- } = _ref32;
419
+ } = _ref33;
425
420
  return `Points on the circle at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, ${point3X} comma ${point3Y}, ${point4X} comma ${point4Y}.`;
426
421
  },
427
422
  srLinearGraph: "A line on a coordinate plane.",
428
- srLinearGraphPoints: _ref33 => {
423
+ srLinearGraphPoints: _ref34 => {
429
424
  let {
430
425
  point1X,
431
426
  point1Y,
432
427
  point2X,
433
428
  point2Y
434
- } = _ref33;
429
+ } = _ref34;
435
430
  return `The line has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`;
436
431
  },
437
432
  srLinearGraphSlopeIncreasing: "Its slope increases from left to right.",
438
433
  srLinearGraphSlopeDecreasing: "Its slope decreases from left to right.",
439
434
  srLinearGraphSlopeHorizontal: "Its slope is zero.",
440
435
  srLinearGraphSlopeVertical: "Its slope is undefined.",
441
- srLinearGraphXOnlyIntercept: _ref34 => {
436
+ srLinearGraphXOnlyIntercept: _ref35 => {
442
437
  let {
443
438
  xIntercept
444
- } = _ref34;
439
+ } = _ref35;
445
440
  return `The line crosses the X-axis at ${xIntercept} comma 0.`;
446
441
  },
447
- srLinearGraphYOnlyIntercept: _ref35 => {
442
+ srLinearGraphYOnlyIntercept: _ref36 => {
448
443
  let {
449
444
  yIntercept
450
- } = _ref35;
445
+ } = _ref36;
451
446
  return `The line crosses the Y-axis at 0 comma ${yIntercept}.`;
452
447
  },
453
- srLinearGraphBothIntercepts: _ref36 => {
448
+ srLinearGraphBothIntercepts: _ref37 => {
454
449
  let {
455
450
  xIntercept,
456
451
  yIntercept
457
- } = _ref36;
452
+ } = _ref37;
458
453
  return `The line crosses the X-axis at ${xIntercept} comma 0 and the Y-axis at 0 comma ${yIntercept}.`;
459
454
  },
460
- srLinearGraphOriginIntercept: "The line crosses the x and y axes at the graph's origin.",
461
- srLinearGrabHandle: _ref37 => {
455
+ srLinearGraphOriginIntercept: "The line crosses the X and Y axes at the graph's origin.",
456
+ srLinearGrabHandle: _ref38 => {
462
457
  let {
463
458
  point1X,
464
459
  point1Y,
465
460
  point2X,
466
461
  point2Y
467
- } = _ref37;
468
- return `Line from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`;
462
+ } = _ref38;
463
+ return `Line going through point ${point1X} comma ${point1Y} and point ${point2X} comma ${point2Y}.`;
469
464
  },
470
- srAngleStartingSide: _ref38 => {
465
+ srAngleStartingSide: _ref39 => {
471
466
  let {
472
467
  x,
473
468
  y
474
- } = _ref38;
469
+ } = _ref39;
475
470
  return `Point 3, starting side at ${x} comma ${y}.`;
476
471
  },
477
- srAngleEndingSide: _ref39 => {
472
+ srAngleEndingSide: _ref40 => {
478
473
  let {
479
474
  x,
480
475
  y
481
- } = _ref39;
476
+ } = _ref40;
482
477
  return `Point 2, ending side at ${x} comma ${y}.`;
483
478
  },
484
- srAngleVertex: _ref40 => {
479
+ srAngleVertex: _ref41 => {
485
480
  let {
486
481
  x,
487
482
  y
488
- } = _ref40;
483
+ } = _ref41;
489
484
  return `Point 1, vertex at ${x} comma ${y}.`;
490
485
  },
491
- srAngleVertexWithAngleMeasure: _ref41 => {
486
+ srAngleVertexWithAngleMeasure: _ref42 => {
492
487
  let {
493
488
  x,
494
489
  y,
495
490
  angleMeasure
496
- } = _ref41;
491
+ } = _ref42;
497
492
  return `Point 1, vertex at ${x} comma ${y}. Angle ${angleMeasure} degrees.`;
498
493
  },
499
494
  srAngleGraphAriaLabel: "An angle on a coordinate plane.",
500
- srAngleGraphAriaDescription: _ref42 => {
495
+ srAngleGraphAriaDescription: _ref43 => {
501
496
  let {
502
497
  angleMeasure,
503
498
  vertexX,
@@ -506,10 +501,10 @@ const mockStrings = {
506
501
  startingSideY,
507
502
  endingSideX,
508
503
  endingSideY
509
- } = _ref42;
504
+ } = _ref43;
510
505
  return `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}.`;
511
506
  },
512
- srAngleInteractiveElements: _ref43 => {
507
+ srAngleInteractiveElements: _ref44 => {
513
508
  let {
514
509
  vertexX,
515
510
  vertexY,
@@ -517,118 +512,136 @@ const mockStrings = {
517
512
  startingSideY,
518
513
  endingSideX,
519
514
  endingSideY
520
- } = _ref43;
515
+ } = _ref44;
521
516
  return `An angle formed by 3 points. The vertex is at ${vertexX} comma ${vertexY}. The starting side point is at ${startingSideX} comma ${startingSideY}. The ending side point is at ${endingSideX} comma ${endingSideY}.`;
522
517
  },
523
518
  srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
524
- srMultipleSegmentGraphAriaLabel: _ref44 => {
519
+ srMultipleSegmentGraphAriaLabel: _ref45 => {
525
520
  let {
526
521
  countOfSegments
527
- } = _ref44;
522
+ } = _ref45;
528
523
  return `${countOfSegments} segments on a coordinate plane.`;
529
524
  },
530
- srMultipleSegmentIndividualLabel: _ref45 => {
525
+ srMultipleSegmentIndividualLabel: _ref46 => {
531
526
  let {
532
527
  point1X,
533
528
  point1Y,
534
529
  point2X,
535
530
  point2Y,
536
531
  indexOfSegment
537
- } = _ref45;
532
+ } = _ref46;
538
533
  return `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`;
539
534
  },
540
- srSingleSegmentLabel: _ref46 => {
535
+ srSingleSegmentLabel: _ref47 => {
541
536
  let {
542
537
  point1X,
543
538
  point1Y,
544
539
  point2X,
545
540
  point2Y
546
- } = _ref46;
541
+ } = _ref47;
547
542
  return `Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`;
548
543
  },
549
- srSegmentLength: _ref47 => {
544
+ srSegmentLength: _ref48 => {
550
545
  let {
551
546
  length
552
- } = _ref47;
547
+ } = _ref48;
553
548
  return `Segment length ${length} units.`;
554
549
  },
555
- srSingleSegmentGraphEndpointAriaLabel: _ref48 => {
550
+ srSingleSegmentGraphEndpointAriaLabel: _ref49 => {
556
551
  let {
557
552
  endpointNumber,
558
553
  x,
559
554
  y
560
- } = _ref48;
555
+ } = _ref49;
561
556
  return `Endpoint ${endpointNumber} at ${x} comma ${y}.`;
562
557
  },
563
- srMultipleSegmentGraphEndpointAriaLabel: _ref49 => {
558
+ srMultipleSegmentGraphEndpointAriaLabel: _ref50 => {
564
559
  let {
565
560
  endpointNumber,
566
561
  x,
567
562
  y,
568
563
  indexOfSegment
569
- } = _ref49;
564
+ } = _ref50;
570
565
  return `Endpoint ${endpointNumber} on segment ${indexOfSegment} at ${x} comma ${y}.`;
571
566
  },
572
- srSegmentGrabHandle: _ref50 => {
567
+ srSegmentGrabHandle: _ref51 => {
573
568
  let {
574
569
  point1X,
575
570
  point1Y,
576
571
  point2X,
577
572
  point2Y
578
- } = _ref50;
573
+ } = _ref51;
579
574
  return `Segment from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`;
580
575
  },
581
576
  srLinearSystemGraph: "Two lines on a coordinate plane.",
582
- srLinearSystemPoints: _ref51 => {
577
+ srLinearSystemPoints: _ref52 => {
583
578
  let {
584
579
  lineNumber,
585
580
  point1X,
586
581
  point1Y,
587
582
  point2X,
588
583
  point2Y
589
- } = _ref51;
584
+ } = _ref52;
590
585
  return `Line ${lineNumber} has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`;
591
586
  },
592
- srLinearSystemPoint: _ref52 => {
587
+ srLinearSystemPoint: _ref53 => {
593
588
  let {
594
589
  lineNumber,
595
590
  pointSequence,
596
591
  x,
597
592
  y
598
- } = _ref52;
593
+ } = _ref53;
599
594
  return `Point ${pointSequence} on line ${lineNumber} at ${x} comma ${y}.`;
600
595
  },
596
+ srLinearSystemGrabHandle: _ref54 => {
597
+ let {
598
+ lineNumber,
599
+ point1X,
600
+ point1Y,
601
+ point2X,
602
+ point2Y
603
+ } = _ref54;
604
+ return `Line ${lineNumber} going through point ${point1X} comma ${point1Y} and point ${point2X} comma ${point2Y}.`;
605
+ },
606
+ srLinearSystemIntersection: _ref55 => {
607
+ let {
608
+ x,
609
+ y
610
+ } = _ref55;
611
+ return `Line 1 and line 2 intersect at point ${x} comma ${y}.`;
612
+ },
613
+ srLinearSystemParallel: "Line 1 and line 2 are parallel.",
601
614
  srRayGraph: "A ray on a coordinate plane.",
602
- srRayPoints: _ref53 => {
615
+ srRayPoints: _ref56 => {
603
616
  let {
604
617
  point1X,
605
618
  point1Y,
606
619
  point2X,
607
620
  point2Y
608
- } = _ref53;
621
+ } = _ref56;
609
622
  return `The endpoint is at ${point1X} comma ${point1Y} and the ray goes through point ${point2X} comma ${point2Y}.`;
610
623
  },
611
- srRayGrabHandle: _ref54 => {
624
+ srRayGrabHandle: _ref57 => {
612
625
  let {
613
626
  point1X,
614
627
  point1Y,
615
628
  point2X,
616
629
  point2Y
617
- } = _ref54;
630
+ } = _ref57;
618
631
  return `Ray with endpoint ${point1X} comma ${point1Y} going through point ${point2X} comma ${point2Y}.`;
619
632
  },
620
- srRayEndpoint: _ref55 => {
633
+ srRayEndpoint: _ref58 => {
621
634
  let {
622
635
  x,
623
636
  y
624
- } = _ref55;
637
+ } = _ref58;
625
638
  return `Endpoint at ${x} comma ${y}.`;
626
639
  },
627
- srRayTerminalPoint: _ref56 => {
640
+ srRayTerminalPoint: _ref59 => {
628
641
  let {
629
642
  x,
630
643
  y
631
- } = _ref56;
644
+ } = _ref59;
632
645
  return `Through point at ${x} comma ${y}.`;
633
646
  },
634
647
  srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
@@ -637,55 +650,55 @@ const mockStrings = {
637
650
  srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
638
651
  srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
639
652
  srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
640
- srQuadraticGraphVertexQuadrant: _ref57 => {
653
+ srQuadraticGraphVertexQuadrant: _ref60 => {
641
654
  let {
642
655
  quadrant
643
- } = _ref57;
656
+ } = _ref60;
644
657
  return `Vertex is in quadrant ${quadrant}.`;
645
658
  },
646
- srQuadraticTwoXIntercepts: _ref58 => {
659
+ srQuadraticTwoXIntercepts: _ref61 => {
647
660
  let {
648
661
  intercept1,
649
662
  intercept2
650
- } = _ref58;
663
+ } = _ref61;
651
664
  return `The X-intercepts are at ${intercept1} comma 0 and ${intercept2} comma 0.`;
652
665
  },
653
- srQuadraticOneXIntercept: _ref59 => {
666
+ srQuadraticOneXIntercept: _ref62 => {
654
667
  let {
655
668
  intercept
656
- } = _ref59;
669
+ } = _ref62;
657
670
  return `The X-intercept is at ${intercept} comma 0.`;
658
671
  },
659
- srQuadraticYIntercept: _ref60 => {
672
+ srQuadraticYIntercept: _ref63 => {
660
673
  let {
661
674
  intercept
662
- } = _ref60;
675
+ } = _ref63;
663
676
  return `The Y-intercept is at 0 comma ${intercept}.`;
664
677
  },
665
- srQuadraticPointOrigin: _ref61 => {
678
+ srQuadraticPointOrigin: _ref64 => {
666
679
  let {
667
680
  pointNumber
668
- } = _ref61;
681
+ } = _ref64;
669
682
  return `Point ${pointNumber} on parabola at the origin.`;
670
683
  },
671
- srQuadraticPointAxis: _ref62 => {
684
+ srQuadraticPointAxis: _ref65 => {
672
685
  let {
673
686
  pointNumber,
674
687
  x,
675
688
  y
676
- } = _ref62;
689
+ } = _ref65;
677
690
  return `Point ${pointNumber} on parabola at ${x} comma ${y}.`;
678
691
  },
679
- srQuadraticPointQuadrant: _ref63 => {
692
+ srQuadraticPointQuadrant: _ref66 => {
680
693
  let {
681
694
  pointNumber,
682
695
  x,
683
696
  y,
684
697
  quadrant
685
- } = _ref63;
698
+ } = _ref66;
686
699
  return `Point ${pointNumber} on parabola in quadrant ${quadrant} at ${x} comma ${y}.`;
687
700
  },
688
- srQuadraticInteractiveElements: _ref64 => {
701
+ srQuadraticInteractiveElements: _ref67 => {
689
702
  let {
690
703
  point1X,
691
704
  point1Y,
@@ -693,83 +706,97 @@ const mockStrings = {
693
706
  point2Y,
694
707
  point3X,
695
708
  point3Y
696
- } = _ref64;
709
+ } = _ref67;
697
710
  return `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`;
698
711
  },
699
712
  srPolygonGraph: "A polygon.",
700
713
  srPolygonGraphCoordinatePlane: "A polygon on a coordinate plane.",
701
- srPolygonGraphPointsNum: _ref65 => {
714
+ srPolygonGraphPointsNum: _ref68 => {
702
715
  let {
703
716
  num
704
- } = _ref65;
717
+ } = _ref68;
705
718
  return `The polygon has ${num} points.`;
706
719
  },
707
720
  srPolygonGraphPointsOne: "The polygon has 1 point.",
708
- srPolygonElementsNum: _ref66 => {
721
+ srPolygonElementsNum: _ref69 => {
709
722
  let {
710
723
  num
711
- } = _ref66;
724
+ } = _ref69;
712
725
  return `A polygon with ${num} points.`;
713
726
  },
714
727
  srPolygonElementsOne: "A polygon with 1 point.",
715
- srPolygonPointAngleApprox: _ref67 => {
728
+ srPolygonPointAngleApprox: _ref70 => {
716
729
  let {
717
730
  angle
718
- } = _ref67;
731
+ } = _ref70;
719
732
  return `Angle approximately equal to ${angle} degrees.`;
720
733
  },
721
- srPolygonPointAngle: _ref68 => {
734
+ srPolygonPointAngle: _ref71 => {
722
735
  let {
723
736
  angle
724
- } = _ref68;
737
+ } = _ref71;
725
738
  return `Angle equal to ${angle} degrees.`;
726
739
  },
727
- srPolygonSideLength: _ref69 => {
740
+ srPolygonSideLength: _ref72 => {
728
741
  let {
729
742
  pointNum,
730
743
  length
731
- } = _ref69;
744
+ } = _ref72;
732
745
  return `A line segment, length equal to ${length} units, connects to point ${pointNum}.`;
733
746
  },
734
- srPolygonSideLengthApprox: _ref70 => {
747
+ srPolygonSideLengthApprox: _ref73 => {
735
748
  let {
736
749
  pointNum,
737
750
  length
738
- } = _ref70;
751
+ } = _ref73;
739
752
  return `A line segment, length approximately equal to ${length} units, connects to point ${pointNum}.`;
740
753
  },
741
754
  srUnlimitedPolygonEmpty: "An empty coordinate plane.",
742
755
  srSinusoidGraph: "A sinusoid function on a coordinate plane.",
743
- srSinusoidRootPoint: _ref71 => {
756
+ srSinusoidRootPoint: _ref74 => {
744
757
  let {
745
758
  x,
746
759
  y
747
- } = _ref71;
760
+ } = _ref74;
748
761
  return `Midline intersection at ${x} comma ${y}.`;
749
762
  },
750
- srSinusoidPeakPoint: _ref72 => {
763
+ srSinusoidMaxPoint: _ref75 => {
751
764
  let {
752
765
  x,
753
766
  y
754
- } = _ref72;
755
- return `Extremum point at ${x} comma ${y}.`;
767
+ } = _ref75;
768
+ return `Maximum point at ${x} comma ${y}.`;
769
+ },
770
+ srSinusoidMinPoint: _ref76 => {
771
+ let {
772
+ x,
773
+ y
774
+ } = _ref76;
775
+ return `Minimum point at ${x} comma ${y}.`;
756
776
  },
757
- srSinusoidDescription: _ref73 => {
777
+ srSinusoidFlatPoint: _ref77 => {
778
+ let {
779
+ x,
780
+ y
781
+ } = _ref77;
782
+ return `Line through point at ${x} comma ${y}.`;
783
+ },
784
+ srSinusoidDescription: _ref78 => {
758
785
  let {
759
786
  minValue,
760
787
  maxValue,
761
788
  cycleStart,
762
789
  cycleEnd
763
- } = _ref73;
790
+ } = _ref78;
764
791
  return `The graph shows a wave with a minimum value of ${minValue} and a maximum value of ${maxValue}. The wave completes a full cycle from ${cycleStart} to ${cycleEnd}.`;
765
792
  },
766
- srSinusoidInteractiveElements: _ref74 => {
793
+ srSinusoidInteractiveElements: _ref79 => {
767
794
  let {
768
795
  point1X,
769
796
  point1Y,
770
797
  point2X,
771
798
  point2Y
772
- } = _ref74;
799
+ } = _ref79;
773
800
  return `Sinusoid graph with midline intersection point at ${point1X} comma ${point1Y} and extremum point at ${point2X} comma ${point2Y}.`;
774
801
  }
775
802
  // The above strings are used for interactive graph SR descriptions.
@@ -1103,7 +1130,7 @@ class InnerMathInput extends React__namespace.Component {
1103
1130
  }, this.context.strings.mathInputDescription), /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContentCore, {
1104
1131
  closeButtonVisible: true,
1105
1132
  style: styles$D.popoverContent
1106
- }, /*#__PURE__*/React__namespace.createElement(mathInput.DesktopKeypad, _extends({
1133
+ }, /*#__PURE__*/React__namespace.createElement(mathInput.DesktopKeypad, _extends__default["default"]({
1107
1134
  onAnalyticsEvent: this.props.onAnalyticsEvent,
1108
1135
  extraKeys: this.props.extraKeys,
1109
1136
  onClickKey: this.handleKeypadPress,
@@ -1118,7 +1145,7 @@ class InnerMathInput extends React__namespace.Component {
1118
1145
  "aria-label": this.state.keypadOpen ? this.context.strings.closeKeypad : this.context.strings.openKeypad,
1119
1146
  role: "button",
1120
1147
  onClick: () => this.state.keypadOpen ? this.closeKeypad() : this.openKeypad()
1121
- }, props => /*#__PURE__*/React__namespace.createElement(MathInputIcon, _extends({
1148
+ }, props => /*#__PURE__*/React__namespace.createElement(MathInputIcon, _extends__default["default"]({
1122
1149
  active: this.state.keypadOpen
1123
1150
  }, props))))));
1124
1151
  }
@@ -1143,7 +1170,7 @@ class MathInput extends React__namespace.Component {
1143
1170
  this.inputRef.current?.insert(value);
1144
1171
  }
1145
1172
  render() {
1146
- return /*#__PURE__*/React__namespace.createElement(InnerMathInput, _extends({}, this.props, {
1173
+ return /*#__PURE__*/React__namespace.createElement(InnerMathInput, _extends__default["default"]({}, this.props, {
1147
1174
  ref: this.inputRef,
1148
1175
  mathInputStrings: this.context.strings
1149
1176
  }));
@@ -1285,11 +1312,11 @@ const useDependencies = () => {
1285
1312
  };
1286
1313
 
1287
1314
  var dependencies = /*#__PURE__*/Object.freeze({
1288
- __proto__: null,
1289
- setDependencies: setDependencies,
1290
- getDependencies: getDependencies,
1291
- DependenciesContext: DependenciesContext,
1292
- useDependencies: useDependencies
1315
+ __proto__: null,
1316
+ setDependencies: setDependencies,
1317
+ getDependencies: getDependencies,
1318
+ DependenciesContext: DependenciesContext,
1319
+ useDependencies: useDependencies
1293
1320
  });
1294
1321
 
1295
1322
  /**
@@ -1393,9 +1420,9 @@ const propTypes = {
1393
1420
  };
1394
1421
 
1395
1422
  var changeable = /*#__PURE__*/Object.freeze({
1396
- __proto__: null,
1397
- change: change,
1398
- propTypes: propTypes
1423
+ __proto__: null,
1424
+ change: change,
1425
+ propTypes: propTypes
1399
1426
  });
1400
1427
 
1401
1428
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -2054,7 +2081,7 @@ const keypadConfigurationForProps = widgetOptions => {
2054
2081
  };
2055
2082
  const ExpressionWithDependencies = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
2056
2083
  const deps = useDependencies();
2057
- return /*#__PURE__*/React__namespace.createElement(Expression, _extends({
2084
+ return /*#__PURE__*/React__namespace.createElement(Expression, _extends__default["default"]({
2058
2085
  ref: ref,
2059
2086
  analytics: deps.analytics
2060
2087
  }, props));
@@ -2105,6 +2132,174 @@ var Expression$1 = {
2105
2132
  }
2106
2133
  };
2107
2134
 
2135
+ class SimpleKeypadInput extends React__namespace.Component {
2136
+ static contextType = keypadContext.KeypadContext;
2137
+ _isMounted = false;
2138
+ inputRef = /*#__PURE__*/React__namespace.createRef();
2139
+ componentDidMount() {
2140
+ // TODO(scottgrant): This is a hack to remove the deprecated call to
2141
+ // this.isMounted() but is still considered an anti-pattern.
2142
+ this._isMounted = true;
2143
+ }
2144
+ componentWillUnmount() {
2145
+ this._isMounted = false;
2146
+ }
2147
+ focus() {
2148
+ // The inputRef is a ref to a MathInput, which
2149
+ // also controls the keypad state during focus events.
2150
+ this.inputRef.current?.focus(this.context.setKeypadActive);
2151
+ }
2152
+ blur() {
2153
+ this.inputRef.current?.blur();
2154
+ }
2155
+ getValue() {
2156
+ return this.props.value;
2157
+ }
2158
+ render() {
2159
+ const _this = this;
2160
+ // Intercept the `onFocus` prop, as we need to configure the keypad
2161
+ // before continuing with the default focus logic for Perseus inputs.
2162
+ // Intercept the `value` prop so as to map `null` to the empty string,
2163
+ // as the `KeypadInput` does not support `null` values.
2164
+ const {
2165
+ keypadElement,
2166
+ onFocus,
2167
+ value,
2168
+ ...rest
2169
+ } = _this.props;
2170
+ return (
2171
+ /*#__PURE__*/
2172
+ // @ts-expect-error - TS2769 - No overload matches this call.
2173
+ React__namespace.createElement(mathInput.KeypadInput, _extends__default["default"]({
2174
+ ref: this.inputRef,
2175
+ keypadElement: keypadElement,
2176
+ onFocus: () => {
2177
+ if (keypadElement) {
2178
+ keypadElement.configure({
2179
+ keypadType: mathInput.KeypadType.FRACTION
2180
+ }, () => {
2181
+ if (_this._isMounted) {
2182
+ onFocus?.();
2183
+ }
2184
+ });
2185
+ } else {
2186
+ onFocus?.();
2187
+ }
2188
+ },
2189
+ value: value == null ? "" : "" + value
2190
+ }, rest))
2191
+ );
2192
+ }
2193
+ }
2194
+
2195
+ // @ts-expect-error - TS2339 - Property 'propTypes' does not exist on type 'typeof SimpleKeypadInput'.
2196
+ SimpleKeypadInput.propTypes = {
2197
+ keypadElement: mathInput.keypadElementPropType,
2198
+ onFocus: PropTypes__default["default"].func,
2199
+ value: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number])
2200
+ };
2201
+
2202
+ const getPromptJSON$s = (renderProps, userInput) => {
2203
+ return {
2204
+ type: "input-number",
2205
+ options: {
2206
+ simplify: renderProps.simplify,
2207
+ answerType: renderProps.answerType
2208
+ },
2209
+ userInput: {
2210
+ value: userInput.currentValue
2211
+ }
2212
+ };
2213
+ };
2214
+
2215
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
2216
+ let lastId = 0;
2217
+ function uniqueIdForInput() {
2218
+ let prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "input-";
2219
+ lastId++;
2220
+ return `${prefix}${lastId}`;
2221
+ }
2222
+ class TextInput extends React__namespace.Component {
2223
+ inputRef = /*#__PURE__*/React__namespace.createRef();
2224
+ static defaultProps = {
2225
+ value: "",
2226
+ disabled: false
2227
+ };
2228
+ id;
2229
+ constructor(props) {
2230
+ super(props);
2231
+ if (props.id) {
2232
+ this.id = props.id;
2233
+ } else {
2234
+ this.id = uniqueIdForInput();
2235
+ }
2236
+ }
2237
+ _getInput = () => {
2238
+ if (!this.inputRef.current) {
2239
+ throw new perseusCore.PerseusError("Input ref accessed before set", perseusCore.Errors.Internal);
2240
+ }
2241
+ return this.inputRef.current;
2242
+ };
2243
+ focus = () => {
2244
+ this._getInput().focus();
2245
+ };
2246
+ blur = () => {
2247
+ this._getInput().blur();
2248
+ };
2249
+ getValue = () => {
2250
+ return this.inputRef.current?.value;
2251
+ };
2252
+ getStringValue = () => {
2253
+ return this.inputRef.current?.value.toString();
2254
+ };
2255
+ setSelectionRange = (selectionStart, selectionEnd) => {
2256
+ this._getInput().setSelectionRange(selectionStart, selectionEnd);
2257
+ };
2258
+ getSelectionStart = () => {
2259
+ return this._getInput().selectionStart;
2260
+ };
2261
+ getSelectionEnd = () => {
2262
+ return this._getInput().selectionEnd;
2263
+ };
2264
+ render() {
2265
+ const {
2266
+ labelText,
2267
+ value,
2268
+ onFocus,
2269
+ onBlur,
2270
+ disabled,
2271
+ placeholder,
2272
+ onKeyDown,
2273
+ style
2274
+ } = this.props;
2275
+ const formattedValue = value === null ? "" : value.toString();
2276
+
2277
+ // Some of our content was saved with empty strings as the label text,
2278
+ // and we don't want to render an empty aria-label attribute.
2279
+ const ariaLabel = labelText || undefined;
2280
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
2281
+ ref: this.inputRef,
2282
+ style: style,
2283
+ disabled: disabled,
2284
+ id: this.id,
2285
+ value: formattedValue,
2286
+ type: "text",
2287
+ "aria-label": ariaLabel,
2288
+ "aria-describedby": this.props["aria-describedby"],
2289
+ onChange: value => this.props.onChange(value),
2290
+ placeholder: placeholder,
2291
+ onFocus: onFocus,
2292
+ onBlur: onBlur,
2293
+ onKeyDown: onKeyDown
2294
+ // @ts-expect-error - TS2322 - Type '{ style: StyleType; disabled: boolean | undefined; id: string; value: string; type: "text"; "aria-label": string | undefined; onChange: (value: string) => void; placeholder: string | undefined; ... 5 more ...; autoComplete: string; }' is not assignable to type 'IntrinsicAttributes & ExportProps & RefAttributes<HTMLInputElement>'.
2295
+ ,
2296
+ autoCorrect: "off",
2297
+ autoCapitalize: "off",
2298
+ autoComplete: "off"
2299
+ });
2300
+ }
2301
+ }
2302
+
2108
2303
  /**
2109
2304
  * A React context for keeping track of whether an component is fully
2110
2305
  * loaded. This is used to know when SvgImage and (Zoomable)TeX are finished
@@ -2197,55 +2392,6 @@ const rWidgetRule = /^\[\[\u2603 (([a-z-]+) ([0-9]+))\]\]/;
2197
2392
  const rTypeFromWidgetId = /^([a-z-]+) ([0-9]+)$/;
2198
2393
  const rWidgetParts = new RegExp(rWidgetRule.source + "$");
2199
2394
  const snowman = "\u2603";
2200
- const seededRNG$1 = function (seed) {
2201
- let randomSeed = seed;
2202
- return function () {
2203
- // Robert Jenkins' 32 bit integer hash function.
2204
- let seed = randomSeed;
2205
- seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
2206
- seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
2207
- seed = seed + 0x165667b1 + (seed << 5) & 0xffffffff;
2208
- seed = (seed + 0xd3a2646c ^ seed << 9) & 0xffffffff;
2209
- seed = seed + 0xfd7046c5 + (seed << 3) & 0xffffffff;
2210
- seed = (seed ^ 0xb55a4f09 ^ seed >>> 16) & 0xffffffff;
2211
- return (randomSeed = seed & 0xfffffff) / 0x10000000;
2212
- };
2213
- };
2214
-
2215
- // Shuffle an array using a given random seed or function.
2216
- // If `ensurePermuted` is true, the input and ouput are guaranteed to be
2217
- // distinct permutations.
2218
- function shuffle$3(array, randomSeed) {
2219
- let ensurePermuted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
2220
- // Always return a copy of the input array
2221
- const shuffled = ___default["default"].clone(array);
2222
-
2223
- // Handle edge cases (input array is empty or uniform)
2224
- if (!shuffled.length || ___default["default"].all(shuffled, function (value) {
2225
- return ___default["default"].isEqual(value, shuffled[0]);
2226
- })) {
2227
- return shuffled;
2228
- }
2229
- let random;
2230
- if (typeof randomSeed === "function") {
2231
- random = randomSeed;
2232
- } else {
2233
- random = seededRNG$1(randomSeed);
2234
- }
2235
- do {
2236
- // Fischer-Yates shuffle
2237
- for (let top = shuffled.length; top > 0; top--) {
2238
- const newEnd = Math.floor(random() * top);
2239
- const temp = shuffled[newEnd];
2240
-
2241
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
2242
- shuffled[newEnd] = shuffled[top - 1];
2243
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
2244
- shuffled[top - 1] = temp;
2245
- }
2246
- } while (ensurePermuted && ___default["default"].isEqual(array, shuffled));
2247
- return shuffled;
2248
- }
2249
2395
 
2250
2396
  /**
2251
2397
  * TODO(somewhatabstract, FEI-3463):
@@ -2676,7 +2822,6 @@ const textarea = {
2676
2822
  * to translate it but don't need the extra dollar signs.
2677
2823
  */
2678
2824
  const unescapeMathMode$1 = label => label.startsWith("$") && label.endsWith("$") ? label.slice(1, -1) : label;
2679
- const random$1 = seededRNG$1(new Date().getTime() & 0xffffffff);
2680
2825
  const Util = {
2681
2826
  inputPathsEqual,
2682
2827
  nestedMap: nestedMap$2,
@@ -2684,8 +2829,6 @@ const Util = {
2684
2829
  rTypeFromWidgetId,
2685
2830
  rWidgetParts,
2686
2831
  snowman,
2687
- seededRNG: seededRNG$1,
2688
- shuffle: shuffle$3,
2689
2832
  split,
2690
2833
  firstNumericalParse: firstNumericalParse$1,
2691
2834
  stringArrayOfSize: stringArrayOfSize$1,
@@ -2713,16 +2856,17 @@ const Util = {
2713
2856
  getSvgUrl: getSvgUrl,
2714
2857
  getDataUrl: getDataUrl,
2715
2858
  textarea,
2716
- unescapeMathMode: unescapeMathMode$1,
2717
- random: random$1
2859
+ unescapeMathMode: unescapeMathMode$1
2718
2860
  };
2719
2861
 
2720
2862
  // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
2721
2863
  // getDependencies() internally... simplify usage to just using a "singleton"
2722
2864
  // which get's the injected logger.
2865
+
2723
2866
  /**
2724
2867
  * Provides logging infrastructure for Perseus
2725
2868
  */
2869
+
2726
2870
  const Log = {
2727
2871
  // Logs a message
2728
2872
  log: (message, extra) => {
@@ -3396,40 +3540,40 @@ const articleMaxWidthInPx = 688;
3396
3540
  const articleMaxWidthTableInPx = 512;
3397
3541
 
3398
3542
  var constants = /*#__PURE__*/Object.freeze({
3399
- __proto__: null,
3400
- baseFontFamily: baseFontFamily,
3401
- boldFontFamily: boldFontFamily,
3402
- kaGreen: kaGreen,
3403
- white: white,
3404
- gray97: gray97,
3405
- gray85: gray85,
3406
- gray76: gray76,
3407
- gray68: gray68,
3408
- gray17: gray17,
3409
- pureSmMin: pureSmMin$1,
3410
- pureMdMin: pureMdMin$1,
3411
- pureLgMin: pureLgMin$1,
3412
- pureXlMin: pureXlMin$1,
3413
- pureXsMax: pureXsMax$1,
3414
- pureSmMax: pureSmMax$1,
3415
- pureMdMax: pureMdMax$1,
3416
- pureLgMax: pureLgMax$1,
3417
- tableBackgroundAccent: tableBackgroundAccent,
3418
- phoneMargin: phoneMargin,
3419
- negativePhoneMargin: negativePhoneMargin,
3420
- hintBorderWidth: hintBorderWidth,
3421
- baseUnitPx: baseUnitPx,
3422
- interactiveSizes: interactiveSizes$1,
3423
- circleSize: circleSize$1,
3424
- radioMarginWidth: radioMarginWidth$1,
3425
- warningColor: warningColor,
3426
- warningColorHover: warningColorHover,
3427
- warningColorActive: warningColorActive,
3428
- publishBlockingErrorColor: publishBlockingErrorColor,
3429
- radioBorderColor: radioBorderColor$1,
3430
- checkedColor: checkedColor$1,
3431
- articleMaxWidthInPx: articleMaxWidthInPx,
3432
- articleMaxWidthTableInPx: articleMaxWidthTableInPx
3543
+ __proto__: null,
3544
+ baseFontFamily: baseFontFamily,
3545
+ boldFontFamily: boldFontFamily,
3546
+ kaGreen: kaGreen,
3547
+ white: white,
3548
+ gray97: gray97,
3549
+ gray85: gray85,
3550
+ gray76: gray76,
3551
+ gray68: gray68,
3552
+ gray17: gray17,
3553
+ pureSmMin: pureSmMin$1,
3554
+ pureMdMin: pureMdMin$1,
3555
+ pureLgMin: pureLgMin$1,
3556
+ pureXlMin: pureXlMin$1,
3557
+ pureXsMax: pureXsMax$1,
3558
+ pureSmMax: pureSmMax$1,
3559
+ pureMdMax: pureMdMax$1,
3560
+ pureLgMax: pureLgMax$1,
3561
+ tableBackgroundAccent: tableBackgroundAccent,
3562
+ phoneMargin: phoneMargin,
3563
+ negativePhoneMargin: negativePhoneMargin,
3564
+ hintBorderWidth: hintBorderWidth,
3565
+ baseUnitPx: baseUnitPx,
3566
+ interactiveSizes: interactiveSizes$1,
3567
+ circleSize: circleSize$1,
3568
+ radioMarginWidth: radioMarginWidth$1,
3569
+ warningColor: warningColor,
3570
+ warningColorHover: warningColorHover,
3571
+ warningColorActive: warningColorActive,
3572
+ publishBlockingErrorColor: publishBlockingErrorColor,
3573
+ radioBorderColor: radioBorderColor$1,
3574
+ checkedColor: checkedColor$1,
3575
+ articleMaxWidthInPx: articleMaxWidthInPx,
3576
+ articleMaxWidthTableInPx: articleMaxWidthTableInPx
3433
3577
  });
3434
3578
 
3435
3579
  /**
@@ -3844,7 +3988,7 @@ var Raphael$1 = (function () {
3844
3988
  appendChild = "appendChild",
3845
3989
  apply = "apply",
3846
3990
  concat = "concat",
3847
- supportsTouch = ("createTouch" in doc),
3991
+ supportsTouch = "createTouch" in doc,
3848
3992
  E = "",
3849
3993
  S = " ",
3850
3994
  Str = String,
@@ -5449,10 +5593,10 @@ var Raphael$1 = (function () {
5449
5593
  });
5450
5594
  break;
5451
5595
  case "gradient":
5452
- (({
5596
+ ({
5453
5597
  circle: 1,
5454
5598
  ellipse: 1
5455
- })[has](o.type) || Str(value).charAt() != "r") && addGradientFill(node, value, o.paper);
5599
+ }[has](o.type) || Str(value).charAt() != "r") && addGradientFill(node, value, o.paper);
5456
5600
  break;
5457
5601
  case "opacity":
5458
5602
  if (attrs.gradient && !attrs[has]("stroke-opacity")) {
@@ -9285,8 +9429,10 @@ const labelDirections = {
9285
9429
  // is equivalent to [[-5, 5], [-10, 10]].
9286
9430
  // - A single magnitude to be used for both x and y. E.g. 10 is equivalent to
9287
9431
  // [[-10, 10], [-10, 10]]
9432
+
9288
9433
  // Overload normalizeRange to specify that it only returns undefined if its
9289
9434
  // argument is undefined.
9435
+
9290
9436
  // normalizeRange converts a RangeSpecifier into a pair of Intervals that give
9291
9437
  // the x and y ranges, respectively
9292
9438
  function normalizeRange(range) {
@@ -9478,7 +9624,7 @@ const GraphUtils = {
9478
9624
  clickBlockList = [],
9479
9625
  blockMouseTriggers = false,
9480
9626
  blockTouchTriggers = false,
9481
- eventCaptureSupported = ("addEventListener" in document),
9627
+ eventCaptureSupported = "addEventListener" in document,
9482
9628
  $document = $(document),
9483
9629
  nextTouchID = 1,
9484
9630
  lastTouchID = 0;
@@ -10173,7 +10319,6 @@ function scaledPolarRad(radius, radians) {
10173
10319
  return [radius * Math.cos(radians), radius * Math.sin(radians) * -1 // SVG flips y axis
10174
10320
  ];
10175
10321
  }
10176
-
10177
10322
  function scaledPolarDeg(radius, degrees) {
10178
10323
  const radians = degrees * Math.PI / 180;
10179
10324
  return scaledPolarRad(radius, radians);
@@ -11991,7 +12136,6 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11991
12136
  if (hideArrow) {
11992
12137
  return; // Don't draw anything!
11993
12138
  }
11994
-
11995
12139
  const handleVec = kmath.vector.subtract(handleCoord, rotatePoint.coord);
11996
12140
  const handlePolar = kmath.vector.polarDegFromCart(handleVec);
11997
12141
  const angle = handlePolar[1];
@@ -12955,7 +13099,6 @@ ___default["default"].extend(MovableAngle.prototype, {
12955
13099
  // clockwise to the first point
12956
13100
  coords[1] // the vertex parameter is last
12957
13101
  );
12958
-
12959
13102
  const clockwiseAngle = rawAngle + 360 % 360;
12960
13103
  return clockwiseAngle;
12961
13104
  },
@@ -14315,7 +14458,6 @@ class MovablePoint$5 {
14315
14458
  // We only update props here, because we want things on state to
14316
14459
  // be persistent, and updated appropriately in modify()
14317
14460
  ),
14318
-
14319
14461
  ...DEFAULT_PROPS$1
14320
14462
  };
14321
14463
  }
@@ -14367,11 +14509,11 @@ class MovablePoint$5 {
14367
14509
  const showTrashTooltip = () => {
14368
14510
  this._showTooltip(container => {
14369
14511
  // eslint-disable-next-line import/no-deprecated
14370
- render( /*#__PURE__*/React__namespace.createElement("span", {
14512
+ render(/*#__PURE__*/React__namespace.createElement("span", {
14371
14513
  style: {
14372
14514
  fontSize: "2em"
14373
14515
  }
14374
- }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconTrash, {
14516
+ }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconTrash, {
14375
14517
  style: {
14376
14518
  position: "static",
14377
14519
  color: KhanColors.INTERACTIVE,
@@ -15170,7 +15312,6 @@ const DEFAULT_PROPS = {
15170
15312
  // likewise
15171
15313
  labelStyle: null // likewise
15172
15314
  };
15173
-
15174
15315
  const DEFAULT_STATE = {
15175
15316
  added: false,
15176
15317
  hasMoved: false,
@@ -16072,7 +16213,7 @@ class ImageLoader extends React__namespace.Component {
16072
16213
  return (
16073
16214
  /*#__PURE__*/
16074
16215
  // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
16075
- React__namespace.createElement("img", _extends({
16216
+ React__namespace.createElement("img", _extends__default["default"]({
16076
16217
  // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- TODO(LEMS-2871): Address a11y error
16077
16218
  tabIndex: 0,
16078
16219
  src: staticUrl(src),
@@ -17846,7 +17987,7 @@ class TranslationLinter {
17846
17987
  * Preprocess TeX code to convert things that MathJax doesn't know how to handle
17847
17988
  * to things is does.
17848
17989
  */
17849
- var preprocessTex = (texCode => texCode
17990
+ var preprocessTex = texCode => texCode
17850
17991
  // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
17851
17992
  // support (yet) with \begin{aligned}...\end{aligned} which renders
17852
17993
  // the same is supported by KaTeX. It does the same for align*.
@@ -17856,7 +17997,7 @@ var preprocessTex = (texCode => texCode
17856
17997
  // "aligned").
17857
17998
  .replace(/\{align[*]?\}/g, "{aligned}")
17858
17999
  // Replace non-breaking spaces with regular spaces.
17859
- .replace(/[\u00a0]/g, " "));
18000
+ .replace(/[\u00a0]/g, " ");
17860
18001
 
17861
18002
  const {
17862
18003
  interactiveSizes
@@ -18115,29 +18256,29 @@ const isLintable = type => {
18115
18256
  };
18116
18257
 
18117
18258
  var widgets$1 = /*#__PURE__*/Object.freeze({
18118
- __proto__: null,
18119
- registerWidget: registerWidget,
18120
- registerWidgets: registerWidgets,
18121
- replaceWidget: replaceWidget,
18122
- replaceDeprecatedWidgets: replaceDeprecatedWidgets,
18123
- registerEditors: registerEditors,
18124
- replaceEditor: replaceEditor,
18125
- replaceDeprecatedEditors: replaceDeprecatedEditors,
18126
- getWidget: getWidget,
18127
- getWidgetExport: getWidgetExport,
18128
- getEditor: getEditor,
18129
- getTransform: getTransform,
18130
- getVersion: getVersion,
18131
- getVersionVector: getVersionVector,
18132
- getPublicWidgets: getPublicWidgets,
18133
- isAccessible: isAccessible,
18134
- getAllWidgetTypes: getAllWidgetTypes,
18135
- getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
18136
- traverseChildWidgets: traverseChildWidgets$3,
18137
- supportsStaticMode: supportsStaticMode,
18138
- getStaticTransform: getStaticTransform,
18139
- getTracking: getTracking,
18140
- isLintable: isLintable
18259
+ __proto__: null,
18260
+ registerWidget: registerWidget,
18261
+ registerWidgets: registerWidgets,
18262
+ replaceWidget: replaceWidget,
18263
+ replaceDeprecatedWidgets: replaceDeprecatedWidgets,
18264
+ registerEditors: registerEditors,
18265
+ replaceEditor: replaceEditor,
18266
+ replaceDeprecatedEditors: replaceDeprecatedEditors,
18267
+ getWidget: getWidget,
18268
+ getWidgetExport: getWidgetExport,
18269
+ getEditor: getEditor,
18270
+ getTransform: getTransform,
18271
+ getVersion: getVersion,
18272
+ getVersionVector: getVersionVector,
18273
+ getPublicWidgets: getPublicWidgets,
18274
+ isAccessible: isAccessible,
18275
+ getAllWidgetTypes: getAllWidgetTypes,
18276
+ getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
18277
+ traverseChildWidgets: traverseChildWidgets$3,
18278
+ supportsStaticMode: supportsStaticMode,
18279
+ getStaticTransform: getStaticTransform,
18280
+ getTracking: getTracking,
18281
+ isLintable: isLintable
18141
18282
  });
18142
18283
 
18143
18284
  class WidgetContainer extends React__namespace.Component {
@@ -18172,7 +18313,6 @@ class WidgetContainer extends React__namespace.Component {
18172
18313
  /* eslint-enable react/no-did-mount-set-state */
18173
18314
  }
18174
18315
  }
18175
-
18176
18316
  UNSAFE_componentWillReceiveProps(nextProps) {
18177
18317
  if (this.props.type !== nextProps.type) {
18178
18318
  throw new Error("WidgetContainer can't change widget type; set a different " + "key instead to recreate the container.");
@@ -18295,7 +18435,7 @@ class WidgetContainer extends React__namespace.Component {
18295
18435
  }
18296
18436
  });
18297
18437
  }
18298
- }, /*#__PURE__*/React__namespace.createElement(WidgetType, _extends({}, this.state.widgetProps, {
18438
+ }, /*#__PURE__*/React__namespace.createElement(WidgetType, _extends__default["default"]({}, this.state.widgetProps, {
18299
18439
  linterContext: linterContext,
18300
18440
  containerSizeClass: this.state.sizeClass,
18301
18441
  ref: this.widgetRef
@@ -19164,7 +19304,7 @@ class Renderer extends React__namespace.Component {
19164
19304
  let {
19165
19305
  setAssetStatus
19166
19306
  } = _ref4;
19167
- return /*#__PURE__*/React__namespace.createElement(SvgImage, _extends({
19307
+ return /*#__PURE__*/React__namespace.createElement(SvgImage, _extends__default["default"]({
19168
19308
  setAssetStatus: setAssetStatus
19169
19309
  // @ts-expect-error - TS2322 - Type 'string | null | undefined' is not assignable to type 'string | undefined'.
19170
19310
  ,
@@ -19688,429 +19828,84 @@ const styles$A = {
19688
19828
  }
19689
19829
  };
19690
19830
 
19691
- /* eslint-disable @khanacademy/ts-no-error-suppressions */
19692
- let lastId = 0;
19693
- function uniqueIdForInput() {
19694
- let prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "input-";
19695
- lastId++;
19696
- return `${prefix}${lastId}`;
19697
- }
19698
- class TextInput extends React__namespace.Component {
19699
- inputRef = /*#__PURE__*/React__namespace.createRef();
19700
- static defaultProps = {
19701
- value: "",
19702
- disabled: false
19703
- };
19704
- id;
19705
- constructor(props) {
19706
- super(props);
19707
- if (props.id) {
19708
- this.id = props.id;
19709
- } else {
19710
- this.id = uniqueIdForInput();
19711
- }
19712
- }
19713
- _getInput = () => {
19714
- if (!this.inputRef.current) {
19715
- throw new perseusCore.PerseusError("Input ref accessed before set", perseusCore.Errors.Internal);
19716
- }
19717
- return this.inputRef.current;
19718
- };
19719
- focus = () => {
19720
- this._getInput().focus();
19721
- };
19722
- blur = () => {
19723
- this._getInput().blur();
19724
- };
19725
- getValue = () => {
19726
- return this.inputRef.current?.value;
19727
- };
19728
- getStringValue = () => {
19729
- return this.inputRef.current?.value.toString();
19730
- };
19731
- setSelectionRange = (selectionStart, selectionEnd) => {
19732
- this._getInput().setSelectionRange(selectionStart, selectionEnd);
19733
- };
19734
- getSelectionStart = () => {
19735
- return this._getInput().selectionStart;
19736
- };
19737
- getSelectionEnd = () => {
19738
- return this._getInput().selectionEnd;
19739
- };
19740
- render() {
19741
- const {
19742
- labelText,
19743
- value,
19744
- onFocus,
19745
- onBlur,
19746
- disabled,
19747
- placeholder,
19748
- onKeyDown,
19749
- style
19750
- } = this.props;
19751
- const formattedValue = value === null ? "" : value.toString();
19752
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
19753
- ref: this.inputRef,
19754
- style: style,
19755
- disabled: disabled,
19756
- id: this.id,
19757
- value: formattedValue,
19758
- type: "text",
19759
- "aria-label": labelText,
19760
- "aria-describedby": this.props["aria-describedby"],
19761
- onChange: value => this.props.onChange(value),
19762
- placeholder: placeholder,
19763
- onFocus: onFocus,
19764
- onBlur: onBlur,
19765
- onKeyDown: onKeyDown
19766
- // @ts-expect-error - TS2322 - Type '{ style: StyleType; disabled: boolean | undefined; id: string; value: string; type: "text"; "aria-label": string | undefined; onChange: (value: string) => void; placeholder: string | undefined; ... 5 more ...; autoComplete: string; }' is not assignable to type 'IntrinsicAttributes & ExportProps & RefAttributes<HTMLInputElement>'.
19767
- ,
19768
- autoCorrect: "off",
19769
- autoCapitalize: "off",
19770
- autoComplete: "off"
19771
- });
19772
- }
19773
- }
19774
-
19775
- /* eslint-disable react/no-unsafe */
19776
- const zIndex = 10;
19777
- let HorizontalDirection = /*#__PURE__*/function (HorizontalDirection) {
19778
- HorizontalDirection["Left"] = "left";
19779
- HorizontalDirection["Right"] = "right";
19780
- return HorizontalDirection;
19781
- }({});
19782
- let VerticalDirection = /*#__PURE__*/function (VerticalDirection) {
19783
- VerticalDirection["Top"] = "top";
19784
- VerticalDirection["Bottom"] = "bottom";
19785
- return VerticalDirection;
19786
- }({});
19787
- const Triangle = props => {
19788
- let borderLeft;
19789
- let borderRight;
19790
- let borderTop;
19791
- let borderBottom;
19792
- const hBorder = `${props.width}px solid transparent`;
19793
- if (props.horizontalDirection === "right") {
19794
- borderLeft = hBorder;
19795
- } else {
19796
- borderRight = hBorder;
19797
- }
19798
- const vBorder = `${props.height}px solid ${props.color}`;
19799
- if (props.verticalDirection === "top") {
19800
- borderTop = vBorder;
19801
- } else {
19802
- borderBottom = vBorder;
19803
- }
19804
- return /*#__PURE__*/React__namespace.createElement("div", {
19805
- style: {
19806
- display: "block",
19807
- height: 0,
19808
- width: 0,
19809
- position: "absolute",
19810
- left: props.left,
19811
- top: props.top,
19812
- borderLeft: borderLeft,
19813
- borderRight: borderRight,
19814
- borderTop: borderTop,
19815
- borderBottom: borderBottom
19816
- }
19817
- });
19818
- };
19819
- const TooltipArrow = _ref => {
19820
- let {
19821
- position = "relative",
19822
- visibility = "visible",
19823
- left = 100,
19824
- top = 0,
19825
- ...props
19826
- } = _ref;
19827
- // TODO(aria): Think about adding a box-shadow to the triangle here
19828
- // See http://css-tricks.com/triangle-with-shadow/
19831
+ const {
19832
+ captureScratchpadTouchStart: captureScratchpadTouchStart$2
19833
+ } = Util;
19834
+ // [LEMS-2411](Jan 2025) Third: This component has been moved to the NumericInput
19835
+ // folder as we are actively working towards removing the InputNumber widget.
19836
+ // This comment can be removed as part of LEMS-2411.
19829
19837
 
19830
- //const isRight = (this.props.horizontalDirection === "right");
19831
- const isTop = props.verticalDirection === "top";
19832
- const frontTopOffset = isTop ? 0 : 1;
19833
- const borderTopOffset = isTop ? 0 : -1;
19834
- return /*#__PURE__*/React__namespace.createElement("div", {
19835
- style: {
19836
- display: "block",
19837
- position: position,
19838
- visibility: visibility,
19839
- left: left,
19840
- top: top,
19841
- width: props.width + 2,
19842
- height: props.height + 1,
19843
- marginTop: -1,
19844
- marginBottom: -2,
19845
- zIndex: zIndex
19846
- }
19847
- }, /*#__PURE__*/React__namespace.createElement(Triangle, {
19848
- horizontalDirection: props.horizontalDirection,
19849
- verticalDirection: props.verticalDirection,
19850
- color: props.border,
19851
- left: 0,
19852
- top: borderTopOffset,
19853
- width: props.width + 2 // one extra for the diagonal
19854
- ,
19855
- height: props.height + 2
19856
- }), /*#__PURE__*/React__namespace.createElement(Triangle, {
19857
- horizontalDirection: props.horizontalDirection,
19858
- verticalDirection: props.verticalDirection,
19859
- color: props.color,
19860
- left: 1,
19861
- top: frontTopOffset,
19862
- width: props.width,
19863
- height: props.height
19864
- }));
19865
- };
19866
- const VERTICAL_CORNERS = {
19867
- top: {
19868
- top: "-100%"
19869
- },
19870
- bottom: {
19871
- top: 0
19872
- }
19873
- };
19874
- const HORIZONTAL_CORNERS = {
19875
- left: {
19876
- targetLeft: 0
19877
- },
19878
- right: {
19879
- targetLeft: "100%"
19880
- }
19881
- };
19882
- const HORIZONTAL_ALIGNMNENTS = {
19883
- left: {
19884
- tooltipLeft: 0,
19885
- arrowLeft: arrowSize => 0
19886
- },
19887
- right: {
19888
- tooltipLeft: "-100%",
19889
- arrowLeft: arrowSize => -arrowSize - 2
19890
- }
19891
- };
19892
19838
  /**
19893
- * DEPRECATED! Use Wonder Blocks tooltip instead.
19894
- *
19895
- * A generic tooltip library for React.js
19896
- *
19897
- * ```
19898
- * import Tooltip from "./tooltip";
19899
- * <Tooltip
19900
- * className="class-for-tooltip-contents"
19901
- * horizontalPosition={HoriziontalDirection.Left}
19902
- * horizontalAlign={HoriziontalDirection.Left}
19903
- * verticalPosition={VerticalDirection.Top}
19904
- * arrowSize={10} // arrow size in pixels
19905
- * borderColor="#ccc" // color of the border for the tooltip
19906
- * show={true} // whether the tooltip should currently be visible
19907
- * targetContainerStyle={targetContainerStyle}
19908
- * >
19909
- * <TargetElementOfTheTooltip />
19910
- * <TooltipContents1 />
19911
- * <TooltipContents2 />
19912
- * </Tooltip>
19913
- * ```
19914
- *
19915
- * To show/hide the tooltip, the parent component should call the
19916
- * `.show()` and `.hide()` methods of the tooltip when appropriate.
19917
- * (These are usually set up as handlers of events on the target element.)
19918
- *
19919
- * Notes:
19920
- * `className` should not specify a border; that is handled by `borderColor`
19921
- * so that the arrow and tooltip match
19922
- *
19923
- * ```
19924
- * __,,--``\\
19925
- * _,,-''`` \\ ,
19926
- * '----------_.------'-.___|\__
19927
- * _.--''`` `)__ )__ @\__
19928
- * ( .. ''---/___,,E/__,E'------`
19929
- * `-''`''
19930
- * Here be dragons.
19931
- * ```
19839
+ * The InputWithExamples component is a child component of the NumericInput
19840
+ * and InputNumber components. It is responsible for rendering the UI elements
19841
+ * for the desktop versions of these widgets, and displays a tooltip with
19842
+ * examples of how to input the selected answer forms.
19932
19843
  */
19933
- class Tooltip extends React__namespace.Component {
19934
- tooltipContainerRef = /*#__PURE__*/React__namespace.createRef();
19935
- static defaultProps = {
19936
- className: "",
19937
- arrowSize: 10,
19938
- borderColor: "#ccc",
19939
- verticalPosition: VerticalDirection.Bottom,
19940
- horizontalPosition: HorizontalDirection.Left,
19941
- horizontalAlign: HorizontalDirection.Left,
19942
- targetContainerStyle: {}
19943
- };
19944
- state = {
19945
- height: null // used for offsetting "top" positioned tooltips
19946
- };
19947
-
19948
- componentDidMount() {
19949
- this._updateHeight();
19950
- }
19951
- UNSAFE_componentWillReceiveProps() {
19952
- // If the contents have changed, reset our measure of the height
19953
- this.setState({
19954
- height: null
19955
- });
19956
- }
19957
- componentDidUpdate() {
19958
- this._updateHeight();
19959
- }
19960
- _renderToolTipDiv(isTooltipAbove) {
19961
- const settings = Object.assign({}, HORIZONTAL_CORNERS[this.props.horizontalPosition], HORIZONTAL_ALIGNMNENTS[this.props.horizontalAlign], VERTICAL_CORNERS[this.props.verticalPosition]);
19962
- let arrowAbove;
19963
- let arrowBelow;
19964
- if (isTooltipAbove) {
19965
- // We put an absolutely positioned arrow in the correct place
19966
- arrowAbove = /*#__PURE__*/React__namespace.createElement(TooltipArrow, {
19967
- verticalDirection: VerticalDirection.Top,
19968
- horizontalDirection: this.props.horizontalAlign,
19969
- position: "absolute",
19970
- color: "white",
19971
- border: this.props.borderColor,
19972
- left: settings.arrowLeft(this.props.arrowSize),
19973
- top: -this.props.arrowSize + 2,
19974
- width: this.props.arrowSize,
19975
- height: this.props.arrowSize,
19976
- zIndex: zIndex
19977
- });
19978
-
19979
- // And we use a visibility: hidden arrow below to shift up the
19980
- // content by the correct amount
19981
- arrowBelow = /*#__PURE__*/React__namespace.createElement(TooltipArrow, {
19982
- verticalDirection: VerticalDirection.Top,
19983
- horizontalDirection: this.props.horizontalAlign,
19984
- visibility: "hidden",
19985
- color: "white",
19986
- border: this.props.borderColor,
19987
- left: settings.arrowLeft(this.props.arrowSize),
19988
- top: -1,
19989
- width: this.props.arrowSize,
19990
- height: this.props.arrowSize,
19991
- zIndex: zIndex
19992
- });
19993
- } else {
19994
- arrowAbove = /*#__PURE__*/React__namespace.createElement(TooltipArrow, {
19995
- verticalDirection: VerticalDirection.Bottom,
19996
- horizontalDirection: this.props.horizontalAlign,
19997
- color: "white",
19998
- border: this.props.borderColor,
19999
- left: settings.arrowLeft(this.props.arrowSize),
20000
- top: -1,
20001
- width: this.props.arrowSize,
20002
- height: this.props.arrowSize,
20003
- zIndex: zIndex
20004
- });
20005
- arrowBelow = null;
20006
- }
20007
-
20008
- /* A positioned div below the input to be the parent for our
20009
- tooltip */
20010
- return /*#__PURE__*/React__namespace.createElement("div", {
20011
- style: {
20012
- position: "relative",
20013
- height: 0,
20014
- display: this.props.show ? "block" : "none"
19844
+ const InputWithExamples = /*#__PURE__*/React.forwardRef((props, ref) => {
19845
+ // Desctructure the props to set default values
19846
+ const {
19847
+ shouldShowExamples = true,
19848
+ onFocus = () => {},
19849
+ onBlur = () => {},
19850
+ disabled = false,
19851
+ linterContext = PerseusLinter__namespace.linterContextDefault,
19852
+ className = ""
19853
+ } = props;
19854
+ const context = React__namespace.useContext(PerseusI18nContext);
19855
+ const inputRef = React__namespace.useRef(null);
19856
+ const [inputFocused, setInputFocused] = React__namespace.useState(false);
19857
+ const id = React.useId();
19858
+ const ariaId = `aria-for-${id}`;
19859
+ React.useImperativeHandle(ref, () => ({
19860
+ current: inputRef.current,
19861
+ focus: () => {
19862
+ if (inputRef.current) {
19863
+ inputRef.current.focus();
19864
+ }
19865
+ },
19866
+ blur: () => {
19867
+ if (inputRef.current) {
19868
+ inputRef.current.blur();
20015
19869
  }
20016
- }, /*#__PURE__*/React__namespace.createElement("div", {
20017
- ref: this.tooltipContainerRef,
20018
- className: "tooltipContainer",
20019
- role: "tooltip",
20020
- style: {
20021
- position: "absolute",
20022
- // height must start out undefined, not null, so that
20023
- // we can measure the actual height with jquery.
20024
- // This is used to position the tooltip with top: -100%
20025
- // when in verticalPosition: "top" mode
20026
- height: this.state.height || undefined,
20027
- left: settings.targetLeft
20028
- }
20029
- }, arrowAbove, /*#__PURE__*/React__namespace.createElement("div", {
20030
- className: this.props.className
20031
- // eslint-disable-next-line react/no-string-refs
20032
- ,
20033
- ref: "tooltipContent",
20034
- style: {
20035
- position: "relative",
20036
- top: settings["top"],
20037
- left: settings.tooltipLeft,
20038
- border: "1px solid " + this.props.borderColor,
20039
- boxShadow: "0 1px 3px " + this.props.borderColor,
20040
- zIndex: zIndex - 1
20041
- }
20042
- }, this.props.children.slice(1)), arrowBelow));
20043
- }
20044
- _updateHeight() {
20045
- const tooltipContainer = this.tooltipContainerRef.current;
20046
- const height = tooltipContainer?.offsetHeight || 0;
20047
- if (height !== this.state.height) {
20048
- this.setState({
20049
- height
20050
- });
20051
19870
  }
20052
- }
20053
- render() {
20054
- const isTooltipAbove = this.props.verticalPosition === "top";
20055
-
20056
- /* We wrap the entire output in a span so that it displays inline */
20057
- return /*#__PURE__*/React__namespace.createElement("span", null, isTooltipAbove && this._renderToolTipDiv(isTooltipAbove), /*#__PURE__*/React__namespace.createElement("div", {
20058
- style: this.props.targetContainerStyle
20059
- }, this.props.children[0]), !isTooltipAbove && this._renderToolTipDiv());
20060
- }
20061
- }
20062
-
20063
- const {
20064
- captureScratchpadTouchStart: captureScratchpadTouchStart$2
20065
- } = Util;
20066
- class InputWithExamples extends React__namespace.Component {
20067
- static contextType = PerseusI18nContext;
20068
- static defaultProps = {
20069
- shouldShowExamples: true,
20070
- onFocus: function () {},
20071
- onBlur: function () {},
20072
- disabled: false,
20073
- linterContext: PerseusLinter__namespace.linterContextDefault,
20074
- className: ""
20075
- };
20076
- state = {
20077
- focused: false,
20078
- showExamples: false
20079
- };
20080
- _getUniqueId = () => {
20081
- return `input-with-examples-${btoa(this.props.id).replace(/=/g, "")}`;
20082
- };
20083
- _getInputClassName = () => {
20084
- // Otherwise, we need to add these INPUT and FOCUSED tags here.
20085
- let className = ClassNames.INPUT;
20086
- if (this.state.focused) {
20087
- className += " " + ClassNames.FOCUSED;
19871
+ }));
19872
+ const getInputClassName = () => {
19873
+ let inputClassName = ClassNames.INPUT;
19874
+ if (inputFocused) {
19875
+ inputClassName += " " + ClassNames.FOCUSED;
20088
19876
  }
20089
- if (this.props.className) {
20090
- className += " " + this.props.className;
19877
+ if (className) {
19878
+ inputClassName += " " + className;
20091
19879
  }
20092
- return className;
19880
+ return inputClassName;
19881
+ };
19882
+ const handleFocus = () => {
19883
+ onFocus();
19884
+ setInputFocused(true);
20093
19885
  };
20094
- _renderInput = () => {
20095
- const id = this._getUniqueId();
20096
- const ariaId = `aria-for-${id}`;
20097
- // Generate text from a known set of format options that will read well in a screen reader
20098
- const examplesAria = this.props.examples.length === 7 ? "" : `${this.props.examples[0]}
20099
- ${this.props.examples.slice(1).join(", or\n")}`
19886
+ const handleBlur = () => {
19887
+ onBlur();
19888
+ setInputFocused(false);
19889
+ };
19890
+ const renderInput = () => {
19891
+ // Generate the provided examples in simple language for screen readers.
19892
+ const examplesAria = props.shouldShowExamples ? `${props.examples[0]}
19893
+ ${props.examples.slice(1).join(", or\n")}`
20100
19894
  // @ts-expect-error TS2550: Property replaceAll does not exist on type string.
20101
- .replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ");
19895
+ .replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ") : "";
20102
19896
  const inputProps = {
20103
19897
  id: id,
20104
- "aria-describedby": ariaId,
20105
- ref: "input",
20106
- className: this._getInputClassName(),
20107
- labelText: this.props.labelText,
20108
- value: this.props.value,
20109
- onFocus: this._handleFocus,
20110
- onBlur: this._handleBlur,
20111
- disabled: this.props.disabled,
20112
- style: this.props.style,
20113
- onChange: this.props.onChange,
19898
+ // If we have examples, we want to provide the aria-describedby attribute
19899
+ "aria-describedby": props.shouldShowExamples ? ariaId : undefined,
19900
+ ref: inputRef,
19901
+ className: getInputClassName(),
19902
+ labelText: props.labelText,
19903
+ value: props.value,
19904
+ onFocus: handleFocus,
19905
+ onBlur: handleBlur,
19906
+ disabled: disabled,
19907
+ style: props.style,
19908
+ onChange: props.onChange,
20114
19909
  onTouchStart: captureScratchpadTouchStart$2,
20115
19910
  autoCapitalize: "off",
20116
19911
  autoComplete: "off",
@@ -20124,158 +19919,32 @@ class InputWithExamples extends React__namespace.Component {
20124
19919
  }
20125
19920
  }, examplesAria));
20126
19921
  };
20127
- _handleFocus = () => {
20128
- this.props.onFocus();
20129
- this.setState({
20130
- focused: true,
20131
- showExamples: true
20132
- });
20133
- };
20134
- show = () => {
20135
- this.setState({
20136
- showExamples: true
20137
- });
20138
- };
20139
- hide = () => {
20140
- this.setState({
20141
- showExamples: false
20142
- });
20143
- };
20144
- _handleBlur = () => {
20145
- this.props.onBlur();
20146
- this.setState({
20147
- focused: false,
20148
- showExamples: false
20149
- });
20150
- };
20151
- focus = () => {
20152
- // eslint-disable-next-line react/no-string-refs
20153
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
20154
- this.refs.input.focus();
20155
- };
20156
- blur = () => {
20157
- // eslint-disable-next-line react/no-string-refs
20158
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
20159
- this.refs.input.blur();
20160
- };
20161
- handleChange = e => {
20162
- this.props.onChange(e.target.value);
20163
- };
20164
- render() {
20165
- const input = this._renderInput();
20166
- const examplesContent = this.props.examples.length <= 2 ? this.props.examples.join(" ") // A single item (with or without leading text) is not a "list"
20167
- : this.props.examples // 2 or more items should display as a list
20168
- .map((example, index) => {
20169
- // If the first example is bold, then it is most likely a heading/leading text.
20170
- // So, it shouldn't be part of the list.
20171
- return index === 0 && example.startsWith("**") ? `${example}\n` : `- ${example}`;
20172
- }).join("\n");
20173
- const showExamples = this.props.shouldShowExamples && this.state.showExamples;
20174
- return /*#__PURE__*/React__namespace.createElement(Tooltip, {
20175
- className: "perseus-formats-tooltip preview-measure",
20176
- horizontalPosition: HorizontalDirection.Left,
20177
- horizontalAlign: HorizontalDirection.Left,
20178
- verticalPosition: VerticalDirection.Bottom,
20179
- arrowSize: 10,
20180
- borderColor: "#ccc",
20181
- show: showExamples
20182
- }, input, /*#__PURE__*/React__namespace.createElement("div", {
20183
- id: this._getUniqueId()
19922
+ const renderTooltipContent = () => {
19923
+ return /*#__PURE__*/React__namespace.createElement(Tooltip.TooltipContent, null, /*#__PURE__*/React__namespace.createElement("div", {
19924
+ id: id,
19925
+ className: "input-with-examples-tooltip"
20184
19926
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
20185
19927
  content: examplesContent,
20186
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "input-with-examples"),
20187
- strings: this.context.strings
19928
+ linterContext: PerseusLinter__namespace.pushContextStack(linterContext, "input-with-examples"),
19929
+ strings: context.strings
20188
19930
  })));
20189
- }
20190
- }
20191
-
20192
- class SimpleKeypadInput extends React__namespace.Component {
20193
- _isMounted = false;
20194
- componentDidMount() {
20195
- // TODO(scottgrant): This is a hack to remove the deprecated call to
20196
- // this.isMounted() but is still considered an anti-pattern.
20197
- this._isMounted = true;
20198
- }
20199
- componentWillUnmount() {
20200
- this._isMounted = false;
20201
- }
20202
- focus() {
20203
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
20204
- this.refs.input.focus(); // eslint-disable-line react/no-string-refs
20205
- }
20206
-
20207
- blur() {
20208
- // eslint-disable-next-line react/no-string-refs
20209
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
20210
- if (typeof this.refs.input?.blur === "function") {
20211
- // eslint-disable-next-line react/no-string-refs
20212
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
20213
- this.refs.input?.blur();
20214
- }
20215
- }
20216
- getValue() {
20217
- return this.props.value;
20218
- }
20219
- render() {
20220
- const _this = this;
20221
- // Intercept the `onFocus` prop, as we need to configure the keypad
20222
- // before continuing with the default focus logic for Perseus inputs.
20223
- // Intercept the `value` prop so as to map `null` to the empty string,
20224
- // as the `KeypadInput` does not support `null` values.
20225
- const {
20226
- keypadElement,
20227
- onFocus,
20228
- value,
20229
- ...rest
20230
- } = _this.props;
20231
- return (
20232
- /*#__PURE__*/
20233
- // @ts-expect-error - TS2769 - No overload matches this call.
20234
- React__namespace.createElement(mathInput.KeypadInput
20235
- // eslint-disable-next-line react/no-string-refs
20236
- , _extends({
20237
- ref: "input",
20238
- keypadElement: keypadElement,
20239
- onFocus: () => {
20240
- if (keypadElement) {
20241
- keypadElement.configure({
20242
- keypadType: mathInput.KeypadType.FRACTION
20243
- }, () => {
20244
- if (_this._isMounted) {
20245
- onFocus?.();
20246
- }
20247
- });
20248
- } else {
20249
- onFocus?.();
20250
- }
20251
- },
20252
- value: value == null ? "" : "" + value
20253
- }, rest))
20254
- );
20255
- }
20256
- }
20257
-
20258
- // @ts-expect-error - TS2339 - Property 'propTypes' does not exist on type 'typeof SimpleKeypadInput'.
20259
- SimpleKeypadInput.propTypes = {
20260
- keypadElement: mathInput.keypadElementPropType,
20261
- onFocus: PropTypes__default["default"].func,
20262
- value: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number])
20263
- };
20264
-
20265
- const getPromptJSON$s = (renderProps, userInput) => {
20266
- return {
20267
- type: "input-number",
20268
- options: {
20269
- simplify: renderProps.simplify,
20270
- answerType: renderProps.answerType
20271
- },
20272
- userInput: {
20273
- value: userInput.currentValue
20274
- }
20275
19931
  };
20276
- };
19932
+ // Display the examples as a string when there are less than or equal to 2 examples.
19933
+ // Otherwise, display the examples as a list.
19934
+ const examplesContent = props.examples.length <= 2 ? props.examples.join(" ") : props.examples.map((example, index) => {
19935
+ return index === 0 && example.startsWith("**") ? `${example}\n` : `- ${example}`;
19936
+ }).join("\n");
20277
19937
 
20278
- const formExamples$1 = {
19938
+ // Display the examples when they are enabled (shouldShowExamples) and the input is focused.
19939
+ const showExamplesTooltip = shouldShowExamples && inputFocused;
19940
+ return /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
19941
+ content: renderTooltipContent(),
19942
+ opened: showExamplesTooltip,
19943
+ placement: "bottom"
19944
+ }, renderInput());
19945
+ });
19946
+
19947
+ const formExamples = {
20279
19948
  integer: function (options, strings) {
20280
19949
  return strings.integerExample;
20281
19950
  },
@@ -20387,7 +20056,7 @@ class InputNumber extends React__namespace.Component {
20387
20056
  } = this.context;
20388
20057
  const type = this.props.answerType;
20389
20058
  const forms = perseusScore.inputNumberAnswerTypes[type].forms.split(/\s*,\s*/);
20390
- const examples = ___default["default"].map(forms, form => formExamples$1[form](this.props, strings));
20059
+ const examples = ___default["default"].map(forms, form => formExamples[form](this.props, strings));
20391
20060
  return [strings.yourAnswer].concat(examples);
20392
20061
  }
20393
20062
  render() {
@@ -20501,7 +20170,11 @@ const getPromptJSON$r = (renderProps, userInput) => {
20501
20170
  };
20502
20171
  };
20503
20172
 
20504
- const formExamples = {
20173
+ /**
20174
+ * The full list of available strings for the numeric input widget,
20175
+ * based on whether the Content Creator has specified that the answer must be simplified.
20176
+ */
20177
+ const NumericExampleStrings = {
20505
20178
  integer: (form, strings) => strings.integerExample,
20506
20179
  proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
20507
20180
  improper: (form, strings) => form.simplify === "optional" ? strings.improperExample : strings.simplifiedImproperExample,
@@ -20509,9 +20182,182 @@ const formExamples = {
20509
20182
  decimal: (form, strings) => strings.decimalExample,
20510
20183
  pi: (form, strings) => strings.piExample
20511
20184
  };
20185
+
20186
+ /**
20187
+ * Generates the specific set of examples for the current question.
20188
+ * This string is shown as examples to the user in a tooltip.
20189
+ */
20190
+ const generateExamples = (answerForms, strings) => {
20191
+ // If the Content Creator has not specified any answer forms,
20192
+ // we do not need to show any examples.
20193
+ if (answerForms.length === 0) {
20194
+ return [];
20195
+ }
20196
+
20197
+ // Generate a list of the unique answer forms.
20198
+ const uniqueForms = getUniqueAnswerForms(answerForms);
20199
+
20200
+ // Generate the example strings for each unique form.
20201
+ const examples = uniqueForms.map(form => {
20202
+ return NumericExampleStrings[form.name](form, strings);
20203
+ });
20204
+
20205
+ // Add the "Your answer" string to the beginning of the examples list.
20206
+ return [strings.yourAnswer].concat(examples);
20207
+ };
20208
+
20209
+ /**
20210
+ * Determines whether to show examples of how to input the various supported answer forms.
20211
+ * We do not show examples if all forms are accepted or if no forms are accepted.
20212
+ */
20213
+ const shouldShowExamples = answerForms => {
20214
+ // If the Content Creator has not specified any answer forms,
20215
+ // we do not need to show any examples.
20216
+ if (answerForms.length === 0) {
20217
+ return false;
20218
+ }
20219
+
20220
+ // Generate a list of the unique names of the selected answer forms.
20221
+ const answerFormNames = getUniqueAnswerForms(answerForms).map(form => form.name);
20222
+
20223
+ // If all forms are accepted, we do not need to show any examples.
20224
+ const allFormsAccepted = answerFormNames.length >= Object.keys(NumericExampleStrings).length;
20225
+ return !allFormsAccepted;
20226
+ };
20227
+
20228
+ /**
20229
+ * uniqueAnswerForms takes a list of answer forms and returns a list of unique
20230
+ * answer forms. This is useful for ensuring that we don't show duplicate examples
20231
+ * to the user.
20232
+ */
20233
+ const getUniqueAnswerForms = function (list) {
20234
+ // We use a Set to keep track of the forms we've already seen.
20235
+ const foundForms = new Set();
20236
+ return list.filter(form => {
20237
+ // If we've already seen this form, skip it.
20238
+ if (foundForms.has(form.name)) {
20239
+ return false;
20240
+ }
20241
+ // Otherwise, add it to the set and return true.
20242
+ foundForms.add(form.name);
20243
+ return true;
20244
+ });
20245
+ };
20246
+
20247
+ /**
20248
+ * Takes a list of lists of answer forms, and returns a list of the forms
20249
+ * in each of these lists in the same order that they're listed in the
20250
+ * `formExamples` forms from above.
20251
+ */
20252
+ const unionAnswerForms = function (answerFormsList) {
20253
+ // Pull out all of the forms from the different lists.
20254
+ const allForms = answerFormsList.flat();
20255
+ // Pull out the unique forms using getUniqueAnswerForms.
20256
+ const uniqueForms = getUniqueAnswerForms(allForms);
20257
+ // Sort them by the order they appear in the `formExamples` list.
20258
+ const formExampleKeys = Object.keys(NumericExampleStrings);
20259
+ return uniqueForms.sort((a, b) => {
20260
+ return formExampleKeys.indexOf(a.name) - formExampleKeys.indexOf(b.name);
20261
+ });
20262
+ };
20263
+
20264
+ /**
20265
+ * The NumericInputComponent is a child component of the NumericInput class
20266
+ * component. It is responsible for rendering the UI elements of the Numeric
20267
+ * Input widget.
20268
+ */
20269
+ const NumericInputComponent = /*#__PURE__*/React.forwardRef((props, ref) => {
20270
+ const context = React.useContext(PerseusI18nContext);
20271
+ const inputRef = React.useRef(null);
20272
+ const [isFocused, setIsFocused] = React.useState(false);
20273
+
20274
+ // Pass the focus and blur methods to the Numeric Input Class component
20275
+ React.useImperativeHandle(ref, () => ({
20276
+ current: inputRef.current,
20277
+ focus: () => {
20278
+ if (inputRef.current) {
20279
+ inputRef.current?.focus();
20280
+ setIsFocused(true);
20281
+ }
20282
+ },
20283
+ blur: () => {
20284
+ if (inputRef.current) {
20285
+ inputRef.current?.blur();
20286
+ setIsFocused(false);
20287
+ }
20288
+ }
20289
+ }));
20290
+ const handleChange = (newValue, cb) => {
20291
+ props.onChange({
20292
+ currentValue: newValue
20293
+ }, cb);
20294
+ props.trackInteraction();
20295
+ };
20296
+ const handleFocus = () => {
20297
+ props.onFocus([]);
20298
+ setIsFocused(true);
20299
+ };
20300
+ const handleBlur = () => {
20301
+ props.onBlur([]);
20302
+ setIsFocused(false);
20303
+ };
20304
+
20305
+ // Styles for the InputWithExamples
20306
+ const styles = aphrodite.StyleSheet.create({
20307
+ inputWithExamples: {
20308
+ borderRadius: "3px",
20309
+ borderWidth: isFocused ? "2px" : "1px",
20310
+ display: "inline-block",
20311
+ fontFamily: `Symbola, "Times New Roman", serif`,
20312
+ fontSize: "18px",
20313
+ height: "32px",
20314
+ lineHeight: "18px",
20315
+ padding: isFocused ? "4px" : "4px 5px",
20316
+ textAlign: props.rightAlign ? "right" : "left",
20317
+ width: props.size === "small" ? 40 : 80
20318
+ }
20319
+ });
20320
+
20321
+ // (mobile-only) If the custom keypad is enabled, use the SimpleKeypadInput component
20322
+ if (props.apiOptions.customKeypad) {
20323
+ const alignmentClass = props.rightAlign ? "perseus-input-right-align" : undefined;
20324
+ return /*#__PURE__*/React__namespace.createElement("div", {
20325
+ className: alignmentClass
20326
+ }, /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput, {
20327
+ ref: inputRef,
20328
+ value: props.currentValue,
20329
+ keypadElement: props.keypadElement,
20330
+ onChange: handleChange,
20331
+ onFocus: handleFocus,
20332
+ onBlur: handleBlur
20333
+ }));
20334
+ }
20335
+ // (desktop-only) Otherwise, use the InputWithExamples component
20336
+ return /*#__PURE__*/React__namespace.createElement(InputWithExamples, {
20337
+ ref: inputRef,
20338
+ value: props.currentValue,
20339
+ onChange: handleChange,
20340
+ labelText: props.labelText,
20341
+ examples: generateExamples(props.answerForms, context.strings),
20342
+ shouldShowExamples: shouldShowExamples(props.answerForms),
20343
+ onFocus: handleFocus,
20344
+ onBlur: handleBlur,
20345
+ id: props.widgetId,
20346
+ disabled: props.apiOptions.readOnly,
20347
+ style: styles.inputWithExamples
20348
+ });
20349
+ });
20350
+
20351
+ /**
20352
+ * The NumericInput widget is a numeric input field that supports a variety of
20353
+ * answer forms, including integers, decimals, fractions, and mixed numbers.
20354
+ *
20355
+ * [Jan 2025] We're currenly migrating from class-based components to functional
20356
+ * components. While we cannot fully migrate this component yet, we can start
20357
+ * by using the functional component for the rendering the UI of the widget.
20358
+ */
20512
20359
  class NumericInput extends React__namespace.Component {
20513
- static contextType = PerseusI18nContext;
20514
- inputRef;
20360
+ inputRef = /*#__PURE__*/React__namespace.createRef();
20515
20361
  static defaultProps = {
20516
20362
  currentValue: "",
20517
20363
  size: "normal",
@@ -20527,51 +20373,15 @@ class NumericInput extends React__namespace.Component {
20527
20373
  currentValue: props.currentValue
20528
20374
  };
20529
20375
  }
20530
- state = {
20531
- // keeps track of the other set of values when switching
20532
- // between 0 and finite solutions
20533
- previousValues: [""],
20534
- isFocused: false
20535
- };
20536
-
20537
- /**
20538
- * Generates a string that demonstrates how to input the various supported
20539
- * answer forms.
20540
- */
20541
- examples() {
20542
- // if the set of specified forms are empty, allow all forms
20543
- const forms = this.props.answerForms?.length !== 0 ? this.props.answerForms : Object.keys(formExamples).map(name => {
20544
- return {
20545
- name: name,
20546
- simplify: "required"
20547
- };
20548
- });
20549
- let examples = ___default["default"].map(forms, form => {
20550
- return formExamples[form.name](form, this.context.strings);
20551
- });
20552
- // Ensure no duplicate tooltip text from simplified and unsimplified
20553
- // versions of the same format
20554
- examples = ___default["default"].uniq(examples);
20555
- return [this.context.strings.yourAnswer].concat(examples);
20556
- }
20557
- shouldShowExamples = () => {
20558
- const noFormsAccepted = this.props.answerForms?.length === 0;
20559
- // To check if all answer forms are accepted, we must first
20560
- // find the *names* of all accepted forms, and see if they are
20561
- // all present, ignoring duplicates
20562
- const answerFormNames = ___default["default"].uniq(this.props.answerForms?.map(form => form.name));
20563
- const allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
20564
- return !noFormsAccepted && !allFormsAccepted;
20565
- };
20566
20376
  focus = () => {
20567
- this.inputRef?.focus();
20377
+ this.inputRef.current?.focus();
20568
20378
  return true;
20569
20379
  };
20570
20380
  focusInputPath = () => {
20571
- this.inputRef?.focus();
20381
+ this.inputRef.current?.focus();
20572
20382
  };
20573
20383
  blurInputPath = () => {
20574
- this.inputRef?.blur();
20384
+ this.inputRef.current?.blur();
20575
20385
  };
20576
20386
  getInputPaths = () => {
20577
20387
  // The widget itself is an input, so we return a single empty list to
@@ -20579,152 +20389,57 @@ class NumericInput extends React__namespace.Component {
20579
20389
  /* c8 ignore next */
20580
20390
  return [[]];
20581
20391
  };
20392
+
20393
+ /**
20394
+ * Sets the value of the input at the given path.
20395
+ */
20582
20396
  setInputValue = (path, newValue, cb) => {
20583
- /* c8 ignore next */
20584
20397
  this.props.onChange({
20585
20398
  currentValue: newValue
20586
20399
  }, cb);
20587
20400
  };
20401
+
20402
+ /**
20403
+ * Returns the value the user has currently input for this widget.
20404
+ */
20588
20405
  getUserInput() {
20589
20406
  return NumericInput.getUserInputFromProps(this.props);
20590
20407
  }
20408
+
20409
+ /**
20410
+ * Returns the JSON representation of the prompt for this widget.
20411
+ * This is used by the AI to determine the prompt for the widget.
20412
+ */
20591
20413
  getPromptJSON() {
20592
20414
  return getPromptJSON$r(this.props, this.getUserInput());
20593
20415
  }
20594
- handleChange = (newValue, cb) => {
20595
- this.props.onChange({
20596
- currentValue: newValue
20597
- }, cb);
20598
- this.props.trackInteraction();
20599
- };
20600
- _handleFocus = () => {
20601
- this.props.onFocus([]);
20602
- this.setState(currentState => {
20603
- return {
20604
- ...currentState,
20605
- isFocused: true
20606
- };
20607
- });
20608
- };
20609
- _handleBlur = () => {
20610
- this.props.onBlur([]);
20611
- this.setState(currentState => {
20612
- return {
20613
- ...currentState,
20614
- isFocused: false
20615
- };
20616
- });
20617
- };
20618
20416
  render() {
20619
- let labelText = this.props.labelText;
20620
- if (labelText == null || labelText === "") {
20621
- labelText = this.context.strings.yourAnswerLabel;
20622
- }
20623
-
20624
- // To right align a custom keypad we need to wrap it.
20625
- const maybeRightAlignKeypadInput = keypadInput => {
20626
- return this.props.rightAlign ? /*#__PURE__*/React__namespace.createElement("div", {
20627
- className: "perseus-input-right-align"
20628
- }, keypadInput) : keypadInput;
20629
- };
20630
- if (this.props.apiOptions.customKeypad) {
20631
- // TODO(charlie): Support "Review Mode".
20632
- return maybeRightAlignKeypadInput( /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput, {
20633
- ref: ref => this.inputRef = ref,
20634
- value: this.props.currentValue,
20635
- keypadElement: this.props.keypadElement,
20636
- onChange: this.handleChange,
20637
- onFocus: this._handleFocus,
20638
- onBlur: this._handleBlur
20639
- }));
20640
- }
20641
-
20642
- // Note: This is _very_ similar to what `input-number.jsx` does. If
20643
- // you modify this, double-check if you also need to modify that
20644
- // component.
20645
- const styles = aphrodite.StyleSheet.create({
20646
- input: {
20647
- borderRadius: "3px",
20648
- borderWidth: this.state.isFocused ? "2px" : "1px",
20649
- display: "inline-block",
20650
- fontFamily: `Symbola, "Times New Roman", serif`,
20651
- fontSize: "18px",
20652
- height: "32px",
20653
- lineHeight: "18px",
20654
- padding: this.state.isFocused ? "4px" : "4px 5px",
20655
- // account for added focus border thickness
20656
- textAlign: this.props.rightAlign ? "right" : "left",
20657
- width: this.props.size === "small" ? 40 : 80
20658
- }
20659
- });
20660
- return /*#__PURE__*/React__namespace.createElement(InputWithExamples, {
20661
- ref: ref => this.inputRef = ref,
20662
- value: this.props.currentValue,
20663
- onChange: this.handleChange,
20664
- labelText: labelText,
20665
- examples: this.examples(),
20666
- shouldShowExamples: this.shouldShowExamples(),
20667
- onFocus: this._handleFocus,
20668
- onBlur: this._handleBlur,
20669
- id: this.props.widgetId,
20670
- disabled: this.props.apiOptions.readOnly,
20671
- style: styles.input
20672
- });
20417
+ return /*#__PURE__*/React__namespace.createElement(NumericInputComponent, _extends__default["default"]({}, this.props, {
20418
+ ref: this.inputRef
20419
+ }));
20673
20420
  }
20674
20421
  }
20675
-
20676
- // TODO(thomas): Currently we receive a list of lists of acceptable answer types
20677
- // and union them down into a single set. It's worth considering whether it
20678
- // wouldn't make more sense to have a single set of acceptable answer types for
20679
- // a given *problem* rather than for each possible [correct/wrong] *answer*.
20680
- // When should two answers to a problem take different answer types?
20681
- // See D27790 for more discussion.
20682
- const unionAnswerForms = function (answerFormsList) {
20683
- // Takes a list of lists of answer forms, and returns a list of the forms
20684
- // in each of these lists in the same order that they're listed in the
20685
- // `formExamples` forms from above.
20686
-
20687
- // uniqueBy takes a list of elements and a function which compares whether
20688
- // two elements are equal, and returns a list of unique elements. This is
20689
- // just a helper function here, but works generally.
20690
- const uniqueBy = function (list, iteratee) {
20691
- // @ts-expect-error - TS2347 - Untyped function calls may not accept type arguments.
20692
- return list.reduce((uniqueList, element) => {
20693
- // For each element, decide whether it's already in the list of
20694
- // unique items.
20695
- const inList = ___default["default"].find(uniqueList, iteratee.bind(null, element));
20696
- if (inList) {
20697
- return uniqueList;
20698
- }
20699
- return uniqueList.concat([element]);
20700
- }, []);
20701
- };
20702
-
20703
- // Pull out all of the forms from the different lists.
20704
- const allForms = answerFormsList.flat();
20705
- // Pull out the unique forms using uniqueBy.
20706
- const uniqueForms = uniqueBy(allForms, ___default["default"].isEqual);
20707
- // Sort them by the order they appear in the `formExamples` list.
20708
- const formExampleKeys = Object.keys(formExamples);
20709
- return ___default["default"].sortBy(uniqueForms, form => {
20710
- return formExampleKeys.indexOf(form.name);
20711
- });
20712
- };
20422
+ // Transforms the widget options to the props used to render the widget.
20713
20423
  const propsTransform = function (widgetOptions) {
20714
- const rendererProps = ___default["default"].extend(___default["default"].omit(widgetOptions, "answers"), {
20424
+ // Omit the answers from the widget options since they are
20425
+ // not needed for rendering the widget.
20426
+ const {
20427
+ answers: _,
20428
+ ...rendererProps
20429
+ } = {
20430
+ ...widgetOptions,
20715
20431
  answerForms: unionAnswerForms(
20716
- // Pull out the name of each form and whether that form has
20717
- // required simplification.
20718
- widgetOptions.answers.map(answer => {
20719
- // @ts-expect-error - TS2345 - Argument of type 'readonly MathFormat[] | undefined' is not assignable to parameter of type 'Collection<any>'.
20720
- return ___default["default"].map(answer.answerForms, form => {
20432
+ // Filter out the correct answers and map them to the answer forms
20433
+ // so that we can generate the examples for the widget.
20434
+ widgetOptions.answers.filter(answer => answer.status === "correct").map(answer => {
20435
+ return (answer.answerForms || []).map(form => {
20721
20436
  return {
20722
20437
  simplify: answer.simplify,
20723
20438
  name: form
20724
20439
  };
20725
20440
  });
20726
20441
  }))
20727
- });
20442
+ };
20728
20443
  return rendererProps;
20729
20444
  };
20730
20445
  var NumericInput$1 = {
@@ -20739,23 +20454,11 @@ var NumericInput$1 = {
20739
20454
  getOneCorrectAnswerFromRubric(rubric) {
20740
20455
  const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
20741
20456
  const answerStrings = correctAnswers.map(answer => {
20742
- // Figure out how this answer is supposed to be
20743
- // displayed
20744
- let format = "decimal";
20745
- if (answer.answerForms && answer.answerForms[0]) {
20746
- // NOTE(johnsullivan): This isn't exactly ideal, but
20747
- // it does behave well for all the currently known
20748
- // problems. See D14742 for some discussion on
20749
- // alternate strategies.
20750
- format = answer.answerForms[0];
20751
- }
20752
-
20753
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
20457
+ // Either get the first answer form or default to decimal
20458
+ const format = answer.answerForms && answer.answerForms[0] ? answer.answerForms[0] : "decimal";
20754
20459
  let answerString = kmath.KhanMath.toNumericString(answer.value, format);
20755
20460
  if (answer.maxError) {
20756
- answerString += " \u00B1 " +
20757
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
20758
- kmath.KhanMath.toNumericString(answer.maxError, format);
20461
+ answerString += " \u00B1 " + kmath.KhanMath.toNumericString(answer.maxError, format);
20759
20462
  }
20760
20463
  return answerString;
20761
20464
  });
@@ -23290,6 +22993,7 @@ const BASE_ICON_SIZE = 10;
23290
22993
  // If you pass in an object you need to specify an SVG path to
23291
22994
  // render along with the width and height of the path (to ensure
23292
22995
  // that it gets scaled correctly).
22996
+
23293
22997
  /**
23294
22998
  * An SVG Icon
23295
22999
  *
@@ -23547,7 +23251,7 @@ function ChoiceInner(props) {
23547
23251
  return /*#__PURE__*/React__namespace.createElement("span", null, letter);
23548
23252
  }
23549
23253
  if (correct) {
23550
- return /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconCheck, {
23254
+ return /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconCheck, {
23551
23255
  style: {
23552
23256
  position: "relative",
23553
23257
  top: -1
@@ -24003,7 +23707,7 @@ const styles$p = aphrodite.StyleSheet.create({
24003
23707
  }
24004
23708
  }
24005
23709
  });
24006
- var Choice$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__*/React__namespace.createElement(Choice, _extends({}, props, {
23710
+ var Choice$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__*/React__namespace.createElement(Choice, _extends__default["default"]({}, props, {
24007
23711
  forwardedRef: ref
24008
23712
  })));
24009
23713
 
@@ -24037,11 +23741,11 @@ const ChoiceNoneAbove = function (_ref) {
24037
23741
  strings: strings
24038
23742
  })
24039
23743
  };
24040
- return /*#__PURE__*/React__namespace.createElement(Choice$1, _extends({}, choiceProps, {
23744
+ return /*#__PURE__*/React__namespace.createElement(Choice$1, _extends__default["default"]({}, choiceProps, {
24041
23745
  ref: forwardedRef
24042
23746
  }));
24043
23747
  };
24044
- var ChoiceNoneAbove$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__*/React__namespace.createElement(ChoiceNoneAbove, _extends({}, props, {
23748
+ var ChoiceNoneAbove$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__*/React__namespace.createElement(ChoiceNoneAbove, _extends__default["default"]({}, props, {
24045
23749
  forwardedRef: ref
24046
23750
  })));
24047
23751
 
@@ -24291,7 +23995,7 @@ const BaseRadio = function (_ref) {
24291
23995
  className: className,
24292
23996
  onClick: clickHandler,
24293
23997
  onTouchStart: labelWrap ? undefined : captureScratchpadTouchStart$1
24294
- }, /*#__PURE__*/React__namespace.createElement(Element, _extends({}, elementProps, {
23998
+ }, /*#__PURE__*/React__namespace.createElement(Element, _extends__default["default"]({}, elementProps, {
24295
23999
  ref: ref
24296
24000
  })))
24297
24001
  );
@@ -24383,7 +24087,6 @@ const styles$o = aphrodite.StyleSheet.create({
24383
24087
  paddingLeft: phoneMargin
24384
24088
  // paddingRight is handled by responsiveFieldset
24385
24089
  },
24386
-
24387
24090
  responsiveFieldset: {
24388
24091
  paddingRight: phoneMargin,
24389
24092
  minWidth: "auto"
@@ -24627,7 +24330,6 @@ class Radio$1 extends React__namespace.Component {
24627
24330
  );
24628
24331
  }
24629
24332
  };
24630
-
24631
24333
  render() {
24632
24334
  const {
24633
24335
  choices
@@ -24743,22 +24445,17 @@ class Radio$1 extends React__namespace.Component {
24743
24445
  }
24744
24446
  }
24745
24447
 
24746
- const {
24747
- shuffle: shuffle$2,
24748
- random
24749
- } = Util;
24750
-
24751
24448
  // Transforms the choices for display.
24752
24449
  const _choiceTransform = (widgetOptions, strings, problemNum) => {
24753
24450
  const _maybeRandomize = function (array) {
24754
- const randomSeed = problemNum === undefined ? random : problemNum;
24451
+ const randomSeed = problemNum === undefined ? perseusCore.random : problemNum;
24755
24452
  // NOTE: `problemNum` will only be set when the radio widget is
24756
24453
  // rendered at the root of an exercise question. It will be `undefined`
24757
24454
  // if it's rendered embedded in another widget, such as `graded-group`,
24758
24455
  // or if rendered within an article. This results in a predictable
24759
24456
  // shuffle order. To avoid this we use a random seed when `problemNum`
24760
24457
  // is `undefined`.
24761
- return widgetOptions.randomize ? shuffle$2(array, randomSeed ?? 0) : array;
24458
+ return widgetOptions.randomize ? perseusCore.shuffle(array, randomSeed ?? 0) : array;
24762
24459
  };
24763
24460
  const _addNoneOfAbove = function (choices) {
24764
24461
  let noneOfTheAbove = null;
@@ -24809,7 +24506,7 @@ const _choiceTransform = (widgetOptions, strings, problemNum) => {
24809
24506
  // 1) we randomize the order first
24810
24507
  _maybeRandomize(choices)));
24811
24508
  };
24812
- const transform = (widgetOptions, strings, problemNum) => {
24509
+ const transform$1 = (widgetOptions, strings, problemNum) => {
24813
24510
  const choices = _choiceTransform(widgetOptions, strings, problemNum);
24814
24511
  const numCorrect = ___default["default"].reduce(widgetOptions.choices, function (memo, choice) {
24815
24512
  return choice.correct ? memo + 1 : memo;
@@ -24835,8 +24532,8 @@ var Radio = {
24835
24532
  displayName: "Radio / Multiple choice",
24836
24533
  accessible: true,
24837
24534
  widget: Radio$1,
24838
- transform: transform,
24839
- staticTransform: transform,
24535
+ transform: transform$1,
24536
+ staticTransform: transform$1,
24840
24537
  version: perseusCore.radioLogic.version,
24841
24538
  propUpgrades: perseusCore.radioLogic.widgetOptionsUpgrades,
24842
24539
  isLintable: true
@@ -24999,8 +24696,7 @@ class Categorizer extends React__namespace.Component {
24999
24696
  const isMobile = this.props.apiOptions.isMobile;
25000
24697
  let indexedItems = this.props.items.map((item, n) => [item, n]);
25001
24698
  if (this.props.randomizeItems) {
25002
- // @ts-expect-error - TS4104 - The type 'readonly (string | number)[][]' is 'readonly' and cannot be assigned to the mutable type '(string | number)[][]'. | TS2345 - Argument of type 'number | null | undefined' is not assignable to parameter of type 'number | RNG'.
25003
- indexedItems = Util.shuffle(indexedItems, this.props.problemNum);
24699
+ indexedItems = perseusCore.shuffle(indexedItems, this.props.problemNum);
25004
24700
  }
25005
24701
  const table = /*#__PURE__*/React__namespace.createElement("table", {
25006
24702
  className: "categorizer-table " + aphrodite.css(styles$n.mobileTable)
@@ -25026,9 +24722,7 @@ class Categorizer extends React__namespace.Component {
25026
24722
  const uniqueId = self.state.uniqueId + "_" + itemNum;
25027
24723
  return /*#__PURE__*/React__namespace.createElement("tr", {
25028
24724
  key: itemNum
25029
- }, /*#__PURE__*/React__namespace.createElement("td", null, /*#__PURE__*/React__namespace.createElement(Renderer
25030
- // @ts-expect-error - TS2322 - Type 'string | number' is not assignable to type 'string | undefined'.
25031
- , {
24725
+ }, /*#__PURE__*/React__namespace.createElement("td", null, /*#__PURE__*/React__namespace.createElement(Renderer, {
25032
24726
  content: item,
25033
24727
  linterContext: this.props.linterContext,
25034
24728
  strings: this.context.strings
@@ -25421,6 +25115,7 @@ var Definition$1 = {
25421
25115
 
25422
25116
  // There are no required props, but this component can receive props from any
25423
25117
  // deprecated widget
25118
+
25424
25119
  class DeprecatedStandin extends React__default["default"].Component {
25425
25120
  static contextType = PerseusI18nContext;
25426
25121
  // this just helps with TS weak typing when a Widget
@@ -25541,20 +25236,20 @@ class Dropdown extends React__namespace.Component {
25541
25236
  }, children)));
25542
25237
  }
25543
25238
  }
25544
- const optionsTransform = widgetOptions => {
25239
+ function transform(widgetOptions) {
25545
25240
  return {
25546
25241
  placeholder: widgetOptions.placeholder,
25547
25242
  visibleLabel: widgetOptions.visibleLabel,
25548
25243
  ariaLabel: widgetOptions.ariaLabel,
25549
25244
  choices: widgetOptions.choices.map(choice => choice.content)
25550
25245
  };
25551
- };
25246
+ }
25552
25247
  var Dropdown$1 = {
25553
25248
  name: "dropdown",
25554
25249
  displayName: "Drop down",
25555
25250
  accessible: true,
25556
25251
  widget: Dropdown,
25557
- transform: optionsTransform
25252
+ transform
25558
25253
  };
25559
25254
 
25560
25255
  const getPromptJSON$j = renderProps => {
@@ -25630,7 +25325,6 @@ class Explanation extends React__namespace.Component {
25630
25325
  // adds space between the right border and any text that follows
25631
25326
  paddingLeft: "2px" // adds space between the left border and the button text
25632
25327
  };
25633
-
25634
25328
  const labelStyle = {
25635
25329
  fontSize: "18px",
25636
25330
  lineHeight: "inherit",
@@ -25786,7 +25480,7 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25786
25480
  fontSize: 28,
25787
25481
  color: wonderBlocksTokens.color.green
25788
25482
  }
25789
- }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconStar, {
25483
+ }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconStar, {
25790
25484
  style: {
25791
25485
  marginBottom: 5
25792
25486
  }
@@ -25979,7 +25673,7 @@ class GradedGroup extends React__namespace.Component {
25979
25673
  if (this.state.status === GRADING_STATUSES.correct) {
25980
25674
  // TODO(jeremy): update to a WB colour
25981
25675
  // @ts-expect-error - TS2322 - Type 'Element' is not assignable to type 'null'.
25982
- icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconOk, {
25676
+ icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconOk, {
25983
25677
  style: {
25984
25678
  color: "#526f03"
25985
25679
  }
@@ -25988,7 +25682,7 @@ class GradedGroup extends React__namespace.Component {
25988
25682
  } else if (this.state.status === GRADING_STATUSES.incorrect) {
25989
25683
  // TODO(jeremy): update to a WB colour
25990
25684
  // @ts-expect-error - TS2322 - Type 'Element' is not assignable to type 'null'.
25991
- icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconRemove, {
25685
+ icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconRemove, {
25992
25686
  style: {
25993
25687
  color: "#ff5454"
25994
25688
  }
@@ -26015,7 +25709,7 @@ class GradedGroup extends React__namespace.Component {
26015
25709
  className: classes
26016
25710
  }, !!this.props.title && /*#__PURE__*/React__namespace.createElement("div", {
26017
25711
  className: aphrodite.css(styles$i.title)
26018
- }, this.props.title), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props, {
25712
+ }, this.props.title), /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({}, this.props, {
26019
25713
  ref: this.rendererRef,
26020
25714
  apiOptions: {
26021
25715
  ...apiOptions,
@@ -26058,7 +25752,7 @@ class GradedGroup extends React__namespace.Component {
26058
25752
  showHint: false
26059
25753
  });
26060
25754
  }
26061
- }, this.context.strings.hideExplanation), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props.hint, {
25755
+ }, this.context.strings.hideExplanation), /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({}, this.props.hint, {
26062
25756
  ref: this.hintRendererRef,
26063
25757
  apiOptions: apiOptions,
26064
25758
  linterContext: this.props.linterContext,
@@ -26283,7 +25977,7 @@ class GradedGroupSet extends React__namespace.Component {
26283
25977
  // TODO(jeremy): Don't spread this.props, instead
26284
25978
  // pass in all props GradedGroup needs explicilty
26285
25979
  // @ts-expect-error - TS2769 - No overload matches this call.
26286
- React__namespace.createElement(GradedGroup, _extends({
25980
+ React__namespace.createElement(GradedGroup, _extends__default["default"]({
26287
25981
  key: i
26288
25982
  }, this.props, gradedGroup, {
26289
25983
  inGradedGroupSet: false,
@@ -26312,7 +26006,7 @@ class GradedGroupSet extends React__namespace.Component {
26312
26006
  onChangeCurrentGroup: currentGroup => this.setState({
26313
26007
  currentGroup
26314
26008
  })
26315
- })), /*#__PURE__*/React__namespace.createElement(GradedGroup, _extends({
26009
+ })), /*#__PURE__*/React__namespace.createElement(GradedGroup, _extends__default["default"]({
26316
26010
  key: this.state.currentGroup
26317
26011
  // @ts-expect-error - TS2322 - Type 'GradedGroup | null' is not assignable to type 'GradedGroup'.
26318
26012
  // Type 'null' is not assignable to type 'GradedGroup'.
@@ -26708,7 +26402,7 @@ class FunctionGrapher extends React__namespace.Component {
26708
26402
  }
26709
26403
  const functionProps = model.getPropsForCoeffs(coeffs, xRange);
26710
26404
  const Movable = movableTypeToComponent[model.movable];
26711
- return /*#__PURE__*/React__namespace.createElement(Movable, _extends({}, functionProps, {
26405
+ return /*#__PURE__*/React__namespace.createElement(Movable, _extends__default["default"]({}, functionProps, {
26712
26406
  key: this.props.model.url,
26713
26407
  range: xRange,
26714
26408
  style: style
@@ -26841,7 +26535,7 @@ class FunctionGrapher extends React__namespace.Component {
26841
26535
  width: box[0],
26842
26536
  height: box[1]
26843
26537
  }
26844
- }, image, /*#__PURE__*/React__namespace.createElement(Graphie, _extends({}, this.props.graph, {
26538
+ }, image, /*#__PURE__*/React__namespace.createElement(Graphie, _extends__default["default"]({}, this.props.graph, {
26845
26539
  setDrawingAreaAvailable: this.props.setDrawingAreaAvailable
26846
26540
  }), this.props.model && this.renderPlot(), this.props.model && this.renderAsymptote(), this.props.model && points)));
26847
26541
  }
@@ -27985,7 +27679,6 @@ class Interaction extends React__namespace.Component {
27985
27679
  }
27986
27680
  });
27987
27681
  }
27988
-
27989
27682
  if (element.type === "parametric") {
27990
27683
  const fn = t => {
27991
27684
  return [
@@ -28038,7 +27731,6 @@ class Interaction extends React__namespace.Component {
28038
27731
  }
28039
27732
  });
28040
27733
  }
28041
-
28042
27734
  if (element.type === "label") {
28043
27735
  const coord = [
28044
27736
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -28370,7 +28062,6 @@ function scaleCommandBy(scaleFactor) {
28370
28062
  command.args[6] * scaleFactor // end y
28371
28063
  ]
28372
28064
  };
28373
-
28374
28065
  default:
28375
28066
  return {
28376
28067
  ...command,
@@ -29130,7 +28821,6 @@ const styles$f = aphrodite.StyleSheet.create({
29130
28821
  userSelect: "none",
29131
28822
  borderWidth: 0 // <button> gives us a border by default
29132
28823
  },
29133
-
29134
28824
  // We want to render the element in-place in our fixtures, so allow us to
29135
28825
  // render in the bottom corner in production and in-place in testing.
29136
28826
  hudFixedPosition: {
@@ -29160,7 +28850,6 @@ const styles$f = aphrodite.StyleSheet.create({
29160
28850
  ":hover": {
29161
28851
  backgroundColor: "#a1a5a9" // in between those two grays
29162
28852
  },
29163
-
29164
28853
  ":active": {
29165
28854
  backgroundColor: gray68
29166
28855
  }
@@ -29205,7 +28894,6 @@ class InfoTip extends React__namespace.Component {
29205
28894
  });
29206
28895
  /* eslint-enable react/no-did-mount-set-state */
29207
28896
  }
29208
-
29209
28897
  render() {
29210
28898
  if (this.state.didMount) {
29211
28899
  return /*#__PURE__*/React__namespace.createElement(InfoTipBase, this.props);
@@ -29471,7 +29159,6 @@ class NumberInput extends React__namespace.Component {
29471
29159
  if (val !== Math.floor(val)) {
29472
29160
  return; // bail if not an integer
29473
29161
  }
29474
-
29475
29162
  if (e.key === "ArrowUp") {
29476
29163
  val = val + 1;
29477
29164
  } else if (e.key === "ArrowDown") {
@@ -29504,7 +29191,7 @@ class NumberInput extends React__namespace.Component {
29504
29191
  useArrowKeys,
29505
29192
  ...restProps
29506
29193
  } = this.props;
29507
- return /*#__PURE__*/React__namespace.createElement("input", _extends({}, restProps, {
29194
+ return /*#__PURE__*/React__namespace.createElement("input", _extends__default["default"]({}, restProps, {
29508
29195
  className: classes,
29509
29196
  type: "text",
29510
29197
  ref: this.inputRef,
@@ -29549,7 +29236,7 @@ class RangeInput extends React__namespace.Component {
29549
29236
  const checkValidity = this.props.checkValidity || truth;
29550
29237
  return /*#__PURE__*/React__namespace.createElement("div", {
29551
29238
  className: "range-input"
29552
- }, /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
29239
+ }, /*#__PURE__*/React__namespace.createElement(NumberInput, _extends__default["default"]({}, this.props, {
29553
29240
  value: value[0],
29554
29241
  checkValidity: val => checkValidity([val, value[1]])
29555
29242
  // eslint-disable-next-line react/jsx-no-bind
@@ -29557,7 +29244,7 @@ class RangeInput extends React__namespace.Component {
29557
29244
  onChange: this.onChange.bind(this, 0),
29558
29245
  placeholder: this.props.placeholder[0],
29559
29246
  allowPiTruncation: this.props.allowPiTruncation
29560
- })), /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
29247
+ })), /*#__PURE__*/React__namespace.createElement(NumberInput, _extends__default["default"]({}, this.props, {
29561
29248
  value: value[1],
29562
29249
  checkValidity: val => checkValidity([value[0], val])
29563
29250
  // eslint-disable-next-line react/jsx-no-bind
@@ -29570,20 +29257,20 @@ class RangeInput extends React__namespace.Component {
29570
29257
  }
29571
29258
 
29572
29259
  var components = /*#__PURE__*/Object.freeze({
29573
- __proto__: null,
29574
- ButtonGroup: ButtonGroup,
29575
- Graphie: Graphie,
29576
- HUD: HUD,
29577
- Icon: Icon,
29578
- InfoTip: InfoTip,
29579
- InlineIcon: InlineIcon,
29580
- MathInput: MathInput,
29581
- MultiButtonGroup: MultiButtonGroup,
29582
- NumberInput: NumberInput,
29583
- RangeInput: RangeInput,
29584
- SvgImage: SvgImage,
29585
- TextInput: TextInput,
29586
- TextListEditor: TextListEditor
29260
+ __proto__: null,
29261
+ ButtonGroup: ButtonGroup,
29262
+ Graphie: Graphie,
29263
+ HUD: HUD,
29264
+ Icon: Icon,
29265
+ InfoTip: InfoTip,
29266
+ InlineIcon: InlineIcon,
29267
+ MathInput: MathInput,
29268
+ MultiButtonGroup: MultiButtonGroup,
29269
+ NumberInput: NumberInput,
29270
+ RangeInput: RangeInput,
29271
+ SvgImage: SvgImage,
29272
+ TextInput: TextInput,
29273
+ TextListEditor: TextListEditor
29587
29274
  });
29588
29275
 
29589
29276
  /**
@@ -29659,17 +29346,17 @@ function GraphLockedLabelsLayer(props) {
29659
29346
  } = props;
29660
29347
  return lockedFigures.map((figure, i) => {
29661
29348
  if (figure.type === "label") {
29662
- return /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29349
+ return /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends__default["default"]({
29663
29350
  key: `label-${i}`
29664
29351
  }, figure));
29665
29352
  }
29666
29353
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, {
29667
29354
  key: i
29668
- }, figure.labels?.map((label, j) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29355
+ }, figure.labels?.map((label, j) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends__default["default"]({
29669
29356
  key: `${i}-label-${j}`
29670
- }, 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({
29357
+ }, 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__default["default"]({
29671
29358
  key: `locked-figure-${i}-point-1-label-${k}`
29672
- }, label))), figure.points[1].labels?.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29359
+ }, label))), figure.points[1].labels?.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends__default["default"]({
29673
29360
  key: `locked-figure-${i}-point-2-label-${k}`
29674
29361
  }, label)))));
29675
29362
  });
@@ -29715,9 +29402,9 @@ const LockedFunction = props => {
29715
29402
  const {
29716
29403
  color,
29717
29404
  strokeStyle,
29718
- directionalAxis
29405
+ directionalAxis,
29406
+ domain
29719
29407
  } = props;
29720
- const domain = props.domain ? [Number.isFinite(props.domain[0]) ? props.domain[0] : -Infinity, Number.isFinite(props.domain[1]) ? props.domain[1] : Infinity] : undefined;
29721
29408
  const plotProps = {
29722
29409
  color: perseusCore.lockedFigureColors[color],
29723
29410
  style: strokeStyle,
@@ -29737,11 +29424,11 @@ const LockedFunction = props => {
29737
29424
  "aria-label": hasAria ? props.ariaLabel : undefined,
29738
29425
  "aria-hidden": !hasAria,
29739
29426
  role: "img"
29740
- }, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends({
29427
+ }, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends__default["default"]({
29741
29428
  y: x => equation.eval({
29742
29429
  x
29743
29430
  })
29744
- }, plotProps)), directionalAxis === "y" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfY, _extends({
29431
+ }, plotProps)), directionalAxis === "y" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfY, _extends__default["default"]({
29745
29432
  x: y => equation.eval({
29746
29433
  y
29747
29434
  })
@@ -30315,28 +30002,28 @@ const GraphLockedLayer = props => {
30315
30002
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lockedFigures.map((figure, index) => {
30316
30003
  switch (figure.type) {
30317
30004
  case "point":
30318
- return /*#__PURE__*/React__namespace.createElement(LockedPoint, _extends({
30005
+ return /*#__PURE__*/React__namespace.createElement(LockedPoint, _extends__default["default"]({
30319
30006
  key: `point-${index}`
30320
30007
  }, figure));
30321
30008
  case "line":
30322
- return /*#__PURE__*/React__namespace.createElement(LockedLine, _extends({
30009
+ return /*#__PURE__*/React__namespace.createElement(LockedLine, _extends__default["default"]({
30323
30010
  key: `line-${index}`,
30324
30011
  range: props.range
30325
30012
  }, figure));
30326
30013
  case "vector":
30327
- return /*#__PURE__*/React__namespace.createElement(LockedVector, _extends({
30014
+ return /*#__PURE__*/React__namespace.createElement(LockedVector, _extends__default["default"]({
30328
30015
  key: `vector-${index}`
30329
30016
  }, figure));
30330
30017
  case "ellipse":
30331
- return /*#__PURE__*/React__namespace.createElement(LockedEllipse, _extends({
30018
+ return /*#__PURE__*/React__namespace.createElement(LockedEllipse, _extends__default["default"]({
30332
30019
  key: `ellipse-${index}`
30333
30020
  }, figure));
30334
30021
  case "polygon":
30335
- return /*#__PURE__*/React__namespace.createElement(LockedPolygon, _extends({
30022
+ return /*#__PURE__*/React__namespace.createElement(LockedPolygon, _extends__default["default"]({
30336
30023
  key: `polygon-${index}`
30337
30024
  }, figure));
30338
30025
  case "function":
30339
- return /*#__PURE__*/React__namespace.createElement(LockedFunction, _extends({
30026
+ return /*#__PURE__*/React__namespace.createElement(LockedFunction, _extends__default["default"]({
30340
30027
  key: `function-${index}`
30341
30028
  }, figure));
30342
30029
  case "label":
@@ -30567,7 +30254,7 @@ const TextLabel = _ref => {
30567
30254
  children,
30568
30255
  ...rest
30569
30256
  } = _ref;
30570
- return /*#__PURE__*/React__default["default"].createElement(mafs.Text, _extends({
30257
+ return /*#__PURE__*/React__default["default"].createElement(mafs.Text, _extends__default["default"]({
30571
30258
  size: 16,
30572
30259
  svgTextProps: {
30573
30260
  filter: "url(#background)",
@@ -31278,6 +30965,9 @@ const MovableLine = props => {
31278
30965
  onMoveLine = () => {},
31279
30966
  onMovePoint = () => {}
31280
30967
  } = props;
30968
+ const {
30969
+ snapStep
30970
+ } = useGraphConfig();
31281
30971
 
31282
30972
  // Aria live states for (0) point 1, (1) point 2, and (2) grab handle.
31283
30973
  // When moving an element, set its aria live to "polite" and the others
@@ -31309,7 +30999,8 @@ const MovableLine = props => {
31309
30999
  onMove: p => {
31310
31000
  setAriaLives(["polite", "off", "off"]);
31311
31001
  onMovePoint(0, p);
31312
- }
31002
+ },
31003
+ constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 0)
31313
31004
  });
31314
31005
  const {
31315
31006
  visiblePoint: visiblePoint2,
@@ -31324,7 +31015,8 @@ const MovableLine = props => {
31324
31015
  onMove: p => {
31325
31016
  setAriaLives(["off", "polite", "off"]);
31326
31017
  onMovePoint(1, p);
31327
- }
31018
+ },
31019
+ constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 1)
31328
31020
  });
31329
31021
  const line = /*#__PURE__*/React__namespace.createElement(Line$1, {
31330
31022
  ariaLabel: ariaLabels?.grabHandleAriaLabel,
@@ -31430,6 +31122,34 @@ const Line$1 = props => {
31430
31122
  color: stroke
31431
31123
  }));
31432
31124
  };
31125
+ const getMovableLineKeyboardConstraint = (line, snapStep, pointIndex) => {
31126
+ // Separate the two points into their own variables, and determine which point is being moved
31127
+ const coordToBeMoved = line[pointIndex];
31128
+ const otherPoint = line[1 - pointIndex];
31129
+
31130
+ // Create a helper function that moves the point and then checks
31131
+ // if it overlaps with the other point in the line after the move.
31132
+ const movePointWithConstraint = moveFunc => {
31133
+ // Move the point
31134
+ let movedCoord = moveFunc(coordToBeMoved);
31135
+ // If the moved point overlaps with the other point in the line,
31136
+ // move the point again.
31137
+ if (mafs.vec.dist(movedCoord, otherPoint) === 0) {
31138
+ movedCoord = moveFunc(movedCoord);
31139
+ }
31140
+ return movedCoord;
31141
+ };
31142
+
31143
+ // Check if the new point will overlap the other point.
31144
+ // If it will, we need to snap the point to the left or right an additional
31145
+ // snapStep to avoid overlap.
31146
+ return {
31147
+ up: movePointWithConstraint(coord => mafs.vec.add(coord, [0, snapStep[1]])),
31148
+ down: movePointWithConstraint(coord => mafs.vec.sub(coord, [0, snapStep[1]])),
31149
+ left: movePointWithConstraint(coord => mafs.vec.sub(coord, [snapStep[0], 0])),
31150
+ right: movePointWithConstraint(coord => mafs.vec.add(coord, [snapStep[0], 0]))
31151
+ };
31152
+ };
31433
31153
  function trimRange(range, graphDimensionsInPixels) {
31434
31154
  const pixelsToTrim = 4;
31435
31155
  const [xRange, yRange] = range;
@@ -31786,7 +31506,8 @@ function CircleGraph(props) {
31786
31506
  } = props;
31787
31507
  const {
31788
31508
  center,
31789
- radiusPoint
31509
+ radiusPoint,
31510
+ snapStep
31790
31511
  } = graphState;
31791
31512
  const {
31792
31513
  strings,
@@ -31849,7 +31570,8 @@ function CircleGraph(props) {
31849
31570
  onMove: newRadiusPoint => {
31850
31571
  setRadiusPointAriaLive("polite");
31851
31572
  dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
31852
- }
31573
+ },
31574
+ constrain: getCircleKeyboardConstraint(center, radiusPoint, snapStep)
31853
31575
  }), /*#__PURE__*/React__namespace.createElement("g", {
31854
31576
  id: radiusId,
31855
31577
  style: a11y.srOnly
@@ -31960,6 +31682,30 @@ function getCircleGraphDescription(state, i18n) {
31960
31682
  const strings = describeCircleGraph(state, i18n);
31961
31683
  return strings.srCircleInteractiveElement;
31962
31684
  }
31685
+ const getCircleKeyboardConstraint = (center, radiusPoint, snapStep) => {
31686
+ // Create a helper function that moves the point and then checks
31687
+ // if it overlaps with the center point after the move.
31688
+ const movePointWithConstraint = moveFunc => {
31689
+ // Move the point
31690
+ let movedCoord = moveFunc(radiusPoint);
31691
+ // If the moved point overlaps with the center point,
31692
+ // move the point again.
31693
+ if (mafs.vec.dist(movedCoord, center) === 0) {
31694
+ movedCoord = moveFunc(movedCoord);
31695
+ }
31696
+ return movedCoord;
31697
+ };
31698
+
31699
+ // Check if the new point overlaps the center point.
31700
+ // If it does, we need to snap the point to the left
31701
+ // or right an additional snapStep to avoid the overlap.
31702
+ return {
31703
+ up: movePointWithConstraint(coord => mafs.vec.add(coord, [0, snapStep[1]])),
31704
+ down: movePointWithConstraint(coord => mafs.vec.sub(coord, [0, snapStep[1]])),
31705
+ left: movePointWithConstraint(coord => mafs.vec.sub(coord, [snapStep[0], 0])),
31706
+ right: movePointWithConstraint(coord => mafs.vec.add(coord, [snapStep[0], 0]))
31707
+ };
31708
+ };
31963
31709
 
31964
31710
  // Exported for testing
31965
31711
  function describeCircleGraph(state, i18n) {
@@ -31972,6 +31718,7 @@ function describeCircleGraph(state, i18n) {
31972
31718
  radiusPoint
31973
31719
  } = state;
31974
31720
  const radius = getRadius(state);
31721
+ const isRadiusOnRight = radiusPoint[X] >= center[X];
31975
31722
 
31976
31723
  // Aria label strings
31977
31724
  const srCircleGraph = strings.srCircleGraph;
@@ -31979,7 +31726,10 @@ function describeCircleGraph(state, i18n) {
31979
31726
  centerX: srFormatNumber(center[0], locale),
31980
31727
  centerY: srFormatNumber(center[1], locale)
31981
31728
  });
31982
- const srCircleRadiusPoint = strings.srCircleRadiusPoint({
31729
+ const srCircleRadiusPoint = isRadiusOnRight ? strings.srCircleRadiusPointRight({
31730
+ radiusPointX: srFormatNumber(radiusPoint[0], locale),
31731
+ radiusPointY: srFormatNumber(radiusPoint[1], locale)
31732
+ }) : strings.srCircleRadiusPointLeft({
31983
31733
  radiusPointX: srFormatNumber(radiusPoint[0], locale),
31984
31734
  radiusPointY: srFormatNumber(radiusPoint[1], locale)
31985
31735
  });
@@ -32145,6 +31895,12 @@ const LinearSystemGraph = props => {
32145
31895
  locale
32146
31896
  } = usePerseusI18n();
32147
31897
  const id = React__namespace.useId();
31898
+ const intersectionId = `${id}-intersection`;
31899
+ const intersectionPoint = kmath.geometry.getLineIntersection(lines[0], lines[1]);
31900
+ const intersectionDescription = intersectionPoint ? strings.srLinearSystemIntersection({
31901
+ x: srFormatNumber(intersectionPoint[0], locale),
31902
+ y: srFormatNumber(intersectionPoint[1], locale)
31903
+ }) : strings.srLinearSystemParallel;
32148
31904
  const linesAriaInfo = lines.map((line, i) => {
32149
31905
  return {
32150
31906
  pointsDescriptionId: `${id}-line${i + 1}-points`,
@@ -32161,16 +31917,17 @@ const LinearSystemGraph = props => {
32161
31917
  slopeDescription: getSlopeStringForLine(line, strings)
32162
31918
  };
32163
31919
  });
31920
+ const individualLineDescriptions = linesAriaInfo.map(_ref => {
31921
+ let {
31922
+ pointsDescriptionId,
31923
+ interceptDescriptionId,
31924
+ slopeDescriptionId
31925
+ } = _ref;
31926
+ return `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`;
31927
+ }).join(" ");
32164
31928
  return /*#__PURE__*/React__namespace.createElement("g", {
32165
31929
  "aria-label": strings.srLinearSystemGraph,
32166
- "aria-describedby": linesAriaInfo.map(_ref => {
32167
- let {
32168
- pointsDescriptionId,
32169
- interceptDescriptionId,
32170
- slopeDescriptionId
32171
- } = _ref;
32172
- return `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`;
32173
- }).join(" ")
31930
+ "aria-describedby": `${individualLineDescriptions} ${intersectionId}`
32174
31931
  }, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(MovableLine, {
32175
31932
  key: i,
32176
31933
  points: line,
@@ -32187,9 +31944,15 @@ const LinearSystemGraph = props => {
32187
31944
  x: srFormatNumber(line[1][0], locale),
32188
31945
  y: srFormatNumber(line[1][1], locale)
32189
31946
  }),
32190
- grabHandleAriaLabel: `${linesAriaInfo[i].interceptDescription} ${linesAriaInfo[i].slopeDescription}`
31947
+ grabHandleAriaLabel: strings.srLinearSystemGrabHandle({
31948
+ lineNumber: i + 1,
31949
+ point1X: srFormatNumber(line[0][0], locale),
31950
+ point1Y: srFormatNumber(line[0][1], locale),
31951
+ point2X: srFormatNumber(line[1][0], locale),
31952
+ point2Y: srFormatNumber(line[1][1], locale)
31953
+ })
32191
31954
  },
32192
- ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId}`,
31955
+ ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId} ${intersectionId}`,
32193
31956
  onMoveLine: delta => {
32194
31957
  dispatch(actions.linearSystem.moveLine(i, delta));
32195
31958
  },
@@ -32221,7 +31984,10 @@ const LinearSystemGraph = props => {
32221
31984
  id: slopeDescriptionId,
32222
31985
  style: a11y.srOnly
32223
31986
  }, slopeDescription));
32224
- }));
31987
+ }), /*#__PURE__*/React__namespace.createElement("g", {
31988
+ id: intersectionId,
31989
+ style: a11y.srOnly
31990
+ }, intersectionDescription));
32225
31991
  };
32226
31992
 
32227
31993
  // Exported for testing
@@ -32887,7 +32653,8 @@ function QuadraticGraph(props) {
32887
32653
  graphState
32888
32654
  } = props;
32889
32655
  const {
32890
- coords
32656
+ coords,
32657
+ snapStep
32891
32658
  } = graphState;
32892
32659
  const {
32893
32660
  strings,
@@ -32940,6 +32707,7 @@ function QuadraticGraph(props) {
32940
32707
  ariaLabel: `${srQuadraticPoint}${srVertex}`,
32941
32708
  point: coord,
32942
32709
  sequenceNumber: i + 1,
32710
+ constrain: getQuadraticKeyboardConstraint(coords, snapStep, i),
32943
32711
  onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
32944
32712
  });
32945
32713
  }), srQuadraticDirection && /*#__PURE__*/React__namespace.createElement("g", {
@@ -33028,6 +32796,60 @@ function describeQuadraticGraph(state, i18n) {
33028
32796
  srQuadraticInteractiveElements
33029
32797
  };
33030
32798
  }
32799
+ const getQuadraticKeyboardConstraint = (coords, snapStep, pointMoved) => {
32800
+ // Make newCoords mutable
32801
+ const newCoords = [coords[0], coords[1], coords[2]];
32802
+
32803
+ // Get the point that is being moved
32804
+ const coordToBeMoved = newCoords[pointMoved];
32805
+
32806
+ // Create a function to validate and adjust the movement of the point
32807
+ const movePointWithConstraint = moveFunc => {
32808
+ // Move the point the desired direction
32809
+ let movedCoord = moveFunc(coordToBeMoved);
32810
+ newCoords[pointMoved] = movedCoord;
32811
+
32812
+ // If these new coordinates are valid, return the moved coord
32813
+ if (areCoordsValid(newCoords)) {
32814
+ return movedCoord;
32815
+ }
32816
+
32817
+ // If the new coordinates are invalid, we need to move the point an additional
32818
+ // snapStep to avoid creating an invalid quadratic graph.
32819
+ movedCoord = moveFunc(movedCoord);
32820
+ newCoords[pointMoved] = movedCoord;
32821
+
32822
+ // If these updated coordinates are valid now, we can return the new coordinates.
32823
+ if (areCoordsValid(newCoords)) {
32824
+ return movedCoord;
32825
+ }
32826
+
32827
+ // Otherwise, if the new coordinates are still invalid, we need to move the point one final snapStep.
32828
+ // This is to support the edge case where all of the points are each exactly one snapStep away
32829
+ // from each other.
32830
+ // Eg. When coords = [[0, 0], [1, 0], [2, 0]], snapStep = [1, 1], and we're moving coords[0] to the right.
32831
+ return moveFunc(movedCoord);
32832
+ };
32833
+ return {
32834
+ up: mafs.vec.add(coordToBeMoved, [0, snapStep[1]]),
32835
+ down: mafs.vec.sub(coordToBeMoved, [0, snapStep[1]]),
32836
+ // For horizontal movement, we need to ensure that the points are not on the same vertical line.
32837
+ left: movePointWithConstraint(coord => mafs.vec.sub(coord, [snapStep[0], 0])),
32838
+ right: movePointWithConstraint(coord => mafs.vec.add(coord, [snapStep[0], 0]))
32839
+ };
32840
+ };
32841
+ const areCoordsValid = coords => {
32842
+ const p1 = coords[0];
32843
+ const p2 = coords[1];
32844
+ const p3 = coords[2];
32845
+
32846
+ // If any of the points share the same x-coordinate,
32847
+ // we are unable to calculate the coefficients, and the graph is invalid.
32848
+ if (p1[0] === p2[0] || p2[0] === p3[0] || p1[0] === p3[0]) {
32849
+ return false;
32850
+ }
32851
+ return true;
32852
+ };
33031
32853
 
33032
32854
  function renderRayGraph(state, dispatch, i18n) {
33033
32855
  return {
@@ -33283,7 +33105,8 @@ function SinusoidGraph(props) {
33283
33105
  // Note: The order of the coordinates is important:
33284
33106
  // The coords[0] is the root and the coords[1] is the first peak
33285
33107
  const {
33286
- coords
33108
+ coords,
33109
+ snapStep
33287
33110
  } = graphState;
33288
33111
 
33289
33112
  // The coefficients are used to calculate the sinusoid equation, plot the graph, and to indicate
@@ -33322,11 +33145,37 @@ function SinusoidGraph(props) {
33322
33145
  key: "point-" + i,
33323
33146
  point: coord,
33324
33147
  sequenceNumber: i + 1,
33148
+ constrain: getSinusoidKeyboardConstraint(coords, snapStep, i),
33325
33149
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
33326
33150
  })), /*#__PURE__*/React__namespace.createElement("g", {
33327
33151
  id: descriptionId
33328
33152
  }, srSinusoidDescription));
33329
33153
  }
33154
+ const getSinusoidKeyboardConstraint = (coords, snapStep, pointIndex) => {
33155
+ // Separate the two points into their own variables, and determine which point is being moved
33156
+ const coordToBeMoved = coords[pointIndex];
33157
+ const otherPoint = coords[1 - pointIndex];
33158
+
33159
+ // Create a helper function that checks if the new point is on the same
33160
+ // vertical line as the other point. If it is, we need to move the point
33161
+ // an additional snapStep.
33162
+ const movePointWithConstraint = moveFunc => {
33163
+ // Move the point
33164
+ let movedCoord = moveFunc(coordToBeMoved);
33165
+ // If the moved point overlaps with the other point in the line,
33166
+ // move the point again.
33167
+ if (movedCoord[X] === otherPoint[X]) {
33168
+ movedCoord = moveFunc(movedCoord);
33169
+ }
33170
+ return movedCoord;
33171
+ };
33172
+ return {
33173
+ up: movePointWithConstraint(coord => mafs.vec.add(coord, [0, snapStep[1]])),
33174
+ down: movePointWithConstraint(coord => mafs.vec.sub(coord, [0, snapStep[1]])),
33175
+ left: movePointWithConstraint(coord => mafs.vec.sub(coord, [snapStep[0], 0])),
33176
+ right: movePointWithConstraint(coord => mafs.vec.add(coord, [snapStep[0], 0]))
33177
+ };
33178
+ };
33330
33179
 
33331
33180
  // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
33332
33181
  const computeSine = function (x,
@@ -33378,6 +33227,14 @@ function describeSinusoidGraph(state, i18n) {
33378
33227
  const [root, peak] = coords;
33379
33228
  const diffX = Math.abs(peak[X] - root[X]);
33380
33229
  const diffY = Math.abs(peak[Y] - root[Y]);
33230
+ const formattedRoot = {
33231
+ x: srFormatNumber(root[X], locale),
33232
+ y: srFormatNumber(root[Y], locale)
33233
+ };
33234
+ const formattedPeak = {
33235
+ x: srFormatNumber(peak[X], locale),
33236
+ y: srFormatNumber(peak[Y], locale)
33237
+ };
33381
33238
  const srSinusoidGraph = strings.srSinusoidGraph;
33382
33239
  const srSinusoidDescription = strings.srSinusoidDescription({
33383
33240
  minValue: srFormatNumber(root[Y] - diffY, locale),
@@ -33385,14 +33242,8 @@ function describeSinusoidGraph(state, i18n) {
33385
33242
  cycleStart: srFormatNumber(root[X] - 2 * diffX, locale),
33386
33243
  cycleEnd: srFormatNumber(root[X] + 2 * diffX, locale)
33387
33244
  });
33388
- const srSinusoidRootPoint = strings.srSinusoidRootPoint({
33389
- x: srFormatNumber(root[X], locale),
33390
- y: srFormatNumber(root[Y], locale)
33391
- });
33392
- const srSinusoidPeakPoint = strings.srSinusoidPeakPoint({
33393
- x: srFormatNumber(peak[X], locale),
33394
- y: srFormatNumber(peak[Y], locale)
33395
- });
33245
+ const srSinusoidRootPoint = strings.srSinusoidRootPoint(formattedRoot);
33246
+ const srSinusoidPeakPoint = peak[Y] === root[Y] ? strings.srSinusoidFlatPoint(formattedPeak) : peak[Y] > root[Y] ? strings.srSinusoidMaxPoint(formattedPeak) : strings.srSinusoidMinPoint(formattedPeak);
33396
33247
  const srSinusoidInteractiveElements = strings.srInteractiveElements({
33397
33248
  elements: strings.srSinusoidInteractiveElements({
33398
33249
  point1X: srFormatNumber(root[X], locale),
@@ -33414,16 +33265,17 @@ const {
33414
33265
  calculateAngleInDegrees,
33415
33266
  convertDegreesToRadians
33416
33267
  } = kmath.angles;
33417
- const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
33268
+ const protractorImage = "https://cdn.kastatic.org/images/perseus/protractor.svg";
33418
33269
 
33419
33270
  // The vector from the center of the protractor to the top left corner of the
33420
33271
  // protractor image, in pixels. Used for positioning.
33421
- const centerToTopLeft = [-180, -170];
33272
+ const centerToTopLeft = [-195, -190];
33422
33273
 
33423
33274
  // The vector from the center of the protractor to the center of the rotation
33424
33275
  // handle.
33425
- const centerToRotationHandle = [-176, -15];
33276
+ const centerToRotationHandle = [-201, -15];
33426
33277
  function Protractor() {
33278
+ const staticUrl = getDependencies().staticUrl;
33427
33279
  const {
33428
33280
  range,
33429
33281
  snapStep
@@ -33435,7 +33287,9 @@ function Protractor() {
33435
33287
  const [center, setCenter] = React.useState(initialCenter);
33436
33288
  const [rotationHandleOffset, setRotationHandleOffset] = React.useState(centerToRotationHandle);
33437
33289
  const draggableRef = React.useRef(null);
33438
- useDraggable({
33290
+ const {
33291
+ dragging
33292
+ } = useDraggable({
33439
33293
  gestureTarget: draggableRef,
33440
33294
  onMove: setCenter,
33441
33295
  point: center,
@@ -33459,10 +33313,11 @@ function Protractor() {
33459
33313
  ref: draggableRef,
33460
33314
  transform: `translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,
33461
33315
  style: {
33462
- transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`
33316
+ transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`,
33317
+ cursor: dragging ? "grabbing" : "grab"
33463
33318
  }
33464
33319
  }, /*#__PURE__*/React__namespace.createElement("image", {
33465
- href: protractorImage
33320
+ href: staticUrl(protractorImage)
33466
33321
  }), /*#__PURE__*/React__namespace.createElement("g", {
33467
33322
  transform: `translate(5, ${-centerToTopLeft[1]})`,
33468
33323
  ref: rotationHandleRef
@@ -35394,8 +35249,8 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
35394
35249
 
35395
35250
  // If the graph is static, it always displays the correct answer. This is
35396
35251
  // standard behavior for Perseus widgets (e.g. compare the Radio widget).
35397
- if (props.static) {
35398
- return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
35252
+ if (props.static && props.correct) {
35253
+ return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends__default["default"]({}, props, {
35399
35254
  state: initializeGraphState({
35400
35255
  ...props,
35401
35256
  graph: props.correct
@@ -35403,7 +35258,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
35403
35258
  dispatch: dispatch
35404
35259
  }));
35405
35260
  }
35406
- return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
35261
+ return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends__default["default"]({}, props, {
35407
35262
  state: state,
35408
35263
  dispatch: dispatch
35409
35264
  }));
@@ -35414,7 +35269,7 @@ const {
35414
35269
  } = kmath.angles;
35415
35270
  const {
35416
35271
  getLineEquation,
35417
- getLineIntersection,
35272
+ getLineIntersectionString,
35418
35273
  magnitude,
35419
35274
  vector
35420
35275
  } = kmath.geometry;
@@ -35496,7 +35351,7 @@ class InteractiveGraph extends React__namespace.Component {
35496
35351
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
35497
35352
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
35498
35353
  const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
35499
- return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends({}, this.props, {
35354
+ return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends__default["default"]({}, this.props, {
35500
35355
  ref: this.mafsRef,
35501
35356
  gridStep: gridStep,
35502
35357
  snapStep: snapStep,
@@ -35779,7 +35634,7 @@ class InteractiveGraph extends React__namespace.Component {
35779
35634
  }
35780
35635
  static getLinearSystemEquationString(props) {
35781
35636
  const coords = InteractiveGraph.getLinearSystemCoords(props.graph, props);
35782
- return "\n" + getLineEquation(coords[0][0], coords[0][1]) + "\n" + getLineEquation(coords[1][0], coords[1][1]) + "\n" + getLineIntersection(coords[0], coords[1]);
35637
+ return "\n" + getLineEquation(coords[0][0], coords[0][1]) + "\n" + getLineEquation(coords[1][0], coords[1][1]) + "\n" + getLineIntersectionString(coords[0], coords[1]);
35783
35638
  }
35784
35639
  static getPointEquationString(props) {
35785
35640
  if (props.graph.type !== "point") {
@@ -35931,10 +35786,10 @@ const AnswerChoices = props => {
35931
35786
  onToggle,
35932
35787
  disabled
35933
35788
  };
35934
- return props.multipleSelect ? /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.MultiSelect, _extends({}, args, {
35789
+ return props.multipleSelect ? /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.MultiSelect, _extends__default["default"]({}, args, {
35935
35790
  selectedValues: selectedValues,
35936
35791
  onChange: selected => onAnswerChange(selected)
35937
- })) : /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, _extends({}, args, {
35792
+ })) : /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, _extends__default["default"]({}, args, {
35938
35793
  selectedValue: selectedValues[0],
35939
35794
  onChange: selected => onAnswerChange([selected]),
35940
35795
  placeholder: props.choices[0].content // not visible, but required
@@ -36220,7 +36075,6 @@ const styles$a = aphrodite.StyleSheet.create({
36220
36075
  markerCorrect: {
36221
36076
  background: "#00880b" // WB green darkened by 18%
36222
36077
  },
36223
-
36224
36078
  markerIncorrect: {
36225
36079
  background: wonderBlocksTokens.color.offBlack64
36226
36080
  }
@@ -36617,7 +36471,6 @@ class LabelImage extends React__namespace.Component {
36617
36471
  const adjustPillDistance = {
36618
36472
  [`margin${markerPosition.charAt(0).toUpperCase() + markerPosition.slice(1)}`]: 10 // move pill further from marker
36619
36473
  };
36620
-
36621
36474
  const answerChoicesActive = index === activeMarkerIndex;
36622
36475
  const showAnswerChoice = marker.selected && !answerChoicesActive && !this.state.hideAnswers;
36623
36476
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
@@ -36666,7 +36519,7 @@ class LabelImage extends React__namespace.Component {
36666
36519
  focused,
36667
36520
  pressed
36668
36521
  } = _ref2;
36669
- return /*#__PURE__*/React__namespace.createElement(Marker, _extends({}, marker, {
36522
+ return /*#__PURE__*/React__namespace.createElement(Marker, _extends__default["default"]({}, marker, {
36670
36523
  showCorrectness: showCorrectness,
36671
36524
  showSelected: opened,
36672
36525
  showPulsate: !markersInteracted,
@@ -36811,7 +36664,7 @@ const styles$9 = aphrodite.StyleSheet.create({
36811
36664
  });
36812
36665
  const LabelImageWithDependencies = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
36813
36666
  const deps = useDependencies();
36814
- return /*#__PURE__*/React__namespace.createElement(LabelImage, _extends({
36667
+ return /*#__PURE__*/React__namespace.createElement(LabelImage, _extends__default["default"]({
36815
36668
  ref: ref,
36816
36669
  analytics: deps.analytics
36817
36670
  }, props));
@@ -37073,7 +36926,6 @@ class Draggable extends React__namespace.Component {
37073
36926
  if (this.props.width) {
37074
36927
  style.width = this.props.width + 1; // Fix for non-integer widths
37075
36928
  }
37076
-
37077
36929
  if (this.props.height) {
37078
36930
  style.height = this.props.height;
37079
36931
  }
@@ -37417,8 +37269,7 @@ class Sortable extends React__namespace.Component {
37417
37269
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
37418
37270
  margin = "0 0 " + this.props.margin + "px 0"; // bottom
37419
37271
  }
37420
-
37421
- cards.push( /*#__PURE__*/React__namespace.createElement(Draggable, {
37272
+ cards.push(/*#__PURE__*/React__namespace.createElement(Draggable, {
37422
37273
  content: item.option,
37423
37274
  key: item.key,
37424
37275
  state: item.state
@@ -37471,7 +37322,7 @@ class Sortable extends React__namespace.Component {
37471
37322
  this, item.key)
37472
37323
  }));
37473
37324
  if (item.state === ItemState.DRAGGING || item.state === ItemState.ANIMATING) {
37474
- cards.push( /*#__PURE__*/React__namespace.createElement(Placeholder, {
37325
+ cards.push(/*#__PURE__*/React__namespace.createElement(Placeholder, {
37475
37326
  key: "placeholder_" + item.key,
37476
37327
  ref: "placeholder_" + item.key,
37477
37328
  width: item.width,
@@ -37551,10 +37402,6 @@ const getPromptJSON$9 = (renderProps, userInput) => {
37551
37402
  };
37552
37403
  };
37553
37404
 
37554
- const {
37555
- shuffle: shuffle$1,
37556
- seededRNG
37557
- } = Util;
37558
37405
  const HACKY_CSS_CLASSNAME = "perseus-widget-matcher";
37559
37406
  class Matcher extends React__namespace.Component {
37560
37407
  static contextType = PerseusI18nContext;
@@ -37656,16 +37503,16 @@ class Matcher extends React__namespace.Component {
37656
37503
  }, "1")));
37657
37504
  }
37658
37505
 
37659
- // Use the same random() function to shuffle both columns sequentially
37660
- const rng = seededRNG(this.props.problemNum);
37661
- let left;
37662
- if (!this.props.orderMatters) {
37663
- // If the order doesn't matter, don't shuffle the left column
37664
- left = this.props.left;
37665
- } else {
37666
- left = shuffle$1(this.props.left, rng, /* ensurePermuted */true);
37667
- }
37668
- const right = shuffle$1(this.props.right, rng, /* ensurePermuted */true);
37506
+ /* TODO(LEMS-2841):
37507
+ Once the getMatcherPublicWidgetOptions function gets connected to the
37508
+ widget, we'll need to update this to the line below to only shuffle
37509
+ on the server.
37510
+ const {left, right} = this.props;
37511
+ */
37512
+ const {
37513
+ left,
37514
+ right
37515
+ } = perseusCore.shuffleMatcher(this.props);
37669
37516
  const showLabels = ___default["default"].any(this.props.labels);
37670
37517
  const constraints = {
37671
37518
  height: ___default["default"].max([this.state.leftHeight, this.state.rightHeight])
@@ -38089,7 +37936,7 @@ class Matrix extends React__namespace.Component {
38089
37936
  boxSizing: "border-box",
38090
37937
  backgroundColor: outside ? "#f3f3f3" : "#fff"
38091
37938
  };
38092
- MatrixInput = /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput, _extends({}, inputProps, {
37939
+ MatrixInput = /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput, _extends__default["default"]({}, inputProps, {
38093
37940
  style: style,
38094
37941
  scrollable: true,
38095
37942
  keypadElement: this.props.keypadElement
@@ -39250,7 +39097,6 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
39250
39097
  if (!___default["default"].isFinite(props.tickStep) || props.tickStep <= 0) {
39251
39098
  return []; // this has screwed me for the last time!
39252
39099
  }
39253
-
39254
39100
  const results = [];
39255
39101
 
39256
39102
  // For convenience, extract some props into separate variables
@@ -39321,6 +39167,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
39321
39167
 
39322
39168
  // TODO: most widgets use some like Widget<Something, PerseusNumberLineWidgetOptions>
39323
39169
  // should this one?
39170
+
39324
39171
  class NumberLine extends React__namespace.Component {
39325
39172
  static contextType = PerseusI18nContext;
39326
39173
  static defaultProps = {
@@ -39465,7 +39312,7 @@ class NumberLine extends React__namespace.Component {
39465
39312
  setup: this._setupGraphie,
39466
39313
  setDrawingAreaAvailable: this.props.apiOptions.setDrawingAreaAvailable,
39467
39314
  isMobile: this.props.apiOptions.isMobile
39468
- }, /*#__PURE__*/React__namespace.createElement(TickMarks, _extends({}, ___default["default"].pick(props, ["range", "numDivisions", "labelTicks", "labelStyle", "labelRange", "tickStep"]), {
39315
+ }, /*#__PURE__*/React__namespace.createElement(TickMarks, _extends__default["default"]({}, ___default["default"].pick(props, ["range", "numDivisions", "labelTicks", "labelStyle", "labelRange", "tickStep"]), {
39469
39316
  isMobile: this.props.apiOptions.isMobile
39470
39317
  })), this._renderInequality(props), this._renderNumberLinePoint(props));
39471
39318
  };
@@ -39692,7 +39539,6 @@ const numberLineTransform = editorProps => {
39692
39539
  } else {
39693
39540
  numDivisions = undefined; // send to getDefaultProps()
39694
39541
  }
39695
-
39696
39542
  ___default["default"].extend(props, {
39697
39543
  numLinePosition: numLinePosition,
39698
39544
  numDivisions: numDivisions,
@@ -39717,7 +39563,6 @@ const staticTransform$1 = editorProps => {
39717
39563
  } else {
39718
39564
  numDivisions = undefined; // send to getDefaultProps()
39719
39565
  }
39720
-
39721
39566
  ___default["default"].extend(props, {
39722
39567
  numLinePosition: numLinePosition,
39723
39568
  numDivisions: numDivisions,
@@ -39912,7 +39757,7 @@ class Card extends React__namespace.Component {
39912
39757
  onTouchCancel: this.onMouseUp
39913
39758
  }, /*#__PURE__*/React__namespace.createElement("div", {
39914
39759
  className: className.join(" ")
39915
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, rendererProps, {
39760
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({}, rendererProps, {
39916
39761
  linterContext: this.props.linterContext,
39917
39762
  strings: this.context.strings
39918
39763
  }))))
@@ -40737,7 +40582,6 @@ class Plotter extends React__namespace.Component {
40737
40582
  const initialY = isMobile ? c.scaleY : 0;
40738
40583
  for (let y = initialY; y <= c.dimY; y += c.scaleY) {
40739
40584
  graphie.label([0, y], kmath.KhanMath.roundToApprox(y, 2), "left", /* isTeX */true /* for the \approx symbol */);
40740
-
40741
40585
  graphie.style({
40742
40586
  stroke: isMobile ? "#e9ebec" : "#000",
40743
40587
  strokeWidth: 1,
@@ -41431,9 +41275,6 @@ const getPromptJSON$1 = userInput => {
41431
41275
  };
41432
41276
  };
41433
41277
 
41434
- const {
41435
- shuffle
41436
- } = Util;
41437
41278
  class Sorter extends React__namespace.Component {
41438
41279
  _isMounted = false;
41439
41280
  static defaultProps = {
@@ -41490,7 +41331,11 @@ class Sorter extends React__namespace.Component {
41490
41331
  this.refs.sortable.moveOptionToIndex(option, index);
41491
41332
  };
41492
41333
  render() {
41493
- const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
41334
+ // TODO(LEMS-2841): Remove client-side shuffle once receiving public
41335
+ // options. The correct field will already be shuffled.
41336
+ // Probably easiest to replace "options" with "this.props.correct" when
41337
+ // setting up Sortable below. Or use the assignment to rename for clarity.
41338
+ const options = perseusCore.shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
41494
41339
  const {
41495
41340
  apiOptions
41496
41341
  } = this.props;
@@ -41698,7 +41543,7 @@ class Table extends React__namespace.Component {
41698
41543
  }, ___default["default"](columns).times(c => {
41699
41544
  return /*#__PURE__*/React__namespace.createElement("td", {
41700
41545
  key: c
41701
- }, /*#__PURE__*/React__namespace.createElement(InputComponent, _extends({
41546
+ }, /*#__PURE__*/React__namespace.createElement(InputComponent, _extends__default["default"]({
41702
41547
  ref: getRefForPath(getInputPath(r, c)),
41703
41548
  type: "text",
41704
41549
  value: this.props.answers[r][c],
@@ -41909,10 +41754,51 @@ const init = function () {
41909
41754
  replaceDeprecatedWidgets();
41910
41755
  };
41911
41756
 
41757
+ /**
41758
+ * Adds the given perseus library version information to the __perseus_debug__
41759
+ * object and ensures that the object is attached to `globalThis` (`window` in
41760
+ * browser environments).
41761
+ *
41762
+ * This allows each library to provide runtime version information to assist in
41763
+ * debugging in production environments.
41764
+ */
41765
+ const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
41766
+ // If the library version is the default value, then we don't want to
41767
+ // prefix it with a "v" to indicate that it is a version number.
41768
+ let prefix = "v";
41769
+ if (libraryVersion === "__lib_version__") {
41770
+ prefix = "";
41771
+ }
41772
+ const formattedVersion = `${prefix}${libraryVersion}`;
41773
+ if (typeof globalThis !== "undefined") {
41774
+ globalThis.__perseus_debug__ = globalThis.__perseus_debug__ ?? {};
41775
+ const existingVersionEntry = globalThis.__perseus_debug__[libraryName];
41776
+ if (existingVersionEntry) {
41777
+ // If we already have an entry and it doesn't match the registered
41778
+ // version, we morph the entry into an array and log a warning.
41779
+ if (existingVersionEntry !== formattedVersion) {
41780
+ // Existing entry might be an array already (oops, at least 2
41781
+ // versions of the library already loaded!).
41782
+ const allVersions = Array.isArray(existingVersionEntry) ? existingVersionEntry : [existingVersionEntry];
41783
+ allVersions.push(formattedVersion);
41784
+ globalThis.__perseus_debug__[libraryName] = allVersions;
41785
+
41786
+ // eslint-disable-next-line no-console
41787
+ console.warn(`Multiple versions of ${libraryName} loaded on this page: ${allVersions.sort().join(", ")}`);
41788
+ }
41789
+ } else {
41790
+ globalThis.__perseus_debug__[libraryName] = formattedVersion;
41791
+ }
41792
+ } else {
41793
+ // eslint-disable-next-line no-console
41794
+ console.warn(`globalThis not found found (${formattedVersion})`);
41795
+ }
41796
+ };
41797
+
41912
41798
  // This file is processed by a Rollup plugin (replace) to inject the production
41913
41799
  const libName = "@khanacademy/perseus";
41914
- const libVersion = "54.0.0";
41915
- perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
41800
+ const libVersion = "55.0.0";
41801
+ addLibraryVersionToPerseusDebug(libName, libVersion);
41916
41802
 
41917
41803
  const apiVersion = {
41918
41804
  major: 12,
@@ -42068,7 +41954,7 @@ class ArticleRenderer extends React__namespace.Component {
42068
41954
  return /*#__PURE__*/React__namespace.createElement("div", {
42069
41955
  key: sectionIndex,
42070
41956
  className: "clearfix"
42071
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, section, {
41957
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({}, section, {
42072
41958
  ref: elem => {
42073
41959
  if (elem) {
42074
41960
  this.sectionRenderers[sectionIndex] = elem;
@@ -42243,7 +42129,6 @@ class HintsRenderer extends React__namespace.Component {
42243
42129
  ReactDOM__default["default"].findDOMNode(this.refs["hintRenderer" + pos]).focus(); // eslint-disable-line react/no-string-refs
42244
42130
  }
42245
42131
  }
42246
-
42247
42132
  _hintsVisible = () => {
42248
42133
  if (this.props.hintsVisible == null || this.props.hintsVisible === -1) {
42249
42134
  return this.props.hints.length;
@@ -42732,7 +42617,7 @@ class ServerItemRenderer extends React__namespace.Component {
42732
42617
  };
42733
42618
  const questionRenderer = /*#__PURE__*/React__namespace.createElement(context$1.Provider, {
42734
42619
  value: contextValue
42735
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({
42620
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({
42736
42621
  keypadElement: this.props.keypadElement,
42737
42622
  problemNum: this.props.problemNum,
42738
42623
  onInteractWithWidget: this.handleInteractWithWidget,
@@ -42781,7 +42666,7 @@ const ref = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__
42781
42666
  let {
42782
42667
  onRendered
42783
42668
  } = _ref;
42784
- return /*#__PURE__*/React__namespace.createElement(ServerItemRenderer, _extends({}, props, {
42669
+ return /*#__PURE__*/React__namespace.createElement(ServerItemRenderer, _extends__default["default"]({}, props, {
42785
42670
  onRendered: onRendered,
42786
42671
  ref: ref
42787
42672
  }));
@@ -44212,24 +44097,24 @@ const ScoringUtil = {
44212
44097
  */
44213
44098
 
44214
44099
  Object.defineProperty(exports, 'isFailure', {
44215
- enumerable: true,
44216
- get: function () { return perseusCore.isFailure; }
44100
+ enumerable: true,
44101
+ get: function () { return perseusCore.isFailure; }
44217
44102
  });
44218
44103
  Object.defineProperty(exports, 'isSuccess', {
44219
- enumerable: true,
44220
- get: function () { return perseusCore.isSuccess; }
44104
+ enumerable: true,
44105
+ get: function () { return perseusCore.isSuccess; }
44221
44106
  });
44222
44107
  Object.defineProperty(exports, 'parseAndMigratePerseusArticle', {
44223
- enumerable: true,
44224
- get: function () { return perseusCore.parseAndMigratePerseusArticle; }
44108
+ enumerable: true,
44109
+ get: function () { return perseusCore.parseAndMigratePerseusArticle; }
44225
44110
  });
44226
44111
  Object.defineProperty(exports, 'parseAndMigratePerseusItem', {
44227
- enumerable: true,
44228
- get: function () { return perseusCore.parseAndMigratePerseusItem; }
44112
+ enumerable: true,
44113
+ get: function () { return perseusCore.parseAndMigratePerseusItem; }
44229
44114
  });
44230
44115
  Object.defineProperty(exports, 'parsePerseusItem', {
44231
- enumerable: true,
44232
- get: function () { return perseusCore.parsePerseusItem; }
44116
+ enumerable: true,
44117
+ get: function () { return perseusCore.parsePerseusItem; }
44233
44118
  });
44234
44119
  exports.ApiOptions = ApiOptions;
44235
44120
  exports.ArticleRenderer = ArticleRenderer;