@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/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/index.js CHANGED
@@ -28952,19 +28952,170 @@ const ItemExtras = [
28952
28952
 
28953
28953
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
28954
28954
  const lockedFigureColors = {
28955
- blue: "#3D7586",
28956
28955
  green: "#447A53",
28957
- gray: "#5D5F66",
28958
28956
  grayH: "#3B3D45",
28959
- grayI: "#21242C",
28960
28957
  purple: "#594094",
28961
- purpleD: "#8351E8",
28962
28958
  pink: "#B25071",
28963
- orange: "#946700",
28964
28959
  red: "#D92916"
28965
28960
  };
28966
28961
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
28967
28962
 
28963
+ function pathBuilder() {
28964
+ return new PathBuilder();
28965
+ }
28966
+ class PathBuilder {
28967
+ path = [];
28968
+ scaleFactor = 1;
28969
+ build() {
28970
+ return this.path.map(scaleCommandBy(this.scaleFactor)).map(commandToString).join("");
28971
+ }
28972
+
28973
+ // add a move (M) command to the path
28974
+ move(x, y) {
28975
+ this.path.push({
28976
+ action: "M",
28977
+ coords: [x, y]
28978
+ });
28979
+ return this;
28980
+ }
28981
+
28982
+ // add a curve (C) command to the path
28983
+ curve(control1X, control1Y, control2X, control2Y, endX, endY) {
28984
+ this.path.push({
28985
+ action: "C",
28986
+ coords: [control1X, control1Y, control2X, control2Y, endX, endY]
28987
+ });
28988
+ return this;
28989
+ }
28990
+
28991
+ // Scale all coordinates of the path by a factor
28992
+ scale(factor) {
28993
+ this.scaleFactor *= factor;
28994
+ return this;
28995
+ }
28996
+ }
28997
+ function commandToString(command) {
28998
+ return `${command.action}${command.coords.join(" ")}`;
28999
+ }
29000
+ function scaleCommandBy(scaleFactor) {
29001
+ return command => ({
29002
+ ...command,
29003
+ coords: command.coords.map(c => c * scaleFactor)
29004
+ });
29005
+ }
29006
+
29007
+ // We use the pathBuilder here to scale up the SVG path coordinates used
29008
+ // elsewhere for arrowheads. We scale in the path itself instead of using a CSS
29009
+ // transform because a transform would also scale up the stroke weight, and we
29010
+ // don't want that.
29011
+ 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();
29012
+ function Arrowhead(props) {
29013
+ const [point] = useTransformVectorsToPixels(props.tip);
29014
+ return /*#__PURE__*/React__namespace.createElement("g", {
29015
+ transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
29016
+ }, /*#__PURE__*/React__namespace.createElement("g", {
29017
+ transform: "translate(-1.5)"
29018
+ }, /*#__PURE__*/React__namespace.createElement("path", {
29019
+ d: arrowPath,
29020
+ fill: "none",
29021
+ style: {
29022
+ stroke: props.color ?? "inherit"
29023
+ },
29024
+ strokeLinejoin: "round",
29025
+ strokeLinecap: "round"
29026
+ })));
29027
+ }
29028
+
29029
+ function SVGLine(props) {
29030
+ const {
29031
+ start,
29032
+ end,
29033
+ style,
29034
+ className,
29035
+ testId
29036
+ } = props;
29037
+ return /*#__PURE__*/React__namespace.createElement("line", {
29038
+ x1: start[0],
29039
+ y1: start[1],
29040
+ x2: end[0],
29041
+ y2: end[1],
29042
+ style: style,
29043
+ className: className,
29044
+ "data-testid": testId
29045
+ });
29046
+ }
29047
+
29048
+ function Vector(props) {
29049
+ const {
29050
+ tail,
29051
+ tip,
29052
+ color,
29053
+ style
29054
+ } = props;
29055
+ const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
29056
+ const direction = mafs.vec.sub(tip, tail);
29057
+ return /*#__PURE__*/React__namespace.createElement("g", {
29058
+ style: {
29059
+ stroke: color,
29060
+ strokeWidth: 2
29061
+ }
29062
+ }, /*#__PURE__*/React__namespace.createElement(SVGLine, {
29063
+ start: tailPx,
29064
+ end: tipPx,
29065
+ style: style
29066
+ }), /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29067
+ tip: tip,
29068
+ angle: angleDegrees(direction)
29069
+ }));
29070
+ }
29071
+ function angleDegrees(_ref) {
29072
+ let [x, y] = _ref;
29073
+ return Math.atan2(y, x) * 180 / Math.PI;
29074
+ }
29075
+
29076
+ /**
29077
+ * Given a ray and a rectangular box, find the point where the ray intersects
29078
+ * the edge of the box. Assumes the `initialPoint` is inside the box.
29079
+ * @param initialPoint - The starting point of the ray.
29080
+ * @param throughPoint - A point that the ray passes through. Must be different from initialPoint.
29081
+ * @param box - The box with which to intersect the ray, in the form [[xMin, xMax], [yMin, yMax]]
29082
+ */
29083
+ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
29084
+ const [[xMin, xMax], [yMin, yMax]] = box;
29085
+ const [aX, aY] = throughPoint;
29086
+ const [bX, bY] = initialPoint;
29087
+ const yDiff = bY - aY;
29088
+ const xDiff = bX - aX;
29089
+ const slope = yDiff / xDiff;
29090
+ const yAtXMin = slope * (xMin - aX) + aY;
29091
+ const yAtXMax = slope * (xMax - aX) + aY;
29092
+ const xAtYMin = (yMin - aY) / slope + aX;
29093
+ const xAtYMax = (yMax - aY) / slope + aX;
29094
+
29095
+ // clock analogy to describe quadrants
29096
+ switch (true) {
29097
+ // 12 o'clock to 2:59
29098
+ case yDiff > 0 && xDiff >= 0:
29099
+ return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
29100
+ // 3 o'clock to 5:59
29101
+ case yDiff <= 0 && xDiff > 0:
29102
+ // xAtYMin evaluates to -Infinity here, so we use absolute value
29103
+ return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
29104
+ // 9 o'clock to 11:59
29105
+ case yDiff >= 0 && xDiff < 0:
29106
+ return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
29107
+ // 6 o'clock to 8:59
29108
+ case yDiff < 0 && xDiff <= 0:
29109
+ return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
29110
+ default:
29111
+ return [xMax, yAtXMax];
29112
+ }
29113
+ };
29114
+ const getLines = points => points.map((point, i) => {
29115
+ const next = points[(i + 1) % points.length];
29116
+ return [point, next];
29117
+ });
29118
+
28968
29119
  const LockedLine = props => {
28969
29120
  const {
28970
29121
  color,
@@ -28972,11 +29123,27 @@ const LockedLine = props => {
28972
29123
  kind,
28973
29124
  points,
28974
29125
  showStartPoint,
28975
- showEndPoint
29126
+ showEndPoint,
29127
+ range
28976
29128
  } = props;
28977
29129
  const [point1, point2] = points;
28978
29130
  let line;
28979
- if (kind === "line" || kind === "segment") {
29131
+ if (kind === "ray") {
29132
+ // Rays extend to the end of the graph in one direction.
29133
+ const endExtend = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29134
+ line = /*#__PURE__*/React__namespace.createElement(Vector, {
29135
+ tail: point1.coord,
29136
+ tip: endExtend,
29137
+ color: lockedFigureColors[color],
29138
+ style: {
29139
+ strokeDasharray: lineStyle === "dashed" ?
29140
+ // TODO(lems-1930): Uncomment this line when the
29141
+ // dashed style is updated in Mafs.
29142
+ // ? "var(--mafs-line-stroke-dash-style)"
29143
+ "4, 3" : undefined
29144
+ }
29145
+ });
29146
+ } else {
28980
29147
  const LineType = kind === "segment" ? mafs.Line.Segment : mafs.Line.ThroughPoints;
28981
29148
  line = /*#__PURE__*/React__namespace.createElement(LineType, {
28982
29149
  point1: point1.coord,
@@ -28984,12 +29151,9 @@ const LockedLine = props => {
28984
29151
  color: lockedFigureColors[color],
28985
29152
  style: lineStyle
28986
29153
  });
28987
- } else {
28988
- // TODO(LEMS-1928): Implement rays
28989
- return null;
28990
29154
  }
28991
29155
  return /*#__PURE__*/React__namespace.createElement("g", {
28992
- className: "locked-line"
29156
+ className: kind === "ray" ? "locked-ray" : "locked-line"
28993
29157
  }, line, showStartPoint && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
28994
29158
  x: point1.coord[0],
28995
29159
  y: point1.coord[1],
@@ -29038,14 +29202,16 @@ const GraphLockedLayer = props => {
29038
29202
  lockedFigures
29039
29203
  } = props;
29040
29204
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lockedFigures.map((figure, index) => {
29041
- let Figure;
29042
29205
  switch (figure.type) {
29043
29206
  case "point":
29044
- Figure = LockedPoint;
29045
- break;
29207
+ return /*#__PURE__*/React__namespace.createElement(LockedPoint, _extends({
29208
+ key: `point-${index}`
29209
+ }, figure));
29046
29210
  case "line":
29047
- Figure = LockedLine;
29048
- break;
29211
+ return /*#__PURE__*/React__namespace.createElement(LockedLine, _extends({
29212
+ key: `line-${index}`,
29213
+ range: props.range
29214
+ }, figure));
29049
29215
  default:
29050
29216
  /**
29051
29217
  * Devlopment-time future-proofing: This should
@@ -29055,9 +29221,6 @@ const GraphLockedLayer = props => {
29055
29221
  */
29056
29222
  throw new wonderStuffCore.UnreachableCaseError(figure);
29057
29223
  }
29058
- return /*#__PURE__*/React__namespace.createElement(Figure, _extends({
29059
- key: `${figure.type}-${index}`
29060
- }, figure));
29061
29224
  }));
29062
29225
  };
29063
29226
 
@@ -29139,160 +29302,6 @@ function snap(snapStep, point) {
29139
29302
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29140
29303
  }
29141
29304
 
29142
- /**
29143
- * Given a ray and a rectangular box, find the point where the ray intersects
29144
- * the edge of the box. Assumes the `initialPoint` is inside the box.
29145
- * @param initialPoint - The starting point of the ray.
29146
- * @param throughPoint - A point that the ray passes through. Must be different from initialPoint.
29147
- * @param box - The box with which to intersect the ray, in the form [[xMin, xMax], [yMin, yMax]]
29148
- */
29149
- const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
29150
- const [[xMin, xMax], [yMin, yMax]] = box;
29151
- const [aX, aY] = throughPoint;
29152
- const [bX, bY] = initialPoint;
29153
- const yDiff = bY - aY;
29154
- const xDiff = bX - aX;
29155
- const slope = yDiff / xDiff;
29156
- const yAtXMin = slope * (xMin - aX) + aY;
29157
- const yAtXMax = slope * (xMax - aX) + aY;
29158
- const xAtYMin = (yMin - aY) / slope + aX;
29159
- const xAtYMax = (yMax - aY) / slope + aX;
29160
-
29161
- // clock analogy to describe quadrants
29162
- switch (true) {
29163
- // 12 o'clock to 2:59
29164
- case yDiff > 0 && xDiff >= 0:
29165
- return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
29166
- // 3 o'clock to 5:59
29167
- case yDiff <= 0 && xDiff > 0:
29168
- // xAtYMin evaluates to -Infinity here, so we use absolute value
29169
- return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
29170
- // 9 o'clock to 11:59
29171
- case yDiff >= 0 && xDiff < 0:
29172
- return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
29173
- // 6 o'clock to 8:59
29174
- case yDiff < 0 && xDiff <= 0:
29175
- return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
29176
- default:
29177
- return [xMax, yAtXMax];
29178
- }
29179
- };
29180
- const getLines = points => points.map((point, i) => {
29181
- const next = points[(i + 1) % points.length];
29182
- return [point, next];
29183
- });
29184
-
29185
- function SVGLine(props) {
29186
- const {
29187
- start,
29188
- end,
29189
- style,
29190
- className,
29191
- testId
29192
- } = props;
29193
- return /*#__PURE__*/React__namespace.createElement("line", {
29194
- x1: start[0],
29195
- y1: start[1],
29196
- x2: end[0],
29197
- y2: end[1],
29198
- style: style,
29199
- className: `${className}`,
29200
- "data-testid": testId
29201
- });
29202
- }
29203
-
29204
- function pathBuilder() {
29205
- return new PathBuilder();
29206
- }
29207
- class PathBuilder {
29208
- path = [];
29209
- scaleFactor = 1;
29210
- build() {
29211
- return this.path.map(scaleCommandBy(this.scaleFactor)).map(commandToString).join("");
29212
- }
29213
-
29214
- // add a move (M) command to the path
29215
- move(x, y) {
29216
- this.path.push({
29217
- action: "M",
29218
- coords: [x, y]
29219
- });
29220
- return this;
29221
- }
29222
-
29223
- // add a curve (C) command to the path
29224
- curve(control1X, control1Y, control2X, control2Y, endX, endY) {
29225
- this.path.push({
29226
- action: "C",
29227
- coords: [control1X, control1Y, control2X, control2Y, endX, endY]
29228
- });
29229
- return this;
29230
- }
29231
-
29232
- // Scale all coordinates of the path by a factor
29233
- scale(factor) {
29234
- this.scaleFactor *= factor;
29235
- return this;
29236
- }
29237
- }
29238
- function commandToString(command) {
29239
- return `${command.action}${command.coords.join(" ")}`;
29240
- }
29241
- function scaleCommandBy(scaleFactor) {
29242
- return command => ({
29243
- ...command,
29244
- coords: command.coords.map(c => c * scaleFactor)
29245
- });
29246
- }
29247
-
29248
- // We use the pathBuilder here to scale up the SVG path coordinates used
29249
- // elsewhere for arrowheads. We scale in the path itself instead of using a CSS
29250
- // transform because a transform would also scale up the stroke weight, and we
29251
- // don't want that.
29252
- 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();
29253
- function Arrowhead(props) {
29254
- const [point] = useTransformVectorsToPixels(props.tip);
29255
- return /*#__PURE__*/React__namespace.createElement("g", {
29256
- transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
29257
- }, /*#__PURE__*/React__namespace.createElement("g", {
29258
- transform: "translate(-1.5)"
29259
- }, /*#__PURE__*/React__namespace.createElement("path", {
29260
- d: arrowPath,
29261
- fill: "none",
29262
- style: {
29263
- stroke: props.color ?? "inherit"
29264
- },
29265
- strokeLinejoin: "round",
29266
- strokeLinecap: "round"
29267
- })));
29268
- }
29269
-
29270
- function Vector(props) {
29271
- const {
29272
- tail,
29273
- tip,
29274
- color
29275
- } = props;
29276
- const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
29277
- const direction = mafs.vec.sub(tip, tail);
29278
- return /*#__PURE__*/React__namespace.createElement("g", {
29279
- style: {
29280
- stroke: color,
29281
- strokeWidth: 2
29282
- }
29283
- }, /*#__PURE__*/React__namespace.createElement(SVGLine, {
29284
- start: tailPx,
29285
- end: tipPx
29286
- }), /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29287
- tip: tip,
29288
- angle: angleDegrees(direction)
29289
- }));
29290
- }
29291
- function angleDegrees(_ref) {
29292
- let [x, y] = _ref;
29293
- return Math.atan2(y, x) * 180 / Math.PI;
29294
- }
29295
-
29296
29305
  const defaultStroke = "var(--movable-line-stroke-color)";
29297
29306
  const MovableLine = props => {
29298
29307
  const {
@@ -31244,6 +31253,7 @@ const MafsGraph = props => {
31244
31253
  labels
31245
31254
  }
31246
31255
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31256
+ className: "mafs-graph",
31247
31257
  style: {
31248
31258
  width,
31249
31259
  height,
@@ -31278,7 +31288,8 @@ const MafsGraph = props => {
31278
31288
  containerSizeClass: props.containerSizeClass,
31279
31289
  markings: props.markings
31280
31290
  }), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
31281
- lockedFigures: props.lockedFigures
31291
+ lockedFigures: props.lockedFigures,
31292
+ range: props.range
31282
31293
  }), renderGraph({
31283
31294
  state,
31284
31295
  dispatch
@@ -43374,7 +43385,7 @@ const init = function (options) {
43374
43385
 
43375
43386
  // This file is processed by a Rollup plugin (replace) to inject the production
43376
43387
  const libName = "@khanacademy/perseus";
43377
- const libVersion = "22.1.0";
43388
+ const libVersion = "22.3.0";
43378
43389
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43379
43390
 
43380
43391
  const apiVersion = {