@khanacademy/perseus 33.0.0 → 33.2.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
@@ -8780,10 +8780,10 @@ var Raphael$1 = (function () {
8780
8780
  * provided on KhanUtil.
8781
8781
  */
8782
8782
  const KhanColors = {
8783
- BLUE: "#6495ED",
8783
+ BLUE: wonderBlocksTokens.color.blue,
8784
8784
  ORANGE: "#FFA500",
8785
8785
  PINK: "#FF00AF",
8786
- GREEN: "#28AE7B",
8786
+ GREEN: wonderBlocksTokens.color.green,
8787
8787
  PURPLE: "#9D38BD",
8788
8788
  RED: "#DF0030",
8789
8789
  GRAY: "gray",
@@ -8859,11 +8859,9 @@ const KhanColors = {
8859
8859
  // of any redesigns.
8860
8860
  _BACKGROUND: "#FDFDFD",
8861
8861
  // TODO(eater): Get rid of this altogether.
8862
- INTERACTING: "#71B307",
8863
- // KA_GREEN
8864
- INTERACTIVE: "#71B307",
8865
- // KA_GREEN
8866
- DYNAMIC: "#6495ED" // BLUE
8862
+ INTERACTING: wonderBlocksTokens.color.green,
8863
+ INTERACTIVE: wonderBlocksTokens.color.green,
8864
+ DYNAMIC: wonderBlocksTokens.color.blue
8867
8865
  };
8868
8866
 
8869
8867
  // DrawingTransform is responsible for transforming from "math coordinates"
@@ -10147,21 +10145,50 @@ const setLabelMargins = function (span, size) {
10147
10145
  marginTop: -height / 2 - y * scale
10148
10146
  });
10149
10147
  } else {
10150
- const $container = $span.closest(".svg-image");
10148
+ // We are using jQuery to collect information and calculate a scale
10149
+ // since we don't have a way to pass it to this function.
10150
+ // We need the width of the container in order to calculate the scale to apply to the label.
10151
+ // Unfortunately, the DOM tree leading to the label is not consistent.
10152
+ // Therefore, we need to check different elements and different style properties to get the width of the container.
10153
+ // Some graphie labels exist within an ".svg-image" container, and others do not.
10154
+ // All graphie labels have a ".graphie" container.
10155
+ // When a label has an ".svg-image" container,
10156
+ // the width information in the ".graphie" element is expressed as a percentage instead of pixels,
10157
+ // so we can't use it.
10158
+ // Instead, the ".svg-image" container can be used to get the width information.
10159
+ // The following lines determine which container element to use for the width measurement.
10160
+ const $svgImage = $span.closest(".svg-image");
10161
+ const $graphie = $span.closest(".graphie");
10162
+ // If an ".svg-image" container exists in the DOM tree, then use it, otherwise use the ".graphie" container.
10163
+ const $container = $svgImage.length > 0 ? $svgImage : $graphie;
10164
+
10151
10165
  // Ensuring that the line-height of the text doesn't throw off placement of the text element.
10152
10166
  // Inherited line-height values can really mess up placement.
10153
10167
  $container.css("line-height", "normal");
10154
10168
 
10155
- // The 'max-width' value is the same as the expected (100% scale) width of the graphie.
10156
- // We are using jQuery to get this information since we don't have a way to pass it to this function.
10157
- const expectedWidth = $container.css("max-width") ?? "0px";
10169
+ // The expected width of the graphie is found in the "max-width" property on ".svg-image" containers,
10170
+ // and is found in the "width" property on ".graphie" containers.
10171
+ const widthValues = $container.css(["max-width", "width"]) ?? {
10172
+ // The browser will always return a value, but tests won't, so ensuring something is returned.
10173
+ "max-width": "0px"
10174
+ };
10175
+ const expectedWidth =
10176
+ // Verified in Chrome, Firefox, and Safari - all return the same "none" value if the property does not exist.
10177
+ widthValues["max-width"] !== "none" ? widthValues["max-width"] : widthValues["width"];
10158
10178
  // We can calculate the true scale of the graphie by taking actual width and dividing by the expected width.
10159
- // NOTE: Using 'slice' to remove the "px" unit from the end of the expected width value.
10160
- const scale = ($container.width() ?? 0) / parseInt(expectedWidth.replace(/px$/, "")) * 100;
10179
+ // NOTE: Using 'replace' to remove the "px" unit from the end of the expected width value.
10180
+ let scale = ($container.width() ?? 0) / parseInt(expectedWidth.replace(/px$/, "")) * 100;
10181
+ // If something failed in the calculation, then default to 100% scale.
10182
+ if (isNaN(scale)) {
10183
+ scale = 100;
10184
+ } else if (scale === 0) {
10185
+ scale = 100;
10186
+ }
10161
10187
 
10162
10188
  // Any padding needs to be scaled accordingly.
10163
- const currentPadding = $span.css("padding") ?? "0px";
10164
- const newPadding = Math.round(parseInt(currentPadding.slice(0, -2)) * scale) / 100;
10189
+ const padding = $span.css("padding") ?? "0px";
10190
+ const currentPadding = padding !== "none" ? padding : "0px";
10191
+ const newPadding = Math.round(parseInt(currentPadding.replace(/px$/, "")) * scale) / 100;
10165
10192
 
10166
10193
  // "multipliers" basically move the position of the text by using 1, 0, -1
10167
10194
  // 'margin-left' and 'margin-top' are used to position the text.
@@ -28617,6 +28644,95 @@ class Graph extends React__namespace.Component {
28617
28644
  }
28618
28645
  }
28619
28646
 
28647
+ function pathBuilder() {
28648
+ return new PathBuilder();
28649
+ }
28650
+ class PathBuilder {
28651
+ path = [];
28652
+ scaleFactor = 1;
28653
+ build() {
28654
+ return this.path.map(scaleCommandBy(this.scaleFactor)).map(commandToString).join("");
28655
+ }
28656
+
28657
+ // add a move (M) command to the path
28658
+ move(x, y) {
28659
+ this.path.push({
28660
+ action: "M",
28661
+ args: [x, y]
28662
+ });
28663
+ return this;
28664
+ }
28665
+ line(x, y) {
28666
+ this.path.push({
28667
+ action: "L",
28668
+ args: [x, y]
28669
+ });
28670
+ return this;
28671
+ }
28672
+ circularArc(radius, toX, toY) {
28673
+ let {
28674
+ sweep = false,
28675
+ largeArc = false
28676
+ } = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
28677
+ this.path.push({
28678
+ action: "A",
28679
+ args: [radius, radius, 0 /* rotation */, largeArc ? 1 : 0, sweep ? 1 : 0, toX, toY]
28680
+ });
28681
+ return this;
28682
+ }
28683
+
28684
+ // add a curve (C) command to the path
28685
+ curve(control1X, control1Y, control2X, control2Y, endX, endY) {
28686
+ this.path.push({
28687
+ action: "C",
28688
+ args: [control1X, control1Y, control2X, control2Y, endX, endY]
28689
+ });
28690
+ return this;
28691
+ }
28692
+
28693
+ // Scale all coordinates of the path by a factor
28694
+ scale(factor) {
28695
+ this.scaleFactor *= factor;
28696
+ return this;
28697
+ }
28698
+ }
28699
+ function commandToString(command) {
28700
+ return `${command.action}${command.args.join(" ")}`;
28701
+ }
28702
+ function scaleCommandBy(scaleFactor) {
28703
+ return command => {
28704
+ switch (command.action) {
28705
+ case "A":
28706
+ // TODO(benchristel): do we want to refactor this switch to
28707
+ // use polymorphism?
28708
+ // Arc command
28709
+ return {
28710
+ ...command,
28711
+ args: [command.args[0] * scaleFactor,
28712
+ // x radius
28713
+ command.args[1] * scaleFactor,
28714
+ // y radius
28715
+ command.args[2],
28716
+ // rotation
28717
+ command.args[3],
28718
+ // largeArc flag
28719
+ command.args[4],
28720
+ // sweep flag
28721
+ command.args[5] * scaleFactor,
28722
+ // end x
28723
+ command.args[6] * scaleFactor // end y
28724
+ ]
28725
+ };
28726
+
28727
+ default:
28728
+ return {
28729
+ ...command,
28730
+ args: command.args.map(c => c * scaleFactor)
28731
+ };
28732
+ }
28733
+ };
28734
+ }
28735
+
28620
28736
  // Restricts `value` to be between `min` and `max`.
28621
28737
  function clamp(value, min, max) {
28622
28738
  if (value < min) {
@@ -28846,380 +28962,6 @@ function pixelsToVectors(pixels, graphState) {
28846
28962
  });
28847
28963
  }
28848
28964
 
28849
- function AxisLabels() {
28850
- const {
28851
- range,
28852
- labels,
28853
- width,
28854
- height
28855
- } = useGraphConfig();
28856
- const yAxisLabelLocation = [0, range[Y][MAX]];
28857
- const xAxisLabelLocation = [range[X][MAX], 0];
28858
- const [xAxisLabelText, yAxisLabelText] = labels;
28859
- const graphInfo = {
28860
- range,
28861
- width,
28862
- height
28863
- };
28864
- const [x1, y1] = pointToPixel(xAxisLabelLocation, graphInfo);
28865
- // The default location for the y-axis-label is at the maximum y point
28866
- // This is just underneath the tick line for that point. The -24 moves the
28867
- // label up one grid square, so it sits on top of the graph
28868
- const [x2, y2] = mafs.vec.add(pointToPixel(yAxisLabelLocation, graphInfo), [0, -24]);
28869
- const {
28870
- TeX
28871
- } = getDependencies();
28872
- return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("span", {
28873
- style: {
28874
- position: "absolute",
28875
- left: x1,
28876
- top: y1,
28877
- fontSize: "14px",
28878
- transform: "translate(7px, -50%)"
28879
- }
28880
- }, /*#__PURE__*/React__default["default"].createElement(TeX, null, xAxisLabelText)), /*#__PURE__*/React__default["default"].createElement("span", {
28881
- style: {
28882
- position: "absolute",
28883
- left: x2,
28884
- top: y2,
28885
- fontSize: "14px",
28886
- transform: "translate(-50%, 0px)"
28887
- }
28888
- }, /*#__PURE__*/React__default["default"].createElement(TeX, null, yAxisLabelText)));
28889
- }
28890
-
28891
- // Universal padding for the axis ticks to ensure that the labels
28892
- // have enough space to render without overlapping the tick lines
28893
- const tickPadding = 5;
28894
- const AxisTickLabels = () => {
28895
- const graphConfig = useGraphConfig();
28896
- const {
28897
- tickStep,
28898
- range
28899
- } = graphConfig;
28900
- const [[xMin, xMax], [yMin, yMax]] = range;
28901
- const [xTickStep, yTickStep] = tickStep;
28902
- const yGridTicks = generateTickLocations$1(yTickStep, yMin, yMax);
28903
- const xGridTicks = generateTickLocations$1(xTickStep, xMin, xMax);
28904
- return /*#__PURE__*/React__namespace.createElement("div", {
28905
- className: "axis-tick-labels"
28906
- }, /*#__PURE__*/React__namespace.createElement(YGridAxis, {
28907
- axisTicks: yGridTicks,
28908
- graphConfig: graphConfig
28909
- }), /*#__PURE__*/React__namespace.createElement(XGridAxis, {
28910
- axisTicks: xGridTicks,
28911
- graphConfig: graphConfig
28912
- }));
28913
- };
28914
-
28915
- // Generate the necessary styles and ticks for the y-axis tick labels container
28916
- const YGridAxis = props => {
28917
- const {
28918
- axisTicks,
28919
- graphConfig
28920
- } = props;
28921
- const {
28922
- width,
28923
- height
28924
- } = graphConfig;
28925
-
28926
- // First get the label height by calculating the difference between the first two y-axis labels
28927
- const yTopPoint = pointToPixel([0, axisTicks[0]], graphConfig);
28928
- const ySecondPoint = pointToPixel([0, axisTicks[1]], graphConfig);
28929
- const yLabelHeight = ySecondPoint[1] - yTopPoint[1];
28930
-
28931
- // When rendering purely positive y-axis labels, we want to adjust the left position
28932
- // from the lowest calculated tick, as yMin may be negative
28933
- const yLowestTick = axisTicks[axisTicks.length - 1];
28934
- const yLeftAdjustment = Math.min(Math.max(yTopPoint[0] - tickPadding, 0), width);
28935
-
28936
- // We only want to render the 0 label on the y-axis if it is left of the grid
28937
- const yAxisIsLeftOfGrid = yLeftAdjustment === 0;
28938
-
28939
- // If the y-axis is right of the graph's viewbox/range,
28940
- // we want to adjust both the positioning and text alignment
28941
- const yAxisRightOfGrid = yLeftAdjustment === width;
28942
-
28943
- // Determine the default spacing for the labels based on whether the y-axis is right of the grid
28944
- const defaultSpacing = yAxisRightOfGrid ? 0.5 : 0;
28945
-
28946
- // We also want to adjust the spacing if there are no negative y-axis labels as the extra space is not needed
28947
- const marginSpacing = yLowestTick >= 0 ? defaultSpacing * 2 : defaultSpacing;
28948
-
28949
- // Determine the text alignment based on whether the y-axis is right of the grid
28950
- // We need to cast this as a const as it is used in the inline styles
28951
- const textAlign = yAxisRightOfGrid ? "left" : "right";
28952
- const yAxisStyles = {
28953
- left: `calc(${yLeftAdjustment}px - ${marginSpacing}em)`,
28954
- top: `calc(${yTopPoint[1]}px - 0.5em)`,
28955
- height,
28956
- textAlign,
28957
- "--y-axis-label-height": yLabelHeight + "px"
28958
- };
28959
- const classNames = yAxisRightOfGrid ? "y-axis-tick-labels y-axis-right-of-grid" : "y-axis-tick-labels ";
28960
- return /*#__PURE__*/React__namespace.createElement("div", {
28961
- className: classNames,
28962
- "data-testid": "y-axis-tick-labels",
28963
- style: yAxisStyles
28964
- }, axisTicks.map(label => {
28965
- return /*#__PURE__*/React__namespace.createElement(AxisTickLabel, {
28966
- label: label,
28967
- axis: "y",
28968
- key: `y-grid-tick-${label}`,
28969
- graphConfig: graphConfig,
28970
- axisOutOfBounds: yAxisIsLeftOfGrid
28971
- });
28972
- }));
28973
- };
28974
-
28975
- // Generate the necessary styles and ticks for the x-axis tick labels container
28976
- const XGridAxis = props => {
28977
- const {
28978
- axisTicks,
28979
- graphConfig
28980
- } = props;
28981
- const {
28982
- width,
28983
- height
28984
- } = graphConfig;
28985
-
28986
- // Determine label width by calculating the difference between the first two x-axis labels
28987
- const xFirstPoint = pointToPixel([axisTicks[0], 0], graphConfig);
28988
- const xSecondPoint = pointToPixel([axisTicks[1], 0], graphConfig);
28989
- const xLabelWidth = Math.abs(xSecondPoint[0] - xFirstPoint[0]);
28990
-
28991
- // Determine the top position of the x-axis labels based on
28992
- // whether the x-axis is above, within, or below the graph
28993
- const xAxisAboveGraph = xFirstPoint[1] < 0;
28994
- const xTopAdjustment = xAxisAboveGraph ? 0 : Math.min(xFirstPoint[1] + tickPadding, height);
28995
-
28996
- // Determine whether the x-axis is outside of the graph's viewbox/range
28997
- // This is used to conditionally render the 0 label on the x-axis
28998
- const xAxisOutOfBounds = xTopAdjustment === height || xAxisAboveGraph;
28999
-
29000
- // Determine the available space to the right of the x-axis labels
29001
- const availableSpaceRight = width - xFirstPoint[0];
29002
-
29003
- // Calculate the total combined width of ALL the x-axis labels
29004
- const axisTicksWidth = axisTicks.length * xLabelWidth;
29005
-
29006
- // Determine if we need to increase the width of the axis past the graph width
29007
- const calculatedWidth = axisTicksWidth > width ? axisTicksWidth : width;
29008
-
29009
- // Determine the right adjustment for the x-axis labels based on the available space
29010
- // We're adjusting the right as the flex-direction is row-reverse
29011
- const xRightAdjustment = availableSpaceRight - xLabelWidth / 2;
29012
- const xAxisStyles = {
29013
- top: xTopAdjustment,
29014
- right: xRightAdjustment,
29015
- width: calculatedWidth,
29016
- "--x-axis-label-width": xLabelWidth + "px"
29017
- };
29018
- return /*#__PURE__*/React__namespace.createElement("div", {
29019
- className: `x-axis-tick-labels ${xAxisAboveGraph && "x-axis-top-of-grid"}`,
29020
- "data-testid": "x-axis-tick-labels",
29021
- style: xAxisStyles
29022
- }, axisTicks.map(label => {
29023
- return /*#__PURE__*/React__namespace.createElement(AxisTickLabel, {
29024
- axis: "x",
29025
- label: label,
29026
- key: `x-grid-tick-${label}`,
29027
- graphConfig: graphConfig,
29028
- axisOutOfBounds: xAxisOutOfBounds
29029
- });
29030
- }));
29031
- };
29032
-
29033
- // Generate the individual tick label elements
29034
- const AxisTickLabel = _ref => {
29035
- let {
29036
- label,
29037
- graphConfig,
29038
- axisOutOfBounds,
29039
- axis
29040
- } = _ref;
29041
- const {
29042
- gridStep,
29043
- tickStep
29044
- } = graphConfig;
29045
- // Determine the point on the axis based on the axis type
29046
- const pointOnAxis = axis === "x" ? [label, 0] : [0, label];
29047
- const pixelPoint = pointToPixel(pointOnAxis, graphConfig);
29048
-
29049
- // Determine the relevant edge of the graph based on the axis type
29050
- const graphEdge = axis === "x" ? graphConfig.width : graphConfig.height;
29051
-
29052
- // Determine the vector index based on the axis type
29053
- const vectorIndex = axis === "x" ? 0 : 1;
29054
-
29055
- // Determine whether or not to render the tick label text
29056
- const shouldShowLabel = showTickLabel({
29057
- label,
29058
- axisOutOfBounds,
29059
- graphEdge,
29060
- position: pixelPoint[vectorIndex],
29061
- gridStep: gridStep[vectorIndex],
29062
- tickStep: tickStep[vectorIndex]
29063
- });
29064
- return /*#__PURE__*/React__namespace.createElement("span", {
29065
- className: `${axis}-axis-tick-label`
29066
- }, shouldShowLabel && label);
29067
- };
29068
-
29069
- // Determine whether or not to show the tick label text
29070
- const showTickLabel = _ref2 => {
29071
- let {
29072
- gridStep,
29073
- tickStep,
29074
- label,
29075
- graphEdge,
29076
- position,
29077
- axisOutOfBounds
29078
- } = _ref2;
29079
- let showLabel = true;
29080
-
29081
- // We only want to show the initial negative tick labels (e.g. -1) on each
29082
- // axis if the tickStep > gridStep, to ensure that these labels do not overlap.
29083
- // e.g. If gridStep = 1 and tickStep = 2, there are 2 grid lines for every 1 tick,
29084
- // which allows enough room for both these tick labels to render.
29085
- showLabel = tickStep > gridStep ? true : label !== -tickStep;
29086
-
29087
- // If the tick label position is greater than the relevant edge of the graph, don't render it
29088
- if (position <= -graphEdge || position >= graphEdge) {
29089
- showLabel = false;
29090
- }
29091
-
29092
- // If the label is 0, we only want to show it if the axis is to the left of the graph's viewbox/range
29093
- // otherwise it will overlap with graph axis labels
29094
- if (label === 0 && !axisOutOfBounds) {
29095
- showLabel = false;
29096
- }
29097
- return showLabel;
29098
- };
29099
-
29100
- // Generate the tick labels for an axis based on the tick step
29101
- function generateTickLocations$1(tickStep, min, max) {
29102
- const positiveTicks = [];
29103
- const negativeTicks = [];
29104
- const ticks = [];
29105
-
29106
- // Add 0 if it is applicable.
29107
- // This will be conditionally rendered using showTickLabel.
29108
- if (min <= 0 && max >= 0) {
29109
- positiveTicks.push(0);
29110
- }
29111
-
29112
- // Add ticks on the positive axis
29113
- // Start at the first tick after 0 or the minimum value,
29114
- // taking into account that the graph may be zoomed in on the positive axis
29115
- let i = Math.max(min, 0) + tickStep;
29116
- for (i; min < i && i < max; i += tickStep) {
29117
- positiveTicks.push(i);
29118
- }
29119
-
29120
- // Add ticks on the negative axis
29121
- // Start at the first tick before 0 or the maximum value,
29122
- // taking into account that the graph may be zoomed in on the negative axis
29123
- i = Math.min(max, 0) - tickStep;
29124
- for (i; i > min; i -= tickStep) {
29125
- negativeTicks.push(i);
29126
- }
29127
-
29128
- // Reverse the ticks so that they are in the correct render order
29129
- positiveTicks.reverse();
29130
- ticks.push(...positiveTicks, ...negativeTicks);
29131
- return ticks;
29132
- }
29133
-
29134
- function pathBuilder() {
29135
- return new PathBuilder();
29136
- }
29137
- class PathBuilder {
29138
- path = [];
29139
- scaleFactor = 1;
29140
- build() {
29141
- return this.path.map(scaleCommandBy(this.scaleFactor)).map(commandToString).join("");
29142
- }
29143
-
29144
- // add a move (M) command to the path
29145
- move(x, y) {
29146
- this.path.push({
29147
- action: "M",
29148
- args: [x, y]
29149
- });
29150
- return this;
29151
- }
29152
- line(x, y) {
29153
- this.path.push({
29154
- action: "L",
29155
- args: [x, y]
29156
- });
29157
- return this;
29158
- }
29159
- circularArc(radius, toX, toY) {
29160
- let {
29161
- sweep = false,
29162
- largeArc = false
29163
- } = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
29164
- this.path.push({
29165
- action: "A",
29166
- args: [radius, radius, 0 /* rotation */, largeArc ? 1 : 0, sweep ? 1 : 0, toX, toY]
29167
- });
29168
- return this;
29169
- }
29170
-
29171
- // add a curve (C) command to the path
29172
- curve(control1X, control1Y, control2X, control2Y, endX, endY) {
29173
- this.path.push({
29174
- action: "C",
29175
- args: [control1X, control1Y, control2X, control2Y, endX, endY]
29176
- });
29177
- return this;
29178
- }
29179
-
29180
- // Scale all coordinates of the path by a factor
29181
- scale(factor) {
29182
- this.scaleFactor *= factor;
29183
- return this;
29184
- }
29185
- }
29186
- function commandToString(command) {
29187
- return `${command.action}${command.args.join(" ")}`;
29188
- }
29189
- function scaleCommandBy(scaleFactor) {
29190
- return command => {
29191
- switch (command.action) {
29192
- case "A":
29193
- // TODO(benchristel): do we want to refactor this switch to
29194
- // use polymorphism?
29195
- // Arc command
29196
- return {
29197
- ...command,
29198
- args: [command.args[0] * scaleFactor,
29199
- // x radius
29200
- command.args[1] * scaleFactor,
29201
- // y radius
29202
- command.args[2],
29203
- // rotation
29204
- command.args[3],
29205
- // largeArc flag
29206
- command.args[4],
29207
- // sweep flag
29208
- command.args[5] * scaleFactor,
29209
- // end x
29210
- command.args[6] * scaleFactor // end y
29211
- ]
29212
- };
29213
-
29214
- default:
29215
- return {
29216
- ...command,
29217
- args: command.args.map(c => c * scaleFactor)
29218
- };
29219
- }
29220
- };
29221
- }
29222
-
29223
28965
  // We use the pathBuilder here to scale up the SVG path coordinates used
29224
28966
  // elsewhere for arrowheads. We scale in the path itself instead of using a CSS
29225
28967
  // transform because a transform would also scale up the stroke weight, and we
@@ -29249,7 +28991,10 @@ function AxisArrows() {
29249
28991
  range: [[xMin, xMax], [yMin, yMax]]
29250
28992
  } = useGraphConfig();
29251
28993
  const axisColor = "var(--mafs-fg)";
29252
- return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
28994
+
28995
+ // Only render the arrows if the axis is within the visible range
28996
+ // as otherwise the arrows will be rendered outside the graph
28997
+ return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, !(yMin > 0 || yMax < 0) && /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29253
28998
  color: axisColor,
29254
28999
  tip: [xMin, 0],
29255
29000
  angle: 180
@@ -29257,7 +29002,7 @@ function AxisArrows() {
29257
29002
  color: axisColor,
29258
29003
  tip: [xMax, 0],
29259
29004
  angle: 0
29260
- }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29005
+ })), !(xMin > 0 || xMax < 0) && /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29261
29006
  color: axisColor,
29262
29007
  tip: [0, yMin],
29263
29008
  angle: 90
@@ -29265,39 +29010,76 @@ function AxisArrows() {
29265
29010
  color: axisColor,
29266
29011
  tip: [0, yMax],
29267
29012
  angle: 270
29268
- }));
29013
+ })));
29269
29014
  }
29270
29015
 
29016
+ function AxisLabels() {
29017
+ const {
29018
+ range,
29019
+ labels,
29020
+ width,
29021
+ height
29022
+ } = useGraphConfig();
29023
+ const yAxisLabelLocation = [0, range[Y][MAX]];
29024
+ const xAxisLabelLocation = [range[X][MAX], 0];
29025
+ const [xAxisLabelText, yAxisLabelText] = labels;
29026
+ const graphInfo = {
29027
+ range,
29028
+ width,
29029
+ height
29030
+ };
29031
+ const [x1, y1] = pointToPixel(xAxisLabelLocation, graphInfo);
29032
+ // The default location for the y-axis-label is at the maximum y point
29033
+ // This is just underneath the tick line for that point. The -24 moves the
29034
+ // label up one grid square, so it sits on top of the graph
29035
+ const [x2, y2] = mafs.vec.add(pointToPixel(yAxisLabelLocation, graphInfo), [0, -24]);
29036
+ const {
29037
+ TeX
29038
+ } = getDependencies();
29039
+ return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("span", {
29040
+ style: {
29041
+ position: "absolute",
29042
+ left: x1,
29043
+ top: y1,
29044
+ fontSize: "14px",
29045
+ transform: "translate(7px, -50%)"
29046
+ }
29047
+ }, /*#__PURE__*/React__default["default"].createElement(TeX, null, xAxisLabelText)), /*#__PURE__*/React__default["default"].createElement("span", {
29048
+ style: {
29049
+ position: "absolute",
29050
+ left: x2,
29051
+ top: y2,
29052
+ fontSize: "14px",
29053
+ transform: "translate(-50%, 0px)"
29054
+ }
29055
+ }, /*#__PURE__*/React__default["default"].createElement(TeX, null, yAxisLabelText)));
29056
+ }
29057
+
29058
+ // The size of the ticks and labels in pixels
29271
29059
  const tickSize = 10;
29272
- const tickStyle = {
29273
- stroke: "black",
29274
- strokeWidth: 1
29275
- };
29060
+ const tickLabelSize = 14;
29276
29061
  const YGridTick = _ref => {
29277
29062
  let {
29278
29063
  y,
29279
- graphInfo
29064
+ range
29280
29065
  } = _ref;
29066
+ // If the graph requires out-of-bounds labels, we want to make sure to set the
29067
+ // coordinates to the edge of the visible range of the graph. Otherwise,
29068
+ // the ticks and labels would render outside of the clipping-mask.
29281
29069
  let xPointOnAxis = 0;
29282
-
29283
- // If the graph is zoomed in, we want to make sure the ticks are still visible
29284
- // even if they are outside the graph's range.
29285
- if (graphInfo.range[X][MIN] > 0) {
29070
+ if (range[X][MIN] > 0) {
29286
29071
  // If the graph is on the positive side of the x-axis, lock the ticks to the left side of the graph
29287
- xPointOnAxis = graphInfo.range[X][MIN];
29072
+ xPointOnAxis = range[X][MIN];
29288
29073
  }
29289
- if (graphInfo.range[X][MAX] < 0) {
29074
+ if (range[X][MAX] < 0) {
29290
29075
  // If the graph is on the negative side of the x-axis, lock the ticks to the right side of the graph
29291
- xPointOnAxis = graphInfo.range[X][MAX];
29076
+ xPointOnAxis = range[X][MAX];
29292
29077
  }
29078
+
29079
+ // Convert the Vector2 coordinates to pixel coordinates
29293
29080
  const pointOnAxis = [xPointOnAxis, y];
29294
29081
  const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
29295
29082
 
29296
- // If the tick is on the edge of the graph's range, don't render it
29297
- if (yPosition === -graphInfo.height || yPosition === graphInfo.height + 20) {
29298
- return null;
29299
- }
29300
-
29301
29083
  // Position of the start of the tick
29302
29084
  const x1 = xPosition - tickSize / 2;
29303
29085
  const y1 = yPosition;
@@ -29305,40 +29087,56 @@ const YGridTick = _ref => {
29305
29087
  // Position of the end of the tick
29306
29088
  const x2 = xPosition + tickSize / 2;
29307
29089
  const y2 = yPosition;
29090
+
29091
+ // Adjust the y position of the x-axis labels based on
29092
+ // whether the x-axis is above, within, or below the graph
29093
+ const xAdjustment = range[X][MAX] <= 0 ? tickLabelSize * 1.5 : -tickLabelSize * 1.1;
29094
+ const xPositionText = xPosition + xAdjustment;
29095
+ const yPositionText = yPosition + tickLabelSize * 0.25; // Center the text vertically on the tick
29096
+
29097
+ // If the graph displays both the y and x axis lines within the graph, we want
29098
+ // to hide the label at -1 on the y-axis to prevent overlap with the x-axis label
29099
+ const showLabel = shouldShowLabel(y, range);
29308
29100
  return /*#__PURE__*/React__namespace.createElement("g", {
29309
- className: "y-axis-ticks"
29101
+ className: "tick"
29310
29102
  }, /*#__PURE__*/React__namespace.createElement("line", {
29311
29103
  x1: x1,
29312
29104
  y1: y1,
29313
29105
  x2: x2,
29314
29106
  y2: y2,
29315
- style: tickStyle
29316
- }));
29107
+ className: "axis-tick"
29108
+ }), showLabel && /*#__PURE__*/React__namespace.createElement("text", {
29109
+ className: "axis-tick-label",
29110
+ style: {
29111
+ fontSize: tickLabelSize
29112
+ },
29113
+ textAnchor: "end",
29114
+ x: xPositionText,
29115
+ y: yPositionText
29116
+ }, y.toString()));
29317
29117
  };
29318
29118
  const XGridTick = _ref2 => {
29319
29119
  let {
29320
29120
  x,
29321
- graphInfo
29121
+ range
29322
29122
  } = _ref2;
29123
+ // If the graph requires out-of-bounds labels, we want to make sure to set the
29124
+ // coordinates to the edge of the visible range of the graph. Otherwise,
29125
+ // the ticks and labels would render outside of the clipping-mask.
29323
29126
  let yPointOnAxis = 0;
29324
- // If the graph is zoomed in, we want to make sure the ticks are still visible
29325
- // even if they are outside the graph's range.
29326
- if (graphInfo.range[Y][MIN] > 0) {
29127
+ if (range[Y][MIN] > 0) {
29327
29128
  // If the graph is on the positive side of the y-axis, lock the ticks to the top of the graph
29328
- yPointOnAxis = graphInfo.range[Y][MIN];
29129
+ yPointOnAxis = range[Y][MIN];
29329
29130
  }
29330
- if (graphInfo.range[Y][MAX] < 0) {
29131
+ if (range[Y][MAX] < 0) {
29331
29132
  // If the graph is on the negative side of the x-axis, lock the ticks to the bottom of the graph
29332
- yPointOnAxis = graphInfo.range[Y][MAX];
29133
+ yPointOnAxis = range[Y][MAX];
29333
29134
  }
29135
+
29136
+ // Convert the Vector2 coordinates to pixel coordinates
29334
29137
  const pointOnAxis = [x, yPointOnAxis];
29335
29138
  const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
29336
29139
 
29337
- // If the tick is on the edge of the graph's range, don't render it
29338
- if (xPosition === -graphInfo.width / 2 || xPosition === graphInfo.width) {
29339
- return null;
29340
- }
29341
-
29342
29140
  // Position of the start of the tick
29343
29141
  const x1 = xPosition;
29344
29142
  const y1 = yPosition + tickSize / 2;
@@ -29346,21 +29144,56 @@ const XGridTick = _ref2 => {
29346
29144
  // Position of the end of the tick
29347
29145
  const x2 = xPosition;
29348
29146
  const y2 = yPosition - tickSize / 2;
29147
+
29148
+ // Adjust the Y position of the x-axis labels based on
29149
+ // whether the x-axis is above, within, or below the graph
29150
+ const yAdjustment = range[Y][MAX] < 0 ? -tickLabelSize : tickLabelSize * 1.75;
29151
+
29152
+ // Adjust the X position of the x-axis labels based on
29153
+ // whether the label is positive or negative, in order to
29154
+ // account for the width of the negative sign
29155
+ const xAdjustment = x < 0 ? -2 : 0;
29156
+
29157
+ // Apply the adjustments to the x and y positions for the text
29158
+ const xPositionText = xPosition + xAdjustment;
29159
+ const yPositionText = yPosition + yAdjustment;
29349
29160
  return /*#__PURE__*/React__namespace.createElement("g", {
29350
- className: "x-axis-ticks"
29161
+ className: "tick"
29351
29162
  }, /*#__PURE__*/React__namespace.createElement("line", {
29352
29163
  x1: x1,
29353
29164
  y1: y1,
29354
29165
  x2: x2,
29355
29166
  y2: y2,
29356
- style: tickStyle
29357
- }));
29167
+ className: "axis-tick"
29168
+ }), /*#__PURE__*/React__namespace.createElement("text", {
29169
+ className: "axis-tick-label",
29170
+ style: {
29171
+ fontSize: tickLabelSize
29172
+ },
29173
+ textAnchor: "middle",
29174
+ x: xPositionText,
29175
+ y: yPositionText
29176
+ }, x.toString()));
29177
+ };
29178
+
29179
+ // Determines whether to show the label for the given tick
29180
+ // Currently, the only condition is to hide the label at -1
29181
+ // on the y-axis when the x-axis is within the graph
29182
+ const shouldShowLabel = (number, range) => {
29183
+ let showLabel = true;
29184
+
29185
+ // If the x-axis is within the graph and the y-axis is at -1, hide the label
29186
+ if (range[X][MIN] < -1 && range[X][MAX] > 0 && number === -1) {
29187
+ showLabel = false;
29188
+ }
29189
+ return showLabel;
29358
29190
  };
29359
29191
  function generateTickLocations(tickStep, min, max) {
29360
29192
  const ticks = [];
29361
29193
 
29362
29194
  // Add ticks in the positive direction
29363
- for (let i = 0 + tickStep; i < max; i += tickStep) {
29195
+ const start = Math.max(min, 0);
29196
+ for (let i = start + tickStep; i < max; i += tickStep) {
29364
29197
  ticks.push(i);
29365
29198
  }
29366
29199
 
@@ -29375,34 +29208,34 @@ function generateTickLocations(tickStep, min, max) {
29375
29208
  const AxisTicks = () => {
29376
29209
  const {
29377
29210
  tickStep,
29378
- range,
29379
- width,
29380
- height
29211
+ range
29381
29212
  } = useGraphConfig();
29382
- const graphInfo = {
29383
- range,
29384
- width,
29385
- height
29386
- };
29387
29213
  const [[xMin, xMax], [yMin, yMax]] = range;
29388
29214
  const [xTickStep, yTickStep] = tickStep;
29215
+
29216
+ // Generate the tick locations & labels for the x and y axes
29389
29217
  const yGridTicks = generateTickLocations(yTickStep, yMin, yMax);
29390
29218
  const xGridTicks = generateTickLocations(xTickStep, xMin, xMax);
29391
29219
  return /*#__PURE__*/React__namespace.createElement("g", {
29392
- className: "axis-ticks"
29220
+ className: "axis-ticks",
29221
+ role: "presentation"
29222
+ }, /*#__PURE__*/React__namespace.createElement("g", {
29223
+ className: "y-axis-ticks"
29393
29224
  }, yGridTicks.map(y => {
29394
29225
  return /*#__PURE__*/React__namespace.createElement(YGridTick, {
29395
29226
  y: y,
29396
29227
  key: `y-grid-tick-${y}`,
29397
- graphInfo: graphInfo
29228
+ range: range
29398
29229
  });
29399
- }), xGridTicks.map(x => {
29230
+ })), /*#__PURE__*/React__namespace.createElement("g", {
29231
+ className: "x-axis-ticks"
29232
+ }, xGridTicks.map(x => {
29400
29233
  return /*#__PURE__*/React__namespace.createElement(XGridTick, {
29401
29234
  x: x,
29402
29235
  key: `x-grid-tick-${x}`,
29403
- graphInfo: graphInfo
29236
+ range: range
29404
29237
  });
29405
- }));
29238
+ })));
29406
29239
  };
29407
29240
 
29408
29241
  /**
@@ -29423,12 +29256,10 @@ const axisOptions = (props, axisIndex) => {
29423
29256
  };
29424
29257
  };
29425
29258
  const Grid = props => {
29426
- return props.markings === "none" ? null : /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Coordinates.Cartesian, {
29259
+ return props.markings === "none" ? null : /*#__PURE__*/React__namespace.createElement(mafs.Coordinates.Cartesian, {
29427
29260
  xAxis: axisOptions(props, X),
29428
29261
  yAxis: axisOptions(props, Y)
29429
- }),
29430
- // Only render the axis ticks and arrows if the markings are set to a full "graph"
29431
- props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)));
29262
+ });
29432
29263
  };
29433
29264
 
29434
29265
  // Displays a stylized open eye: lint warnings are visible
@@ -32186,7 +32017,8 @@ function UnlimitedPointGraph(props) {
32186
32017
  const [[left, top]] = useTransformVectorsToPixels([minX, maxY]);
32187
32018
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
32188
32019
  style: {
32189
- fill: "rgba(0,0,0,0)"
32020
+ fill: "rgba(0,0,0,0)",
32021
+ cursor: "crosshair"
32190
32022
  },
32191
32023
  width: widthPx,
32192
32024
  height: heightPx,
@@ -32370,6 +32202,22 @@ const MafsGraph = props => {
32370
32202
  const uniqueId = React__namespace.useId();
32371
32203
  const descriptionId = `interactive-graph-description-${uniqueId}`;
32372
32204
  const graphRef = React__namespace.useRef(null);
32205
+
32206
+ // Set up the SVG attributes for the nested SVGs that help lock
32207
+ // the grid and graph elements to the bounds of the graph.
32208
+ const {
32209
+ viewboxX,
32210
+ viewboxY
32211
+ } = calculateNestedSVGCoords(state.range, width, height);
32212
+ const viewBox = `${viewboxX} ${viewboxY} ${width} ${height}`;
32213
+ const nestedSVGAttributes = {
32214
+ width,
32215
+ height,
32216
+ viewBox,
32217
+ preserveAspectRatio: "xMidYMin",
32218
+ x: viewboxX,
32219
+ y: viewboxY
32220
+ };
32373
32221
  return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
32374
32222
  value: {
32375
32223
  range: state.range,
@@ -32391,7 +32239,7 @@ const MafsGraph = props => {
32391
32239
  padding: "25px 25px 0 0",
32392
32240
  boxSizing: "content-box",
32393
32241
  marginLeft: "20px",
32394
- marginBottom: "20px",
32242
+ marginBottom: "30px",
32395
32243
  pointerEvents: props.static ? "none" : "auto",
32396
32244
  userSelect: "none",
32397
32245
  width,
@@ -32426,7 +32274,7 @@ const MafsGraph = props => {
32426
32274
  bottom: 0,
32427
32275
  left: 0
32428
32276
  }
32429
- }, props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null), /*#__PURE__*/React__namespace.createElement(AxisTickLabels, null)), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
32277
+ }, props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null)), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
32430
32278
  preserveAspectRatio: false,
32431
32279
  viewBox: {
32432
32280
  x: state.range[X],
@@ -32437,37 +32285,59 @@ const MafsGraph = props => {
32437
32285
  zoom: false,
32438
32286
  width: width,
32439
32287
  height: height
32440
- }, /*#__PURE__*/React__namespace.createElement(SvgDefs, null), /*#__PURE__*/React__namespace.createElement(Grid, {
32441
- tickStep: props.step,
32288
+ }, /*#__PURE__*/React__namespace.createElement(SvgDefs, null), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, /*#__PURE__*/React__namespace.createElement(Grid, {
32442
32289
  gridStep: props.gridStep,
32443
32290
  range: state.range,
32444
32291
  containerSizeClass: props.containerSizeClass,
32445
- markings: props.markings
32446
- }), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
32292
+ markings: props.markings,
32293
+ width: width,
32294
+ height: height
32295
+ })),
32296
+ // Only render the axis ticks and arrows if the markings are set to a full "graph"
32297
+ props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
32447
32298
  lockedFigures: props.lockedFigures,
32448
32299
  range: state.range
32449
32300
  }), props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
32450
32301
  state,
32451
32302
  dispatch
32452
- })))), renderGraphControls({
32303
+ }))))), renderGraphControls({
32453
32304
  state,
32454
- dispatch
32305
+ dispatch,
32306
+ width
32455
32307
  })));
32456
32308
  };
32457
- const renderPointGraphControls = props => /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
32309
+ const renderPointGraphControls = props => /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32310
+ style: {
32311
+ flexDirection: "row",
32312
+ width: props.width
32313
+ }
32314
+ }, /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
32458
32315
  kind: "secondary",
32459
32316
  style: {
32460
32317
  width: "100%",
32461
32318
  marginLeft: "20px"
32462
32319
  },
32320
+ tabIndex: 0,
32463
32321
  onClick: () => {
32464
32322
  props.dispatch(actions.pointGraph.addPoint([0, 0]));
32465
32323
  }
32466
- }, "Add Point");
32324
+ }, "Add Point"), (props.state.focusedPointIndex !== null || props.state.previouslyFocusedPointIndex !== null) && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
32325
+ kind: "secondary",
32326
+ color: "destructive",
32327
+ tabIndex: 0,
32328
+ style: {
32329
+ width: "100%",
32330
+ marginLeft: "20px"
32331
+ },
32332
+ onClick: event => {
32333
+ props.dispatch(actions.pointGraph.removePoint(props.state.previouslyFocusedPointIndex));
32334
+ }
32335
+ }, "Remove Point"));
32467
32336
  const renderGraphControls = props => {
32468
32337
  const {
32469
32338
  state,
32470
- dispatch
32339
+ dispatch,
32340
+ width
32471
32341
  } = props;
32472
32342
  const {
32473
32343
  type
@@ -32477,7 +32347,8 @@ const renderGraphControls = props => {
32477
32347
  if (state.numPoints === "unlimited") {
32478
32348
  return renderPointGraphControls({
32479
32349
  state,
32480
- dispatch
32350
+ dispatch,
32351
+ width
32481
32352
  });
32482
32353
  }
32483
32354
  return null;
@@ -32485,6 +32356,56 @@ const renderGraphControls = props => {
32485
32356
  return null;
32486
32357
  }
32487
32358
  };
32359
+
32360
+ // Calculate the difference between the min and max values of a range
32361
+ const getRangeDiff = range => {
32362
+ const [min, max] = range;
32363
+ return Math.abs(max - min);
32364
+ };
32365
+
32366
+ // We need to adjust the nested SVG viewbox x and Y values based on the range of the graph in order
32367
+ // to ensure that the graph is sized and positioned correctly within the Mafs SVG and the clipping mask.
32368
+ // Exported for testing.
32369
+ const calculateNestedSVGCoords = (range, width, height) => {
32370
+ // X RANGE
32371
+ let viewboxX = 0; // When xMin is 0, we want to use 0 as the viewboxX value
32372
+ const totalXRange = getRangeDiff(range[X]);
32373
+ const gridCellWidth = width / totalXRange;
32374
+ const minX = range[X][MIN];
32375
+
32376
+ // If xMin is entirely positive, we need to adjust the
32377
+ // viewboxX to be the grid cell width multiplied by xMin
32378
+ if (minX > 0) {
32379
+ viewboxX = gridCellWidth * Math.abs(minX);
32380
+ }
32381
+ // If xMin is negative, we need to adjust the viewboxX to be
32382
+ // the negative value of the grid cell width multiplied by xMin
32383
+ if (minX < 0) {
32384
+ viewboxX = -gridCellWidth * Math.abs(minX);
32385
+ }
32386
+
32387
+ // Y RANGE
32388
+ let viewboxY = -height; // When yMin is 0, we want to use the negative value of the graph height
32389
+ const totalYRange = getRangeDiff(range[Y]);
32390
+ const gridCellHeight = height / totalYRange;
32391
+ const minY = range[Y][MIN];
32392
+
32393
+ // If the y range is entirely positive, we want a negative sum of the
32394
+ // height and the gridcell height multiplied by the absolute value of yMin
32395
+ if (minY > 0) {
32396
+ viewboxY = -height - gridCellHeight * Math.abs(minY);
32397
+ }
32398
+
32399
+ // If the yMin is negative, we want to multiply the gridcell height
32400
+ // by the absolute value of yMin, and subtract the full height of the graph
32401
+ if (minY < 0) {
32402
+ viewboxY = gridCellHeight * Math.abs(minY) - height;
32403
+ }
32404
+ return {
32405
+ viewboxX,
32406
+ viewboxY
32407
+ };
32408
+ };
32488
32409
  const renderGraph = props => {
32489
32410
  const {
32490
32411
  state,
@@ -32601,7 +32522,8 @@ function initializeGraphState(params) {
32601
32522
  type: graph.type,
32602
32523
  coords: getPointCoords(graph, range, step),
32603
32524
  numPoints: graph.numPoints || 0,
32604
- focusedPointIndex: null
32525
+ focusedPointIndex: null,
32526
+ previouslyFocusedPointIndex: null
32605
32527
  };
32606
32528
  case "circle":
32607
32529
  return {
@@ -32898,6 +32820,7 @@ function doBlurPoint(state, action) {
32898
32820
  case "point":
32899
32821
  return {
32900
32822
  ...state,
32823
+ previouslyFocusedPointIndex: state.focusedPointIndex,
32901
32824
  focusedPointIndex: null
32902
32825
  };
32903
32826
  default:
@@ -32944,7 +32867,6 @@ function doMovePointInFigure(state, action) {
32944
32867
  }
32945
32868
  case "angle":
32946
32869
  case "circle":
32947
- throw new Error("FIXME implement circle reducer");
32948
32870
  case "point":
32949
32871
  case "polygon":
32950
32872
  case "quadratic":
@@ -33270,7 +33192,8 @@ function doRemovePoint(state, action) {
33270
33192
  return {
33271
33193
  ...state,
33272
33194
  coords: state.coords.filter((_, i) => i !== action.index),
33273
- focusedPointIndex: null
33195
+ focusedPointIndex: null,
33196
+ previouslyFocusedPointIndex: null
33274
33197
  };
33275
33198
  }
33276
33199
  const getDeltaVertex = (maxMoves, minMoves, delta) => {
@@ -40094,15 +40017,15 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
40094
40017
 
40095
40018
  // Render the text labels
40096
40019
  results.push(graphie.style(props.isMobile ? {
40097
- color: KhanColors.BLUE_D
40020
+ color: KhanColors.BLUE
40098
40021
  } : {}, () => _label(graphie, props.labelStyle, leftLabel, leftLabel, base)));
40099
40022
  results.push(graphie.style(props.isMobile ? {
40100
- color: KhanColors.BLUE_D
40023
+ color: KhanColors.BLUE
40101
40024
  } : {}, () => _label(graphie, props.labelStyle, rightLabel, rightLabel, base)));
40102
40025
 
40103
40026
  // Render the labels' lines
40104
40027
  graphie.style({
40105
- stroke: props.isMobile ? KhanColors.BLUE_D : KhanColors.DYNAMIC,
40028
+ stroke: KhanColors.BLUE,
40106
40029
  strokeWidth: 3.5
40107
40030
  }, () => {
40108
40031
  results.push(graphie.line([leftLabel, -0.2], [leftLabel, 0.2]));
@@ -40289,21 +40212,21 @@ class NumberLine extends React__namespace.Component {
40289
40212
  if (isOpen) {
40290
40213
  fill = KhanColors._BACKGROUND;
40291
40214
  } else if (props.static) {
40292
- fill = KhanColors.DYNAMIC;
40215
+ fill = KhanColors.BLUE;
40293
40216
  } else {
40294
- fill = KhanColors.INTERACTIVE;
40217
+ fill = KhanColors.GREEN;
40295
40218
  }
40296
40219
  const normalStyle = {
40297
40220
  fill: fill,
40298
- stroke: props.static ? KhanColors.DYNAMIC : KhanColors.INTERACTIVE,
40221
+ stroke: props.static ? KhanColors.BLUE : KhanColors.GREEN,
40299
40222
  "stroke-width": isOpen ? 3 : 1
40300
40223
  };
40301
40224
  const highlightStyle = {
40302
- fill: isOpen ? KhanColors._BACKGROUND : KhanColors.INTERACTING,
40225
+ fill: isOpen ? KhanColors._BACKGROUND : KhanColors.GREEN,
40303
40226
  "stroke-width": isOpen ? 3 : 1
40304
40227
  };
40305
40228
  const mobileDotStyle = props.isInequality ? {
40306
- stroke: KhanColors.INTERACTIVE,
40229
+ stroke: KhanColors.GREEN,
40307
40230
  "fill-opacity": isOpen ? 0 : 1
40308
40231
  } : {};
40309
40232
  return /*#__PURE__*/React__namespace.createElement(MovablePoint
@@ -40363,7 +40286,7 @@ class NumberLine extends React__namespace.Component {
40363
40286
  const end = this._getInequalityEndpoint(props);
40364
40287
  const style = {
40365
40288
  arrows: "->",
40366
- stroke: this.props.apiOptions.isMobile ? KhanColors.INTERACTIVE : KhanColors.DYNAMIC,
40289
+ stroke: this.props.apiOptions.isMobile ? KhanColors.GREEN : KhanColors.BLUE,
40367
40290
  strokeWidth: 3.5
40368
40291
  };
40369
40292
  const isGreater = ["ge", "gt"].includes(props.rel);
@@ -45099,7 +45022,7 @@ const init = function (options) {
45099
45022
 
45100
45023
  // This file is processed by a Rollup plugin (replace) to inject the production
45101
45024
  const libName = "@khanacademy/perseus";
45102
- const libVersion = "33.0.0";
45025
+ const libVersion = "33.2.0";
45103
45026
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45104
45027
 
45105
45028
  const apiVersion = {