@khanacademy/perseus 53.1.0 → 54.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) 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 +603 -1867
  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 +678 -691
  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 +3 -12
  15. package/dist/util/graphie.d.ts +1 -1
  16. package/dist/widget-ai-utils/radio/radio-ai-utils.d.ts +2 -1
  17. package/dist/widget-type-utils.d.ts +1 -0
  18. package/dist/widgets/categorizer/categorizer.d.ts +1 -2
  19. package/dist/widgets/cs-program/cs-program.d.ts +0 -2
  20. package/dist/widgets/dropdown/dropdown.d.ts +0 -2
  21. package/dist/widgets/expression/expression.d.ts +3 -4
  22. package/dist/widgets/grapher/grapher.d.ts +0 -2
  23. package/dist/widgets/iframe/iframe.d.ts +1 -2
  24. package/dist/widgets/interactive-graph.d.ts +2 -87
  25. package/dist/widgets/interactive-graphs/backgrounds/axis-labels.d.ts +4 -1
  26. package/dist/widgets/interactive-graphs/graphs/angle.d.ts +2 -1
  27. package/dist/widgets/interactive-graphs/graphs/circle.d.ts +6 -2
  28. package/dist/widgets/interactive-graphs/graphs/components/hairlines.d.ts +7 -0
  29. package/dist/widgets/interactive-graphs/graphs/components/movable-point-view.d.ts +1 -0
  30. package/dist/widgets/interactive-graphs/graphs/components/svg-line.d.ts +2 -1
  31. package/dist/widgets/interactive-graphs/graphs/linear-system.d.ts +2 -2
  32. package/dist/widgets/interactive-graphs/graphs/linear.d.ts +1 -1
  33. package/dist/widgets/interactive-graphs/graphs/point.d.ts +3 -2
  34. package/dist/widgets/interactive-graphs/graphs/ray.d.ts +1 -1
  35. package/dist/widgets/interactive-graphs/graphs/screenreader-text.d.ts +1 -0
  36. package/dist/widgets/interactive-graphs/graphs/segment.d.ts +2 -2
  37. package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +2 -1
  38. package/dist/widgets/interactive-graphs/graphs/utils.d.ts +0 -3
  39. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +1 -2
  40. package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +3 -3
  41. package/dist/widgets/label-image/label-image.d.ts +1 -3
  42. package/dist/widgets/matrix/matrix.d.ts +1 -2
  43. package/dist/widgets/number-line/number-line.d.ts +0 -2
  44. package/dist/widgets/numeric-input/numeric-input.d.ts +0 -2
  45. package/dist/widgets/orderer/orderer.d.ts +0 -2
  46. package/dist/widgets/plotter/plotter.d.ts +0 -2
  47. package/dist/widgets/radio/radio.d.ts +1 -2
  48. package/dist/widgets/sorter/sorter.d.ts +1 -2
  49. package/dist/widgets/table/table.d.ts +0 -2
  50. package/dist/widgets.d.ts +1 -2
  51. package/package.json +10 -10
  52. 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) {
@@ -16010,12 +16069,17 @@ class ImageLoader extends React__namespace.Component {
16010
16069
  };
16011
16070
  }
16012
16071
  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));
16072
+ return (
16073
+ /*#__PURE__*/
16074
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
16075
+ React__namespace.createElement("img", _extends({
16076
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- TODO(LEMS-2871): Address a11y error
16077
+ tabIndex: 0,
16078
+ src: staticUrl(src),
16079
+ onKeyUp: onKeyUp,
16080
+ onKeyDown: onKeyDown
16081
+ }, imgProps))
16082
+ );
16019
16083
  };
16020
16084
  render() {
16021
16085
  switch (this.state.status) {
@@ -16689,16 +16753,20 @@ class Zoomable extends React__namespace.Component {
16689
16753
  WebkitTapHighlightColor: "transparent",
16690
16754
  ...transitionStyle
16691
16755
  };
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);
16756
+ return (
16757
+ /*#__PURE__*/
16758
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- TODO(LEMS-2871): Address a11y error
16759
+ React__namespace.createElement("span", {
16760
+ onClick: this.handleClick,
16761
+ onClickCapture: this.handleClickIfZoomed,
16762
+ onTouchCancelCapture: this.stopPropagationIfZoomed,
16763
+ onTouchEndCapture: this.stopPropagationIfZoomed,
16764
+ onTouchStartCapture: this.stopPropagationIfZoomed
16765
+ // @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'.
16766
+ ,
16767
+ style: style
16768
+ }, this.props.children)
16769
+ );
16702
16770
  }
16703
16771
  }
16704
16772
 
@@ -17921,9 +17989,6 @@ const getWidget = name => {
17921
17989
  const getWidgetExport = name => {
17922
17990
  return widgets[name] ?? null;
17923
17991
  };
17924
- const getPublicWidgetOptionsFunction = name => {
17925
- return widgets[name]?.getPublicWidgetOptions ?? (i => i);
17926
- };
17927
17992
  const getEditor = name => {
17928
17993
  return ___default["default"].has(editors, name) ? editors[name] : null;
17929
17994
  };
@@ -18060,7 +18125,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
18060
18125
  replaceDeprecatedEditors: replaceDeprecatedEditors,
18061
18126
  getWidget: getWidget,
18062
18127
  getWidgetExport: getWidgetExport,
18063
- getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
18064
18128
  getEditor: getEditor,
18065
18129
  getTransform: getTransform,
18066
18130
  getVersion: getVersion,
@@ -18136,6 +18200,7 @@ class WidgetContainer extends React__namespace.Component {
18136
18200
  "perseus-widget__definition": this.props.type === "definition"
18137
18201
  });
18138
18202
  const type = this.props.type;
18203
+ const userAgent = navigator.userAgent;
18139
18204
  const WidgetType = getWidget(type);
18140
18205
  if (WidgetType == null) {
18141
18206
  // This is for the good of all people!!
@@ -18146,6 +18211,11 @@ class WidgetContainer extends React__namespace.Component {
18146
18211
  className: className
18147
18212
  });
18148
18213
  }
18214
+ let subType = "null";
18215
+ if (type === "interactive-graph") {
18216
+ const props = this.state.widgetProps;
18217
+ subType = props.graph?.type ?? "null";
18218
+ }
18149
18219
  let alignment = this.state.widgetProps.alignment;
18150
18220
  if (alignment === "default") {
18151
18221
  alignment = perseusCore.CoreWidgetRegistry.getDefaultAlignment(type);
@@ -18202,12 +18272,26 @@ class WidgetContainer extends React__namespace.Component {
18202
18272
  widget_type: type,
18203
18273
  widget_id: this.props.id
18204
18274
  },
18205
- onError: () => {
18275
+ onError: error => {
18276
+ // TODO(LEMS-2826): Remove analytics event in LEMS-2826 in favor of ti below.
18206
18277
  analytics.onAnalyticsEvent({
18207
18278
  type: "perseus:widget-rendering-error",
18208
18279
  payload: {
18280
+ widgetSubType: subType,
18209
18281
  widgetType: type,
18210
- widgetId: this.props.id
18282
+ widgetId: this.props.id,
18283
+ message: error.message,
18284
+ userAgent: userAgent
18285
+ }
18286
+ });
18287
+ analytics.onAnalyticsEvent({
18288
+ type: "perseus:widget-rendering-error:ti",
18289
+ payload: {
18290
+ widgetSubType: subType,
18291
+ widgetType: type,
18292
+ widgetId: this.props.id,
18293
+ message: error.message,
18294
+ userAgent: userAgent
18211
18295
  }
18212
18296
  });
18213
18297
  }
@@ -20650,7 +20734,6 @@ var NumericInput$1 = {
20650
20734
  widget: NumericInput,
20651
20735
  transform: propsTransform,
20652
20736
  isLintable: true,
20653
- getPublicWidgetOptions: perseusCore.getNumericInputPublicWidgetOptions,
20654
20737
  // TODO(LEMS-2656): remove TS suppression
20655
20738
  // @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusNumericInputRubric'
20656
20739
  getOneCorrectAnswerFromRubric(rubric) {
@@ -21457,7 +21540,10 @@ class HighlightTooltip extends React__namespace.PureComponent {
21457
21540
  // using div instead of TooltipContent because
21458
21541
  // TooltipContent wouldn't let me overwrite
21459
21542
  // user-select and onClick
21460
- const content = /*#__PURE__*/React__namespace.createElement("div", {
21543
+ const content =
21544
+ /*#__PURE__*/
21545
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- TODO(LEMS-2871): Address a11y error
21546
+ React__namespace.createElement("div", {
21461
21547
  className: aphrodite.css(styles$x.tooltipLabel),
21462
21548
  onClick: this.props.onClick
21463
21549
  }, this.props.label);
@@ -21468,14 +21554,18 @@ class HighlightTooltip extends React__namespace.PureComponent {
21468
21554
  top: `calc(${Math.round(focusRect.top)}px + 0.95em)`,
21469
21555
  height: 0
21470
21556
  };
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)));
21557
+ return (
21558
+ /*#__PURE__*/
21559
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO(LEMS-2871): Address a11y error
21560
+ React__namespace.createElement("div", {
21561
+ style: style,
21562
+ onMouseEnter: this.props.onMouseEnter,
21563
+ onMouseLeave: this.props.onMouseLeave
21564
+ }, /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
21565
+ content: content,
21566
+ opened: true
21567
+ }, /*#__PURE__*/React__namespace.createElement("div", null)))
21568
+ );
21479
21569
  }
21480
21570
  }
21481
21571
  const styles$x = aphrodite.StyleSheet.create({
@@ -24192,15 +24282,19 @@ const BaseRadio = function (_ref) {
24192
24282
  // content somehow? Would changing our choice of key
24193
24283
  // somehow break something happening inside a choice's
24194
24284
  // 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
- })));
24285
+ return (
24286
+ /*#__PURE__*/
24287
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
24288
+ React__namespace.createElement("li", {
24289
+ key: i,
24290
+ ref: e => listElem = e,
24291
+ className: className,
24292
+ onClick: clickHandler,
24293
+ onTouchStart: labelWrap ? undefined : captureScratchpadTouchStart$1
24294
+ }, /*#__PURE__*/React__namespace.createElement(Element, _extends({}, elementProps, {
24295
+ ref: ref
24296
+ })))
24297
+ );
24204
24298
  })));
24205
24299
 
24206
24300
  // Allow for horizontal scrolling if content is too wide, which may be
@@ -24745,8 +24839,7 @@ var Radio = {
24745
24839
  staticTransform: transform,
24746
24840
  version: perseusCore.radioLogic.version,
24747
24841
  propUpgrades: perseusCore.radioLogic.widgetOptionsUpgrades,
24748
- isLintable: true,
24749
- getPublicWidgetOptions: perseusCore.getRadioPublicWidgetOptions
24842
+ isLintable: true
24750
24843
  };
24751
24844
 
24752
24845
  /* eslint-disable import/no-named-as-default */
@@ -25035,8 +25128,7 @@ var Categorizer$1 = {
25035
25128
  staticTransform: editorProps => {
25036
25129
  return ___default["default"].pick(editorProps, "items", "categories", "values", "randomizeItems");
25037
25130
  },
25038
- isLintable: true,
25039
- getPublicWidgetOptions: perseusCore.getCategorizerPublicWidgetOptions
25131
+ isLintable: true
25040
25132
  };
25041
25133
 
25042
25134
  /**
@@ -25246,8 +25338,7 @@ var CSProgram$1 = {
25246
25338
  name: "cs-program",
25247
25339
  displayName: "CS Program",
25248
25340
  widget: CSProgram,
25249
- hidden: true,
25250
- getPublicWidgetOptions: perseusCore.getCSProgramPublicWidgetOptions
25341
+ hidden: true
25251
25342
  };
25252
25343
 
25253
25344
  const getPromptJSON$l = renderProps => {
@@ -25463,8 +25554,7 @@ var Dropdown$1 = {
25463
25554
  displayName: "Drop down",
25464
25555
  accessible: true,
25465
25556
  widget: Dropdown,
25466
- transform: optionsTransform,
25467
- getPublicWidgetOptions: perseusCore.getDropdownPublicWidgetOptions
25557
+ transform: optionsTransform
25468
25558
  };
25469
25559
 
25470
25560
  const getPromptJSON$j = renderProps => {
@@ -26956,8 +27046,7 @@ var Grapher$1 = {
26956
27046
  hidden: true,
26957
27047
  widget: Grapher,
26958
27048
  transform: propTransform$2,
26959
- staticTransform: staticTransform$4,
26960
- getPublicWidgetOptions: perseusCore.getGrapherPublicWidgetOptions
27049
+ staticTransform: staticTransform$4
26961
27050
  };
26962
27051
 
26963
27052
  const getPromptJSON$f = rendererJSON => {
@@ -27251,12 +27340,16 @@ class Iframe extends React__namespace.Component {
27251
27340
  // that we need. This makes it a bit safer in case some content
27252
27341
  // creator "went wild".
27253
27342
  // 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
- });
27343
+ return (
27344
+ /*#__PURE__*/
27345
+ // eslint-disable-next-line jsx-a11y/iframe-has-title -- TODO(LEMS-2871): Address a11y error
27346
+ React__namespace.createElement("iframe", {
27347
+ sandbox: sandboxProperties,
27348
+ style: style,
27349
+ src: url,
27350
+ allowFullScreen: this.props.allowFullScreen
27351
+ })
27352
+ );
27260
27353
  }
27261
27354
  }
27262
27355
  var Iframe$1 = {
@@ -27264,8 +27357,7 @@ var Iframe$1 = {
27264
27357
  displayName: "Iframe (deprecated)",
27265
27358
  widget: Iframe,
27266
27359
  // Let's not expose it to all content creators yet
27267
- hidden: true,
27268
- getPublicWidgetOptions: perseusCore.getIFramePublicWidgetOptions
27360
+ hidden: true
27269
27361
  };
27270
27362
 
27271
27363
  const getPromptJSON$d = renderProps => {
@@ -27282,7 +27374,7 @@ const getPromptJSON$d = renderProps => {
27282
27374
 
27283
27375
  const defaultBoxSize = 400;
27284
27376
  const defaultRange = [0, 10];
27285
- const defaultBackgroundImage$2 = {
27377
+ const defaultBackgroundImage$1 = {
27286
27378
  url: null,
27287
27379
  width: 0,
27288
27380
  height: 0
@@ -27297,7 +27389,7 @@ class ImageWidget extends React__namespace.Component {
27297
27389
  title: "",
27298
27390
  range: [defaultRange, defaultRange],
27299
27391
  box: [defaultBoxSize, defaultBoxSize],
27300
- backgroundImage: defaultBackgroundImage$2,
27392
+ backgroundImage: defaultBackgroundImage$1,
27301
27393
  labels: [],
27302
27394
  alt: "",
27303
27395
  caption: "",
@@ -28068,306 +28160,6 @@ var Interactive = {
28068
28160
  hidden: true
28069
28161
  };
28070
28162
 
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
28163
  const getPromptJSON$b = (props, userInput) => {
28372
28164
  return {
28373
28165
  type: "interactive-graph",
@@ -28932,13 +28724,19 @@ function escapeIfUnescaped(character) {
28932
28724
  }
28933
28725
  }
28934
28726
 
28935
- function AxisLabels() {
28727
+ function AxisLabels(_ref) {
28728
+ let {
28729
+ i18n
28730
+ } = _ref;
28936
28731
  const {
28937
28732
  range,
28938
28733
  labels,
28939
28734
  width,
28940
28735
  height
28941
28736
  } = useGraphConfig();
28737
+ const {
28738
+ strings
28739
+ } = i18n;
28942
28740
  const yAxisLabelLocation = [0, range[Y][MAX]];
28943
28741
  const xAxisLabelLocation = [range[X][MAX], 0];
28944
28742
  const [xAxisLabelText, yAxisLabelText] = labels;
@@ -28956,6 +28754,7 @@ function AxisLabels() {
28956
28754
  TeX
28957
28755
  } = getDependencies();
28958
28756
  return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("span", {
28757
+ "aria-label": strings.xAxis,
28959
28758
  style: {
28960
28759
  position: "absolute",
28961
28760
  left: x1,
@@ -28964,6 +28763,7 @@ function AxisLabels() {
28964
28763
  transform: "translate(7px, -50%)"
28965
28764
  }
28966
28765
  }, /*#__PURE__*/React__default["default"].createElement(TeX, null, replaceOutsideTeX(xAxisLabelText))), /*#__PURE__*/React__default["default"].createElement("span", {
28766
+ "aria-label": strings.yAxis,
28967
28767
  style: {
28968
28768
  position: "absolute",
28969
28769
  left: x2,
@@ -29772,7 +29572,6 @@ class RangeInput extends React__namespace.Component {
29772
29572
  var components = /*#__PURE__*/Object.freeze({
29773
29573
  __proto__: null,
29774
29574
  ButtonGroup: ButtonGroup,
29775
- Graph: Graph,
29776
29575
  Graphie: Graphie,
29777
29576
  HUD: HUD,
29778
29577
  Icon: Icon,
@@ -29916,9 +29715,9 @@ const LockedFunction = props => {
29916
29715
  const {
29917
29716
  color,
29918
29717
  strokeStyle,
29919
- directionalAxis,
29920
- domain
29718
+ directionalAxis
29921
29719
  } = props;
29720
+ const domain = props.domain ? [Number.isFinite(props.domain[0]) ? props.domain[0] : -Infinity, Number.isFinite(props.domain[1]) ? props.domain[1] : Infinity] : undefined;
29922
29721
  const plotProps = {
29923
29722
  color: perseusCore.lockedFigureColors[color],
29924
29723
  style: strokeStyle,
@@ -29998,6 +29797,11 @@ function Vector(props) {
29998
29797
  }
29999
29798
 
30000
29799
  function srFormatNumber(a, locale, maximumFractionDigits) {
29800
+ const piBasedNumber = getPiMultiple(a);
29801
+ if (piBasedNumber) {
29802
+ return piBasedNumber;
29803
+ }
29804
+
30001
29805
  // adding zero here converts negative zero to positive zero.
30002
29806
  return (0 + a).toLocaleString(locale, {
30003
29807
  maximumFractionDigits: maximumFractionDigits ?? 3,
@@ -30005,6 +29809,77 @@ function srFormatNumber(a, locale, maximumFractionDigits) {
30005
29809
  });
30006
29810
  }
30007
29811
 
29812
+ // Exported for testing
29813
+ function getPiMultiple(a) {
29814
+ // - Save some calculations by checking if this is an integer first.
29815
+ // If a is an integer, it's definitely not a multiple of π.
29816
+ // - 0 should not be presented as a multiple of π.
29817
+ // - If a is greater than 1e12, then it thinks that it's always a
29818
+ // multiple of π even when it's not, due to rounding errors.
29819
+ if (Number.isInteger(a) || a === 0 || a > 1e12) {
29820
+ return null;
29821
+ }
29822
+
29823
+ // Figure out the coefficient before the π.
29824
+ // Example: If a = π/2, then piCoefficient = 0.5
29825
+ // Example: If a = 2π, then piCoefficient = 2
29826
+ const piCoefficient = a / Math.PI;
29827
+ // Truncate the coefficient to account for floating point errors.
29828
+ // π calculations are only accurate up to precision of 1e-12.
29829
+ const truncatedCoefficient = parseFloat(piCoefficient.toFixed(12));
29830
+
29831
+ // If the coefficient is already an integer, then the number
29832
+ // is a multiple of π. Return it here.
29833
+ // Example: If a = 2π, then truncatedCoefficient = 2, so return "2π"
29834
+ if (Number.isInteger(truncatedCoefficient)) {
29835
+ // Return "π" rather than "1π".
29836
+ if (truncatedCoefficient === 1) {
29837
+ return "π";
29838
+ }
29839
+
29840
+ // Return "-π" rather than "-1π".
29841
+ if (truncatedCoefficient === -1) {
29842
+ return "-π";
29843
+ }
29844
+ return truncatedCoefficient + "π";
29845
+ }
29846
+
29847
+ // If the coefficient is not an integer, then we need to
29848
+ // check if it's a multiple of π/2, π/3, π/4, or π/6.
29849
+ // These are π-based values on a unit circle.
29850
+ const acceptableDivisors = [2, 3, 4, 6];
29851
+
29852
+ // Loop through the acceptable divisors and check if the
29853
+ // coefficient is a multiple of (1/divisor).
29854
+ // Example: If a = 5π/6, the coeff is 5/6, which is a multiple of 1/6.
29855
+ // So we return "5π/6".
29856
+ for (const divisor of acceptableDivisors) {
29857
+ // Check if the coefficient is a multiple of (1/divisor) by
29858
+ // multiplying the coefficient by the divisor and checking if
29859
+ // the result is an integer.
29860
+ // Example: If a = 5π/6, then piCoefficient = 5/6. We multiply
29861
+ // by 6 (divisor) to get 5, which is an integer.
29862
+ const coefficientNumerator = parseFloat(
29863
+ // π calculations are only accurate up to precision of 1e-12.
29864
+ (piCoefficient * divisor).toFixed(12));
29865
+ if (Number.isInteger(coefficientNumerator)) {
29866
+ // Handle the case where the coefficient numberator is just 1.
29867
+ // We don't want to write "π/6" as "1π/6"
29868
+ if (coefficientNumerator === 1) {
29869
+ return "π/" + divisor;
29870
+ }
29871
+
29872
+ // Handle the case where the coefficient numberator is just -1.
29873
+ // We don't want to write "π/6" as "-1π/6"
29874
+ if (coefficientNumerator === -1) {
29875
+ return "-π/" + divisor;
29876
+ }
29877
+ return coefficientNumerator + "π/" + divisor;
29878
+ }
29879
+ }
29880
+ return null;
29881
+ }
29882
+
30008
29883
  /**
30009
29884
  * Given a ray and a rectangular box, find the point where the ray intersects
30010
29885
  * the edge of the box. Assumes the `initialPoint` is inside the box.
@@ -31172,6 +31047,34 @@ function range(min, max) {
31172
31047
  return result;
31173
31048
  }
31174
31049
 
31050
+ function Hairlines(props) {
31051
+ const {
31052
+ point
31053
+ } = props;
31054
+ const {
31055
+ range
31056
+ } = useGraphConfig();
31057
+ const [[xMin, xMax], [yMin, yMax]] = range;
31058
+ const [[x, y]] = useTransformVectorsToPixels(point);
31059
+ const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
31060
+ const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
31061
+ const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
31062
+ const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
31063
+ return /*#__PURE__*/React__namespace.createElement("g", null, /*#__PURE__*/React__namespace.createElement("line", {
31064
+ x1: verticalStartX,
31065
+ y1: y,
31066
+ x2: verticalEndX,
31067
+ y2: y,
31068
+ stroke: wonderBlocksTokens.color.blue
31069
+ }), /*#__PURE__*/React__namespace.createElement("line", {
31070
+ x1: x,
31071
+ y1: horizontalStartY,
31072
+ x2: x,
31073
+ y2: horizontalEndY,
31074
+ stroke: wonderBlocksTokens.color.blue
31075
+ }));
31076
+ }
31077
+
31175
31078
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
31176
31079
  // graph.
31177
31080
  const hitboxSizePx = 48;
@@ -31181,7 +31084,6 @@ const hitboxSizePx = 48;
31181
31084
  // on an interactive graph.
31182
31085
  const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
31183
31086
  const {
31184
- range,
31185
31087
  markings,
31186
31088
  showTooltips
31187
31089
  } = useGraphConfig();
@@ -31189,6 +31091,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
31189
31091
  point,
31190
31092
  color = wonderBlocksTokens.color.blue,
31191
31093
  dragging,
31094
+ focused,
31192
31095
  cursor,
31193
31096
  showFocusRing,
31194
31097
  onClick = () => {}
@@ -31202,25 +31105,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
31202
31105
  })?.[0] ?? "blue";
31203
31106
  const pointClasses = classNames("movable-point", dragging && "movable-point--dragging", showFocusRing && "movable-point--focus");
31204
31107
  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
- }));
31108
+ const showHairlines = (dragging || focused) && markings !== "none";
31224
31109
  const svgForPoint = /*#__PURE__*/React__namespace.createElement("g", {
31225
31110
  ref: hitboxRef,
31226
31111
  className: pointClasses,
@@ -31256,7 +31141,9 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
31256
31141
  },
31257
31142
  "data-testid": "movable-point__center"
31258
31143
  }));
31259
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
31144
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && /*#__PURE__*/React__namespace.createElement(Hairlines, {
31145
+ point: point
31146
+ }), showTooltips ? /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
31260
31147
  autoUpdate: true,
31261
31148
  backgroundColor: wbColorName,
31262
31149
  content: `(${point[X]}, ${point[Y]})`,
@@ -31352,6 +31239,7 @@ function useControlPoint(params) {
31352
31239
  },
31353
31240
  point: point,
31354
31241
  dragging: dragging,
31242
+ focused: focused,
31355
31243
  color: color,
31356
31244
  ref: visiblePointRef,
31357
31245
  showFocusRing: focused
@@ -31569,13 +31457,13 @@ const {
31569
31457
  getClockwiseAngle: getClockwiseAngle$2,
31570
31458
  polar: polar$1
31571
31459
  } = kmath.angles;
31572
- function renderAngleGraph(state, dispatch) {
31460
+ function renderAngleGraph(state, dispatch, i18n) {
31573
31461
  return {
31574
31462
  graph: /*#__PURE__*/React__namespace.createElement(AngleGraph, {
31575
31463
  graphState: state,
31576
31464
  dispatch: dispatch
31577
31465
  }),
31578
- interactiveElementsDescription: null
31466
+ interactiveElementsDescription: getAngleGraphDescription(state, i18n)
31579
31467
  };
31580
31468
  }
31581
31469
  function AngleGraph(props) {
@@ -31586,6 +31474,9 @@ function AngleGraph(props) {
31586
31474
  const {
31587
31475
  graphDimensionsInPixels
31588
31476
  } = useGraphConfig();
31477
+ const i18n = usePerseusI18n();
31478
+ const id = React__namespace.useId();
31479
+ const descriptionId = id + "-description";
31589
31480
  const {
31590
31481
  coords,
31591
31482
  showAngles,
@@ -31635,80 +31526,106 @@ function AngleGraph(props) {
31635
31526
  showAngles: showAngles || false // Whether to show the angle or not
31636
31527
  };
31637
31528
 
31529
+ // Aria strings
31638
31530
  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]);
31531
+ srAngleGraphAriaLabel,
31532
+ srAngleGraphAriaDescription,
31533
+ srAngleStartingSide,
31534
+ srAngleEndingSide,
31535
+ srAngleVertex
31536
+ } = describeAngleGraph(graphState, i18n);
31685
31537
 
31686
31538
  // Render the lines, angle, and then movable points
31687
31539
  return /*#__PURE__*/React__namespace.createElement("g", {
31688
- "aria-label": wholeAngleAriaLabel,
31689
- "aria-describedby": "angle-description"
31540
+ "aria-label": srAngleGraphAriaLabel,
31541
+ "aria-describedby": descriptionId
31690
31542
  }, svgLines, /*#__PURE__*/React__namespace.createElement(Angle, angleParams), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31691
31543
  point: coords[1],
31692
31544
  sequenceNumber: 1,
31693
31545
  constrain: p => p,
31694
31546
  onMove: destination => dispatch(actions.angle.movePoint(1, destination)),
31695
- ariaLabel: vertexAriaLabel
31547
+ ariaLabel: srAngleVertex
31696
31548
  }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31697
31549
  point: coords[0],
31698
31550
  sequenceNumber: 2,
31699
31551
  constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
31700
31552
  onMove: destination => dispatch(actions.angle.movePoint(0, destination)),
31701
- ariaLabel: terminalSideAriaLabel
31553
+ ariaLabel: srAngleEndingSide
31702
31554
  }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31703
31555
  point: coords[2],
31704
31556
  sequenceNumber: 3,
31705
31557
  constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
31706
31558
  onMove: destination => dispatch(actions.angle.movePoint(2, destination)),
31707
- ariaLabel: initialSideAriaLabel
31559
+ ariaLabel: srAngleStartingSide
31708
31560
  }), /*#__PURE__*/React__namespace.createElement("g", {
31709
- id: "angle-description",
31561
+ id: descriptionId,
31710
31562
  style: a11y.srOnly
31711
- }, wholeAngleDescription));
31563
+ }, srAngleGraphAriaDescription));
31564
+ }
31565
+ function getAngleGraphDescription(state, i18n) {
31566
+ const {
31567
+ strings,
31568
+ locale
31569
+ } = i18n;
31570
+ const {
31571
+ coords
31572
+ } = state;
31573
+ return strings.srInteractiveElements({
31574
+ elements: strings.srAngleInteractiveElements({
31575
+ vertexX: srFormatNumber(coords[1][X], locale),
31576
+ vertexY: srFormatNumber(coords[1][Y], locale),
31577
+ startingSideX: srFormatNumber(coords[2][X], locale),
31578
+ startingSideY: srFormatNumber(coords[2][Y], locale),
31579
+ endingSideX: srFormatNumber(coords[0][X], locale),
31580
+ endingSideY: srFormatNumber(coords[0][Y], locale)
31581
+ })
31582
+ });
31583
+ }
31584
+ function describeAngleGraph(state, i18n) {
31585
+ const {
31586
+ strings,
31587
+ locale
31588
+ } = i18n;
31589
+ const {
31590
+ coords,
31591
+ allowReflexAngles,
31592
+ showAngles
31593
+ } = state;
31594
+ const [endingSide, vertex, startingSide] = coords;
31595
+ const angleMeasure = srFormatNumber(getClockwiseAngle$2(coords, allowReflexAngles), locale);
31596
+ const srAngleGraphAriaLabel = strings.srAngleGraphAriaLabel;
31597
+ const srAngleGraphAriaDescription = strings.srAngleGraphAriaDescription({
31598
+ angleMeasure,
31599
+ vertexX: srFormatNumber(vertex[X], locale),
31600
+ vertexY: srFormatNumber(vertex[Y], locale),
31601
+ startingSideX: srFormatNumber(startingSide[X], locale),
31602
+ startingSideY: srFormatNumber(startingSide[Y], locale),
31603
+ endingSideX: srFormatNumber(endingSide[X], locale),
31604
+ endingSideY: srFormatNumber(endingSide[Y], locale)
31605
+ });
31606
+ const srAngleStartingSide = strings.srAngleStartingSide({
31607
+ x: srFormatNumber(startingSide[X], locale),
31608
+ y: srFormatNumber(startingSide[Y], locale)
31609
+ });
31610
+ const srAngleEndingSide = strings.srAngleEndingSide({
31611
+ x: srFormatNumber(endingSide[X], locale),
31612
+ y: srFormatNumber(endingSide[Y], locale)
31613
+ });
31614
+ const srAngleVertex = showAngles ? strings.srAngleVertexWithAngleMeasure({
31615
+ x: srFormatNumber(vertex[X], locale),
31616
+ y: srFormatNumber(vertex[Y], locale),
31617
+ angleMeasure
31618
+ }) : strings.srAngleVertex({
31619
+ x: srFormatNumber(vertex[X], locale),
31620
+ y: srFormatNumber(vertex[Y], locale)
31621
+ });
31622
+ return {
31623
+ srAngleGraphAriaLabel,
31624
+ srAngleGraphAriaDescription,
31625
+ srAngleStartingSide,
31626
+ srAngleEndingSide,
31627
+ srAngleVertex
31628
+ };
31712
31629
  }
31713
31630
  const positiveX = [1, 0];
31714
31631
  const negativeX = [-1, 0];
@@ -31852,17 +31769,16 @@ function getRadius(graph) {
31852
31769
  return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
31853
31770
  }
31854
31771
 
31855
- function renderCircleGraph(state, dispatch) {
31772
+ function renderCircleGraph(state, dispatch, i18n) {
31856
31773
  return {
31857
31774
  graph: /*#__PURE__*/React__namespace.createElement(CircleGraph, {
31858
31775
  graphState: state,
31859
31776
  dispatch: dispatch
31860
31777
  }),
31861
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(CircleGraphDescription, {
31862
- state: state
31863
- })
31778
+ interactiveElementsDescription: getCircleGraphDescription(state, i18n)
31864
31779
  };
31865
31780
  }
31781
+ // Exported for testing
31866
31782
  function CircleGraph(props) {
31867
31783
  const {
31868
31784
  dispatch,
@@ -31955,6 +31871,7 @@ function MovableCircle(props) {
31955
31871
  snapStep,
31956
31872
  disableKeyboardInteraction
31957
31873
  } = useGraphConfig();
31874
+ const [focused, setFocused] = React__namespace.useState(false);
31958
31875
  const draggableRef = React.useRef(null);
31959
31876
  const {
31960
31877
  dragging
@@ -31973,7 +31890,9 @@ function MovableCircle(props) {
31973
31890
  ref: draggableRef,
31974
31891
  role: "button",
31975
31892
  tabIndex: disableKeyboardInteraction ? -1 : 0,
31976
- className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
31893
+ className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`,
31894
+ onFocus: () => setFocused(true),
31895
+ onBlur: () => setFocused(false)
31977
31896
  }, /*#__PURE__*/React__namespace.createElement("ellipse", {
31978
31897
  className: "focus-ring",
31979
31898
  cx: centerPx[X],
@@ -31988,19 +31907,29 @@ function MovableCircle(props) {
31988
31907
  rx: radiiPx[X],
31989
31908
  ry: radiiPx[Y]
31990
31909
  }), /*#__PURE__*/React__namespace.createElement(DragHandle, {
31991
- center: center
31910
+ center: center,
31911
+ dragging: dragging,
31912
+ focused: focused
31992
31913
  }));
31993
31914
  }
31994
31915
  const dragHandleDimensions = [24, 14];
31995
31916
  const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
31996
31917
  function DragHandle(props) {
31997
31918
  const {
31998
- center
31919
+ center,
31920
+ dragging,
31921
+ focused
31999
31922
  } = props;
32000
- const cornerRadius = Math.min(...dragHandleDimensions) / 2;
32001
31923
  const [centerPx] = useTransformVectorsToPixels(center);
31924
+ const {
31925
+ markings
31926
+ } = useGraphConfig();
31927
+ const cornerRadius = Math.min(...dragHandleDimensions) / 2;
32002
31928
  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", {
31929
+ const showHairlines = (dragging || focused) && markings !== "none";
31930
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && /*#__PURE__*/React__namespace.createElement(Hairlines, {
31931
+ point: center
31932
+ }), /*#__PURE__*/React__namespace.createElement("rect", {
32004
31933
  className: "movable-circle-handle",
32005
31934
  x: topLeft[X],
32006
31935
  y: topLeft[Y],
@@ -32027,14 +31956,7 @@ function crossProduct(as, bs) {
32027
31956
  }
32028
31957
  return result;
32029
31958
  }
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();
31959
+ function getCircleGraphDescription(state, i18n) {
32038
31960
  const strings = describeCircleGraph(state, i18n);
32039
31961
  return strings.srCircleInteractiveElement;
32040
31962
  }
@@ -32087,15 +32009,13 @@ function describeCircleGraph(state, i18n) {
32087
32009
  };
32088
32010
  }
32089
32011
 
32090
- function renderLinearGraph(state, dispatch) {
32012
+ function renderLinearGraph(state, dispatch, i18n) {
32091
32013
  return {
32092
32014
  graph: /*#__PURE__*/React__namespace.createElement(LinearGraph, {
32093
32015
  graphState: state,
32094
32016
  dispatch: dispatch
32095
32017
  }),
32096
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(LinearGraphDescription, {
32097
- state: state
32098
- })
32018
+ interactiveElementsDescription: getLinearGraphDescription(state, i18n)
32099
32019
  };
32100
32020
  }
32101
32021
  const LinearGraph = (props, key) => {
@@ -32160,14 +32080,7 @@ const LinearGraph = (props, key) => {
32160
32080
  style: a11y.srOnly
32161
32081
  }, slopeString));
32162
32082
  };
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();
32083
+ function getLinearGraphDescription(state, i18n) {
32171
32084
  const strings = describeLinearGraph(state, i18n);
32172
32085
  return strings.srLinearInteractiveElement;
32173
32086
  }
@@ -32211,15 +32124,13 @@ function describeLinearGraph(state, i18n) {
32211
32124
  };
32212
32125
  }
32213
32126
 
32214
- function renderLinearSystemGraph(state, dispatch) {
32127
+ function renderLinearSystemGraph(state, dispatch, i18n) {
32215
32128
  return {
32216
32129
  graph: /*#__PURE__*/React__namespace.createElement(LinearSystemGraph, {
32217
32130
  graphState: state,
32218
32131
  dispatch: dispatch
32219
32132
  }),
32220
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(LinearSystemGraphDescription, {
32221
- state: state
32222
- })
32133
+ interactiveElementsDescription: getLinearSystemGraphDescription(state, i18n)
32223
32134
  };
32224
32135
  }
32225
32136
  const LinearSystemGraph = props => {
@@ -32312,19 +32223,9 @@ const LinearSystemGraph = props => {
32312
32223
  }, slopeDescription));
32313
32224
  }));
32314
32225
  };
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
32226
 
32326
32227
  // Exported for testing
32327
- function describeLinearSystemGraph(state, i18n) {
32228
+ function getLinearSystemGraphDescription(state, i18n) {
32328
32229
  const {
32329
32230
  strings,
32330
32231
  locale
@@ -32350,15 +32251,13 @@ function describeLinearSystemGraph(state, i18n) {
32350
32251
  });
32351
32252
  }
32352
32253
 
32353
- function renderPointGraph(state, dispatch) {
32254
+ function renderPointGraph(state, dispatch, i18n) {
32354
32255
  return {
32355
32256
  graph: /*#__PURE__*/React__namespace.createElement(PointGraph, {
32356
32257
  graphState: state,
32357
32258
  dispatch: dispatch
32358
32259
  }),
32359
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(PointGraphDescription, {
32360
- state: state
32361
- })
32260
+ interactiveElementsDescription: getPointGraphDescription(state, i18n)
32362
32261
  };
32363
32262
  }
32364
32263
  function PointGraph(props) {
@@ -32380,7 +32279,9 @@ function PointGraph(props) {
32380
32279
  if (focusedIndex != null) {
32381
32280
  pointsRef.current[focusedIndex]?.focus();
32382
32281
  }
32383
- }, [props.graphState.focusedPointIndex, pointsRef]);
32282
+ }, [props.graphState.focusedPointIndex,
32283
+ // Ensure that we re-focus if a point is deleted.
32284
+ props.graphState.coords.length, pointsRef]);
32384
32285
  const statefulProps = {
32385
32286
  ...props,
32386
32287
  graphConfig,
@@ -32452,18 +32353,9 @@ function UnlimitedPointGraph(statefulProps) {
32452
32353
  }
32453
32354
  })));
32454
32355
  }
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
32356
 
32465
32357
  // Exported for testing
32466
- function describePointGraph(state, i18n) {
32358
+ function getPointGraphDescription(state, i18n) {
32467
32359
  const {
32468
32360
  strings,
32469
32361
  locale
@@ -32471,8 +32363,8 @@ function describePointGraph(state, i18n) {
32471
32363
  if (state.coords.length === 0) {
32472
32364
  return strings.srNoInteractiveElements;
32473
32365
  }
32474
- const pointDescriptions = state.coords.map((_ref2, index) => {
32475
- let [x, y] = _ref2;
32366
+ const pointDescriptions = state.coords.map((_ref, index) => {
32367
+ let [x, y] = _ref;
32476
32368
  return strings.srPointAtCoordinates({
32477
32369
  num: index + 1,
32478
32370
  x: srFormatNumber(x, locale),
@@ -32552,7 +32444,9 @@ const PolygonGraph = props => {
32552
32444
  if (focusedIndex != null) {
32553
32445
  pointsRef.current[focusedIndex]?.focus();
32554
32446
  }
32555
- }, [props.graphState.focusedPointIndex, pointsRef]);
32447
+ }, [props.graphState.focusedPointIndex,
32448
+ // Ensure that we re-focus if a point is deleted.
32449
+ props.graphState.coords.length, pointsRef]);
32556
32450
 
32557
32451
  // If the unlimited polygon is rendered with 3 or more coordinates
32558
32452
  // Close the polygon, but only on first render.
@@ -33135,15 +33029,13 @@ function describeQuadraticGraph(state, i18n) {
33135
33029
  };
33136
33030
  }
33137
33031
 
33138
- function renderRayGraph(state, dispatch) {
33032
+ function renderRayGraph(state, dispatch, i18n) {
33139
33033
  return {
33140
33034
  graph: /*#__PURE__*/React__namespace.createElement(RayGraph, {
33141
33035
  graphState: state,
33142
33036
  dispatch: dispatch
33143
33037
  }),
33144
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(RayGraphDescription, {
33145
- state: state
33146
- })
33038
+ interactiveElementsDescription: getRayGraphDescription(state, i18n)
33147
33039
  };
33148
33040
  }
33149
33041
  const RayGraph = props => {
@@ -33197,14 +33089,7 @@ const RayGraph = props => {
33197
33089
  style: a11y.srOnly
33198
33090
  }, srRayPoints));
33199
33091
  };
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();
33092
+ function getRayGraphDescription(state, i18n) {
33208
33093
  const strings = describeRayGraph(state, i18n);
33209
33094
  return strings.srRayInteractiveElement;
33210
33095
  }
@@ -33254,15 +33139,13 @@ function describeRayGraph(state, i18n) {
33254
33139
  };
33255
33140
  }
33256
33141
 
33257
- function renderSegmentGraph(state, dispatch) {
33142
+ function renderSegmentGraph(state, dispatch, i18n) {
33258
33143
  return {
33259
33144
  graph: /*#__PURE__*/React__namespace.createElement(SegmentGraph, {
33260
33145
  graphState: state,
33261
33146
  dispatch: dispatch
33262
33147
  }),
33263
- interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(SegmentGraphDescription, {
33264
- state: state
33265
- })
33148
+ interactiveElementsDescription: getSegmentGraphDescription(state, i18n)
33266
33149
  };
33267
33150
  }
33268
33151
  const SegmentGraph = _ref => {
@@ -33356,25 +33239,15 @@ const SegmentGraph = _ref => {
33356
33239
  function getLengthOfSegment(segment) {
33357
33240
  return kmath.point.distanceToPoint(...segment);
33358
33241
  }
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
33242
 
33370
33243
  // Exported for testing
33371
- function describeSegmentGraph(state, i18n) {
33244
+ function getSegmentGraphDescription(state, i18n) {
33372
33245
  const {
33373
33246
  strings,
33374
33247
  locale
33375
33248
  } = i18n;
33376
- const segmentDescriptions = state.coords.map((_ref3, index) => {
33377
- let [point1, point2] = _ref3;
33249
+ const segmentDescriptions = state.coords.map((_ref2, index) => {
33250
+ let [point1, point2] = _ref2;
33378
33251
  return strings.srMultipleSegmentIndividualLabel({
33379
33252
  point1X: srFormatNumber(point1[X], locale),
33380
33253
  point1Y: srFormatNumber(point1[Y], locale),
@@ -33388,13 +33261,13 @@ function describeSegmentGraph(state, i18n) {
33388
33261
  });
33389
33262
  }
33390
33263
 
33391
- function renderSinusoidGraph(state, dispatch) {
33264
+ function renderSinusoidGraph(state, dispatch, i18n) {
33392
33265
  return {
33393
33266
  graph: /*#__PURE__*/React__namespace.createElement(SinusoidGraph, {
33394
33267
  graphState: state,
33395
33268
  dispatch: dispatch
33396
33269
  }),
33397
- interactiveElementsDescription: null
33270
+ interactiveElementsDescription: getSinusoidDescription(state, i18n)
33398
33271
  };
33399
33272
  }
33400
33273
  function SinusoidGraph(props) {
@@ -33402,6 +33275,9 @@ function SinusoidGraph(props) {
33402
33275
  dispatch,
33403
33276
  graphState
33404
33277
  } = props;
33278
+ const i18n = usePerseusI18n();
33279
+ const id = React__namespace.useId();
33280
+ const descriptionId = id + "-description";
33405
33281
 
33406
33282
  // Destructure the coordinates from the graph state
33407
33283
  // Note: The order of the coordinates is important:
@@ -33426,15 +33302,30 @@ function SinusoidGraph(props) {
33426
33302
  if (coeffs !== undefined) {
33427
33303
  coeffRef.current = coeffs;
33428
33304
  }
33429
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
33305
+
33306
+ // Aria strings
33307
+ const {
33308
+ srSinusoidGraph,
33309
+ srSinusoidDescription,
33310
+ srSinusoidRootPoint,
33311
+ srSinusoidPeakPoint
33312
+ } = describeSinusoidGraph(graphState, i18n);
33313
+ return /*#__PURE__*/React__namespace.createElement("g", {
33314
+ // Outer graph minimal description
33315
+ "aria-label": srSinusoidGraph,
33316
+ "aria-describedby": descriptionId
33317
+ }, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
33430
33318
  y: x => computeSine(x, coeffRef.current),
33431
33319
  color: wonderBlocksTokens.color.blue
33432
33320
  }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
33321
+ ariaLabel: i === 0 ? srSinusoidRootPoint : srSinusoidPeakPoint,
33433
33322
  key: "point-" + i,
33434
33323
  point: coord,
33435
33324
  sequenceNumber: i + 1,
33436
33325
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
33437
- })));
33326
+ })), /*#__PURE__*/React__namespace.createElement("g", {
33327
+ id: descriptionId
33328
+ }, srSinusoidDescription));
33438
33329
  }
33439
33330
 
33440
33331
  // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
@@ -33472,6 +33363,52 @@ const getSinusoidCoefficients$1 = coords => {
33472
33363
  verticalOffset
33473
33364
  };
33474
33365
  };
33366
+ function getSinusoidDescription(state, i18n) {
33367
+ const strings = describeSinusoidGraph(state, i18n);
33368
+ return strings.srSinusoidInteractiveElements;
33369
+ }
33370
+ function describeSinusoidGraph(state, i18n) {
33371
+ const {
33372
+ strings,
33373
+ locale
33374
+ } = i18n;
33375
+ const {
33376
+ coords
33377
+ } = state;
33378
+ const [root, peak] = coords;
33379
+ const diffX = Math.abs(peak[X] - root[X]);
33380
+ const diffY = Math.abs(peak[Y] - root[Y]);
33381
+ const srSinusoidGraph = strings.srSinusoidGraph;
33382
+ const srSinusoidDescription = strings.srSinusoidDescription({
33383
+ minValue: srFormatNumber(root[Y] - diffY, locale),
33384
+ maxValue: srFormatNumber(root[Y] + diffY, locale),
33385
+ cycleStart: srFormatNumber(root[X] - 2 * diffX, locale),
33386
+ cycleEnd: srFormatNumber(root[X] + 2 * diffX, locale)
33387
+ });
33388
+ const srSinusoidRootPoint = strings.srSinusoidRootPoint({
33389
+ x: srFormatNumber(root[X], locale),
33390
+ y: srFormatNumber(root[Y], locale)
33391
+ });
33392
+ const srSinusoidPeakPoint = strings.srSinusoidPeakPoint({
33393
+ x: srFormatNumber(peak[X], locale),
33394
+ y: srFormatNumber(peak[Y], locale)
33395
+ });
33396
+ const srSinusoidInteractiveElements = strings.srInteractiveElements({
33397
+ elements: strings.srSinusoidInteractiveElements({
33398
+ point1X: srFormatNumber(root[X], locale),
33399
+ point1Y: srFormatNumber(root[Y], locale),
33400
+ point2X: srFormatNumber(peak[X], locale),
33401
+ point2Y: srFormatNumber(peak[Y], locale)
33402
+ })
33403
+ });
33404
+ return {
33405
+ srSinusoidGraph,
33406
+ srSinusoidDescription,
33407
+ srSinusoidRootPoint,
33408
+ srSinusoidPeakPoint,
33409
+ srSinusoidInteractiveElements
33410
+ };
33411
+ }
33475
33412
 
33476
33413
  const {
33477
33414
  calculateAngleInDegrees,
@@ -33642,6 +33579,7 @@ const MafsGraph = props => {
33642
33579
  const descriptionId = `interactive-graph-description-${uniqueId}`;
33643
33580
  const interactiveElementsDescriptionId = `interactive-graph-interactive-elements-description-${uniqueId}`;
33644
33581
  const unlimitedGraphKeyboardPromptId = `unlimited-graph-keyboard-prompt-${uniqueId}`;
33582
+ const instructionsId = `instructions-${uniqueId}`;
33645
33583
  const graphRef = React__namespace.useRef(null);
33646
33584
  const {
33647
33585
  analytics
@@ -33669,6 +33607,7 @@ const MafsGraph = props => {
33669
33607
  const interactionPrompt = isUnlimitedGraphState(state) && state.showKeyboardInteractionInvitation;
33670
33608
  wonderBlocksCore.useOnMountEffect(() => {
33671
33609
  analytics.onAnalyticsEvent({
33610
+ // TODO(LEMS-2827): Remove analytics event in LEMS-2827 in favor of ti below.
33672
33611
  type: "perseus:interactive-graph-widget:rendered",
33673
33612
  payload: {
33674
33613
  type,
@@ -33676,6 +33615,14 @@ const MafsGraph = props => {
33676
33615
  widgetId: "interactive-graph"
33677
33616
  }
33678
33617
  });
33618
+ analytics.onAnalyticsEvent({
33619
+ type: "perseus:widget:rendered:ti",
33620
+ payload: {
33621
+ widgetSubType: type,
33622
+ widgetType: "INTERACTIVE_GRAPH",
33623
+ widgetId: "interactive-graph"
33624
+ }
33625
+ });
33679
33626
  });
33680
33627
  const {
33681
33628
  graph,
@@ -33719,7 +33666,7 @@ const MafsGraph = props => {
33719
33666
  handleKeyboardEvent(event, state, dispatch);
33720
33667
  },
33721
33668
  "aria-label": fullGraphAriaLabel,
33722
- "aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId),
33669
+ "aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId, state.type !== "none" && instructionsId),
33723
33670
  ref: graphRef,
33724
33671
  tabIndex: 0,
33725
33672
  onFocus: event => {
@@ -33736,7 +33683,11 @@ const MafsGraph = props => {
33736
33683
  id: interactiveElementsDescriptionId,
33737
33684
  tabIndex: -1,
33738
33685
  className: "mafs-sr-only"
33739
- }, interactiveElementsDescription), /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
33686
+ }, interactiveElementsDescription), state.type !== "none" && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
33687
+ id: instructionsId,
33688
+ tabIndex: -1,
33689
+ className: "mafs-sr-only"
33690
+ }, isUnlimitedGraphState(state) ? strings.srUnlimitedGraphInstructions : strings.srGraphInstructions), /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
33740
33691
  box: props.box,
33741
33692
  backgroundImage: props.backgroundImage
33742
33693
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
@@ -33745,7 +33696,9 @@ const MafsGraph = props => {
33745
33696
  bottom: 0,
33746
33697
  left: 0
33747
33698
  }
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
33699
+ }, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, {
33700
+ i18n: i18n
33701
+ })), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View
33749
33702
  // If we have locked figures, we set aria-hidden
33750
33703
  // to false so that screen readers can read the
33751
33704
  // locked figures. If we don't have any locked
@@ -34082,25 +34035,25 @@ const renderGraphElements = props => {
34082
34035
  } = state;
34083
34036
  switch (type) {
34084
34037
  case "angle":
34085
- return renderAngleGraph(state, dispatch);
34038
+ return renderAngleGraph(state, dispatch, i18n);
34086
34039
  case "segment":
34087
- return renderSegmentGraph(state, dispatch);
34040
+ return renderSegmentGraph(state, dispatch, i18n);
34088
34041
  case "linear-system":
34089
- return renderLinearSystemGraph(state, dispatch);
34042
+ return renderLinearSystemGraph(state, dispatch, i18n);
34090
34043
  case "linear":
34091
- return renderLinearGraph(state, dispatch);
34044
+ return renderLinearGraph(state, dispatch, i18n);
34092
34045
  case "ray":
34093
- return renderRayGraph(state, dispatch);
34046
+ return renderRayGraph(state, dispatch, i18n);
34094
34047
  case "polygon":
34095
34048
  return renderPolygonGraph(state, dispatch, i18n, markings);
34096
34049
  case "point":
34097
- return renderPointGraph(state, dispatch);
34050
+ return renderPointGraph(state, dispatch, i18n);
34098
34051
  case "circle":
34099
- return renderCircleGraph(state, dispatch);
34052
+ return renderCircleGraph(state, dispatch, i18n);
34100
34053
  case "quadratic":
34101
34054
  return renderQuadraticGraph(state, dispatch, i18n);
34102
34055
  case "sinusoid":
34103
- return renderSinusoidGraph(state, dispatch);
34056
+ return renderSinusoidGraph(state, dispatch, i18n);
34104
34057
  case "none":
34105
34058
  return {
34106
34059
  graph: null,
@@ -34520,13 +34473,13 @@ const {
34520
34473
  polar
34521
34474
  } = kmath.angles;
34522
34475
  const {
34523
- angleMeasures: angleMeasures$1,
34524
- ccw: ccw$1,
34525
- lawOfCosines: lawOfCosines$1,
34476
+ angleMeasures,
34477
+ ccw,
34478
+ lawOfCosines,
34526
34479
  magnitude: magnitude$1,
34527
34480
  polygonSidesIntersect,
34528
34481
  reverseVector,
34529
- sign: sign$1,
34482
+ sign,
34530
34483
  vector: vector$1
34531
34484
  } = kmath.geometry;
34532
34485
  const {
@@ -35033,7 +34986,7 @@ function doAddPoint(state, action) {
35033
34986
  ...state,
35034
34987
  hasBeenInteractedWith: true,
35035
34988
  coords: newCoords,
35036
- showRemovePointButton: false,
34989
+ showRemovePointButton: true,
35037
34990
  focusedPointIndex: newCoords.length - 1
35038
34991
  };
35039
34992
  }
@@ -35041,17 +34994,12 @@ function doRemovePoint(state, action) {
35041
34994
  if (!isUnlimitedGraphState(state)) {
35042
34995
  return state;
35043
34996
  }
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
- }
34997
+ const nextFocusedPointIndex = state.coords.length > 1 ? state.coords.length - 2 : null;
35050
34998
  return {
35051
34999
  ...state,
35052
35000
  coords: state.coords.filter((_, i) => i !== action.index),
35053
35001
  focusedPointIndex: nextFocusedPointIndex,
35054
- showRemovePointButton: false
35002
+ showRemovePointButton: nextFocusedPointIndex !== null ? true : false
35055
35003
  };
35056
35004
  }
35057
35005
  const getDeltaVertex = (maxMoves, minMoves, delta) => {
@@ -35228,7 +35176,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
35228
35176
  });
35229
35177
 
35230
35178
  // Gets the radian angles between the coords and maps them to degrees
35231
- const angles = angleMeasures$1(coordsCopy).map(angle => angle * 180 / Math.PI);
35179
+ const angles = angleMeasures(coordsCopy).map(angle => angle * 180 / Math.PI);
35232
35180
 
35233
35181
  // Gets the relative index of a point
35234
35182
  const rel = j => {
@@ -35262,7 +35210,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
35262
35210
  // we want to add the inner angle to the outer angle, and if it's on the right,
35263
35211
  // we want to subtract the inner angle from the outer angle. The angle solved
35264
35212
  // 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;
35213
+ const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
35266
35214
 
35267
35215
  // Solve for side by using the law of sines
35268
35216
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
@@ -35316,7 +35264,7 @@ function boundAndSnapToSides(destinationPoint, _ref7, index) {
35316
35264
 
35317
35265
  // Solve for angle by using the law of cosines
35318
35266
  // Angle at the first vertex of the polygon
35319
- const innerAngle = lawOfCosines$1(sides[0], sides[2], sides[1]);
35267
+ const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
35320
35268
 
35321
35269
  // Angle at the second vertex of the polygon
35322
35270
  const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
@@ -35327,7 +35275,7 @@ function boundAndSnapToSides(destinationPoint, _ref7, index) {
35327
35275
  // we want to add the inner angle to the outer angle, and if it's on the right,
35328
35276
  // we want to subtract the inner angle from the outer angle. The angle solved
35329
35277
  // 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;
35278
+ const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
35331
35279
 
35332
35280
  // Uses the length of the first side of the polygon (radial coordinate)
35333
35281
  // and the angle between the first and second sides of the
@@ -35465,27 +35413,19 @@ const {
35465
35413
  getClockwiseAngle
35466
35414
  } = kmath.angles;
35467
35415
  const {
35468
- angleMeasures,
35469
- ccw,
35470
- collinear,
35471
35416
  getLineEquation,
35472
35417
  getLineIntersection,
35473
- intersects,
35474
- lawOfCosines,
35475
35418
  magnitude,
35476
- rotate,
35477
- sign,
35478
35419
  vector
35479
35420
  } = kmath.geometry;
35480
35421
  const defaultBackgroundImage = {
35481
35422
  url: null
35482
35423
  };
35483
35424
  const UNLIMITED = "unlimited";
35484
- function capitalize(str) {
35485
- return str.replace(/(?:^|-)(.)/g, function (match, letter) {
35486
- return letter.toUpperCase();
35487
- });
35488
- }
35425
+
35426
+ // Sample background image:
35427
+ // https://ka-perseus-graphie.s3.amazonaws.com/29c1b0fcd17fe63df0f148fe357044d5d5c7d0bb.png
35428
+
35489
35429
  function numSteps(range, step) {
35490
35430
  return Math.floor((range[1] - range[0]) / step);
35491
35431
  }
@@ -35530,7 +35470,6 @@ function getQuadraticCoefficients(coords) {
35530
35470
  return [a, b, c];
35531
35471
  }
35532
35472
  class InteractiveGraph extends React__namespace.Component {
35533
- legacyGraphRef = /*#__PURE__*/React__namespace.createRef();
35534
35473
  mafsRef = /*#__PURE__*/React__namespace.createRef();
35535
35474
  static defaultProps = {
35536
35475
  labels: ["x", "y"],
@@ -35548,9 +35487,6 @@ class InteractiveGraph extends React__namespace.Component {
35548
35487
  if (this.mafsRef.current?.getUserInput) {
35549
35488
  return this.mafsRef.current.getUserInput();
35550
35489
  }
35551
- if (this.legacyGraphRef.current?.getUserInput) {
35552
- return this.legacyGraphRef.current.getUserInput();
35553
- }
35554
35490
  throw new perseusCore.PerseusError("Cannot getUserInput from a graph that has never rendered", perseusCore.Errors.NotAllowed);
35555
35491
  }
35556
35492
  getPromptJSON() {
@@ -35744,8 +35680,32 @@ class InteractiveGraph extends React__namespace.Component {
35744
35680
  }
35745
35681
  static getEquationString(props) {
35746
35682
  const type = props.graph.type;
35747
- const funcName = "get" + capitalize(type) + "EquationString";
35748
- return InteractiveGraph[funcName](props);
35683
+ switch (type) {
35684
+ case "none":
35685
+ return InteractiveGraph.getNoneEquationString();
35686
+ case "linear":
35687
+ return InteractiveGraph.getLinearEquationString(props);
35688
+ case "quadratic":
35689
+ return InteractiveGraph.getQuadraticEquationString(props);
35690
+ case "sinusoid":
35691
+ return InteractiveGraph.getSinusoidEquationString(props);
35692
+ case "circle":
35693
+ return InteractiveGraph.getCircleEquationString(props);
35694
+ case "linear-system":
35695
+ return InteractiveGraph.getLinearSystemEquationString(props);
35696
+ case "point":
35697
+ return InteractiveGraph.getPointEquationString(props);
35698
+ case "segment":
35699
+ return InteractiveGraph.getSegmentEquationString(props);
35700
+ case "ray":
35701
+ return InteractiveGraph.getRayEquationString(props);
35702
+ case "polygon":
35703
+ return InteractiveGraph.getPolygonEquationString(props);
35704
+ case "angle":
35705
+ return InteractiveGraph.getAngleEquationString(props);
35706
+ default:
35707
+ throw new wonderStuffCore.UnreachableCaseError(type);
35708
+ }
35749
35709
  }
35750
35710
  static pointsFromNormalized(props, coordsList, noSnap) {
35751
35711
  // @ts-expect-error - TS2322 - Type 'number[][]' is not assignable to type 'readonly Coord[]'.
@@ -35889,8 +35849,7 @@ var InteractiveGraph$1 = {
35889
35849
  name: "interactive-graph",
35890
35850
  displayName: "Interactive graph (Assessments only)",
35891
35851
  widget: InteractiveGraph,
35892
- staticTransform: staticTransform$3,
35893
- getPublicWidgetOptions: perseusCore.getInteractiveGraphPublicWidgetOptions
35852
+ staticTransform: staticTransform$3
35894
35853
  };
35895
35854
 
35896
35855
  // TODO: can we use WB typography?
@@ -36526,10 +36485,15 @@ class LabelImage extends React__namespace.Component {
36526
36485
  });
36527
36486
  }
36528
36487
  activateMarker(index, opened) {
36488
+ // TODO(LEMS-2830): Remove analytics event in LEMS-2830 in favor of ti below.
36529
36489
  this.props.analytics?.onAnalyticsEvent({
36530
36490
  type: "perseus:label-image:marker-interacted-with",
36531
36491
  payload: null
36532
36492
  });
36493
+ this.props.analytics?.onAnalyticsEvent({
36494
+ type: "perseus:label-image:marker-interacted-with:ti",
36495
+ payload: null
36496
+ });
36533
36497
  const {
36534
36498
  activeMarkerIndex
36535
36499
  } = this.state;
@@ -36673,10 +36637,15 @@ class LabelImage extends React__namespace.Component {
36673
36637
  })),
36674
36638
  multipleSelect: this.props.multipleAnswers,
36675
36639
  onChange: selection => {
36640
+ // TODO(LEMS-2829): Remove analytics event in LEMS-2829 in favor of ti below.
36676
36641
  this.props.analytics?.onAnalyticsEvent({
36677
36642
  type: "perseus:label-image:choiced-interacted-with",
36678
36643
  payload: null
36679
36644
  });
36645
+ this.props.analytics?.onAnalyticsEvent({
36646
+ type: "perseus:label-image:choiced-interacted-with:ti",
36647
+ payload: null
36648
+ });
36680
36649
  this.handleAnswerChoicesChangeForMarker(index, selection);
36681
36650
  },
36682
36651
  onToggle: opened => this.activateMarker(index, opened)
@@ -36781,10 +36750,15 @@ class LabelImage extends React__namespace.Component {
36781
36750
  })), this.renderMarkers()), /*#__PURE__*/React__namespace.createElement(HideAnswersToggle, {
36782
36751
  areAnswersHidden: this.state.hideAnswers,
36783
36752
  onChange: hideAnswers => {
36753
+ // TODO(LEMS-2831): Remove analytics event in LEMS-2831 in favor of ti below.
36784
36754
  this.props.analytics?.onAnalyticsEvent({
36785
36755
  type: "perseus:label-image:toggle-answers-hidden",
36786
36756
  payload: null
36787
36757
  });
36758
+ this.props.analytics?.onAnalyticsEvent({
36759
+ type: "perseus:label-image:toggle-answers-hidden:ti",
36760
+ payload: null
36761
+ });
36788
36762
  this.setState({
36789
36763
  hideAnswers
36790
36764
  });
@@ -36847,8 +36821,7 @@ var LabelImage$1 = {
36847
36821
  displayName: "Label Image",
36848
36822
  widget: LabelImageWithDependencies,
36849
36823
  accessible: true,
36850
- isLintable: true,
36851
- getPublicWidgetOptions: perseusCore.getLabelImagePublicWidgetOptions
36824
+ isLintable: true
36852
36825
  };
36853
36826
 
36854
36827
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -37107,20 +37080,24 @@ class Draggable extends React__namespace.Component {
37107
37080
  if (this.props.margin != null) {
37108
37081
  style.margin = this.props.margin;
37109
37082
  }
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
- }));
37083
+ return (
37084
+ /*#__PURE__*/
37085
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- TODO(LEMS-2871): Address a11y error
37086
+ React__namespace.createElement("li", {
37087
+ className: className,
37088
+ style: style,
37089
+ onMouseDown: this.onMouseDown,
37090
+ onTouchStart: this.onMouseDown,
37091
+ onTouchMove: this.onMouseMove,
37092
+ onTouchEnd: this.onMouseUp,
37093
+ onTouchCancel: this.onMouseUp
37094
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, {
37095
+ content: this.props.content,
37096
+ linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "draggable"),
37097
+ onRender: this.props.onRender,
37098
+ strings: this.context.strings
37099
+ }))
37100
+ );
37124
37101
  }
37125
37102
  }
37126
37103
  class Sortable extends React__namespace.Component {
@@ -38179,8 +38156,7 @@ var Matrix$1 = {
38179
38156
  widget: Matrix,
38180
38157
  transform: propTransform$1,
38181
38158
  staticTransform: staticTransform$2,
38182
- isLintable: true,
38183
- getPublicWidgetOptions: perseusCore.getMatrixPublicWidgetOptions
38159
+ isLintable: true
38184
38160
  };
38185
38161
 
38186
38162
  const getPromptJSON$7 = () => {
@@ -39757,8 +39733,7 @@ var NumberLine$1 = {
39757
39733
  displayName: "Number line",
39758
39734
  widget: NumberLine,
39759
39735
  transform: numberLineTransform,
39760
- staticTransform: staticTransform$1,
39761
- getPublicWidgetOptions: perseusCore.getNumberLinePublicWidgetOptions
39736
+ staticTransform: staticTransform$1
39762
39737
  };
39763
39738
 
39764
39739
  const getPromptJSON$5 = (renderProps, userInput) => {
@@ -39925,19 +39900,23 @@ class Card extends React__namespace.Component {
39925
39900
  // Pull out the content to get rendered
39926
39901
  const rendererProps = ___default["default"].pick(this.props, "content");
39927
39902
  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
- }))));
39903
+ return (
39904
+ /*#__PURE__*/
39905
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO(LEMS-2871): Address a11y error
39906
+ React__namespace.createElement("div", {
39907
+ className: "card-wrap",
39908
+ style: style,
39909
+ onMouseDown: onMouseDown,
39910
+ onTouchStart: onMouseDown,
39911
+ onTouchEnd: this.onMouseUp,
39912
+ onTouchCancel: this.onMouseUp
39913
+ }, /*#__PURE__*/React__namespace.createElement("div", {
39914
+ className: className.join(" ")
39915
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, rendererProps, {
39916
+ linterContext: this.props.linterContext,
39917
+ strings: this.context.strings
39918
+ }))))
39919
+ );
39941
39920
  }
39942
39921
  }
39943
39922
  class Orderer extends React__namespace.Component {
@@ -40309,8 +40288,7 @@ var Orderer$1 = {
40309
40288
  displayName: "Orderer",
40310
40289
  hidden: true,
40311
40290
  widget: Orderer,
40312
- isLintable: true,
40313
- getPublicWidgetOptions: perseusCore.getOrdererPublicWidgetOptions
40291
+ isLintable: true
40314
40292
  };
40315
40293
 
40316
40294
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -41376,8 +41354,7 @@ var Plotter$1 = {
41376
41354
  displayName: "Plotter",
41377
41355
  hidden: true,
41378
41356
  widget: Plotter,
41379
- staticTransform: staticTransform,
41380
- getPublicWidgetOptions: perseusCore.getPlotterPublicWidgetOptions
41357
+ staticTransform: staticTransform
41381
41358
  };
41382
41359
 
41383
41360
  const getPromptJSON$2 = () => {
@@ -41537,8 +41514,7 @@ var Sorter$1 = {
41537
41514
  name: "sorter",
41538
41515
  displayName: "Sorter",
41539
41516
  widget: Sorter,
41540
- isLintable: true,
41541
- getPublicWidgetOptions: perseusCore.getSorterPublicWidgetOptions
41517
+ isLintable: true
41542
41518
  };
41543
41519
 
41544
41520
  const {
@@ -41754,8 +41730,7 @@ var Table$1 = {
41754
41730
  widget: Table,
41755
41731
  transform: propTransform,
41756
41732
  hidden: true,
41757
- isLintable: true,
41758
- getPublicWidgetOptions: perseusCore.getTablePublicWidgetOptions
41733
+ isLintable: true
41759
41734
  };
41760
41735
 
41761
41736
  const getPromptJSON = () => {
@@ -41936,7 +41911,7 @@ const init = function () {
41936
41911
 
41937
41912
  // This file is processed by a Rollup plugin (replace) to inject the production
41938
41913
  const libName = "@khanacademy/perseus";
41939
- const libVersion = "53.1.0";
41914
+ const libVersion = "54.0.0";
41940
41915
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
41941
41916
 
41942
41917
  const apiVersion = {
@@ -43430,6 +43405,17 @@ function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
43430
43405
  const widget = widgetMap[WidgetId];
43431
43406
  return widget?.type ?? null;
43432
43407
  }
43408
+ function getWidgetSubTypeByWidgetId(widgetId, widgetMap) {
43409
+ const widget = widgetMap[widgetId];
43410
+ const widgetType = widget?.type ?? null;
43411
+ switch (widgetType) {
43412
+ case "interactive-graph":
43413
+ const graph = widget.options.graph;
43414
+ return graph?.type ?? null;
43415
+ default:
43416
+ return null;
43417
+ }
43418
+ }
43433
43419
 
43434
43420
  /**
43435
43421
  * Does the content have a specific type of widget?
@@ -44308,6 +44294,7 @@ exports.getSegmentCoords = getSegmentCoords;
44308
44294
  exports.getSinusoidCoords = getSinusoidCoords;
44309
44295
  exports.getValidWidgetIds = getValidWidgetIds;
44310
44296
  exports.getWidgetFromWidgetMap = getWidgetFromWidgetMap;
44297
+ exports.getWidgetSubTypeByWidgetId = getWidgetSubTypeByWidgetId;
44311
44298
  exports.getWidgetTypeByWidgetId = getWidgetTypeByWidgetId;
44312
44299
  exports.getWidgetsFromWidgetMap = getWidgetsFromWidgetMap;
44313
44300
  exports.getWidgetsMapFromItemData = getWidgetsMapFromItemData;