@khanacademy/perseus 56.0.0 → 56.1.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 +14 -4
- package/dist/es/index.js +216 -49
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +14 -4
- package/dist/index.js +213 -47
- package/dist/index.js.map +1 -1
- package/dist/widgets/interactive-graph.d.ts +1 -0
- package/dist/widgets/interactive-graphs/graphs/components/movable-line.d.ts +0 -1
- package/dist/widgets/interactive-graphs/graphs/components/vector.d.ts +2 -1
- package/dist/widgets/interactive-graphs/graphs/polygon.d.ts +6 -0
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-reducer.d.ts +1 -0
- package/dist/widgets/interactive-graphs/reducer/use-graph-config.d.ts +1 -0
- package/package.json +37 -37
package/dist/es/index.css
CHANGED
|
@@ -4446,6 +4446,14 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
|
|
|
4446
4446
|
--mafs-green: #00a60e; /* WB color.green */
|
|
4447
4447
|
--mafs-violet: #9059ff; /* WB color.purple */
|
|
4448
4448
|
--mafs-yellow: #ffb100; /* WB color.gold */
|
|
4449
|
+
/* Note: offBlack50 is not the best for contrast. However, this color needs
|
|
4450
|
+
to be distinguishable from our regular interactive blue so it is clear that
|
|
4451
|
+
the element is not interactive, even though the visual elements (e.g.
|
|
4452
|
+
outlines, shadows) may indicate otherwise. When using a darker gray, it
|
|
4453
|
+
looks the same as our WB blue in grayscale. We had to make a call here, and
|
|
4454
|
+
we decided to make it lighter and more distinguishable even if it sacrifices
|
|
4455
|
+
color contrast. (Nisha 2025) */
|
|
4456
|
+
--static-gray: #909296 /* WB color.offBlack50 */;
|
|
4449
4457
|
|
|
4450
4458
|
/* overridden on a per-point basis */
|
|
4451
4459
|
--movable-point-color: var(--mafs-blue);
|
|
@@ -4595,7 +4603,6 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
|
|
|
4595
4603
|
}
|
|
4596
4604
|
|
|
4597
4605
|
.MafsView .movable-circle .circle {
|
|
4598
|
-
stroke: var(--mafs-blue);
|
|
4599
4606
|
stroke-width: 2px;
|
|
4600
4607
|
fill: transparent;
|
|
4601
4608
|
}
|
|
@@ -4626,7 +4633,6 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
|
|
|
4626
4633
|
}
|
|
4627
4634
|
|
|
4628
4635
|
.MafsView .movable-circle .movable-circle-handle {
|
|
4629
|
-
fill: var(--mafs-blue); /* WB fadedOffBlack64 */
|
|
4630
4636
|
stroke: #fff;
|
|
4631
4637
|
stroke-width: 2px;
|
|
4632
4638
|
}
|
|
@@ -4646,12 +4652,16 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
|
|
|
4646
4652
|
stroke: rgba(33, 36, 44, 0.32);
|
|
4647
4653
|
}
|
|
4648
4654
|
|
|
4649
|
-
.MafsView .angle-arc
|
|
4650
|
-
.MafsView .arc-right-angle {
|
|
4655
|
+
.MafsView .angle-arc-interactive {
|
|
4651
4656
|
stroke: var(--mafs-blue);
|
|
4652
4657
|
stroke-width: 0.1px;
|
|
4653
4658
|
}
|
|
4654
4659
|
|
|
4660
|
+
.MafsView .angle-arc-static {
|
|
4661
|
+
stroke: var(--static-gray);
|
|
4662
|
+
stroke-width: 0.1px;
|
|
4663
|
+
}
|
|
4664
|
+
|
|
4655
4665
|
.MafsView .axis-tick-label {
|
|
4656
4666
|
/* There is a bug in the MathJax font for Safari, so we use KaTeX */
|
|
4657
4667
|
font-family: "KaTeX_Main", "Mafs-MJXTEX", sans-serif;
|
package/dist/es/index.js
CHANGED
|
@@ -27696,7 +27696,8 @@ const defaultGraphConfig = {
|
|
|
27696
27696
|
width: 0,
|
|
27697
27697
|
height: 0,
|
|
27698
27698
|
labels: [],
|
|
27699
|
-
disableKeyboardInteraction: false
|
|
27699
|
+
disableKeyboardInteraction: false,
|
|
27700
|
+
interactiveColor: "var(--mafs-blue)"
|
|
27700
27701
|
};
|
|
27701
27702
|
const GraphConfigContext = /*#__PURE__*/createContext(defaultGraphConfig);
|
|
27702
27703
|
function useGraphConfig() {
|
|
@@ -27767,6 +27768,11 @@ function Arrowhead(props) {
|
|
|
27767
27768
|
var _props$color;
|
|
27768
27769
|
const [point] = useTransformVectorsToPixels(props.tip);
|
|
27769
27770
|
return /*#__PURE__*/React.createElement("g", {
|
|
27771
|
+
// Use aria-hidden to hide the line from screen readers
|
|
27772
|
+
// so it doesn't read as "image" with no context.
|
|
27773
|
+
// The elements using this should have their own aria-labels,
|
|
27774
|
+
// so this is okay.
|
|
27775
|
+
"aria-hidden": true,
|
|
27770
27776
|
className: "interactive-graph-arrowhead",
|
|
27771
27777
|
transform: `translate(${point[X]} ${point[Y]}) rotate(${props.angle})`
|
|
27772
27778
|
}, /*#__PURE__*/React.createElement("g", {
|
|
@@ -28931,6 +28937,11 @@ function SVGLine(props) {
|
|
|
28931
28937
|
testId
|
|
28932
28938
|
} = props;
|
|
28933
28939
|
return /*#__PURE__*/React.createElement("line", {
|
|
28940
|
+
// Use aria-hidden to hide the line from screen readers
|
|
28941
|
+
// so it doesn't read as "image" with no context.
|
|
28942
|
+
// The elements using this should have their own aria-labels,
|
|
28943
|
+
// so this is okay.
|
|
28944
|
+
"aria-hidden": true,
|
|
28934
28945
|
x1: start[X],
|
|
28935
28946
|
y1: start[Y],
|
|
28936
28947
|
x2: end[X],
|
|
@@ -28941,15 +28952,23 @@ function SVGLine(props) {
|
|
|
28941
28952
|
});
|
|
28942
28953
|
}
|
|
28943
28954
|
|
|
28955
|
+
/**
|
|
28956
|
+
* Used to visualize vectors for both interative elements
|
|
28957
|
+
* as well as locked figures within an Interactive Graph widget.
|
|
28958
|
+
*/
|
|
28944
28959
|
const {
|
|
28945
28960
|
calculateAngleInDegrees: calculateAngleInDegrees$3
|
|
28946
28961
|
} = angles;
|
|
28947
28962
|
function Vector(props) {
|
|
28963
|
+
const {
|
|
28964
|
+
interactiveColor
|
|
28965
|
+
} = useGraphConfig();
|
|
28948
28966
|
const {
|
|
28949
28967
|
tail,
|
|
28950
28968
|
tip,
|
|
28951
|
-
color,
|
|
28952
|
-
style
|
|
28969
|
+
color = interactiveColor,
|
|
28970
|
+
style,
|
|
28971
|
+
testId
|
|
28953
28972
|
} = props;
|
|
28954
28973
|
const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
|
|
28955
28974
|
const direction = vec.sub(tipPx, tailPx);
|
|
@@ -28958,7 +28977,8 @@ function Vector(props) {
|
|
|
28958
28977
|
style: {
|
|
28959
28978
|
stroke: color,
|
|
28960
28979
|
strokeWidth: 2
|
|
28961
|
-
}
|
|
28980
|
+
},
|
|
28981
|
+
"data-testid": testId
|
|
28962
28982
|
}, /*#__PURE__*/React.createElement(SVGLine, {
|
|
28963
28983
|
start: tailPx,
|
|
28964
28984
|
end: tipPx,
|
|
@@ -29253,9 +29273,17 @@ function getAngleFromPoints(points, i) {
|
|
|
29253
29273
|
const a = vec.dist(point, pt1);
|
|
29254
29274
|
const b = vec.dist(point, pt2);
|
|
29255
29275
|
const c = vec.dist(pt1, pt2);
|
|
29276
|
+
let lawOfCosinesRadicand = (a ** 2 + b ** 2 - c ** 2) / (2 * a * b);
|
|
29277
|
+
|
|
29278
|
+
// If the equation results in a number greater than 1 or less than -1.
|
|
29279
|
+
// Correct to ensure a valid angle.
|
|
29280
|
+
// This ensures we are not producing NaN results from Math.acos.
|
|
29281
|
+
if (lawOfCosinesRadicand < -1 || lawOfCosinesRadicand > 1) {
|
|
29282
|
+
lawOfCosinesRadicand = Math.round(lawOfCosinesRadicand);
|
|
29283
|
+
}
|
|
29256
29284
|
|
|
29257
29285
|
// Law of cosines
|
|
29258
|
-
const angle = Math.acos(
|
|
29286
|
+
const angle = Math.acos(lawOfCosinesRadicand);
|
|
29259
29287
|
return angle;
|
|
29260
29288
|
}
|
|
29261
29289
|
function getSideLengthsFromPoints(points, i, isPolygonOpen) {
|
|
@@ -29779,9 +29807,17 @@ const PolygonAngle = ({
|
|
|
29779
29807
|
const a = vec.dist(centerPoint, endPoints[0]);
|
|
29780
29808
|
const b = vec.dist(centerPoint, endPoints[1]);
|
|
29781
29809
|
const c = vec.dist(endPoints[0], endPoints[1]);
|
|
29810
|
+
let lawOfCosinesRadicand = (a ** 2 + b ** 2 - c ** 2) / (2 * a * b);
|
|
29811
|
+
|
|
29812
|
+
// If the equation results in a number greater than 1 or less than -1.
|
|
29813
|
+
// Correct to ensure a valid angle.
|
|
29814
|
+
// This ensures we are not producing NaN results from Math.acos.
|
|
29815
|
+
if (lawOfCosinesRadicand < -1 || lawOfCosinesRadicand > 1) {
|
|
29816
|
+
lawOfCosinesRadicand = Math.round(lawOfCosinesRadicand);
|
|
29817
|
+
}
|
|
29782
29818
|
|
|
29783
29819
|
// Law of cosines
|
|
29784
|
-
const angle = Math.acos(
|
|
29820
|
+
const angle = Math.acos(lawOfCosinesRadicand);
|
|
29785
29821
|
const y1 = centerY + (startY - centerY) / a * radius;
|
|
29786
29822
|
const x2 = centerX + (endX - centerX) / b * radius;
|
|
29787
29823
|
const x1 = centerX + (startX - centerX) / a * radius;
|
|
@@ -29897,6 +29933,12 @@ const Angle = ({
|
|
|
29897
29933
|
// Calculate the text position based on the angle and whether we allow reflex angles
|
|
29898
29934
|
// Let's try the angle bisector method to find the midpoint of the arc
|
|
29899
29935
|
const [textX, textY] = calculateBisectorPoint(point1, point2, vertex, isReflexive, allowReflexAngles, radius);
|
|
29936
|
+
|
|
29937
|
+
// Determine the color style for the arc when interactive vs disabled
|
|
29938
|
+
const {
|
|
29939
|
+
disableKeyboardInteraction
|
|
29940
|
+
} = useGraphConfig();
|
|
29941
|
+
const arcClassName = disableKeyboardInteraction ? "angle-arc-static" : "angle-arc-interactive";
|
|
29900
29942
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("filter", {
|
|
29901
29943
|
id: "background",
|
|
29902
29944
|
x: "-5%",
|
|
@@ -29913,10 +29955,10 @@ const Angle = ({
|
|
|
29913
29955
|
start: [x1, y1],
|
|
29914
29956
|
vertex: [x2, y2],
|
|
29915
29957
|
end: [x3, y3],
|
|
29916
|
-
className:
|
|
29958
|
+
className: arcClassName
|
|
29917
29959
|
}) : /*#__PURE__*/React.createElement(Arc, {
|
|
29918
29960
|
arc: arc,
|
|
29919
|
-
className:
|
|
29961
|
+
className: arcClassName
|
|
29920
29962
|
}), showAngles && /*#__PURE__*/React.createElement(TextLabel, {
|
|
29921
29963
|
x: textX,
|
|
29922
29964
|
y: textY,
|
|
@@ -29934,10 +29976,16 @@ const RightAngleSquare = ({
|
|
|
29934
29976
|
end: [x3, y3],
|
|
29935
29977
|
className
|
|
29936
29978
|
}) => /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
29979
|
+
// Use aria-hidden to hide the line from screen readers
|
|
29980
|
+
// so it doesn't read as "image" with no context.
|
|
29981
|
+
// The elements using this should have their own aria-labels,
|
|
29982
|
+
// so this is okay.
|
|
29983
|
+
"aria-hidden": true,
|
|
29937
29984
|
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
29938
29985
|
strokeWidth: 0.02,
|
|
29939
29986
|
fill: "none",
|
|
29940
|
-
className: className
|
|
29987
|
+
className: className,
|
|
29988
|
+
"data-testid": "angle-indicators__right-angle"
|
|
29941
29989
|
}));
|
|
29942
29990
|
|
|
29943
29991
|
// We're conditionally adding the class name here so that we can style the arc differently
|
|
@@ -29947,10 +29995,16 @@ const Arc = ({
|
|
|
29947
29995
|
className
|
|
29948
29996
|
}) => {
|
|
29949
29997
|
return /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
29998
|
+
// Use aria-hidden to hide the line from screen readers
|
|
29999
|
+
// so it doesn't read as "image" with no context.
|
|
30000
|
+
// The elements using this should have their own aria-labels,
|
|
30001
|
+
// so this is okay.
|
|
30002
|
+
"aria-hidden": true,
|
|
29950
30003
|
d: arc,
|
|
29951
30004
|
strokeWidth: 0.02,
|
|
29952
30005
|
fill: "none",
|
|
29953
|
-
className: className
|
|
30006
|
+
className: className,
|
|
30007
|
+
"data-testid": "angle-indicators__arc"
|
|
29954
30008
|
}));
|
|
29955
30009
|
};
|
|
29956
30010
|
const isRightPolygonAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
@@ -30222,7 +30276,13 @@ function Hairlines(props) {
|
|
|
30222
30276
|
const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
|
|
30223
30277
|
const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
|
|
30224
30278
|
const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
|
|
30225
|
-
return /*#__PURE__*/React.createElement("g",
|
|
30279
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
30280
|
+
// Use aria-hidden to hide the line from screen readers
|
|
30281
|
+
// so it doesn't read as "image" with no context.
|
|
30282
|
+
// The elements using this should have their own aria-labels,
|
|
30283
|
+
// so this is okay.
|
|
30284
|
+
"aria-hidden": true
|
|
30285
|
+
}, /*#__PURE__*/React.createElement("line", {
|
|
30226
30286
|
x1: verticalStartX,
|
|
30227
30287
|
y1: y,
|
|
30228
30288
|
x2: verticalEndX,
|
|
@@ -30248,11 +30308,12 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
|
|
|
30248
30308
|
var _Object$entries$find$, _Object$entries$find;
|
|
30249
30309
|
const {
|
|
30250
30310
|
markings,
|
|
30251
|
-
showTooltips
|
|
30311
|
+
showTooltips,
|
|
30312
|
+
interactiveColor
|
|
30252
30313
|
} = useGraphConfig();
|
|
30253
30314
|
const {
|
|
30254
30315
|
point,
|
|
30255
|
-
color: color$1 =
|
|
30316
|
+
color: color$1 = interactiveColor,
|
|
30256
30317
|
dragging,
|
|
30257
30318
|
focused,
|
|
30258
30319
|
cursor,
|
|
@@ -30267,6 +30328,11 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
|
|
|
30267
30328
|
const [[x, y]] = useTransformVectorsToPixels(point);
|
|
30268
30329
|
const showHairlines = (dragging || focused) && markings !== "none";
|
|
30269
30330
|
const svgForPoint = /*#__PURE__*/React.createElement("g", {
|
|
30331
|
+
// Use aria-hidden to hide the line from screen readers
|
|
30332
|
+
// so it doesn't read as "image" with no context.
|
|
30333
|
+
// The elements using this should have their own aria-labels,
|
|
30334
|
+
// so this is okay.
|
|
30335
|
+
"aria-hidden": true,
|
|
30270
30336
|
ref: hitboxRef,
|
|
30271
30337
|
className: pointClasses,
|
|
30272
30338
|
style: {
|
|
@@ -30367,9 +30433,22 @@ function useControlPoint(params) {
|
|
|
30367
30433
|
x: srFormatNumber(point[X], locale),
|
|
30368
30434
|
y: srFormatNumber(point[Y], locale)
|
|
30369
30435
|
});
|
|
30436
|
+
|
|
30437
|
+
// Set the forwarded ref to the focusable handle element when it changes.
|
|
30370
30438
|
useLayoutEffect(() => {
|
|
30371
30439
|
setForwardedRef(forwardedRef, focusableHandleRef.current);
|
|
30372
30440
|
}, [forwardedRef]);
|
|
30441
|
+
|
|
30442
|
+
// If the point is being dragged and is not focused, focus the focusable handle.
|
|
30443
|
+
useLayoutEffect(() => {
|
|
30444
|
+
if (dragging && !focused) {
|
|
30445
|
+
var _focusableHandleRef$c;
|
|
30446
|
+
// If the point is being dragged, focus the focusable handle so that
|
|
30447
|
+
// users can continue to interact with the point using the keyboard or buttons.
|
|
30448
|
+
// This particular focus call ensures that the focus ring and hairlines are visible.
|
|
30449
|
+
(_focusableHandleRef$c = focusableHandleRef.current) == null || _focusableHandleRef$c.focus();
|
|
30450
|
+
}
|
|
30451
|
+
}, [dragging, focused]);
|
|
30373
30452
|
const focusableHandle = /*#__PURE__*/React.createElement("g", {
|
|
30374
30453
|
"data-testid": "movable-point__focusable-handle",
|
|
30375
30454
|
className: "movable-point__focusable-handle",
|
|
@@ -30391,9 +30470,9 @@ function useControlPoint(params) {
|
|
|
30391
30470
|
const visiblePoint = /*#__PURE__*/React.createElement(MovablePointView, {
|
|
30392
30471
|
cursor: cursor,
|
|
30393
30472
|
onClick: () => {
|
|
30394
|
-
var _focusableHandleRef$
|
|
30473
|
+
var _focusableHandleRef$c2;
|
|
30395
30474
|
onClick();
|
|
30396
|
-
(_focusableHandleRef$
|
|
30475
|
+
(_focusableHandleRef$c2 = focusableHandleRef.current) == null || _focusableHandleRef$c2.focus();
|
|
30397
30476
|
},
|
|
30398
30477
|
point: point,
|
|
30399
30478
|
dragging: dragging,
|
|
@@ -30431,7 +30510,6 @@ const MovableLine = props => {
|
|
|
30431
30510
|
points: [start, end],
|
|
30432
30511
|
ariaLabels,
|
|
30433
30512
|
ariaDescribedBy,
|
|
30434
|
-
color,
|
|
30435
30513
|
extend,
|
|
30436
30514
|
onMoveLine = () => {},
|
|
30437
30515
|
onMovePoint = () => {}
|
|
@@ -30466,7 +30544,6 @@ const MovableLine = props => {
|
|
|
30466
30544
|
ariaLive: ariaLives[0],
|
|
30467
30545
|
point: start,
|
|
30468
30546
|
sequenceNumber: 1,
|
|
30469
|
-
color,
|
|
30470
30547
|
onMove: p => {
|
|
30471
30548
|
setAriaLives(["polite", "off", "off"]);
|
|
30472
30549
|
onMovePoint(0, p);
|
|
@@ -30482,7 +30559,6 @@ const MovableLine = props => {
|
|
|
30482
30559
|
ariaLive: ariaLives[1],
|
|
30483
30560
|
point: end,
|
|
30484
30561
|
sequenceNumber: 2,
|
|
30485
|
-
color,
|
|
30486
30562
|
onMove: p => {
|
|
30487
30563
|
setAriaLives(["off", "polite", "off"]);
|
|
30488
30564
|
onMovePoint(1, p);
|
|
@@ -30495,7 +30571,6 @@ const MovableLine = props => {
|
|
|
30495
30571
|
ariaLive: ariaLives[2],
|
|
30496
30572
|
start: start,
|
|
30497
30573
|
end: end,
|
|
30498
|
-
stroke: color,
|
|
30499
30574
|
extend: extend,
|
|
30500
30575
|
onMove: delta => {
|
|
30501
30576
|
setAriaLives(["off", "off", "polite"]);
|
|
@@ -30504,7 +30579,6 @@ const MovableLine = props => {
|
|
|
30504
30579
|
});
|
|
30505
30580
|
return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle1, line, focusableHandle2, visiblePoint1, visiblePoint2);
|
|
30506
30581
|
};
|
|
30507
|
-
const defaultStroke = "var(--movable-line-stroke-color)";
|
|
30508
30582
|
const Line$1 = props => {
|
|
30509
30583
|
const {
|
|
30510
30584
|
start,
|
|
@@ -30513,7 +30587,6 @@ const Line$1 = props => {
|
|
|
30513
30587
|
ariaDescribedBy,
|
|
30514
30588
|
ariaLive,
|
|
30515
30589
|
extend,
|
|
30516
|
-
stroke = defaultStroke,
|
|
30517
30590
|
onMove
|
|
30518
30591
|
} = props;
|
|
30519
30592
|
const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
|
|
@@ -30521,7 +30594,8 @@ const Line$1 = props => {
|
|
|
30521
30594
|
range,
|
|
30522
30595
|
graphDimensionsInPixels,
|
|
30523
30596
|
snapStep,
|
|
30524
|
-
disableKeyboardInteraction
|
|
30597
|
+
disableKeyboardInteraction,
|
|
30598
|
+
interactiveColor
|
|
30525
30599
|
} = useGraphConfig();
|
|
30526
30600
|
let startExtend = undefined;
|
|
30527
30601
|
let endExtend = undefined;
|
|
@@ -30578,7 +30652,7 @@ const Line$1 = props => {
|
|
|
30578
30652
|
start: startPtPx,
|
|
30579
30653
|
end: endPtPx,
|
|
30580
30654
|
style: {
|
|
30581
|
-
stroke,
|
|
30655
|
+
stroke: interactiveColor,
|
|
30582
30656
|
strokeWidth: "var(--movable-line-stroke-weight)"
|
|
30583
30657
|
},
|
|
30584
30658
|
className: dragging ? "movable-dragging" : "",
|
|
@@ -30586,11 +30660,11 @@ const Line$1 = props => {
|
|
|
30586
30660
|
})), startExtend && /*#__PURE__*/React.createElement(Vector, {
|
|
30587
30661
|
tail: start,
|
|
30588
30662
|
tip: startExtend,
|
|
30589
|
-
|
|
30663
|
+
testId: "movable-line__vector"
|
|
30590
30664
|
}), endExtend && /*#__PURE__*/React.createElement(Vector, {
|
|
30591
30665
|
tail: end,
|
|
30592
30666
|
tip: endExtend,
|
|
30593
|
-
|
|
30667
|
+
testId: "movable-line__vector"
|
|
30594
30668
|
}));
|
|
30595
30669
|
};
|
|
30596
30670
|
const getMovableLineKeyboardConstraint = (line, snapStep, pointIndex) => {
|
|
@@ -30662,7 +30736,8 @@ function AngleGraph(props) {
|
|
|
30662
30736
|
graphState
|
|
30663
30737
|
} = props;
|
|
30664
30738
|
const {
|
|
30665
|
-
graphDimensionsInPixels
|
|
30739
|
+
graphDimensionsInPixels,
|
|
30740
|
+
interactiveColor
|
|
30666
30741
|
} = useGraphConfig();
|
|
30667
30742
|
const i18n = usePerseusI18n();
|
|
30668
30743
|
const id = React.useId();
|
|
@@ -30693,13 +30768,14 @@ function AngleGraph(props) {
|
|
|
30693
30768
|
start: startPtPx,
|
|
30694
30769
|
end: endPtPx,
|
|
30695
30770
|
style: {
|
|
30696
|
-
stroke:
|
|
30771
|
+
stroke: interactiveColor,
|
|
30697
30772
|
strokeWidth: "var(--movable-line-stroke-weight)"
|
|
30698
|
-
}
|
|
30773
|
+
},
|
|
30774
|
+
testId: "angle-graph__line"
|
|
30699
30775
|
}), /*#__PURE__*/React.createElement(Vector, {
|
|
30700
30776
|
tail: angleLines[i][1],
|
|
30701
30777
|
tip: endExtend,
|
|
30702
|
-
|
|
30778
|
+
testId: "angle-graph__vector"
|
|
30703
30779
|
}));
|
|
30704
30780
|
});
|
|
30705
30781
|
|
|
@@ -31043,7 +31119,8 @@ function MovableCircle(props) {
|
|
|
31043
31119
|
} = props;
|
|
31044
31120
|
const {
|
|
31045
31121
|
snapStep,
|
|
31046
|
-
disableKeyboardInteraction
|
|
31122
|
+
disableKeyboardInteraction,
|
|
31123
|
+
interactiveColor
|
|
31047
31124
|
} = useGraphConfig();
|
|
31048
31125
|
const [focused, setFocused] = React.useState(false);
|
|
31049
31126
|
const draggableRef = useRef(null);
|
|
@@ -31079,7 +31156,9 @@ function MovableCircle(props) {
|
|
|
31079
31156
|
cx: centerPx[X],
|
|
31080
31157
|
cy: centerPx[Y],
|
|
31081
31158
|
rx: radiiPx[X],
|
|
31082
|
-
ry: radiiPx[Y]
|
|
31159
|
+
ry: radiiPx[Y],
|
|
31160
|
+
stroke: interactiveColor,
|
|
31161
|
+
"data-testid": "movable-circle__circle"
|
|
31083
31162
|
}), /*#__PURE__*/React.createElement(DragHandle, {
|
|
31084
31163
|
center: center,
|
|
31085
31164
|
dragging: dragging,
|
|
@@ -31096,7 +31175,8 @@ function DragHandle(props) {
|
|
|
31096
31175
|
} = props;
|
|
31097
31176
|
const [centerPx] = useTransformVectorsToPixels(center);
|
|
31098
31177
|
const {
|
|
31099
|
-
markings
|
|
31178
|
+
markings,
|
|
31179
|
+
interactiveColor
|
|
31100
31180
|
} = useGraphConfig();
|
|
31101
31181
|
const cornerRadius = Math.min(...dragHandleDimensions) / 2;
|
|
31102
31182
|
const topLeft = vec.sub(centerPx, vec.scale(dragHandleDimensions, 0.5));
|
|
@@ -31110,7 +31190,9 @@ function DragHandle(props) {
|
|
|
31110
31190
|
width: dragHandleDimensions[X],
|
|
31111
31191
|
height: dragHandleDimensions[Y],
|
|
31112
31192
|
rx: cornerRadius,
|
|
31113
|
-
ry: cornerRadius
|
|
31193
|
+
ry: cornerRadius,
|
|
31194
|
+
fill: interactiveColor,
|
|
31195
|
+
"data-testid": "movable-circle__handle"
|
|
31114
31196
|
}), dragHandleDotPositions.map(offsetPx => {
|
|
31115
31197
|
const [xPx, yPx] = vec.add(offsetPx, centerPx);
|
|
31116
31198
|
return /*#__PURE__*/React.createElement("circle", {
|
|
@@ -31269,8 +31351,7 @@ const LinearGraph = (props, key) => {
|
|
|
31269
31351
|
start: true,
|
|
31270
31352
|
end: true
|
|
31271
31353
|
},
|
|
31272
|
-
onMovePoint: (endpointIndex, destination) => dispatch(actions.linear.movePoint(endpointIndex, destination))
|
|
31273
|
-
color: "var(--movable-line-stroke-color)"
|
|
31354
|
+
onMovePoint: (endpointIndex, destination) => dispatch(actions.linear.movePoint(endpointIndex, destination))
|
|
31274
31355
|
}), /*#__PURE__*/React.createElement("g", {
|
|
31275
31356
|
id: pointsDescriptionId,
|
|
31276
31357
|
style: a11y.srOnly
|
|
@@ -31409,8 +31490,7 @@ const LinearSystemGraph = props => {
|
|
|
31409
31490
|
start: true,
|
|
31410
31491
|
end: true
|
|
31411
31492
|
},
|
|
31412
|
-
onMovePoint: (endpointIndex, destination) => dispatch(actions.linearSystem.movePointInFigure(i, endpointIndex, destination))
|
|
31413
|
-
color: "var(--movable-line-stroke-color)"
|
|
31493
|
+
onMovePoint: (endpointIndex, destination) => dispatch(actions.linearSystem.movePointInFigure(i, endpointIndex, destination))
|
|
31414
31494
|
})), linesAriaInfo.map(({
|
|
31415
31495
|
pointsDescriptionId,
|
|
31416
31496
|
interceptDescriptionId,
|
|
@@ -32226,6 +32306,7 @@ function doMovePoint(state, action) {
|
|
|
32226
32306
|
{
|
|
32227
32307
|
return _extends({}, state, {
|
|
32228
32308
|
hasBeenInteractedWith: true,
|
|
32309
|
+
focusedPointIndex: action.index,
|
|
32229
32310
|
coords: setAtIndex({
|
|
32230
32311
|
array: state.coords,
|
|
32231
32312
|
index: action.index,
|
|
@@ -32545,7 +32626,9 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
|
32545
32626
|
coords
|
|
32546
32627
|
}, index) {
|
|
32547
32628
|
const startingPoint = coords[index];
|
|
32548
|
-
|
|
32629
|
+
return calculateAngleSnap(destinationPoint, range, coords, index, startingPoint);
|
|
32630
|
+
}
|
|
32631
|
+
function calculateAngleSnap(destinationPoint, range, coords, index, startingPoint) {
|
|
32549
32632
|
// Needed to prevent updating the original coords before the checks for
|
|
32550
32633
|
// degenerate triangles and overlapping sides
|
|
32551
32634
|
const coordsCopy = [...coords];
|
|
@@ -32834,7 +32917,8 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
32834
32917
|
snapStep
|
|
32835
32918
|
} = statefulProps.graphState;
|
|
32836
32919
|
const {
|
|
32837
|
-
disableKeyboardInteraction
|
|
32920
|
+
disableKeyboardInteraction,
|
|
32921
|
+
interactiveColor
|
|
32838
32922
|
} = graphConfig;
|
|
32839
32923
|
const {
|
|
32840
32924
|
strings,
|
|
@@ -32870,12 +32954,16 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
32870
32954
|
"aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
|
|
32871
32955
|
}, /*#__PURE__*/React.createElement(Polygon, {
|
|
32872
32956
|
points: [...points],
|
|
32873
|
-
color:
|
|
32957
|
+
color: interactiveColor,
|
|
32874
32958
|
svgPolygonProps: {
|
|
32875
32959
|
strokeWidth: focusVisible ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
|
|
32876
32960
|
style: {
|
|
32877
32961
|
fill: "transparent"
|
|
32878
|
-
}
|
|
32962
|
+
},
|
|
32963
|
+
// Use aria-hidden to hide the line from screen readers
|
|
32964
|
+
// so it doesn't read as "image" with no context.
|
|
32965
|
+
// This is okay because the graph has its own aria-label.
|
|
32966
|
+
"aria-hidden": true
|
|
32879
32967
|
}
|
|
32880
32968
|
}), points.map((point, i) => {
|
|
32881
32969
|
const pt1 = points.at(i - 1);
|
|
@@ -33015,6 +33103,9 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
33015
33103
|
strings,
|
|
33016
33104
|
locale
|
|
33017
33105
|
} = usePerseusI18n();
|
|
33106
|
+
const {
|
|
33107
|
+
interactiveColor
|
|
33108
|
+
} = useGraphConfig();
|
|
33018
33109
|
const id = React.useId();
|
|
33019
33110
|
const polygonPointsNumId = id + "-points-num";
|
|
33020
33111
|
const polygonPointsId = id + "-points";
|
|
@@ -33054,14 +33145,22 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
33054
33145
|
"aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
|
|
33055
33146
|
}, /*#__PURE__*/React.createElement(Polyline, {
|
|
33056
33147
|
points: [...points],
|
|
33057
|
-
color:
|
|
33148
|
+
color: interactiveColor,
|
|
33058
33149
|
svgPolylineProps: {
|
|
33059
33150
|
strokeWidth: "var(--movable-line-stroke-weight)",
|
|
33060
33151
|
style: {
|
|
33061
33152
|
fill: "transparent"
|
|
33062
|
-
}
|
|
33153
|
+
},
|
|
33154
|
+
// Use aria-hidden to hide the line from screen readers
|
|
33155
|
+
// so it doesn't read as "image" with no context.
|
|
33156
|
+
// This is okay because the graph has its own aria-label.
|
|
33157
|
+
"aria-hidden": true
|
|
33063
33158
|
}
|
|
33064
33159
|
}), /*#__PURE__*/React.createElement("rect", {
|
|
33160
|
+
// Use aria-hidden to hide the line from screen readers
|
|
33161
|
+
// so it doesn't read as "image" with no context.
|
|
33162
|
+
// This is okay because the graph has its own aria-label.
|
|
33163
|
+
"aria-hidden": true,
|
|
33065
33164
|
style: {
|
|
33066
33165
|
fill: "rgba(0,0,0,0)",
|
|
33067
33166
|
cursor: "crosshair"
|
|
@@ -33215,7 +33314,7 @@ function getKeyboardMovementConstraintForPoint(points, index, range, snapStep, s
|
|
|
33215
33314
|
case "sides":
|
|
33216
33315
|
return getSideSnapConstraint(points, index, range);
|
|
33217
33316
|
case "angles":
|
|
33218
|
-
return
|
|
33317
|
+
return getAngleSnapConstraint(points, index, range);
|
|
33219
33318
|
default:
|
|
33220
33319
|
throw new UnreachableCaseError(snapTo);
|
|
33221
33320
|
}
|
|
@@ -33269,6 +33368,49 @@ function getSideSnapConstraint(points, index, range) {
|
|
|
33269
33368
|
right: movePointWithConstraint(coord => vec.add(coord, [1, 0]))
|
|
33270
33369
|
};
|
|
33271
33370
|
}
|
|
33371
|
+
function getAngleSnapConstraint(points, index, range) {
|
|
33372
|
+
// Make newPoints mutable.
|
|
33373
|
+
const newPoints = [...points];
|
|
33374
|
+
|
|
33375
|
+
// Get the point that is being moved.
|
|
33376
|
+
const pointToBeMoved = newPoints[index];
|
|
33377
|
+
|
|
33378
|
+
// Create a helper function that moves the point to a valid location
|
|
33379
|
+
// for angle snapping.
|
|
33380
|
+
const movePointWithConstraint = moveFunc => {
|
|
33381
|
+
// The direction the user is attempting to move the point in.
|
|
33382
|
+
let destinationAttempt = bound$1({
|
|
33383
|
+
snapStep: [0, 0],
|
|
33384
|
+
range,
|
|
33385
|
+
point: moveFunc(pointToBeMoved)
|
|
33386
|
+
});
|
|
33387
|
+
// The new point we're moving to.
|
|
33388
|
+
let newPoint = pointToBeMoved;
|
|
33389
|
+
|
|
33390
|
+
// Move the point and keep trying until we are at the boarder
|
|
33391
|
+
// of the graph.
|
|
33392
|
+
while (newPoint[0] === pointToBeMoved[0] && newPoint[1] === pointToBeMoved[1] && isInBound({
|
|
33393
|
+
range,
|
|
33394
|
+
point: destinationAttempt
|
|
33395
|
+
})) {
|
|
33396
|
+
newPoint = calculateAngleSnap(destinationAttempt, range, newPoints, index, pointToBeMoved);
|
|
33397
|
+
|
|
33398
|
+
// Increment the destinationAttempt.
|
|
33399
|
+
// For every time it does not work increment the direction for x and y.
|
|
33400
|
+
destinationAttempt = moveFunc(destinationAttempt);
|
|
33401
|
+
}
|
|
33402
|
+
return newPoint;
|
|
33403
|
+
};
|
|
33404
|
+
|
|
33405
|
+
// For each direction look for the next movable point by a small step to increase changes
|
|
33406
|
+
// of finding the next angle value.
|
|
33407
|
+
return {
|
|
33408
|
+
up: movePointWithConstraint(coord => vec.add(coord, [0, 0.1])),
|
|
33409
|
+
down: movePointWithConstraint(coord => vec.sub(coord, [0, 0.1])),
|
|
33410
|
+
left: movePointWithConstraint(coord => vec.sub(coord, [0.1, 0])),
|
|
33411
|
+
right: movePointWithConstraint(coord => vec.add(coord, [0.1, 0]))
|
|
33412
|
+
};
|
|
33413
|
+
}
|
|
33272
33414
|
|
|
33273
33415
|
function renderQuadraticGraph(state, dispatch, i18n) {
|
|
33274
33416
|
return {
|
|
@@ -33288,6 +33430,9 @@ function QuadraticGraph(props) {
|
|
|
33288
33430
|
coords,
|
|
33289
33431
|
snapStep
|
|
33290
33432
|
} = graphState;
|
|
33433
|
+
const {
|
|
33434
|
+
interactiveColor
|
|
33435
|
+
} = useGraphConfig();
|
|
33291
33436
|
const {
|
|
33292
33437
|
strings,
|
|
33293
33438
|
locale
|
|
@@ -33329,7 +33474,13 @@ function QuadraticGraph(props) {
|
|
|
33329
33474
|
"aria-describedby": `${quadraticDirectionId} ${quadraticVertexId} ${quadraticInterceptsId}`
|
|
33330
33475
|
}, /*#__PURE__*/React.createElement(Plot$2.OfX, {
|
|
33331
33476
|
y: y,
|
|
33332
|
-
color:
|
|
33477
|
+
color: interactiveColor,
|
|
33478
|
+
svgPathProps: {
|
|
33479
|
+
// Use aria-hidden to hide the line from screen readers
|
|
33480
|
+
// so it doesn't read as "image" with no context.
|
|
33481
|
+
// This is okay because the graph has its own aria-label.
|
|
33482
|
+
"aria-hidden": true
|
|
33483
|
+
}
|
|
33333
33484
|
}), coords.map((coord, i) => {
|
|
33334
33485
|
const srQuadraticPoint = getQuadraticPointString(i + 1, coord, strings, locale);
|
|
33335
33486
|
// Vertex might not exist if the quadratic graph is a line
|
|
@@ -33724,6 +33875,9 @@ function SinusoidGraph(props) {
|
|
|
33724
33875
|
dispatch,
|
|
33725
33876
|
graphState
|
|
33726
33877
|
} = props;
|
|
33878
|
+
const {
|
|
33879
|
+
interactiveColor
|
|
33880
|
+
} = useGraphConfig();
|
|
33727
33881
|
const i18n = usePerseusI18n();
|
|
33728
33882
|
const id = React.useId();
|
|
33729
33883
|
const descriptionId = id + "-description";
|
|
@@ -33766,7 +33920,13 @@ function SinusoidGraph(props) {
|
|
|
33766
33920
|
"aria-describedby": descriptionId
|
|
33767
33921
|
}, /*#__PURE__*/React.createElement(Plot$2.OfX, {
|
|
33768
33922
|
y: x => computeSine(x, coeffRef.current),
|
|
33769
|
-
color:
|
|
33923
|
+
color: interactiveColor,
|
|
33924
|
+
svgPathProps: {
|
|
33925
|
+
// Use aria-hidden to hide the line from screen readers
|
|
33926
|
+
// so it doesn't read as "image" with no context.
|
|
33927
|
+
// This is okay because the graph has its own aria-label.
|
|
33928
|
+
"aria-hidden": true
|
|
33929
|
+
}
|
|
33770
33930
|
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33771
33931
|
ariaLabel: i === 0 ? srSinusoidRootPoint : srSinusoidPeakPoint,
|
|
33772
33932
|
key: "point-" + i,
|
|
@@ -34115,6 +34275,7 @@ const MafsGraph = props => {
|
|
|
34115
34275
|
i18n,
|
|
34116
34276
|
markings: props.markings
|
|
34117
34277
|
});
|
|
34278
|
+
const disableInteraction = readOnly || !!props.static;
|
|
34118
34279
|
return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
|
|
34119
34280
|
value: {
|
|
34120
34281
|
range: state.range,
|
|
@@ -34127,7 +34288,12 @@ const MafsGraph = props => {
|
|
|
34127
34288
|
width,
|
|
34128
34289
|
height,
|
|
34129
34290
|
labels,
|
|
34130
|
-
disableKeyboardInteraction:
|
|
34291
|
+
disableKeyboardInteraction: disableInteraction,
|
|
34292
|
+
// If the graph is read-only or static, we want to make it
|
|
34293
|
+
// visually clear that the graph is no longer interactive.
|
|
34294
|
+
// We do this by changing the color of the interactive elements
|
|
34295
|
+
// to a static gray color rather than our standard blue.
|
|
34296
|
+
interactiveColor: disableInteraction ? "var(--static-gray)" : "var(--mafs-blue)"
|
|
34131
34297
|
}
|
|
34132
34298
|
}, /*#__PURE__*/React.createElement(View, {
|
|
34133
34299
|
className: "mafs-graph-container"
|
|
@@ -35143,7 +35309,8 @@ InteractiveGraph.defaultProps = {
|
|
|
35143
35309
|
const staticTransform$3 = _.identity;
|
|
35144
35310
|
var InteractiveGraph$1 = {
|
|
35145
35311
|
name: "interactive-graph",
|
|
35146
|
-
displayName: "Interactive graph
|
|
35312
|
+
displayName: "Interactive graph",
|
|
35313
|
+
accessible: true,
|
|
35147
35314
|
widget: InteractiveGraph,
|
|
35148
35315
|
staticTransform: staticTransform$3
|
|
35149
35316
|
};
|
|
@@ -41232,7 +41399,7 @@ const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
|
|
|
41232
41399
|
|
|
41233
41400
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
41234
41401
|
const libName = "@khanacademy/perseus";
|
|
41235
|
-
const libVersion = "56.
|
|
41402
|
+
const libVersion = "56.1.0";
|
|
41236
41403
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
41237
41404
|
|
|
41238
41405
|
const apiVersion = {
|