@khanacademy/perseus 22.1.0 → 22.3.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.css CHANGED
@@ -1864,8 +1864,7 @@ body.mobile ul.inequalities-one-line-radios > li {
1864
1864
  .framework-perseus.perseus-mobile .perseus-image-caption.has-title .paragraph .paragraph strong:first-child {
1865
1865
  color: #3b3e40;
1866
1866
  }
1867
- .framework-perseus.perseus-mobile .katex,
1868
- .framework-perseus.perseus-mobile mjx-container {
1867
+ .framework-perseus.perseus-mobile :is(.katex, mjx-container):not(.mafs-graph *) {
1869
1868
  font-size: 21px;
1870
1869
  line-height: 1.2;
1871
1870
  color: #21242c;
@@ -2007,8 +2006,7 @@ body.mobile ul.inequalities-one-line-radios > li {
2007
2006
  .framework-perseus.perseus-mobile .perseus-image-caption.has-title .paragraph .paragraph strong:first-child {
2008
2007
  color: #3b3e40;
2009
2008
  }
2010
- .framework-perseus.perseus-mobile .katex,
2011
- .framework-perseus.perseus-mobile mjx-container {
2009
+ .framework-perseus.perseus-mobile :is(.katex, mjx-container):not(.mafs-graph *) {
2012
2010
  font-size: 23px;
2013
2011
  line-height: 1.3;
2014
2012
  color: #21242c;
@@ -2150,8 +2148,7 @@ body.mobile ul.inequalities-one-line-radios > li {
2150
2148
  .framework-perseus.perseus-mobile .perseus-image-caption.has-title .paragraph .paragraph strong:first-child {
2151
2149
  color: #3b3e40;
2152
2150
  }
2153
- .framework-perseus.perseus-mobile .katex,
2154
- .framework-perseus.perseus-mobile mjx-container {
2151
+ .framework-perseus.perseus-mobile :is(.katex, mjx-container):not(.mafs-graph *) {
2155
2152
  font-size: 25px;
2156
2153
  line-height: 1.2;
2157
2154
  color: #21242c;
@@ -4172,6 +4169,7 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
4172
4169
 
4173
4170
  --mafs-origin-color: var(--mafs-fg);
4174
4171
  --mafs-line-color: #555;
4172
+ --mafs-line-stroke-dash-style: 4, 3;
4175
4173
  --grid-line-subdivision-color: #222;
4176
4174
 
4177
4175
  --mafs-red: #f11d0e;
package/dist/es/index.js CHANGED
@@ -28664,19 +28664,171 @@ const ItemExtras = [
28664
28664
 
28665
28665
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
28666
28666
  const lockedFigureColors = {
28667
- blue: "#3D7586",
28668
28667
  green: "#447A53",
28669
- gray: "#5D5F66",
28670
28668
  grayH: "#3B3D45",
28671
- grayI: "#21242C",
28672
28669
  purple: "#594094",
28673
- purpleD: "#8351E8",
28674
28670
  pink: "#B25071",
28675
- orange: "#946700",
28676
28671
  red: "#D92916"
28677
28672
  };
28678
28673
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
28679
28674
 
28675
+ function pathBuilder() {
28676
+ return new PathBuilder();
28677
+ }
28678
+ class PathBuilder {
28679
+ constructor() {
28680
+ this.path = [];
28681
+ this.scaleFactor = 1;
28682
+ }
28683
+ build() {
28684
+ return this.path.map(scaleCommandBy(this.scaleFactor)).map(commandToString).join("");
28685
+ }
28686
+
28687
+ // add a move (M) command to the path
28688
+ move(x, y) {
28689
+ this.path.push({
28690
+ action: "M",
28691
+ coords: [x, y]
28692
+ });
28693
+ return this;
28694
+ }
28695
+
28696
+ // add a curve (C) command to the path
28697
+ curve(control1X, control1Y, control2X, control2Y, endX, endY) {
28698
+ this.path.push({
28699
+ action: "C",
28700
+ coords: [control1X, control1Y, control2X, control2Y, endX, endY]
28701
+ });
28702
+ return this;
28703
+ }
28704
+
28705
+ // Scale all coordinates of the path by a factor
28706
+ scale(factor) {
28707
+ this.scaleFactor *= factor;
28708
+ return this;
28709
+ }
28710
+ }
28711
+ function commandToString(command) {
28712
+ return `${command.action}${command.coords.join(" ")}`;
28713
+ }
28714
+ function scaleCommandBy(scaleFactor) {
28715
+ return command => _extends({}, command, {
28716
+ coords: command.coords.map(c => c * scaleFactor)
28717
+ });
28718
+ }
28719
+
28720
+ // We use the pathBuilder here to scale up the SVG path coordinates used
28721
+ // elsewhere for arrowheads. We scale in the path itself instead of using a CSS
28722
+ // transform because a transform would also scale up the stroke weight, and we
28723
+ // don't want that.
28724
+ 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();
28725
+ function Arrowhead(props) {
28726
+ var _props$color;
28727
+ const [point] = useTransformVectorsToPixels(props.tip);
28728
+ return /*#__PURE__*/React.createElement("g", {
28729
+ transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
28730
+ }, /*#__PURE__*/React.createElement("g", {
28731
+ transform: "translate(-1.5)"
28732
+ }, /*#__PURE__*/React.createElement("path", {
28733
+ d: arrowPath,
28734
+ fill: "none",
28735
+ style: {
28736
+ stroke: (_props$color = props.color) != null ? _props$color : "inherit"
28737
+ },
28738
+ strokeLinejoin: "round",
28739
+ strokeLinecap: "round"
28740
+ })));
28741
+ }
28742
+
28743
+ function SVGLine(props) {
28744
+ const {
28745
+ start,
28746
+ end,
28747
+ style,
28748
+ className,
28749
+ testId
28750
+ } = props;
28751
+ return /*#__PURE__*/React.createElement("line", {
28752
+ x1: start[0],
28753
+ y1: start[1],
28754
+ x2: end[0],
28755
+ y2: end[1],
28756
+ style: style,
28757
+ className: className,
28758
+ "data-testid": testId
28759
+ });
28760
+ }
28761
+
28762
+ function Vector(props) {
28763
+ const {
28764
+ tail,
28765
+ tip,
28766
+ color,
28767
+ style
28768
+ } = props;
28769
+ const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
28770
+ const direction = vec.sub(tip, tail);
28771
+ return /*#__PURE__*/React.createElement("g", {
28772
+ style: {
28773
+ stroke: color,
28774
+ strokeWidth: 2
28775
+ }
28776
+ }, /*#__PURE__*/React.createElement(SVGLine, {
28777
+ start: tailPx,
28778
+ end: tipPx,
28779
+ style: style
28780
+ }), /*#__PURE__*/React.createElement(Arrowhead, {
28781
+ tip: tip,
28782
+ angle: angleDegrees(direction)
28783
+ }));
28784
+ }
28785
+ function angleDegrees([x, y]) {
28786
+ return Math.atan2(y, x) * 180 / Math.PI;
28787
+ }
28788
+
28789
+ /**
28790
+ * Given a ray and a rectangular box, find the point where the ray intersects
28791
+ * the edge of the box. Assumes the `initialPoint` is inside the box.
28792
+ * @param initialPoint - The starting point of the ray.
28793
+ * @param throughPoint - A point that the ray passes through. Must be different from initialPoint.
28794
+ * @param box - The box with which to intersect the ray, in the form [[xMin, xMax], [yMin, yMax]]
28795
+ */
28796
+ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
28797
+ const [[xMin, xMax], [yMin, yMax]] = box;
28798
+ const [aX, aY] = throughPoint;
28799
+ const [bX, bY] = initialPoint;
28800
+ const yDiff = bY - aY;
28801
+ const xDiff = bX - aX;
28802
+ const slope = yDiff / xDiff;
28803
+ const yAtXMin = slope * (xMin - aX) + aY;
28804
+ const yAtXMax = slope * (xMax - aX) + aY;
28805
+ const xAtYMin = (yMin - aY) / slope + aX;
28806
+ const xAtYMax = (yMax - aY) / slope + aX;
28807
+
28808
+ // clock analogy to describe quadrants
28809
+ switch (true) {
28810
+ // 12 o'clock to 2:59
28811
+ case yDiff > 0 && xDiff >= 0:
28812
+ return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
28813
+ // 3 o'clock to 5:59
28814
+ case yDiff <= 0 && xDiff > 0:
28815
+ // xAtYMin evaluates to -Infinity here, so we use absolute value
28816
+ return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
28817
+ // 9 o'clock to 11:59
28818
+ case yDiff >= 0 && xDiff < 0:
28819
+ return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
28820
+ // 6 o'clock to 8:59
28821
+ case yDiff < 0 && xDiff <= 0:
28822
+ return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
28823
+ default:
28824
+ return [xMax, yAtXMax];
28825
+ }
28826
+ };
28827
+ const getLines = points => points.map((point, i) => {
28828
+ const next = points[(i + 1) % points.length];
28829
+ return [point, next];
28830
+ });
28831
+
28680
28832
  const LockedLine = props => {
28681
28833
  const {
28682
28834
  color: color$1,
@@ -28684,11 +28836,27 @@ const LockedLine = props => {
28684
28836
  kind,
28685
28837
  points,
28686
28838
  showStartPoint,
28687
- showEndPoint
28839
+ showEndPoint,
28840
+ range
28688
28841
  } = props;
28689
28842
  const [point1, point2] = points;
28690
28843
  let line;
28691
- if (kind === "line" || kind === "segment") {
28844
+ if (kind === "ray") {
28845
+ // Rays extend to the end of the graph in one direction.
28846
+ const endExtend = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28847
+ line = /*#__PURE__*/React.createElement(Vector, {
28848
+ tail: point1.coord,
28849
+ tip: endExtend,
28850
+ color: lockedFigureColors[color$1],
28851
+ style: {
28852
+ strokeDasharray: lineStyle === "dashed" ?
28853
+ // TODO(lems-1930): Uncomment this line when the
28854
+ // dashed style is updated in Mafs.
28855
+ // ? "var(--mafs-line-stroke-dash-style)"
28856
+ "4, 3" : undefined
28857
+ }
28858
+ });
28859
+ } else {
28692
28860
  const LineType = kind === "segment" ? Line$3.Segment : Line$3.ThroughPoints;
28693
28861
  line = /*#__PURE__*/React.createElement(LineType, {
28694
28862
  point1: point1.coord,
@@ -28696,12 +28864,9 @@ const LockedLine = props => {
28696
28864
  color: lockedFigureColors[color$1],
28697
28865
  style: lineStyle
28698
28866
  });
28699
- } else {
28700
- // TODO(LEMS-1928): Implement rays
28701
- return null;
28702
28867
  }
28703
28868
  return /*#__PURE__*/React.createElement("g", {
28704
- className: "locked-line"
28869
+ className: kind === "ray" ? "locked-ray" : "locked-line"
28705
28870
  }, line, showStartPoint && /*#__PURE__*/React.createElement(Point$2, {
28706
28871
  x: point1.coord[0],
28707
28872
  y: point1.coord[1],
@@ -28750,14 +28915,16 @@ const GraphLockedLayer = props => {
28750
28915
  lockedFigures
28751
28916
  } = props;
28752
28917
  return /*#__PURE__*/React.createElement(React.Fragment, null, lockedFigures.map((figure, index) => {
28753
- let Figure;
28754
28918
  switch (figure.type) {
28755
28919
  case "point":
28756
- Figure = LockedPoint;
28757
- break;
28920
+ return /*#__PURE__*/React.createElement(LockedPoint, _extends({
28921
+ key: `point-${index}`
28922
+ }, figure));
28758
28923
  case "line":
28759
- Figure = LockedLine;
28760
- break;
28924
+ return /*#__PURE__*/React.createElement(LockedLine, _extends({
28925
+ key: `line-${index}`,
28926
+ range: props.range
28927
+ }, figure));
28761
28928
  default:
28762
28929
  /**
28763
28930
  * Devlopment-time future-proofing: This should
@@ -28767,9 +28934,6 @@ const GraphLockedLayer = props => {
28767
28934
  */
28768
28935
  throw new UnreachableCaseError(figure);
28769
28936
  }
28770
- return /*#__PURE__*/React.createElement(Figure, _extends({
28771
- key: `${figure.type}-${index}`
28772
- }, figure));
28773
28937
  }));
28774
28938
  };
28775
28939
 
@@ -28851,161 +29015,6 @@ function snap(snapStep, point) {
28851
29015
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
28852
29016
  }
28853
29017
 
28854
- /**
28855
- * Given a ray and a rectangular box, find the point where the ray intersects
28856
- * the edge of the box. Assumes the `initialPoint` is inside the box.
28857
- * @param initialPoint - The starting point of the ray.
28858
- * @param throughPoint - A point that the ray passes through. Must be different from initialPoint.
28859
- * @param box - The box with which to intersect the ray, in the form [[xMin, xMax], [yMin, yMax]]
28860
- */
28861
- const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
28862
- const [[xMin, xMax], [yMin, yMax]] = box;
28863
- const [aX, aY] = throughPoint;
28864
- const [bX, bY] = initialPoint;
28865
- const yDiff = bY - aY;
28866
- const xDiff = bX - aX;
28867
- const slope = yDiff / xDiff;
28868
- const yAtXMin = slope * (xMin - aX) + aY;
28869
- const yAtXMax = slope * (xMax - aX) + aY;
28870
- const xAtYMin = (yMin - aY) / slope + aX;
28871
- const xAtYMax = (yMax - aY) / slope + aX;
28872
-
28873
- // clock analogy to describe quadrants
28874
- switch (true) {
28875
- // 12 o'clock to 2:59
28876
- case yDiff > 0 && xDiff >= 0:
28877
- return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
28878
- // 3 o'clock to 5:59
28879
- case yDiff <= 0 && xDiff > 0:
28880
- // xAtYMin evaluates to -Infinity here, so we use absolute value
28881
- return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
28882
- // 9 o'clock to 11:59
28883
- case yDiff >= 0 && xDiff < 0:
28884
- return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
28885
- // 6 o'clock to 8:59
28886
- case yDiff < 0 && xDiff <= 0:
28887
- return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
28888
- default:
28889
- return [xMax, yAtXMax];
28890
- }
28891
- };
28892
- const getLines = points => points.map((point, i) => {
28893
- const next = points[(i + 1) % points.length];
28894
- return [point, next];
28895
- });
28896
-
28897
- function SVGLine(props) {
28898
- const {
28899
- start,
28900
- end,
28901
- style,
28902
- className,
28903
- testId
28904
- } = props;
28905
- return /*#__PURE__*/React.createElement("line", {
28906
- x1: start[0],
28907
- y1: start[1],
28908
- x2: end[0],
28909
- y2: end[1],
28910
- style: style,
28911
- className: `${className}`,
28912
- "data-testid": testId
28913
- });
28914
- }
28915
-
28916
- function pathBuilder() {
28917
- return new PathBuilder();
28918
- }
28919
- class PathBuilder {
28920
- constructor() {
28921
- this.path = [];
28922
- this.scaleFactor = 1;
28923
- }
28924
- build() {
28925
- return this.path.map(scaleCommandBy(this.scaleFactor)).map(commandToString).join("");
28926
- }
28927
-
28928
- // add a move (M) command to the path
28929
- move(x, y) {
28930
- this.path.push({
28931
- action: "M",
28932
- coords: [x, y]
28933
- });
28934
- return this;
28935
- }
28936
-
28937
- // add a curve (C) command to the path
28938
- curve(control1X, control1Y, control2X, control2Y, endX, endY) {
28939
- this.path.push({
28940
- action: "C",
28941
- coords: [control1X, control1Y, control2X, control2Y, endX, endY]
28942
- });
28943
- return this;
28944
- }
28945
-
28946
- // Scale all coordinates of the path by a factor
28947
- scale(factor) {
28948
- this.scaleFactor *= factor;
28949
- return this;
28950
- }
28951
- }
28952
- function commandToString(command) {
28953
- return `${command.action}${command.coords.join(" ")}`;
28954
- }
28955
- function scaleCommandBy(scaleFactor) {
28956
- return command => _extends({}, command, {
28957
- coords: command.coords.map(c => c * scaleFactor)
28958
- });
28959
- }
28960
-
28961
- // We use the pathBuilder here to scale up the SVG path coordinates used
28962
- // elsewhere for arrowheads. We scale in the path itself instead of using a CSS
28963
- // transform because a transform would also scale up the stroke weight, and we
28964
- // don't want that.
28965
- 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();
28966
- function Arrowhead(props) {
28967
- var _props$color;
28968
- const [point] = useTransformVectorsToPixels(props.tip);
28969
- return /*#__PURE__*/React.createElement("g", {
28970
- transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
28971
- }, /*#__PURE__*/React.createElement("g", {
28972
- transform: "translate(-1.5)"
28973
- }, /*#__PURE__*/React.createElement("path", {
28974
- d: arrowPath,
28975
- fill: "none",
28976
- style: {
28977
- stroke: (_props$color = props.color) != null ? _props$color : "inherit"
28978
- },
28979
- strokeLinejoin: "round",
28980
- strokeLinecap: "round"
28981
- })));
28982
- }
28983
-
28984
- function Vector(props) {
28985
- const {
28986
- tail,
28987
- tip,
28988
- color
28989
- } = props;
28990
- const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
28991
- const direction = vec.sub(tip, tail);
28992
- return /*#__PURE__*/React.createElement("g", {
28993
- style: {
28994
- stroke: color,
28995
- strokeWidth: 2
28996
- }
28997
- }, /*#__PURE__*/React.createElement(SVGLine, {
28998
- start: tailPx,
28999
- end: tipPx
29000
- }), /*#__PURE__*/React.createElement(Arrowhead, {
29001
- tip: tip,
29002
- angle: angleDegrees(direction)
29003
- }));
29004
- }
29005
- function angleDegrees([x, y]) {
29006
- return Math.atan2(y, x) * 180 / Math.PI;
29007
- }
29008
-
29009
29018
  const defaultStroke = "var(--movable-line-stroke-color)";
29010
29019
  const MovableLine = props => {
29011
29020
  const {
@@ -30919,6 +30928,7 @@ const MafsGraph = props => {
30919
30928
  labels
30920
30929
  }
30921
30930
  }, /*#__PURE__*/React.createElement(View, {
30931
+ className: "mafs-graph",
30922
30932
  style: {
30923
30933
  width,
30924
30934
  height,
@@ -30953,7 +30963,8 @@ const MafsGraph = props => {
30953
30963
  containerSizeClass: props.containerSizeClass,
30954
30964
  markings: props.markings
30955
30965
  }), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
30956
- lockedFigures: props.lockedFigures
30966
+ lockedFigures: props.lockedFigures,
30967
+ range: props.range
30957
30968
  }), renderGraph({
30958
30969
  state,
30959
30970
  dispatch
@@ -43054,7 +43065,7 @@ const init = function init(options) {
43054
43065
 
43055
43066
  // This file is processed by a Rollup plugin (replace) to inject the production
43056
43067
  const libName = "@khanacademy/perseus";
43057
- const libVersion = "22.1.0";
43068
+ const libVersion = "22.3.0";
43058
43069
  addLibraryVersionToPerseusDebug(libName, libVersion);
43059
43070
 
43060
43071
  const apiVersion = {