@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/es/index.js CHANGED
@@ -2,7 +2,7 @@ import * as KAS from '@khanacademy/kas';
2
2
  import { keyTranslator, CursorContext, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
3
  import * as PerseusLinter from '@khanacademy/perseus-linter';
4
4
  import { linterContextDefault, Rule, linterContextProps } from '@khanacademy/perseus-linter';
5
- import { View, RenderStateRoot, UniqueIDProvider, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
5
+ import { View, RenderStateRoot, useUniqueIdWithMock, UniqueIDProvider, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
6
6
  import * as i18n from '@khanacademy/wonder-blocks-i18n';
7
7
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
8
8
  import classNames from 'classnames';
@@ -30,7 +30,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
30
30
  import caretDown from '@phosphor-icons/core/assets/regular/caret-down.svg';
31
31
  import caretUp from '@phosphor-icons/core/assets/regular/caret-up.svg';
32
32
  import debounce$2 from 'lodash.debounce';
33
- import { Point as Point$2, useTransformContext, vec, useMovable, Vector, Text as Text$1, Polygon, Coordinates, Mafs } from 'mafs';
33
+ import { Point as Point$2, useTransformContext, vec, useMovable, Text as Text$1, Polygon, Coordinates, Mafs } from 'mafs';
34
34
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
35
35
  import questionIcon from '@phosphor-icons/core/regular/question.svg';
36
36
  import { LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
@@ -4259,9 +4259,9 @@ function _classPrivateFieldBase(receiver, privateKey) {
4259
4259
  return receiver;
4260
4260
  }
4261
4261
 
4262
- var id$1 = 0;
4262
+ var id = 0;
4263
4263
  function _classPrivateFieldKey(name) {
4264
- return "__private_" + id$1++ + "_" + name;
4264
+ return "__private_" + id++ + "_" + name;
4265
4265
  }
4266
4266
 
4267
4267
  /*!
@@ -10366,14 +10366,14 @@ class WrappedPath extends WrappedDrawing {
10366
10366
  }
10367
10367
  }
10368
10368
 
10369
- class Arrowhead extends WrappedPath {
10369
+ class Arrowhead$1 extends WrappedPath {
10370
10370
  constructor(graphie, style) {
10371
10371
  // Points that define the arrowhead
10372
10372
  const center = [0.75, 0];
10373
10373
  const points = [[-3, 4], [-2.75, 2.5], [0, 0.25], center, [0, -0.25], [-2.75, -2.5], [-3, -4]].map(point => {
10374
10374
  // Scale points by `Arrowhead.scale` around `center`
10375
10375
  const pv = vector$1.subtract(point, center);
10376
- const pvScaled = vector$1.scale(pv, Arrowhead.scale);
10376
+ const pvScaled = vector$1.scale(pv, Arrowhead$1.scale);
10377
10377
  return vector$1.add(center, pvScaled);
10378
10378
  });
10379
10379
  const unscaledPoints = points.map(graphie.unscalePoint);
@@ -10391,14 +10391,14 @@ 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
 
10398
10398
  // We can't just pass in a path to `graph.fixedPath` as we need to modify
10399
10399
  // the points in some way, so instead we provide a function for creating
10400
10400
  // the path once the points have been transformed
10401
- Arrowhead.scale = 1.4;
10401
+ Arrowhead$1.scale = 1.4;
10402
10402
  const createCubicPath = function createCubicPath(points) {
10403
10403
  let path = "M" + points[0][0] + " " + points[0][1];
10404
10404
  for (let i = 1; i < points.length; i += 3) {
@@ -11638,10 +11638,10 @@ _.extend(GraphUtils.Graphie.prototype, {
11638
11638
  if (this._arrows == null) {
11639
11639
  this._arrows = [];
11640
11640
  if (this.extendLine) {
11641
- this._arrows.push(new Arrowhead(graph, this.normalStyle));
11642
- this._arrows.push(new Arrowhead(graph, this.normalStyle));
11641
+ this._arrows.push(new Arrowhead$1(graph, this.normalStyle));
11642
+ this._arrows.push(new Arrowhead$1(graph, this.normalStyle));
11643
11643
  } else if (this.extendRay) {
11644
- this._arrows.push(new Arrowhead(graph, this.normalStyle));
11644
+ this._arrows.push(new Arrowhead$1(graph, this.normalStyle));
11645
11645
  }
11646
11646
  }
11647
11647
  const coordForArrow = [this.coordA, this.coordZ];
@@ -14146,16 +14146,16 @@ const draw$3 = {
14146
14146
  // @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'?
14147
14147
  this._arrows.push(
14148
14148
  // @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; }'.
14149
- new Arrowhead(this.graphie, this.normalStyle()));
14149
+ new Arrowhead$1(this.graphie, this.normalStyle()));
14150
14150
  // @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'?
14151
14151
  this._arrows.push(
14152
14152
  // @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; }'.
14153
- new Arrowhead(this.graphie, this.normalStyle()));
14153
+ new Arrowhead$1(this.graphie, this.normalStyle()));
14154
14154
  } else if (state.extendRay) {
14155
14155
  // @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'?
14156
14156
  this._arrows.push(
14157
14157
  // @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; }'.
14158
- new Arrowhead(this.graphie, this.normalStyle()));
14158
+ new Arrowhead$1(this.graphie, this.normalStyle()));
14159
14159
  }
14160
14160
  }
14161
14161
 
@@ -19463,6 +19463,7 @@ class Renderer extends React.Component {
19463
19463
  apiOptions: this.getApiOptions(),
19464
19464
  keypadElement: this.props.keypadElement,
19465
19465
  questionCompleted: this.props.questionCompleted,
19466
+ showSolutions: this.props.showSolutions,
19466
19467
  onFocus: _.partial(this._onWidgetFocus, id),
19467
19468
  onBlur: _.partial(this._onWidgetBlur, id),
19468
19469
  findWidgets: this.findWidgets,
@@ -20657,6 +20658,7 @@ Renderer.defaultProps = {
20657
20658
  images: {},
20658
20659
  highlightedWidgets: [],
20659
20660
  questionCompleted: false,
20661
+ showSolutions: "none",
20660
20662
  // onRender may be called multiple times per render, for example
20661
20663
  // if there are multiple images or TeX pieces within `content`.
20662
20664
  // It is a good idea to debounce any functions passed here.
@@ -23076,14 +23078,6 @@ const ellipsisHorizontalIcon = {
23076
23078
  width: 100,
23077
23079
  height: 27.284
23078
23080
  };
23079
- // Note(TB): Received errors when using useUniqueIdWithMock
23080
- // so created this workaround function. Will update when
23081
- // useUniqueIdWithMock is available.
23082
- // https://khanacademy.atlassian.net/browse/FEI-4861?atlOrigin=eyJpIjoiNDJlZWMwNjM1NWJhNDBkMWFjY2FmN2I0ZjcxZmQxOGUiLCJwIjoiaiJ9
23083
- let id = 0;
23084
- function uniqueId() {
23085
- return `choice-${id++}`;
23086
- }
23087
23081
  const Choice = function Choice(props) {
23088
23082
  const {
23089
23083
  disabled = false,
@@ -23134,7 +23128,8 @@ const Choice = function Choice(props) {
23134
23128
  const showDimmed = !reviewMode && apiOptions.readOnly || crossedOut;
23135
23129
  const letter = getChoiceLetter(pos);
23136
23130
  const a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness);
23137
- const choiceId = uniqueId();
23131
+ const idFactory = useUniqueIdWithMock(`choice`);
23132
+ const choiceId = idFactory.get(letter);
23138
23133
  return /*#__PURE__*/React.createElement("div", {
23139
23134
  style: {
23140
23135
  // @ts-expect-error - TS2322 - Type '{ dispay: string; flexDirection: "column"; color: string; }' is not assignable to type 'Properties<string | number, string & {}>'.
@@ -23807,6 +23802,7 @@ class Radio$1 extends React.Component {
23807
23802
  * Turn on rationale display for the currently selected choices. Note that
23808
23803
  * this leaves rationales on for choices that are already showing
23809
23804
  * rationales.
23805
+ * @deprecated Internal only. Use `showSolutions` prop instead.
23810
23806
  */
23811
23807
  this.showRationalesForCurrentlySelectedChoices = rubric => {
23812
23808
  const {
@@ -23827,7 +23823,7 @@ class Radio$1 extends React.Component {
23827
23823
  // We use the same behavior for the readOnly flag as for
23828
23824
  // rationaleShown, but we keep it separate in case other
23829
23825
  // behaviors want to disable choices without showing rationales.
23830
- readOnly: state.selected || state.readOnly || widgetCorrect,
23826
+ readOnly: state.selected || state.readOnly || widgetCorrect || this.props.showSolutions !== "none",
23831
23827
  correctnessShown: state.selected || state.correctnessShown,
23832
23828
  previouslyAnswered: state.previouslyAnswered || state.selected
23833
23829
  }));
@@ -23969,6 +23965,11 @@ class Radio$1 extends React.Component {
23969
23965
  choicesSelected: props.choices.map(() => false)
23970
23966
  };
23971
23967
  }
23968
+ componentDidUpdate(prevProps) {
23969
+ if (this.props.showSolutions === "selected" && prevProps.showSolutions !== "selected") {
23970
+ this.showRationalesForCurrentlySelectedChoices(this.props);
23971
+ }
23972
+ }
23972
23973
  // TODO(LP-10672): I think this might be unused right now. I can't find anywhere
23973
23974
  // that we pass a value to `.focus()` and it seems to have been used for
23974
23975
  // adding hints when editing.
@@ -24000,6 +24001,20 @@ class Radio$1 extends React.Component {
24000
24001
  correctnessShown: true,
24001
24002
  previouslyAnswered: false
24002
24003
  }));
24004
+ } else if (this.props.showSolutions === "all") {
24005
+ choiceStates = choices.map(({
24006
+ correct
24007
+ }) => ({
24008
+ selected: correct,
24009
+ // to draw the eye to the correct answer
24010
+ crossedOut: false,
24011
+ readOnly: true,
24012
+ highlighted: false,
24013
+ // has no effect in this mode
24014
+ rationaleShown: true,
24015
+ correctnessShown: true,
24016
+ previouslyAnswered: false
24017
+ }));
24003
24018
  } else if (this.props.choiceStates) {
24004
24019
  choiceStates = this.props.choiceStates;
24005
24020
  } else if (this.props.values) {
@@ -24087,7 +24102,8 @@ Radio$1.defaultProps = {
24087
24102
  multipleSelect: false,
24088
24103
  countChoices: false,
24089
24104
  deselectEnabled: false,
24090
- linterContext: linterContextDefault
24105
+ linterContext: linterContextDefault,
24106
+ showSolutions: "none"
24091
24107
  };
24092
24108
 
24093
24109
  const {
@@ -28322,6 +28338,118 @@ const getLines = points => points.map((point, i) => {
28322
28338
  return [point, next];
28323
28339
  });
28324
28340
 
28341
+ function SVGLine(props) {
28342
+ const {
28343
+ start,
28344
+ end,
28345
+ style,
28346
+ className,
28347
+ testId
28348
+ } = props;
28349
+ return /*#__PURE__*/React.createElement("line", {
28350
+ x1: start[0],
28351
+ y1: start[1],
28352
+ x2: end[0],
28353
+ y2: end[1],
28354
+ style: style,
28355
+ className: `${className}`,
28356
+ "data-test-id": testId
28357
+ });
28358
+ }
28359
+
28360
+ function pathBuilder() {
28361
+ return new PathBuilder();
28362
+ }
28363
+ class PathBuilder {
28364
+ constructor() {
28365
+ this.path = [];
28366
+ this.scaleFactor = 1;
28367
+ }
28368
+ build() {
28369
+ return this.path.map(scaleCommandBy(this.scaleFactor)).map(commandToString).join("");
28370
+ }
28371
+
28372
+ // add a move (M) command to the path
28373
+ move(x, y) {
28374
+ this.path.push({
28375
+ action: "M",
28376
+ coords: [x, y]
28377
+ });
28378
+ return this;
28379
+ }
28380
+
28381
+ // add a curve (C) command to the path
28382
+ curve(control1X, control1Y, control2X, control2Y, endX, endY) {
28383
+ this.path.push({
28384
+ action: "C",
28385
+ coords: [control1X, control1Y, control2X, control2Y, endX, endY]
28386
+ });
28387
+ return this;
28388
+ }
28389
+
28390
+ // Scale all coordinates of the path by a factor
28391
+ scale(factor) {
28392
+ this.scaleFactor *= factor;
28393
+ return this;
28394
+ }
28395
+ }
28396
+ function commandToString(command) {
28397
+ return `${command.action}${command.coords.join(" ")}`;
28398
+ }
28399
+ function scaleCommandBy(scaleFactor) {
28400
+ return command => _extends({}, command, {
28401
+ coords: command.coords.map(c => c * scaleFactor)
28402
+ });
28403
+ }
28404
+
28405
+ // We use the pathBuilder here to scale up the SVG path coordinates used
28406
+ // elsewhere for arrowheads. We scale in the path itself instead of using a CSS
28407
+ // transform because a transform would also scale up the stroke weight, and we
28408
+ // don't want that.
28409
+ 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();
28410
+ function Arrowhead(props) {
28411
+ const [point] = useTransform([props.x, props.y]);
28412
+ return /*#__PURE__*/React.createElement("g", {
28413
+ transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
28414
+ }, /*#__PURE__*/React.createElement("g", {
28415
+ transform: "translate(-1.5)"
28416
+ }, /*#__PURE__*/React.createElement("path", {
28417
+ d: arrowPath,
28418
+ fill: "none",
28419
+ style: {
28420
+ stroke: "inherit"
28421
+ },
28422
+ strokeLinejoin: "round",
28423
+ strokeLinecap: "round"
28424
+ })));
28425
+ }
28426
+
28427
+ function Vector(props) {
28428
+ const {
28429
+ tail,
28430
+ tip,
28431
+ color
28432
+ } = props;
28433
+ const [tailPx, tipPx] = useTransform(tail, tip);
28434
+ const direction = vec.sub(tip, tail);
28435
+ return /*#__PURE__*/React.createElement("g", {
28436
+ style: {
28437
+ stroke: color,
28438
+ strokeWidth: 2
28439
+ }
28440
+ }, /*#__PURE__*/React.createElement(SVGLine, {
28441
+ start: tailPx,
28442
+ end: tipPx
28443
+ }), /*#__PURE__*/React.createElement(Arrowhead, {
28444
+ x: tip[0],
28445
+ y: tip[1],
28446
+ angle: angleDegrees(direction)
28447
+ }));
28448
+ }
28449
+ function angleDegrees([x, y]) {
28450
+ return Math.atan2(y, x) * 180 / Math.PI;
28451
+ }
28452
+
28325
28453
  const defaultStroke = "var(--movable-line-stroke-color)";
28326
28454
  const MovableLine = props => {
28327
28455
  const {
@@ -28382,7 +28510,7 @@ const MovableLine = props => {
28382
28510
  stroke,
28383
28511
  strokeWidth: "var(--movable-line-stroke-weight)"
28384
28512
  },
28385
- dragging: dragging,
28513
+ className: dragging ? "movable-dragging" : "",
28386
28514
  testId: "movable-line__line"
28387
28515
  })), startExtend && /*#__PURE__*/React.createElement(Vector, {
28388
28516
  tail: start,
@@ -28394,33 +28522,15 @@ const MovableLine = props => {
28394
28522
  color: stroke
28395
28523
  }));
28396
28524
  };
28397
- function SVGLine(props) {
28398
- const {
28399
- start,
28400
- end,
28401
- style,
28402
- dragging,
28403
- className,
28404
- testId
28405
- } = props;
28406
- const draggingClass = dragging ? "movable-dragging" : "";
28407
- return /*#__PURE__*/React.createElement("line", {
28408
- x1: start[0],
28409
- y1: start[1],
28410
- x2: end[0],
28411
- y2: end[1],
28412
- style: style,
28413
- className: `${className} ${draggingClass}`,
28414
- "data-test-id": testId
28415
- });
28416
- }
28417
28525
 
28418
- const GraphStateContext = /*#__PURE__*/createContext({
28419
- state: {},
28420
- dispatch: () => {}
28421
- });
28422
- function useGraphState() {
28423
- return React__default.useContext(GraphStateContext);
28526
+ const defaultGraphConfig = {
28527
+ range: [[0, 1], [0, 1]],
28528
+ snapStep: [1, 1],
28529
+ markings: "none"
28530
+ };
28531
+ const GraphConfigContext = /*#__PURE__*/createContext(defaultGraphConfig);
28532
+ function useGraphConfig() {
28533
+ return React__default.useContext(GraphConfigContext);
28424
28534
  }
28425
28535
 
28426
28536
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
@@ -28428,8 +28538,10 @@ function useGraphState() {
28428
28538
  const hitboxSizePx = 48;
28429
28539
  const StyledMovablePoint = props => {
28430
28540
  const {
28431
- state
28432
- } = useGraphState();
28541
+ range,
28542
+ snapStep,
28543
+ markings
28544
+ } = useGraphConfig();
28433
28545
  const hitboxRef = useRef(null);
28434
28546
  const {
28435
28547
  point,
@@ -28442,17 +28554,34 @@ const StyledMovablePoint = props => {
28442
28554
  gestureTarget: hitboxRef,
28443
28555
  point,
28444
28556
  onMove,
28445
- constrain: p => snap(state.snapStep, p)
28557
+ constrain: p => snap(snapStep, p)
28446
28558
  });
28559
+ const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""}`;
28447
28560
  const [[x, y]] = useTransform(point);
28448
- return /*#__PURE__*/React.createElement("g", {
28561
+ const [xMin, xMax] = range[0];
28562
+ const [yMin, yMax] = range[1];
28563
+ const [[verticalStartX]] = useTransform([xMin, 0]);
28564
+ const [[verticalEndX]] = useTransform([xMax, 0]);
28565
+ const [[_, horizontalStartY]] = useTransform([0, yMin]);
28566
+ const [[__, horizontalEndY]] = useTransform([0, yMax]);
28567
+ const showHairlines = dragging && markings !== "none";
28568
+ return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && /*#__PURE__*/React.createElement("g", null, /*#__PURE__*/React.createElement("line", {
28569
+ x1: verticalStartX,
28570
+ y1: y,
28571
+ x2: verticalEndX,
28572
+ y2: y,
28573
+ stroke: color.green
28574
+ }), /*#__PURE__*/React.createElement("line", {
28575
+ x1: x,
28576
+ y1: horizontalStartY,
28577
+ x2: x,
28578
+ y2: horizontalEndY,
28579
+ stroke: color.green
28580
+ })), /*#__PURE__*/React.createElement("g", {
28449
28581
  ref: hitboxRef,
28450
- className: "movable-point",
28582
+ className: pointClasses,
28451
28583
  tabIndex: 0,
28452
28584
  style: {
28453
- cursor: dragging ? "grabbing" : "grab",
28454
- touchAction: "none",
28455
- outline: "none",
28456
28585
  "--movable-point-color": color$1
28457
28586
  },
28458
28587
  "data-test-id": "movable-point"
@@ -28481,7 +28610,7 @@ const StyledMovablePoint = props => {
28481
28610
  fill: color$1
28482
28611
  },
28483
28612
  "data-test-id": "movable-point__center"
28484
- }));
28613
+ })));
28485
28614
  };
28486
28615
 
28487
28616
  const SegmentGraph = props => {
@@ -28489,15 +28618,11 @@ const SegmentGraph = props => {
28489
28618
  dispatch
28490
28619
  } = props;
28491
28620
  const {
28492
- coords: segments,
28493
- snapStep,
28494
- range
28621
+ coords: segments
28495
28622
  } = props.graphState;
28496
28623
  return /*#__PURE__*/React.createElement(React.Fragment, null, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement(SegmentView, {
28497
28624
  key: i,
28498
- collinearPair: segment,
28499
- snaps: snapStep,
28500
- range: range,
28625
+ points: segment,
28501
28626
  onMoveLine: delta => {
28502
28627
  dispatch(moveLine(i, delta));
28503
28628
  },
@@ -28510,7 +28635,7 @@ const SegmentGraph = props => {
28510
28635
  const SegmentView = props => {
28511
28636
  const {
28512
28637
  onMoveLine: onMoveSegment,
28513
- collinearPair: [start, end]
28638
+ points: [start, end]
28514
28639
  } = props;
28515
28640
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableLine, {
28516
28641
  start: start,
@@ -28535,16 +28660,12 @@ const LinearGraph = props => {
28535
28660
  } = props;
28536
28661
  const {
28537
28662
  coords: lines,
28538
- snapStep,
28539
- range,
28540
28663
  type
28541
28664
  } = props.graphState;
28542
28665
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
28543
28666
  return /*#__PURE__*/React.createElement(React.Fragment, null, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(LineView, {
28544
28667
  key: i,
28545
- collinearPair: line,
28546
- snaps: snapStep,
28547
- range: range,
28668
+ points: line,
28548
28669
  onMoveLine: delta => {
28549
28670
  dispatch(moveLine(i, delta));
28550
28671
  },
@@ -28559,10 +28680,12 @@ const LineView = props => {
28559
28680
  const {
28560
28681
  onMoveLine,
28561
28682
  onMovePoint,
28562
- collinearPair: [start, end],
28563
- range,
28683
+ points: [start, end],
28564
28684
  stroke
28565
28685
  } = props;
28686
+ const {
28687
+ range
28688
+ } = useGraphConfig();
28566
28689
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableLine, {
28567
28690
  start: start,
28568
28691
  end: end,
@@ -28876,46 +28999,26 @@ function PointGraph(props) {
28876
28999
  })));
28877
29000
  }
28878
29001
 
28879
- function Arrow(props) {
28880
- const {
28881
- userTransform,
28882
- viewTransform
28883
- } = useTransformContext();
28884
- const point = [props.x, props.y];
28885
- const userTransformedPoint = vec.transform(point, userTransform);
28886
- const viewTransformedPoint = vec.transform(userTransformedPoint, viewTransform);
28887
- return /*#__PURE__*/React__default.createElement("g", {
28888
- transform: `translate(${viewTransformedPoint[0]} ${viewTransformedPoint[1]}) rotate(${props.rotate}) scale(1.25)`
28889
- }, /*#__PURE__*/React__default.createElement("g", {
28890
- transform: "translate(-1)"
28891
- }, /*#__PURE__*/React__default.createElement("path", {
28892
- d: "M-3 4 C-2.75 2.5 0 0.25 0.75 0C0 -0.25 -2.75 -2.5 -3 -4",
28893
- fill: "none"
28894
- })));
28895
- }
28896
29002
  function AxisArrows() {
28897
29003
  const {
28898
- state
28899
- } = useGraphState();
28900
- const range = state.range;
28901
- const [xMin, xMax] = range[0];
28902
- const [yMin, yMax] = range[1];
28903
- return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(Arrow, {
29004
+ range: [[xMin, xMax], [yMin, yMax]]
29005
+ } = useGraphConfig();
29006
+ return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(Arrowhead, {
28904
29007
  x: xMax,
28905
29008
  y: 0,
28906
- rotate: 0
28907
- }), /*#__PURE__*/React__default.createElement(Arrow, {
29009
+ angle: 0
29010
+ }), /*#__PURE__*/React__default.createElement(Arrowhead, {
28908
29011
  x: 0,
28909
29012
  y: yMin,
28910
- rotate: 90
28911
- }), /*#__PURE__*/React__default.createElement(Arrow, {
29013
+ angle: 90
29014
+ }), /*#__PURE__*/React__default.createElement(Arrowhead, {
28912
29015
  x: xMin,
28913
29016
  y: 0,
28914
- rotate: 180
28915
- }), /*#__PURE__*/React__default.createElement(Arrow, {
29017
+ angle: 180
29018
+ }), /*#__PURE__*/React__default.createElement(Arrowhead, {
28916
29019
  x: 0,
28917
29020
  y: yMax,
28918
- rotate: 270
29021
+ angle: 270
28919
29022
  }));
28920
29023
  }
28921
29024
 
@@ -28924,8 +29027,19 @@ const tickStyle = {
28924
29027
  stroke: "black",
28925
29028
  strokeWidth: 1
28926
29029
  };
29030
+
29031
+ // We only want to show the initial negative tick labels (e.g. -1) on each
29032
+ // axis if the tickStep > gridStep, to ensure that these labels do not overlap.
29033
+ // e.g. If gridStep = 1 and tickStep = 2, there are 2 grid lines for every 1 tick,
29034
+ // which allows enough room for both these tick labels to render.
29035
+ const showTickLabel = (gridStep, tickStep, label) => {
29036
+ const showLabel = tickStep > gridStep ? true : label !== -tickStep;
29037
+ return showLabel;
29038
+ };
28927
29039
  const YGridTick = ({
28928
- y
29040
+ y,
29041
+ gridStep,
29042
+ tickStep
28929
29043
  }) => {
28930
29044
  const pointOnAxis = [0, y];
28931
29045
  const [[xPosition, yPosition]] = useTransform(pointOnAxis);
@@ -28937,7 +29051,7 @@ const YGridTick = ({
28937
29051
  x2: xPosition + tickSize / 2,
28938
29052
  y2: yPosition,
28939
29053
  style: tickStyle
28940
- }), y !== -1 && /*#__PURE__*/React.createElement("text", {
29054
+ }), showTickLabel(gridStep, tickStep, y) && /*#__PURE__*/React.createElement("text", {
28941
29055
  height: 20,
28942
29056
  width: 50,
28943
29057
  textAnchor: "end",
@@ -28946,7 +29060,9 @@ const YGridTick = ({
28946
29060
  }, y.toString()));
28947
29061
  };
28948
29062
  const XGridTick = ({
28949
- x
29063
+ x,
29064
+ gridStep,
29065
+ tickStep
28950
29066
  }) => {
28951
29067
  const pointOnAxis = [x, 0];
28952
29068
  const [[xPosition, yPosition]] = useTransform(pointOnAxis);
@@ -28958,7 +29074,7 @@ const XGridTick = ({
28958
29074
  x2: xPosition,
28959
29075
  y2: yPosition - tickSize / 2,
28960
29076
  style: tickStyle
28961
- }), x !== -1 && /*#__PURE__*/React.createElement("text", {
29077
+ }), showTickLabel(gridStep, tickStep, x) && /*#__PURE__*/React.createElement("text", {
28962
29078
  height: 20,
28963
29079
  width: 50,
28964
29080
  textAnchor: "middle",
@@ -28993,12 +29109,16 @@ const AxisTicks = props => {
28993
29109
  }, yGridTicks.map(y => {
28994
29110
  return /*#__PURE__*/React.createElement(YGridTick, {
28995
29111
  y: y,
28996
- key: `y-grid-tick-${y}`
29112
+ key: `y-grid-tick-${y}`,
29113
+ gridStep: props.gridStep[0],
29114
+ tickStep: yTickStep
28997
29115
  });
28998
29116
  }), xGridTicks.map(x => {
28999
29117
  return /*#__PURE__*/React.createElement(XGridTick, {
29000
29118
  x: x,
29001
- key: `x-grid-tick-${x}`
29119
+ key: `x-grid-tick-${x}`,
29120
+ gridStep: props.gridStep[1],
29121
+ tickStep: xTickStep
29002
29122
  });
29003
29123
  }));
29004
29124
  };
@@ -29024,10 +29144,13 @@ const Grid = props => {
29024
29144
  return props.markings === "none" ? null : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Coordinates.Cartesian, {
29025
29145
  xAxis: axisOptions(props, 0),
29026
29146
  yAxis: axisOptions(props, 1)
29027
- }), /*#__PURE__*/React.createElement(AxisTicks, {
29147
+ }), props.markings === "graph" && /*#__PURE__*/React.createElement(AxisArrows, null),
29148
+ // Only render the axis ticks and arrows if the markings are set to a full "graph"
29149
+ props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, {
29028
29150
  range: props.range,
29029
- tickStep: props.tickStep
29030
- }), props.markings === "graph" && /*#__PURE__*/React.createElement(AxisArrows, null));
29151
+ tickStep: props.tickStep,
29152
+ gridStep: props.gridStep
29153
+ }), /*#__PURE__*/React.createElement(AxisArrows, null)));
29031
29154
  };
29032
29155
 
29033
29156
  // Displays a stylized open eye: lint warnings are visible
@@ -29986,29 +30109,30 @@ function initializeGraphState(params) {
29986
30109
  graph,
29987
30110
  step,
29988
30111
  snapStep,
29989
- range
30112
+ range,
30113
+ markings
29990
30114
  } = params;
30115
+ const shared = {
30116
+ hasBeenInteractedWith: false,
30117
+ range,
30118
+ snapStep,
30119
+ markings
30120
+ };
29991
30121
  switch (graph.type) {
29992
30122
  case "segment":
29993
- return {
30123
+ return _extends({}, shared, {
29994
30124
  type: "segment",
29995
- hasBeenInteractedWith: false,
29996
- range,
29997
- snapStep,
29998
30125
  coords: getDefaultSegments({
29999
30126
  graph,
30000
30127
  step,
30001
30128
  range
30002
30129
  })
30003
- };
30130
+ });
30004
30131
  case "linear":
30005
30132
  case "linear-system":
30006
30133
  case "ray":
30007
- return {
30134
+ return _extends({}, shared, {
30008
30135
  type: graph.type,
30009
- hasBeenInteractedWith: false,
30010
- range,
30011
- snapStep,
30012
30136
  // Linear and ray graphs have a single tuple of points, while a
30013
30137
  // linear system has two tuples of points.
30014
30138
  coords: getLineCoords({
@@ -30016,13 +30140,10 @@ function initializeGraphState(params) {
30016
30140
  range,
30017
30141
  step
30018
30142
  })
30019
- };
30143
+ });
30020
30144
  case "polygon":
30021
- return {
30145
+ return _extends({}, shared, {
30022
30146
  type: "polygon",
30023
- hasBeenInteractedWith: false,
30024
- range,
30025
- snapStep,
30026
30147
  showAngles: Boolean(graph.showAngles),
30027
30148
  showSides: Boolean(graph.showSides),
30028
30149
  coords: getPolygonCoords({
@@ -30030,19 +30151,16 @@ function initializeGraphState(params) {
30030
30151
  range,
30031
30152
  step
30032
30153
  })
30033
- };
30154
+ });
30034
30155
  case "point":
30035
- return {
30156
+ return _extends({}, shared, {
30036
30157
  type: graph.type,
30037
- hasBeenInteractedWith: false,
30038
- range,
30039
- snapStep,
30040
30158
  coords: getDefaultPoints({
30041
30159
  graph,
30042
30160
  step,
30043
30161
  range
30044
30162
  })
30045
- };
30163
+ });
30046
30164
  case "angle":
30047
30165
  case "circle":
30048
30166
  case "sinusoid":
@@ -30282,10 +30400,11 @@ const MafsGraph = props => {
30282
30400
  useEffect(() => {
30283
30401
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
30284
30402
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
30285
- return /*#__PURE__*/React.createElement(GraphStateContext.Provider, {
30403
+ return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
30286
30404
  value: {
30287
- state,
30288
- dispatch
30405
+ range: state.range,
30406
+ snapStep: state.snapStep,
30407
+ markings: props.markings
30289
30408
  }
30290
30409
  }, /*#__PURE__*/React.createElement(View, {
30291
30410
  style: {
@@ -42370,7 +42489,7 @@ const init = function init(options) {
42370
42489
 
42371
42490
  // This file is processed by a Rollup plugin (replace) to inject the production
42372
42491
  const libName = "@khanacademy/perseus";
42373
- const libVersion = "21.3.0";
42492
+ const libVersion = "21.4.0";
42374
42493
  addLibraryVersionToPerseusDebug(libName, libVersion);
42375
42494
 
42376
42495
  const apiVersion = {