@khanacademy/perseus 21.3.1 → 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,11 +28712,30 @@ 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
  });
28602
28717
  const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""}`;
28603
28718
  const [[x, y]] = useTransform(point);
28604
- 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", {
28605
28739
  ref: hitboxRef,
28606
28740
  className: pointClasses,
28607
28741
  tabIndex: 0,
@@ -28634,7 +28768,7 @@ const StyledMovablePoint = props => {
28634
28768
  fill: color
28635
28769
  },
28636
28770
  "data-test-id": "movable-point__center"
28637
- }));
28771
+ })));
28638
28772
  };
28639
28773
 
28640
28774
  const SegmentGraph = props => {
@@ -28642,15 +28776,11 @@ const SegmentGraph = props => {
28642
28776
  dispatch
28643
28777
  } = props;
28644
28778
  const {
28645
- coords: segments,
28646
- snapStep,
28647
- range
28779
+ coords: segments
28648
28780
  } = props.graphState;
28649
28781
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement(SegmentView, {
28650
28782
  key: i,
28651
- collinearPair: segment,
28652
- snaps: snapStep,
28653
- range: range,
28783
+ points: segment,
28654
28784
  onMoveLine: delta => {
28655
28785
  dispatch(moveLine(i, delta));
28656
28786
  },
@@ -28663,7 +28793,7 @@ const SegmentGraph = props => {
28663
28793
  const SegmentView = props => {
28664
28794
  const {
28665
28795
  onMoveLine: onMoveSegment,
28666
- collinearPair: [start, end]
28796
+ points: [start, end]
28667
28797
  } = props;
28668
28798
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableLine, {
28669
28799
  start: start,
@@ -28688,16 +28818,12 @@ const LinearGraph = props => {
28688
28818
  } = props;
28689
28819
  const {
28690
28820
  coords: lines,
28691
- snapStep,
28692
- range,
28693
28821
  type
28694
28822
  } = props.graphState;
28695
28823
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
28696
28824
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(LineView, {
28697
28825
  key: i,
28698
- collinearPair: line,
28699
- snaps: snapStep,
28700
- range: range,
28826
+ points: line,
28701
28827
  onMoveLine: delta => {
28702
28828
  dispatch(moveLine(i, delta));
28703
28829
  },
@@ -28712,10 +28838,12 @@ const LineView = props => {
28712
28838
  const {
28713
28839
  onMoveLine,
28714
28840
  onMovePoint,
28715
- collinearPair: [start, end],
28716
- range,
28841
+ points: [start, end],
28717
28842
  stroke
28718
28843
  } = props;
28844
+ const {
28845
+ range
28846
+ } = useGraphConfig();
28719
28847
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableLine, {
28720
28848
  start: start,
28721
28849
  end: end,
@@ -29037,46 +29165,26 @@ function PointGraph(props) {
29037
29165
  })));
29038
29166
  }
29039
29167
 
29040
- function Arrow(props) {
29041
- const {
29042
- userTransform,
29043
- viewTransform
29044
- } = mafs.useTransformContext();
29045
- const point = [props.x, props.y];
29046
- const userTransformedPoint = mafs.vec.transform(point, userTransform);
29047
- const viewTransformedPoint = mafs.vec.transform(userTransformedPoint, viewTransform);
29048
- return /*#__PURE__*/React__default["default"].createElement("g", {
29049
- transform: `translate(${viewTransformedPoint[0]} ${viewTransformedPoint[1]}) rotate(${props.rotate}) scale(1.25)`
29050
- }, /*#__PURE__*/React__default["default"].createElement("g", {
29051
- transform: "translate(-1)"
29052
- }, /*#__PURE__*/React__default["default"].createElement("path", {
29053
- d: "M-3 4 C-2.75 2.5 0 0.25 0.75 0C0 -0.25 -2.75 -2.5 -3 -4",
29054
- fill: "none"
29055
- })));
29056
- }
29057
29168
  function AxisArrows() {
29058
29169
  const {
29059
- state
29060
- } = useGraphState();
29061
- const range = state.range;
29062
- const [xMin, xMax] = range[0];
29063
- const [yMin, yMax] = range[1];
29064
- 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, {
29065
29173
  x: xMax,
29066
29174
  y: 0,
29067
- rotate: 0
29068
- }), /*#__PURE__*/React__default["default"].createElement(Arrow, {
29175
+ angle: 0
29176
+ }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29069
29177
  x: 0,
29070
29178
  y: yMin,
29071
- rotate: 90
29072
- }), /*#__PURE__*/React__default["default"].createElement(Arrow, {
29179
+ angle: 90
29180
+ }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29073
29181
  x: xMin,
29074
29182
  y: 0,
29075
- rotate: 180
29076
- }), /*#__PURE__*/React__default["default"].createElement(Arrow, {
29183
+ angle: 180
29184
+ }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29077
29185
  x: 0,
29078
29186
  y: yMax,
29079
- rotate: 270
29187
+ angle: 270
29080
29188
  }));
29081
29189
  }
29082
29190
 
@@ -29085,9 +29193,20 @@ const tickStyle = {
29085
29193
  stroke: "black",
29086
29194
  strokeWidth: 1
29087
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
+ };
29088
29205
  const YGridTick = _ref => {
29089
29206
  let {
29090
- y
29207
+ y,
29208
+ gridStep,
29209
+ tickStep
29091
29210
  } = _ref;
29092
29211
  const pointOnAxis = [0, y];
29093
29212
  const [[xPosition, yPosition]] = useTransform(pointOnAxis);
@@ -29099,7 +29218,7 @@ const YGridTick = _ref => {
29099
29218
  x2: xPosition + tickSize / 2,
29100
29219
  y2: yPosition,
29101
29220
  style: tickStyle
29102
- }), y !== -1 && /*#__PURE__*/React__namespace.createElement("text", {
29221
+ }), showTickLabel(gridStep, tickStep, y) && /*#__PURE__*/React__namespace.createElement("text", {
29103
29222
  height: 20,
29104
29223
  width: 50,
29105
29224
  textAnchor: "end",
@@ -29109,7 +29228,9 @@ const YGridTick = _ref => {
29109
29228
  };
29110
29229
  const XGridTick = _ref2 => {
29111
29230
  let {
29112
- x
29231
+ x,
29232
+ gridStep,
29233
+ tickStep
29113
29234
  } = _ref2;
29114
29235
  const pointOnAxis = [x, 0];
29115
29236
  const [[xPosition, yPosition]] = useTransform(pointOnAxis);
@@ -29121,7 +29242,7 @@ const XGridTick = _ref2 => {
29121
29242
  x2: xPosition,
29122
29243
  y2: yPosition - tickSize / 2,
29123
29244
  style: tickStyle
29124
- }), x !== -1 && /*#__PURE__*/React__namespace.createElement("text", {
29245
+ }), showTickLabel(gridStep, tickStep, x) && /*#__PURE__*/React__namespace.createElement("text", {
29125
29246
  height: 20,
29126
29247
  width: 50,
29127
29248
  textAnchor: "middle",
@@ -29156,12 +29277,16 @@ const AxisTicks = props => {
29156
29277
  }, yGridTicks.map(y => {
29157
29278
  return /*#__PURE__*/React__namespace.createElement(YGridTick, {
29158
29279
  y: y,
29159
- key: `y-grid-tick-${y}`
29280
+ key: `y-grid-tick-${y}`,
29281
+ gridStep: props.gridStep[0],
29282
+ tickStep: yTickStep
29160
29283
  });
29161
29284
  }), xGridTicks.map(x => {
29162
29285
  return /*#__PURE__*/React__namespace.createElement(XGridTick, {
29163
29286
  x: x,
29164
- key: `x-grid-tick-${x}`
29287
+ key: `x-grid-tick-${x}`,
29288
+ gridStep: props.gridStep[1],
29289
+ tickStep: xTickStep
29165
29290
  });
29166
29291
  }));
29167
29292
  };
@@ -29187,10 +29312,13 @@ const Grid = props => {
29187
29312
  return props.markings === "none" ? null : /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Coordinates.Cartesian, {
29188
29313
  xAxis: axisOptions(props, 0),
29189
29314
  yAxis: axisOptions(props, 1)
29190
- }), /*#__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, {
29191
29318
  range: props.range,
29192
- tickStep: props.tickStep
29193
- }), props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(AxisArrows, null));
29319
+ tickStep: props.tickStep,
29320
+ gridStep: props.gridStep
29321
+ }), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)));
29194
29322
  };
29195
29323
 
29196
29324
  // Displays a stylized open eye: lint warnings are visible
@@ -30161,15 +30289,20 @@ function initializeGraphState(params) {
30161
30289
  graph,
30162
30290
  step,
30163
30291
  snapStep,
30164
- range
30292
+ range,
30293
+ markings
30165
30294
  } = params;
30295
+ const shared = {
30296
+ hasBeenInteractedWith: false,
30297
+ range,
30298
+ snapStep,
30299
+ markings
30300
+ };
30166
30301
  switch (graph.type) {
30167
30302
  case "segment":
30168
30303
  return {
30304
+ ...shared,
30169
30305
  type: "segment",
30170
- hasBeenInteractedWith: false,
30171
- range,
30172
- snapStep,
30173
30306
  coords: getDefaultSegments({
30174
30307
  graph,
30175
30308
  step,
@@ -30180,10 +30313,8 @@ function initializeGraphState(params) {
30180
30313
  case "linear-system":
30181
30314
  case "ray":
30182
30315
  return {
30316
+ ...shared,
30183
30317
  type: graph.type,
30184
- hasBeenInteractedWith: false,
30185
- range,
30186
- snapStep,
30187
30318
  // Linear and ray graphs have a single tuple of points, while a
30188
30319
  // linear system has two tuples of points.
30189
30320
  coords: getLineCoords({
@@ -30194,10 +30325,8 @@ function initializeGraphState(params) {
30194
30325
  };
30195
30326
  case "polygon":
30196
30327
  return {
30328
+ ...shared,
30197
30329
  type: "polygon",
30198
- hasBeenInteractedWith: false,
30199
- range,
30200
- snapStep,
30201
30330
  showAngles: Boolean(graph.showAngles),
30202
30331
  showSides: Boolean(graph.showSides),
30203
30332
  coords: getPolygonCoords({
@@ -30208,10 +30337,8 @@ function initializeGraphState(params) {
30208
30337
  };
30209
30338
  case "point":
30210
30339
  return {
30340
+ ...shared,
30211
30341
  type: graph.type,
30212
- hasBeenInteractedWith: false,
30213
- range,
30214
- snapStep,
30215
30342
  coords: getDefaultPoints({
30216
30343
  graph,
30217
30344
  step,
@@ -30464,10 +30591,11 @@ const MafsGraph = props => {
30464
30591
  React.useEffect(() => {
30465
30592
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
30466
30593
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
30467
- return /*#__PURE__*/React__namespace.createElement(GraphStateContext.Provider, {
30594
+ return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
30468
30595
  value: {
30469
- state,
30470
- dispatch
30596
+ range: state.range,
30597
+ snapStep: state.snapStep,
30598
+ markings: props.markings
30471
30599
  }
30472
30600
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30473
30601
  style: {
@@ -42516,7 +42644,7 @@ const init = function (options) {
42516
42644
 
42517
42645
  // This file is processed by a Rollup plugin (replace) to inject the production
42518
42646
  const libName = "@khanacademy/perseus";
42519
- const libVersion = "21.3.1";
42647
+ const libVersion = "21.4.0";
42520
42648
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
42521
42649
 
42522
42650
  const apiVersion = {