@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/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/index.js
CHANGED
|
@@ -28143,7 +28143,8 @@ const defaultGraphConfig = {
|
|
|
28143
28143
|
width: 0,
|
|
28144
28144
|
height: 0,
|
|
28145
28145
|
labels: [],
|
|
28146
|
-
disableKeyboardInteraction: false
|
|
28146
|
+
disableKeyboardInteraction: false,
|
|
28147
|
+
interactiveColor: "var(--mafs-blue)"
|
|
28147
28148
|
};
|
|
28148
28149
|
const GraphConfigContext = /*#__PURE__*/React.createContext(defaultGraphConfig);
|
|
28149
28150
|
function useGraphConfig() {
|
|
@@ -28220,6 +28221,11 @@ const arrowPath = pathBuilder().move(-3, 4).curve(-2.75, 2.5, 0, 0.25, 0.75, 0).
|
|
|
28220
28221
|
function Arrowhead(props) {
|
|
28221
28222
|
const [point] = useTransformVectorsToPixels(props.tip);
|
|
28222
28223
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
28224
|
+
// Use aria-hidden to hide the line from screen readers
|
|
28225
|
+
// so it doesn't read as "image" with no context.
|
|
28226
|
+
// The elements using this should have their own aria-labels,
|
|
28227
|
+
// so this is okay.
|
|
28228
|
+
"aria-hidden": true,
|
|
28223
28229
|
className: "interactive-graph-arrowhead",
|
|
28224
28230
|
transform: `translate(${point[X]} ${point[Y]}) rotate(${props.angle})`
|
|
28225
28231
|
}, /*#__PURE__*/React__namespace.createElement("g", {
|
|
@@ -29389,6 +29395,11 @@ function SVGLine(props) {
|
|
|
29389
29395
|
testId
|
|
29390
29396
|
} = props;
|
|
29391
29397
|
return /*#__PURE__*/React__namespace.createElement("line", {
|
|
29398
|
+
// Use aria-hidden to hide the line from screen readers
|
|
29399
|
+
// so it doesn't read as "image" with no context.
|
|
29400
|
+
// The elements using this should have their own aria-labels,
|
|
29401
|
+
// so this is okay.
|
|
29402
|
+
"aria-hidden": true,
|
|
29392
29403
|
x1: start[X],
|
|
29393
29404
|
y1: start[Y],
|
|
29394
29405
|
x2: end[X],
|
|
@@ -29399,15 +29410,23 @@ function SVGLine(props) {
|
|
|
29399
29410
|
});
|
|
29400
29411
|
}
|
|
29401
29412
|
|
|
29413
|
+
/**
|
|
29414
|
+
* Used to visualize vectors for both interative elements
|
|
29415
|
+
* as well as locked figures within an Interactive Graph widget.
|
|
29416
|
+
*/
|
|
29402
29417
|
const {
|
|
29403
29418
|
calculateAngleInDegrees: calculateAngleInDegrees$3
|
|
29404
29419
|
} = kmath.angles;
|
|
29405
29420
|
function Vector(props) {
|
|
29421
|
+
const {
|
|
29422
|
+
interactiveColor
|
|
29423
|
+
} = useGraphConfig();
|
|
29406
29424
|
const {
|
|
29407
29425
|
tail,
|
|
29408
29426
|
tip,
|
|
29409
|
-
color,
|
|
29410
|
-
style
|
|
29427
|
+
color = interactiveColor,
|
|
29428
|
+
style,
|
|
29429
|
+
testId
|
|
29411
29430
|
} = props;
|
|
29412
29431
|
const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
|
|
29413
29432
|
const direction = mafs.vec.sub(tipPx, tailPx);
|
|
@@ -29416,7 +29435,8 @@ function Vector(props) {
|
|
|
29416
29435
|
style: {
|
|
29417
29436
|
stroke: color,
|
|
29418
29437
|
strokeWidth: 2
|
|
29419
|
-
}
|
|
29438
|
+
},
|
|
29439
|
+
"data-testid": testId
|
|
29420
29440
|
}, /*#__PURE__*/React__namespace.createElement(SVGLine, {
|
|
29421
29441
|
start: tailPx,
|
|
29422
29442
|
end: tipPx,
|
|
@@ -29714,9 +29734,17 @@ function getAngleFromPoints(points, i) {
|
|
|
29714
29734
|
const a = mafs.vec.dist(point, pt1);
|
|
29715
29735
|
const b = mafs.vec.dist(point, pt2);
|
|
29716
29736
|
const c = mafs.vec.dist(pt1, pt2);
|
|
29737
|
+
let lawOfCosinesRadicand = (a ** 2 + b ** 2 - c ** 2) / (2 * a * b);
|
|
29738
|
+
|
|
29739
|
+
// If the equation results in a number greater than 1 or less than -1.
|
|
29740
|
+
// Correct to ensure a valid angle.
|
|
29741
|
+
// This ensures we are not producing NaN results from Math.acos.
|
|
29742
|
+
if (lawOfCosinesRadicand < -1 || lawOfCosinesRadicand > 1) {
|
|
29743
|
+
lawOfCosinesRadicand = Math.round(lawOfCosinesRadicand);
|
|
29744
|
+
}
|
|
29717
29745
|
|
|
29718
29746
|
// Law of cosines
|
|
29719
|
-
const angle = Math.acos(
|
|
29747
|
+
const angle = Math.acos(lawOfCosinesRadicand);
|
|
29720
29748
|
return angle;
|
|
29721
29749
|
}
|
|
29722
29750
|
function getSideLengthsFromPoints(points, i, isPolygonOpen) {
|
|
@@ -30243,9 +30271,17 @@ const PolygonAngle = _ref => {
|
|
|
30243
30271
|
const a = mafs.vec.dist(centerPoint, endPoints[0]);
|
|
30244
30272
|
const b = mafs.vec.dist(centerPoint, endPoints[1]);
|
|
30245
30273
|
const c = mafs.vec.dist(endPoints[0], endPoints[1]);
|
|
30274
|
+
let lawOfCosinesRadicand = (a ** 2 + b ** 2 - c ** 2) / (2 * a * b);
|
|
30275
|
+
|
|
30276
|
+
// If the equation results in a number greater than 1 or less than -1.
|
|
30277
|
+
// Correct to ensure a valid angle.
|
|
30278
|
+
// This ensures we are not producing NaN results from Math.acos.
|
|
30279
|
+
if (lawOfCosinesRadicand < -1 || lawOfCosinesRadicand > 1) {
|
|
30280
|
+
lawOfCosinesRadicand = Math.round(lawOfCosinesRadicand);
|
|
30281
|
+
}
|
|
30246
30282
|
|
|
30247
30283
|
// Law of cosines
|
|
30248
|
-
const angle = Math.acos(
|
|
30284
|
+
const angle = Math.acos(lawOfCosinesRadicand);
|
|
30249
30285
|
const y1 = centerY + (startY - centerY) / a * radius;
|
|
30250
30286
|
const x2 = centerX + (endX - centerX) / b * radius;
|
|
30251
30287
|
const x1 = centerX + (startX - centerX) / a * radius;
|
|
@@ -30362,6 +30398,12 @@ const Angle = _ref2 => {
|
|
|
30362
30398
|
// Calculate the text position based on the angle and whether we allow reflex angles
|
|
30363
30399
|
// Let's try the angle bisector method to find the midpoint of the arc
|
|
30364
30400
|
const [textX, textY] = calculateBisectorPoint(point1, point2, vertex, isReflexive, allowReflexAngles, radius);
|
|
30401
|
+
|
|
30402
|
+
// Determine the color style for the arc when interactive vs disabled
|
|
30403
|
+
const {
|
|
30404
|
+
disableKeyboardInteraction
|
|
30405
|
+
} = useGraphConfig();
|
|
30406
|
+
const arcClassName = disableKeyboardInteraction ? "angle-arc-static" : "angle-arc-interactive";
|
|
30365
30407
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("filter", {
|
|
30366
30408
|
id: "background",
|
|
30367
30409
|
x: "-5%",
|
|
@@ -30378,10 +30420,10 @@ const Angle = _ref2 => {
|
|
|
30378
30420
|
start: [x1, y1],
|
|
30379
30421
|
vertex: [x2, y2],
|
|
30380
30422
|
end: [x3, y3],
|
|
30381
|
-
className:
|
|
30423
|
+
className: arcClassName
|
|
30382
30424
|
}) : /*#__PURE__*/React__namespace.createElement(Arc, {
|
|
30383
30425
|
arc: arc,
|
|
30384
|
-
className:
|
|
30426
|
+
className: arcClassName
|
|
30385
30427
|
}), showAngles && /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
30386
30428
|
x: textX,
|
|
30387
30429
|
y: textY,
|
|
@@ -30401,10 +30443,16 @@ const RightAngleSquare = _ref3 => {
|
|
|
30401
30443
|
className
|
|
30402
30444
|
} = _ref3;
|
|
30403
30445
|
return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
|
|
30446
|
+
// Use aria-hidden to hide the line from screen readers
|
|
30447
|
+
// so it doesn't read as "image" with no context.
|
|
30448
|
+
// The elements using this should have their own aria-labels,
|
|
30449
|
+
// so this is okay.
|
|
30450
|
+
"aria-hidden": true,
|
|
30404
30451
|
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
30405
30452
|
strokeWidth: 0.02,
|
|
30406
30453
|
fill: "none",
|
|
30407
|
-
className: className
|
|
30454
|
+
className: className,
|
|
30455
|
+
"data-testid": "angle-indicators__right-angle"
|
|
30408
30456
|
}));
|
|
30409
30457
|
};
|
|
30410
30458
|
|
|
@@ -30416,10 +30464,16 @@ const Arc = _ref4 => {
|
|
|
30416
30464
|
className
|
|
30417
30465
|
} = _ref4;
|
|
30418
30466
|
return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
|
|
30467
|
+
// Use aria-hidden to hide the line from screen readers
|
|
30468
|
+
// so it doesn't read as "image" with no context.
|
|
30469
|
+
// The elements using this should have their own aria-labels,
|
|
30470
|
+
// so this is okay.
|
|
30471
|
+
"aria-hidden": true,
|
|
30419
30472
|
d: arc,
|
|
30420
30473
|
strokeWidth: 0.02,
|
|
30421
30474
|
fill: "none",
|
|
30422
|
-
className: className
|
|
30475
|
+
className: className,
|
|
30476
|
+
"data-testid": "angle-indicators__arc"
|
|
30423
30477
|
}));
|
|
30424
30478
|
};
|
|
30425
30479
|
const isRightPolygonAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
@@ -30692,7 +30746,13 @@ function Hairlines(props) {
|
|
|
30692
30746
|
const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
|
|
30693
30747
|
const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
|
|
30694
30748
|
const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
|
|
30695
|
-
return /*#__PURE__*/React__namespace.createElement("g",
|
|
30749
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
30750
|
+
// Use aria-hidden to hide the line from screen readers
|
|
30751
|
+
// so it doesn't read as "image" with no context.
|
|
30752
|
+
// The elements using this should have their own aria-labels,
|
|
30753
|
+
// so this is okay.
|
|
30754
|
+
"aria-hidden": true
|
|
30755
|
+
}, /*#__PURE__*/React__namespace.createElement("line", {
|
|
30696
30756
|
x1: verticalStartX,
|
|
30697
30757
|
y1: y,
|
|
30698
30758
|
x2: verticalEndX,
|
|
@@ -30717,11 +30777,12 @@ const hitboxSizePx = 48;
|
|
|
30717
30777
|
const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
|
|
30718
30778
|
const {
|
|
30719
30779
|
markings,
|
|
30720
|
-
showTooltips
|
|
30780
|
+
showTooltips,
|
|
30781
|
+
interactiveColor
|
|
30721
30782
|
} = useGraphConfig();
|
|
30722
30783
|
const {
|
|
30723
30784
|
point,
|
|
30724
|
-
color =
|
|
30785
|
+
color = interactiveColor,
|
|
30725
30786
|
dragging,
|
|
30726
30787
|
focused,
|
|
30727
30788
|
cursor,
|
|
@@ -30739,6 +30800,11 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
|
|
|
30739
30800
|
const [[x, y]] = useTransformVectorsToPixels(point);
|
|
30740
30801
|
const showHairlines = (dragging || focused) && markings !== "none";
|
|
30741
30802
|
const svgForPoint = /*#__PURE__*/React__namespace.createElement("g", {
|
|
30803
|
+
// Use aria-hidden to hide the line from screen readers
|
|
30804
|
+
// so it doesn't read as "image" with no context.
|
|
30805
|
+
// The elements using this should have their own aria-labels,
|
|
30806
|
+
// so this is okay.
|
|
30807
|
+
"aria-hidden": true,
|
|
30742
30808
|
ref: hitboxRef,
|
|
30743
30809
|
className: pointClasses,
|
|
30744
30810
|
style: {
|
|
@@ -30842,9 +30908,21 @@ function useControlPoint(params) {
|
|
|
30842
30908
|
x: srFormatNumber(point[X], locale),
|
|
30843
30909
|
y: srFormatNumber(point[Y], locale)
|
|
30844
30910
|
});
|
|
30911
|
+
|
|
30912
|
+
// Set the forwarded ref to the focusable handle element when it changes.
|
|
30845
30913
|
React.useLayoutEffect(() => {
|
|
30846
30914
|
setForwardedRef(forwardedRef, focusableHandleRef.current);
|
|
30847
30915
|
}, [forwardedRef]);
|
|
30916
|
+
|
|
30917
|
+
// If the point is being dragged and is not focused, focus the focusable handle.
|
|
30918
|
+
React.useLayoutEffect(() => {
|
|
30919
|
+
if (dragging && !focused) {
|
|
30920
|
+
// If the point is being dragged, focus the focusable handle so that
|
|
30921
|
+
// users can continue to interact with the point using the keyboard or buttons.
|
|
30922
|
+
// This particular focus call ensures that the focus ring and hairlines are visible.
|
|
30923
|
+
focusableHandleRef.current?.focus();
|
|
30924
|
+
}
|
|
30925
|
+
}, [dragging, focused]);
|
|
30848
30926
|
const focusableHandle = /*#__PURE__*/React__namespace.createElement("g", {
|
|
30849
30927
|
"data-testid": "movable-point__focusable-handle",
|
|
30850
30928
|
className: "movable-point__focusable-handle",
|
|
@@ -30905,7 +30983,6 @@ const MovableLine = props => {
|
|
|
30905
30983
|
points: [start, end],
|
|
30906
30984
|
ariaLabels,
|
|
30907
30985
|
ariaDescribedBy,
|
|
30908
|
-
color,
|
|
30909
30986
|
extend,
|
|
30910
30987
|
onMoveLine = () => {},
|
|
30911
30988
|
onMovePoint = () => {}
|
|
@@ -30940,7 +31017,6 @@ const MovableLine = props => {
|
|
|
30940
31017
|
ariaLive: ariaLives[0],
|
|
30941
31018
|
point: start,
|
|
30942
31019
|
sequenceNumber: 1,
|
|
30943
|
-
color,
|
|
30944
31020
|
onMove: p => {
|
|
30945
31021
|
setAriaLives(["polite", "off", "off"]);
|
|
30946
31022
|
onMovePoint(0, p);
|
|
@@ -30956,7 +31032,6 @@ const MovableLine = props => {
|
|
|
30956
31032
|
ariaLive: ariaLives[1],
|
|
30957
31033
|
point: end,
|
|
30958
31034
|
sequenceNumber: 2,
|
|
30959
|
-
color,
|
|
30960
31035
|
onMove: p => {
|
|
30961
31036
|
setAriaLives(["off", "polite", "off"]);
|
|
30962
31037
|
onMovePoint(1, p);
|
|
@@ -30969,7 +31044,6 @@ const MovableLine = props => {
|
|
|
30969
31044
|
ariaLive: ariaLives[2],
|
|
30970
31045
|
start: start,
|
|
30971
31046
|
end: end,
|
|
30972
|
-
stroke: color,
|
|
30973
31047
|
extend: extend,
|
|
30974
31048
|
onMove: delta => {
|
|
30975
31049
|
setAriaLives(["off", "off", "polite"]);
|
|
@@ -30978,7 +31052,6 @@ const MovableLine = props => {
|
|
|
30978
31052
|
});
|
|
30979
31053
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, focusableHandle1, line, focusableHandle2, visiblePoint1, visiblePoint2);
|
|
30980
31054
|
};
|
|
30981
|
-
const defaultStroke = "var(--movable-line-stroke-color)";
|
|
30982
31055
|
const Line$1 = props => {
|
|
30983
31056
|
const {
|
|
30984
31057
|
start,
|
|
@@ -30987,7 +31060,6 @@ const Line$1 = props => {
|
|
|
30987
31060
|
ariaDescribedBy,
|
|
30988
31061
|
ariaLive,
|
|
30989
31062
|
extend,
|
|
30990
|
-
stroke = defaultStroke,
|
|
30991
31063
|
onMove
|
|
30992
31064
|
} = props;
|
|
30993
31065
|
const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
|
|
@@ -30995,7 +31067,8 @@ const Line$1 = props => {
|
|
|
30995
31067
|
range,
|
|
30996
31068
|
graphDimensionsInPixels,
|
|
30997
31069
|
snapStep,
|
|
30998
|
-
disableKeyboardInteraction
|
|
31070
|
+
disableKeyboardInteraction,
|
|
31071
|
+
interactiveColor
|
|
30999
31072
|
} = useGraphConfig();
|
|
31000
31073
|
let startExtend = undefined;
|
|
31001
31074
|
let endExtend = undefined;
|
|
@@ -31052,7 +31125,7 @@ const Line$1 = props => {
|
|
|
31052
31125
|
start: startPtPx,
|
|
31053
31126
|
end: endPtPx,
|
|
31054
31127
|
style: {
|
|
31055
|
-
stroke,
|
|
31128
|
+
stroke: interactiveColor,
|
|
31056
31129
|
strokeWidth: "var(--movable-line-stroke-weight)"
|
|
31057
31130
|
},
|
|
31058
31131
|
className: dragging ? "movable-dragging" : "",
|
|
@@ -31060,11 +31133,11 @@ const Line$1 = props => {
|
|
|
31060
31133
|
})), startExtend && /*#__PURE__*/React__namespace.createElement(Vector, {
|
|
31061
31134
|
tail: start,
|
|
31062
31135
|
tip: startExtend,
|
|
31063
|
-
|
|
31136
|
+
testId: "movable-line__vector"
|
|
31064
31137
|
}), endExtend && /*#__PURE__*/React__namespace.createElement(Vector, {
|
|
31065
31138
|
tail: end,
|
|
31066
31139
|
tip: endExtend,
|
|
31067
|
-
|
|
31140
|
+
testId: "movable-line__vector"
|
|
31068
31141
|
}));
|
|
31069
31142
|
};
|
|
31070
31143
|
const getMovableLineKeyboardConstraint = (line, snapStep, pointIndex) => {
|
|
@@ -31137,7 +31210,8 @@ function AngleGraph(props) {
|
|
|
31137
31210
|
graphState
|
|
31138
31211
|
} = props;
|
|
31139
31212
|
const {
|
|
31140
|
-
graphDimensionsInPixels
|
|
31213
|
+
graphDimensionsInPixels,
|
|
31214
|
+
interactiveColor
|
|
31141
31215
|
} = useGraphConfig();
|
|
31142
31216
|
const i18n = usePerseusI18n();
|
|
31143
31217
|
const id = React__namespace.useId();
|
|
@@ -31169,13 +31243,14 @@ function AngleGraph(props) {
|
|
|
31169
31243
|
start: startPtPx,
|
|
31170
31244
|
end: endPtPx,
|
|
31171
31245
|
style: {
|
|
31172
|
-
stroke:
|
|
31246
|
+
stroke: interactiveColor,
|
|
31173
31247
|
strokeWidth: "var(--movable-line-stroke-weight)"
|
|
31174
|
-
}
|
|
31248
|
+
},
|
|
31249
|
+
testId: "angle-graph__line"
|
|
31175
31250
|
}), /*#__PURE__*/React__namespace.createElement(Vector, {
|
|
31176
31251
|
tail: angleLines[i][1],
|
|
31177
31252
|
tip: endExtend,
|
|
31178
|
-
|
|
31253
|
+
testId: "angle-graph__vector"
|
|
31179
31254
|
}));
|
|
31180
31255
|
});
|
|
31181
31256
|
|
|
@@ -31532,7 +31607,8 @@ function MovableCircle(props) {
|
|
|
31532
31607
|
} = props;
|
|
31533
31608
|
const {
|
|
31534
31609
|
snapStep,
|
|
31535
|
-
disableKeyboardInteraction
|
|
31610
|
+
disableKeyboardInteraction,
|
|
31611
|
+
interactiveColor
|
|
31536
31612
|
} = useGraphConfig();
|
|
31537
31613
|
const [focused, setFocused] = React__namespace.useState(false);
|
|
31538
31614
|
const draggableRef = React.useRef(null);
|
|
@@ -31568,7 +31644,9 @@ function MovableCircle(props) {
|
|
|
31568
31644
|
cx: centerPx[X],
|
|
31569
31645
|
cy: centerPx[Y],
|
|
31570
31646
|
rx: radiiPx[X],
|
|
31571
|
-
ry: radiiPx[Y]
|
|
31647
|
+
ry: radiiPx[Y],
|
|
31648
|
+
stroke: interactiveColor,
|
|
31649
|
+
"data-testid": "movable-circle__circle"
|
|
31572
31650
|
}), /*#__PURE__*/React__namespace.createElement(DragHandle, {
|
|
31573
31651
|
center: center,
|
|
31574
31652
|
dragging: dragging,
|
|
@@ -31585,7 +31663,8 @@ function DragHandle(props) {
|
|
|
31585
31663
|
} = props;
|
|
31586
31664
|
const [centerPx] = useTransformVectorsToPixels(center);
|
|
31587
31665
|
const {
|
|
31588
|
-
markings
|
|
31666
|
+
markings,
|
|
31667
|
+
interactiveColor
|
|
31589
31668
|
} = useGraphConfig();
|
|
31590
31669
|
const cornerRadius = Math.min(...dragHandleDimensions) / 2;
|
|
31591
31670
|
const topLeft = mafs.vec.sub(centerPx, mafs.vec.scale(dragHandleDimensions, 0.5));
|
|
@@ -31599,7 +31678,9 @@ function DragHandle(props) {
|
|
|
31599
31678
|
width: dragHandleDimensions[X],
|
|
31600
31679
|
height: dragHandleDimensions[Y],
|
|
31601
31680
|
rx: cornerRadius,
|
|
31602
|
-
ry: cornerRadius
|
|
31681
|
+
ry: cornerRadius,
|
|
31682
|
+
fill: interactiveColor,
|
|
31683
|
+
"data-testid": "movable-circle__handle"
|
|
31603
31684
|
}), dragHandleDotPositions.map(offsetPx => {
|
|
31604
31685
|
const [xPx, yPx] = mafs.vec.add(offsetPx, centerPx);
|
|
31605
31686
|
return /*#__PURE__*/React__namespace.createElement("circle", {
|
|
@@ -31758,8 +31839,7 @@ const LinearGraph = (props, key) => {
|
|
|
31758
31839
|
start: true,
|
|
31759
31840
|
end: true
|
|
31760
31841
|
},
|
|
31761
|
-
onMovePoint: (endpointIndex, destination) => dispatch(actions.linear.movePoint(endpointIndex, destination))
|
|
31762
|
-
color: "var(--movable-line-stroke-color)"
|
|
31842
|
+
onMovePoint: (endpointIndex, destination) => dispatch(actions.linear.movePoint(endpointIndex, destination))
|
|
31763
31843
|
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
31764
31844
|
id: pointsDescriptionId,
|
|
31765
31845
|
style: a11y.srOnly
|
|
@@ -31901,8 +31981,7 @@ const LinearSystemGraph = props => {
|
|
|
31901
31981
|
start: true,
|
|
31902
31982
|
end: true
|
|
31903
31983
|
},
|
|
31904
|
-
onMovePoint: (endpointIndex, destination) => dispatch(actions.linearSystem.movePointInFigure(i, endpointIndex, destination))
|
|
31905
|
-
color: "var(--movable-line-stroke-color)"
|
|
31984
|
+
onMovePoint: (endpointIndex, destination) => dispatch(actions.linearSystem.movePointInFigure(i, endpointIndex, destination))
|
|
31906
31985
|
})), linesAriaInfo.map(_ref2 => {
|
|
31907
31986
|
let {
|
|
31908
31987
|
pointsDescriptionId,
|
|
@@ -32748,6 +32827,7 @@ function doMovePoint(state, action) {
|
|
|
32748
32827
|
return {
|
|
32749
32828
|
...state,
|
|
32750
32829
|
hasBeenInteractedWith: true,
|
|
32830
|
+
focusedPointIndex: action.index,
|
|
32751
32831
|
coords: setAtIndex({
|
|
32752
32832
|
array: state.coords,
|
|
32753
32833
|
index: action.index,
|
|
@@ -33083,7 +33163,9 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
|
|
|
33083
33163
|
coords
|
|
33084
33164
|
} = _ref6;
|
|
33085
33165
|
const startingPoint = coords[index];
|
|
33086
|
-
|
|
33166
|
+
return calculateAngleSnap(destinationPoint, range, coords, index, startingPoint);
|
|
33167
|
+
}
|
|
33168
|
+
function calculateAngleSnap(destinationPoint, range, coords, index, startingPoint) {
|
|
33087
33169
|
// Needed to prevent updating the original coords before the checks for
|
|
33088
33170
|
// degenerate triangles and overlapping sides
|
|
33089
33171
|
const coordsCopy = [...coords];
|
|
@@ -33375,7 +33457,8 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
33375
33457
|
snapStep
|
|
33376
33458
|
} = statefulProps.graphState;
|
|
33377
33459
|
const {
|
|
33378
|
-
disableKeyboardInteraction
|
|
33460
|
+
disableKeyboardInteraction,
|
|
33461
|
+
interactiveColor
|
|
33379
33462
|
} = graphConfig;
|
|
33380
33463
|
const {
|
|
33381
33464
|
strings,
|
|
@@ -33411,12 +33494,16 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
33411
33494
|
"aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
|
|
33412
33495
|
}, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
33413
33496
|
points: [...points],
|
|
33414
|
-
color:
|
|
33497
|
+
color: interactiveColor,
|
|
33415
33498
|
svgPolygonProps: {
|
|
33416
33499
|
strokeWidth: focusVisible ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
|
|
33417
33500
|
style: {
|
|
33418
33501
|
fill: "transparent"
|
|
33419
|
-
}
|
|
33502
|
+
},
|
|
33503
|
+
// Use aria-hidden to hide the line from screen readers
|
|
33504
|
+
// so it doesn't read as "image" with no context.
|
|
33505
|
+
// This is okay because the graph has its own aria-label.
|
|
33506
|
+
"aria-hidden": true
|
|
33420
33507
|
}
|
|
33421
33508
|
}), points.map((point, i) => {
|
|
33422
33509
|
const pt1 = points.at(i - 1);
|
|
@@ -33557,6 +33644,9 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
33557
33644
|
strings,
|
|
33558
33645
|
locale
|
|
33559
33646
|
} = usePerseusI18n();
|
|
33647
|
+
const {
|
|
33648
|
+
interactiveColor
|
|
33649
|
+
} = useGraphConfig();
|
|
33560
33650
|
const id = React__namespace.useId();
|
|
33561
33651
|
const polygonPointsNumId = id + "-points-num";
|
|
33562
33652
|
const polygonPointsId = id + "-points";
|
|
@@ -33597,14 +33687,22 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
33597
33687
|
"aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
|
|
33598
33688
|
}, /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
|
|
33599
33689
|
points: [...points],
|
|
33600
|
-
color:
|
|
33690
|
+
color: interactiveColor,
|
|
33601
33691
|
svgPolylineProps: {
|
|
33602
33692
|
strokeWidth: "var(--movable-line-stroke-weight)",
|
|
33603
33693
|
style: {
|
|
33604
33694
|
fill: "transparent"
|
|
33605
|
-
}
|
|
33695
|
+
},
|
|
33696
|
+
// Use aria-hidden to hide the line from screen readers
|
|
33697
|
+
// so it doesn't read as "image" with no context.
|
|
33698
|
+
// This is okay because the graph has its own aria-label.
|
|
33699
|
+
"aria-hidden": true
|
|
33606
33700
|
}
|
|
33607
33701
|
}), /*#__PURE__*/React__namespace.createElement("rect", {
|
|
33702
|
+
// Use aria-hidden to hide the line from screen readers
|
|
33703
|
+
// so it doesn't read as "image" with no context.
|
|
33704
|
+
// This is okay because the graph has its own aria-label.
|
|
33705
|
+
"aria-hidden": true,
|
|
33608
33706
|
style: {
|
|
33609
33707
|
fill: "rgba(0,0,0,0)",
|
|
33610
33708
|
cursor: "crosshair"
|
|
@@ -33758,7 +33856,7 @@ function getKeyboardMovementConstraintForPoint(points, index, range, snapStep, s
|
|
|
33758
33856
|
case "sides":
|
|
33759
33857
|
return getSideSnapConstraint(points, index, range);
|
|
33760
33858
|
case "angles":
|
|
33761
|
-
return
|
|
33859
|
+
return getAngleSnapConstraint(points, index, range);
|
|
33762
33860
|
default:
|
|
33763
33861
|
throw new wonderStuffCore.UnreachableCaseError(snapTo);
|
|
33764
33862
|
}
|
|
@@ -33812,6 +33910,49 @@ function getSideSnapConstraint(points, index, range) {
|
|
|
33812
33910
|
right: movePointWithConstraint(coord => mafs.vec.add(coord, [1, 0]))
|
|
33813
33911
|
};
|
|
33814
33912
|
}
|
|
33913
|
+
function getAngleSnapConstraint(points, index, range) {
|
|
33914
|
+
// Make newPoints mutable.
|
|
33915
|
+
const newPoints = [...points];
|
|
33916
|
+
|
|
33917
|
+
// Get the point that is being moved.
|
|
33918
|
+
const pointToBeMoved = newPoints[index];
|
|
33919
|
+
|
|
33920
|
+
// Create a helper function that moves the point to a valid location
|
|
33921
|
+
// for angle snapping.
|
|
33922
|
+
const movePointWithConstraint = moveFunc => {
|
|
33923
|
+
// The direction the user is attempting to move the point in.
|
|
33924
|
+
let destinationAttempt = bound$1({
|
|
33925
|
+
snapStep: [0, 0],
|
|
33926
|
+
range,
|
|
33927
|
+
point: moveFunc(pointToBeMoved)
|
|
33928
|
+
});
|
|
33929
|
+
// The new point we're moving to.
|
|
33930
|
+
let newPoint = pointToBeMoved;
|
|
33931
|
+
|
|
33932
|
+
// Move the point and keep trying until we are at the boarder
|
|
33933
|
+
// of the graph.
|
|
33934
|
+
while (newPoint[0] === pointToBeMoved[0] && newPoint[1] === pointToBeMoved[1] && isInBound({
|
|
33935
|
+
range,
|
|
33936
|
+
point: destinationAttempt
|
|
33937
|
+
})) {
|
|
33938
|
+
newPoint = calculateAngleSnap(destinationAttempt, range, newPoints, index, pointToBeMoved);
|
|
33939
|
+
|
|
33940
|
+
// Increment the destinationAttempt.
|
|
33941
|
+
// For every time it does not work increment the direction for x and y.
|
|
33942
|
+
destinationAttempt = moveFunc(destinationAttempt);
|
|
33943
|
+
}
|
|
33944
|
+
return newPoint;
|
|
33945
|
+
};
|
|
33946
|
+
|
|
33947
|
+
// For each direction look for the next movable point by a small step to increase changes
|
|
33948
|
+
// of finding the next angle value.
|
|
33949
|
+
return {
|
|
33950
|
+
up: movePointWithConstraint(coord => mafs.vec.add(coord, [0, 0.1])),
|
|
33951
|
+
down: movePointWithConstraint(coord => mafs.vec.sub(coord, [0, 0.1])),
|
|
33952
|
+
left: movePointWithConstraint(coord => mafs.vec.sub(coord, [0.1, 0])),
|
|
33953
|
+
right: movePointWithConstraint(coord => mafs.vec.add(coord, [0.1, 0]))
|
|
33954
|
+
};
|
|
33955
|
+
}
|
|
33815
33956
|
|
|
33816
33957
|
function renderQuadraticGraph(state, dispatch, i18n) {
|
|
33817
33958
|
return {
|
|
@@ -33831,6 +33972,9 @@ function QuadraticGraph(props) {
|
|
|
33831
33972
|
coords,
|
|
33832
33973
|
snapStep
|
|
33833
33974
|
} = graphState;
|
|
33975
|
+
const {
|
|
33976
|
+
interactiveColor
|
|
33977
|
+
} = useGraphConfig();
|
|
33834
33978
|
const {
|
|
33835
33979
|
strings,
|
|
33836
33980
|
locale
|
|
@@ -33872,7 +34016,13 @@ function QuadraticGraph(props) {
|
|
|
33872
34016
|
"aria-describedby": `${quadraticDirectionId} ${quadraticVertexId} ${quadraticInterceptsId}`
|
|
33873
34017
|
}, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
|
|
33874
34018
|
y: y,
|
|
33875
|
-
color:
|
|
34019
|
+
color: interactiveColor,
|
|
34020
|
+
svgPathProps: {
|
|
34021
|
+
// Use aria-hidden to hide the line from screen readers
|
|
34022
|
+
// so it doesn't read as "image" with no context.
|
|
34023
|
+
// This is okay because the graph has its own aria-label.
|
|
34024
|
+
"aria-hidden": true
|
|
34025
|
+
}
|
|
33876
34026
|
}), coords.map((coord, i) => {
|
|
33877
34027
|
const srQuadraticPoint = getQuadraticPointString(i + 1, coord, strings, locale);
|
|
33878
34028
|
// Vertex might not exist if the quadratic graph is a line
|
|
@@ -34272,6 +34422,9 @@ function SinusoidGraph(props) {
|
|
|
34272
34422
|
dispatch,
|
|
34273
34423
|
graphState
|
|
34274
34424
|
} = props;
|
|
34425
|
+
const {
|
|
34426
|
+
interactiveColor
|
|
34427
|
+
} = useGraphConfig();
|
|
34275
34428
|
const i18n = usePerseusI18n();
|
|
34276
34429
|
const id = React__namespace.useId();
|
|
34277
34430
|
const descriptionId = id + "-description";
|
|
@@ -34314,7 +34467,13 @@ function SinusoidGraph(props) {
|
|
|
34314
34467
|
"aria-describedby": descriptionId
|
|
34315
34468
|
}, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
|
|
34316
34469
|
y: x => computeSine(x, coeffRef.current),
|
|
34317
|
-
color:
|
|
34470
|
+
color: interactiveColor,
|
|
34471
|
+
svgPathProps: {
|
|
34472
|
+
// Use aria-hidden to hide the line from screen readers
|
|
34473
|
+
// so it doesn't read as "image" with no context.
|
|
34474
|
+
// This is okay because the graph has its own aria-label.
|
|
34475
|
+
"aria-hidden": true
|
|
34476
|
+
}
|
|
34318
34477
|
}), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34319
34478
|
ariaLabel: i === 0 ? srSinusoidRootPoint : srSinusoidPeakPoint,
|
|
34320
34479
|
key: "point-" + i,
|
|
@@ -34663,6 +34822,7 @@ const MafsGraph = props => {
|
|
|
34663
34822
|
i18n,
|
|
34664
34823
|
markings: props.markings
|
|
34665
34824
|
});
|
|
34825
|
+
const disableInteraction = readOnly || !!props.static;
|
|
34666
34826
|
return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
|
|
34667
34827
|
value: {
|
|
34668
34828
|
range: state.range,
|
|
@@ -34675,7 +34835,12 @@ const MafsGraph = props => {
|
|
|
34675
34835
|
width,
|
|
34676
34836
|
height,
|
|
34677
34837
|
labels,
|
|
34678
|
-
disableKeyboardInteraction:
|
|
34838
|
+
disableKeyboardInteraction: disableInteraction,
|
|
34839
|
+
// If the graph is read-only or static, we want to make it
|
|
34840
|
+
// visually clear that the graph is no longer interactive.
|
|
34841
|
+
// We do this by changing the color of the interactive elements
|
|
34842
|
+
// to a static gray color rather than our standard blue.
|
|
34843
|
+
interactiveColor: disableInteraction ? "var(--static-gray)" : "var(--mafs-blue)"
|
|
34679
34844
|
}
|
|
34680
34845
|
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
34681
34846
|
className: "mafs-graph-container"
|
|
@@ -35701,7 +35866,8 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
35701
35866
|
const staticTransform$3 = ___default["default"].identity;
|
|
35702
35867
|
var InteractiveGraph$1 = {
|
|
35703
35868
|
name: "interactive-graph",
|
|
35704
|
-
displayName: "Interactive graph
|
|
35869
|
+
displayName: "Interactive graph",
|
|
35870
|
+
accessible: true,
|
|
35705
35871
|
widget: InteractiveGraph,
|
|
35706
35872
|
staticTransform: staticTransform$3
|
|
35707
35873
|
};
|
|
@@ -41796,7 +41962,7 @@ const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
|
|
|
41796
41962
|
|
|
41797
41963
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
41798
41964
|
const libName = "@khanacademy/perseus";
|
|
41799
|
-
const libVersion = "56.
|
|
41965
|
+
const libVersion = "56.1.0";
|
|
41800
41966
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
41801
41967
|
|
|
41802
41968
|
const apiVersion = {
|