@khanacademy/perseus 21.3.0 → 21.4.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.
package/dist/index.js CHANGED
@@ -10366,7 +10366,7 @@ class WrappedPath extends WrappedDrawing {
10366
10366
  }
10367
10367
  }
10368
10368
 
10369
- class Arrowhead extends WrappedPath {
10369
+ class Arrowhead$1 extends WrappedPath {
10370
10370
  static scale = 1.4;
10371
10371
  constructor(graphie, style) {
10372
10372
  // Points that define the arrowhead
@@ -10374,7 +10374,7 @@ class Arrowhead extends WrappedPath {
10374
10374
  const points = [[-3, 4], [-2.75, 2.5], [0, 0.25], center, [0, -0.25], [-2.75, -2.5], [-3, -4]].map(point => {
10375
10375
  // Scale points by `Arrowhead.scale` around `center`
10376
10376
  const pv = kmath.vector.subtract(point, center);
10377
- const pvScaled = kmath.vector.scale(pv, Arrowhead.scale);
10377
+ const pvScaled = kmath.vector.scale(pv, Arrowhead$1.scale);
10378
10378
  return kmath.vector.add(center, pvScaled);
10379
10379
  });
10380
10380
  const unscaledPoints = points.map(graphie.unscalePoint);
@@ -10391,7 +10391,7 @@ class Arrowhead extends WrappedPath {
10391
10391
  }
10392
10392
  toCoordAtAngle(coord, angle) {
10393
10393
  const clipPoint = this.graphie.scalePoint(getClipPoint(this.graphie, coord, angle));
10394
- this.transform("translateX(" + (clipPoint[0] + Arrowhead.scale * this.center[0]) + "px) " + "translateY(" + (clipPoint[1] + Arrowhead.scale * this.center[1]) + "px) " + "translateZ(0) " + "rotate(" + (360 - KhanMath.bound(angle)) + "deg)");
10394
+ this.transform("translateX(" + (clipPoint[0] + Arrowhead$1.scale * this.center[0]) + "px) " + "translateY(" + (clipPoint[1] + Arrowhead$1.scale * this.center[1]) + "px) " + "translateZ(0) " + "rotate(" + (360 - KhanMath.bound(angle)) + "deg)");
10395
10395
  }
10396
10396
  }
10397
10397
 
@@ -11632,10 +11632,10 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11632
11632
  if (this._arrows == null) {
11633
11633
  this._arrows = [];
11634
11634
  if (this.extendLine) {
11635
- this._arrows.push(new Arrowhead(graph, this.normalStyle));
11636
- this._arrows.push(new Arrowhead(graph, this.normalStyle));
11635
+ this._arrows.push(new Arrowhead$1(graph, this.normalStyle));
11636
+ this._arrows.push(new Arrowhead$1(graph, this.normalStyle));
11637
11637
  } else if (this.extendRay) {
11638
- this._arrows.push(new Arrowhead(graph, this.normalStyle));
11638
+ this._arrows.push(new Arrowhead$1(graph, this.normalStyle));
11639
11639
  }
11640
11640
  }
11641
11641
  const coordForArrow = [this.coordA, this.coordZ];
@@ -14162,16 +14162,16 @@ const draw$3 = {
14162
14162
  // @ts-expect-error - TS2551 - Property '_arrows' does not exist on type '{ readonly basic: (state: any) => void; readonly arrows: (state: any) => void; readonly highlight: (state: any, prevState: any) => void; }'. Did you mean 'arrows'?
14163
14163
  this._arrows.push(
14164
14164
  // @ts-expect-error - TS2339 - Property 'graphie' does not exist on type '{ readonly basic: (state: any) => void; readonly arrows: (state: any) => void; readonly highlight: (state: any, prevState: any) => void; }'. | TS2339 - Property 'normalStyle' does not exist on type '{ readonly basic: (state: any) => void; readonly arrows: (state: any) => void; readonly highlight: (state: any, prevState: any) => void; }'.
14165
- new Arrowhead(this.graphie, this.normalStyle()));
14165
+ new Arrowhead$1(this.graphie, this.normalStyle()));
14166
14166
  // @ts-expect-error - TS2551 - Property '_arrows' does not exist on type '{ readonly basic: (state: any) => void; readonly arrows: (state: any) => void; readonly highlight: (state: any, prevState: any) => void; }'. Did you mean 'arrows'?
14167
14167
  this._arrows.push(
14168
14168
  // @ts-expect-error - TS2339 - Property 'graphie' does not exist on type '{ readonly basic: (state: any) => void; readonly arrows: (state: any) => void; readonly highlight: (state: any, prevState: any) => void; }'. | TS2339 - Property 'normalStyle' does not exist on type '{ readonly basic: (state: any) => void; readonly arrows: (state: any) => void; readonly highlight: (state: any, prevState: any) => void; }'.
14169
- new Arrowhead(this.graphie, this.normalStyle()));
14169
+ new Arrowhead$1(this.graphie, this.normalStyle()));
14170
14170
  } else if (state.extendRay) {
14171
14171
  // @ts-expect-error - TS2551 - Property '_arrows' does not exist on type '{ readonly basic: (state: any) => void; readonly arrows: (state: any) => void; readonly highlight: (state: any, prevState: any) => void; }'. Did you mean 'arrows'?
14172
14172
  this._arrows.push(
14173
14173
  // @ts-expect-error - TS2339 - Property 'graphie' does not exist on type '{ readonly basic: (state: any) => void; readonly arrows: (state: any) => void; readonly highlight: (state: any, prevState: any) => void; }'. | TS2339 - Property 'normalStyle' does not exist on type '{ readonly basic: (state: any) => void; readonly arrows: (state: any) => void; readonly highlight: (state: any, prevState: any) => void; }'.
14174
- new Arrowhead(this.graphie, this.normalStyle()));
14174
+ new Arrowhead$1(this.graphie, this.normalStyle()));
14175
14175
  }
14176
14176
  }
14177
14177
 
@@ -19398,6 +19398,7 @@ class Renderer extends React__namespace.Component {
19398
19398
  images: {},
19399
19399
  highlightedWidgets: [],
19400
19400
  questionCompleted: false,
19401
+ showSolutions: "none",
19401
19402
  // onRender may be called multiple times per render, for example
19402
19403
  // if there are multiple images or TeX pieces within `content`.
19403
19404
  // It is a good idea to debounce any functions passed here.
@@ -19688,6 +19689,7 @@ class Renderer extends React__namespace.Component {
19688
19689
  apiOptions: this.getApiOptions(),
19689
19690
  keypadElement: this.props.keypadElement,
19690
19691
  questionCompleted: this.props.questionCompleted,
19692
+ showSolutions: this.props.showSolutions,
19691
19693
  onFocus: ___default["default"].partial(this._onWidgetFocus, id),
19692
19694
  onBlur: ___default["default"].partial(this._onWidgetBlur, id),
19693
19695
  findWidgets: this.findWidgets,
@@ -23171,14 +23173,6 @@ const ellipsisHorizontalIcon = {
23171
23173
  width: 100,
23172
23174
  height: 27.284
23173
23175
  };
23174
- // Note(TB): Received errors when using useUniqueIdWithMock
23175
- // so created this workaround function. Will update when
23176
- // useUniqueIdWithMock is available.
23177
- // https://khanacademy.atlassian.net/browse/FEI-4861?atlOrigin=eyJpIjoiNDJlZWMwNjM1NWJhNDBkMWFjY2FmN2I0ZjcxZmQxOGUiLCJwIjoiaiJ9
23178
- let id = 0;
23179
- function uniqueId() {
23180
- return `choice-${id++}`;
23181
- }
23182
23176
  const Choice = function (props) {
23183
23177
  const {
23184
23178
  disabled = false,
@@ -23228,7 +23222,8 @@ const Choice = function (props) {
23228
23222
  const showDimmed = !reviewMode && apiOptions.readOnly || crossedOut;
23229
23223
  const letter = getChoiceLetter(pos);
23230
23224
  const a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness);
23231
- const choiceId = uniqueId();
23225
+ const idFactory = wonderBlocksCore.useUniqueIdWithMock(`choice`);
23226
+ const choiceId = idFactory.get(letter);
23232
23227
  return /*#__PURE__*/React__namespace.createElement("div", {
23233
23228
  style: {
23234
23229
  // @ts-expect-error - TS2322 - Type '{ dispay: string; flexDirection: "column"; color: string; }' is not assignable to type 'Properties<string | number, string & {}>'.
@@ -23815,7 +23810,8 @@ class Radio$1 extends React__namespace.Component {
23815
23810
  multipleSelect: false,
23816
23811
  countChoices: false,
23817
23812
  deselectEnabled: false,
23818
- linterContext: PerseusLinter.linterContextDefault
23813
+ linterContext: PerseusLinter.linterContextDefault,
23814
+ showSolutions: "none"
23819
23815
  };
23820
23816
  static validate(userInput, rubric) {
23821
23817
  const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
@@ -23925,6 +23921,11 @@ class Radio$1 extends React__namespace.Component {
23925
23921
  choicesSelected: props.choices.map(() => false)
23926
23922
  };
23927
23923
  }
23924
+ componentDidUpdate(prevProps) {
23925
+ if (this.props.showSolutions === "selected" && prevProps.showSolutions !== "selected") {
23926
+ this.showRationalesForCurrentlySelectedChoices(this.props);
23927
+ }
23928
+ }
23928
23929
  _renderRenderer = (() => {
23929
23930
  var _this = this;
23930
23931
  return function () {
@@ -24047,6 +24048,7 @@ class Radio$1 extends React__namespace.Component {
24047
24048
  * Turn on rationale display for the currently selected choices. Note that
24048
24049
  * this leaves rationales on for choices that are already showing
24049
24050
  * rationales.
24051
+ * @deprecated Internal only. Use `showSolutions` prop instead.
24050
24052
  */
24051
24053
  showRationalesForCurrentlySelectedChoices = rubric => {
24052
24054
  const {
@@ -24068,7 +24070,7 @@ class Radio$1 extends React__namespace.Component {
24068
24070
  // We use the same behavior for the readOnly flag as for
24069
24071
  // rationaleShown, but we keep it separate in case other
24070
24072
  // behaviors want to disable choices without showing rationales.
24071
- readOnly: state.selected || state.readOnly || widgetCorrect,
24073
+ readOnly: state.selected || state.readOnly || widgetCorrect || this.props.showSolutions !== "none",
24072
24074
  correctnessShown: state.selected || state.correctnessShown,
24073
24075
  previouslyAnswered: state.previouslyAnswered || state.selected
24074
24076
  }));
@@ -24119,6 +24121,23 @@ class Radio$1 extends React__namespace.Component {
24119
24121
  correctnessShown: true,
24120
24122
  previouslyAnswered: false
24121
24123
  }));
24124
+ } else if (this.props.showSolutions === "all") {
24125
+ choiceStates = choices.map(_ref => {
24126
+ let {
24127
+ correct
24128
+ } = _ref;
24129
+ return {
24130
+ selected: correct,
24131
+ // to draw the eye to the correct answer
24132
+ crossedOut: false,
24133
+ readOnly: true,
24134
+ highlighted: false,
24135
+ // has no effect in this mode
24136
+ rationaleShown: true,
24137
+ correctnessShown: true,
24138
+ previouslyAnswered: false
24139
+ };
24140
+ });
24122
24141
  } else if (this.props.choiceStates) {
24123
24142
  choiceStates = this.props.choiceStates;
24124
24143
  } else if (this.props.values) {
@@ -28477,6 +28496,118 @@ const getLines = points => points.map((point, i) => {
28477
28496
  return [point, next];
28478
28497
  });
28479
28498
 
28499
+ function SVGLine(props) {
28500
+ const {
28501
+ start,
28502
+ end,
28503
+ style,
28504
+ className,
28505
+ testId
28506
+ } = props;
28507
+ return /*#__PURE__*/React__namespace.createElement("line", {
28508
+ x1: start[0],
28509
+ y1: start[1],
28510
+ x2: end[0],
28511
+ y2: end[1],
28512
+ style: style,
28513
+ className: `${className}`,
28514
+ "data-test-id": testId
28515
+ });
28516
+ }
28517
+
28518
+ function pathBuilder() {
28519
+ return new PathBuilder();
28520
+ }
28521
+ class PathBuilder {
28522
+ path = [];
28523
+ scaleFactor = 1;
28524
+ build() {
28525
+ return this.path.map(scaleCommandBy(this.scaleFactor)).map(commandToString).join("");
28526
+ }
28527
+
28528
+ // add a move (M) command to the path
28529
+ move(x, y) {
28530
+ this.path.push({
28531
+ action: "M",
28532
+ coords: [x, y]
28533
+ });
28534
+ return this;
28535
+ }
28536
+
28537
+ // add a curve (C) command to the path
28538
+ curve(control1X, control1Y, control2X, control2Y, endX, endY) {
28539
+ this.path.push({
28540
+ action: "C",
28541
+ coords: [control1X, control1Y, control2X, control2Y, endX, endY]
28542
+ });
28543
+ return this;
28544
+ }
28545
+
28546
+ // Scale all coordinates of the path by a factor
28547
+ scale(factor) {
28548
+ this.scaleFactor *= factor;
28549
+ return this;
28550
+ }
28551
+ }
28552
+ function commandToString(command) {
28553
+ return `${command.action}${command.coords.join(" ")}`;
28554
+ }
28555
+ function scaleCommandBy(scaleFactor) {
28556
+ return command => ({
28557
+ ...command,
28558
+ coords: command.coords.map(c => c * scaleFactor)
28559
+ });
28560
+ }
28561
+
28562
+ // We use the pathBuilder here to scale up the SVG path coordinates used
28563
+ // elsewhere for arrowheads. We scale in the path itself instead of using a CSS
28564
+ // transform because a transform would also scale up the stroke weight, and we
28565
+ // don't want that.
28566
+ const arrowPath = pathBuilder().move(-3, 4).curve(-2.75, 2.5, 0, 0.25, 0.75, 0).curve(0, -0.25, -2.75, -2.5, -3, -4).scale(1.4).build();
28567
+ function Arrowhead(props) {
28568
+ const [point] = useTransform([props.x, props.y]);
28569
+ return /*#__PURE__*/React__namespace.createElement("g", {
28570
+ transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
28571
+ }, /*#__PURE__*/React__namespace.createElement("g", {
28572
+ transform: "translate(-1.5)"
28573
+ }, /*#__PURE__*/React__namespace.createElement("path", {
28574
+ d: arrowPath,
28575
+ fill: "none",
28576
+ style: {
28577
+ stroke: "inherit"
28578
+ },
28579
+ strokeLinejoin: "round",
28580
+ strokeLinecap: "round"
28581
+ })));
28582
+ }
28583
+
28584
+ function Vector(props) {
28585
+ const {
28586
+ tail,
28587
+ tip,
28588
+ color
28589
+ } = props;
28590
+ const [tailPx, tipPx] = useTransform(tail, tip);
28591
+ const direction = mafs.vec.sub(tip, tail);
28592
+ return /*#__PURE__*/React__namespace.createElement("g", {
28593
+ style: {
28594
+ stroke: color,
28595
+ strokeWidth: 2
28596
+ }
28597
+ }, /*#__PURE__*/React__namespace.createElement(SVGLine, {
28598
+ start: tailPx,
28599
+ end: tipPx
28600
+ }), /*#__PURE__*/React__namespace.createElement(Arrowhead, {
28601
+ x: tip[0],
28602
+ y: tip[1],
28603
+ angle: angleDegrees(direction)
28604
+ }));
28605
+ }
28606
+ function angleDegrees(_ref) {
28607
+ let [x, y] = _ref;
28608
+ return Math.atan2(y, x) * 180 / Math.PI;
28609
+ }
28610
+
28480
28611
  const defaultStroke = "var(--movable-line-stroke-color)";
28481
28612
  const MovableLine = props => {
28482
28613
  const {
@@ -28537,45 +28668,27 @@ const MovableLine = props => {
28537
28668
  stroke,
28538
28669
  strokeWidth: "var(--movable-line-stroke-weight)"
28539
28670
  },
28540
- dragging: dragging,
28671
+ className: dragging ? "movable-dragging" : "",
28541
28672
  testId: "movable-line__line"
28542
- })), startExtend && /*#__PURE__*/React__namespace.createElement(mafs.Vector, {
28673
+ })), startExtend && /*#__PURE__*/React__namespace.createElement(Vector, {
28543
28674
  tail: start,
28544
28675
  tip: startExtend,
28545
28676
  color: stroke
28546
- }), endExtend && /*#__PURE__*/React__namespace.createElement(mafs.Vector, {
28677
+ }), endExtend && /*#__PURE__*/React__namespace.createElement(Vector, {
28547
28678
  tail: end,
28548
28679
  tip: endExtend,
28549
28680
  color: stroke
28550
28681
  }));
28551
28682
  };
28552
- function SVGLine(props) {
28553
- const {
28554
- start,
28555
- end,
28556
- style,
28557
- dragging,
28558
- className,
28559
- testId
28560
- } = props;
28561
- const draggingClass = dragging ? "movable-dragging" : "";
28562
- return /*#__PURE__*/React__namespace.createElement("line", {
28563
- x1: start[0],
28564
- y1: start[1],
28565
- x2: end[0],
28566
- y2: end[1],
28567
- style: style,
28568
- className: `${className} ${draggingClass}`,
28569
- "data-test-id": testId
28570
- });
28571
- }
28572
28683
 
28573
- const GraphStateContext = /*#__PURE__*/React.createContext({
28574
- state: {},
28575
- dispatch: () => {}
28576
- });
28577
- function useGraphState() {
28578
- return React__default["default"].useContext(GraphStateContext);
28684
+ const defaultGraphConfig = {
28685
+ range: [[0, 1], [0, 1]],
28686
+ snapStep: [1, 1],
28687
+ markings: "none"
28688
+ };
28689
+ const GraphConfigContext = /*#__PURE__*/React.createContext(defaultGraphConfig);
28690
+ function useGraphConfig() {
28691
+ return React__default["default"].useContext(GraphConfigContext);
28579
28692
  }
28580
28693
 
28581
28694
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
@@ -28583,8 +28696,10 @@ function useGraphState() {
28583
28696
  const hitboxSizePx = 48;
28584
28697
  const StyledMovablePoint = props => {
28585
28698
  const {
28586
- state
28587
- } = useGraphState();
28699
+ range,
28700
+ snapStep,
28701
+ markings
28702
+ } = useGraphConfig();
28588
28703
  const hitboxRef = React.useRef(null);
28589
28704
  const {
28590
28705
  point,
@@ -28597,17 +28712,34 @@ const StyledMovablePoint = props => {
28597
28712
  gestureTarget: hitboxRef,
28598
28713
  point,
28599
28714
  onMove,
28600
- constrain: p => snap(state.snapStep, p)
28715
+ constrain: p => snap(snapStep, p)
28601
28716
  });
28717
+ const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""}`;
28602
28718
  const [[x, y]] = useTransform(point);
28603
- return /*#__PURE__*/React__namespace.createElement("g", {
28719
+ const [xMin, xMax] = range[0];
28720
+ const [yMin, yMax] = range[1];
28721
+ const [[verticalStartX]] = useTransform([xMin, 0]);
28722
+ const [[verticalEndX]] = useTransform([xMax, 0]);
28723
+ const [[_, horizontalStartY]] = useTransform([0, yMin]);
28724
+ const [[__, horizontalEndY]] = useTransform([0, yMax]);
28725
+ const showHairlines = dragging && markings !== "none";
28726
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && /*#__PURE__*/React__namespace.createElement("g", null, /*#__PURE__*/React__namespace.createElement("line", {
28727
+ x1: verticalStartX,
28728
+ y1: y,
28729
+ x2: verticalEndX,
28730
+ y2: y,
28731
+ stroke: wonderBlocksTokens.color.green
28732
+ }), /*#__PURE__*/React__namespace.createElement("line", {
28733
+ x1: x,
28734
+ y1: horizontalStartY,
28735
+ x2: x,
28736
+ y2: horizontalEndY,
28737
+ stroke: wonderBlocksTokens.color.green
28738
+ })), /*#__PURE__*/React__namespace.createElement("g", {
28604
28739
  ref: hitboxRef,
28605
- className: "movable-point",
28740
+ className: pointClasses,
28606
28741
  tabIndex: 0,
28607
28742
  style: {
28608
- cursor: dragging ? "grabbing" : "grab",
28609
- touchAction: "none",
28610
- outline: "none",
28611
28743
  "--movable-point-color": color
28612
28744
  },
28613
28745
  "data-test-id": "movable-point"
@@ -28636,7 +28768,7 @@ const StyledMovablePoint = props => {
28636
28768
  fill: color
28637
28769
  },
28638
28770
  "data-test-id": "movable-point__center"
28639
- }));
28771
+ })));
28640
28772
  };
28641
28773
 
28642
28774
  const SegmentGraph = props => {
@@ -28644,15 +28776,11 @@ const SegmentGraph = props => {
28644
28776
  dispatch
28645
28777
  } = props;
28646
28778
  const {
28647
- coords: segments,
28648
- snapStep,
28649
- range
28779
+ coords: segments
28650
28780
  } = props.graphState;
28651
28781
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement(SegmentView, {
28652
28782
  key: i,
28653
- collinearPair: segment,
28654
- snaps: snapStep,
28655
- range: range,
28783
+ points: segment,
28656
28784
  onMoveLine: delta => {
28657
28785
  dispatch(moveLine(i, delta));
28658
28786
  },
@@ -28665,7 +28793,7 @@ const SegmentGraph = props => {
28665
28793
  const SegmentView = props => {
28666
28794
  const {
28667
28795
  onMoveLine: onMoveSegment,
28668
- collinearPair: [start, end]
28796
+ points: [start, end]
28669
28797
  } = props;
28670
28798
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableLine, {
28671
28799
  start: start,
@@ -28690,16 +28818,12 @@ const LinearGraph = props => {
28690
28818
  } = props;
28691
28819
  const {
28692
28820
  coords: lines,
28693
- snapStep,
28694
- range,
28695
28821
  type
28696
28822
  } = props.graphState;
28697
28823
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
28698
28824
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(LineView, {
28699
28825
  key: i,
28700
- collinearPair: line,
28701
- snaps: snapStep,
28702
- range: range,
28826
+ points: line,
28703
28827
  onMoveLine: delta => {
28704
28828
  dispatch(moveLine(i, delta));
28705
28829
  },
@@ -28714,10 +28838,12 @@ const LineView = props => {
28714
28838
  const {
28715
28839
  onMoveLine,
28716
28840
  onMovePoint,
28717
- collinearPair: [start, end],
28718
- range,
28841
+ points: [start, end],
28719
28842
  stroke
28720
28843
  } = props;
28844
+ const {
28845
+ range
28846
+ } = useGraphConfig();
28721
28847
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableLine, {
28722
28848
  start: start,
28723
28849
  end: end,
@@ -29039,46 +29165,26 @@ function PointGraph(props) {
29039
29165
  })));
29040
29166
  }
29041
29167
 
29042
- function Arrow(props) {
29043
- const {
29044
- userTransform,
29045
- viewTransform
29046
- } = mafs.useTransformContext();
29047
- const point = [props.x, props.y];
29048
- const userTransformedPoint = mafs.vec.transform(point, userTransform);
29049
- const viewTransformedPoint = mafs.vec.transform(userTransformedPoint, viewTransform);
29050
- return /*#__PURE__*/React__default["default"].createElement("g", {
29051
- transform: `translate(${viewTransformedPoint[0]} ${viewTransformedPoint[1]}) rotate(${props.rotate}) scale(1.25)`
29052
- }, /*#__PURE__*/React__default["default"].createElement("g", {
29053
- transform: "translate(-1)"
29054
- }, /*#__PURE__*/React__default["default"].createElement("path", {
29055
- d: "M-3 4 C-2.75 2.5 0 0.25 0.75 0C0 -0.25 -2.75 -2.5 -3 -4",
29056
- fill: "none"
29057
- })));
29058
- }
29059
29168
  function AxisArrows() {
29060
29169
  const {
29061
- state
29062
- } = useGraphState();
29063
- const range = state.range;
29064
- const [xMin, xMax] = range[0];
29065
- const [yMin, yMax] = range[1];
29066
- return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(Arrow, {
29170
+ range: [[xMin, xMax], [yMin, yMax]]
29171
+ } = useGraphConfig();
29172
+ return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29067
29173
  x: xMax,
29068
29174
  y: 0,
29069
- rotate: 0
29070
- }), /*#__PURE__*/React__default["default"].createElement(Arrow, {
29175
+ angle: 0
29176
+ }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29071
29177
  x: 0,
29072
29178
  y: yMin,
29073
- rotate: 90
29074
- }), /*#__PURE__*/React__default["default"].createElement(Arrow, {
29179
+ angle: 90
29180
+ }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29075
29181
  x: xMin,
29076
29182
  y: 0,
29077
- rotate: 180
29078
- }), /*#__PURE__*/React__default["default"].createElement(Arrow, {
29183
+ angle: 180
29184
+ }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29079
29185
  x: 0,
29080
29186
  y: yMax,
29081
- rotate: 270
29187
+ angle: 270
29082
29188
  }));
29083
29189
  }
29084
29190
 
@@ -29087,9 +29193,20 @@ const tickStyle = {
29087
29193
  stroke: "black",
29088
29194
  strokeWidth: 1
29089
29195
  };
29196
+
29197
+ // We only want to show the initial negative tick labels (e.g. -1) on each
29198
+ // axis if the tickStep > gridStep, to ensure that these labels do not overlap.
29199
+ // e.g. If gridStep = 1 and tickStep = 2, there are 2 grid lines for every 1 tick,
29200
+ // which allows enough room for both these tick labels to render.
29201
+ const showTickLabel = (gridStep, tickStep, label) => {
29202
+ const showLabel = tickStep > gridStep ? true : label !== -tickStep;
29203
+ return showLabel;
29204
+ };
29090
29205
  const YGridTick = _ref => {
29091
29206
  let {
29092
- y
29207
+ y,
29208
+ gridStep,
29209
+ tickStep
29093
29210
  } = _ref;
29094
29211
  const pointOnAxis = [0, y];
29095
29212
  const [[xPosition, yPosition]] = useTransform(pointOnAxis);
@@ -29101,7 +29218,7 @@ const YGridTick = _ref => {
29101
29218
  x2: xPosition + tickSize / 2,
29102
29219
  y2: yPosition,
29103
29220
  style: tickStyle
29104
- }), y !== -1 && /*#__PURE__*/React__namespace.createElement("text", {
29221
+ }), showTickLabel(gridStep, tickStep, y) && /*#__PURE__*/React__namespace.createElement("text", {
29105
29222
  height: 20,
29106
29223
  width: 50,
29107
29224
  textAnchor: "end",
@@ -29111,7 +29228,9 @@ const YGridTick = _ref => {
29111
29228
  };
29112
29229
  const XGridTick = _ref2 => {
29113
29230
  let {
29114
- x
29231
+ x,
29232
+ gridStep,
29233
+ tickStep
29115
29234
  } = _ref2;
29116
29235
  const pointOnAxis = [x, 0];
29117
29236
  const [[xPosition, yPosition]] = useTransform(pointOnAxis);
@@ -29123,7 +29242,7 @@ const XGridTick = _ref2 => {
29123
29242
  x2: xPosition,
29124
29243
  y2: yPosition - tickSize / 2,
29125
29244
  style: tickStyle
29126
- }), x !== -1 && /*#__PURE__*/React__namespace.createElement("text", {
29245
+ }), showTickLabel(gridStep, tickStep, x) && /*#__PURE__*/React__namespace.createElement("text", {
29127
29246
  height: 20,
29128
29247
  width: 50,
29129
29248
  textAnchor: "middle",
@@ -29158,12 +29277,16 @@ const AxisTicks = props => {
29158
29277
  }, yGridTicks.map(y => {
29159
29278
  return /*#__PURE__*/React__namespace.createElement(YGridTick, {
29160
29279
  y: y,
29161
- key: `y-grid-tick-${y}`
29280
+ key: `y-grid-tick-${y}`,
29281
+ gridStep: props.gridStep[0],
29282
+ tickStep: yTickStep
29162
29283
  });
29163
29284
  }), xGridTicks.map(x => {
29164
29285
  return /*#__PURE__*/React__namespace.createElement(XGridTick, {
29165
29286
  x: x,
29166
- key: `x-grid-tick-${x}`
29287
+ key: `x-grid-tick-${x}`,
29288
+ gridStep: props.gridStep[1],
29289
+ tickStep: xTickStep
29167
29290
  });
29168
29291
  }));
29169
29292
  };
@@ -29189,10 +29312,13 @@ const Grid = props => {
29189
29312
  return props.markings === "none" ? null : /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Coordinates.Cartesian, {
29190
29313
  xAxis: axisOptions(props, 0),
29191
29314
  yAxis: axisOptions(props, 1)
29192
- }), /*#__PURE__*/React__namespace.createElement(AxisTicks, {
29315
+ }), props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(AxisArrows, null),
29316
+ // Only render the axis ticks and arrows if the markings are set to a full "graph"
29317
+ props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, {
29193
29318
  range: props.range,
29194
- tickStep: props.tickStep
29195
- }), props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(AxisArrows, null));
29319
+ tickStep: props.tickStep,
29320
+ gridStep: props.gridStep
29321
+ }), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)));
29196
29322
  };
29197
29323
 
29198
29324
  // Displays a stylized open eye: lint warnings are visible
@@ -30163,15 +30289,20 @@ function initializeGraphState(params) {
30163
30289
  graph,
30164
30290
  step,
30165
30291
  snapStep,
30166
- range
30292
+ range,
30293
+ markings
30167
30294
  } = params;
30295
+ const shared = {
30296
+ hasBeenInteractedWith: false,
30297
+ range,
30298
+ snapStep,
30299
+ markings
30300
+ };
30168
30301
  switch (graph.type) {
30169
30302
  case "segment":
30170
30303
  return {
30304
+ ...shared,
30171
30305
  type: "segment",
30172
- hasBeenInteractedWith: false,
30173
- range,
30174
- snapStep,
30175
30306
  coords: getDefaultSegments({
30176
30307
  graph,
30177
30308
  step,
@@ -30182,10 +30313,8 @@ function initializeGraphState(params) {
30182
30313
  case "linear-system":
30183
30314
  case "ray":
30184
30315
  return {
30316
+ ...shared,
30185
30317
  type: graph.type,
30186
- hasBeenInteractedWith: false,
30187
- range,
30188
- snapStep,
30189
30318
  // Linear and ray graphs have a single tuple of points, while a
30190
30319
  // linear system has two tuples of points.
30191
30320
  coords: getLineCoords({
@@ -30196,10 +30325,8 @@ function initializeGraphState(params) {
30196
30325
  };
30197
30326
  case "polygon":
30198
30327
  return {
30328
+ ...shared,
30199
30329
  type: "polygon",
30200
- hasBeenInteractedWith: false,
30201
- range,
30202
- snapStep,
30203
30330
  showAngles: Boolean(graph.showAngles),
30204
30331
  showSides: Boolean(graph.showSides),
30205
30332
  coords: getPolygonCoords({
@@ -30210,10 +30337,8 @@ function initializeGraphState(params) {
30210
30337
  };
30211
30338
  case "point":
30212
30339
  return {
30340
+ ...shared,
30213
30341
  type: graph.type,
30214
- hasBeenInteractedWith: false,
30215
- range,
30216
- snapStep,
30217
30342
  coords: getDefaultPoints({
30218
30343
  graph,
30219
30344
  step,
@@ -30466,10 +30591,11 @@ const MafsGraph = props => {
30466
30591
  React.useEffect(() => {
30467
30592
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
30468
30593
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
30469
- return /*#__PURE__*/React__namespace.createElement(GraphStateContext.Provider, {
30594
+ return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
30470
30595
  value: {
30471
- state,
30472
- dispatch
30596
+ range: state.range,
30597
+ snapStep: state.snapStep,
30598
+ markings: props.markings
30473
30599
  }
30474
30600
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30475
30601
  style: {
@@ -42518,7 +42644,7 @@ const init = function (options) {
42518
42644
 
42519
42645
  // This file is processed by a Rollup plugin (replace) to inject the production
42520
42646
  const libName = "@khanacademy/perseus";
42521
- const libVersion = "21.3.0";
42647
+ const libVersion = "21.4.0";
42522
42648
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
42523
42649
 
42524
42650
  const apiVersion = {