@khanacademy/perseus 53.1.0 → 54.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/components/highlighting/types.d.ts +2 -1
  2. package/dist/components.d.ts +0 -1
  3. package/dist/es/index.js +621 -1948
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/es/strings.js +61 -10
  6. package/dist/es/strings.js.map +1 -1
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +698 -775
  9. package/dist/index.js.map +1 -1
  10. package/dist/interactive2/movable.d.ts +2 -1
  11. package/dist/strings.d.ts +57 -6
  12. package/dist/strings.js +139 -67
  13. package/dist/strings.js.map +1 -1
  14. package/dist/types.d.ts +6 -13
  15. package/dist/util/graphie.d.ts +1 -1
  16. package/dist/util.d.ts +0 -5
  17. package/dist/widget-ai-utils/radio/radio-ai-utils.d.ts +2 -1
  18. package/dist/widget-type-utils.d.ts +1 -0
  19. package/dist/widgets/categorizer/categorizer.d.ts +1 -2
  20. package/dist/widgets/cs-program/cs-program.d.ts +0 -2
  21. package/dist/widgets/dropdown/dropdown.d.ts +0 -2
  22. package/dist/widgets/expression/expression.d.ts +3 -6
  23. package/dist/widgets/grapher/grapher.d.ts +0 -2
  24. package/dist/widgets/iframe/iframe.d.ts +1 -2
  25. package/dist/widgets/interactive-graph.d.ts +2 -87
  26. package/dist/widgets/interactive-graphs/backgrounds/axis-labels.d.ts +4 -1
  27. package/dist/widgets/interactive-graphs/graphs/angle.d.ts +2 -1
  28. package/dist/widgets/interactive-graphs/graphs/circle.d.ts +6 -2
  29. package/dist/widgets/interactive-graphs/graphs/components/hairlines.d.ts +7 -0
  30. package/dist/widgets/interactive-graphs/graphs/components/movable-point-view.d.ts +1 -0
  31. package/dist/widgets/interactive-graphs/graphs/components/svg-line.d.ts +2 -1
  32. package/dist/widgets/interactive-graphs/graphs/linear-system.d.ts +2 -2
  33. package/dist/widgets/interactive-graphs/graphs/linear.d.ts +1 -1
  34. package/dist/widgets/interactive-graphs/graphs/point.d.ts +3 -2
  35. package/dist/widgets/interactive-graphs/graphs/ray.d.ts +1 -1
  36. package/dist/widgets/interactive-graphs/graphs/screenreader-text.d.ts +1 -0
  37. package/dist/widgets/interactive-graphs/graphs/segment.d.ts +2 -2
  38. package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +2 -1
  39. package/dist/widgets/interactive-graphs/graphs/utils.d.ts +0 -3
  40. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +1 -2
  41. package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +3 -3
  42. package/dist/widgets/label-image/label-image.d.ts +1 -3
  43. package/dist/widgets/matrix/matrix.d.ts +1 -2
  44. package/dist/widgets/number-line/number-line.d.ts +0 -2
  45. package/dist/widgets/numeric-input/numeric-input.d.ts +0 -2
  46. package/dist/widgets/orderer/orderer.d.ts +0 -2
  47. package/dist/widgets/plotter/plotter.d.ts +0 -2
  48. package/dist/widgets/radio/radio.d.ts +1 -2
  49. package/dist/widgets/sorter/sorter.d.ts +1 -2
  50. package/dist/widgets/table/table.d.ts +0 -2
  51. package/dist/widgets.d.ts +1 -2
  52. package/package.json +10 -10
  53. package/dist/components/graph.d.ts +0 -61
package/dist/index.js CHANGED
@@ -33,7 +33,6 @@ var Banner = require('@khanacademy/wonder-blocks-banner');
33
33
  var wonderBlocksDropdown = require('@khanacademy/wonder-blocks-dropdown');
34
34
  var caretDown = require('@phosphor-icons/core/regular/caret-down.svg');
35
35
  var caretUp = require('@phosphor-icons/core/regular/caret-up.svg');
36
- require('lodash.debounce');
37
36
  var mafs = require('mafs');
38
37
  var wonderBlocksIcon = require('@khanacademy/wonder-blocks-icon');
39
38
  var questionIcon = require('@phosphor-icons/core/regular/question.svg');
@@ -367,6 +366,10 @@ const mockStrings = {
367
366
  simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
368
367
  selectAnAnswer: "Select an answer",
369
368
  // The following strings are used for interactive graph SR descriptions.
369
+ srGraphInstructions: "Use the Tab key to move through the interactive elements in the graph. When an interactive element has focus, use the Arrow keys to move it.",
370
+ srUnlimitedGraphInstructions: "Press Shift + Enter to interact with the graph. Use the Tab key to move through the interactive elements in the graph and access the graph Action Bar. When an interactive element has focus, use the Arrow keys to move it or use the Delete key to remove it from the graph. Use the buttons in the Action Bar to add or adjust elements within the graph.",
371
+ xAxis: "X-axis",
372
+ yAxis: "Y-axis",
370
373
  graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
371
374
  addPoint: "Add Point",
372
375
  removePoint: "Remove Point",
@@ -464,25 +467,37 @@ const mockStrings = {
464
467
  } = _ref37;
465
468
  return `Line from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`;
466
469
  },
467
- srAngleSideAtCoordinates: _ref38 => {
470
+ srAngleStartingSide: _ref38 => {
468
471
  let {
469
- point,
470
- side,
471
472
  x,
472
473
  y
473
474
  } = _ref38;
474
- return `Point ${point}, ${side} at ${x} comma ${y}`;
475
+ return `Point 3, starting side at ${x} comma ${y}.`;
475
476
  },
476
- srAngleVertexAtCoordinatesWithAngleMeasure: _ref39 => {
477
+ srAngleEndingSide: _ref39 => {
478
+ let {
479
+ x,
480
+ y
481
+ } = _ref39;
482
+ return `Point 2, ending side at ${x} comma ${y}.`;
483
+ },
484
+ srAngleVertex: _ref40 => {
485
+ let {
486
+ x,
487
+ y
488
+ } = _ref40;
489
+ return `Point 1, vertex at ${x} comma ${y}.`;
490
+ },
491
+ srAngleVertexWithAngleMeasure: _ref41 => {
477
492
  let {
478
493
  x,
479
494
  y,
480
495
  angleMeasure
481
- } = _ref39;
482
- return `Point 2, vertex at ${x} comma ${y}. Angle ${angleMeasure} degrees`;
496
+ } = _ref41;
497
+ return `Point 1, vertex at ${x} comma ${y}. Angle ${angleMeasure} degrees.`;
483
498
  },
484
499
  srAngleGraphAriaLabel: "An angle on a coordinate plane.",
485
- srAngleGraphAriaDescription: _ref40 => {
500
+ srAngleGraphAriaDescription: _ref42 => {
486
501
  let {
487
502
  angleMeasure,
488
503
  vertexX,
@@ -491,118 +506,129 @@ const mockStrings = {
491
506
  startingSideY,
492
507
  endingSideX,
493
508
  endingSideY
494
- } = _ref40;
509
+ } = _ref42;
495
510
  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}.`;
496
511
  },
512
+ srAngleInteractiveElements: _ref43 => {
513
+ let {
514
+ vertexX,
515
+ vertexY,
516
+ startingSideX,
517
+ startingSideY,
518
+ endingSideX,
519
+ endingSideY
520
+ } = _ref43;
521
+ 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
+ },
497
523
  srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
498
- srMultipleSegmentGraphAriaLabel: _ref41 => {
524
+ srMultipleSegmentGraphAriaLabel: _ref44 => {
499
525
  let {
500
526
  countOfSegments
501
- } = _ref41;
527
+ } = _ref44;
502
528
  return `${countOfSegments} segments on a coordinate plane.`;
503
529
  },
504
- srMultipleSegmentIndividualLabel: _ref42 => {
530
+ srMultipleSegmentIndividualLabel: _ref45 => {
505
531
  let {
506
532
  point1X,
507
533
  point1Y,
508
534
  point2X,
509
535
  point2Y,
510
536
  indexOfSegment
511
- } = _ref42;
537
+ } = _ref45;
512
538
  return `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`;
513
539
  },
514
- srSingleSegmentLabel: _ref43 => {
540
+ srSingleSegmentLabel: _ref46 => {
515
541
  let {
516
542
  point1X,
517
543
  point1Y,
518
544
  point2X,
519
545
  point2Y
520
- } = _ref43;
546
+ } = _ref46;
521
547
  return `Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`;
522
548
  },
523
- srSegmentLength: _ref44 => {
549
+ srSegmentLength: _ref47 => {
524
550
  let {
525
551
  length
526
- } = _ref44;
552
+ } = _ref47;
527
553
  return `Segment length ${length} units.`;
528
554
  },
529
- srSingleSegmentGraphEndpointAriaLabel: _ref45 => {
555
+ srSingleSegmentGraphEndpointAriaLabel: _ref48 => {
530
556
  let {
531
557
  endpointNumber,
532
558
  x,
533
559
  y
534
- } = _ref45;
560
+ } = _ref48;
535
561
  return `Endpoint ${endpointNumber} at ${x} comma ${y}.`;
536
562
  },
537
- srMultipleSegmentGraphEndpointAriaLabel: _ref46 => {
563
+ srMultipleSegmentGraphEndpointAriaLabel: _ref49 => {
538
564
  let {
539
565
  endpointNumber,
540
566
  x,
541
567
  y,
542
568
  indexOfSegment
543
- } = _ref46;
569
+ } = _ref49;
544
570
  return `Endpoint ${endpointNumber} on segment ${indexOfSegment} at ${x} comma ${y}.`;
545
571
  },
546
- srSegmentGrabHandle: _ref47 => {
572
+ srSegmentGrabHandle: _ref50 => {
547
573
  let {
548
574
  point1X,
549
575
  point1Y,
550
576
  point2X,
551
577
  point2Y
552
- } = _ref47;
578
+ } = _ref50;
553
579
  return `Segment from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`;
554
580
  },
555
581
  srLinearSystemGraph: "Two lines on a coordinate plane.",
556
- srLinearSystemPoints: _ref48 => {
582
+ srLinearSystemPoints: _ref51 => {
557
583
  let {
558
584
  lineNumber,
559
585
  point1X,
560
586
  point1Y,
561
587
  point2X,
562
588
  point2Y
563
- } = _ref48;
589
+ } = _ref51;
564
590
  return `Line ${lineNumber} has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`;
565
591
  },
566
- srLinearSystemPoint: _ref49 => {
592
+ srLinearSystemPoint: _ref52 => {
567
593
  let {
568
594
  lineNumber,
569
595
  pointSequence,
570
596
  x,
571
597
  y
572
- } = _ref49;
598
+ } = _ref52;
573
599
  return `Point ${pointSequence} on line ${lineNumber} at ${x} comma ${y}.`;
574
600
  },
575
601
  srRayGraph: "A ray on a coordinate plane.",
576
- srRayPoints: _ref50 => {
602
+ srRayPoints: _ref53 => {
577
603
  let {
578
604
  point1X,
579
605
  point1Y,
580
606
  point2X,
581
607
  point2Y
582
- } = _ref50;
608
+ } = _ref53;
583
609
  return `The endpoint is at ${point1X} comma ${point1Y} and the ray goes through point ${point2X} comma ${point2Y}.`;
584
610
  },
585
- srRayGrabHandle: _ref51 => {
611
+ srRayGrabHandle: _ref54 => {
586
612
  let {
587
613
  point1X,
588
614
  point1Y,
589
615
  point2X,
590
616
  point2Y
591
- } = _ref51;
617
+ } = _ref54;
592
618
  return `Ray with endpoint ${point1X} comma ${point1Y} going through point ${point2X} comma ${point2Y}.`;
593
619
  },
594
- srRayEndpoint: _ref52 => {
620
+ srRayEndpoint: _ref55 => {
595
621
  let {
596
622
  x,
597
623
  y
598
- } = _ref52;
624
+ } = _ref55;
599
625
  return `Endpoint at ${x} comma ${y}.`;
600
626
  },
601
- srRayTerminalPoint: _ref53 => {
627
+ srRayTerminalPoint: _ref56 => {
602
628
  let {
603
629
  x,
604
630
  y
605
- } = _ref53;
631
+ } = _ref56;
606
632
  return `Through point at ${x} comma ${y}.`;
607
633
  },
608
634
  srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
@@ -611,55 +637,55 @@ const mockStrings = {
611
637
  srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
612
638
  srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
613
639
  srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
614
- srQuadraticGraphVertexQuadrant: _ref54 => {
640
+ srQuadraticGraphVertexQuadrant: _ref57 => {
615
641
  let {
616
642
  quadrant
617
- } = _ref54;
643
+ } = _ref57;
618
644
  return `Vertex is in quadrant ${quadrant}.`;
619
645
  },
620
- srQuadraticTwoXIntercepts: _ref55 => {
646
+ srQuadraticTwoXIntercepts: _ref58 => {
621
647
  let {
622
648
  intercept1,
623
649
  intercept2
624
- } = _ref55;
650
+ } = _ref58;
625
651
  return `The X-intercepts are at ${intercept1} comma 0 and ${intercept2} comma 0.`;
626
652
  },
627
- srQuadraticOneXIntercept: _ref56 => {
653
+ srQuadraticOneXIntercept: _ref59 => {
628
654
  let {
629
655
  intercept
630
- } = _ref56;
656
+ } = _ref59;
631
657
  return `The X-intercept is at ${intercept} comma 0.`;
632
658
  },
633
- srQuadraticYIntercept: _ref57 => {
659
+ srQuadraticYIntercept: _ref60 => {
634
660
  let {
635
661
  intercept
636
- } = _ref57;
662
+ } = _ref60;
637
663
  return `The Y-intercept is at 0 comma ${intercept}.`;
638
664
  },
639
- srQuadraticPointOrigin: _ref58 => {
665
+ srQuadraticPointOrigin: _ref61 => {
640
666
  let {
641
667
  pointNumber
642
- } = _ref58;
668
+ } = _ref61;
643
669
  return `Point ${pointNumber} on parabola at the origin.`;
644
670
  },
645
- srQuadraticPointAxis: _ref59 => {
671
+ srQuadraticPointAxis: _ref62 => {
646
672
  let {
647
673
  pointNumber,
648
674
  x,
649
675
  y
650
- } = _ref59;
676
+ } = _ref62;
651
677
  return `Point ${pointNumber} on parabola at ${x} comma ${y}.`;
652
678
  },
653
- srQuadraticPointQuadrant: _ref60 => {
679
+ srQuadraticPointQuadrant: _ref63 => {
654
680
  let {
655
681
  pointNumber,
656
682
  x,
657
683
  y,
658
684
  quadrant
659
- } = _ref60;
685
+ } = _ref63;
660
686
  return `Point ${pointNumber} on parabola in quadrant ${quadrant} at ${x} comma ${y}.`;
661
687
  },
662
- srQuadraticInteractiveElements: _ref61 => {
688
+ srQuadraticInteractiveElements: _ref64 => {
663
689
  let {
664
690
  point1X,
665
691
  point1Y,
@@ -667,52 +693,85 @@ const mockStrings = {
667
693
  point2Y,
668
694
  point3X,
669
695
  point3Y
670
- } = _ref61;
696
+ } = _ref64;
671
697
  return `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`;
672
698
  },
673
699
  srPolygonGraph: "A polygon.",
674
700
  srPolygonGraphCoordinatePlane: "A polygon on a coordinate plane.",
675
- srPolygonGraphPointsNum: _ref62 => {
701
+ srPolygonGraphPointsNum: _ref65 => {
676
702
  let {
677
703
  num
678
- } = _ref62;
704
+ } = _ref65;
679
705
  return `The polygon has ${num} points.`;
680
706
  },
681
707
  srPolygonGraphPointsOne: "The polygon has 1 point.",
682
- srPolygonElementsNum: _ref63 => {
708
+ srPolygonElementsNum: _ref66 => {
683
709
  let {
684
710
  num
685
- } = _ref63;
711
+ } = _ref66;
686
712
  return `A polygon with ${num} points.`;
687
713
  },
688
714
  srPolygonElementsOne: "A polygon with 1 point.",
689
- srPolygonPointAngleApprox: _ref64 => {
715
+ srPolygonPointAngleApprox: _ref67 => {
690
716
  let {
691
717
  angle
692
- } = _ref64;
718
+ } = _ref67;
693
719
  return `Angle approximately equal to ${angle} degrees.`;
694
720
  },
695
- srPolygonPointAngle: _ref65 => {
721
+ srPolygonPointAngle: _ref68 => {
696
722
  let {
697
723
  angle
698
- } = _ref65;
724
+ } = _ref68;
699
725
  return `Angle equal to ${angle} degrees.`;
700
726
  },
701
- srPolygonSideLength: _ref66 => {
727
+ srPolygonSideLength: _ref69 => {
702
728
  let {
703
729
  pointNum,
704
730
  length
705
- } = _ref66;
731
+ } = _ref69;
706
732
  return `A line segment, length equal to ${length} units, connects to point ${pointNum}.`;
707
733
  },
708
- srPolygonSideLengthApprox: _ref67 => {
734
+ srPolygonSideLengthApprox: _ref70 => {
709
735
  let {
710
736
  pointNum,
711
737
  length
712
- } = _ref67;
738
+ } = _ref70;
713
739
  return `A line segment, length approximately equal to ${length} units, connects to point ${pointNum}.`;
714
740
  },
715
- srUnlimitedPolygonEmpty: "An empty coordinate plane."
741
+ srUnlimitedPolygonEmpty: "An empty coordinate plane.",
742
+ srSinusoidGraph: "A sinusoid function on a coordinate plane.",
743
+ srSinusoidRootPoint: _ref71 => {
744
+ let {
745
+ x,
746
+ y
747
+ } = _ref71;
748
+ return `Midline intersection at ${x} comma ${y}.`;
749
+ },
750
+ srSinusoidPeakPoint: _ref72 => {
751
+ let {
752
+ x,
753
+ y
754
+ } = _ref72;
755
+ return `Extremum point at ${x} comma ${y}.`;
756
+ },
757
+ srSinusoidDescription: _ref73 => {
758
+ let {
759
+ minValue,
760
+ maxValue,
761
+ cycleStart,
762
+ cycleEnd
763
+ } = _ref73;
764
+ 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
+ },
766
+ srSinusoidInteractiveElements: _ref74 => {
767
+ let {
768
+ point1X,
769
+ point1Y,
770
+ point2X,
771
+ point2Y
772
+ } = _ref74;
773
+ return `Sinusoid graph with midline intersection point at ${point1X} comma ${point1Y} and extremum point at ${point2X} comma ${point2Y}.`;
774
+ }
716
775
  // The above strings are used for interactive graph SR descriptions.
717
776
  };
718
777
 
@@ -1634,6 +1693,7 @@ const ApiOptions = {
1634
1693
  },
1635
1694
  baseElements: {
1636
1695
  Link: props => {
1696
+ // eslint-disable-next-line jsx-a11y/anchor-has-content -- TODO(LEMS-2871): Address a11y error
1637
1697
  return /*#__PURE__*/React__namespace.createElement("a", props);
1638
1698
  }
1639
1699
  },
@@ -2034,7 +2094,6 @@ var Expression$1 = {
2034
2094
  propUpgrades: perseusCore.expressionLogic.widgetOptionsUpgrades,
2035
2095
  // For use by the editor
2036
2096
  isLintable: true,
2037
- getPublicWidgetOptions: perseusCore.getExpressionPublicWidgetOptions,
2038
2097
  // TODO(LEMS-2656): remove TS suppression
2039
2098
  // @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
2040
2099
  getOneCorrectAnswerFromRubric(rubric) {
@@ -2138,55 +2197,6 @@ const rWidgetRule = /^\[\[\u2603 (([a-z-]+) ([0-9]+))\]\]/;
2138
2197
  const rTypeFromWidgetId = /^([a-z-]+) ([0-9]+)$/;
2139
2198
  const rWidgetParts = new RegExp(rWidgetRule.source + "$");
2140
2199
  const snowman = "\u2603";
2141
- const seededRNG$1 = function (seed) {
2142
- let randomSeed = seed;
2143
- return function () {
2144
- // Robert Jenkins' 32 bit integer hash function.
2145
- let seed = randomSeed;
2146
- seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
2147
- seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
2148
- seed = seed + 0x165667b1 + (seed << 5) & 0xffffffff;
2149
- seed = (seed + 0xd3a2646c ^ seed << 9) & 0xffffffff;
2150
- seed = seed + 0xfd7046c5 + (seed << 3) & 0xffffffff;
2151
- seed = (seed ^ 0xb55a4f09 ^ seed >>> 16) & 0xffffffff;
2152
- return (randomSeed = seed & 0xfffffff) / 0x10000000;
2153
- };
2154
- };
2155
-
2156
- // Shuffle an array using a given random seed or function.
2157
- // If `ensurePermuted` is true, the input and ouput are guaranteed to be
2158
- // distinct permutations.
2159
- function shuffle$3(array, randomSeed) {
2160
- let ensurePermuted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
2161
- // Always return a copy of the input array
2162
- const shuffled = ___default["default"].clone(array);
2163
-
2164
- // Handle edge cases (input array is empty or uniform)
2165
- if (!shuffled.length || ___default["default"].all(shuffled, function (value) {
2166
- return ___default["default"].isEqual(value, shuffled[0]);
2167
- })) {
2168
- return shuffled;
2169
- }
2170
- let random;
2171
- if (typeof randomSeed === "function") {
2172
- random = randomSeed;
2173
- } else {
2174
- random = seededRNG$1(randomSeed);
2175
- }
2176
- do {
2177
- // Fischer-Yates shuffle
2178
- for (let top = shuffled.length; top > 0; top--) {
2179
- const newEnd = Math.floor(random() * top);
2180
- const temp = shuffled[newEnd];
2181
-
2182
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
2183
- shuffled[newEnd] = shuffled[top - 1];
2184
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
2185
- shuffled[top - 1] = temp;
2186
- }
2187
- } while (ensurePermuted && ___default["default"].isEqual(array, shuffled));
2188
- return shuffled;
2189
- }
2190
2200
 
2191
2201
  /**
2192
2202
  * TODO(somewhatabstract, FEI-3463):
@@ -2617,7 +2627,6 @@ const textarea = {
2617
2627
  * to translate it but don't need the extra dollar signs.
2618
2628
  */
2619
2629
  const unescapeMathMode$1 = label => label.startsWith("$") && label.endsWith("$") ? label.slice(1, -1) : label;
2620
- const random$1 = seededRNG$1(new Date().getTime() & 0xffffffff);
2621
2630
  const Util = {
2622
2631
  inputPathsEqual,
2623
2632
  nestedMap: nestedMap$2,
@@ -2625,8 +2634,6 @@ const Util = {
2625
2634
  rTypeFromWidgetId,
2626
2635
  rWidgetParts,
2627
2636
  snowman,
2628
- seededRNG: seededRNG$1,
2629
- shuffle: shuffle$3,
2630
2637
  split,
2631
2638
  firstNumericalParse: firstNumericalParse$1,
2632
2639
  stringArrayOfSize: stringArrayOfSize$1,
@@ -2654,8 +2661,7 @@ const Util = {
2654
2661
  getSvgUrl: getSvgUrl,
2655
2662
  getDataUrl: getDataUrl,
2656
2663
  textarea,
2657
- unescapeMathMode: unescapeMathMode$1,
2658
- random: random$1
2664
+ unescapeMathMode: unescapeMathMode$1
2659
2665
  };
2660
2666
 
2661
2667
  // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
@@ -16010,12 +16016,17 @@ class ImageLoader extends React__namespace.Component {
16010
16016
  };
16011
16017
  }
16012
16018
  const staticUrl = getDependencies().staticUrl;
16013
- return /*#__PURE__*/React__namespace.createElement("img", _extends({
16014
- tabIndex: 0,
16015
- src: staticUrl(src),
16016
- onKeyUp: onKeyUp,
16017
- onKeyDown: onKeyDown
16018
- }, imgProps));
16019
+ return (
16020
+ /*#__PURE__*/
16021
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
16022
+ React__namespace.createElement("img", _extends({
16023
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- TODO(LEMS-2871): Address a11y error
16024
+ tabIndex: 0,
16025
+ src: staticUrl(src),
16026
+ onKeyUp: onKeyUp,
16027
+ onKeyDown: onKeyDown
16028
+ }, imgProps))
16029
+ );
16019
16030
  };
16020
16031
  render() {
16021
16032
  switch (this.state.status) {
@@ -16689,16 +16700,20 @@ class Zoomable extends React__namespace.Component {
16689
16700
  WebkitTapHighlightColor: "transparent",
16690
16701
  ...transitionStyle
16691
16702
  };
16692
- return /*#__PURE__*/React__namespace.createElement("span", {
16693
- onClick: this.handleClick,
16694
- onClickCapture: this.handleClickIfZoomed,
16695
- onTouchCancelCapture: this.stopPropagationIfZoomed,
16696
- onTouchEndCapture: this.stopPropagationIfZoomed,
16697
- onTouchStartCapture: this.stopPropagationIfZoomed
16698
- // @ts-expect-error - TS2322 - Type '{ readonly transitionProperty: string; readonly transitionDuration: string; readonly transitionTimingFunction: string; readonly display: "block"; readonly width: "100%"; readonly height: number | ... 1 more ... | undefined; readonly transform: string; readonly transformOrigin: "0 0"; readonly opacity: 0 | 1; readonl...' is not assignable to type 'CSSProperties | undefined'.
16699
- ,
16700
- style: style
16701
- }, this.props.children);
16703
+ return (
16704
+ /*#__PURE__*/
16705
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- TODO(LEMS-2871): Address a11y error
16706
+ React__namespace.createElement("span", {
16707
+ onClick: this.handleClick,
16708
+ onClickCapture: this.handleClickIfZoomed,
16709
+ onTouchCancelCapture: this.stopPropagationIfZoomed,
16710
+ onTouchEndCapture: this.stopPropagationIfZoomed,
16711
+ onTouchStartCapture: this.stopPropagationIfZoomed
16712
+ // @ts-expect-error - TS2322 - Type '{ readonly transitionProperty: string; readonly transitionDuration: string; readonly transitionTimingFunction: string; readonly display: "block"; readonly width: "100%"; readonly height: number | ... 1 more ... | undefined; readonly transform: string; readonly transformOrigin: "0 0"; readonly opacity: 0 | 1; readonl...' is not assignable to type 'CSSProperties | undefined'.
16713
+ ,
16714
+ style: style
16715
+ }, this.props.children)
16716
+ );
16702
16717
  }
16703
16718
  }
16704
16719
 
@@ -17921,9 +17936,6 @@ const getWidget = name => {
17921
17936
  const getWidgetExport = name => {
17922
17937
  return widgets[name] ?? null;
17923
17938
  };
17924
- const getPublicWidgetOptionsFunction = name => {
17925
- return widgets[name]?.getPublicWidgetOptions ?? (i => i);
17926
- };
17927
17939
  const getEditor = name => {
17928
17940
  return ___default["default"].has(editors, name) ? editors[name] : null;
17929
17941
  };
@@ -18060,7 +18072,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
18060
18072
  replaceDeprecatedEditors: replaceDeprecatedEditors,
18061
18073
  getWidget: getWidget,
18062
18074
  getWidgetExport: getWidgetExport,
18063
- getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
18064
18075
  getEditor: getEditor,
18065
18076
  getTransform: getTransform,
18066
18077
  getVersion: getVersion,
@@ -18136,6 +18147,7 @@ class WidgetContainer extends React__namespace.Component {
18136
18147
  "perseus-widget__definition": this.props.type === "definition"
18137
18148
  });
18138
18149
  const type = this.props.type;
18150
+ const userAgent = navigator.userAgent;
18139
18151
  const WidgetType = getWidget(type);
18140
18152
  if (WidgetType == null) {
18141
18153
  // This is for the good of all people!!
@@ -18146,6 +18158,11 @@ class WidgetContainer extends React__namespace.Component {
18146
18158
  className: className
18147
18159
  });
18148
18160
  }
18161
+ let subType = "null";
18162
+ if (type === "interactive-graph") {
18163
+ const props = this.state.widgetProps;
18164
+ subType = props.graph?.type ?? "null";
18165
+ }
18149
18166
  let alignment = this.state.widgetProps.alignment;
18150
18167
  if (alignment === "default") {
18151
18168
  alignment = perseusCore.CoreWidgetRegistry.getDefaultAlignment(type);
@@ -18202,12 +18219,26 @@ class WidgetContainer extends React__namespace.Component {
18202
18219
  widget_type: type,
18203
18220
  widget_id: this.props.id
18204
18221
  },
18205
- onError: () => {
18222
+ onError: error => {
18223
+ // TODO(LEMS-2826): Remove analytics event in LEMS-2826 in favor of ti below.
18206
18224
  analytics.onAnalyticsEvent({
18207
18225
  type: "perseus:widget-rendering-error",
18208
18226
  payload: {
18227
+ widgetSubType: subType,
18228
+ widgetType: type,
18229
+ widgetId: this.props.id,
18230
+ message: error.message,
18231
+ userAgent: userAgent
18232
+ }
18233
+ });
18234
+ analytics.onAnalyticsEvent({
18235
+ type: "perseus:widget-rendering-error:ti",
18236
+ payload: {
18237
+ widgetSubType: subType,
18209
18238
  widgetType: type,
18210
- widgetId: this.props.id
18239
+ widgetId: this.props.id,
18240
+ message: error.message,
18241
+ userAgent: userAgent
18211
18242
  }
18212
18243
  });
18213
18244
  }
@@ -20650,7 +20681,6 @@ var NumericInput$1 = {
20650
20681
  widget: NumericInput,
20651
20682
  transform: propsTransform,
20652
20683
  isLintable: true,
20653
- getPublicWidgetOptions: perseusCore.getNumericInputPublicWidgetOptions,
20654
20684
  // TODO(LEMS-2656): remove TS suppression
20655
20685
  // @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusNumericInputRubric'
20656
20686
  getOneCorrectAnswerFromRubric(rubric) {
@@ -21457,7 +21487,10 @@ class HighlightTooltip extends React__namespace.PureComponent {
21457
21487
  // using div instead of TooltipContent because
21458
21488
  // TooltipContent wouldn't let me overwrite
21459
21489
  // user-select and onClick
21460
- const content = /*#__PURE__*/React__namespace.createElement("div", {
21490
+ const content =
21491
+ /*#__PURE__*/
21492
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- TODO(LEMS-2871): Address a11y error
21493
+ React__namespace.createElement("div", {
21461
21494
  className: aphrodite.css(styles$x.tooltipLabel),
21462
21495
  onClick: this.props.onClick
21463
21496
  }, this.props.label);
@@ -21468,14 +21501,18 @@ class HighlightTooltip extends React__namespace.PureComponent {
21468
21501
  top: `calc(${Math.round(focusRect.top)}px + 0.95em)`,
21469
21502
  height: 0
21470
21503
  };
21471
- return /*#__PURE__*/React__namespace.createElement("div", {
21472
- style: style,
21473
- onMouseEnter: this.props.onMouseEnter,
21474
- onMouseLeave: this.props.onMouseLeave
21475
- }, /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
21476
- content: content,
21477
- opened: true
21478
- }, /*#__PURE__*/React__namespace.createElement("div", null)));
21504
+ return (
21505
+ /*#__PURE__*/
21506
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO(LEMS-2871): Address a11y error
21507
+ React__namespace.createElement("div", {
21508
+ style: style,
21509
+ onMouseEnter: this.props.onMouseEnter,
21510
+ onMouseLeave: this.props.onMouseLeave
21511
+ }, /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
21512
+ content: content,
21513
+ opened: true
21514
+ }, /*#__PURE__*/React__namespace.createElement("div", null)))
21515
+ );
21479
21516
  }
21480
21517
  }
21481
21518
  const styles$x = aphrodite.StyleSheet.create({
@@ -24192,15 +24229,19 @@ const BaseRadio = function (_ref) {
24192
24229
  // content somehow? Would changing our choice of key
24193
24230
  // somehow break something happening inside a choice's
24194
24231
  // child Renderers, by changing when we mount/unmount?
24195
- return /*#__PURE__*/React__namespace.createElement("li", {
24196
- key: i,
24197
- ref: e => listElem = e,
24198
- className: className,
24199
- onClick: clickHandler,
24200
- onTouchStart: labelWrap ? undefined : captureScratchpadTouchStart$1
24201
- }, /*#__PURE__*/React__namespace.createElement(Element, _extends({}, elementProps, {
24202
- ref: ref
24203
- })));
24232
+ return (
24233
+ /*#__PURE__*/
24234
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
24235
+ React__namespace.createElement("li", {
24236
+ key: i,
24237
+ ref: e => listElem = e,
24238
+ className: className,
24239
+ onClick: clickHandler,
24240
+ onTouchStart: labelWrap ? undefined : captureScratchpadTouchStart$1
24241
+ }, /*#__PURE__*/React__namespace.createElement(Element, _extends({}, elementProps, {
24242
+ ref: ref
24243
+ })))
24244
+ );
24204
24245
  })));
24205
24246
 
24206
24247
  // Allow for horizontal scrolling if content is too wide, which may be
@@ -24649,22 +24690,17 @@ class Radio$1 extends React__namespace.Component {
24649
24690
  }
24650
24691
  }
24651
24692
 
24652
- const {
24653
- shuffle: shuffle$2,
24654
- random
24655
- } = Util;
24656
-
24657
24693
  // Transforms the choices for display.
24658
24694
  const _choiceTransform = (widgetOptions, strings, problemNum) => {
24659
24695
  const _maybeRandomize = function (array) {
24660
- const randomSeed = problemNum === undefined ? random : problemNum;
24696
+ const randomSeed = problemNum === undefined ? perseusCore.random : problemNum;
24661
24697
  // NOTE: `problemNum` will only be set when the radio widget is
24662
24698
  // rendered at the root of an exercise question. It will be `undefined`
24663
24699
  // if it's rendered embedded in another widget, such as `graded-group`,
24664
24700
  // or if rendered within an article. This results in a predictable
24665
24701
  // shuffle order. To avoid this we use a random seed when `problemNum`
24666
24702
  // is `undefined`.
24667
- return widgetOptions.randomize ? shuffle$2(array, randomSeed ?? 0) : array;
24703
+ return widgetOptions.randomize ? perseusCore.shuffle(array, randomSeed ?? 0) : array;
24668
24704
  };
24669
24705
  const _addNoneOfAbove = function (choices) {
24670
24706
  let noneOfTheAbove = null;
@@ -24745,8 +24781,7 @@ var Radio = {
24745
24781
  staticTransform: transform,
24746
24782
  version: perseusCore.radioLogic.version,
24747
24783
  propUpgrades: perseusCore.radioLogic.widgetOptionsUpgrades,
24748
- isLintable: true,
24749
- getPublicWidgetOptions: perseusCore.getRadioPublicWidgetOptions
24784
+ isLintable: true
24750
24785
  };
24751
24786
 
24752
24787
  /* eslint-disable import/no-named-as-default */
@@ -24906,8 +24941,7 @@ class Categorizer extends React__namespace.Component {
24906
24941
  const isMobile = this.props.apiOptions.isMobile;
24907
24942
  let indexedItems = this.props.items.map((item, n) => [item, n]);
24908
24943
  if (this.props.randomizeItems) {
24909
- // @ts-expect-error - TS4104 - The type 'readonly (string | number)[][]' is 'readonly' and cannot be assigned to the mutable type '(string | number)[][]'. | TS2345 - Argument of type 'number | null | undefined' is not assignable to parameter of type 'number | RNG'.
24910
- indexedItems = Util.shuffle(indexedItems, this.props.problemNum);
24944
+ indexedItems = perseusCore.shuffle(indexedItems, this.props.problemNum);
24911
24945
  }
24912
24946
  const table = /*#__PURE__*/React__namespace.createElement("table", {
24913
24947
  className: "categorizer-table " + aphrodite.css(styles$n.mobileTable)
@@ -24933,9 +24967,7 @@ class Categorizer extends React__namespace.Component {
24933
24967
  const uniqueId = self.state.uniqueId + "_" + itemNum;
24934
24968
  return /*#__PURE__*/React__namespace.createElement("tr", {
24935
24969
  key: itemNum
24936
- }, /*#__PURE__*/React__namespace.createElement("td", null, /*#__PURE__*/React__namespace.createElement(Renderer
24937
- // @ts-expect-error - TS2322 - Type 'string | number' is not assignable to type 'string | undefined'.
24938
- , {
24970
+ }, /*#__PURE__*/React__namespace.createElement("td", null, /*#__PURE__*/React__namespace.createElement(Renderer, {
24939
24971
  content: item,
24940
24972
  linterContext: this.props.linterContext,
24941
24973
  strings: this.context.strings
@@ -25035,8 +25067,7 @@ var Categorizer$1 = {
25035
25067
  staticTransform: editorProps => {
25036
25068
  return ___default["default"].pick(editorProps, "items", "categories", "values", "randomizeItems");
25037
25069
  },
25038
- isLintable: true,
25039
- getPublicWidgetOptions: perseusCore.getCategorizerPublicWidgetOptions
25070
+ isLintable: true
25040
25071
  };
25041
25072
 
25042
25073
  /**
@@ -25246,8 +25277,7 @@ var CSProgram$1 = {
25246
25277
  name: "cs-program",
25247
25278
  displayName: "CS Program",
25248
25279
  widget: CSProgram,
25249
- hidden: true,
25250
- getPublicWidgetOptions: perseusCore.getCSProgramPublicWidgetOptions
25280
+ hidden: true
25251
25281
  };
25252
25282
 
25253
25283
  const getPromptJSON$l = renderProps => {
@@ -25463,8 +25493,7 @@ var Dropdown$1 = {
25463
25493
  displayName: "Drop down",
25464
25494
  accessible: true,
25465
25495
  widget: Dropdown,
25466
- transform: optionsTransform,
25467
- getPublicWidgetOptions: perseusCore.getDropdownPublicWidgetOptions
25496
+ transform: optionsTransform
25468
25497
  };
25469
25498
 
25470
25499
  const getPromptJSON$j = renderProps => {
@@ -26956,8 +26985,7 @@ var Grapher$1 = {
26956
26985
  hidden: true,
26957
26986
  widget: Grapher,
26958
26987
  transform: propTransform$2,
26959
- staticTransform: staticTransform$4,
26960
- getPublicWidgetOptions: perseusCore.getGrapherPublicWidgetOptions
26988
+ staticTransform: staticTransform$4
26961
26989
  };
26962
26990
 
26963
26991
  const getPromptJSON$f = rendererJSON => {
@@ -27251,12 +27279,16 @@ class Iframe extends React__namespace.Component {
27251
27279
  // that we need. This makes it a bit safer in case some content
27252
27280
  // creator "went wild".
27253
27281
  // http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
27254
- return /*#__PURE__*/React__namespace.createElement("iframe", {
27255
- sandbox: sandboxProperties,
27256
- style: style,
27257
- src: url,
27258
- allowFullScreen: this.props.allowFullScreen
27259
- });
27282
+ return (
27283
+ /*#__PURE__*/
27284
+ // eslint-disable-next-line jsx-a11y/iframe-has-title -- TODO(LEMS-2871): Address a11y error
27285
+ React__namespace.createElement("iframe", {
27286
+ sandbox: sandboxProperties,
27287
+ style: style,
27288
+ src: url,
27289
+ allowFullScreen: this.props.allowFullScreen
27290
+ })
27291
+ );
27260
27292
  }
27261
27293
  }
27262
27294
  var Iframe$1 = {
@@ -27264,8 +27296,7 @@ var Iframe$1 = {
27264
27296
  displayName: "Iframe (deprecated)",
27265
27297
  widget: Iframe,
27266
27298
  // Let's not expose it to all content creators yet
27267
- hidden: true,
27268
- getPublicWidgetOptions: perseusCore.getIFramePublicWidgetOptions
27299
+ hidden: true
27269
27300
  };
27270
27301
 
27271
27302
  const getPromptJSON$d = renderProps => {
@@ -27282,7 +27313,7 @@ const getPromptJSON$d = renderProps => {
27282
27313
 
27283
27314
  const defaultBoxSize = 400;
27284
27315
  const defaultRange = [0, 10];
27285
- const defaultBackgroundImage$2 = {
27316
+ const defaultBackgroundImage$1 = {
27286
27317
  url: null,
27287
27318
  width: 0,
27288
27319
  height: 0
@@ -27297,7 +27328,7 @@ class ImageWidget extends React__namespace.Component {
27297
27328
  title: "",
27298
27329
  range: [defaultRange, defaultRange],
27299
27330
  box: [defaultBoxSize, defaultBoxSize],
27300
- backgroundImage: defaultBackgroundImage$2,
27331
+ backgroundImage: defaultBackgroundImage$1,
27301
27332
  labels: [],
27302
27333
  alt: "",
27303
27334
  caption: "",
@@ -28068,306 +28099,6 @@ var Interactive = {
28068
28099
  hidden: true
28069
28100
  };
28070
28101
 
28071
- /* eslint-disable @khanacademy/ts-no-error-suppressions */
28072
- const defaultBackgroundImage$1 = {
28073
- url: null
28074
- };
28075
-
28076
- /* Style objects */
28077
- const defaultInstructionsStyle = {
28078
- fontStyle: "italic",
28079
- fontWeight: "bold",
28080
- fontSize: "32px",
28081
- width: "100%",
28082
- height: "100%",
28083
- textAlign: "center",
28084
- backgroundColor: "white",
28085
- position: "absolute",
28086
- zIndex: 1,
28087
- transition: "opacity .25s ease-in-out"
28088
- };
28089
- const instructionsTextStyle = {
28090
- position: "relative",
28091
- top: "25%"
28092
- };
28093
- function numSteps$1(range, step) {
28094
- return Math.floor((range[1] - range[0]) / step);
28095
- }
28096
- class Graph extends React__namespace.Component {
28097
- graphieDivRef = /*#__PURE__*/React__namespace.createRef();
28098
- protractor;
28099
- ruler;
28100
- _graphie;
28101
- // @ts-expect-error - TS2564 - Property '_hasSetupGraphieThisUpdate' has no initializer and is not definitely assigned in the constructor.
28102
- _hasSetupGraphieThisUpdate;
28103
- // @ts-expect-error - TS2564 - Property '_shouldSetupGraphie' has no initializer and is not definitely assigned in the constructor.
28104
- _shouldSetupGraphie;
28105
- static defaultProps = {
28106
- labels: ["x", "y"],
28107
- range: [[-10, 10], [-10, 10]],
28108
- step: [1, 1],
28109
- gridStep: [1, 1],
28110
- snapStep: [0.5, 0.5],
28111
- markings: "graph",
28112
- backgroundImage: defaultBackgroundImage$1,
28113
- showProtractor: false,
28114
- showRuler: false,
28115
- rulerLabel: "",
28116
- rulerTicks: 10,
28117
- isMobile: false
28118
- };
28119
- componentDidMount() {
28120
- this._setupGraphie(true);
28121
- }
28122
- UNSAFE_componentWillReceiveProps(nextProps) {
28123
- const potentialChanges = ["labels", "range", "step", "markings", "showProtractor", "showRuler", "rulerLabel", "rulerTicks", "gridStep", "snapStep"];
28124
- const self = this;
28125
- ___default["default"].each(potentialChanges, function (prop) {
28126
- if (!___default["default"].isEqual(self.props[prop], nextProps[prop])) {
28127
- self._shouldSetupGraphie = true;
28128
- }
28129
- });
28130
- }
28131
- componentDidUpdate() {
28132
- // Only setupGraphie once per componentDidUpdate().
28133
- // See explanation in setupGraphie().
28134
- this._hasSetupGraphieThisUpdate = false;
28135
- if (this._shouldSetupGraphie) {
28136
- this._setupGraphie(false);
28137
- this._shouldSetupGraphie = false;
28138
- }
28139
- }
28140
-
28141
- /* Reset the graphie canvas to its initial state
28142
- *
28143
- * Use when re-rendering the parent component and you need a blank
28144
- * graphie.
28145
- */
28146
- reset = () => {
28147
- this._setupGraphie(false);
28148
- };
28149
- graphie = () => {
28150
- return this._graphie;
28151
- };
28152
- pointsFromNormalized = (coordsList, noSnap) => {
28153
- const self = this;
28154
- return ___default["default"].map(coordsList, function (coords) {
28155
- return ___default["default"].map(coords, function (coord, i) {
28156
- const range = self.props.range[i];
28157
- if (noSnap) {
28158
- return range[0] + (range[1] - range[0]) * coord;
28159
- }
28160
- const step = self.props.step[i];
28161
- const nSteps = numSteps$1(range, step);
28162
- const tick = Math.round(coord * nSteps);
28163
- return range[0] + step * tick;
28164
- });
28165
- });
28166
- };
28167
- _setupGraphie = initialMount => {
28168
- // Only setupGraphie once per componentDidUpdate().
28169
- // This prevents this component from rendering graphie
28170
- // and then immediately re-render graphie because its
28171
- // parent component asked it to. This will happen when
28172
- // props on the parent and props on this component both
28173
- // require graphie to be re-rendered.
28174
- if (this._hasSetupGraphieThisUpdate) {
28175
- return;
28176
- }
28177
- const graphieDiv = this.graphieDivRef.current;
28178
- // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'empty' does not exist on type 'JQueryStatic'.
28179
- $__default["default"](graphieDiv).empty();
28180
-
28181
- // Content creators may need to explicitly add the dollar signs so the
28182
- // strings are picked up by our translation tools. However, these math
28183
- // annotations are redundant because we already render all graph labels
28184
- // in math mode. For example, a label value of `$\text{Dollars}$` will
28185
- // be translatable, but we only want to pass the string
28186
- // `\text{Dollars}` to the Graph widget.
28187
- const labels = this.props.labels.map(label => Util.unescapeMathMode(label));
28188
- const range = this.props.range;
28189
- // @ts-expect-error - TS2345: Argument of type 'Element | Text | null' is not assignable to parameter of type 'HTMLElement'.
28190
- const graphie = this._graphie = GraphUtils.createGraphie(graphieDiv);
28191
- const gridConfig = this._getGridConfig();
28192
- // @ts-expect-error - TS2339: Property 'snap' does not exist on type 'Graphie'.
28193
- graphie.snap = this.props.snapStep;
28194
- if (this.props.markings === "graph") {
28195
- graphie.graphInit({
28196
- range: range,
28197
- scale: kmath.vector.map(gridConfig, g => g.scale),
28198
- axisArrows: "<->",
28199
- labelFormat: function (s) {
28200
- return "\\small{" + s + "}";
28201
- },
28202
- gridStep: this.props.gridStep,
28203
- tickStep: kmath.vector.map(gridConfig, g => g.tickStep),
28204
- labelStep: 1,
28205
- unityLabels: kmath.vector.map(gridConfig, g => g.unityLabel),
28206
- isMobile: this.props.isMobile
28207
- });
28208
- graphie.label([0, range[1][1]], labels[1], this.props.isMobile ? "below right" : "above");
28209
- graphie.label([range[0][1], 0], labels[0], this.props.isMobile ? "above left" : "right");
28210
- } else if (this.props.markings === "grid") {
28211
- graphie.graphInit({
28212
- range: range,
28213
- scale: kmath.vector.map(gridConfig, g => g.scale),
28214
- gridStep: this.props.gridStep,
28215
- axes: false,
28216
- ticks: false,
28217
- labels: false,
28218
- isMobile: this.props.isMobile
28219
- });
28220
- } else if (this.props.markings === "none") {
28221
- graphie.init({
28222
- range: range,
28223
- scale: kmath.vector.map(gridConfig, g => g.scale),
28224
- isMobile: this.props.isMobile
28225
- });
28226
- }
28227
-
28228
- // Add instructions just before mouse layer
28229
- const visible = 0.5;
28230
- const invisible = 0.0;
28231
- let $instructionsWrapper;
28232
- if (this.props.instructions) {
28233
- $instructionsWrapper = $__default["default"]("<div/>");
28234
- ___default["default"].each(defaultInstructionsStyle, function (value, key) {
28235
- $instructionsWrapper.css(key, value);
28236
- });
28237
- $instructionsWrapper.css("opacity", visible);
28238
- const $instructions = $__default["default"]("<span/>", {
28239
- text: this.props.instructions
28240
- });
28241
- ___default["default"].each(instructionsTextStyle, function (value, key) {
28242
- $instructions.css(key, value);
28243
- });
28244
- $instructionsWrapper.append($instructions);
28245
- // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'append' does not exist on type 'JQueryStatic'.
28246
- $__default["default"](graphieDiv).append($instructionsWrapper);
28247
- } else {
28248
- $instructionsWrapper = undefined;
28249
- }
28250
-
28251
- // Add some handlers for instructions text (if necessary)
28252
- const onMouseDown = $instructionsWrapper || this.props.onMouseDown ? ___default["default"].bind(function (coord) {
28253
- if ($instructionsWrapper) {
28254
- $instructionsWrapper.remove();
28255
- $instructionsWrapper = null;
28256
- }
28257
- // eslint-disable-next-line @babel/no-invalid-this
28258
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
28259
- // eslint-disable-next-line @babel/no-invalid-this
28260
- this.props.onMouseDown(coord);
28261
- }, this) : undefined;
28262
- const onMouseOver = $instructionsWrapper ? function () {
28263
- $instructionsWrapper?.css("opacity", invisible);
28264
- } : undefined;
28265
- const onMouseOut = $instructionsWrapper ? function () {
28266
- $instructionsWrapper?.css("opacity", visible);
28267
- } : undefined;
28268
- /* eslint-enable indent */
28269
-
28270
- graphie.addMouseLayer({
28271
- onClick: this.props.onClick,
28272
- onMouseDown: onMouseDown,
28273
- onMouseOver: onMouseOver,
28274
- onMouseOut: onMouseOut,
28275
- onMouseMove: this.props.onMouseMove,
28276
- allowScratchpad: true,
28277
- setDrawingAreaAvailable: this.props.setDrawingAreaAvailable
28278
- });
28279
- this._updateProtractor();
28280
- this._updateRuler();
28281
-
28282
- // We set this flag before jumping into our callback
28283
- // to avoid recursing if our callback calls reset() itself
28284
- this._hasSetupGraphieThisUpdate = true;
28285
- if (!initialMount && this.props.onGraphieUpdated) {
28286
- // Calling a parent callback in componentDidMount is bad and
28287
- // results in hard-to-reason-about lifecycle problems (esp. with
28288
- // refs), so we do it only on update and rely on the parent to
28289
- // query for the graphie object on initial mount
28290
- this.props.onGraphieUpdated(graphie);
28291
- }
28292
- };
28293
- _getGridConfig = () => {
28294
- return kmath.vector.map(this.props.step, (step, i) => Util.gridDimensionConfig(step, this.props.range[i], this.props.box[i], this.props.gridStep[i]));
28295
- };
28296
- _updateProtractor = () => {
28297
- if (this.protractor) {
28298
- this.protractor.remove();
28299
- }
28300
- if (this.props.showProtractor) {
28301
- // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
28302
- const coord = this.pointsFromNormalized([[0.5, 0.05]])[0];
28303
- this.protractor = this._graphie.protractor(coord);
28304
- }
28305
- };
28306
- _updateRuler = () => {
28307
- if (this.ruler) {
28308
- this.ruler.remove();
28309
- }
28310
- if (this.props.showRuler) {
28311
- // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
28312
- const coord = this.pointsFromNormalized([[0.5, 0.25]])[0];
28313
- const extent = this._graphie.range[0][1] - this._graphie.range[0][0];
28314
- this.ruler = this._graphie.ruler({
28315
- center: coord,
28316
- label: this.props.rulerLabel,
28317
- pixelsPerUnit: this._graphie.scale[0],
28318
- ticksPerUnit: this.props.rulerTicks,
28319
- units: Math.round(0.8 * extent)
28320
- });
28321
- }
28322
- };
28323
- toJSON = () => {
28324
- return ___default["default"].pick(this.props, "range", "step", "markings", "labels", "backgroundImage", "showProtractor", "showRuler", "rulerLabel", "rulerTicks", "gridStep", "snapStep");
28325
- };
28326
- render() {
28327
- let image;
28328
- const imageData = this.props.backgroundImage;
28329
- if (imageData.url) {
28330
- const scale = this.props.box[0] / interactiveSizes$1.defaultBoxSize;
28331
- image = /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref => {
28332
- let {
28333
- setAssetStatus
28334
- } = _ref;
28335
- return /*#__PURE__*/React__namespace.createElement(SvgImage
28336
- // @ts-expect-error - TS2741 - Property 'alt' is missing in type '{ src: any; width: any; height: any; scale: number; responsive: false; }' but required in type 'Pick<Readonly<Props> & Readonly<{ children?: ReactNode; }>, "children" | "height" | "width" | "title" | "alt" | "trackInteraction" | "preloader" | "allowFullBleed" | "extraGraphie" | "overrideAriaHidden">'.
28337
- , {
28338
- src: imageData.url,
28339
- width: imageData.width,
28340
- height: imageData.height,
28341
- scale: scale,
28342
- responsive: false,
28343
- setAssetStatus: setAssetStatus
28344
- });
28345
- });
28346
- } else {
28347
- image = null;
28348
- }
28349
- return /*#__PURE__*/React__namespace.createElement("div", {
28350
- className: "graphie-container blank-background",
28351
- style: {
28352
- width: this.props.box[0],
28353
- height: this.props.box[1]
28354
- }
28355
- // @ts-expect-error - TS2339 - Property 'onMouseOut' does not exist on type 'Graph'.
28356
- ,
28357
- onMouseOut: this.onMouseOut
28358
- // @ts-expect-error - TS2339 - Property 'onMouseOver' does not exist on type 'Graph'.
28359
- ,
28360
- onMouseOver: this.onMouseOver
28361
- // @ts-expect-error - TS2339 - Property 'onClick' does not exist on type 'Graph'.
28362
- ,
28363
- onClick: this.onClick
28364
- }, image, /*#__PURE__*/React__namespace.createElement("div", {
28365
- className: "graphie",
28366
- ref: this.graphieDivRef
28367
- }));
28368
- }
28369
- }
28370
-
28371
28102
  const getPromptJSON$b = (props, userInput) => {
28372
28103
  return {
28373
28104
  type: "interactive-graph",
@@ -28932,13 +28663,19 @@ function escapeIfUnescaped(character) {
28932
28663
  }
28933
28664
  }
28934
28665
 
28935
- function AxisLabels() {
28666
+ function AxisLabels(_ref) {
28667
+ let {
28668
+ i18n
28669
+ } = _ref;
28936
28670
  const {
28937
28671
  range,
28938
28672
  labels,
28939
28673
  width,
28940
28674
  height
28941
28675
  } = useGraphConfig();
28676
+ const {
28677
+ strings
28678
+ } = i18n;
28942
28679
  const yAxisLabelLocation = [0, range[Y][MAX]];
28943
28680
  const xAxisLabelLocation = [range[X][MAX], 0];
28944
28681
  const [xAxisLabelText, yAxisLabelText] = labels;
@@ -28956,6 +28693,7 @@ function AxisLabels() {
28956
28693
  TeX
28957
28694
  } = getDependencies();
28958
28695
  return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("span", {
28696
+ "aria-label": strings.xAxis,
28959
28697
  style: {
28960
28698
  position: "absolute",
28961
28699
  left: x1,
@@ -28964,6 +28702,7 @@ function AxisLabels() {
28964
28702
  transform: "translate(7px, -50%)"
28965
28703
  }
28966
28704
  }, /*#__PURE__*/React__default["default"].createElement(TeX, null, replaceOutsideTeX(xAxisLabelText))), /*#__PURE__*/React__default["default"].createElement("span", {
28705
+ "aria-label": strings.yAxis,
28967
28706
  style: {
28968
28707
  position: "absolute",
28969
28708
  left: x2,
@@ -29772,7 +29511,6 @@ class RangeInput extends React__namespace.Component {
29772
29511
  var components = /*#__PURE__*/Object.freeze({
29773
29512
  __proto__: null,
29774
29513
  ButtonGroup: ButtonGroup,
29775
- Graph: Graph,
29776
29514
  Graphie: Graphie,
29777
29515
  HUD: HUD,
29778
29516
  Icon: Icon,
@@ -29916,9 +29654,9 @@ const LockedFunction = props => {
29916
29654
  const {
29917
29655
  color,
29918
29656
  strokeStyle,
29919
- directionalAxis,
29920
- domain
29657
+ directionalAxis
29921
29658
  } = 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;
29922
29660
  const plotProps = {
29923
29661
  color: perseusCore.lockedFigureColors[color],
29924
29662
  style: strokeStyle,
@@ -29998,6 +29736,11 @@ function Vector(props) {
29998
29736
  }
29999
29737
 
30000
29738
  function srFormatNumber(a, locale, maximumFractionDigits) {
29739
+ const piBasedNumber = getPiMultiple(a);
29740
+ if (piBasedNumber) {
29741
+ return piBasedNumber;
29742
+ }
29743
+
30001
29744
  // adding zero here converts negative zero to positive zero.
30002
29745
  return (0 + a).toLocaleString(locale, {
30003
29746
  maximumFractionDigits: maximumFractionDigits ?? 3,
@@ -30005,6 +29748,77 @@ function srFormatNumber(a, locale, maximumFractionDigits) {
30005
29748
  });
30006
29749
  }
30007
29750
 
29751
+ // Exported for testing
29752
+ function getPiMultiple(a) {
29753
+ // - Save some calculations by checking if this is an integer first.
29754
+ // If a is an integer, it's definitely not a multiple of π.
29755
+ // - 0 should not be presented as a multiple of π.
29756
+ // - If a is greater than 1e12, then it thinks that it's always a
29757
+ // multiple of π even when it's not, due to rounding errors.
29758
+ if (Number.isInteger(a) || a === 0 || a > 1e12) {
29759
+ return null;
29760
+ }
29761
+
29762
+ // Figure out the coefficient before the π.
29763
+ // Example: If a = π/2, then piCoefficient = 0.5
29764
+ // Example: If a = 2π, then piCoefficient = 2
29765
+ const piCoefficient = a / Math.PI;
29766
+ // Truncate the coefficient to account for floating point errors.
29767
+ // π calculations are only accurate up to precision of 1e-12.
29768
+ const truncatedCoefficient = parseFloat(piCoefficient.toFixed(12));
29769
+
29770
+ // If the coefficient is already an integer, then the number
29771
+ // is a multiple of π. Return it here.
29772
+ // Example: If a = 2π, then truncatedCoefficient = 2, so return "2π"
29773
+ if (Number.isInteger(truncatedCoefficient)) {
29774
+ // Return "π" rather than "1π".
29775
+ if (truncatedCoefficient === 1) {
29776
+ return "π";
29777
+ }
29778
+
29779
+ // Return "-π" rather than "-1π".
29780
+ if (truncatedCoefficient === -1) {
29781
+ return "-π";
29782
+ }
29783
+ return truncatedCoefficient + "π";
29784
+ }
29785
+
29786
+ // If the coefficient is not an integer, then we need to
29787
+ // check if it's a multiple of π/2, π/3, π/4, or π/6.
29788
+ // These are π-based values on a unit circle.
29789
+ const acceptableDivisors = [2, 3, 4, 6];
29790
+
29791
+ // Loop through the acceptable divisors and check if the
29792
+ // coefficient is a multiple of (1/divisor).
29793
+ // Example: If a = 5π/6, the coeff is 5/6, which is a multiple of 1/6.
29794
+ // So we return "5π/6".
29795
+ for (const divisor of acceptableDivisors) {
29796
+ // Check if the coefficient is a multiple of (1/divisor) by
29797
+ // multiplying the coefficient by the divisor and checking if
29798
+ // the result is an integer.
29799
+ // Example: If a = 5π/6, then piCoefficient = 5/6. We multiply
29800
+ // by 6 (divisor) to get 5, which is an integer.
29801
+ const coefficientNumerator = parseFloat(
29802
+ // π calculations are only accurate up to precision of 1e-12.
29803
+ (piCoefficient * divisor).toFixed(12));
29804
+ if (Number.isInteger(coefficientNumerator)) {
29805
+ // Handle the case where the coefficient numberator is just 1.
29806
+ // We don't want to write "π/6" as "1π/6"
29807
+ if (coefficientNumerator === 1) {
29808
+ return "π/" + divisor;
29809
+ }
29810
+
29811
+ // Handle the case where the coefficient numberator is just -1.
29812
+ // We don't want to write "π/6" as "-1π/6"
29813
+ if (coefficientNumerator === -1) {
29814
+ return "-π/" + divisor;
29815
+ }
29816
+ return coefficientNumerator + "π/" + divisor;
29817
+ }
29818
+ }
29819
+ return null;
29820
+ }
29821
+
30008
29822
  /**
30009
29823
  * Given a ray and a rectangular box, find the point where the ray intersects
30010
29824
  * the edge of the box. Assumes the `initialPoint` is inside the box.
@@ -31172,6 +30986,34 @@ function range(min, max) {
31172
30986
  return result;
31173
30987
  }
31174
30988
 
30989
+ function Hairlines(props) {
30990
+ const {
30991
+ point
30992
+ } = props;
30993
+ const {
30994
+ range
30995
+ } = useGraphConfig();
30996
+ const [[xMin, xMax], [yMin, yMax]] = range;
30997
+ const [[x, y]] = useTransformVectorsToPixels(point);
30998
+ const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
30999
+ const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
31000
+ const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
31001
+ const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
31002
+ return /*#__PURE__*/React__namespace.createElement("g", null, /*#__PURE__*/React__namespace.createElement("line", {
31003
+ x1: verticalStartX,
31004
+ y1: y,
31005
+ x2: verticalEndX,
31006
+ y2: y,
31007
+ stroke: wonderBlocksTokens.color.blue
31008
+ }), /*#__PURE__*/React__namespace.createElement("line", {
31009
+ x1: x,
31010
+ y1: horizontalStartY,
31011
+ x2: x,
31012
+ y2: horizontalEndY,
31013
+ stroke: wonderBlocksTokens.color.blue
31014
+ }));
31015
+ }
31016
+
31175
31017
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
31176
31018
  // graph.
31177
31019
  const hitboxSizePx = 48;
@@ -31181,7 +31023,6 @@ const hitboxSizePx = 48;
31181
31023
  // on an interactive graph.
31182
31024
  const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
31183
31025
  const {
31184
- range,
31185
31026
  markings,
31186
31027
  showTooltips
31187
31028
  } = useGraphConfig();
@@ -31189,6 +31030,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
31189
31030
  point,
31190
31031
  color = wonderBlocksTokens.color.blue,
31191
31032
  dragging,
31033
+ focused,
31192
31034
  cursor,
31193
31035
  showFocusRing,
31194
31036
  onClick = () => {}
@@ -31202,25 +31044,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
31202
31044
  })?.[0] ?? "blue";
31203
31045
  const pointClasses = classNames("movable-point", dragging && "movable-point--dragging", showFocusRing && "movable-point--focus");
31204
31046
  const [[x, y]] = useTransformVectorsToPixels(point);
31205
- const [[xMin, xMax], [yMin, yMax]] = range;
31206
- const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
31207
- const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
31208
- const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
31209
- const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
31210
- const showHairlines = dragging && markings !== "none";
31211
- const hairlines = /*#__PURE__*/React__namespace.createElement("g", null, /*#__PURE__*/React__namespace.createElement("line", {
31212
- x1: verticalStartX,
31213
- y1: y,
31214
- x2: verticalEndX,
31215
- y2: y,
31216
- stroke: color
31217
- }), /*#__PURE__*/React__namespace.createElement("line", {
31218
- x1: x,
31219
- y1: horizontalStartY,
31220
- x2: x,
31221
- y2: horizontalEndY,
31222
- stroke: color
31223
- }));
31047
+ const showHairlines = (dragging || focused) && markings !== "none";
31224
31048
  const svgForPoint = /*#__PURE__*/React__namespace.createElement("g", {
31225
31049
  ref: hitboxRef,
31226
31050
  className: pointClasses,
@@ -31256,7 +31080,9 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
31256
31080
  },
31257
31081
  "data-testid": "movable-point__center"
31258
31082
  }));
31259
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
31083
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && /*#__PURE__*/React__namespace.createElement(Hairlines, {
31084
+ point: point
31085
+ }), showTooltips ? /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
31260
31086
  autoUpdate: true,
31261
31087
  backgroundColor: wbColorName,
31262
31088
  content: `(${point[X]}, ${point[Y]})`,
@@ -31352,6 +31178,7 @@ function useControlPoint(params) {
31352
31178
  },
31353
31179
  point: point,
31354
31180
  dragging: dragging,
31181
+ focused: focused,
31355
31182
  color: color,
31356
31183
  ref: visiblePointRef,
31357
31184
  showFocusRing: focused
@@ -31569,13 +31396,13 @@ const {
31569
31396
  getClockwiseAngle: getClockwiseAngle$2,
31570
31397
  polar: polar$1
31571
31398
  } = kmath.angles;
31572
- function renderAngleGraph(state, dispatch) {
31399
+ function renderAngleGraph(state, dispatch, i18n) {
31573
31400
  return {
31574
31401
  graph: /*#__PURE__*/React__namespace.createElement(AngleGraph, {
31575
31402
  graphState: state,
31576
31403
  dispatch: dispatch
31577
31404
  }),
31578
- interactiveElementsDescription: null
31405
+ interactiveElementsDescription: getAngleGraphDescription(state, i18n)
31579
31406
  };
31580
31407
  }
31581
31408
  function AngleGraph(props) {
@@ -31586,6 +31413,9 @@ function AngleGraph(props) {
31586
31413
  const {
31587
31414
  graphDimensionsInPixels
31588
31415
  } = useGraphConfig();
31416
+ const i18n = usePerseusI18n();
31417
+ const id = React__namespace.useId();
31418
+ const descriptionId = id + "-description";
31589
31419
  const {
31590
31420
  coords,
31591
31421
  showAngles,
@@ -31635,80 +31465,106 @@ function AngleGraph(props) {
31635
31465
  showAngles: showAngles || false // Whether to show the angle or not
31636
31466
  };
31637
31467
 
31468
+ // Aria strings
31638
31469
  const {
31639
- strings,
31640
- locale
31641
- } = usePerseusI18n();
31642
- const angleMeasure = srFormatNumber(getClockwiseAngle$2([endPoints[0], centerPoint, endPoints[1]], allowReflexAngles), locale);
31643
- const wholeAngleAriaLabel = strings.srAngleGraphAriaLabel;
31644
- const wholeAngleDescription = strings.srAngleGraphAriaDescription({
31645
- angleMeasure,
31646
- vertexX: srFormatNumber(coords[1][X], locale),
31647
- vertexY: srFormatNumber(coords[1][Y], locale),
31648
- startingSideX: srFormatNumber(coords[2][X], locale),
31649
- startingSideY: srFormatNumber(coords[2][Y], locale),
31650
- endingSideX: srFormatNumber(coords[0][X], locale),
31651
- endingSideY: srFormatNumber(coords[0][Y], locale)
31652
- });
31653
- const formatCoordinates = (x, y) => ({
31654
- x: srFormatNumber(x, locale),
31655
- y: srFormatNumber(y, locale)
31656
- });
31657
- const formatSideAriaLabel = (side, newPoint) => {
31658
- const formattedCoordinates = formatCoordinates(newPoint[X], newPoint[Y]);
31659
- const label = side === "terminal" ? strings.srAngleSideAtCoordinates({
31660
- point: 1,
31661
- side: "terminal side",
31662
- ...formattedCoordinates
31663
- }) : strings.srAngleSideAtCoordinates({
31664
- point: 3,
31665
- side: "initial side",
31666
- ...formattedCoordinates
31667
- });
31668
- return label;
31669
- };
31670
- const formatVertexAriaLabel = newPoint => {
31671
- const formattedVertexCoordinates = formatCoordinates(newPoint[X], newPoint[Y]);
31672
- const label = showAngles ? strings.srAngleVertexAtCoordinatesWithAngleMeasure({
31673
- ...formattedVertexCoordinates,
31674
- angleMeasure: angleMeasure
31675
- }) : strings.srAngleSideAtCoordinates({
31676
- point: 2,
31677
- side: "vertex",
31678
- ...formattedVertexCoordinates
31679
- });
31680
- return label;
31681
- };
31682
- const initialSideAriaLabel = formatSideAriaLabel("initial", coords[2]);
31683
- const terminalSideAriaLabel = formatSideAriaLabel("terminal", coords[0]);
31684
- const vertexAriaLabel = formatVertexAriaLabel(coords[1]);
31470
+ srAngleGraphAriaLabel,
31471
+ srAngleGraphAriaDescription,
31472
+ srAngleStartingSide,
31473
+ srAngleEndingSide,
31474
+ srAngleVertex
31475
+ } = describeAngleGraph(graphState, i18n);
31685
31476
 
31686
31477
  // Render the lines, angle, and then movable points
31687
31478
  return /*#__PURE__*/React__namespace.createElement("g", {
31688
- "aria-label": wholeAngleAriaLabel,
31689
- "aria-describedby": "angle-description"
31479
+ "aria-label": srAngleGraphAriaLabel,
31480
+ "aria-describedby": descriptionId
31690
31481
  }, svgLines, /*#__PURE__*/React__namespace.createElement(Angle, angleParams), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31691
31482
  point: coords[1],
31692
31483
  sequenceNumber: 1,
31693
31484
  constrain: p => p,
31694
31485
  onMove: destination => dispatch(actions.angle.movePoint(1, destination)),
31695
- ariaLabel: vertexAriaLabel
31486
+ ariaLabel: srAngleVertex
31696
31487
  }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31697
31488
  point: coords[0],
31698
31489
  sequenceNumber: 2,
31699
31490
  constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
31700
31491
  onMove: destination => dispatch(actions.angle.movePoint(0, destination)),
31701
- ariaLabel: terminalSideAriaLabel
31492
+ ariaLabel: srAngleEndingSide
31702
31493
  }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31703
31494
  point: coords[2],
31704
31495
  sequenceNumber: 3,
31705
31496
  constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
31706
31497
  onMove: destination => dispatch(actions.angle.movePoint(2, destination)),
31707
- ariaLabel: initialSideAriaLabel
31498
+ ariaLabel: srAngleStartingSide
31708
31499
  }), /*#__PURE__*/React__namespace.createElement("g", {
31709
- id: "angle-description",
31500
+ id: descriptionId,
31710
31501
  style: a11y.srOnly
31711
- }, wholeAngleDescription));
31502
+ }, srAngleGraphAriaDescription));
31503
+ }
31504
+ function getAngleGraphDescription(state, i18n) {
31505
+ const {
31506
+ strings,
31507
+ locale
31508
+ } = i18n;
31509
+ const {
31510
+ coords
31511
+ } = state;
31512
+ return strings.srInteractiveElements({
31513
+ elements: strings.srAngleInteractiveElements({
31514
+ vertexX: srFormatNumber(coords[1][X], locale),
31515
+ vertexY: srFormatNumber(coords[1][Y], locale),
31516
+ startingSideX: srFormatNumber(coords[2][X], locale),
31517
+ startingSideY: srFormatNumber(coords[2][Y], locale),
31518
+ endingSideX: srFormatNumber(coords[0][X], locale),
31519
+ endingSideY: srFormatNumber(coords[0][Y], locale)
31520
+ })
31521
+ });
31522
+ }
31523
+ function describeAngleGraph(state, i18n) {
31524
+ const {
31525
+ strings,
31526
+ locale
31527
+ } = i18n;
31528
+ const {
31529
+ coords,
31530
+ allowReflexAngles,
31531
+ showAngles
31532
+ } = state;
31533
+ const [endingSide, vertex, startingSide] = coords;
31534
+ const angleMeasure = srFormatNumber(getClockwiseAngle$2(coords, allowReflexAngles), locale);
31535
+ const srAngleGraphAriaLabel = strings.srAngleGraphAriaLabel;
31536
+ const srAngleGraphAriaDescription = strings.srAngleGraphAriaDescription({
31537
+ angleMeasure,
31538
+ vertexX: srFormatNumber(vertex[X], locale),
31539
+ vertexY: srFormatNumber(vertex[Y], locale),
31540
+ startingSideX: srFormatNumber(startingSide[X], locale),
31541
+ startingSideY: srFormatNumber(startingSide[Y], locale),
31542
+ endingSideX: srFormatNumber(endingSide[X], locale),
31543
+ endingSideY: srFormatNumber(endingSide[Y], locale)
31544
+ });
31545
+ const srAngleStartingSide = strings.srAngleStartingSide({
31546
+ x: srFormatNumber(startingSide[X], locale),
31547
+ y: srFormatNumber(startingSide[Y], locale)
31548
+ });
31549
+ const srAngleEndingSide = strings.srAngleEndingSide({
31550
+ x: srFormatNumber(endingSide[X], locale),
31551
+ y: srFormatNumber(endingSide[Y], locale)
31552
+ });
31553
+ const srAngleVertex = showAngles ? strings.srAngleVertexWithAngleMeasure({
31554
+ x: srFormatNumber(vertex[X], locale),
31555
+ y: srFormatNumber(vertex[Y], locale),
31556
+ angleMeasure
31557
+ }) : strings.srAngleVertex({
31558
+ x: srFormatNumber(vertex[X], locale),
31559
+ y: srFormatNumber(vertex[Y], locale)
31560
+ });
31561
+ return {
31562
+ srAngleGraphAriaLabel,
31563
+ srAngleGraphAriaDescription,
31564
+ srAngleStartingSide,
31565
+ srAngleEndingSide,
31566
+ srAngleVertex
31567
+ };
31712
31568
  }
31713
31569
  const positiveX = [1, 0];
31714
31570
  const negativeX = [-1, 0];
@@ -31852,17 +31708,16 @@ function getRadius(graph) {
31852
31708
  return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
31853
31709
  }
31854
31710
 
31855
- function renderCircleGraph(state, dispatch) {
31711
+ function renderCircleGraph(state, dispatch, i18n) {
31856
31712
  return {
31857
31713
  graph: /*#__PURE__*/React__namespace.createElement(CircleGraph, {
31858
31714
  graphState: state,
31859
31715
  dispatch: dispatch
31860
31716
  }),
31861
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(CircleGraphDescription, {
31862
- state: state
31863
- })
31717
+ interactiveElementsDescription: getCircleGraphDescription(state, i18n)
31864
31718
  };
31865
31719
  }
31720
+ // Exported for testing
31866
31721
  function CircleGraph(props) {
31867
31722
  const {
31868
31723
  dispatch,
@@ -31955,6 +31810,7 @@ function MovableCircle(props) {
31955
31810
  snapStep,
31956
31811
  disableKeyboardInteraction
31957
31812
  } = useGraphConfig();
31813
+ const [focused, setFocused] = React__namespace.useState(false);
31958
31814
  const draggableRef = React.useRef(null);
31959
31815
  const {
31960
31816
  dragging
@@ -31973,7 +31829,9 @@ function MovableCircle(props) {
31973
31829
  ref: draggableRef,
31974
31830
  role: "button",
31975
31831
  tabIndex: disableKeyboardInteraction ? -1 : 0,
31976
- className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
31832
+ className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`,
31833
+ onFocus: () => setFocused(true),
31834
+ onBlur: () => setFocused(false)
31977
31835
  }, /*#__PURE__*/React__namespace.createElement("ellipse", {
31978
31836
  className: "focus-ring",
31979
31837
  cx: centerPx[X],
@@ -31988,19 +31846,29 @@ function MovableCircle(props) {
31988
31846
  rx: radiiPx[X],
31989
31847
  ry: radiiPx[Y]
31990
31848
  }), /*#__PURE__*/React__namespace.createElement(DragHandle, {
31991
- center: center
31849
+ center: center,
31850
+ dragging: dragging,
31851
+ focused: focused
31992
31852
  }));
31993
31853
  }
31994
31854
  const dragHandleDimensions = [24, 14];
31995
31855
  const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
31996
31856
  function DragHandle(props) {
31997
31857
  const {
31998
- center
31858
+ center,
31859
+ dragging,
31860
+ focused
31999
31861
  } = props;
32000
- const cornerRadius = Math.min(...dragHandleDimensions) / 2;
32001
31862
  const [centerPx] = useTransformVectorsToPixels(center);
31863
+ const {
31864
+ markings
31865
+ } = useGraphConfig();
31866
+ const cornerRadius = Math.min(...dragHandleDimensions) / 2;
32002
31867
  const topLeft = mafs.vec.sub(centerPx, mafs.vec.scale(dragHandleDimensions, 0.5));
32003
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
31868
+ const showHairlines = (dragging || focused) && markings !== "none";
31869
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && /*#__PURE__*/React__namespace.createElement(Hairlines, {
31870
+ point: center
31871
+ }), /*#__PURE__*/React__namespace.createElement("rect", {
32004
31872
  className: "movable-circle-handle",
32005
31873
  x: topLeft[X],
32006
31874
  y: topLeft[Y],
@@ -32027,14 +31895,7 @@ function crossProduct(as, bs) {
32027
31895
  }
32028
31896
  return result;
32029
31897
  }
32030
- function CircleGraphDescription(_ref) {
32031
- let {
32032
- state
32033
- } = _ref;
32034
- // The reason that CircleGraphDescription is a component (rather than a
32035
- // function that returns a string) is because it needs to use a
32036
- // hook: `usePerseusI18n`.
32037
- const i18n = usePerseusI18n();
31898
+ function getCircleGraphDescription(state, i18n) {
32038
31899
  const strings = describeCircleGraph(state, i18n);
32039
31900
  return strings.srCircleInteractiveElement;
32040
31901
  }
@@ -32087,15 +31948,13 @@ function describeCircleGraph(state, i18n) {
32087
31948
  };
32088
31949
  }
32089
31950
 
32090
- function renderLinearGraph(state, dispatch) {
31951
+ function renderLinearGraph(state, dispatch, i18n) {
32091
31952
  return {
32092
31953
  graph: /*#__PURE__*/React__namespace.createElement(LinearGraph, {
32093
31954
  graphState: state,
32094
31955
  dispatch: dispatch
32095
31956
  }),
32096
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(LinearGraphDescription, {
32097
- state: state
32098
- })
31957
+ interactiveElementsDescription: getLinearGraphDescription(state, i18n)
32099
31958
  };
32100
31959
  }
32101
31960
  const LinearGraph = (props, key) => {
@@ -32160,14 +32019,7 @@ const LinearGraph = (props, key) => {
32160
32019
  style: a11y.srOnly
32161
32020
  }, slopeString));
32162
32021
  };
32163
- function LinearGraphDescription(_ref) {
32164
- let {
32165
- state
32166
- } = _ref;
32167
- // The reason that LinearGraphDescription is a component (rather than a
32168
- // function that returns a string) is because it needs to use a
32169
- // hook: `usePerseusI18n`.
32170
- const i18n = usePerseusI18n();
32022
+ function getLinearGraphDescription(state, i18n) {
32171
32023
  const strings = describeLinearGraph(state, i18n);
32172
32024
  return strings.srLinearInteractiveElement;
32173
32025
  }
@@ -32211,15 +32063,13 @@ function describeLinearGraph(state, i18n) {
32211
32063
  };
32212
32064
  }
32213
32065
 
32214
- function renderLinearSystemGraph(state, dispatch) {
32066
+ function renderLinearSystemGraph(state, dispatch, i18n) {
32215
32067
  return {
32216
32068
  graph: /*#__PURE__*/React__namespace.createElement(LinearSystemGraph, {
32217
32069
  graphState: state,
32218
32070
  dispatch: dispatch
32219
32071
  }),
32220
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(LinearSystemGraphDescription, {
32221
- state: state
32222
- })
32072
+ interactiveElementsDescription: getLinearSystemGraphDescription(state, i18n)
32223
32073
  };
32224
32074
  }
32225
32075
  const LinearSystemGraph = props => {
@@ -32312,19 +32162,9 @@ const LinearSystemGraph = props => {
32312
32162
  }, slopeDescription));
32313
32163
  }));
32314
32164
  };
32315
- function LinearSystemGraphDescription(_ref3) {
32316
- let {
32317
- state
32318
- } = _ref3;
32319
- // The reason that LinearSystemGraphDescription is a component (rather
32320
- // than a function that returns a string) is because it needs to use a
32321
- // hook: `usePerseusI18n`.
32322
- const i18n = usePerseusI18n();
32323
- return describeLinearSystemGraph(state, i18n);
32324
- }
32325
32165
 
32326
32166
  // Exported for testing
32327
- function describeLinearSystemGraph(state, i18n) {
32167
+ function getLinearSystemGraphDescription(state, i18n) {
32328
32168
  const {
32329
32169
  strings,
32330
32170
  locale
@@ -32350,15 +32190,13 @@ function describeLinearSystemGraph(state, i18n) {
32350
32190
  });
32351
32191
  }
32352
32192
 
32353
- function renderPointGraph(state, dispatch) {
32193
+ function renderPointGraph(state, dispatch, i18n) {
32354
32194
  return {
32355
32195
  graph: /*#__PURE__*/React__namespace.createElement(PointGraph, {
32356
32196
  graphState: state,
32357
32197
  dispatch: dispatch
32358
32198
  }),
32359
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(PointGraphDescription, {
32360
- state: state
32361
- })
32199
+ interactiveElementsDescription: getPointGraphDescription(state, i18n)
32362
32200
  };
32363
32201
  }
32364
32202
  function PointGraph(props) {
@@ -32380,7 +32218,9 @@ function PointGraph(props) {
32380
32218
  if (focusedIndex != null) {
32381
32219
  pointsRef.current[focusedIndex]?.focus();
32382
32220
  }
32383
- }, [props.graphState.focusedPointIndex, pointsRef]);
32221
+ }, [props.graphState.focusedPointIndex,
32222
+ // Ensure that we re-focus if a point is deleted.
32223
+ props.graphState.coords.length, pointsRef]);
32384
32224
  const statefulProps = {
32385
32225
  ...props,
32386
32226
  graphConfig,
@@ -32452,18 +32292,9 @@ function UnlimitedPointGraph(statefulProps) {
32452
32292
  }
32453
32293
  })));
32454
32294
  }
32455
- function PointGraphDescription(_ref) {
32456
- let {
32457
- state
32458
- } = _ref;
32459
- // PointGraphDescription needs to `usePerseusI18n`, so it has to be a
32460
- // component rather than a function that simply returns a string.
32461
- const i18n = usePerseusI18n();
32462
- return describePointGraph(state, i18n);
32463
- }
32464
32295
 
32465
32296
  // Exported for testing
32466
- function describePointGraph(state, i18n) {
32297
+ function getPointGraphDescription(state, i18n) {
32467
32298
  const {
32468
32299
  strings,
32469
32300
  locale
@@ -32471,8 +32302,8 @@ function describePointGraph(state, i18n) {
32471
32302
  if (state.coords.length === 0) {
32472
32303
  return strings.srNoInteractiveElements;
32473
32304
  }
32474
- const pointDescriptions = state.coords.map((_ref2, index) => {
32475
- let [x, y] = _ref2;
32305
+ const pointDescriptions = state.coords.map((_ref, index) => {
32306
+ let [x, y] = _ref;
32476
32307
  return strings.srPointAtCoordinates({
32477
32308
  num: index + 1,
32478
32309
  x: srFormatNumber(x, locale),
@@ -32552,7 +32383,9 @@ const PolygonGraph = props => {
32552
32383
  if (focusedIndex != null) {
32553
32384
  pointsRef.current[focusedIndex]?.focus();
32554
32385
  }
32555
- }, [props.graphState.focusedPointIndex, pointsRef]);
32386
+ }, [props.graphState.focusedPointIndex,
32387
+ // Ensure that we re-focus if a point is deleted.
32388
+ props.graphState.coords.length, pointsRef]);
32556
32389
 
32557
32390
  // If the unlimited polygon is rendered with 3 or more coordinates
32558
32391
  // Close the polygon, but only on first render.
@@ -33135,15 +32968,13 @@ function describeQuadraticGraph(state, i18n) {
33135
32968
  };
33136
32969
  }
33137
32970
 
33138
- function renderRayGraph(state, dispatch) {
32971
+ function renderRayGraph(state, dispatch, i18n) {
33139
32972
  return {
33140
32973
  graph: /*#__PURE__*/React__namespace.createElement(RayGraph, {
33141
32974
  graphState: state,
33142
32975
  dispatch: dispatch
33143
32976
  }),
33144
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(RayGraphDescription, {
33145
- state: state
33146
- })
32977
+ interactiveElementsDescription: getRayGraphDescription(state, i18n)
33147
32978
  };
33148
32979
  }
33149
32980
  const RayGraph = props => {
@@ -33197,14 +33028,7 @@ const RayGraph = props => {
33197
33028
  style: a11y.srOnly
33198
33029
  }, srRayPoints));
33199
33030
  };
33200
- function RayGraphDescription(_ref) {
33201
- let {
33202
- state
33203
- } = _ref;
33204
- // The reason that RayGraphDescription is a component (rather than a
33205
- // function that returns a string) is because it needs to use a
33206
- // hook: `usePerseusI18n`.
33207
- const i18n = usePerseusI18n();
33031
+ function getRayGraphDescription(state, i18n) {
33208
33032
  const strings = describeRayGraph(state, i18n);
33209
33033
  return strings.srRayInteractiveElement;
33210
33034
  }
@@ -33254,15 +33078,13 @@ function describeRayGraph(state, i18n) {
33254
33078
  };
33255
33079
  }
33256
33080
 
33257
- function renderSegmentGraph(state, dispatch) {
33081
+ function renderSegmentGraph(state, dispatch, i18n) {
33258
33082
  return {
33259
33083
  graph: /*#__PURE__*/React__namespace.createElement(SegmentGraph, {
33260
33084
  graphState: state,
33261
33085
  dispatch: dispatch
33262
33086
  }),
33263
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(SegmentGraphDescription, {
33264
- state: state
33265
- })
33087
+ interactiveElementsDescription: getSegmentGraphDescription(state, i18n)
33266
33088
  };
33267
33089
  }
33268
33090
  const SegmentGraph = _ref => {
@@ -33356,25 +33178,15 @@ const SegmentGraph = _ref => {
33356
33178
  function getLengthOfSegment(segment) {
33357
33179
  return kmath.point.distanceToPoint(...segment);
33358
33180
  }
33359
- function SegmentGraphDescription(_ref2) {
33360
- let {
33361
- state
33362
- } = _ref2;
33363
- // The reason that SegmentGraphDescription is a component (rather than a
33364
- // function that returns a string) is because it needs to use a
33365
- // hook: `usePerseusI18n`.
33366
- const i18n = usePerseusI18n();
33367
- return describeSegmentGraph(state, i18n);
33368
- }
33369
33181
 
33370
33182
  // Exported for testing
33371
- function describeSegmentGraph(state, i18n) {
33183
+ function getSegmentGraphDescription(state, i18n) {
33372
33184
  const {
33373
33185
  strings,
33374
33186
  locale
33375
33187
  } = i18n;
33376
- const segmentDescriptions = state.coords.map((_ref3, index) => {
33377
- let [point1, point2] = _ref3;
33188
+ const segmentDescriptions = state.coords.map((_ref2, index) => {
33189
+ let [point1, point2] = _ref2;
33378
33190
  return strings.srMultipleSegmentIndividualLabel({
33379
33191
  point1X: srFormatNumber(point1[X], locale),
33380
33192
  point1Y: srFormatNumber(point1[Y], locale),
@@ -33388,13 +33200,13 @@ function describeSegmentGraph(state, i18n) {
33388
33200
  });
33389
33201
  }
33390
33202
 
33391
- function renderSinusoidGraph(state, dispatch) {
33203
+ function renderSinusoidGraph(state, dispatch, i18n) {
33392
33204
  return {
33393
33205
  graph: /*#__PURE__*/React__namespace.createElement(SinusoidGraph, {
33394
33206
  graphState: state,
33395
33207
  dispatch: dispatch
33396
33208
  }),
33397
- interactiveElementsDescription: null
33209
+ interactiveElementsDescription: getSinusoidDescription(state, i18n)
33398
33210
  };
33399
33211
  }
33400
33212
  function SinusoidGraph(props) {
@@ -33402,6 +33214,9 @@ function SinusoidGraph(props) {
33402
33214
  dispatch,
33403
33215
  graphState
33404
33216
  } = props;
33217
+ const i18n = usePerseusI18n();
33218
+ const id = React__namespace.useId();
33219
+ const descriptionId = id + "-description";
33405
33220
 
33406
33221
  // Destructure the coordinates from the graph state
33407
33222
  // Note: The order of the coordinates is important:
@@ -33426,15 +33241,30 @@ function SinusoidGraph(props) {
33426
33241
  if (coeffs !== undefined) {
33427
33242
  coeffRef.current = coeffs;
33428
33243
  }
33429
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
33244
+
33245
+ // Aria strings
33246
+ const {
33247
+ srSinusoidGraph,
33248
+ srSinusoidDescription,
33249
+ srSinusoidRootPoint,
33250
+ srSinusoidPeakPoint
33251
+ } = describeSinusoidGraph(graphState, i18n);
33252
+ return /*#__PURE__*/React__namespace.createElement("g", {
33253
+ // Outer graph minimal description
33254
+ "aria-label": srSinusoidGraph,
33255
+ "aria-describedby": descriptionId
33256
+ }, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
33430
33257
  y: x => computeSine(x, coeffRef.current),
33431
33258
  color: wonderBlocksTokens.color.blue
33432
33259
  }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
33260
+ ariaLabel: i === 0 ? srSinusoidRootPoint : srSinusoidPeakPoint,
33433
33261
  key: "point-" + i,
33434
33262
  point: coord,
33435
33263
  sequenceNumber: i + 1,
33436
33264
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
33437
- })));
33265
+ })), /*#__PURE__*/React__namespace.createElement("g", {
33266
+ id: descriptionId
33267
+ }, srSinusoidDescription));
33438
33268
  }
33439
33269
 
33440
33270
  // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
@@ -33472,6 +33302,52 @@ const getSinusoidCoefficients$1 = coords => {
33472
33302
  verticalOffset
33473
33303
  };
33474
33304
  };
33305
+ function getSinusoidDescription(state, i18n) {
33306
+ const strings = describeSinusoidGraph(state, i18n);
33307
+ return strings.srSinusoidInteractiveElements;
33308
+ }
33309
+ function describeSinusoidGraph(state, i18n) {
33310
+ const {
33311
+ strings,
33312
+ locale
33313
+ } = i18n;
33314
+ const {
33315
+ coords
33316
+ } = state;
33317
+ const [root, peak] = coords;
33318
+ const diffX = Math.abs(peak[X] - root[X]);
33319
+ const diffY = Math.abs(peak[Y] - root[Y]);
33320
+ const srSinusoidGraph = strings.srSinusoidGraph;
33321
+ const srSinusoidDescription = strings.srSinusoidDescription({
33322
+ minValue: srFormatNumber(root[Y] - diffY, locale),
33323
+ maxValue: srFormatNumber(root[Y] + diffY, locale),
33324
+ cycleStart: srFormatNumber(root[X] - 2 * diffX, locale),
33325
+ cycleEnd: srFormatNumber(root[X] + 2 * diffX, locale)
33326
+ });
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
+ });
33335
+ const srSinusoidInteractiveElements = strings.srInteractiveElements({
33336
+ elements: strings.srSinusoidInteractiveElements({
33337
+ point1X: srFormatNumber(root[X], locale),
33338
+ point1Y: srFormatNumber(root[Y], locale),
33339
+ point2X: srFormatNumber(peak[X], locale),
33340
+ point2Y: srFormatNumber(peak[Y], locale)
33341
+ })
33342
+ });
33343
+ return {
33344
+ srSinusoidGraph,
33345
+ srSinusoidDescription,
33346
+ srSinusoidRootPoint,
33347
+ srSinusoidPeakPoint,
33348
+ srSinusoidInteractiveElements
33349
+ };
33350
+ }
33475
33351
 
33476
33352
  const {
33477
33353
  calculateAngleInDegrees,
@@ -33642,6 +33518,7 @@ const MafsGraph = props => {
33642
33518
  const descriptionId = `interactive-graph-description-${uniqueId}`;
33643
33519
  const interactiveElementsDescriptionId = `interactive-graph-interactive-elements-description-${uniqueId}`;
33644
33520
  const unlimitedGraphKeyboardPromptId = `unlimited-graph-keyboard-prompt-${uniqueId}`;
33521
+ const instructionsId = `instructions-${uniqueId}`;
33645
33522
  const graphRef = React__namespace.useRef(null);
33646
33523
  const {
33647
33524
  analytics
@@ -33669,6 +33546,7 @@ const MafsGraph = props => {
33669
33546
  const interactionPrompt = isUnlimitedGraphState(state) && state.showKeyboardInteractionInvitation;
33670
33547
  wonderBlocksCore.useOnMountEffect(() => {
33671
33548
  analytics.onAnalyticsEvent({
33549
+ // TODO(LEMS-2827): Remove analytics event in LEMS-2827 in favor of ti below.
33672
33550
  type: "perseus:interactive-graph-widget:rendered",
33673
33551
  payload: {
33674
33552
  type,
@@ -33676,6 +33554,14 @@ const MafsGraph = props => {
33676
33554
  widgetId: "interactive-graph"
33677
33555
  }
33678
33556
  });
33557
+ analytics.onAnalyticsEvent({
33558
+ type: "perseus:widget:rendered:ti",
33559
+ payload: {
33560
+ widgetSubType: type,
33561
+ widgetType: "INTERACTIVE_GRAPH",
33562
+ widgetId: "interactive-graph"
33563
+ }
33564
+ });
33679
33565
  });
33680
33566
  const {
33681
33567
  graph,
@@ -33719,7 +33605,7 @@ const MafsGraph = props => {
33719
33605
  handleKeyboardEvent(event, state, dispatch);
33720
33606
  },
33721
33607
  "aria-label": fullGraphAriaLabel,
33722
- "aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId),
33608
+ "aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId, state.type !== "none" && instructionsId),
33723
33609
  ref: graphRef,
33724
33610
  tabIndex: 0,
33725
33611
  onFocus: event => {
@@ -33736,7 +33622,11 @@ const MafsGraph = props => {
33736
33622
  id: interactiveElementsDescriptionId,
33737
33623
  tabIndex: -1,
33738
33624
  className: "mafs-sr-only"
33739
- }, interactiveElementsDescription), /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
33625
+ }, interactiveElementsDescription), state.type !== "none" && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
33626
+ id: instructionsId,
33627
+ tabIndex: -1,
33628
+ className: "mafs-sr-only"
33629
+ }, isUnlimitedGraphState(state) ? strings.srUnlimitedGraphInstructions : strings.srGraphInstructions), /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
33740
33630
  box: props.box,
33741
33631
  backgroundImage: props.backgroundImage
33742
33632
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
@@ -33745,7 +33635,9 @@ const MafsGraph = props => {
33745
33635
  bottom: 0,
33746
33636
  left: 0
33747
33637
  }
33748
- }, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null)), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View
33638
+ }, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, {
33639
+ i18n: i18n
33640
+ })), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View
33749
33641
  // If we have locked figures, we set aria-hidden
33750
33642
  // to false so that screen readers can read the
33751
33643
  // locked figures. If we don't have any locked
@@ -34082,25 +33974,25 @@ const renderGraphElements = props => {
34082
33974
  } = state;
34083
33975
  switch (type) {
34084
33976
  case "angle":
34085
- return renderAngleGraph(state, dispatch);
33977
+ return renderAngleGraph(state, dispatch, i18n);
34086
33978
  case "segment":
34087
- return renderSegmentGraph(state, dispatch);
33979
+ return renderSegmentGraph(state, dispatch, i18n);
34088
33980
  case "linear-system":
34089
- return renderLinearSystemGraph(state, dispatch);
33981
+ return renderLinearSystemGraph(state, dispatch, i18n);
34090
33982
  case "linear":
34091
- return renderLinearGraph(state, dispatch);
33983
+ return renderLinearGraph(state, dispatch, i18n);
34092
33984
  case "ray":
34093
- return renderRayGraph(state, dispatch);
33985
+ return renderRayGraph(state, dispatch, i18n);
34094
33986
  case "polygon":
34095
33987
  return renderPolygonGraph(state, dispatch, i18n, markings);
34096
33988
  case "point":
34097
- return renderPointGraph(state, dispatch);
33989
+ return renderPointGraph(state, dispatch, i18n);
34098
33990
  case "circle":
34099
- return renderCircleGraph(state, dispatch);
33991
+ return renderCircleGraph(state, dispatch, i18n);
34100
33992
  case "quadratic":
34101
33993
  return renderQuadraticGraph(state, dispatch, i18n);
34102
33994
  case "sinusoid":
34103
- return renderSinusoidGraph(state, dispatch);
33995
+ return renderSinusoidGraph(state, dispatch, i18n);
34104
33996
  case "none":
34105
33997
  return {
34106
33998
  graph: null,
@@ -34520,13 +34412,13 @@ const {
34520
34412
  polar
34521
34413
  } = kmath.angles;
34522
34414
  const {
34523
- angleMeasures: angleMeasures$1,
34524
- ccw: ccw$1,
34525
- lawOfCosines: lawOfCosines$1,
34415
+ angleMeasures,
34416
+ ccw,
34417
+ lawOfCosines,
34526
34418
  magnitude: magnitude$1,
34527
34419
  polygonSidesIntersect,
34528
34420
  reverseVector,
34529
- sign: sign$1,
34421
+ sign,
34530
34422
  vector: vector$1
34531
34423
  } = kmath.geometry;
34532
34424
  const {
@@ -35033,7 +34925,7 @@ function doAddPoint(state, action) {
35033
34925
  ...state,
35034
34926
  hasBeenInteractedWith: true,
35035
34927
  coords: newCoords,
35036
- showRemovePointButton: false,
34928
+ showRemovePointButton: true,
35037
34929
  focusedPointIndex: newCoords.length - 1
35038
34930
  };
35039
34931
  }
@@ -35041,17 +34933,12 @@ function doRemovePoint(state, action) {
35041
34933
  if (!isUnlimitedGraphState(state)) {
35042
34934
  return state;
35043
34935
  }
35044
- let nextFocusedPointIndex;
35045
- if (state.interactionMode === "mouse") {
35046
- nextFocusedPointIndex = null;
35047
- } else {
35048
- nextFocusedPointIndex = state.coords.length > 1 ? state.coords.length - 2 : null;
35049
- }
34936
+ const nextFocusedPointIndex = state.coords.length > 1 ? state.coords.length - 2 : null;
35050
34937
  return {
35051
34938
  ...state,
35052
34939
  coords: state.coords.filter((_, i) => i !== action.index),
35053
34940
  focusedPointIndex: nextFocusedPointIndex,
35054
- showRemovePointButton: false
34941
+ showRemovePointButton: nextFocusedPointIndex !== null ? true : false
35055
34942
  };
35056
34943
  }
35057
34944
  const getDeltaVertex = (maxMoves, minMoves, delta) => {
@@ -35228,7 +35115,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
35228
35115
  });
35229
35116
 
35230
35117
  // Gets the radian angles between the coords and maps them to degrees
35231
- const angles = angleMeasures$1(coordsCopy).map(angle => angle * 180 / Math.PI);
35118
+ const angles = angleMeasures(coordsCopy).map(angle => angle * 180 / Math.PI);
35232
35119
 
35233
35120
  // Gets the relative index of a point
35234
35121
  const rel = j => {
@@ -35262,7 +35149,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
35262
35149
  // we want to add the inner angle to the outer angle, and if it's on the right,
35263
35150
  // we want to subtract the inner angle from the outer angle. The angle solved
35264
35151
  // for is then used in the polar function to determine the new point.
35265
- const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
35152
+ const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
35266
35153
 
35267
35154
  // Solve for side by using the law of sines
35268
35155
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
@@ -35316,7 +35203,7 @@ function boundAndSnapToSides(destinationPoint, _ref7, index) {
35316
35203
 
35317
35204
  // Solve for angle by using the law of cosines
35318
35205
  // Angle at the first vertex of the polygon
35319
- const innerAngle = lawOfCosines$1(sides[0], sides[2], sides[1]);
35206
+ const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
35320
35207
 
35321
35208
  // Angle at the second vertex of the polygon
35322
35209
  const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
@@ -35327,7 +35214,7 @@ function boundAndSnapToSides(destinationPoint, _ref7, index) {
35327
35214
  // we want to add the inner angle to the outer angle, and if it's on the right,
35328
35215
  // we want to subtract the inner angle from the outer angle. The angle solved
35329
35216
  // for is then used in the polar function to determine the new point.
35330
- const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
35217
+ const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
35331
35218
 
35332
35219
  // Uses the length of the first side of the polygon (radial coordinate)
35333
35220
  // and the angle between the first and second sides of the
@@ -35465,27 +35352,19 @@ const {
35465
35352
  getClockwiseAngle
35466
35353
  } = kmath.angles;
35467
35354
  const {
35468
- angleMeasures,
35469
- ccw,
35470
- collinear,
35471
35355
  getLineEquation,
35472
35356
  getLineIntersection,
35473
- intersects,
35474
- lawOfCosines,
35475
35357
  magnitude,
35476
- rotate,
35477
- sign,
35478
35358
  vector
35479
35359
  } = kmath.geometry;
35480
35360
  const defaultBackgroundImage = {
35481
35361
  url: null
35482
35362
  };
35483
35363
  const UNLIMITED = "unlimited";
35484
- function capitalize(str) {
35485
- return str.replace(/(?:^|-)(.)/g, function (match, letter) {
35486
- return letter.toUpperCase();
35487
- });
35488
- }
35364
+
35365
+ // Sample background image:
35366
+ // https://ka-perseus-graphie.s3.amazonaws.com/29c1b0fcd17fe63df0f148fe357044d5d5c7d0bb.png
35367
+
35489
35368
  function numSteps(range, step) {
35490
35369
  return Math.floor((range[1] - range[0]) / step);
35491
35370
  }
@@ -35530,7 +35409,6 @@ function getQuadraticCoefficients(coords) {
35530
35409
  return [a, b, c];
35531
35410
  }
35532
35411
  class InteractiveGraph extends React__namespace.Component {
35533
- legacyGraphRef = /*#__PURE__*/React__namespace.createRef();
35534
35412
  mafsRef = /*#__PURE__*/React__namespace.createRef();
35535
35413
  static defaultProps = {
35536
35414
  labels: ["x", "y"],
@@ -35548,9 +35426,6 @@ class InteractiveGraph extends React__namespace.Component {
35548
35426
  if (this.mafsRef.current?.getUserInput) {
35549
35427
  return this.mafsRef.current.getUserInput();
35550
35428
  }
35551
- if (this.legacyGraphRef.current?.getUserInput) {
35552
- return this.legacyGraphRef.current.getUserInput();
35553
- }
35554
35429
  throw new perseusCore.PerseusError("Cannot getUserInput from a graph that has never rendered", perseusCore.Errors.NotAllowed);
35555
35430
  }
35556
35431
  getPromptJSON() {
@@ -35744,8 +35619,32 @@ class InteractiveGraph extends React__namespace.Component {
35744
35619
  }
35745
35620
  static getEquationString(props) {
35746
35621
  const type = props.graph.type;
35747
- const funcName = "get" + capitalize(type) + "EquationString";
35748
- return InteractiveGraph[funcName](props);
35622
+ switch (type) {
35623
+ case "none":
35624
+ return InteractiveGraph.getNoneEquationString();
35625
+ case "linear":
35626
+ return InteractiveGraph.getLinearEquationString(props);
35627
+ case "quadratic":
35628
+ return InteractiveGraph.getQuadraticEquationString(props);
35629
+ case "sinusoid":
35630
+ return InteractiveGraph.getSinusoidEquationString(props);
35631
+ case "circle":
35632
+ return InteractiveGraph.getCircleEquationString(props);
35633
+ case "linear-system":
35634
+ return InteractiveGraph.getLinearSystemEquationString(props);
35635
+ case "point":
35636
+ return InteractiveGraph.getPointEquationString(props);
35637
+ case "segment":
35638
+ return InteractiveGraph.getSegmentEquationString(props);
35639
+ case "ray":
35640
+ return InteractiveGraph.getRayEquationString(props);
35641
+ case "polygon":
35642
+ return InteractiveGraph.getPolygonEquationString(props);
35643
+ case "angle":
35644
+ return InteractiveGraph.getAngleEquationString(props);
35645
+ default:
35646
+ throw new wonderStuffCore.UnreachableCaseError(type);
35647
+ }
35749
35648
  }
35750
35649
  static pointsFromNormalized(props, coordsList, noSnap) {
35751
35650
  // @ts-expect-error - TS2322 - Type 'number[][]' is not assignable to type 'readonly Coord[]'.
@@ -35889,8 +35788,7 @@ var InteractiveGraph$1 = {
35889
35788
  name: "interactive-graph",
35890
35789
  displayName: "Interactive graph (Assessments only)",
35891
35790
  widget: InteractiveGraph,
35892
- staticTransform: staticTransform$3,
35893
- getPublicWidgetOptions: perseusCore.getInteractiveGraphPublicWidgetOptions
35791
+ staticTransform: staticTransform$3
35894
35792
  };
35895
35793
 
35896
35794
  // TODO: can we use WB typography?
@@ -36526,10 +36424,15 @@ class LabelImage extends React__namespace.Component {
36526
36424
  });
36527
36425
  }
36528
36426
  activateMarker(index, opened) {
36427
+ // TODO(LEMS-2830): Remove analytics event in LEMS-2830 in favor of ti below.
36529
36428
  this.props.analytics?.onAnalyticsEvent({
36530
36429
  type: "perseus:label-image:marker-interacted-with",
36531
36430
  payload: null
36532
36431
  });
36432
+ this.props.analytics?.onAnalyticsEvent({
36433
+ type: "perseus:label-image:marker-interacted-with:ti",
36434
+ payload: null
36435
+ });
36533
36436
  const {
36534
36437
  activeMarkerIndex
36535
36438
  } = this.state;
@@ -36673,10 +36576,15 @@ class LabelImage extends React__namespace.Component {
36673
36576
  })),
36674
36577
  multipleSelect: this.props.multipleAnswers,
36675
36578
  onChange: selection => {
36579
+ // TODO(LEMS-2829): Remove analytics event in LEMS-2829 in favor of ti below.
36676
36580
  this.props.analytics?.onAnalyticsEvent({
36677
36581
  type: "perseus:label-image:choiced-interacted-with",
36678
36582
  payload: null
36679
36583
  });
36584
+ this.props.analytics?.onAnalyticsEvent({
36585
+ type: "perseus:label-image:choiced-interacted-with:ti",
36586
+ payload: null
36587
+ });
36680
36588
  this.handleAnswerChoicesChangeForMarker(index, selection);
36681
36589
  },
36682
36590
  onToggle: opened => this.activateMarker(index, opened)
@@ -36781,10 +36689,15 @@ class LabelImage extends React__namespace.Component {
36781
36689
  })), this.renderMarkers()), /*#__PURE__*/React__namespace.createElement(HideAnswersToggle, {
36782
36690
  areAnswersHidden: this.state.hideAnswers,
36783
36691
  onChange: hideAnswers => {
36692
+ // TODO(LEMS-2831): Remove analytics event in LEMS-2831 in favor of ti below.
36784
36693
  this.props.analytics?.onAnalyticsEvent({
36785
36694
  type: "perseus:label-image:toggle-answers-hidden",
36786
36695
  payload: null
36787
36696
  });
36697
+ this.props.analytics?.onAnalyticsEvent({
36698
+ type: "perseus:label-image:toggle-answers-hidden:ti",
36699
+ payload: null
36700
+ });
36788
36701
  this.setState({
36789
36702
  hideAnswers
36790
36703
  });
@@ -36847,8 +36760,7 @@ var LabelImage$1 = {
36847
36760
  displayName: "Label Image",
36848
36761
  widget: LabelImageWithDependencies,
36849
36762
  accessible: true,
36850
- isLintable: true,
36851
- getPublicWidgetOptions: perseusCore.getLabelImagePublicWidgetOptions
36763
+ isLintable: true
36852
36764
  };
36853
36765
 
36854
36766
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -37107,20 +37019,24 @@ class Draggable extends React__namespace.Component {
37107
37019
  if (this.props.margin != null) {
37108
37020
  style.margin = this.props.margin;
37109
37021
  }
37110
- return /*#__PURE__*/React__namespace.createElement("li", {
37111
- className: className,
37112
- style: style,
37113
- onMouseDown: this.onMouseDown,
37114
- onTouchStart: this.onMouseDown,
37115
- onTouchMove: this.onMouseMove,
37116
- onTouchEnd: this.onMouseUp,
37117
- onTouchCancel: this.onMouseUp
37118
- }, /*#__PURE__*/React__namespace.createElement(Renderer, {
37119
- content: this.props.content,
37120
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "draggable"),
37121
- onRender: this.props.onRender,
37122
- strings: this.context.strings
37123
- }));
37022
+ return (
37023
+ /*#__PURE__*/
37024
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
37025
+ React__namespace.createElement("li", {
37026
+ className: className,
37027
+ style: style,
37028
+ onMouseDown: this.onMouseDown,
37029
+ onTouchStart: this.onMouseDown,
37030
+ onTouchMove: this.onMouseMove,
37031
+ onTouchEnd: this.onMouseUp,
37032
+ onTouchCancel: this.onMouseUp
37033
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, {
37034
+ content: this.props.content,
37035
+ linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "draggable"),
37036
+ onRender: this.props.onRender,
37037
+ strings: this.context.strings
37038
+ }))
37039
+ );
37124
37040
  }
37125
37041
  }
37126
37042
  class Sortable extends React__namespace.Component {
@@ -37574,10 +37490,6 @@ const getPromptJSON$9 = (renderProps, userInput) => {
37574
37490
  };
37575
37491
  };
37576
37492
 
37577
- const {
37578
- shuffle: shuffle$1,
37579
- seededRNG
37580
- } = Util;
37581
37493
  const HACKY_CSS_CLASSNAME = "perseus-widget-matcher";
37582
37494
  class Matcher extends React__namespace.Component {
37583
37495
  static contextType = PerseusI18nContext;
@@ -37679,16 +37591,16 @@ class Matcher extends React__namespace.Component {
37679
37591
  }, "1")));
37680
37592
  }
37681
37593
 
37682
- // Use the same random() function to shuffle both columns sequentially
37683
- const rng = seededRNG(this.props.problemNum);
37684
- let left;
37685
- if (!this.props.orderMatters) {
37686
- // If the order doesn't matter, don't shuffle the left column
37687
- left = this.props.left;
37688
- } else {
37689
- left = shuffle$1(this.props.left, rng, /* ensurePermuted */true);
37690
- }
37691
- const right = shuffle$1(this.props.right, rng, /* ensurePermuted */true);
37594
+ /* TODO(LEMS-2841):
37595
+ Once the getMatcherPublicWidgetOptions function gets connected to the
37596
+ widget, we'll need to update this to the line below to only shuffle
37597
+ on the server.
37598
+ const {left, right} = this.props;
37599
+ */
37600
+ const {
37601
+ left,
37602
+ right
37603
+ } = perseusCore.shuffleMatcher(this.props);
37692
37604
  const showLabels = ___default["default"].any(this.props.labels);
37693
37605
  const constraints = {
37694
37606
  height: ___default["default"].max([this.state.leftHeight, this.state.rightHeight])
@@ -38179,8 +38091,7 @@ var Matrix$1 = {
38179
38091
  widget: Matrix,
38180
38092
  transform: propTransform$1,
38181
38093
  staticTransform: staticTransform$2,
38182
- isLintable: true,
38183
- getPublicWidgetOptions: perseusCore.getMatrixPublicWidgetOptions
38094
+ isLintable: true
38184
38095
  };
38185
38096
 
38186
38097
  const getPromptJSON$7 = () => {
@@ -39757,8 +39668,7 @@ var NumberLine$1 = {
39757
39668
  displayName: "Number line",
39758
39669
  widget: NumberLine,
39759
39670
  transform: numberLineTransform,
39760
- staticTransform: staticTransform$1,
39761
- getPublicWidgetOptions: perseusCore.getNumberLinePublicWidgetOptions
39671
+ staticTransform: staticTransform$1
39762
39672
  };
39763
39673
 
39764
39674
  const getPromptJSON$5 = (renderProps, userInput) => {
@@ -39925,19 +39835,23 @@ class Card extends React__namespace.Component {
39925
39835
  // Pull out the content to get rendered
39926
39836
  const rendererProps = ___default["default"].pick(this.props, "content");
39927
39837
  const onMouseDown = this.props.animating ? $__default["default"].noop : this.onMouseDown;
39928
- return /*#__PURE__*/React__namespace.createElement("div", {
39929
- className: "card-wrap",
39930
- style: style,
39931
- onMouseDown: onMouseDown,
39932
- onTouchStart: onMouseDown,
39933
- onTouchEnd: this.onMouseUp,
39934
- onTouchCancel: this.onMouseUp
39935
- }, /*#__PURE__*/React__namespace.createElement("div", {
39936
- className: className.join(" ")
39937
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, rendererProps, {
39938
- linterContext: this.props.linterContext,
39939
- strings: this.context.strings
39940
- }))));
39838
+ return (
39839
+ /*#__PURE__*/
39840
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO(LEMS-2871): Address a11y error
39841
+ React__namespace.createElement("div", {
39842
+ className: "card-wrap",
39843
+ style: style,
39844
+ onMouseDown: onMouseDown,
39845
+ onTouchStart: onMouseDown,
39846
+ onTouchEnd: this.onMouseUp,
39847
+ onTouchCancel: this.onMouseUp
39848
+ }, /*#__PURE__*/React__namespace.createElement("div", {
39849
+ className: className.join(" ")
39850
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, rendererProps, {
39851
+ linterContext: this.props.linterContext,
39852
+ strings: this.context.strings
39853
+ }))))
39854
+ );
39941
39855
  }
39942
39856
  }
39943
39857
  class Orderer extends React__namespace.Component {
@@ -40309,8 +40223,7 @@ var Orderer$1 = {
40309
40223
  displayName: "Orderer",
40310
40224
  hidden: true,
40311
40225
  widget: Orderer,
40312
- isLintable: true,
40313
- getPublicWidgetOptions: perseusCore.getOrdererPublicWidgetOptions
40226
+ isLintable: true
40314
40227
  };
40315
40228
 
40316
40229
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -41376,8 +41289,7 @@ var Plotter$1 = {
41376
41289
  displayName: "Plotter",
41377
41290
  hidden: true,
41378
41291
  widget: Plotter,
41379
- staticTransform: staticTransform,
41380
- getPublicWidgetOptions: perseusCore.getPlotterPublicWidgetOptions
41292
+ staticTransform: staticTransform
41381
41293
  };
41382
41294
 
41383
41295
  const getPromptJSON$2 = () => {
@@ -41454,9 +41366,6 @@ const getPromptJSON$1 = userInput => {
41454
41366
  };
41455
41367
  };
41456
41368
 
41457
- const {
41458
- shuffle
41459
- } = Util;
41460
41369
  class Sorter extends React__namespace.Component {
41461
41370
  _isMounted = false;
41462
41371
  static defaultProps = {
@@ -41513,7 +41422,11 @@ class Sorter extends React__namespace.Component {
41513
41422
  this.refs.sortable.moveOptionToIndex(option, index);
41514
41423
  };
41515
41424
  render() {
41516
- const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
41425
+ // TODO(LEMS-2841): Remove client-side shuffle once receiving public
41426
+ // options. The correct field will already be shuffled.
41427
+ // Probably easiest to replace "options" with "this.props.correct" when
41428
+ // setting up Sortable below. Or use the assignment to rename for clarity.
41429
+ const options = perseusCore.shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
41517
41430
  const {
41518
41431
  apiOptions
41519
41432
  } = this.props;
@@ -41537,8 +41450,7 @@ var Sorter$1 = {
41537
41450
  name: "sorter",
41538
41451
  displayName: "Sorter",
41539
41452
  widget: Sorter,
41540
- isLintable: true,
41541
- getPublicWidgetOptions: perseusCore.getSorterPublicWidgetOptions
41453
+ isLintable: true
41542
41454
  };
41543
41455
 
41544
41456
  const {
@@ -41754,8 +41666,7 @@ var Table$1 = {
41754
41666
  widget: Table,
41755
41667
  transform: propTransform,
41756
41668
  hidden: true,
41757
- isLintable: true,
41758
- getPublicWidgetOptions: perseusCore.getTablePublicWidgetOptions
41669
+ isLintable: true
41759
41670
  };
41760
41671
 
41761
41672
  const getPromptJSON = () => {
@@ -41936,7 +41847,7 @@ const init = function () {
41936
41847
 
41937
41848
  // This file is processed by a Rollup plugin (replace) to inject the production
41938
41849
  const libName = "@khanacademy/perseus";
41939
- const libVersion = "53.1.0";
41850
+ const libVersion = "54.1.0";
41940
41851
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
41941
41852
 
41942
41853
  const apiVersion = {
@@ -43430,6 +43341,17 @@ function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
43430
43341
  const widget = widgetMap[WidgetId];
43431
43342
  return widget?.type ?? null;
43432
43343
  }
43344
+ function getWidgetSubTypeByWidgetId(widgetId, widgetMap) {
43345
+ const widget = widgetMap[widgetId];
43346
+ const widgetType = widget?.type ?? null;
43347
+ switch (widgetType) {
43348
+ case "interactive-graph":
43349
+ const graph = widget.options.graph;
43350
+ return graph?.type ?? null;
43351
+ default:
43352
+ return null;
43353
+ }
43354
+ }
43433
43355
 
43434
43356
  /**
43435
43357
  * Does the content have a specific type of widget?
@@ -44308,6 +44230,7 @@ exports.getSegmentCoords = getSegmentCoords;
44308
44230
  exports.getSinusoidCoords = getSinusoidCoords;
44309
44231
  exports.getValidWidgetIds = getValidWidgetIds;
44310
44232
  exports.getWidgetFromWidgetMap = getWidgetFromWidgetMap;
44233
+ exports.getWidgetSubTypeByWidgetId = getWidgetSubTypeByWidgetId;
44311
44234
  exports.getWidgetTypeByWidgetId = getWidgetTypeByWidgetId;
44312
44235
  exports.getWidgetsFromWidgetMap = getWidgetsFromWidgetMap;
44313
44236
  exports.getWidgetsMapFromItemData = getWidgetsMapFromItemData;