@khanacademy/perseus 54.1.0 → 55.0.1

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 +902 -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 +1036 -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,175 @@ 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
+ testId: "input-with-examples",
2292
+ onFocus: onFocus,
2293
+ onBlur: onBlur,
2294
+ onKeyDown: onKeyDown
2295
+ // @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>'.
2296
+ ,
2297
+ autoCorrect: "off",
2298
+ autoCapitalize: "off",
2299
+ autoComplete: "off"
2300
+ });
2301
+ }
2302
+ }
2303
+
2108
2304
  /**
2109
2305
  * A React context for keeping track of whether an component is fully
2110
2306
  * loaded. This is used to know when SvgImage and (Zoomable)TeX are finished
@@ -2667,9 +2863,11 @@ const Util = {
2667
2863
  // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
2668
2864
  // getDependencies() internally... simplify usage to just using a "singleton"
2669
2865
  // which get's the injected logger.
2866
+
2670
2867
  /**
2671
2868
  * Provides logging infrastructure for Perseus
2672
2869
  */
2870
+
2673
2871
  const Log = {
2674
2872
  // Logs a message
2675
2873
  log: (message, extra) => {
@@ -3343,40 +3541,40 @@ const articleMaxWidthInPx = 688;
3343
3541
  const articleMaxWidthTableInPx = 512;
3344
3542
 
3345
3543
  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
3544
+ __proto__: null,
3545
+ baseFontFamily: baseFontFamily,
3546
+ boldFontFamily: boldFontFamily,
3547
+ kaGreen: kaGreen,
3548
+ white: white,
3549
+ gray97: gray97,
3550
+ gray85: gray85,
3551
+ gray76: gray76,
3552
+ gray68: gray68,
3553
+ gray17: gray17,
3554
+ pureSmMin: pureSmMin$1,
3555
+ pureMdMin: pureMdMin$1,
3556
+ pureLgMin: pureLgMin$1,
3557
+ pureXlMin: pureXlMin$1,
3558
+ pureXsMax: pureXsMax$1,
3559
+ pureSmMax: pureSmMax$1,
3560
+ pureMdMax: pureMdMax$1,
3561
+ pureLgMax: pureLgMax$1,
3562
+ tableBackgroundAccent: tableBackgroundAccent,
3563
+ phoneMargin: phoneMargin,
3564
+ negativePhoneMargin: negativePhoneMargin,
3565
+ hintBorderWidth: hintBorderWidth,
3566
+ baseUnitPx: baseUnitPx,
3567
+ interactiveSizes: interactiveSizes$1,
3568
+ circleSize: circleSize$1,
3569
+ radioMarginWidth: radioMarginWidth$1,
3570
+ warningColor: warningColor,
3571
+ warningColorHover: warningColorHover,
3572
+ warningColorActive: warningColorActive,
3573
+ publishBlockingErrorColor: publishBlockingErrorColor,
3574
+ radioBorderColor: radioBorderColor$1,
3575
+ checkedColor: checkedColor$1,
3576
+ articleMaxWidthInPx: articleMaxWidthInPx,
3577
+ articleMaxWidthTableInPx: articleMaxWidthTableInPx
3380
3578
  });
3381
3579
 
3382
3580
  /**
@@ -3791,7 +3989,7 @@ var Raphael$1 = (function () {
3791
3989
  appendChild = "appendChild",
3792
3990
  apply = "apply",
3793
3991
  concat = "concat",
3794
- supportsTouch = ("createTouch" in doc),
3992
+ supportsTouch = "createTouch" in doc,
3795
3993
  E = "",
3796
3994
  S = " ",
3797
3995
  Str = String,
@@ -5396,10 +5594,10 @@ var Raphael$1 = (function () {
5396
5594
  });
5397
5595
  break;
5398
5596
  case "gradient":
5399
- (({
5597
+ ({
5400
5598
  circle: 1,
5401
5599
  ellipse: 1
5402
- })[has](o.type) || Str(value).charAt() != "r") && addGradientFill(node, value, o.paper);
5600
+ }[has](o.type) || Str(value).charAt() != "r") && addGradientFill(node, value, o.paper);
5403
5601
  break;
5404
5602
  case "opacity":
5405
5603
  if (attrs.gradient && !attrs[has]("stroke-opacity")) {
@@ -9232,8 +9430,10 @@ const labelDirections = {
9232
9430
  // is equivalent to [[-5, 5], [-10, 10]].
9233
9431
  // - A single magnitude to be used for both x and y. E.g. 10 is equivalent to
9234
9432
  // [[-10, 10], [-10, 10]]
9433
+
9235
9434
  // Overload normalizeRange to specify that it only returns undefined if its
9236
9435
  // argument is undefined.
9436
+
9237
9437
  // normalizeRange converts a RangeSpecifier into a pair of Intervals that give
9238
9438
  // the x and y ranges, respectively
9239
9439
  function normalizeRange(range) {
@@ -9425,7 +9625,7 @@ const GraphUtils = {
9425
9625
  clickBlockList = [],
9426
9626
  blockMouseTriggers = false,
9427
9627
  blockTouchTriggers = false,
9428
- eventCaptureSupported = ("addEventListener" in document),
9628
+ eventCaptureSupported = "addEventListener" in document,
9429
9629
  $document = $(document),
9430
9630
  nextTouchID = 1,
9431
9631
  lastTouchID = 0;
@@ -10120,7 +10320,6 @@ function scaledPolarRad(radius, radians) {
10120
10320
  return [radius * Math.cos(radians), radius * Math.sin(radians) * -1 // SVG flips y axis
10121
10321
  ];
10122
10322
  }
10123
-
10124
10323
  function scaledPolarDeg(radius, degrees) {
10125
10324
  const radians = degrees * Math.PI / 180;
10126
10325
  return scaledPolarRad(radius, radians);
@@ -11938,7 +12137,6 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11938
12137
  if (hideArrow) {
11939
12138
  return; // Don't draw anything!
11940
12139
  }
11941
-
11942
12140
  const handleVec = kmath.vector.subtract(handleCoord, rotatePoint.coord);
11943
12141
  const handlePolar = kmath.vector.polarDegFromCart(handleVec);
11944
12142
  const angle = handlePolar[1];
@@ -12902,7 +13100,6 @@ ___default["default"].extend(MovableAngle.prototype, {
12902
13100
  // clockwise to the first point
12903
13101
  coords[1] // the vertex parameter is last
12904
13102
  );
12905
-
12906
13103
  const clockwiseAngle = rawAngle + 360 % 360;
12907
13104
  return clockwiseAngle;
12908
13105
  },
@@ -14262,7 +14459,6 @@ class MovablePoint$5 {
14262
14459
  // We only update props here, because we want things on state to
14263
14460
  // be persistent, and updated appropriately in modify()
14264
14461
  ),
14265
-
14266
14462
  ...DEFAULT_PROPS$1
14267
14463
  };
14268
14464
  }
@@ -14314,11 +14510,11 @@ class MovablePoint$5 {
14314
14510
  const showTrashTooltip = () => {
14315
14511
  this._showTooltip(container => {
14316
14512
  // eslint-disable-next-line import/no-deprecated
14317
- render( /*#__PURE__*/React__namespace.createElement("span", {
14513
+ render(/*#__PURE__*/React__namespace.createElement("span", {
14318
14514
  style: {
14319
14515
  fontSize: "2em"
14320
14516
  }
14321
- }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconTrash, {
14517
+ }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconTrash, {
14322
14518
  style: {
14323
14519
  position: "static",
14324
14520
  color: KhanColors.INTERACTIVE,
@@ -15117,7 +15313,6 @@ const DEFAULT_PROPS = {
15117
15313
  // likewise
15118
15314
  labelStyle: null // likewise
15119
15315
  };
15120
-
15121
15316
  const DEFAULT_STATE = {
15122
15317
  added: false,
15123
15318
  hasMoved: false,
@@ -16019,7 +16214,7 @@ class ImageLoader extends React__namespace.Component {
16019
16214
  return (
16020
16215
  /*#__PURE__*/
16021
16216
  // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
16022
- React__namespace.createElement("img", _extends({
16217
+ React__namespace.createElement("img", _extends__default["default"]({
16023
16218
  // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- TODO(LEMS-2871): Address a11y error
16024
16219
  tabIndex: 0,
16025
16220
  src: staticUrl(src),
@@ -17793,7 +17988,7 @@ class TranslationLinter {
17793
17988
  * Preprocess TeX code to convert things that MathJax doesn't know how to handle
17794
17989
  * to things is does.
17795
17990
  */
17796
- var preprocessTex = (texCode => texCode
17991
+ var preprocessTex = texCode => texCode
17797
17992
  // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
17798
17993
  // support (yet) with \begin{aligned}...\end{aligned} which renders
17799
17994
  // the same is supported by KaTeX. It does the same for align*.
@@ -17803,7 +17998,7 @@ var preprocessTex = (texCode => texCode
17803
17998
  // "aligned").
17804
17999
  .replace(/\{align[*]?\}/g, "{aligned}")
17805
18000
  // Replace non-breaking spaces with regular spaces.
17806
- .replace(/[\u00a0]/g, " "));
18001
+ .replace(/[\u00a0]/g, " ");
17807
18002
 
17808
18003
  const {
17809
18004
  interactiveSizes
@@ -18062,29 +18257,29 @@ const isLintable = type => {
18062
18257
  };
18063
18258
 
18064
18259
  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
18260
+ __proto__: null,
18261
+ registerWidget: registerWidget,
18262
+ registerWidgets: registerWidgets,
18263
+ replaceWidget: replaceWidget,
18264
+ replaceDeprecatedWidgets: replaceDeprecatedWidgets,
18265
+ registerEditors: registerEditors,
18266
+ replaceEditor: replaceEditor,
18267
+ replaceDeprecatedEditors: replaceDeprecatedEditors,
18268
+ getWidget: getWidget,
18269
+ getWidgetExport: getWidgetExport,
18270
+ getEditor: getEditor,
18271
+ getTransform: getTransform,
18272
+ getVersion: getVersion,
18273
+ getVersionVector: getVersionVector,
18274
+ getPublicWidgets: getPublicWidgets,
18275
+ isAccessible: isAccessible,
18276
+ getAllWidgetTypes: getAllWidgetTypes,
18277
+ getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
18278
+ traverseChildWidgets: traverseChildWidgets$3,
18279
+ supportsStaticMode: supportsStaticMode,
18280
+ getStaticTransform: getStaticTransform,
18281
+ getTracking: getTracking,
18282
+ isLintable: isLintable
18088
18283
  });
18089
18284
 
18090
18285
  class WidgetContainer extends React__namespace.Component {
@@ -18119,7 +18314,6 @@ class WidgetContainer extends React__namespace.Component {
18119
18314
  /* eslint-enable react/no-did-mount-set-state */
18120
18315
  }
18121
18316
  }
18122
-
18123
18317
  UNSAFE_componentWillReceiveProps(nextProps) {
18124
18318
  if (this.props.type !== nextProps.type) {
18125
18319
  throw new Error("WidgetContainer can't change widget type; set a different " + "key instead to recreate the container.");
@@ -18242,7 +18436,7 @@ class WidgetContainer extends React__namespace.Component {
18242
18436
  }
18243
18437
  });
18244
18438
  }
18245
- }, /*#__PURE__*/React__namespace.createElement(WidgetType, _extends({}, this.state.widgetProps, {
18439
+ }, /*#__PURE__*/React__namespace.createElement(WidgetType, _extends__default["default"]({}, this.state.widgetProps, {
18246
18440
  linterContext: linterContext,
18247
18441
  containerSizeClass: this.state.sizeClass,
18248
18442
  ref: this.widgetRef
@@ -19111,7 +19305,7 @@ class Renderer extends React__namespace.Component {
19111
19305
  let {
19112
19306
  setAssetStatus
19113
19307
  } = _ref4;
19114
- return /*#__PURE__*/React__namespace.createElement(SvgImage, _extends({
19308
+ return /*#__PURE__*/React__namespace.createElement(SvgImage, _extends__default["default"]({
19115
19309
  setAssetStatus: setAssetStatus
19116
19310
  // @ts-expect-error - TS2322 - Type 'string | null | undefined' is not assignable to type 'string | undefined'.
19117
19311
  ,
@@ -19635,429 +19829,84 @@ const styles$A = {
19635
19829
  }
19636
19830
  };
19637
19831
 
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/
19832
+ const {
19833
+ captureScratchpadTouchStart: captureScratchpadTouchStart$2
19834
+ } = Util;
19835
+ // [LEMS-2411](Jan 2025) Third: This component has been moved to the NumericInput
19836
+ // folder as we are actively working towards removing the InputNumber widget.
19837
+ // This comment can be removed as part of LEMS-2411.
19776
19838
 
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
19839
  /**
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
- * ```
19840
+ * The InputWithExamples component is a child component of the NumericInput
19841
+ * and InputNumber components. It is responsible for rendering the UI elements
19842
+ * for the desktop versions of these widgets, and displays a tooltip with
19843
+ * examples of how to input the selected answer forms.
19879
19844
  */
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"
19845
+ const InputWithExamples = /*#__PURE__*/React.forwardRef((props, ref) => {
19846
+ // Desctructure the props to set default values
19847
+ const {
19848
+ shouldShowExamples = true,
19849
+ onFocus = () => {},
19850
+ onBlur = () => {},
19851
+ disabled = false,
19852
+ linterContext = PerseusLinter__namespace.linterContextDefault,
19853
+ className = ""
19854
+ } = props;
19855
+ const context = React__namespace.useContext(PerseusI18nContext);
19856
+ const inputRef = React__namespace.useRef(null);
19857
+ const [inputFocused, setInputFocused] = React__namespace.useState(false);
19858
+ const id = React.useId();
19859
+ const ariaId = `aria-for-${id}`;
19860
+ React.useImperativeHandle(ref, () => ({
19861
+ current: inputRef.current,
19862
+ focus: () => {
19863
+ if (inputRef.current) {
19864
+ inputRef.current.focus();
19865
+ }
19866
+ },
19867
+ blur: () => {
19868
+ if (inputRef.current) {
19869
+ inputRef.current.blur();
19962
19870
  }
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
19871
  }
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;
19872
+ }));
19873
+ const getInputClassName = () => {
19874
+ let inputClassName = ClassNames.INPUT;
19875
+ if (inputFocused) {
19876
+ inputClassName += " " + ClassNames.FOCUSED;
20035
19877
  }
20036
- if (this.props.className) {
20037
- className += " " + this.props.className;
19878
+ if (className) {
19879
+ inputClassName += " " + className;
20038
19880
  }
20039
- return className;
19881
+ return inputClassName;
19882
+ };
19883
+ const handleFocus = () => {
19884
+ onFocus();
19885
+ setInputFocused(true);
19886
+ };
19887
+ const handleBlur = () => {
19888
+ onBlur();
19889
+ setInputFocused(false);
20040
19890
  };
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")}`
19891
+ const renderInput = () => {
19892
+ // Generate the provided examples in simple language for screen readers.
19893
+ const examplesAria = props.shouldShowExamples ? `${props.examples[0]}
19894
+ ${props.examples.slice(1).join(", or\n")}`
20047
19895
  // @ts-expect-error TS2550: Property replaceAll does not exist on type string.
20048
- .replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ");
19896
+ .replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ") : "";
20049
19897
  const inputProps = {
20050
19898
  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,
19899
+ // If we have examples, we want to provide the aria-describedby attribute
19900
+ "aria-describedby": props.shouldShowExamples ? ariaId : undefined,
19901
+ ref: inputRef,
19902
+ className: getInputClassName(),
19903
+ labelText: props.labelText,
19904
+ value: props.value,
19905
+ onFocus: handleFocus,
19906
+ onBlur: handleBlur,
19907
+ disabled: disabled,
19908
+ style: props.style,
19909
+ onChange: props.onChange,
20061
19910
  onTouchStart: captureScratchpadTouchStart$2,
20062
19911
  autoCapitalize: "off",
20063
19912
  autoComplete: "off",
@@ -20071,158 +19920,32 @@ class InputWithExamples extends React__namespace.Component {
20071
19920
  }
20072
19921
  }, examplesAria));
20073
19922
  };
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()
19923
+ const renderTooltipContent = () => {
19924
+ return /*#__PURE__*/React__namespace.createElement(Tooltip.TooltipContent, null, /*#__PURE__*/React__namespace.createElement("div", {
19925
+ id: id,
19926
+ className: "input-with-examples-tooltip"
20131
19927
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
20132
19928
  content: examplesContent,
20133
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "input-with-examples"),
20134
- strings: this.context.strings
19929
+ linterContext: PerseusLinter__namespace.pushContextStack(linterContext, "input-with-examples"),
19930
+ strings: context.strings
20135
19931
  })));
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
19932
  };
20223
- };
19933
+ // Display the examples as a string when there are less than or equal to 2 examples.
19934
+ // Otherwise, display the examples as a list.
19935
+ const examplesContent = props.examples.length <= 2 ? props.examples.join(" ") : props.examples.map((example, index) => {
19936
+ return index === 0 && example.startsWith("**") ? `${example}\n` : `- ${example}`;
19937
+ }).join("\n");
20224
19938
 
20225
- const formExamples$1 = {
19939
+ // Display the examples when they are enabled (shouldShowExamples) and the input is focused.
19940
+ const showExamplesTooltip = shouldShowExamples && inputFocused;
19941
+ return /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
19942
+ content: renderTooltipContent(),
19943
+ opened: showExamplesTooltip,
19944
+ placement: "bottom"
19945
+ }, renderInput());
19946
+ });
19947
+
19948
+ const formExamples = {
20226
19949
  integer: function (options, strings) {
20227
19950
  return strings.integerExample;
20228
19951
  },
@@ -20334,7 +20057,7 @@ class InputNumber extends React__namespace.Component {
20334
20057
  } = this.context;
20335
20058
  const type = this.props.answerType;
20336
20059
  const forms = perseusScore.inputNumberAnswerTypes[type].forms.split(/\s*,\s*/);
20337
- const examples = ___default["default"].map(forms, form => formExamples$1[form](this.props, strings));
20060
+ const examples = ___default["default"].map(forms, form => formExamples[form](this.props, strings));
20338
20061
  return [strings.yourAnswer].concat(examples);
20339
20062
  }
20340
20063
  render() {
@@ -20448,7 +20171,11 @@ const getPromptJSON$r = (renderProps, userInput) => {
20448
20171
  };
20449
20172
  };
20450
20173
 
20451
- const formExamples = {
20174
+ /**
20175
+ * The full list of available strings for the numeric input widget,
20176
+ * based on whether the Content Creator has specified that the answer must be simplified.
20177
+ */
20178
+ const NumericExampleStrings = {
20452
20179
  integer: (form, strings) => strings.integerExample,
20453
20180
  proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
20454
20181
  improper: (form, strings) => form.simplify === "optional" ? strings.improperExample : strings.simplifiedImproperExample,
@@ -20456,9 +20183,182 @@ const formExamples = {
20456
20183
  decimal: (form, strings) => strings.decimalExample,
20457
20184
  pi: (form, strings) => strings.piExample
20458
20185
  };
20186
+
20187
+ /**
20188
+ * Generates the specific set of examples for the current question.
20189
+ * This string is shown as examples to the user in a tooltip.
20190
+ */
20191
+ const generateExamples = (answerForms, strings) => {
20192
+ // If the Content Creator has not specified any answer forms,
20193
+ // we do not need to show any examples.
20194
+ if (answerForms.length === 0) {
20195
+ return [];
20196
+ }
20197
+
20198
+ // Generate a list of the unique answer forms.
20199
+ const uniqueForms = getUniqueAnswerForms(answerForms);
20200
+
20201
+ // Generate the example strings for each unique form.
20202
+ const examples = uniqueForms.map(form => {
20203
+ return NumericExampleStrings[form.name](form, strings);
20204
+ });
20205
+
20206
+ // Add the "Your answer" string to the beginning of the examples list.
20207
+ return [strings.yourAnswer].concat(examples);
20208
+ };
20209
+
20210
+ /**
20211
+ * Determines whether to show examples of how to input the various supported answer forms.
20212
+ * We do not show examples if all forms are accepted or if no forms are accepted.
20213
+ */
20214
+ const shouldShowExamples = answerForms => {
20215
+ // If the Content Creator has not specified any answer forms,
20216
+ // we do not need to show any examples.
20217
+ if (answerForms.length === 0) {
20218
+ return false;
20219
+ }
20220
+
20221
+ // Generate a list of the unique names of the selected answer forms.
20222
+ const answerFormNames = getUniqueAnswerForms(answerForms).map(form => form.name);
20223
+
20224
+ // If all forms are accepted, we do not need to show any examples.
20225
+ const allFormsAccepted = answerFormNames.length >= Object.keys(NumericExampleStrings).length;
20226
+ return !allFormsAccepted;
20227
+ };
20228
+
20229
+ /**
20230
+ * uniqueAnswerForms takes a list of answer forms and returns a list of unique
20231
+ * answer forms. This is useful for ensuring that we don't show duplicate examples
20232
+ * to the user.
20233
+ */
20234
+ const getUniqueAnswerForms = function (list) {
20235
+ // We use a Set to keep track of the forms we've already seen.
20236
+ const foundForms = new Set();
20237
+ return list.filter(form => {
20238
+ // If we've already seen this form, skip it.
20239
+ if (foundForms.has(form.name)) {
20240
+ return false;
20241
+ }
20242
+ // Otherwise, add it to the set and return true.
20243
+ foundForms.add(form.name);
20244
+ return true;
20245
+ });
20246
+ };
20247
+
20248
+ /**
20249
+ * Takes a list of lists of answer forms, and returns a list of the forms
20250
+ * in each of these lists in the same order that they're listed in the
20251
+ * `formExamples` forms from above.
20252
+ */
20253
+ const unionAnswerForms = function (answerFormsList) {
20254
+ // Pull out all of the forms from the different lists.
20255
+ const allForms = answerFormsList.flat();
20256
+ // Pull out the unique forms using getUniqueAnswerForms.
20257
+ const uniqueForms = getUniqueAnswerForms(allForms);
20258
+ // Sort them by the order they appear in the `formExamples` list.
20259
+ const formExampleKeys = Object.keys(NumericExampleStrings);
20260
+ return uniqueForms.sort((a, b) => {
20261
+ return formExampleKeys.indexOf(a.name) - formExampleKeys.indexOf(b.name);
20262
+ });
20263
+ };
20264
+
20265
+ /**
20266
+ * The NumericInputComponent is a child component of the NumericInput class
20267
+ * component. It is responsible for rendering the UI elements of the Numeric
20268
+ * Input widget.
20269
+ */
20270
+ const NumericInputComponent = /*#__PURE__*/React.forwardRef((props, ref) => {
20271
+ const context = React.useContext(PerseusI18nContext);
20272
+ const inputRef = React.useRef(null);
20273
+ const [isFocused, setIsFocused] = React.useState(false);
20274
+
20275
+ // Pass the focus and blur methods to the Numeric Input Class component
20276
+ React.useImperativeHandle(ref, () => ({
20277
+ current: inputRef.current,
20278
+ focus: () => {
20279
+ if (inputRef.current) {
20280
+ inputRef.current?.focus();
20281
+ setIsFocused(true);
20282
+ }
20283
+ },
20284
+ blur: () => {
20285
+ if (inputRef.current) {
20286
+ inputRef.current?.blur();
20287
+ setIsFocused(false);
20288
+ }
20289
+ }
20290
+ }));
20291
+ const handleChange = (newValue, cb) => {
20292
+ props.onChange({
20293
+ currentValue: newValue
20294
+ }, cb);
20295
+ props.trackInteraction();
20296
+ };
20297
+ const handleFocus = () => {
20298
+ props.onFocus([]);
20299
+ setIsFocused(true);
20300
+ };
20301
+ const handleBlur = () => {
20302
+ props.onBlur([]);
20303
+ setIsFocused(false);
20304
+ };
20305
+
20306
+ // Styles for the InputWithExamples
20307
+ const styles = aphrodite.StyleSheet.create({
20308
+ inputWithExamples: {
20309
+ borderRadius: "3px",
20310
+ borderWidth: isFocused ? "2px" : "1px",
20311
+ display: "inline-block",
20312
+ fontFamily: `Symbola, "Times New Roman", serif`,
20313
+ fontSize: "18px",
20314
+ height: "32px",
20315
+ lineHeight: "18px",
20316
+ padding: isFocused ? "4px" : "4px 5px",
20317
+ textAlign: props.rightAlign ? "right" : "left",
20318
+ width: props.size === "small" ? 40 : 80
20319
+ }
20320
+ });
20321
+
20322
+ // (mobile-only) If the custom keypad is enabled, use the SimpleKeypadInput component
20323
+ if (props.apiOptions.customKeypad) {
20324
+ const alignmentClass = props.rightAlign ? "perseus-input-right-align" : undefined;
20325
+ return /*#__PURE__*/React__namespace.createElement("div", {
20326
+ className: alignmentClass
20327
+ }, /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput, {
20328
+ ref: inputRef,
20329
+ value: props.currentValue,
20330
+ keypadElement: props.keypadElement,
20331
+ onChange: handleChange,
20332
+ onFocus: handleFocus,
20333
+ onBlur: handleBlur
20334
+ }));
20335
+ }
20336
+ // (desktop-only) Otherwise, use the InputWithExamples component
20337
+ return /*#__PURE__*/React__namespace.createElement(InputWithExamples, {
20338
+ ref: inputRef,
20339
+ value: props.currentValue,
20340
+ onChange: handleChange,
20341
+ labelText: props.labelText,
20342
+ examples: generateExamples(props.answerForms, context.strings),
20343
+ shouldShowExamples: shouldShowExamples(props.answerForms),
20344
+ onFocus: handleFocus,
20345
+ onBlur: handleBlur,
20346
+ id: props.widgetId,
20347
+ disabled: props.apiOptions.readOnly,
20348
+ style: styles.inputWithExamples
20349
+ });
20350
+ });
20351
+
20352
+ /**
20353
+ * The NumericInput widget is a numeric input field that supports a variety of
20354
+ * answer forms, including integers, decimals, fractions, and mixed numbers.
20355
+ *
20356
+ * [Jan 2025] We're currenly migrating from class-based components to functional
20357
+ * components. While we cannot fully migrate this component yet, we can start
20358
+ * by using the functional component for the rendering the UI of the widget.
20359
+ */
20459
20360
  class NumericInput extends React__namespace.Component {
20460
- static contextType = PerseusI18nContext;
20461
- inputRef;
20361
+ inputRef = /*#__PURE__*/React__namespace.createRef();
20462
20362
  static defaultProps = {
20463
20363
  currentValue: "",
20464
20364
  size: "normal",
@@ -20474,51 +20374,15 @@ class NumericInput extends React__namespace.Component {
20474
20374
  currentValue: props.currentValue
20475
20375
  };
20476
20376
  }
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
20377
  focus = () => {
20514
- this.inputRef?.focus();
20378
+ this.inputRef.current?.focus();
20515
20379
  return true;
20516
20380
  };
20517
20381
  focusInputPath = () => {
20518
- this.inputRef?.focus();
20382
+ this.inputRef.current?.focus();
20519
20383
  };
20520
20384
  blurInputPath = () => {
20521
- this.inputRef?.blur();
20385
+ this.inputRef.current?.blur();
20522
20386
  };
20523
20387
  getInputPaths = () => {
20524
20388
  // The widget itself is an input, so we return a single empty list to
@@ -20526,152 +20390,57 @@ class NumericInput extends React__namespace.Component {
20526
20390
  /* c8 ignore next */
20527
20391
  return [[]];
20528
20392
  };
20393
+
20394
+ /**
20395
+ * Sets the value of the input at the given path.
20396
+ */
20529
20397
  setInputValue = (path, newValue, cb) => {
20530
- /* c8 ignore next */
20531
20398
  this.props.onChange({
20532
20399
  currentValue: newValue
20533
20400
  }, cb);
20534
20401
  };
20402
+
20403
+ /**
20404
+ * Returns the value the user has currently input for this widget.
20405
+ */
20535
20406
  getUserInput() {
20536
20407
  return NumericInput.getUserInputFromProps(this.props);
20537
20408
  }
20409
+
20410
+ /**
20411
+ * Returns the JSON representation of the prompt for this widget.
20412
+ * This is used by the AI to determine the prompt for the widget.
20413
+ */
20538
20414
  getPromptJSON() {
20539
20415
  return getPromptJSON$r(this.props, this.getUserInput());
20540
20416
  }
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
20417
  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
- });
20418
+ return /*#__PURE__*/React__namespace.createElement(NumericInputComponent, _extends__default["default"]({}, this.props, {
20419
+ ref: this.inputRef
20420
+ }));
20620
20421
  }
20621
20422
  }
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
- };
20423
+ // Transforms the widget options to the props used to render the widget.
20660
20424
  const propsTransform = function (widgetOptions) {
20661
- const rendererProps = ___default["default"].extend(___default["default"].omit(widgetOptions, "answers"), {
20425
+ // Omit the answers from the widget options since they are
20426
+ // not needed for rendering the widget.
20427
+ const {
20428
+ answers: _,
20429
+ ...rendererProps
20430
+ } = {
20431
+ ...widgetOptions,
20662
20432
  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 => {
20433
+ // Filter out the correct answers and map them to the answer forms
20434
+ // so that we can generate the examples for the widget.
20435
+ widgetOptions.answers.filter(answer => answer.status === "correct").map(answer => {
20436
+ return (answer.answerForms || []).map(form => {
20668
20437
  return {
20669
20438
  simplify: answer.simplify,
20670
20439
  name: form
20671
20440
  };
20672
20441
  });
20673
20442
  }))
20674
- });
20443
+ };
20675
20444
  return rendererProps;
20676
20445
  };
20677
20446
  var NumericInput$1 = {
@@ -20686,23 +20455,11 @@ var NumericInput$1 = {
20686
20455
  getOneCorrectAnswerFromRubric(rubric) {
20687
20456
  const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
20688
20457
  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'.
20458
+ // Either get the first answer form or default to decimal
20459
+ const format = answer.answerForms && answer.answerForms[0] ? answer.answerForms[0] : "decimal";
20701
20460
  let answerString = kmath.KhanMath.toNumericString(answer.value, format);
20702
20461
  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);
20462
+ answerString += " \u00B1 " + kmath.KhanMath.toNumericString(answer.maxError, format);
20706
20463
  }
20707
20464
  return answerString;
20708
20465
  });
@@ -23237,6 +22994,7 @@ const BASE_ICON_SIZE = 10;
23237
22994
  // If you pass in an object you need to specify an SVG path to
23238
22995
  // render along with the width and height of the path (to ensure
23239
22996
  // that it gets scaled correctly).
22997
+
23240
22998
  /**
23241
22999
  * An SVG Icon
23242
23000
  *
@@ -23494,7 +23252,7 @@ function ChoiceInner(props) {
23494
23252
  return /*#__PURE__*/React__namespace.createElement("span", null, letter);
23495
23253
  }
23496
23254
  if (correct) {
23497
- return /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconCheck, {
23255
+ return /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconCheck, {
23498
23256
  style: {
23499
23257
  position: "relative",
23500
23258
  top: -1
@@ -23950,7 +23708,7 @@ const styles$p = aphrodite.StyleSheet.create({
23950
23708
  }
23951
23709
  }
23952
23710
  });
23953
- var Choice$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__*/React__namespace.createElement(Choice, _extends({}, props, {
23711
+ var Choice$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__*/React__namespace.createElement(Choice, _extends__default["default"]({}, props, {
23954
23712
  forwardedRef: ref
23955
23713
  })));
23956
23714
 
@@ -23984,11 +23742,11 @@ const ChoiceNoneAbove = function (_ref) {
23984
23742
  strings: strings
23985
23743
  })
23986
23744
  };
23987
- return /*#__PURE__*/React__namespace.createElement(Choice$1, _extends({}, choiceProps, {
23745
+ return /*#__PURE__*/React__namespace.createElement(Choice$1, _extends__default["default"]({}, choiceProps, {
23988
23746
  ref: forwardedRef
23989
23747
  }));
23990
23748
  };
23991
- var ChoiceNoneAbove$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__*/React__namespace.createElement(ChoiceNoneAbove, _extends({}, props, {
23749
+ var ChoiceNoneAbove$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__*/React__namespace.createElement(ChoiceNoneAbove, _extends__default["default"]({}, props, {
23992
23750
  forwardedRef: ref
23993
23751
  })));
23994
23752
 
@@ -24238,7 +23996,7 @@ const BaseRadio = function (_ref) {
24238
23996
  className: className,
24239
23997
  onClick: clickHandler,
24240
23998
  onTouchStart: labelWrap ? undefined : captureScratchpadTouchStart$1
24241
- }, /*#__PURE__*/React__namespace.createElement(Element, _extends({}, elementProps, {
23999
+ }, /*#__PURE__*/React__namespace.createElement(Element, _extends__default["default"]({}, elementProps, {
24242
24000
  ref: ref
24243
24001
  })))
24244
24002
  );
@@ -24330,7 +24088,6 @@ const styles$o = aphrodite.StyleSheet.create({
24330
24088
  paddingLeft: phoneMargin
24331
24089
  // paddingRight is handled by responsiveFieldset
24332
24090
  },
24333
-
24334
24091
  responsiveFieldset: {
24335
24092
  paddingRight: phoneMargin,
24336
24093
  minWidth: "auto"
@@ -24574,7 +24331,6 @@ class Radio$1 extends React__namespace.Component {
24574
24331
  );
24575
24332
  }
24576
24333
  };
24577
-
24578
24334
  render() {
24579
24335
  const {
24580
24336
  choices
@@ -24751,7 +24507,7 @@ const _choiceTransform = (widgetOptions, strings, problemNum) => {
24751
24507
  // 1) we randomize the order first
24752
24508
  _maybeRandomize(choices)));
24753
24509
  };
24754
- const transform = (widgetOptions, strings, problemNum) => {
24510
+ const transform$1 = (widgetOptions, strings, problemNum) => {
24755
24511
  const choices = _choiceTransform(widgetOptions, strings, problemNum);
24756
24512
  const numCorrect = ___default["default"].reduce(widgetOptions.choices, function (memo, choice) {
24757
24513
  return choice.correct ? memo + 1 : memo;
@@ -24777,8 +24533,8 @@ var Radio = {
24777
24533
  displayName: "Radio / Multiple choice",
24778
24534
  accessible: true,
24779
24535
  widget: Radio$1,
24780
- transform: transform,
24781
- staticTransform: transform,
24536
+ transform: transform$1,
24537
+ staticTransform: transform$1,
24782
24538
  version: perseusCore.radioLogic.version,
24783
24539
  propUpgrades: perseusCore.radioLogic.widgetOptionsUpgrades,
24784
24540
  isLintable: true
@@ -25360,6 +25116,7 @@ var Definition$1 = {
25360
25116
 
25361
25117
  // There are no required props, but this component can receive props from any
25362
25118
  // deprecated widget
25119
+
25363
25120
  class DeprecatedStandin extends React__default["default"].Component {
25364
25121
  static contextType = PerseusI18nContext;
25365
25122
  // this just helps with TS weak typing when a Widget
@@ -25480,20 +25237,20 @@ class Dropdown extends React__namespace.Component {
25480
25237
  }, children)));
25481
25238
  }
25482
25239
  }
25483
- const optionsTransform = widgetOptions => {
25240
+ function transform(widgetOptions) {
25484
25241
  return {
25485
25242
  placeholder: widgetOptions.placeholder,
25486
25243
  visibleLabel: widgetOptions.visibleLabel,
25487
25244
  ariaLabel: widgetOptions.ariaLabel,
25488
25245
  choices: widgetOptions.choices.map(choice => choice.content)
25489
25246
  };
25490
- };
25247
+ }
25491
25248
  var Dropdown$1 = {
25492
25249
  name: "dropdown",
25493
25250
  displayName: "Drop down",
25494
25251
  accessible: true,
25495
25252
  widget: Dropdown,
25496
- transform: optionsTransform
25253
+ transform
25497
25254
  };
25498
25255
 
25499
25256
  const getPromptJSON$j = renderProps => {
@@ -25569,7 +25326,6 @@ class Explanation extends React__namespace.Component {
25569
25326
  // adds space between the right border and any text that follows
25570
25327
  paddingLeft: "2px" // adds space between the left border and the button text
25571
25328
  };
25572
-
25573
25329
  const labelStyle = {
25574
25330
  fontSize: "18px",
25575
25331
  lineHeight: "inherit",
@@ -25725,7 +25481,7 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25725
25481
  fontSize: 28,
25726
25482
  color: wonderBlocksTokens.color.green
25727
25483
  }
25728
- }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconStar, {
25484
+ }, /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconStar, {
25729
25485
  style: {
25730
25486
  marginBottom: 5
25731
25487
  }
@@ -25918,7 +25674,7 @@ class GradedGroup extends React__namespace.Component {
25918
25674
  if (this.state.status === GRADING_STATUSES.correct) {
25919
25675
  // TODO(jeremy): update to a WB colour
25920
25676
  // @ts-expect-error - TS2322 - Type 'Element' is not assignable to type 'null'.
25921
- icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconOk, {
25677
+ icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconOk, {
25922
25678
  style: {
25923
25679
  color: "#526f03"
25924
25680
  }
@@ -25927,7 +25683,7 @@ class GradedGroup extends React__namespace.Component {
25927
25683
  } else if (this.state.status === GRADING_STATUSES.incorrect) {
25928
25684
  // TODO(jeremy): update to a WB colour
25929
25685
  // @ts-expect-error - TS2322 - Type 'Element' is not assignable to type 'null'.
25930
- icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconRemove, {
25686
+ icon = /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends__default["default"]({}, iconRemove, {
25931
25687
  style: {
25932
25688
  color: "#ff5454"
25933
25689
  }
@@ -25954,7 +25710,7 @@ class GradedGroup extends React__namespace.Component {
25954
25710
  className: classes
25955
25711
  }, !!this.props.title && /*#__PURE__*/React__namespace.createElement("div", {
25956
25712
  className: aphrodite.css(styles$i.title)
25957
- }, this.props.title), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props, {
25713
+ }, this.props.title), /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({}, this.props, {
25958
25714
  ref: this.rendererRef,
25959
25715
  apiOptions: {
25960
25716
  ...apiOptions,
@@ -25997,7 +25753,7 @@ class GradedGroup extends React__namespace.Component {
25997
25753
  showHint: false
25998
25754
  });
25999
25755
  }
26000
- }, this.context.strings.hideExplanation), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props.hint, {
25756
+ }, this.context.strings.hideExplanation), /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({}, this.props.hint, {
26001
25757
  ref: this.hintRendererRef,
26002
25758
  apiOptions: apiOptions,
26003
25759
  linterContext: this.props.linterContext,
@@ -26222,7 +25978,7 @@ class GradedGroupSet extends React__namespace.Component {
26222
25978
  // TODO(jeremy): Don't spread this.props, instead
26223
25979
  // pass in all props GradedGroup needs explicilty
26224
25980
  // @ts-expect-error - TS2769 - No overload matches this call.
26225
- React__namespace.createElement(GradedGroup, _extends({
25981
+ React__namespace.createElement(GradedGroup, _extends__default["default"]({
26226
25982
  key: i
26227
25983
  }, this.props, gradedGroup, {
26228
25984
  inGradedGroupSet: false,
@@ -26251,7 +26007,7 @@ class GradedGroupSet extends React__namespace.Component {
26251
26007
  onChangeCurrentGroup: currentGroup => this.setState({
26252
26008
  currentGroup
26253
26009
  })
26254
- })), /*#__PURE__*/React__namespace.createElement(GradedGroup, _extends({
26010
+ })), /*#__PURE__*/React__namespace.createElement(GradedGroup, _extends__default["default"]({
26255
26011
  key: this.state.currentGroup
26256
26012
  // @ts-expect-error - TS2322 - Type 'GradedGroup | null' is not assignable to type 'GradedGroup'.
26257
26013
  // Type 'null' is not assignable to type 'GradedGroup'.
@@ -26647,7 +26403,7 @@ class FunctionGrapher extends React__namespace.Component {
26647
26403
  }
26648
26404
  const functionProps = model.getPropsForCoeffs(coeffs, xRange);
26649
26405
  const Movable = movableTypeToComponent[model.movable];
26650
- return /*#__PURE__*/React__namespace.createElement(Movable, _extends({}, functionProps, {
26406
+ return /*#__PURE__*/React__namespace.createElement(Movable, _extends__default["default"]({}, functionProps, {
26651
26407
  key: this.props.model.url,
26652
26408
  range: xRange,
26653
26409
  style: style
@@ -26780,7 +26536,7 @@ class FunctionGrapher extends React__namespace.Component {
26780
26536
  width: box[0],
26781
26537
  height: box[1]
26782
26538
  }
26783
- }, image, /*#__PURE__*/React__namespace.createElement(Graphie, _extends({}, this.props.graph, {
26539
+ }, image, /*#__PURE__*/React__namespace.createElement(Graphie, _extends__default["default"]({}, this.props.graph, {
26784
26540
  setDrawingAreaAvailable: this.props.setDrawingAreaAvailable
26785
26541
  }), this.props.model && this.renderPlot(), this.props.model && this.renderAsymptote(), this.props.model && points)));
26786
26542
  }
@@ -27924,7 +27680,6 @@ class Interaction extends React__namespace.Component {
27924
27680
  }
27925
27681
  });
27926
27682
  }
27927
-
27928
27683
  if (element.type === "parametric") {
27929
27684
  const fn = t => {
27930
27685
  return [
@@ -27977,7 +27732,6 @@ class Interaction extends React__namespace.Component {
27977
27732
  }
27978
27733
  });
27979
27734
  }
27980
-
27981
27735
  if (element.type === "label") {
27982
27736
  const coord = [
27983
27737
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -28309,7 +28063,6 @@ function scaleCommandBy(scaleFactor) {
28309
28063
  command.args[6] * scaleFactor // end y
28310
28064
  ]
28311
28065
  };
28312
-
28313
28066
  default:
28314
28067
  return {
28315
28068
  ...command,
@@ -29069,7 +28822,6 @@ const styles$f = aphrodite.StyleSheet.create({
29069
28822
  userSelect: "none",
29070
28823
  borderWidth: 0 // <button> gives us a border by default
29071
28824
  },
29072
-
29073
28825
  // We want to render the element in-place in our fixtures, so allow us to
29074
28826
  // render in the bottom corner in production and in-place in testing.
29075
28827
  hudFixedPosition: {
@@ -29099,7 +28851,6 @@ const styles$f = aphrodite.StyleSheet.create({
29099
28851
  ":hover": {
29100
28852
  backgroundColor: "#a1a5a9" // in between those two grays
29101
28853
  },
29102
-
29103
28854
  ":active": {
29104
28855
  backgroundColor: gray68
29105
28856
  }
@@ -29144,7 +28895,6 @@ class InfoTip extends React__namespace.Component {
29144
28895
  });
29145
28896
  /* eslint-enable react/no-did-mount-set-state */
29146
28897
  }
29147
-
29148
28898
  render() {
29149
28899
  if (this.state.didMount) {
29150
28900
  return /*#__PURE__*/React__namespace.createElement(InfoTipBase, this.props);
@@ -29410,7 +29160,6 @@ class NumberInput extends React__namespace.Component {
29410
29160
  if (val !== Math.floor(val)) {
29411
29161
  return; // bail if not an integer
29412
29162
  }
29413
-
29414
29163
  if (e.key === "ArrowUp") {
29415
29164
  val = val + 1;
29416
29165
  } else if (e.key === "ArrowDown") {
@@ -29443,7 +29192,7 @@ class NumberInput extends React__namespace.Component {
29443
29192
  useArrowKeys,
29444
29193
  ...restProps
29445
29194
  } = this.props;
29446
- return /*#__PURE__*/React__namespace.createElement("input", _extends({}, restProps, {
29195
+ return /*#__PURE__*/React__namespace.createElement("input", _extends__default["default"]({}, restProps, {
29447
29196
  className: classes,
29448
29197
  type: "text",
29449
29198
  ref: this.inputRef,
@@ -29488,7 +29237,7 @@ class RangeInput extends React__namespace.Component {
29488
29237
  const checkValidity = this.props.checkValidity || truth;
29489
29238
  return /*#__PURE__*/React__namespace.createElement("div", {
29490
29239
  className: "range-input"
29491
- }, /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
29240
+ }, /*#__PURE__*/React__namespace.createElement(NumberInput, _extends__default["default"]({}, this.props, {
29492
29241
  value: value[0],
29493
29242
  checkValidity: val => checkValidity([val, value[1]])
29494
29243
  // eslint-disable-next-line react/jsx-no-bind
@@ -29496,7 +29245,7 @@ class RangeInput extends React__namespace.Component {
29496
29245
  onChange: this.onChange.bind(this, 0),
29497
29246
  placeholder: this.props.placeholder[0],
29498
29247
  allowPiTruncation: this.props.allowPiTruncation
29499
- })), /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
29248
+ })), /*#__PURE__*/React__namespace.createElement(NumberInput, _extends__default["default"]({}, this.props, {
29500
29249
  value: value[1],
29501
29250
  checkValidity: val => checkValidity([value[0], val])
29502
29251
  // eslint-disable-next-line react/jsx-no-bind
@@ -29509,20 +29258,20 @@ class RangeInput extends React__namespace.Component {
29509
29258
  }
29510
29259
 
29511
29260
  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
29261
+ __proto__: null,
29262
+ ButtonGroup: ButtonGroup,
29263
+ Graphie: Graphie,
29264
+ HUD: HUD,
29265
+ Icon: Icon,
29266
+ InfoTip: InfoTip,
29267
+ InlineIcon: InlineIcon,
29268
+ MathInput: MathInput,
29269
+ MultiButtonGroup: MultiButtonGroup,
29270
+ NumberInput: NumberInput,
29271
+ RangeInput: RangeInput,
29272
+ SvgImage: SvgImage,
29273
+ TextInput: TextInput,
29274
+ TextListEditor: TextListEditor
29526
29275
  });
29527
29276
 
29528
29277
  /**
@@ -29598,17 +29347,17 @@ function GraphLockedLabelsLayer(props) {
29598
29347
  } = props;
29599
29348
  return lockedFigures.map((figure, i) => {
29600
29349
  if (figure.type === "label") {
29601
- return /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29350
+ return /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends__default["default"]({
29602
29351
  key: `label-${i}`
29603
29352
  }, figure));
29604
29353
  }
29605
29354
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, {
29606
29355
  key: i
29607
- }, figure.labels?.map((label, j) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29356
+ }, figure.labels?.map((label, j) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends__default["default"]({
29608
29357
  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({
29358
+ }, 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
29359
  key: `locked-figure-${i}-point-1-label-${k}`
29611
- }, label))), figure.points[1].labels?.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29360
+ }, label))), figure.points[1].labels?.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends__default["default"]({
29612
29361
  key: `locked-figure-${i}-point-2-label-${k}`
29613
29362
  }, label)))));
29614
29363
  });
@@ -29654,9 +29403,9 @@ const LockedFunction = props => {
29654
29403
  const {
29655
29404
  color,
29656
29405
  strokeStyle,
29657
- directionalAxis
29406
+ directionalAxis,
29407
+ domain
29658
29408
  } = 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
29409
  const plotProps = {
29661
29410
  color: perseusCore.lockedFigureColors[color],
29662
29411
  style: strokeStyle,
@@ -29676,11 +29425,11 @@ const LockedFunction = props => {
29676
29425
  "aria-label": hasAria ? props.ariaLabel : undefined,
29677
29426
  "aria-hidden": !hasAria,
29678
29427
  role: "img"
29679
- }, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends({
29428
+ }, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends__default["default"]({
29680
29429
  y: x => equation.eval({
29681
29430
  x
29682
29431
  })
29683
- }, plotProps)), directionalAxis === "y" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfY, _extends({
29432
+ }, plotProps)), directionalAxis === "y" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfY, _extends__default["default"]({
29684
29433
  x: y => equation.eval({
29685
29434
  y
29686
29435
  })
@@ -30254,28 +30003,28 @@ const GraphLockedLayer = props => {
30254
30003
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lockedFigures.map((figure, index) => {
30255
30004
  switch (figure.type) {
30256
30005
  case "point":
30257
- return /*#__PURE__*/React__namespace.createElement(LockedPoint, _extends({
30006
+ return /*#__PURE__*/React__namespace.createElement(LockedPoint, _extends__default["default"]({
30258
30007
  key: `point-${index}`
30259
30008
  }, figure));
30260
30009
  case "line":
30261
- return /*#__PURE__*/React__namespace.createElement(LockedLine, _extends({
30010
+ return /*#__PURE__*/React__namespace.createElement(LockedLine, _extends__default["default"]({
30262
30011
  key: `line-${index}`,
30263
30012
  range: props.range
30264
30013
  }, figure));
30265
30014
  case "vector":
30266
- return /*#__PURE__*/React__namespace.createElement(LockedVector, _extends({
30015
+ return /*#__PURE__*/React__namespace.createElement(LockedVector, _extends__default["default"]({
30267
30016
  key: `vector-${index}`
30268
30017
  }, figure));
30269
30018
  case "ellipse":
30270
- return /*#__PURE__*/React__namespace.createElement(LockedEllipse, _extends({
30019
+ return /*#__PURE__*/React__namespace.createElement(LockedEllipse, _extends__default["default"]({
30271
30020
  key: `ellipse-${index}`
30272
30021
  }, figure));
30273
30022
  case "polygon":
30274
- return /*#__PURE__*/React__namespace.createElement(LockedPolygon, _extends({
30023
+ return /*#__PURE__*/React__namespace.createElement(LockedPolygon, _extends__default["default"]({
30275
30024
  key: `polygon-${index}`
30276
30025
  }, figure));
30277
30026
  case "function":
30278
- return /*#__PURE__*/React__namespace.createElement(LockedFunction, _extends({
30027
+ return /*#__PURE__*/React__namespace.createElement(LockedFunction, _extends__default["default"]({
30279
30028
  key: `function-${index}`
30280
30029
  }, figure));
30281
30030
  case "label":
@@ -30506,7 +30255,7 @@ const TextLabel = _ref => {
30506
30255
  children,
30507
30256
  ...rest
30508
30257
  } = _ref;
30509
- return /*#__PURE__*/React__default["default"].createElement(mafs.Text, _extends({
30258
+ return /*#__PURE__*/React__default["default"].createElement(mafs.Text, _extends__default["default"]({
30510
30259
  size: 16,
30511
30260
  svgTextProps: {
30512
30261
  filter: "url(#background)",
@@ -31217,6 +30966,9 @@ const MovableLine = props => {
31217
30966
  onMoveLine = () => {},
31218
30967
  onMovePoint = () => {}
31219
30968
  } = props;
30969
+ const {
30970
+ snapStep
30971
+ } = useGraphConfig();
31220
30972
 
31221
30973
  // Aria live states for (0) point 1, (1) point 2, and (2) grab handle.
31222
30974
  // When moving an element, set its aria live to "polite" and the others
@@ -31248,7 +31000,8 @@ const MovableLine = props => {
31248
31000
  onMove: p => {
31249
31001
  setAriaLives(["polite", "off", "off"]);
31250
31002
  onMovePoint(0, p);
31251
- }
31003
+ },
31004
+ constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 0)
31252
31005
  });
31253
31006
  const {
31254
31007
  visiblePoint: visiblePoint2,
@@ -31263,7 +31016,8 @@ const MovableLine = props => {
31263
31016
  onMove: p => {
31264
31017
  setAriaLives(["off", "polite", "off"]);
31265
31018
  onMovePoint(1, p);
31266
- }
31019
+ },
31020
+ constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 1)
31267
31021
  });
31268
31022
  const line = /*#__PURE__*/React__namespace.createElement(Line$1, {
31269
31023
  ariaLabel: ariaLabels?.grabHandleAriaLabel,
@@ -31369,6 +31123,34 @@ const Line$1 = props => {
31369
31123
  color: stroke
31370
31124
  }));
31371
31125
  };
31126
+ const getMovableLineKeyboardConstraint = (line, snapStep, pointIndex) => {
31127
+ // Separate the two points into their own variables, and determine which point is being moved
31128
+ const coordToBeMoved = line[pointIndex];
31129
+ const otherPoint = line[1 - pointIndex];
31130
+
31131
+ // Create a helper function that moves the point and then checks
31132
+ // if it overlaps with the other point in the line after the move.
31133
+ const movePointWithConstraint = moveFunc => {
31134
+ // Move the point
31135
+ let movedCoord = moveFunc(coordToBeMoved);
31136
+ // If the moved point overlaps with the other point in the line,
31137
+ // move the point again.
31138
+ if (mafs.vec.dist(movedCoord, otherPoint) === 0) {
31139
+ movedCoord = moveFunc(movedCoord);
31140
+ }
31141
+ return movedCoord;
31142
+ };
31143
+
31144
+ // Check if the new point will overlap the other point.
31145
+ // If it will, we need to snap the point to the left or right an additional
31146
+ // snapStep to avoid overlap.
31147
+ return {
31148
+ up: movePointWithConstraint(coord => mafs.vec.add(coord, [0, snapStep[1]])),
31149
+ down: movePointWithConstraint(coord => mafs.vec.sub(coord, [0, snapStep[1]])),
31150
+ left: movePointWithConstraint(coord => mafs.vec.sub(coord, [snapStep[0], 0])),
31151
+ right: movePointWithConstraint(coord => mafs.vec.add(coord, [snapStep[0], 0]))
31152
+ };
31153
+ };
31372
31154
  function trimRange(range, graphDimensionsInPixels) {
31373
31155
  const pixelsToTrim = 4;
31374
31156
  const [xRange, yRange] = range;
@@ -31725,7 +31507,8 @@ function CircleGraph(props) {
31725
31507
  } = props;
31726
31508
  const {
31727
31509
  center,
31728
- radiusPoint
31510
+ radiusPoint,
31511
+ snapStep
31729
31512
  } = graphState;
31730
31513
  const {
31731
31514
  strings,
@@ -31788,7 +31571,8 @@ function CircleGraph(props) {
31788
31571
  onMove: newRadiusPoint => {
31789
31572
  setRadiusPointAriaLive("polite");
31790
31573
  dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
31791
- }
31574
+ },
31575
+ constrain: getCircleKeyboardConstraint(center, radiusPoint, snapStep)
31792
31576
  }), /*#__PURE__*/React__namespace.createElement("g", {
31793
31577
  id: radiusId,
31794
31578
  style: a11y.srOnly
@@ -31899,6 +31683,30 @@ function getCircleGraphDescription(state, i18n) {
31899
31683
  const strings = describeCircleGraph(state, i18n);
31900
31684
  return strings.srCircleInteractiveElement;
31901
31685
  }
31686
+ const getCircleKeyboardConstraint = (center, radiusPoint, snapStep) => {
31687
+ // Create a helper function that moves the point and then checks
31688
+ // if it overlaps with the center point after the move.
31689
+ const movePointWithConstraint = moveFunc => {
31690
+ // Move the point
31691
+ let movedCoord = moveFunc(radiusPoint);
31692
+ // If the moved point overlaps with the center point,
31693
+ // move the point again.
31694
+ if (mafs.vec.dist(movedCoord, center) === 0) {
31695
+ movedCoord = moveFunc(movedCoord);
31696
+ }
31697
+ return movedCoord;
31698
+ };
31699
+
31700
+ // Check if the new point overlaps the center point.
31701
+ // If it does, we need to snap the point to the left
31702
+ // or right an additional snapStep to avoid the overlap.
31703
+ return {
31704
+ up: movePointWithConstraint(coord => mafs.vec.add(coord, [0, snapStep[1]])),
31705
+ down: movePointWithConstraint(coord => mafs.vec.sub(coord, [0, snapStep[1]])),
31706
+ left: movePointWithConstraint(coord => mafs.vec.sub(coord, [snapStep[0], 0])),
31707
+ right: movePointWithConstraint(coord => mafs.vec.add(coord, [snapStep[0], 0]))
31708
+ };
31709
+ };
31902
31710
 
31903
31711
  // Exported for testing
31904
31712
  function describeCircleGraph(state, i18n) {
@@ -31911,6 +31719,7 @@ function describeCircleGraph(state, i18n) {
31911
31719
  radiusPoint
31912
31720
  } = state;
31913
31721
  const radius = getRadius(state);
31722
+ const isRadiusOnRight = radiusPoint[X] >= center[X];
31914
31723
 
31915
31724
  // Aria label strings
31916
31725
  const srCircleGraph = strings.srCircleGraph;
@@ -31918,7 +31727,10 @@ function describeCircleGraph(state, i18n) {
31918
31727
  centerX: srFormatNumber(center[0], locale),
31919
31728
  centerY: srFormatNumber(center[1], locale)
31920
31729
  });
31921
- const srCircleRadiusPoint = strings.srCircleRadiusPoint({
31730
+ const srCircleRadiusPoint = isRadiusOnRight ? strings.srCircleRadiusPointRight({
31731
+ radiusPointX: srFormatNumber(radiusPoint[0], locale),
31732
+ radiusPointY: srFormatNumber(radiusPoint[1], locale)
31733
+ }) : strings.srCircleRadiusPointLeft({
31922
31734
  radiusPointX: srFormatNumber(radiusPoint[0], locale),
31923
31735
  radiusPointY: srFormatNumber(radiusPoint[1], locale)
31924
31736
  });
@@ -32084,6 +31896,12 @@ const LinearSystemGraph = props => {
32084
31896
  locale
32085
31897
  } = usePerseusI18n();
32086
31898
  const id = React__namespace.useId();
31899
+ const intersectionId = `${id}-intersection`;
31900
+ const intersectionPoint = kmath.geometry.getLineIntersection(lines[0], lines[1]);
31901
+ const intersectionDescription = intersectionPoint ? strings.srLinearSystemIntersection({
31902
+ x: srFormatNumber(intersectionPoint[0], locale),
31903
+ y: srFormatNumber(intersectionPoint[1], locale)
31904
+ }) : strings.srLinearSystemParallel;
32087
31905
  const linesAriaInfo = lines.map((line, i) => {
32088
31906
  return {
32089
31907
  pointsDescriptionId: `${id}-line${i + 1}-points`,
@@ -32100,16 +31918,17 @@ const LinearSystemGraph = props => {
32100
31918
  slopeDescription: getSlopeStringForLine(line, strings)
32101
31919
  };
32102
31920
  });
31921
+ const individualLineDescriptions = linesAriaInfo.map(_ref => {
31922
+ let {
31923
+ pointsDescriptionId,
31924
+ interceptDescriptionId,
31925
+ slopeDescriptionId
31926
+ } = _ref;
31927
+ return `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`;
31928
+ }).join(" ");
32103
31929
  return /*#__PURE__*/React__namespace.createElement("g", {
32104
31930
  "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(" ")
31931
+ "aria-describedby": `${individualLineDescriptions} ${intersectionId}`
32113
31932
  }, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(MovableLine, {
32114
31933
  key: i,
32115
31934
  points: line,
@@ -32126,9 +31945,15 @@ const LinearSystemGraph = props => {
32126
31945
  x: srFormatNumber(line[1][0], locale),
32127
31946
  y: srFormatNumber(line[1][1], locale)
32128
31947
  }),
32129
- grabHandleAriaLabel: `${linesAriaInfo[i].interceptDescription} ${linesAriaInfo[i].slopeDescription}`
31948
+ grabHandleAriaLabel: strings.srLinearSystemGrabHandle({
31949
+ lineNumber: i + 1,
31950
+ point1X: srFormatNumber(line[0][0], locale),
31951
+ point1Y: srFormatNumber(line[0][1], locale),
31952
+ point2X: srFormatNumber(line[1][0], locale),
31953
+ point2Y: srFormatNumber(line[1][1], locale)
31954
+ })
32130
31955
  },
32131
- ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId}`,
31956
+ ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId} ${intersectionId}`,
32132
31957
  onMoveLine: delta => {
32133
31958
  dispatch(actions.linearSystem.moveLine(i, delta));
32134
31959
  },
@@ -32160,7 +31985,10 @@ const LinearSystemGraph = props => {
32160
31985
  id: slopeDescriptionId,
32161
31986
  style: a11y.srOnly
32162
31987
  }, slopeDescription));
32163
- }));
31988
+ }), /*#__PURE__*/React__namespace.createElement("g", {
31989
+ id: intersectionId,
31990
+ style: a11y.srOnly
31991
+ }, intersectionDescription));
32164
31992
  };
32165
31993
 
32166
31994
  // Exported for testing
@@ -32826,7 +32654,8 @@ function QuadraticGraph(props) {
32826
32654
  graphState
32827
32655
  } = props;
32828
32656
  const {
32829
- coords
32657
+ coords,
32658
+ snapStep
32830
32659
  } = graphState;
32831
32660
  const {
32832
32661
  strings,
@@ -32879,6 +32708,7 @@ function QuadraticGraph(props) {
32879
32708
  ariaLabel: `${srQuadraticPoint}${srVertex}`,
32880
32709
  point: coord,
32881
32710
  sequenceNumber: i + 1,
32711
+ constrain: getQuadraticKeyboardConstraint(coords, snapStep, i),
32882
32712
  onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
32883
32713
  });
32884
32714
  }), srQuadraticDirection && /*#__PURE__*/React__namespace.createElement("g", {
@@ -32967,6 +32797,60 @@ function describeQuadraticGraph(state, i18n) {
32967
32797
  srQuadraticInteractiveElements
32968
32798
  };
32969
32799
  }
32800
+ const getQuadraticKeyboardConstraint = (coords, snapStep, pointMoved) => {
32801
+ // Make newCoords mutable
32802
+ const newCoords = [coords[0], coords[1], coords[2]];
32803
+
32804
+ // Get the point that is being moved
32805
+ const coordToBeMoved = newCoords[pointMoved];
32806
+
32807
+ // Create a function to validate and adjust the movement of the point
32808
+ const movePointWithConstraint = moveFunc => {
32809
+ // Move the point the desired direction
32810
+ let movedCoord = moveFunc(coordToBeMoved);
32811
+ newCoords[pointMoved] = movedCoord;
32812
+
32813
+ // If these new coordinates are valid, return the moved coord
32814
+ if (areCoordsValid(newCoords)) {
32815
+ return movedCoord;
32816
+ }
32817
+
32818
+ // If the new coordinates are invalid, we need to move the point an additional
32819
+ // snapStep to avoid creating an invalid quadratic graph.
32820
+ movedCoord = moveFunc(movedCoord);
32821
+ newCoords[pointMoved] = movedCoord;
32822
+
32823
+ // If these updated coordinates are valid now, we can return the new coordinates.
32824
+ if (areCoordsValid(newCoords)) {
32825
+ return movedCoord;
32826
+ }
32827
+
32828
+ // Otherwise, if the new coordinates are still invalid, we need to move the point one final snapStep.
32829
+ // This is to support the edge case where all of the points are each exactly one snapStep away
32830
+ // from each other.
32831
+ // Eg. When coords = [[0, 0], [1, 0], [2, 0]], snapStep = [1, 1], and we're moving coords[0] to the right.
32832
+ return moveFunc(movedCoord);
32833
+ };
32834
+ return {
32835
+ up: mafs.vec.add(coordToBeMoved, [0, snapStep[1]]),
32836
+ down: mafs.vec.sub(coordToBeMoved, [0, snapStep[1]]),
32837
+ // For horizontal movement, we need to ensure that the points are not on the same vertical line.
32838
+ left: movePointWithConstraint(coord => mafs.vec.sub(coord, [snapStep[0], 0])),
32839
+ right: movePointWithConstraint(coord => mafs.vec.add(coord, [snapStep[0], 0]))
32840
+ };
32841
+ };
32842
+ const areCoordsValid = coords => {
32843
+ const p1 = coords[0];
32844
+ const p2 = coords[1];
32845
+ const p3 = coords[2];
32846
+
32847
+ // If any of the points share the same x-coordinate,
32848
+ // we are unable to calculate the coefficients, and the graph is invalid.
32849
+ if (p1[0] === p2[0] || p2[0] === p3[0] || p1[0] === p3[0]) {
32850
+ return false;
32851
+ }
32852
+ return true;
32853
+ };
32970
32854
 
32971
32855
  function renderRayGraph(state, dispatch, i18n) {
32972
32856
  return {
@@ -33222,7 +33106,8 @@ function SinusoidGraph(props) {
33222
33106
  // Note: The order of the coordinates is important:
33223
33107
  // The coords[0] is the root and the coords[1] is the first peak
33224
33108
  const {
33225
- coords
33109
+ coords,
33110
+ snapStep
33226
33111
  } = graphState;
33227
33112
 
33228
33113
  // The coefficients are used to calculate the sinusoid equation, plot the graph, and to indicate
@@ -33261,11 +33146,37 @@ function SinusoidGraph(props) {
33261
33146
  key: "point-" + i,
33262
33147
  point: coord,
33263
33148
  sequenceNumber: i + 1,
33149
+ constrain: getSinusoidKeyboardConstraint(coords, snapStep, i),
33264
33150
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
33265
33151
  })), /*#__PURE__*/React__namespace.createElement("g", {
33266
33152
  id: descriptionId
33267
33153
  }, srSinusoidDescription));
33268
33154
  }
33155
+ const getSinusoidKeyboardConstraint = (coords, snapStep, pointIndex) => {
33156
+ // Separate the two points into their own variables, and determine which point is being moved
33157
+ const coordToBeMoved = coords[pointIndex];
33158
+ const otherPoint = coords[1 - pointIndex];
33159
+
33160
+ // Create a helper function that checks if the new point is on the same
33161
+ // vertical line as the other point. If it is, we need to move the point
33162
+ // an additional snapStep.
33163
+ const movePointWithConstraint = moveFunc => {
33164
+ // Move the point
33165
+ let movedCoord = moveFunc(coordToBeMoved);
33166
+ // If the moved point overlaps with the other point in the line,
33167
+ // move the point again.
33168
+ if (movedCoord[X] === otherPoint[X]) {
33169
+ movedCoord = moveFunc(movedCoord);
33170
+ }
33171
+ return movedCoord;
33172
+ };
33173
+ return {
33174
+ up: movePointWithConstraint(coord => mafs.vec.add(coord, [0, snapStep[1]])),
33175
+ down: movePointWithConstraint(coord => mafs.vec.sub(coord, [0, snapStep[1]])),
33176
+ left: movePointWithConstraint(coord => mafs.vec.sub(coord, [snapStep[0], 0])),
33177
+ right: movePointWithConstraint(coord => mafs.vec.add(coord, [snapStep[0], 0]))
33178
+ };
33179
+ };
33269
33180
 
33270
33181
  // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
33271
33182
  const computeSine = function (x,
@@ -33317,6 +33228,14 @@ function describeSinusoidGraph(state, i18n) {
33317
33228
  const [root, peak] = coords;
33318
33229
  const diffX = Math.abs(peak[X] - root[X]);
33319
33230
  const diffY = Math.abs(peak[Y] - root[Y]);
33231
+ const formattedRoot = {
33232
+ x: srFormatNumber(root[X], locale),
33233
+ y: srFormatNumber(root[Y], locale)
33234
+ };
33235
+ const formattedPeak = {
33236
+ x: srFormatNumber(peak[X], locale),
33237
+ y: srFormatNumber(peak[Y], locale)
33238
+ };
33320
33239
  const srSinusoidGraph = strings.srSinusoidGraph;
33321
33240
  const srSinusoidDescription = strings.srSinusoidDescription({
33322
33241
  minValue: srFormatNumber(root[Y] - diffY, locale),
@@ -33324,14 +33243,8 @@ function describeSinusoidGraph(state, i18n) {
33324
33243
  cycleStart: srFormatNumber(root[X] - 2 * diffX, locale),
33325
33244
  cycleEnd: srFormatNumber(root[X] + 2 * diffX, locale)
33326
33245
  });
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
- });
33246
+ const srSinusoidRootPoint = strings.srSinusoidRootPoint(formattedRoot);
33247
+ const srSinusoidPeakPoint = peak[Y] === root[Y] ? strings.srSinusoidFlatPoint(formattedPeak) : peak[Y] > root[Y] ? strings.srSinusoidMaxPoint(formattedPeak) : strings.srSinusoidMinPoint(formattedPeak);
33335
33248
  const srSinusoidInteractiveElements = strings.srInteractiveElements({
33336
33249
  elements: strings.srSinusoidInteractiveElements({
33337
33250
  point1X: srFormatNumber(root[X], locale),
@@ -33353,16 +33266,17 @@ const {
33353
33266
  calculateAngleInDegrees,
33354
33267
  convertDegreesToRadians
33355
33268
  } = kmath.angles;
33356
- const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
33269
+ const protractorImage = "https://cdn.kastatic.org/images/perseus/protractor.svg";
33357
33270
 
33358
33271
  // The vector from the center of the protractor to the top left corner of the
33359
33272
  // protractor image, in pixels. Used for positioning.
33360
- const centerToTopLeft = [-180, -170];
33273
+ const centerToTopLeft = [-195, -190];
33361
33274
 
33362
33275
  // The vector from the center of the protractor to the center of the rotation
33363
33276
  // handle.
33364
- const centerToRotationHandle = [-176, -15];
33277
+ const centerToRotationHandle = [-201, -15];
33365
33278
  function Protractor() {
33279
+ const staticUrl = getDependencies().staticUrl;
33366
33280
  const {
33367
33281
  range,
33368
33282
  snapStep
@@ -33374,7 +33288,9 @@ function Protractor() {
33374
33288
  const [center, setCenter] = React.useState(initialCenter);
33375
33289
  const [rotationHandleOffset, setRotationHandleOffset] = React.useState(centerToRotationHandle);
33376
33290
  const draggableRef = React.useRef(null);
33377
- useDraggable({
33291
+ const {
33292
+ dragging
33293
+ } = useDraggable({
33378
33294
  gestureTarget: draggableRef,
33379
33295
  onMove: setCenter,
33380
33296
  point: center,
@@ -33398,10 +33314,11 @@ function Protractor() {
33398
33314
  ref: draggableRef,
33399
33315
  transform: `translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,
33400
33316
  style: {
33401
- transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`
33317
+ transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`,
33318
+ cursor: dragging ? "grabbing" : "grab"
33402
33319
  }
33403
33320
  }, /*#__PURE__*/React__namespace.createElement("image", {
33404
- href: protractorImage
33321
+ href: staticUrl(protractorImage)
33405
33322
  }), /*#__PURE__*/React__namespace.createElement("g", {
33406
33323
  transform: `translate(5, ${-centerToTopLeft[1]})`,
33407
33324
  ref: rotationHandleRef
@@ -35333,8 +35250,8 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
35333
35250
 
35334
35251
  // If the graph is static, it always displays the correct answer. This is
35335
35252
  // standard behavior for Perseus widgets (e.g. compare the Radio widget).
35336
- if (props.static) {
35337
- return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
35253
+ if (props.static && props.correct) {
35254
+ return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends__default["default"]({}, props, {
35338
35255
  state: initializeGraphState({
35339
35256
  ...props,
35340
35257
  graph: props.correct
@@ -35342,7 +35259,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
35342
35259
  dispatch: dispatch
35343
35260
  }));
35344
35261
  }
35345
- return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
35262
+ return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends__default["default"]({}, props, {
35346
35263
  state: state,
35347
35264
  dispatch: dispatch
35348
35265
  }));
@@ -35353,7 +35270,7 @@ const {
35353
35270
  } = kmath.angles;
35354
35271
  const {
35355
35272
  getLineEquation,
35356
- getLineIntersection,
35273
+ getLineIntersectionString,
35357
35274
  magnitude,
35358
35275
  vector
35359
35276
  } = kmath.geometry;
@@ -35435,7 +35352,7 @@ class InteractiveGraph extends React__namespace.Component {
35435
35352
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
35436
35353
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
35437
35354
  const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
35438
- return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends({}, this.props, {
35355
+ return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends__default["default"]({}, this.props, {
35439
35356
  ref: this.mafsRef,
35440
35357
  gridStep: gridStep,
35441
35358
  snapStep: snapStep,
@@ -35718,7 +35635,7 @@ class InteractiveGraph extends React__namespace.Component {
35718
35635
  }
35719
35636
  static getLinearSystemEquationString(props) {
35720
35637
  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]);
35638
+ return "\n" + getLineEquation(coords[0][0], coords[0][1]) + "\n" + getLineEquation(coords[1][0], coords[1][1]) + "\n" + getLineIntersectionString(coords[0], coords[1]);
35722
35639
  }
35723
35640
  static getPointEquationString(props) {
35724
35641
  if (props.graph.type !== "point") {
@@ -35870,10 +35787,10 @@ const AnswerChoices = props => {
35870
35787
  onToggle,
35871
35788
  disabled
35872
35789
  };
35873
- return props.multipleSelect ? /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.MultiSelect, _extends({}, args, {
35790
+ return props.multipleSelect ? /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.MultiSelect, _extends__default["default"]({}, args, {
35874
35791
  selectedValues: selectedValues,
35875
35792
  onChange: selected => onAnswerChange(selected)
35876
- })) : /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, _extends({}, args, {
35793
+ })) : /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, _extends__default["default"]({}, args, {
35877
35794
  selectedValue: selectedValues[0],
35878
35795
  onChange: selected => onAnswerChange([selected]),
35879
35796
  placeholder: props.choices[0].content // not visible, but required
@@ -36159,7 +36076,6 @@ const styles$a = aphrodite.StyleSheet.create({
36159
36076
  markerCorrect: {
36160
36077
  background: "#00880b" // WB green darkened by 18%
36161
36078
  },
36162
-
36163
36079
  markerIncorrect: {
36164
36080
  background: wonderBlocksTokens.color.offBlack64
36165
36081
  }
@@ -36556,7 +36472,6 @@ class LabelImage extends React__namespace.Component {
36556
36472
  const adjustPillDistance = {
36557
36473
  [`margin${markerPosition.charAt(0).toUpperCase() + markerPosition.slice(1)}`]: 10 // move pill further from marker
36558
36474
  };
36559
-
36560
36475
  const answerChoicesActive = index === activeMarkerIndex;
36561
36476
  const showAnswerChoice = marker.selected && !answerChoicesActive && !this.state.hideAnswers;
36562
36477
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
@@ -36605,7 +36520,7 @@ class LabelImage extends React__namespace.Component {
36605
36520
  focused,
36606
36521
  pressed
36607
36522
  } = _ref2;
36608
- return /*#__PURE__*/React__namespace.createElement(Marker, _extends({}, marker, {
36523
+ return /*#__PURE__*/React__namespace.createElement(Marker, _extends__default["default"]({}, marker, {
36609
36524
  showCorrectness: showCorrectness,
36610
36525
  showSelected: opened,
36611
36526
  showPulsate: !markersInteracted,
@@ -36750,7 +36665,7 @@ const styles$9 = aphrodite.StyleSheet.create({
36750
36665
  });
36751
36666
  const LabelImageWithDependencies = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
36752
36667
  const deps = useDependencies();
36753
- return /*#__PURE__*/React__namespace.createElement(LabelImage, _extends({
36668
+ return /*#__PURE__*/React__namespace.createElement(LabelImage, _extends__default["default"]({
36754
36669
  ref: ref,
36755
36670
  analytics: deps.analytics
36756
36671
  }, props));
@@ -37012,7 +36927,6 @@ class Draggable extends React__namespace.Component {
37012
36927
  if (this.props.width) {
37013
36928
  style.width = this.props.width + 1; // Fix for non-integer widths
37014
36929
  }
37015
-
37016
36930
  if (this.props.height) {
37017
36931
  style.height = this.props.height;
37018
36932
  }
@@ -37356,8 +37270,7 @@ class Sortable extends React__namespace.Component {
37356
37270
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
37357
37271
  margin = "0 0 " + this.props.margin + "px 0"; // bottom
37358
37272
  }
37359
-
37360
- cards.push( /*#__PURE__*/React__namespace.createElement(Draggable, {
37273
+ cards.push(/*#__PURE__*/React__namespace.createElement(Draggable, {
37361
37274
  content: item.option,
37362
37275
  key: item.key,
37363
37276
  state: item.state
@@ -37410,7 +37323,7 @@ class Sortable extends React__namespace.Component {
37410
37323
  this, item.key)
37411
37324
  }));
37412
37325
  if (item.state === ItemState.DRAGGING || item.state === ItemState.ANIMATING) {
37413
- cards.push( /*#__PURE__*/React__namespace.createElement(Placeholder, {
37326
+ cards.push(/*#__PURE__*/React__namespace.createElement(Placeholder, {
37414
37327
  key: "placeholder_" + item.key,
37415
37328
  ref: "placeholder_" + item.key,
37416
37329
  width: item.width,
@@ -38024,7 +37937,7 @@ class Matrix extends React__namespace.Component {
38024
37937
  boxSizing: "border-box",
38025
37938
  backgroundColor: outside ? "#f3f3f3" : "#fff"
38026
37939
  };
38027
- MatrixInput = /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput, _extends({}, inputProps, {
37940
+ MatrixInput = /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput, _extends__default["default"]({}, inputProps, {
38028
37941
  style: style,
38029
37942
  scrollable: true,
38030
37943
  keypadElement: this.props.keypadElement
@@ -39185,7 +39098,6 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
39185
39098
  if (!___default["default"].isFinite(props.tickStep) || props.tickStep <= 0) {
39186
39099
  return []; // this has screwed me for the last time!
39187
39100
  }
39188
-
39189
39101
  const results = [];
39190
39102
 
39191
39103
  // For convenience, extract some props into separate variables
@@ -39256,6 +39168,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
39256
39168
 
39257
39169
  // TODO: most widgets use some like Widget<Something, PerseusNumberLineWidgetOptions>
39258
39170
  // should this one?
39171
+
39259
39172
  class NumberLine extends React__namespace.Component {
39260
39173
  static contextType = PerseusI18nContext;
39261
39174
  static defaultProps = {
@@ -39400,7 +39313,7 @@ class NumberLine extends React__namespace.Component {
39400
39313
  setup: this._setupGraphie,
39401
39314
  setDrawingAreaAvailable: this.props.apiOptions.setDrawingAreaAvailable,
39402
39315
  isMobile: this.props.apiOptions.isMobile
39403
- }, /*#__PURE__*/React__namespace.createElement(TickMarks, _extends({}, ___default["default"].pick(props, ["range", "numDivisions", "labelTicks", "labelStyle", "labelRange", "tickStep"]), {
39316
+ }, /*#__PURE__*/React__namespace.createElement(TickMarks, _extends__default["default"]({}, ___default["default"].pick(props, ["range", "numDivisions", "labelTicks", "labelStyle", "labelRange", "tickStep"]), {
39404
39317
  isMobile: this.props.apiOptions.isMobile
39405
39318
  })), this._renderInequality(props), this._renderNumberLinePoint(props));
39406
39319
  };
@@ -39627,7 +39540,6 @@ const numberLineTransform = editorProps => {
39627
39540
  } else {
39628
39541
  numDivisions = undefined; // send to getDefaultProps()
39629
39542
  }
39630
-
39631
39543
  ___default["default"].extend(props, {
39632
39544
  numLinePosition: numLinePosition,
39633
39545
  numDivisions: numDivisions,
@@ -39652,7 +39564,6 @@ const staticTransform$1 = editorProps => {
39652
39564
  } else {
39653
39565
  numDivisions = undefined; // send to getDefaultProps()
39654
39566
  }
39655
-
39656
39567
  ___default["default"].extend(props, {
39657
39568
  numLinePosition: numLinePosition,
39658
39569
  numDivisions: numDivisions,
@@ -39847,7 +39758,7 @@ class Card extends React__namespace.Component {
39847
39758
  onTouchCancel: this.onMouseUp
39848
39759
  }, /*#__PURE__*/React__namespace.createElement("div", {
39849
39760
  className: className.join(" ")
39850
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, rendererProps, {
39761
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({}, rendererProps, {
39851
39762
  linterContext: this.props.linterContext,
39852
39763
  strings: this.context.strings
39853
39764
  }))))
@@ -40672,7 +40583,6 @@ class Plotter extends React__namespace.Component {
40672
40583
  const initialY = isMobile ? c.scaleY : 0;
40673
40584
  for (let y = initialY; y <= c.dimY; y += c.scaleY) {
40674
40585
  graphie.label([0, y], kmath.KhanMath.roundToApprox(y, 2), "left", /* isTeX */true /* for the \approx symbol */);
40675
-
40676
40586
  graphie.style({
40677
40587
  stroke: isMobile ? "#e9ebec" : "#000",
40678
40588
  strokeWidth: 1,
@@ -41634,7 +41544,7 @@ class Table extends React__namespace.Component {
41634
41544
  }, ___default["default"](columns).times(c => {
41635
41545
  return /*#__PURE__*/React__namespace.createElement("td", {
41636
41546
  key: c
41637
- }, /*#__PURE__*/React__namespace.createElement(InputComponent, _extends({
41547
+ }, /*#__PURE__*/React__namespace.createElement(InputComponent, _extends__default["default"]({
41638
41548
  ref: getRefForPath(getInputPath(r, c)),
41639
41549
  type: "text",
41640
41550
  value: this.props.answers[r][c],
@@ -41845,10 +41755,51 @@ const init = function () {
41845
41755
  replaceDeprecatedWidgets();
41846
41756
  };
41847
41757
 
41758
+ /**
41759
+ * Adds the given perseus library version information to the __perseus_debug__
41760
+ * object and ensures that the object is attached to `globalThis` (`window` in
41761
+ * browser environments).
41762
+ *
41763
+ * This allows each library to provide runtime version information to assist in
41764
+ * debugging in production environments.
41765
+ */
41766
+ const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
41767
+ // If the library version is the default value, then we don't want to
41768
+ // prefix it with a "v" to indicate that it is a version number.
41769
+ let prefix = "v";
41770
+ if (libraryVersion === "__lib_version__") {
41771
+ prefix = "";
41772
+ }
41773
+ const formattedVersion = `${prefix}${libraryVersion}`;
41774
+ if (typeof globalThis !== "undefined") {
41775
+ globalThis.__perseus_debug__ = globalThis.__perseus_debug__ ?? {};
41776
+ const existingVersionEntry = globalThis.__perseus_debug__[libraryName];
41777
+ if (existingVersionEntry) {
41778
+ // If we already have an entry and it doesn't match the registered
41779
+ // version, we morph the entry into an array and log a warning.
41780
+ if (existingVersionEntry !== formattedVersion) {
41781
+ // Existing entry might be an array already (oops, at least 2
41782
+ // versions of the library already loaded!).
41783
+ const allVersions = Array.isArray(existingVersionEntry) ? existingVersionEntry : [existingVersionEntry];
41784
+ allVersions.push(formattedVersion);
41785
+ globalThis.__perseus_debug__[libraryName] = allVersions;
41786
+
41787
+ // eslint-disable-next-line no-console
41788
+ console.warn(`Multiple versions of ${libraryName} loaded on this page: ${allVersions.sort().join(", ")}`);
41789
+ }
41790
+ } else {
41791
+ globalThis.__perseus_debug__[libraryName] = formattedVersion;
41792
+ }
41793
+ } else {
41794
+ // eslint-disable-next-line no-console
41795
+ console.warn(`globalThis not found found (${formattedVersion})`);
41796
+ }
41797
+ };
41798
+
41848
41799
  // This file is processed by a Rollup plugin (replace) to inject the production
41849
41800
  const libName = "@khanacademy/perseus";
41850
- const libVersion = "54.1.0";
41851
- perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
41801
+ const libVersion = "55.0.1";
41802
+ addLibraryVersionToPerseusDebug(libName, libVersion);
41852
41803
 
41853
41804
  const apiVersion = {
41854
41805
  major: 12,
@@ -42004,7 +41955,7 @@ class ArticleRenderer extends React__namespace.Component {
42004
41955
  return /*#__PURE__*/React__namespace.createElement("div", {
42005
41956
  key: sectionIndex,
42006
41957
  className: "clearfix"
42007
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, section, {
41958
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({}, section, {
42008
41959
  ref: elem => {
42009
41960
  if (elem) {
42010
41961
  this.sectionRenderers[sectionIndex] = elem;
@@ -42179,7 +42130,6 @@ class HintsRenderer extends React__namespace.Component {
42179
42130
  ReactDOM__default["default"].findDOMNode(this.refs["hintRenderer" + pos]).focus(); // eslint-disable-line react/no-string-refs
42180
42131
  }
42181
42132
  }
42182
-
42183
42133
  _hintsVisible = () => {
42184
42134
  if (this.props.hintsVisible == null || this.props.hintsVisible === -1) {
42185
42135
  return this.props.hints.length;
@@ -42668,7 +42618,7 @@ class ServerItemRenderer extends React__namespace.Component {
42668
42618
  };
42669
42619
  const questionRenderer = /*#__PURE__*/React__namespace.createElement(context$1.Provider, {
42670
42620
  value: contextValue
42671
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({
42621
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends__default["default"]({
42672
42622
  keypadElement: this.props.keypadElement,
42673
42623
  problemNum: this.props.problemNum,
42674
42624
  onInteractWithWidget: this.handleInteractWithWidget,
@@ -42717,7 +42667,7 @@ const ref = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__
42717
42667
  let {
42718
42668
  onRendered
42719
42669
  } = _ref;
42720
- return /*#__PURE__*/React__namespace.createElement(ServerItemRenderer, _extends({}, props, {
42670
+ return /*#__PURE__*/React__namespace.createElement(ServerItemRenderer, _extends__default["default"]({}, props, {
42721
42671
  onRendered: onRendered,
42722
42672
  ref: ref
42723
42673
  }));
@@ -44148,24 +44098,24 @@ const ScoringUtil = {
44148
44098
  */
44149
44099
 
44150
44100
  Object.defineProperty(exports, 'isFailure', {
44151
- enumerable: true,
44152
- get: function () { return perseusCore.isFailure; }
44101
+ enumerable: true,
44102
+ get: function () { return perseusCore.isFailure; }
44153
44103
  });
44154
44104
  Object.defineProperty(exports, 'isSuccess', {
44155
- enumerable: true,
44156
- get: function () { return perseusCore.isSuccess; }
44105
+ enumerable: true,
44106
+ get: function () { return perseusCore.isSuccess; }
44157
44107
  });
44158
44108
  Object.defineProperty(exports, 'parseAndMigratePerseusArticle', {
44159
- enumerable: true,
44160
- get: function () { return perseusCore.parseAndMigratePerseusArticle; }
44109
+ enumerable: true,
44110
+ get: function () { return perseusCore.parseAndMigratePerseusArticle; }
44161
44111
  });
44162
44112
  Object.defineProperty(exports, 'parseAndMigratePerseusItem', {
44163
- enumerable: true,
44164
- get: function () { return perseusCore.parseAndMigratePerseusItem; }
44113
+ enumerable: true,
44114
+ get: function () { return perseusCore.parseAndMigratePerseusItem; }
44165
44115
  });
44166
44116
  Object.defineProperty(exports, 'parsePerseusItem', {
44167
- enumerable: true,
44168
- get: function () { return perseusCore.parsePerseusItem; }
44117
+ enumerable: true,
44118
+ get: function () { return perseusCore.parsePerseusItem; }
44169
44119
  });
44170
44120
  exports.ApiOptions = ApiOptions;
44171
44121
  exports.ArticleRenderer = ArticleRenderer;