@khanacademy/perseus 54.1.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 (37) 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 +901 -985
  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 +1035 -1086
  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/widget-ai-utils/numeric-input/prompt-utils.d.ts +1 -1
  22. package/dist/widgets/dropdown/dropdown.d.ts +5 -4
  23. package/dist/widgets/interactive-graph.d.ts +4 -2
  24. package/dist/widgets/interactive-graphs/graphs/circle.d.ts +7 -0
  25. package/dist/widgets/interactive-graphs/graphs/components/movable-line.d.ts +6 -0
  26. package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +8 -0
  27. package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +7 -0
  28. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +1 -1
  29. package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +6 -1
  30. package/dist/widgets/numeric-input/index.d.ts +1 -1
  31. package/dist/widgets/numeric-input/input-with-examples.d.ts +8 -0
  32. package/dist/widgets/numeric-input/numeric-input.class.d.ts +66 -0
  33. package/dist/widgets/numeric-input/numeric-input.d.ts +123 -77
  34. package/dist/widgets/numeric-input/utils.d.ts +25 -0
  35. package/package.json +120 -124
  36. package/dist/components/input-with-examples.d.ts +0 -51
  37. 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
@@ -2667,9 +2862,11 @@ const Util = {
2667
2862
  // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
2668
2863
  // getDependencies() internally... simplify usage to just using a "singleton"
2669
2864
  // which get's the injected logger.
2865
+
2670
2866
  /**
2671
2867
  * Provides logging infrastructure for Perseus
2672
2868
  */
2869
+
2673
2870
  const Log = {
2674
2871
  // Logs a message
2675
2872
  log: (message, extra) => {
@@ -3343,40 +3540,40 @@ const articleMaxWidthInPx = 688;
3343
3540
  const articleMaxWidthTableInPx = 512;
3344
3541
 
3345
3542
  var constants = /*#__PURE__*/Object.freeze({
3346
- __proto__: null,
3347
- baseFontFamily: baseFontFamily,
3348
- boldFontFamily: boldFontFamily,
3349
- kaGreen: kaGreen,
3350
- white: white,
3351
- gray97: gray97,
3352
- gray85: gray85,
3353
- gray76: gray76,
3354
- gray68: gray68,
3355
- gray17: gray17,
3356
- pureSmMin: pureSmMin$1,
3357
- pureMdMin: pureMdMin$1,
3358
- pureLgMin: pureLgMin$1,
3359
- pureXlMin: pureXlMin$1,
3360
- pureXsMax: pureXsMax$1,
3361
- pureSmMax: pureSmMax$1,
3362
- pureMdMax: pureMdMax$1,
3363
- pureLgMax: pureLgMax$1,
3364
- tableBackgroundAccent: tableBackgroundAccent,
3365
- phoneMargin: phoneMargin,
3366
- negativePhoneMargin: negativePhoneMargin,
3367
- hintBorderWidth: hintBorderWidth,
3368
- baseUnitPx: baseUnitPx,
3369
- interactiveSizes: interactiveSizes$1,
3370
- circleSize: circleSize$1,
3371
- radioMarginWidth: radioMarginWidth$1,
3372
- warningColor: warningColor,
3373
- warningColorHover: warningColorHover,
3374
- warningColorActive: warningColorActive,
3375
- publishBlockingErrorColor: publishBlockingErrorColor,
3376
- radioBorderColor: radioBorderColor$1,
3377
- checkedColor: checkedColor$1,
3378
- articleMaxWidthInPx: articleMaxWidthInPx,
3379
- 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
3380
3577
  });
3381
3578
 
3382
3579
  /**
@@ -3791,7 +3988,7 @@ var Raphael$1 = (function () {
3791
3988
  appendChild = "appendChild",
3792
3989
  apply = "apply",
3793
3990
  concat = "concat",
3794
- supportsTouch = ("createTouch" in doc),
3991
+ supportsTouch = "createTouch" in doc,
3795
3992
  E = "",
3796
3993
  S = " ",
3797
3994
  Str = String,
@@ -5396,10 +5593,10 @@ var Raphael$1 = (function () {
5396
5593
  });
5397
5594
  break;
5398
5595
  case "gradient":
5399
- (({
5596
+ ({
5400
5597
  circle: 1,
5401
5598
  ellipse: 1
5402
- })[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);
5403
5600
  break;
5404
5601
  case "opacity":
5405
5602
  if (attrs.gradient && !attrs[has]("stroke-opacity")) {
@@ -9232,8 +9429,10 @@ const labelDirections = {
9232
9429
  // is equivalent to [[-5, 5], [-10, 10]].
9233
9430
  // - A single magnitude to be used for both x and y. E.g. 10 is equivalent to
9234
9431
  // [[-10, 10], [-10, 10]]
9432
+
9235
9433
  // Overload normalizeRange to specify that it only returns undefined if its
9236
9434
  // argument is undefined.
9435
+
9237
9436
  // normalizeRange converts a RangeSpecifier into a pair of Intervals that give
9238
9437
  // the x and y ranges, respectively
9239
9438
  function normalizeRange(range) {
@@ -9425,7 +9624,7 @@ const GraphUtils = {
9425
9624
  clickBlockList = [],
9426
9625
  blockMouseTriggers = false,
9427
9626
  blockTouchTriggers = false,
9428
- eventCaptureSupported = ("addEventListener" in document),
9627
+ eventCaptureSupported = "addEventListener" in document,
9429
9628
  $document = $(document),
9430
9629
  nextTouchID = 1,
9431
9630
  lastTouchID = 0;
@@ -10120,7 +10319,6 @@ function scaledPolarRad(radius, radians) {
10120
10319
  return [radius * Math.cos(radians), radius * Math.sin(radians) * -1 // SVG flips y axis
10121
10320
  ];
10122
10321
  }
10123
-
10124
10322
  function scaledPolarDeg(radius, degrees) {
10125
10323
  const radians = degrees * Math.PI / 180;
10126
10324
  return scaledPolarRad(radius, radians);
@@ -11938,7 +12136,6 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11938
12136
  if (hideArrow) {
11939
12137
  return; // Don't draw anything!
11940
12138
  }
11941
-
11942
12139
  const handleVec = kmath.vector.subtract(handleCoord, rotatePoint.coord);
11943
12140
  const handlePolar = kmath.vector.polarDegFromCart(handleVec);
11944
12141
  const angle = handlePolar[1];
@@ -12902,7 +13099,6 @@ ___default["default"].extend(MovableAngle.prototype, {
12902
13099
  // clockwise to the first point
12903
13100
  coords[1] // the vertex parameter is last
12904
13101
  );
12905
-
12906
13102
  const clockwiseAngle = rawAngle + 360 % 360;
12907
13103
  return clockwiseAngle;
12908
13104
  },
@@ -14262,7 +14458,6 @@ class MovablePoint$5 {
14262
14458
  // We only update props here, because we want things on state to
14263
14459
  // be persistent, and updated appropriately in modify()
14264
14460
  ),
14265
-
14266
14461
  ...DEFAULT_PROPS$1
14267
14462
  };
14268
14463
  }
@@ -14314,11 +14509,11 @@ class MovablePoint$5 {
14314
14509
  const showTrashTooltip = () => {
14315
14510
  this._showTooltip(container => {
14316
14511
  // eslint-disable-next-line import/no-deprecated
14317
- render( /*#__PURE__*/React__namespace.createElement("span", {
14512
+ render(/*#__PURE__*/React__namespace.createElement("span", {
14318
14513
  style: {
14319
14514
  fontSize: "2em"
14320
14515
  }
14321
- }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconTrash, {
14516
+ }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconTrash, {
14322
14517
  style: {
14323
14518
  position: "static",
14324
14519
  color: KhanColors.INTERACTIVE,
@@ -15117,7 +15312,6 @@ const DEFAULT_PROPS = {
15117
15312
  // likewise
15118
15313
  labelStyle: null // likewise
15119
15314
  };
15120
-
15121
15315
  const DEFAULT_STATE = {
15122
15316
  added: false,
15123
15317
  hasMoved: false,
@@ -16019,7 +16213,7 @@ class ImageLoader extends React__namespace.Component {
16019
16213
  return (
16020
16214
  /*#__PURE__*/
16021
16215
  // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
16022
- React__namespace.createElement("img", _extends({
16216
+ React__namespace.createElement("img", _extends__default["default"]({
16023
16217
  // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- TODO(LEMS-2871): Address a11y error
16024
16218
  tabIndex: 0,
16025
16219
  src: staticUrl(src),
@@ -17793,7 +17987,7 @@ class TranslationLinter {
17793
17987
  * Preprocess TeX code to convert things that MathJax doesn't know how to handle
17794
17988
  * to things is does.
17795
17989
  */
17796
- var preprocessTex = (texCode => texCode
17990
+ var preprocessTex = texCode => texCode
17797
17991
  // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
17798
17992
  // support (yet) with \begin{aligned}...\end{aligned} which renders
17799
17993
  // the same is supported by KaTeX. It does the same for align*.
@@ -17803,7 +17997,7 @@ var preprocessTex = (texCode => texCode
17803
17997
  // "aligned").
17804
17998
  .replace(/\{align[*]?\}/g, "{aligned}")
17805
17999
  // Replace non-breaking spaces with regular spaces.
17806
- .replace(/[\u00a0]/g, " "));
18000
+ .replace(/[\u00a0]/g, " ");
17807
18001
 
17808
18002
  const {
17809
18003
  interactiveSizes
@@ -18062,29 +18256,29 @@ const isLintable = type => {
18062
18256
  };
18063
18257
 
18064
18258
  var widgets$1 = /*#__PURE__*/Object.freeze({
18065
- __proto__: null,
18066
- registerWidget: registerWidget,
18067
- registerWidgets: registerWidgets,
18068
- replaceWidget: replaceWidget,
18069
- replaceDeprecatedWidgets: replaceDeprecatedWidgets,
18070
- registerEditors: registerEditors,
18071
- replaceEditor: replaceEditor,
18072
- replaceDeprecatedEditors: replaceDeprecatedEditors,
18073
- getWidget: getWidget,
18074
- getWidgetExport: getWidgetExport,
18075
- getEditor: getEditor,
18076
- getTransform: getTransform,
18077
- getVersion: getVersion,
18078
- getVersionVector: getVersionVector,
18079
- getPublicWidgets: getPublicWidgets,
18080
- isAccessible: isAccessible,
18081
- getAllWidgetTypes: getAllWidgetTypes,
18082
- getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
18083
- traverseChildWidgets: traverseChildWidgets$3,
18084
- supportsStaticMode: supportsStaticMode,
18085
- getStaticTransform: getStaticTransform,
18086
- getTracking: getTracking,
18087
- 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
18088
18282
  });
18089
18283
 
18090
18284
  class WidgetContainer extends React__namespace.Component {
@@ -18119,7 +18313,6 @@ class WidgetContainer extends React__namespace.Component {
18119
18313
  /* eslint-enable react/no-did-mount-set-state */
18120
18314
  }
18121
18315
  }
18122
-
18123
18316
  UNSAFE_componentWillReceiveProps(nextProps) {
18124
18317
  if (this.props.type !== nextProps.type) {
18125
18318
  throw new Error("WidgetContainer can't change widget type; set a different " + "key instead to recreate the container.");
@@ -18242,7 +18435,7 @@ class WidgetContainer extends React__namespace.Component {
18242
18435
  }
18243
18436
  });
18244
18437
  }
18245
- }, /*#__PURE__*/React__namespace.createElement(WidgetType, _extends({}, this.state.widgetProps, {
18438
+ }, /*#__PURE__*/React__namespace.createElement(WidgetType, _extends__default["default"]({}, this.state.widgetProps, {
18246
18439
  linterContext: linterContext,
18247
18440
  containerSizeClass: this.state.sizeClass,
18248
18441
  ref: this.widgetRef
@@ -19111,7 +19304,7 @@ class Renderer extends React__namespace.Component {
19111
19304
  let {
19112
19305
  setAssetStatus
19113
19306
  } = _ref4;
19114
- return /*#__PURE__*/React__namespace.createElement(SvgImage, _extends({
19307
+ return /*#__PURE__*/React__namespace.createElement(SvgImage, _extends__default["default"]({
19115
19308
  setAssetStatus: setAssetStatus
19116
19309
  // @ts-expect-error - TS2322 - Type 'string | null | undefined' is not assignable to type 'string | undefined'.
19117
19310
  ,
@@ -19635,429 +19828,84 @@ const styles$A = {
19635
19828
  }
19636
19829
  };
19637
19830
 
19638
- /* eslint-disable @khanacademy/ts-no-error-suppressions */
19639
- let lastId = 0;
19640
- function uniqueIdForInput() {
19641
- let prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "input-";
19642
- lastId++;
19643
- return `${prefix}${lastId}`;
19644
- }
19645
- class TextInput extends React__namespace.Component {
19646
- inputRef = /*#__PURE__*/React__namespace.createRef();
19647
- static defaultProps = {
19648
- value: "",
19649
- disabled: false
19650
- };
19651
- id;
19652
- constructor(props) {
19653
- super(props);
19654
- if (props.id) {
19655
- this.id = props.id;
19656
- } else {
19657
- this.id = uniqueIdForInput();
19658
- }
19659
- }
19660
- _getInput = () => {
19661
- if (!this.inputRef.current) {
19662
- throw new perseusCore.PerseusError("Input ref accessed before set", perseusCore.Errors.Internal);
19663
- }
19664
- return this.inputRef.current;
19665
- };
19666
- focus = () => {
19667
- this._getInput().focus();
19668
- };
19669
- blur = () => {
19670
- this._getInput().blur();
19671
- };
19672
- getValue = () => {
19673
- return this.inputRef.current?.value;
19674
- };
19675
- getStringValue = () => {
19676
- return this.inputRef.current?.value.toString();
19677
- };
19678
- setSelectionRange = (selectionStart, selectionEnd) => {
19679
- this._getInput().setSelectionRange(selectionStart, selectionEnd);
19680
- };
19681
- getSelectionStart = () => {
19682
- return this._getInput().selectionStart;
19683
- };
19684
- getSelectionEnd = () => {
19685
- return this._getInput().selectionEnd;
19686
- };
19687
- render() {
19688
- const {
19689
- labelText,
19690
- value,
19691
- onFocus,
19692
- onBlur,
19693
- disabled,
19694
- placeholder,
19695
- onKeyDown,
19696
- style
19697
- } = this.props;
19698
- const formattedValue = value === null ? "" : value.toString();
19699
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
19700
- ref: this.inputRef,
19701
- style: style,
19702
- disabled: disabled,
19703
- id: this.id,
19704
- value: formattedValue,
19705
- type: "text",
19706
- "aria-label": labelText,
19707
- "aria-describedby": this.props["aria-describedby"],
19708
- onChange: value => this.props.onChange(value),
19709
- placeholder: placeholder,
19710
- onFocus: onFocus,
19711
- onBlur: onBlur,
19712
- onKeyDown: onKeyDown
19713
- // @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>'.
19714
- ,
19715
- autoCorrect: "off",
19716
- autoCapitalize: "off",
19717
- autoComplete: "off"
19718
- });
19719
- }
19720
- }
19721
-
19722
- /* eslint-disable react/no-unsafe */
19723
- const zIndex = 10;
19724
- let HorizontalDirection = /*#__PURE__*/function (HorizontalDirection) {
19725
- HorizontalDirection["Left"] = "left";
19726
- HorizontalDirection["Right"] = "right";
19727
- return HorizontalDirection;
19728
- }({});
19729
- let VerticalDirection = /*#__PURE__*/function (VerticalDirection) {
19730
- VerticalDirection["Top"] = "top";
19731
- VerticalDirection["Bottom"] = "bottom";
19732
- return VerticalDirection;
19733
- }({});
19734
- const Triangle = props => {
19735
- let borderLeft;
19736
- let borderRight;
19737
- let borderTop;
19738
- let borderBottom;
19739
- const hBorder = `${props.width}px solid transparent`;
19740
- if (props.horizontalDirection === "right") {
19741
- borderLeft = hBorder;
19742
- } else {
19743
- borderRight = hBorder;
19744
- }
19745
- const vBorder = `${props.height}px solid ${props.color}`;
19746
- if (props.verticalDirection === "top") {
19747
- borderTop = vBorder;
19748
- } else {
19749
- borderBottom = vBorder;
19750
- }
19751
- return /*#__PURE__*/React__namespace.createElement("div", {
19752
- style: {
19753
- display: "block",
19754
- height: 0,
19755
- width: 0,
19756
- position: "absolute",
19757
- left: props.left,
19758
- top: props.top,
19759
- borderLeft: borderLeft,
19760
- borderRight: borderRight,
19761
- borderTop: borderTop,
19762
- borderBottom: borderBottom
19763
- }
19764
- });
19765
- };
19766
- const TooltipArrow = _ref => {
19767
- let {
19768
- position = "relative",
19769
- visibility = "visible",
19770
- left = 100,
19771
- top = 0,
19772
- ...props
19773
- } = _ref;
19774
- // TODO(aria): Think about adding a box-shadow to the triangle here
19775
- // 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.
19776
19837
 
19777
- //const isRight = (this.props.horizontalDirection === "right");
19778
- const isTop = props.verticalDirection === "top";
19779
- const frontTopOffset = isTop ? 0 : 1;
19780
- const borderTopOffset = isTop ? 0 : -1;
19781
- return /*#__PURE__*/React__namespace.createElement("div", {
19782
- style: {
19783
- display: "block",
19784
- position: position,
19785
- visibility: visibility,
19786
- left: left,
19787
- top: top,
19788
- width: props.width + 2,
19789
- height: props.height + 1,
19790
- marginTop: -1,
19791
- marginBottom: -2,
19792
- zIndex: zIndex
19793
- }
19794
- }, /*#__PURE__*/React__namespace.createElement(Triangle, {
19795
- horizontalDirection: props.horizontalDirection,
19796
- verticalDirection: props.verticalDirection,
19797
- color: props.border,
19798
- left: 0,
19799
- top: borderTopOffset,
19800
- width: props.width + 2 // one extra for the diagonal
19801
- ,
19802
- height: props.height + 2
19803
- }), /*#__PURE__*/React__namespace.createElement(Triangle, {
19804
- horizontalDirection: props.horizontalDirection,
19805
- verticalDirection: props.verticalDirection,
19806
- color: props.color,
19807
- left: 1,
19808
- top: frontTopOffset,
19809
- width: props.width,
19810
- height: props.height
19811
- }));
19812
- };
19813
- const VERTICAL_CORNERS = {
19814
- top: {
19815
- top: "-100%"
19816
- },
19817
- bottom: {
19818
- top: 0
19819
- }
19820
- };
19821
- const HORIZONTAL_CORNERS = {
19822
- left: {
19823
- targetLeft: 0
19824
- },
19825
- right: {
19826
- targetLeft: "100%"
19827
- }
19828
- };
19829
- const HORIZONTAL_ALIGNMNENTS = {
19830
- left: {
19831
- tooltipLeft: 0,
19832
- arrowLeft: arrowSize => 0
19833
- },
19834
- right: {
19835
- tooltipLeft: "-100%",
19836
- arrowLeft: arrowSize => -arrowSize - 2
19837
- }
19838
- };
19839
19838
  /**
19840
- * DEPRECATED! Use Wonder Blocks tooltip instead.
19841
- *
19842
- * A generic tooltip library for React.js
19843
- *
19844
- * ```
19845
- * import Tooltip from "./tooltip";
19846
- * <Tooltip
19847
- * className="class-for-tooltip-contents"
19848
- * horizontalPosition={HoriziontalDirection.Left}
19849
- * horizontalAlign={HoriziontalDirection.Left}
19850
- * verticalPosition={VerticalDirection.Top}
19851
- * arrowSize={10} // arrow size in pixels
19852
- * borderColor="#ccc" // color of the border for the tooltip
19853
- * show={true} // whether the tooltip should currently be visible
19854
- * targetContainerStyle={targetContainerStyle}
19855
- * >
19856
- * <TargetElementOfTheTooltip />
19857
- * <TooltipContents1 />
19858
- * <TooltipContents2 />
19859
- * </Tooltip>
19860
- * ```
19861
- *
19862
- * To show/hide the tooltip, the parent component should call the
19863
- * `.show()` and `.hide()` methods of the tooltip when appropriate.
19864
- * (These are usually set up as handlers of events on the target element.)
19865
- *
19866
- * Notes:
19867
- * `className` should not specify a border; that is handled by `borderColor`
19868
- * so that the arrow and tooltip match
19869
- *
19870
- * ```
19871
- * __,,--``\\
19872
- * _,,-''`` \\ ,
19873
- * '----------_.------'-.___|\__
19874
- * _.--''`` `)__ )__ @\__
19875
- * ( .. ''---/___,,E/__,E'------`
19876
- * `-''`''
19877
- * Here be dragons.
19878
- * ```
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.
19879
19843
  */
19880
- class Tooltip extends React__namespace.Component {
19881
- tooltipContainerRef = /*#__PURE__*/React__namespace.createRef();
19882
- static defaultProps = {
19883
- className: "",
19884
- arrowSize: 10,
19885
- borderColor: "#ccc",
19886
- verticalPosition: VerticalDirection.Bottom,
19887
- horizontalPosition: HorizontalDirection.Left,
19888
- horizontalAlign: HorizontalDirection.Left,
19889
- targetContainerStyle: {}
19890
- };
19891
- state = {
19892
- height: null // used for offsetting "top" positioned tooltips
19893
- };
19894
-
19895
- componentDidMount() {
19896
- this._updateHeight();
19897
- }
19898
- UNSAFE_componentWillReceiveProps() {
19899
- // If the contents have changed, reset our measure of the height
19900
- this.setState({
19901
- height: null
19902
- });
19903
- }
19904
- componentDidUpdate() {
19905
- this._updateHeight();
19906
- }
19907
- _renderToolTipDiv(isTooltipAbove) {
19908
- const settings = Object.assign({}, HORIZONTAL_CORNERS[this.props.horizontalPosition], HORIZONTAL_ALIGNMNENTS[this.props.horizontalAlign], VERTICAL_CORNERS[this.props.verticalPosition]);
19909
- let arrowAbove;
19910
- let arrowBelow;
19911
- if (isTooltipAbove) {
19912
- // We put an absolutely positioned arrow in the correct place
19913
- arrowAbove = /*#__PURE__*/React__namespace.createElement(TooltipArrow, {
19914
- verticalDirection: VerticalDirection.Top,
19915
- horizontalDirection: this.props.horizontalAlign,
19916
- position: "absolute",
19917
- color: "white",
19918
- border: this.props.borderColor,
19919
- left: settings.arrowLeft(this.props.arrowSize),
19920
- top: -this.props.arrowSize + 2,
19921
- width: this.props.arrowSize,
19922
- height: this.props.arrowSize,
19923
- zIndex: zIndex
19924
- });
19925
-
19926
- // And we use a visibility: hidden arrow below to shift up the
19927
- // content by the correct amount
19928
- arrowBelow = /*#__PURE__*/React__namespace.createElement(TooltipArrow, {
19929
- verticalDirection: VerticalDirection.Top,
19930
- horizontalDirection: this.props.horizontalAlign,
19931
- visibility: "hidden",
19932
- color: "white",
19933
- border: this.props.borderColor,
19934
- left: settings.arrowLeft(this.props.arrowSize),
19935
- top: -1,
19936
- width: this.props.arrowSize,
19937
- height: this.props.arrowSize,
19938
- zIndex: zIndex
19939
- });
19940
- } else {
19941
- arrowAbove = /*#__PURE__*/React__namespace.createElement(TooltipArrow, {
19942
- verticalDirection: VerticalDirection.Bottom,
19943
- horizontalDirection: this.props.horizontalAlign,
19944
- color: "white",
19945
- border: this.props.borderColor,
19946
- left: settings.arrowLeft(this.props.arrowSize),
19947
- top: -1,
19948
- width: this.props.arrowSize,
19949
- height: this.props.arrowSize,
19950
- zIndex: zIndex
19951
- });
19952
- arrowBelow = null;
19953
- }
19954
-
19955
- /* A positioned div below the input to be the parent for our
19956
- tooltip */
19957
- return /*#__PURE__*/React__namespace.createElement("div", {
19958
- style: {
19959
- position: "relative",
19960
- height: 0,
19961
- 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();
19962
19869
  }
19963
- }, /*#__PURE__*/React__namespace.createElement("div", {
19964
- ref: this.tooltipContainerRef,
19965
- className: "tooltipContainer",
19966
- role: "tooltip",
19967
- style: {
19968
- position: "absolute",
19969
- // height must start out undefined, not null, so that
19970
- // we can measure the actual height with jquery.
19971
- // This is used to position the tooltip with top: -100%
19972
- // when in verticalPosition: "top" mode
19973
- height: this.state.height || undefined,
19974
- left: settings.targetLeft
19975
- }
19976
- }, arrowAbove, /*#__PURE__*/React__namespace.createElement("div", {
19977
- className: this.props.className
19978
- // eslint-disable-next-line react/no-string-refs
19979
- ,
19980
- ref: "tooltipContent",
19981
- style: {
19982
- position: "relative",
19983
- top: settings["top"],
19984
- left: settings.tooltipLeft,
19985
- border: "1px solid " + this.props.borderColor,
19986
- boxShadow: "0 1px 3px " + this.props.borderColor,
19987
- zIndex: zIndex - 1
19988
- }
19989
- }, this.props.children.slice(1)), arrowBelow));
19990
- }
19991
- _updateHeight() {
19992
- const tooltipContainer = this.tooltipContainerRef.current;
19993
- const height = tooltipContainer?.offsetHeight || 0;
19994
- if (height !== this.state.height) {
19995
- this.setState({
19996
- height
19997
- });
19998
19870
  }
19999
- }
20000
- render() {
20001
- const isTooltipAbove = this.props.verticalPosition === "top";
20002
-
20003
- /* We wrap the entire output in a span so that it displays inline */
20004
- return /*#__PURE__*/React__namespace.createElement("span", null, isTooltipAbove && this._renderToolTipDiv(isTooltipAbove), /*#__PURE__*/React__namespace.createElement("div", {
20005
- style: this.props.targetContainerStyle
20006
- }, this.props.children[0]), !isTooltipAbove && this._renderToolTipDiv());
20007
- }
20008
- }
20009
-
20010
- const {
20011
- captureScratchpadTouchStart: captureScratchpadTouchStart$2
20012
- } = Util;
20013
- class InputWithExamples extends React__namespace.Component {
20014
- static contextType = PerseusI18nContext;
20015
- static defaultProps = {
20016
- shouldShowExamples: true,
20017
- onFocus: function () {},
20018
- onBlur: function () {},
20019
- disabled: false,
20020
- linterContext: PerseusLinter__namespace.linterContextDefault,
20021
- className: ""
20022
- };
20023
- state = {
20024
- focused: false,
20025
- showExamples: false
20026
- };
20027
- _getUniqueId = () => {
20028
- return `input-with-examples-${btoa(this.props.id).replace(/=/g, "")}`;
20029
- };
20030
- _getInputClassName = () => {
20031
- // Otherwise, we need to add these INPUT and FOCUSED tags here.
20032
- let className = ClassNames.INPUT;
20033
- if (this.state.focused) {
20034
- className += " " + ClassNames.FOCUSED;
19871
+ }));
19872
+ const getInputClassName = () => {
19873
+ let inputClassName = ClassNames.INPUT;
19874
+ if (inputFocused) {
19875
+ inputClassName += " " + ClassNames.FOCUSED;
20035
19876
  }
20036
- if (this.props.className) {
20037
- className += " " + this.props.className;
19877
+ if (className) {
19878
+ inputClassName += " " + className;
20038
19879
  }
20039
- return className;
19880
+ return inputClassName;
19881
+ };
19882
+ const handleFocus = () => {
19883
+ onFocus();
19884
+ setInputFocused(true);
19885
+ };
19886
+ const handleBlur = () => {
19887
+ onBlur();
19888
+ setInputFocused(false);
20040
19889
  };
20041
- _renderInput = () => {
20042
- const id = this._getUniqueId();
20043
- const ariaId = `aria-for-${id}`;
20044
- // Generate text from a known set of format options that will read well in a screen reader
20045
- const examplesAria = this.props.examples.length === 7 ? "" : `${this.props.examples[0]}
20046
- ${this.props.examples.slice(1).join(", or\n")}`
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")}`
20047
19894
  // @ts-expect-error TS2550: Property replaceAll does not exist on type string.
20048
- .replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ");
19895
+ .replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ") : "";
20049
19896
  const inputProps = {
20050
19897
  id: id,
20051
- "aria-describedby": ariaId,
20052
- ref: "input",
20053
- className: this._getInputClassName(),
20054
- labelText: this.props.labelText,
20055
- value: this.props.value,
20056
- onFocus: this._handleFocus,
20057
- onBlur: this._handleBlur,
20058
- disabled: this.props.disabled,
20059
- style: this.props.style,
20060
- 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,
20061
19909
  onTouchStart: captureScratchpadTouchStart$2,
20062
19910
  autoCapitalize: "off",
20063
19911
  autoComplete: "off",
@@ -20071,158 +19919,32 @@ class InputWithExamples extends React__namespace.Component {
20071
19919
  }
20072
19920
  }, examplesAria));
20073
19921
  };
20074
- _handleFocus = () => {
20075
- this.props.onFocus();
20076
- this.setState({
20077
- focused: true,
20078
- showExamples: true
20079
- });
20080
- };
20081
- show = () => {
20082
- this.setState({
20083
- showExamples: true
20084
- });
20085
- };
20086
- hide = () => {
20087
- this.setState({
20088
- showExamples: false
20089
- });
20090
- };
20091
- _handleBlur = () => {
20092
- this.props.onBlur();
20093
- this.setState({
20094
- focused: false,
20095
- showExamples: false
20096
- });
20097
- };
20098
- focus = () => {
20099
- // eslint-disable-next-line react/no-string-refs
20100
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
20101
- this.refs.input.focus();
20102
- };
20103
- blur = () => {
20104
- // eslint-disable-next-line react/no-string-refs
20105
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
20106
- this.refs.input.blur();
20107
- };
20108
- handleChange = e => {
20109
- this.props.onChange(e.target.value);
20110
- };
20111
- render() {
20112
- const input = this._renderInput();
20113
- const examplesContent = this.props.examples.length <= 2 ? this.props.examples.join(" ") // A single item (with or without leading text) is not a "list"
20114
- : this.props.examples // 2 or more items should display as a list
20115
- .map((example, index) => {
20116
- // If the first example is bold, then it is most likely a heading/leading text.
20117
- // So, it shouldn't be part of the list.
20118
- return index === 0 && example.startsWith("**") ? `${example}\n` : `- ${example}`;
20119
- }).join("\n");
20120
- const showExamples = this.props.shouldShowExamples && this.state.showExamples;
20121
- return /*#__PURE__*/React__namespace.createElement(Tooltip, {
20122
- className: "perseus-formats-tooltip preview-measure",
20123
- horizontalPosition: HorizontalDirection.Left,
20124
- horizontalAlign: HorizontalDirection.Left,
20125
- verticalPosition: VerticalDirection.Bottom,
20126
- arrowSize: 10,
20127
- borderColor: "#ccc",
20128
- show: showExamples
20129
- }, input, /*#__PURE__*/React__namespace.createElement("div", {
20130
- 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"
20131
19926
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
20132
19927
  content: examplesContent,
20133
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "input-with-examples"),
20134
- strings: this.context.strings
19928
+ linterContext: PerseusLinter__namespace.pushContextStack(linterContext, "input-with-examples"),
19929
+ strings: context.strings
20135
19930
  })));
20136
- }
20137
- }
20138
-
20139
- class SimpleKeypadInput extends React__namespace.Component {
20140
- _isMounted = false;
20141
- componentDidMount() {
20142
- // TODO(scottgrant): This is a hack to remove the deprecated call to
20143
- // this.isMounted() but is still considered an anti-pattern.
20144
- this._isMounted = true;
20145
- }
20146
- componentWillUnmount() {
20147
- this._isMounted = false;
20148
- }
20149
- focus() {
20150
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
20151
- this.refs.input.focus(); // eslint-disable-line react/no-string-refs
20152
- }
20153
-
20154
- blur() {
20155
- // eslint-disable-next-line react/no-string-refs
20156
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
20157
- if (typeof this.refs.input?.blur === "function") {
20158
- // eslint-disable-next-line react/no-string-refs
20159
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
20160
- this.refs.input?.blur();
20161
- }
20162
- }
20163
- getValue() {
20164
- return this.props.value;
20165
- }
20166
- render() {
20167
- const _this = this;
20168
- // Intercept the `onFocus` prop, as we need to configure the keypad
20169
- // before continuing with the default focus logic for Perseus inputs.
20170
- // Intercept the `value` prop so as to map `null` to the empty string,
20171
- // as the `KeypadInput` does not support `null` values.
20172
- const {
20173
- keypadElement,
20174
- onFocus,
20175
- value,
20176
- ...rest
20177
- } = _this.props;
20178
- return (
20179
- /*#__PURE__*/
20180
- // @ts-expect-error - TS2769 - No overload matches this call.
20181
- React__namespace.createElement(mathInput.KeypadInput
20182
- // eslint-disable-next-line react/no-string-refs
20183
- , _extends({
20184
- ref: "input",
20185
- keypadElement: keypadElement,
20186
- onFocus: () => {
20187
- if (keypadElement) {
20188
- keypadElement.configure({
20189
- keypadType: mathInput.KeypadType.FRACTION
20190
- }, () => {
20191
- if (_this._isMounted) {
20192
- onFocus?.();
20193
- }
20194
- });
20195
- } else {
20196
- onFocus?.();
20197
- }
20198
- },
20199
- value: value == null ? "" : "" + value
20200
- }, rest))
20201
- );
20202
- }
20203
- }
20204
-
20205
- // @ts-expect-error - TS2339 - Property 'propTypes' does not exist on type 'typeof SimpleKeypadInput'.
20206
- SimpleKeypadInput.propTypes = {
20207
- keypadElement: mathInput.keypadElementPropType,
20208
- onFocus: PropTypes__default["default"].func,
20209
- value: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number])
20210
- };
20211
-
20212
- const getPromptJSON$s = (renderProps, userInput) => {
20213
- return {
20214
- type: "input-number",
20215
- options: {
20216
- simplify: renderProps.simplify,
20217
- answerType: renderProps.answerType
20218
- },
20219
- userInput: {
20220
- value: userInput.currentValue
20221
- }
20222
19931
  };
20223
- };
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");
20224
19937
 
20225
- 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 = {
20226
19948
  integer: function (options, strings) {
20227
19949
  return strings.integerExample;
20228
19950
  },
@@ -20334,7 +20056,7 @@ class InputNumber extends React__namespace.Component {
20334
20056
  } = this.context;
20335
20057
  const type = this.props.answerType;
20336
20058
  const forms = perseusScore.inputNumberAnswerTypes[type].forms.split(/\s*,\s*/);
20337
- 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));
20338
20060
  return [strings.yourAnswer].concat(examples);
20339
20061
  }
20340
20062
  render() {
@@ -20448,7 +20170,11 @@ const getPromptJSON$r = (renderProps, userInput) => {
20448
20170
  };
20449
20171
  };
20450
20172
 
20451
- 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 = {
20452
20178
  integer: (form, strings) => strings.integerExample,
20453
20179
  proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
20454
20180
  improper: (form, strings) => form.simplify === "optional" ? strings.improperExample : strings.simplifiedImproperExample,
@@ -20456,9 +20182,182 @@ const formExamples = {
20456
20182
  decimal: (form, strings) => strings.decimalExample,
20457
20183
  pi: (form, strings) => strings.piExample
20458
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
+ */
20459
20359
  class NumericInput extends React__namespace.Component {
20460
- static contextType = PerseusI18nContext;
20461
- inputRef;
20360
+ inputRef = /*#__PURE__*/React__namespace.createRef();
20462
20361
  static defaultProps = {
20463
20362
  currentValue: "",
20464
20363
  size: "normal",
@@ -20474,51 +20373,15 @@ class NumericInput extends React__namespace.Component {
20474
20373
  currentValue: props.currentValue
20475
20374
  };
20476
20375
  }
20477
- state = {
20478
- // keeps track of the other set of values when switching
20479
- // between 0 and finite solutions
20480
- previousValues: [""],
20481
- isFocused: false
20482
- };
20483
-
20484
- /**
20485
- * Generates a string that demonstrates how to input the various supported
20486
- * answer forms.
20487
- */
20488
- examples() {
20489
- // if the set of specified forms are empty, allow all forms
20490
- const forms = this.props.answerForms?.length !== 0 ? this.props.answerForms : Object.keys(formExamples).map(name => {
20491
- return {
20492
- name: name,
20493
- simplify: "required"
20494
- };
20495
- });
20496
- let examples = ___default["default"].map(forms, form => {
20497
- return formExamples[form.name](form, this.context.strings);
20498
- });
20499
- // Ensure no duplicate tooltip text from simplified and unsimplified
20500
- // versions of the same format
20501
- examples = ___default["default"].uniq(examples);
20502
- return [this.context.strings.yourAnswer].concat(examples);
20503
- }
20504
- shouldShowExamples = () => {
20505
- const noFormsAccepted = this.props.answerForms?.length === 0;
20506
- // To check if all answer forms are accepted, we must first
20507
- // find the *names* of all accepted forms, and see if they are
20508
- // all present, ignoring duplicates
20509
- const answerFormNames = ___default["default"].uniq(this.props.answerForms?.map(form => form.name));
20510
- const allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
20511
- return !noFormsAccepted && !allFormsAccepted;
20512
- };
20513
20376
  focus = () => {
20514
- this.inputRef?.focus();
20377
+ this.inputRef.current?.focus();
20515
20378
  return true;
20516
20379
  };
20517
20380
  focusInputPath = () => {
20518
- this.inputRef?.focus();
20381
+ this.inputRef.current?.focus();
20519
20382
  };
20520
20383
  blurInputPath = () => {
20521
- this.inputRef?.blur();
20384
+ this.inputRef.current?.blur();
20522
20385
  };
20523
20386
  getInputPaths = () => {
20524
20387
  // The widget itself is an input, so we return a single empty list to
@@ -20526,152 +20389,57 @@ class NumericInput extends React__namespace.Component {
20526
20389
  /* c8 ignore next */
20527
20390
  return [[]];
20528
20391
  };
20392
+
20393
+ /**
20394
+ * Sets the value of the input at the given path.
20395
+ */
20529
20396
  setInputValue = (path, newValue, cb) => {
20530
- /* c8 ignore next */
20531
20397
  this.props.onChange({
20532
20398
  currentValue: newValue
20533
20399
  }, cb);
20534
20400
  };
20401
+
20402
+ /**
20403
+ * Returns the value the user has currently input for this widget.
20404
+ */
20535
20405
  getUserInput() {
20536
20406
  return NumericInput.getUserInputFromProps(this.props);
20537
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
+ */
20538
20413
  getPromptJSON() {
20539
20414
  return getPromptJSON$r(this.props, this.getUserInput());
20540
20415
  }
20541
- handleChange = (newValue, cb) => {
20542
- this.props.onChange({
20543
- currentValue: newValue
20544
- }, cb);
20545
- this.props.trackInteraction();
20546
- };
20547
- _handleFocus = () => {
20548
- this.props.onFocus([]);
20549
- this.setState(currentState => {
20550
- return {
20551
- ...currentState,
20552
- isFocused: true
20553
- };
20554
- });
20555
- };
20556
- _handleBlur = () => {
20557
- this.props.onBlur([]);
20558
- this.setState(currentState => {
20559
- return {
20560
- ...currentState,
20561
- isFocused: false
20562
- };
20563
- });
20564
- };
20565
20416
  render() {
20566
- let labelText = this.props.labelText;
20567
- if (labelText == null || labelText === "") {
20568
- labelText = this.context.strings.yourAnswerLabel;
20569
- }
20570
-
20571
- // To right align a custom keypad we need to wrap it.
20572
- const maybeRightAlignKeypadInput = keypadInput => {
20573
- return this.props.rightAlign ? /*#__PURE__*/React__namespace.createElement("div", {
20574
- className: "perseus-input-right-align"
20575
- }, keypadInput) : keypadInput;
20576
- };
20577
- if (this.props.apiOptions.customKeypad) {
20578
- // TODO(charlie): Support "Review Mode".
20579
- return maybeRightAlignKeypadInput( /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput, {
20580
- ref: ref => this.inputRef = ref,
20581
- value: this.props.currentValue,
20582
- keypadElement: this.props.keypadElement,
20583
- onChange: this.handleChange,
20584
- onFocus: this._handleFocus,
20585
- onBlur: this._handleBlur
20586
- }));
20587
- }
20588
-
20589
- // Note: This is _very_ similar to what `input-number.jsx` does. If
20590
- // you modify this, double-check if you also need to modify that
20591
- // component.
20592
- const styles = aphrodite.StyleSheet.create({
20593
- input: {
20594
- borderRadius: "3px",
20595
- borderWidth: this.state.isFocused ? "2px" : "1px",
20596
- display: "inline-block",
20597
- fontFamily: `Symbola, "Times New Roman", serif`,
20598
- fontSize: "18px",
20599
- height: "32px",
20600
- lineHeight: "18px",
20601
- padding: this.state.isFocused ? "4px" : "4px 5px",
20602
- // account for added focus border thickness
20603
- textAlign: this.props.rightAlign ? "right" : "left",
20604
- width: this.props.size === "small" ? 40 : 80
20605
- }
20606
- });
20607
- return /*#__PURE__*/React__namespace.createElement(InputWithExamples, {
20608
- ref: ref => this.inputRef = ref,
20609
- value: this.props.currentValue,
20610
- onChange: this.handleChange,
20611
- labelText: labelText,
20612
- examples: this.examples(),
20613
- shouldShowExamples: this.shouldShowExamples(),
20614
- onFocus: this._handleFocus,
20615
- onBlur: this._handleBlur,
20616
- id: this.props.widgetId,
20617
- disabled: this.props.apiOptions.readOnly,
20618
- style: styles.input
20619
- });
20417
+ return /*#__PURE__*/React__namespace.createElement(NumericInputComponent, _extends__default["default"]({}, this.props, {
20418
+ ref: this.inputRef
20419
+ }));
20620
20420
  }
20621
20421
  }
20622
-
20623
- // TODO(thomas): Currently we receive a list of lists of acceptable answer types
20624
- // and union them down into a single set. It's worth considering whether it
20625
- // wouldn't make more sense to have a single set of acceptable answer types for
20626
- // a given *problem* rather than for each possible [correct/wrong] *answer*.
20627
- // When should two answers to a problem take different answer types?
20628
- // See D27790 for more discussion.
20629
- const unionAnswerForms = function (answerFormsList) {
20630
- // Takes a list of lists of answer forms, and returns a list of the forms
20631
- // in each of these lists in the same order that they're listed in the
20632
- // `formExamples` forms from above.
20633
-
20634
- // uniqueBy takes a list of elements and a function which compares whether
20635
- // two elements are equal, and returns a list of unique elements. This is
20636
- // just a helper function here, but works generally.
20637
- const uniqueBy = function (list, iteratee) {
20638
- // @ts-expect-error - TS2347 - Untyped function calls may not accept type arguments.
20639
- return list.reduce((uniqueList, element) => {
20640
- // For each element, decide whether it's already in the list of
20641
- // unique items.
20642
- const inList = ___default["default"].find(uniqueList, iteratee.bind(null, element));
20643
- if (inList) {
20644
- return uniqueList;
20645
- }
20646
- return uniqueList.concat([element]);
20647
- }, []);
20648
- };
20649
-
20650
- // Pull out all of the forms from the different lists.
20651
- const allForms = answerFormsList.flat();
20652
- // Pull out the unique forms using uniqueBy.
20653
- const uniqueForms = uniqueBy(allForms, ___default["default"].isEqual);
20654
- // Sort them by the order they appear in the `formExamples` list.
20655
- const formExampleKeys = Object.keys(formExamples);
20656
- return ___default["default"].sortBy(uniqueForms, form => {
20657
- return formExampleKeys.indexOf(form.name);
20658
- });
20659
- };
20422
+ // Transforms the widget options to the props used to render the widget.
20660
20423
  const propsTransform = function (widgetOptions) {
20661
- 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,
20662
20431
  answerForms: unionAnswerForms(
20663
- // Pull out the name of each form and whether that form has
20664
- // required simplification.
20665
- widgetOptions.answers.map(answer => {
20666
- // @ts-expect-error - TS2345 - Argument of type 'readonly MathFormat[] | undefined' is not assignable to parameter of type 'Collection<any>'.
20667
- 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 => {
20668
20436
  return {
20669
20437
  simplify: answer.simplify,
20670
20438
  name: form
20671
20439
  };
20672
20440
  });
20673
20441
  }))
20674
- });
20442
+ };
20675
20443
  return rendererProps;
20676
20444
  };
20677
20445
  var NumericInput$1 = {
@@ -20686,23 +20454,11 @@ var NumericInput$1 = {
20686
20454
  getOneCorrectAnswerFromRubric(rubric) {
20687
20455
  const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
20688
20456
  const answerStrings = correctAnswers.map(answer => {
20689
- // Figure out how this answer is supposed to be
20690
- // displayed
20691
- let format = "decimal";
20692
- if (answer.answerForms && answer.answerForms[0]) {
20693
- // NOTE(johnsullivan): This isn't exactly ideal, but
20694
- // it does behave well for all the currently known
20695
- // problems. See D14742 for some discussion on
20696
- // alternate strategies.
20697
- format = answer.answerForms[0];
20698
- }
20699
-
20700
- // @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";
20701
20459
  let answerString = kmath.KhanMath.toNumericString(answer.value, format);
20702
20460
  if (answer.maxError) {
20703
- answerString += " \u00B1 " +
20704
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
20705
- kmath.KhanMath.toNumericString(answer.maxError, format);
20461
+ answerString += " \u00B1 " + kmath.KhanMath.toNumericString(answer.maxError, format);
20706
20462
  }
20707
20463
  return answerString;
20708
20464
  });
@@ -23237,6 +22993,7 @@ const BASE_ICON_SIZE = 10;
23237
22993
  // If you pass in an object you need to specify an SVG path to
23238
22994
  // render along with the width and height of the path (to ensure
23239
22995
  // that it gets scaled correctly).
22996
+
23240
22997
  /**
23241
22998
  * An SVG Icon
23242
22999
  *
@@ -23494,7 +23251,7 @@ function ChoiceInner(props) {
23494
23251
  return /*#__PURE__*/React__namespace.createElement("span", null, letter);
23495
23252
  }
23496
23253
  if (correct) {
23497
- return /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconCheck, {
23254
+ return /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconCheck, {
23498
23255
  style: {
23499
23256
  position: "relative",
23500
23257
  top: -1
@@ -23950,7 +23707,7 @@ const styles$p = aphrodite.StyleSheet.create({
23950
23707
  }
23951
23708
  }
23952
23709
  });
23953
- 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, {
23954
23711
  forwardedRef: ref
23955
23712
  })));
23956
23713
 
@@ -23984,11 +23741,11 @@ const ChoiceNoneAbove = function (_ref) {
23984
23741
  strings: strings
23985
23742
  })
23986
23743
  };
23987
- return /*#__PURE__*/React__namespace.createElement(Choice$1, _extends({}, choiceProps, {
23744
+ return /*#__PURE__*/React__namespace.createElement(Choice$1, _extends__default["default"]({}, choiceProps, {
23988
23745
  ref: forwardedRef
23989
23746
  }));
23990
23747
  };
23991
- 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, {
23992
23749
  forwardedRef: ref
23993
23750
  })));
23994
23751
 
@@ -24238,7 +23995,7 @@ const BaseRadio = function (_ref) {
24238
23995
  className: className,
24239
23996
  onClick: clickHandler,
24240
23997
  onTouchStart: labelWrap ? undefined : captureScratchpadTouchStart$1
24241
- }, /*#__PURE__*/React__namespace.createElement(Element, _extends({}, elementProps, {
23998
+ }, /*#__PURE__*/React__namespace.createElement(Element, _extends__default["default"]({}, elementProps, {
24242
23999
  ref: ref
24243
24000
  })))
24244
24001
  );
@@ -24330,7 +24087,6 @@ const styles$o = aphrodite.StyleSheet.create({
24330
24087
  paddingLeft: phoneMargin
24331
24088
  // paddingRight is handled by responsiveFieldset
24332
24089
  },
24333
-
24334
24090
  responsiveFieldset: {
24335
24091
  paddingRight: phoneMargin,
24336
24092
  minWidth: "auto"
@@ -24574,7 +24330,6 @@ class Radio$1 extends React__namespace.Component {
24574
24330
  );
24575
24331
  }
24576
24332
  };
24577
-
24578
24333
  render() {
24579
24334
  const {
24580
24335
  choices
@@ -24751,7 +24506,7 @@ const _choiceTransform = (widgetOptions, strings, problemNum) => {
24751
24506
  // 1) we randomize the order first
24752
24507
  _maybeRandomize(choices)));
24753
24508
  };
24754
- const transform = (widgetOptions, strings, problemNum) => {
24509
+ const transform$1 = (widgetOptions, strings, problemNum) => {
24755
24510
  const choices = _choiceTransform(widgetOptions, strings, problemNum);
24756
24511
  const numCorrect = ___default["default"].reduce(widgetOptions.choices, function (memo, choice) {
24757
24512
  return choice.correct ? memo + 1 : memo;
@@ -24777,8 +24532,8 @@ var Radio = {
24777
24532
  displayName: "Radio / Multiple choice",
24778
24533
  accessible: true,
24779
24534
  widget: Radio$1,
24780
- transform: transform,
24781
- staticTransform: transform,
24535
+ transform: transform$1,
24536
+ staticTransform: transform$1,
24782
24537
  version: perseusCore.radioLogic.version,
24783
24538
  propUpgrades: perseusCore.radioLogic.widgetOptionsUpgrades,
24784
24539
  isLintable: true
@@ -25360,6 +25115,7 @@ var Definition$1 = {
25360
25115
 
25361
25116
  // There are no required props, but this component can receive props from any
25362
25117
  // deprecated widget
25118
+
25363
25119
  class DeprecatedStandin extends React__default["default"].Component {
25364
25120
  static contextType = PerseusI18nContext;
25365
25121
  // this just helps with TS weak typing when a Widget
@@ -25480,20 +25236,20 @@ class Dropdown extends React__namespace.Component {
25480
25236
  }, children)));
25481
25237
  }
25482
25238
  }
25483
- const optionsTransform = widgetOptions => {
25239
+ function transform(widgetOptions) {
25484
25240
  return {
25485
25241
  placeholder: widgetOptions.placeholder,
25486
25242
  visibleLabel: widgetOptions.visibleLabel,
25487
25243
  ariaLabel: widgetOptions.ariaLabel,
25488
25244
  choices: widgetOptions.choices.map(choice => choice.content)
25489
25245
  };
25490
- };
25246
+ }
25491
25247
  var Dropdown$1 = {
25492
25248
  name: "dropdown",
25493
25249
  displayName: "Drop down",
25494
25250
  accessible: true,
25495
25251
  widget: Dropdown,
25496
- transform: optionsTransform
25252
+ transform
25497
25253
  };
25498
25254
 
25499
25255
  const getPromptJSON$j = renderProps => {
@@ -25569,7 +25325,6 @@ class Explanation extends React__namespace.Component {
25569
25325
  // adds space between the right border and any text that follows
25570
25326
  paddingLeft: "2px" // adds space between the left border and the button text
25571
25327
  };
25572
-
25573
25328
  const labelStyle = {
25574
25329
  fontSize: "18px",
25575
25330
  lineHeight: "inherit",
@@ -25725,7 +25480,7 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25725
25480
  fontSize: 28,
25726
25481
  color: wonderBlocksTokens.color.green
25727
25482
  }
25728
- }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconStar, {
25483
+ }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconStar, {
25729
25484
  style: {
25730
25485
  marginBottom: 5
25731
25486
  }
@@ -25918,7 +25673,7 @@ class GradedGroup extends React__namespace.Component {
25918
25673
  if (this.state.status === GRADING_STATUSES.correct) {
25919
25674
  // TODO(jeremy): update to a WB colour
25920
25675
  // @ts-expect-error - TS2322 - Type 'Element' is not assignable to type 'null'.
25921
- icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconOk, {
25676
+ icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconOk, {
25922
25677
  style: {
25923
25678
  color: "#526f03"
25924
25679
  }
@@ -25927,7 +25682,7 @@ class GradedGroup extends React__namespace.Component {
25927
25682
  } else if (this.state.status === GRADING_STATUSES.incorrect) {
25928
25683
  // TODO(jeremy): update to a WB colour
25929
25684
  // @ts-expect-error - TS2322 - Type 'Element' is not assignable to type 'null'.
25930
- icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconRemove, {
25685
+ icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconRemove, {
25931
25686
  style: {
25932
25687
  color: "#ff5454"
25933
25688
  }
@@ -25954,7 +25709,7 @@ class GradedGroup extends React__namespace.Component {
25954
25709
  className: classes
25955
25710
  }, !!this.props.title && /*#__PURE__*/React__namespace.createElement("div", {
25956
25711
  className: aphrodite.css(styles$i.title)
25957
- }, 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, {
25958
25713
  ref: this.rendererRef,
25959
25714
  apiOptions: {
25960
25715
  ...apiOptions,
@@ -25997,7 +25752,7 @@ class GradedGroup extends React__namespace.Component {
25997
25752
  showHint: false
25998
25753
  });
25999
25754
  }
26000
- }, 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, {
26001
25756
  ref: this.hintRendererRef,
26002
25757
  apiOptions: apiOptions,
26003
25758
  linterContext: this.props.linterContext,
@@ -26222,7 +25977,7 @@ class GradedGroupSet extends React__namespace.Component {
26222
25977
  // TODO(jeremy): Don't spread this.props, instead
26223
25978
  // pass in all props GradedGroup needs explicilty
26224
25979
  // @ts-expect-error - TS2769 - No overload matches this call.
26225
- React__namespace.createElement(GradedGroup, _extends({
25980
+ React__namespace.createElement(GradedGroup, _extends__default["default"]({
26226
25981
  key: i
26227
25982
  }, this.props, gradedGroup, {
26228
25983
  inGradedGroupSet: false,
@@ -26251,7 +26006,7 @@ class GradedGroupSet extends React__namespace.Component {
26251
26006
  onChangeCurrentGroup: currentGroup => this.setState({
26252
26007
  currentGroup
26253
26008
  })
26254
- })), /*#__PURE__*/React__namespace.createElement(GradedGroup, _extends({
26009
+ })), /*#__PURE__*/React__namespace.createElement(GradedGroup, _extends__default["default"]({
26255
26010
  key: this.state.currentGroup
26256
26011
  // @ts-expect-error - TS2322 - Type 'GradedGroup | null' is not assignable to type 'GradedGroup'.
26257
26012
  // Type 'null' is not assignable to type 'GradedGroup'.
@@ -26647,7 +26402,7 @@ class FunctionGrapher extends React__namespace.Component {
26647
26402
  }
26648
26403
  const functionProps = model.getPropsForCoeffs(coeffs, xRange);
26649
26404
  const Movable = movableTypeToComponent[model.movable];
26650
- return /*#__PURE__*/React__namespace.createElement(Movable, _extends({}, functionProps, {
26405
+ return /*#__PURE__*/React__namespace.createElement(Movable, _extends__default["default"]({}, functionProps, {
26651
26406
  key: this.props.model.url,
26652
26407
  range: xRange,
26653
26408
  style: style
@@ -26780,7 +26535,7 @@ class FunctionGrapher extends React__namespace.Component {
26780
26535
  width: box[0],
26781
26536
  height: box[1]
26782
26537
  }
26783
- }, image, /*#__PURE__*/React__namespace.createElement(Graphie, _extends({}, this.props.graph, {
26538
+ }, image, /*#__PURE__*/React__namespace.createElement(Graphie, _extends__default["default"]({}, this.props.graph, {
26784
26539
  setDrawingAreaAvailable: this.props.setDrawingAreaAvailable
26785
26540
  }), this.props.model && this.renderPlot(), this.props.model && this.renderAsymptote(), this.props.model && points)));
26786
26541
  }
@@ -27924,7 +27679,6 @@ class Interaction extends React__namespace.Component {
27924
27679
  }
27925
27680
  });
27926
27681
  }
27927
-
27928
27682
  if (element.type === "parametric") {
27929
27683
  const fn = t => {
27930
27684
  return [
@@ -27977,7 +27731,6 @@ class Interaction extends React__namespace.Component {
27977
27731
  }
27978
27732
  });
27979
27733
  }
27980
-
27981
27734
  if (element.type === "label") {
27982
27735
  const coord = [
27983
27736
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -28309,7 +28062,6 @@ function scaleCommandBy(scaleFactor) {
28309
28062
  command.args[6] * scaleFactor // end y
28310
28063
  ]
28311
28064
  };
28312
-
28313
28065
  default:
28314
28066
  return {
28315
28067
  ...command,
@@ -29069,7 +28821,6 @@ const styles$f = aphrodite.StyleSheet.create({
29069
28821
  userSelect: "none",
29070
28822
  borderWidth: 0 // <button> gives us a border by default
29071
28823
  },
29072
-
29073
28824
  // We want to render the element in-place in our fixtures, so allow us to
29074
28825
  // render in the bottom corner in production and in-place in testing.
29075
28826
  hudFixedPosition: {
@@ -29099,7 +28850,6 @@ const styles$f = aphrodite.StyleSheet.create({
29099
28850
  ":hover": {
29100
28851
  backgroundColor: "#a1a5a9" // in between those two grays
29101
28852
  },
29102
-
29103
28853
  ":active": {
29104
28854
  backgroundColor: gray68
29105
28855
  }
@@ -29144,7 +28894,6 @@ class InfoTip extends React__namespace.Component {
29144
28894
  });
29145
28895
  /* eslint-enable react/no-did-mount-set-state */
29146
28896
  }
29147
-
29148
28897
  render() {
29149
28898
  if (this.state.didMount) {
29150
28899
  return /*#__PURE__*/React__namespace.createElement(InfoTipBase, this.props);
@@ -29410,7 +29159,6 @@ class NumberInput extends React__namespace.Component {
29410
29159
  if (val !== Math.floor(val)) {
29411
29160
  return; // bail if not an integer
29412
29161
  }
29413
-
29414
29162
  if (e.key === "ArrowUp") {
29415
29163
  val = val + 1;
29416
29164
  } else if (e.key === "ArrowDown") {
@@ -29443,7 +29191,7 @@ class NumberInput extends React__namespace.Component {
29443
29191
  useArrowKeys,
29444
29192
  ...restProps
29445
29193
  } = this.props;
29446
- return /*#__PURE__*/React__namespace.createElement("input", _extends({}, restProps, {
29194
+ return /*#__PURE__*/React__namespace.createElement("input", _extends__default["default"]({}, restProps, {
29447
29195
  className: classes,
29448
29196
  type: "text",
29449
29197
  ref: this.inputRef,
@@ -29488,7 +29236,7 @@ class RangeInput extends React__namespace.Component {
29488
29236
  const checkValidity = this.props.checkValidity || truth;
29489
29237
  return /*#__PURE__*/React__namespace.createElement("div", {
29490
29238
  className: "range-input"
29491
- }, /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
29239
+ }, /*#__PURE__*/React__namespace.createElement(NumberInput, _extends__default["default"]({}, this.props, {
29492
29240
  value: value[0],
29493
29241
  checkValidity: val => checkValidity([val, value[1]])
29494
29242
  // eslint-disable-next-line react/jsx-no-bind
@@ -29496,7 +29244,7 @@ class RangeInput extends React__namespace.Component {
29496
29244
  onChange: this.onChange.bind(this, 0),
29497
29245
  placeholder: this.props.placeholder[0],
29498
29246
  allowPiTruncation: this.props.allowPiTruncation
29499
- })), /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
29247
+ })), /*#__PURE__*/React__namespace.createElement(NumberInput, _extends__default["default"]({}, this.props, {
29500
29248
  value: value[1],
29501
29249
  checkValidity: val => checkValidity([value[0], val])
29502
29250
  // eslint-disable-next-line react/jsx-no-bind
@@ -29509,20 +29257,20 @@ class RangeInput extends React__namespace.Component {
29509
29257
  }
29510
29258
 
29511
29259
  var components = /*#__PURE__*/Object.freeze({
29512
- __proto__: null,
29513
- ButtonGroup: ButtonGroup,
29514
- Graphie: Graphie,
29515
- HUD: HUD,
29516
- Icon: Icon,
29517
- InfoTip: InfoTip,
29518
- InlineIcon: InlineIcon,
29519
- MathInput: MathInput,
29520
- MultiButtonGroup: MultiButtonGroup,
29521
- NumberInput: NumberInput,
29522
- RangeInput: RangeInput,
29523
- SvgImage: SvgImage,
29524
- TextInput: TextInput,
29525
- 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
29526
29274
  });
29527
29275
 
29528
29276
  /**
@@ -29598,17 +29346,17 @@ function GraphLockedLabelsLayer(props) {
29598
29346
  } = props;
29599
29347
  return lockedFigures.map((figure, i) => {
29600
29348
  if (figure.type === "label") {
29601
- return /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29349
+ return /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends__default["default"]({
29602
29350
  key: `label-${i}`
29603
29351
  }, figure));
29604
29352
  }
29605
29353
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, {
29606
29354
  key: i
29607
- }, 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"]({
29608
29356
  key: `${i}-label-${j}`
29609
- }, 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"]({
29610
29358
  key: `locked-figure-${i}-point-1-label-${k}`
29611
- }, 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"]({
29612
29360
  key: `locked-figure-${i}-point-2-label-${k}`
29613
29361
  }, label)))));
29614
29362
  });
@@ -29654,9 +29402,9 @@ const LockedFunction = props => {
29654
29402
  const {
29655
29403
  color,
29656
29404
  strokeStyle,
29657
- directionalAxis
29405
+ directionalAxis,
29406
+ domain
29658
29407
  } = props;
29659
- const domain = props.domain ? [Number.isFinite(props.domain[0]) ? props.domain[0] : -Infinity, Number.isFinite(props.domain[1]) ? props.domain[1] : Infinity] : undefined;
29660
29408
  const plotProps = {
29661
29409
  color: perseusCore.lockedFigureColors[color],
29662
29410
  style: strokeStyle,
@@ -29676,11 +29424,11 @@ const LockedFunction = props => {
29676
29424
  "aria-label": hasAria ? props.ariaLabel : undefined,
29677
29425
  "aria-hidden": !hasAria,
29678
29426
  role: "img"
29679
- }, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends({
29427
+ }, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends__default["default"]({
29680
29428
  y: x => equation.eval({
29681
29429
  x
29682
29430
  })
29683
- }, 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"]({
29684
29432
  x: y => equation.eval({
29685
29433
  y
29686
29434
  })
@@ -30254,28 +30002,28 @@ const GraphLockedLayer = props => {
30254
30002
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lockedFigures.map((figure, index) => {
30255
30003
  switch (figure.type) {
30256
30004
  case "point":
30257
- return /*#__PURE__*/React__namespace.createElement(LockedPoint, _extends({
30005
+ return /*#__PURE__*/React__namespace.createElement(LockedPoint, _extends__default["default"]({
30258
30006
  key: `point-${index}`
30259
30007
  }, figure));
30260
30008
  case "line":
30261
- return /*#__PURE__*/React__namespace.createElement(LockedLine, _extends({
30009
+ return /*#__PURE__*/React__namespace.createElement(LockedLine, _extends__default["default"]({
30262
30010
  key: `line-${index}`,
30263
30011
  range: props.range
30264
30012
  }, figure));
30265
30013
  case "vector":
30266
- return /*#__PURE__*/React__namespace.createElement(LockedVector, _extends({
30014
+ return /*#__PURE__*/React__namespace.createElement(LockedVector, _extends__default["default"]({
30267
30015
  key: `vector-${index}`
30268
30016
  }, figure));
30269
30017
  case "ellipse":
30270
- return /*#__PURE__*/React__namespace.createElement(LockedEllipse, _extends({
30018
+ return /*#__PURE__*/React__namespace.createElement(LockedEllipse, _extends__default["default"]({
30271
30019
  key: `ellipse-${index}`
30272
30020
  }, figure));
30273
30021
  case "polygon":
30274
- return /*#__PURE__*/React__namespace.createElement(LockedPolygon, _extends({
30022
+ return /*#__PURE__*/React__namespace.createElement(LockedPolygon, _extends__default["default"]({
30275
30023
  key: `polygon-${index}`
30276
30024
  }, figure));
30277
30025
  case "function":
30278
- return /*#__PURE__*/React__namespace.createElement(LockedFunction, _extends({
30026
+ return /*#__PURE__*/React__namespace.createElement(LockedFunction, _extends__default["default"]({
30279
30027
  key: `function-${index}`
30280
30028
  }, figure));
30281
30029
  case "label":
@@ -30506,7 +30254,7 @@ const TextLabel = _ref => {
30506
30254
  children,
30507
30255
  ...rest
30508
30256
  } = _ref;
30509
- return /*#__PURE__*/React__default["default"].createElement(mafs.Text, _extends({
30257
+ return /*#__PURE__*/React__default["default"].createElement(mafs.Text, _extends__default["default"]({
30510
30258
  size: 16,
30511
30259
  svgTextProps: {
30512
30260
  filter: "url(#background)",
@@ -31217,6 +30965,9 @@ const MovableLine = props => {
31217
30965
  onMoveLine = () => {},
31218
30966
  onMovePoint = () => {}
31219
30967
  } = props;
30968
+ const {
30969
+ snapStep
30970
+ } = useGraphConfig();
31220
30971
 
31221
30972
  // Aria live states for (0) point 1, (1) point 2, and (2) grab handle.
31222
30973
  // When moving an element, set its aria live to "polite" and the others
@@ -31248,7 +30999,8 @@ const MovableLine = props => {
31248
30999
  onMove: p => {
31249
31000
  setAriaLives(["polite", "off", "off"]);
31250
31001
  onMovePoint(0, p);
31251
- }
31002
+ },
31003
+ constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 0)
31252
31004
  });
31253
31005
  const {
31254
31006
  visiblePoint: visiblePoint2,
@@ -31263,7 +31015,8 @@ const MovableLine = props => {
31263
31015
  onMove: p => {
31264
31016
  setAriaLives(["off", "polite", "off"]);
31265
31017
  onMovePoint(1, p);
31266
- }
31018
+ },
31019
+ constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 1)
31267
31020
  });
31268
31021
  const line = /*#__PURE__*/React__namespace.createElement(Line$1, {
31269
31022
  ariaLabel: ariaLabels?.grabHandleAriaLabel,
@@ -31369,6 +31122,34 @@ const Line$1 = props => {
31369
31122
  color: stroke
31370
31123
  }));
31371
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
+ };
31372
31153
  function trimRange(range, graphDimensionsInPixels) {
31373
31154
  const pixelsToTrim = 4;
31374
31155
  const [xRange, yRange] = range;
@@ -31725,7 +31506,8 @@ function CircleGraph(props) {
31725
31506
  } = props;
31726
31507
  const {
31727
31508
  center,
31728
- radiusPoint
31509
+ radiusPoint,
31510
+ snapStep
31729
31511
  } = graphState;
31730
31512
  const {
31731
31513
  strings,
@@ -31788,7 +31570,8 @@ function CircleGraph(props) {
31788
31570
  onMove: newRadiusPoint => {
31789
31571
  setRadiusPointAriaLive("polite");
31790
31572
  dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
31791
- }
31573
+ },
31574
+ constrain: getCircleKeyboardConstraint(center, radiusPoint, snapStep)
31792
31575
  }), /*#__PURE__*/React__namespace.createElement("g", {
31793
31576
  id: radiusId,
31794
31577
  style: a11y.srOnly
@@ -31899,6 +31682,30 @@ function getCircleGraphDescription(state, i18n) {
31899
31682
  const strings = describeCircleGraph(state, i18n);
31900
31683
  return strings.srCircleInteractiveElement;
31901
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
+ };
31902
31709
 
31903
31710
  // Exported for testing
31904
31711
  function describeCircleGraph(state, i18n) {
@@ -31911,6 +31718,7 @@ function describeCircleGraph(state, i18n) {
31911
31718
  radiusPoint
31912
31719
  } = state;
31913
31720
  const radius = getRadius(state);
31721
+ const isRadiusOnRight = radiusPoint[X] >= center[X];
31914
31722
 
31915
31723
  // Aria label strings
31916
31724
  const srCircleGraph = strings.srCircleGraph;
@@ -31918,7 +31726,10 @@ function describeCircleGraph(state, i18n) {
31918
31726
  centerX: srFormatNumber(center[0], locale),
31919
31727
  centerY: srFormatNumber(center[1], locale)
31920
31728
  });
31921
- 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({
31922
31733
  radiusPointX: srFormatNumber(radiusPoint[0], locale),
31923
31734
  radiusPointY: srFormatNumber(radiusPoint[1], locale)
31924
31735
  });
@@ -32084,6 +31895,12 @@ const LinearSystemGraph = props => {
32084
31895
  locale
32085
31896
  } = usePerseusI18n();
32086
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;
32087
31904
  const linesAriaInfo = lines.map((line, i) => {
32088
31905
  return {
32089
31906
  pointsDescriptionId: `${id}-line${i + 1}-points`,
@@ -32100,16 +31917,17 @@ const LinearSystemGraph = props => {
32100
31917
  slopeDescription: getSlopeStringForLine(line, strings)
32101
31918
  };
32102
31919
  });
31920
+ const individualLineDescriptions = linesAriaInfo.map(_ref => {
31921
+ let {
31922
+ pointsDescriptionId,
31923
+ interceptDescriptionId,
31924
+ slopeDescriptionId
31925
+ } = _ref;
31926
+ return `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`;
31927
+ }).join(" ");
32103
31928
  return /*#__PURE__*/React__namespace.createElement("g", {
32104
31929
  "aria-label": strings.srLinearSystemGraph,
32105
- "aria-describedby": linesAriaInfo.map(_ref => {
32106
- let {
32107
- pointsDescriptionId,
32108
- interceptDescriptionId,
32109
- slopeDescriptionId
32110
- } = _ref;
32111
- return `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`;
32112
- }).join(" ")
31930
+ "aria-describedby": `${individualLineDescriptions} ${intersectionId}`
32113
31931
  }, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(MovableLine, {
32114
31932
  key: i,
32115
31933
  points: line,
@@ -32126,9 +31944,15 @@ const LinearSystemGraph = props => {
32126
31944
  x: srFormatNumber(line[1][0], locale),
32127
31945
  y: srFormatNumber(line[1][1], locale)
32128
31946
  }),
32129
- 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
+ })
32130
31954
  },
32131
- ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId}`,
31955
+ ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId} ${intersectionId}`,
32132
31956
  onMoveLine: delta => {
32133
31957
  dispatch(actions.linearSystem.moveLine(i, delta));
32134
31958
  },
@@ -32160,7 +31984,10 @@ const LinearSystemGraph = props => {
32160
31984
  id: slopeDescriptionId,
32161
31985
  style: a11y.srOnly
32162
31986
  }, slopeDescription));
32163
- }));
31987
+ }), /*#__PURE__*/React__namespace.createElement("g", {
31988
+ id: intersectionId,
31989
+ style: a11y.srOnly
31990
+ }, intersectionDescription));
32164
31991
  };
32165
31992
 
32166
31993
  // Exported for testing
@@ -32826,7 +32653,8 @@ function QuadraticGraph(props) {
32826
32653
  graphState
32827
32654
  } = props;
32828
32655
  const {
32829
- coords
32656
+ coords,
32657
+ snapStep
32830
32658
  } = graphState;
32831
32659
  const {
32832
32660
  strings,
@@ -32879,6 +32707,7 @@ function QuadraticGraph(props) {
32879
32707
  ariaLabel: `${srQuadraticPoint}${srVertex}`,
32880
32708
  point: coord,
32881
32709
  sequenceNumber: i + 1,
32710
+ constrain: getQuadraticKeyboardConstraint(coords, snapStep, i),
32882
32711
  onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
32883
32712
  });
32884
32713
  }), srQuadraticDirection && /*#__PURE__*/React__namespace.createElement("g", {
@@ -32967,6 +32796,60 @@ function describeQuadraticGraph(state, i18n) {
32967
32796
  srQuadraticInteractiveElements
32968
32797
  };
32969
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
+ };
32970
32853
 
32971
32854
  function renderRayGraph(state, dispatch, i18n) {
32972
32855
  return {
@@ -33222,7 +33105,8 @@ function SinusoidGraph(props) {
33222
33105
  // Note: The order of the coordinates is important:
33223
33106
  // The coords[0] is the root and the coords[1] is the first peak
33224
33107
  const {
33225
- coords
33108
+ coords,
33109
+ snapStep
33226
33110
  } = graphState;
33227
33111
 
33228
33112
  // The coefficients are used to calculate the sinusoid equation, plot the graph, and to indicate
@@ -33261,11 +33145,37 @@ function SinusoidGraph(props) {
33261
33145
  key: "point-" + i,
33262
33146
  point: coord,
33263
33147
  sequenceNumber: i + 1,
33148
+ constrain: getSinusoidKeyboardConstraint(coords, snapStep, i),
33264
33149
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
33265
33150
  })), /*#__PURE__*/React__namespace.createElement("g", {
33266
33151
  id: descriptionId
33267
33152
  }, srSinusoidDescription));
33268
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
+ };
33269
33179
 
33270
33180
  // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
33271
33181
  const computeSine = function (x,
@@ -33317,6 +33227,14 @@ function describeSinusoidGraph(state, i18n) {
33317
33227
  const [root, peak] = coords;
33318
33228
  const diffX = Math.abs(peak[X] - root[X]);
33319
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
+ };
33320
33238
  const srSinusoidGraph = strings.srSinusoidGraph;
33321
33239
  const srSinusoidDescription = strings.srSinusoidDescription({
33322
33240
  minValue: srFormatNumber(root[Y] - diffY, locale),
@@ -33324,14 +33242,8 @@ function describeSinusoidGraph(state, i18n) {
33324
33242
  cycleStart: srFormatNumber(root[X] - 2 * diffX, locale),
33325
33243
  cycleEnd: srFormatNumber(root[X] + 2 * diffX, locale)
33326
33244
  });
33327
- const srSinusoidRootPoint = strings.srSinusoidRootPoint({
33328
- x: srFormatNumber(root[X], locale),
33329
- y: srFormatNumber(root[Y], locale)
33330
- });
33331
- const srSinusoidPeakPoint = strings.srSinusoidPeakPoint({
33332
- x: srFormatNumber(peak[X], locale),
33333
- y: srFormatNumber(peak[Y], locale)
33334
- });
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);
33335
33247
  const srSinusoidInteractiveElements = strings.srInteractiveElements({
33336
33248
  elements: strings.srSinusoidInteractiveElements({
33337
33249
  point1X: srFormatNumber(root[X], locale),
@@ -33353,16 +33265,17 @@ const {
33353
33265
  calculateAngleInDegrees,
33354
33266
  convertDegreesToRadians
33355
33267
  } = kmath.angles;
33356
- const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
33268
+ const protractorImage = "https://cdn.kastatic.org/images/perseus/protractor.svg";
33357
33269
 
33358
33270
  // The vector from the center of the protractor to the top left corner of the
33359
33271
  // protractor image, in pixels. Used for positioning.
33360
- const centerToTopLeft = [-180, -170];
33272
+ const centerToTopLeft = [-195, -190];
33361
33273
 
33362
33274
  // The vector from the center of the protractor to the center of the rotation
33363
33275
  // handle.
33364
- const centerToRotationHandle = [-176, -15];
33276
+ const centerToRotationHandle = [-201, -15];
33365
33277
  function Protractor() {
33278
+ const staticUrl = getDependencies().staticUrl;
33366
33279
  const {
33367
33280
  range,
33368
33281
  snapStep
@@ -33374,7 +33287,9 @@ function Protractor() {
33374
33287
  const [center, setCenter] = React.useState(initialCenter);
33375
33288
  const [rotationHandleOffset, setRotationHandleOffset] = React.useState(centerToRotationHandle);
33376
33289
  const draggableRef = React.useRef(null);
33377
- useDraggable({
33290
+ const {
33291
+ dragging
33292
+ } = useDraggable({
33378
33293
  gestureTarget: draggableRef,
33379
33294
  onMove: setCenter,
33380
33295
  point: center,
@@ -33398,10 +33313,11 @@ function Protractor() {
33398
33313
  ref: draggableRef,
33399
33314
  transform: `translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,
33400
33315
  style: {
33401
- transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`
33316
+ transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`,
33317
+ cursor: dragging ? "grabbing" : "grab"
33402
33318
  }
33403
33319
  }, /*#__PURE__*/React__namespace.createElement("image", {
33404
- href: protractorImage
33320
+ href: staticUrl(protractorImage)
33405
33321
  }), /*#__PURE__*/React__namespace.createElement("g", {
33406
33322
  transform: `translate(5, ${-centerToTopLeft[1]})`,
33407
33323
  ref: rotationHandleRef
@@ -35333,8 +35249,8 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
35333
35249
 
35334
35250
  // If the graph is static, it always displays the correct answer. This is
35335
35251
  // standard behavior for Perseus widgets (e.g. compare the Radio widget).
35336
- if (props.static) {
35337
- 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, {
35338
35254
  state: initializeGraphState({
35339
35255
  ...props,
35340
35256
  graph: props.correct
@@ -35342,7 +35258,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
35342
35258
  dispatch: dispatch
35343
35259
  }));
35344
35260
  }
35345
- return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
35261
+ return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends__default["default"]({}, props, {
35346
35262
  state: state,
35347
35263
  dispatch: dispatch
35348
35264
  }));
@@ -35353,7 +35269,7 @@ const {
35353
35269
  } = kmath.angles;
35354
35270
  const {
35355
35271
  getLineEquation,
35356
- getLineIntersection,
35272
+ getLineIntersectionString,
35357
35273
  magnitude,
35358
35274
  vector
35359
35275
  } = kmath.geometry;
@@ -35435,7 +35351,7 @@ class InteractiveGraph extends React__namespace.Component {
35435
35351
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
35436
35352
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
35437
35353
  const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
35438
- return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends({}, this.props, {
35354
+ return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends__default["default"]({}, this.props, {
35439
35355
  ref: this.mafsRef,
35440
35356
  gridStep: gridStep,
35441
35357
  snapStep: snapStep,
@@ -35718,7 +35634,7 @@ class InteractiveGraph extends React__namespace.Component {
35718
35634
  }
35719
35635
  static getLinearSystemEquationString(props) {
35720
35636
  const coords = InteractiveGraph.getLinearSystemCoords(props.graph, props);
35721
- 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]);
35722
35638
  }
35723
35639
  static getPointEquationString(props) {
35724
35640
  if (props.graph.type !== "point") {
@@ -35870,10 +35786,10 @@ const AnswerChoices = props => {
35870
35786
  onToggle,
35871
35787
  disabled
35872
35788
  };
35873
- 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, {
35874
35790
  selectedValues: selectedValues,
35875
35791
  onChange: selected => onAnswerChange(selected)
35876
- })) : /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, _extends({}, args, {
35792
+ })) : /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, _extends__default["default"]({}, args, {
35877
35793
  selectedValue: selectedValues[0],
35878
35794
  onChange: selected => onAnswerChange([selected]),
35879
35795
  placeholder: props.choices[0].content // not visible, but required
@@ -36159,7 +36075,6 @@ const styles$a = aphrodite.StyleSheet.create({
36159
36075
  markerCorrect: {
36160
36076
  background: "#00880b" // WB green darkened by 18%
36161
36077
  },
36162
-
36163
36078
  markerIncorrect: {
36164
36079
  background: wonderBlocksTokens.color.offBlack64
36165
36080
  }
@@ -36556,7 +36471,6 @@ class LabelImage extends React__namespace.Component {
36556
36471
  const adjustPillDistance = {
36557
36472
  [`margin${markerPosition.charAt(0).toUpperCase() + markerPosition.slice(1)}`]: 10 // move pill further from marker
36558
36473
  };
36559
-
36560
36474
  const answerChoicesActive = index === activeMarkerIndex;
36561
36475
  const showAnswerChoice = marker.selected && !answerChoicesActive && !this.state.hideAnswers;
36562
36476
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
@@ -36605,7 +36519,7 @@ class LabelImage extends React__namespace.Component {
36605
36519
  focused,
36606
36520
  pressed
36607
36521
  } = _ref2;
36608
- return /*#__PURE__*/React__namespace.createElement(Marker, _extends({}, marker, {
36522
+ return /*#__PURE__*/React__namespace.createElement(Marker, _extends__default["default"]({}, marker, {
36609
36523
  showCorrectness: showCorrectness,
36610
36524
  showSelected: opened,
36611
36525
  showPulsate: !markersInteracted,
@@ -36750,7 +36664,7 @@ const styles$9 = aphrodite.StyleSheet.create({
36750
36664
  });
36751
36665
  const LabelImageWithDependencies = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
36752
36666
  const deps = useDependencies();
36753
- return /*#__PURE__*/React__namespace.createElement(LabelImage, _extends({
36667
+ return /*#__PURE__*/React__namespace.createElement(LabelImage, _extends__default["default"]({
36754
36668
  ref: ref,
36755
36669
  analytics: deps.analytics
36756
36670
  }, props));
@@ -37012,7 +36926,6 @@ class Draggable extends React__namespace.Component {
37012
36926
  if (this.props.width) {
37013
36927
  style.width = this.props.width + 1; // Fix for non-integer widths
37014
36928
  }
37015
-
37016
36929
  if (this.props.height) {
37017
36930
  style.height = this.props.height;
37018
36931
  }
@@ -37356,8 +37269,7 @@ class Sortable extends React__namespace.Component {
37356
37269
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
37357
37270
  margin = "0 0 " + this.props.margin + "px 0"; // bottom
37358
37271
  }
37359
-
37360
- cards.push( /*#__PURE__*/React__namespace.createElement(Draggable, {
37272
+ cards.push(/*#__PURE__*/React__namespace.createElement(Draggable, {
37361
37273
  content: item.option,
37362
37274
  key: item.key,
37363
37275
  state: item.state
@@ -37410,7 +37322,7 @@ class Sortable extends React__namespace.Component {
37410
37322
  this, item.key)
37411
37323
  }));
37412
37324
  if (item.state === ItemState.DRAGGING || item.state === ItemState.ANIMATING) {
37413
- cards.push( /*#__PURE__*/React__namespace.createElement(Placeholder, {
37325
+ cards.push(/*#__PURE__*/React__namespace.createElement(Placeholder, {
37414
37326
  key: "placeholder_" + item.key,
37415
37327
  ref: "placeholder_" + item.key,
37416
37328
  width: item.width,
@@ -38024,7 +37936,7 @@ class Matrix extends React__namespace.Component {
38024
37936
  boxSizing: "border-box",
38025
37937
  backgroundColor: outside ? "#f3f3f3" : "#fff"
38026
37938
  };
38027
- MatrixInput = /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput, _extends({}, inputProps, {
37939
+ MatrixInput = /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput, _extends__default["default"]({}, inputProps, {
38028
37940
  style: style,
38029
37941
  scrollable: true,
38030
37942
  keypadElement: this.props.keypadElement
@@ -39185,7 +39097,6 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
39185
39097
  if (!___default["default"].isFinite(props.tickStep) || props.tickStep <= 0) {
39186
39098
  return []; // this has screwed me for the last time!
39187
39099
  }
39188
-
39189
39100
  const results = [];
39190
39101
 
39191
39102
  // For convenience, extract some props into separate variables
@@ -39256,6 +39167,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
39256
39167
 
39257
39168
  // TODO: most widgets use some like Widget<Something, PerseusNumberLineWidgetOptions>
39258
39169
  // should this one?
39170
+
39259
39171
  class NumberLine extends React__namespace.Component {
39260
39172
  static contextType = PerseusI18nContext;
39261
39173
  static defaultProps = {
@@ -39400,7 +39312,7 @@ class NumberLine extends React__namespace.Component {
39400
39312
  setup: this._setupGraphie,
39401
39313
  setDrawingAreaAvailable: this.props.apiOptions.setDrawingAreaAvailable,
39402
39314
  isMobile: this.props.apiOptions.isMobile
39403
- }, /*#__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"]), {
39404
39316
  isMobile: this.props.apiOptions.isMobile
39405
39317
  })), this._renderInequality(props), this._renderNumberLinePoint(props));
39406
39318
  };
@@ -39627,7 +39539,6 @@ const numberLineTransform = editorProps => {
39627
39539
  } else {
39628
39540
  numDivisions = undefined; // send to getDefaultProps()
39629
39541
  }
39630
-
39631
39542
  ___default["default"].extend(props, {
39632
39543
  numLinePosition: numLinePosition,
39633
39544
  numDivisions: numDivisions,
@@ -39652,7 +39563,6 @@ const staticTransform$1 = editorProps => {
39652
39563
  } else {
39653
39564
  numDivisions = undefined; // send to getDefaultProps()
39654
39565
  }
39655
-
39656
39566
  ___default["default"].extend(props, {
39657
39567
  numLinePosition: numLinePosition,
39658
39568
  numDivisions: numDivisions,
@@ -39847,7 +39757,7 @@ class Card extends React__namespace.Component {
39847
39757
  onTouchCancel: this.onMouseUp
39848
39758
  }, /*#__PURE__*/React__namespace.createElement("div", {
39849
39759
  className: className.join(" ")
39850
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, rendererProps, {
39760
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({}, rendererProps, {
39851
39761
  linterContext: this.props.linterContext,
39852
39762
  strings: this.context.strings
39853
39763
  }))))
@@ -40672,7 +40582,6 @@ class Plotter extends React__namespace.Component {
40672
40582
  const initialY = isMobile ? c.scaleY : 0;
40673
40583
  for (let y = initialY; y <= c.dimY; y += c.scaleY) {
40674
40584
  graphie.label([0, y], kmath.KhanMath.roundToApprox(y, 2), "left", /* isTeX */true /* for the \approx symbol */);
40675
-
40676
40585
  graphie.style({
40677
40586
  stroke: isMobile ? "#e9ebec" : "#000",
40678
40587
  strokeWidth: 1,
@@ -41634,7 +41543,7 @@ class Table extends React__namespace.Component {
41634
41543
  }, ___default["default"](columns).times(c => {
41635
41544
  return /*#__PURE__*/React__namespace.createElement("td", {
41636
41545
  key: c
41637
- }, /*#__PURE__*/React__namespace.createElement(InputComponent, _extends({
41546
+ }, /*#__PURE__*/React__namespace.createElement(InputComponent, _extends__default["default"]({
41638
41547
  ref: getRefForPath(getInputPath(r, c)),
41639
41548
  type: "text",
41640
41549
  value: this.props.answers[r][c],
@@ -41845,10 +41754,51 @@ const init = function () {
41845
41754
  replaceDeprecatedWidgets();
41846
41755
  };
41847
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
+
41848
41798
  // This file is processed by a Rollup plugin (replace) to inject the production
41849
41799
  const libName = "@khanacademy/perseus";
41850
- const libVersion = "54.1.0";
41851
- perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
41800
+ const libVersion = "55.0.0";
41801
+ addLibraryVersionToPerseusDebug(libName, libVersion);
41852
41802
 
41853
41803
  const apiVersion = {
41854
41804
  major: 12,
@@ -42004,7 +41954,7 @@ class ArticleRenderer extends React__namespace.Component {
42004
41954
  return /*#__PURE__*/React__namespace.createElement("div", {
42005
41955
  key: sectionIndex,
42006
41956
  className: "clearfix"
42007
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, section, {
41957
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({}, section, {
42008
41958
  ref: elem => {
42009
41959
  if (elem) {
42010
41960
  this.sectionRenderers[sectionIndex] = elem;
@@ -42179,7 +42129,6 @@ class HintsRenderer extends React__namespace.Component {
42179
42129
  ReactDOM__default["default"].findDOMNode(this.refs["hintRenderer" + pos]).focus(); // eslint-disable-line react/no-string-refs
42180
42130
  }
42181
42131
  }
42182
-
42183
42132
  _hintsVisible = () => {
42184
42133
  if (this.props.hintsVisible == null || this.props.hintsVisible === -1) {
42185
42134
  return this.props.hints.length;
@@ -42668,7 +42617,7 @@ class ServerItemRenderer extends React__namespace.Component {
42668
42617
  };
42669
42618
  const questionRenderer = /*#__PURE__*/React__namespace.createElement(context$1.Provider, {
42670
42619
  value: contextValue
42671
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({
42620
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({
42672
42621
  keypadElement: this.props.keypadElement,
42673
42622
  problemNum: this.props.problemNum,
42674
42623
  onInteractWithWidget: this.handleInteractWithWidget,
@@ -42717,7 +42666,7 @@ const ref = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__
42717
42666
  let {
42718
42667
  onRendered
42719
42668
  } = _ref;
42720
- return /*#__PURE__*/React__namespace.createElement(ServerItemRenderer, _extends({}, props, {
42669
+ return /*#__PURE__*/React__namespace.createElement(ServerItemRenderer, _extends__default["default"]({}, props, {
42721
42670
  onRendered: onRendered,
42722
42671
  ref: ref
42723
42672
  }));
@@ -44148,24 +44097,24 @@ const ScoringUtil = {
44148
44097
  */
44149
44098
 
44150
44099
  Object.defineProperty(exports, 'isFailure', {
44151
- enumerable: true,
44152
- get: function () { return perseusCore.isFailure; }
44100
+ enumerable: true,
44101
+ get: function () { return perseusCore.isFailure; }
44153
44102
  });
44154
44103
  Object.defineProperty(exports, 'isSuccess', {
44155
- enumerable: true,
44156
- get: function () { return perseusCore.isSuccess; }
44104
+ enumerable: true,
44105
+ get: function () { return perseusCore.isSuccess; }
44157
44106
  });
44158
44107
  Object.defineProperty(exports, 'parseAndMigratePerseusArticle', {
44159
- enumerable: true,
44160
- get: function () { return perseusCore.parseAndMigratePerseusArticle; }
44108
+ enumerable: true,
44109
+ get: function () { return perseusCore.parseAndMigratePerseusArticle; }
44161
44110
  });
44162
44111
  Object.defineProperty(exports, 'parseAndMigratePerseusItem', {
44163
- enumerable: true,
44164
- get: function () { return perseusCore.parseAndMigratePerseusItem; }
44112
+ enumerable: true,
44113
+ get: function () { return perseusCore.parseAndMigratePerseusItem; }
44165
44114
  });
44166
44115
  Object.defineProperty(exports, 'parsePerseusItem', {
44167
- enumerable: true,
44168
- get: function () { return perseusCore.parsePerseusItem; }
44116
+ enumerable: true,
44117
+ get: function () { return perseusCore.parsePerseusItem; }
44169
44118
  });
44170
44119
  exports.ApiOptions = ApiOptions;
44171
44120
  exports.ArticleRenderer = ArticleRenderer;