-
Notifications
You must be signed in to change notification settings - Fork 351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Interactive Graph] Polygon side snapping is keyboard accessible. #2270
base: main
Are you sure you want to change the base?
Conversation
…and mouse behavior.
… we keep it whole numbers.
Size Change: +1.34 kB (+0.15%) Total Size: 874 kB
ℹ️ View Unchanged
|
npm Snapshot: PublishedGood news!! We've packaged up the latest commit from this PR (5bdf5dd) and published it to npm. You Example: pnpm add @khanacademy/perseus@PR2270 If you are working in Khan Academy's webapp, you can run: ./dev/tools/bump_perseus_version.sh -t PR2270 |
…olygons to be keyboard accessible.
const constrain = ["angles", "sides"].includes(snapTo) | ||
? (p) => p | ||
: (p) => snap(snapStep, p); | ||
const constrain: KeyboardMovementConstraint = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Can this be a function named getConstraintFn
that includes a switch statement for the snapTo type? I think that might be easier to follow for engineers looking at this in the future.
Something like
function getConstraintFn(): KeyboardMovementConstraint {
switch(snapTo) {
case "angles":
return p => p
case "sides":
return getSideSnapConstraint(...)
default: // "grid"
return p => snap(snapStep, p);
}
}
And then you can pass just this function into constrain
below.
@@ -683,3 +689,60 @@ function describePolygonGraph( | |||
srPolygonInteractiveElements, | |||
}; | |||
} | |||
|
|||
export function getSideSnapConstraint( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a test for this?
@@ -66,6 +66,22 @@ export function bound({ | |||
return clampToBox(boundingBox, point); | |||
} | |||
|
|||
// Returns true if the point is within the range of the graph. | |||
export function isInBound({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a test for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good overall! Thanks for this huge accessibility improvement!
Requesting changes because coords
is being mutated in the reducer; we should make a copy instead. I left a few other suggestions inline too.
// Takes the destination point and makes sure it is within the bounds of the graph | ||
// SnapStep is [0, 0] because we don't want to snap to the grid | ||
coordsCopy[index] = bound({ | ||
coords[index] = bound({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mutates the input coords
array, which is likely to cause problems at some point because React assumes immutability. We should use setAtIndex
to immutably transform the array.
const targetCoords = setAtIndex({
array: coords,
index,
newValue: bound({point: destinationPoint, range, snapStep: [0, 0]}),
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I adjusted the implementation to have the components/functions that call this one to pass in a coordsCopy
, so there's no issue about mutating the shared state. Though as I'm looking at it this might cause issues down the line, I'll adjust this to make sure the function make's it's own copy so other functions just need to pass in the coord state. 😉
return ( | ||
point[0] >= range[0][0] && | ||
point[0] <= range[0][1] && | ||
point[1] >= range[1][0] && | ||
point[1] <= range[1][1] | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW we have constants in interactive-graphs/math/interval.ts
and interactive-graphs/math/coordinates.ts
that could potentially make this easier to read:
return (
point[X] >= range[X][MIN] &&
point[X] <= range[X][MAX] &&
point[Y] >= range[Y][MIN] &&
point[Y] <= range[Y][MAX]
);
Though I don't think we use these constants in many places, so maybe they're more surprising than helpful.
It also might be worth moving this function to interactive-graphs/math/box.ts
since that is the module that knows all about ranges and points.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds good! One question I have is if we have a function/utility that already check this? I know we have the bound
function that returns a point within the bounds of our graph, but I could not find a function that returns true or false if a point is outside the graph. 🤔
? (p) => p | ||
: (p) => snap(snapStep, p); | ||
const constrain: KeyboardMovementConstraint = | ||
snapTo === "angles" ? (p) => p : (p) => snap(snapStep, p); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use a switch statement to get the constrain
value? Currently, the logic is split between two ternaries that are a couple hundred lines apart, and I think that will confuse future readers.
function getKeyboardMovementConstraintForPoint(
pointIndex: number,
): KeyboardMovementConstraint {
switch (snapTo) {
case "grid":
return (p) => snap(snapStep, p);
case "angles":
return (p) => p;
case "sides":
return getSideSnapConstraint(points, pointIndex, graphConfig.range);
default:
throw new UnreachableCaseError(snapTo);
}
}
Complication: it looks like the constrain
on line 102 is used in the useDraggable
below, which I think is for moving the whole polygon. So we'd also need:
function getConstrainFunctionForWholePolygon(): (point: vec.Vector2) => vec.Vector2 {
return snapTo === "angles" ? (p) => p : (p) => snap(snapStep, p);
}
@@ -66,6 +66,22 @@ export function bound({ | |||
return clampToBox(boundingBox, point); | |||
} | |||
|
|||
// Returns true if the point is within the range of the graph. | |||
export function isInBound({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there another function that currently exists to check if a point is in bounds? I know we have the function bound
which moves a point to be in bounds, but it doesn't seem like we have one as a simple boolean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really want these test examples to be on graph markings. It makes it easier to debug and troubleshoot. ❤️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why this file changed. I'm going to take a look and make sure this is correct.
// Takes the destination point and makes sure it is within the bounds of the graph | ||
// SnapStep is [0, 0] because we don't want to snap to the grid | ||
coordsCopy[index] = bound({ | ||
coords[index] = bound({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I adjusted the implementation to have the components/functions that call this one to pass in a coordsCopy
, so there's no issue about mutating the shared state. Though as I'm looking at it this might cause issues down the line, I'll adjust this to make sure the function make's it's own copy so other functions just need to pass in the coord state. 😉
return ( | ||
point[0] >= range[0][0] && | ||
point[0] <= range[0][1] && | ||
point[1] >= range[1][0] && | ||
point[1] <= range[1][1] | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds good! One question I have is if we have a function/utility that already check this? I know we have the bound
function that returns a point within the bounds of our graph, but I could not find a function that returns true or false if a point is outside the graph. 🤔
Summary:
Updating Polygon interactive graph implementation to have keyboard accessible side snapping. This is done by hooking up the side snapping behavior into a constraint function to make to keyboard arrow keys.
Issue: LEMS-2243
Test plan: