Skip to content

Commit

Permalink
Focus movable points when you click on them
Browse files Browse the repository at this point in the history
  • Loading branch information
benchristel committed Aug 28, 2024
1 parent 40d0b67 commit a741067
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ type Props = {
dragging: boolean;
focusBehavior: FocusBehaviorConfig;
cursor?: CSSCursor | undefined;
onClick?: () => unknown;
};

type FocusBehaviorConfig =
| {type: "uncontrolled"; tabIndex: number}
| {type: "controlled"; showFocusRing: boolean};
type FocusBehaviorConfig = ControlledFocusBehavior | UncontrolledFocusBehavior;

type ControlledFocusBehavior = {type: "controlled"; showFocusRing: boolean};
type UncontrolledFocusBehavior = {
type: "uncontrolled";
tabIndex: number;
onFocusChange: (isFocused: boolean) => unknown;
};

// The hitbox size of 48px by 48px is preserved from the legacy interactive
// graph.
Expand All @@ -46,6 +52,7 @@ export const MovablePointView = forwardRef(
dragging,
focusBehavior,
cursor,
onClick = () => {},
} = props;

// WB Tooltip requires a color name for the background color.
Expand Down Expand Up @@ -94,6 +101,11 @@ export const MovablePointView = forwardRef(
tabIndex={
disableKeyboardInteraction ? -1 : tabIndex(focusBehavior)
}
onFocus={() => {
return getOnFocusChangeCallback(focusBehavior)(true);
}}
onBlur={() => getOnFocusChangeCallback(focusBehavior)(false)}
onClick={onClick}
>
<circle
className="movable-point-hitbox"
Expand Down Expand Up @@ -148,3 +160,10 @@ function tabIndex(config: FocusBehaviorConfig) {
}
return undefined;
}

function getOnFocusChangeCallback(config: FocusBehaviorConfig) {
if (config.type === "uncontrolled") {
return config.onFocusChange;
}
return () => {};
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import Tooltip from "@khanacademy/wonder-blocks-tooltip";
import {render} from "@testing-library/react";
import {render, screen} from "@testing-library/react";
import {
type UserEvent,
userEvent as userEventLib,
} from "@testing-library/user-event";
import {Mafs} from "mafs";
import React from "react";

Expand Down Expand Up @@ -44,11 +48,15 @@ describe("MovablePoint", () => {
labels: [],
};

let userEvent: UserEvent;
beforeEach(() => {
useGraphConfigMock = jest.spyOn(ReducerGraphConfig, "default");
useDraggableMock = jest
.spyOn(UseDraggableModule, "useDraggable")
.mockReturnValue({dragging: false});
userEvent = userEventLib.setup({
advanceTimers: jest.advanceTimersByTime,
});
});

describe("Tooltip", () => {
Expand Down Expand Up @@ -185,4 +193,52 @@ describe("MovablePoint", () => {
expect(svgGroups).toHaveLength(1);
});
});

it("calls onFocusChange(true) when you tab to it", async () => {
const focusChangeSpy = jest.fn();
render(
<Mafs width={200} height={200}>
<MovablePoint point={[0, 0]} onFocusChange={focusChangeSpy} />,
</Mafs>,
);

expect(focusChangeSpy).not.toHaveBeenCalled();

await userEvent.tab(); // tab to the graph
await userEvent.tab(); // tab to the point

expect(focusChangeSpy.mock.calls).toEqual([[true]]);
});

it("calls onFocusChange(false) when you tab away from it", async () => {
const focusChangeSpy = jest.fn();
render(
<Mafs width={200} height={200}>
<MovablePoint point={[0, 0]} onFocusChange={focusChangeSpy} />,
</Mafs>,
);

expect(focusChangeSpy).not.toHaveBeenCalled();

await userEvent.tab(); // tab to the graph
await userEvent.tab(); // tab to the point
await userEvent.tab(); // tab away

expect(focusChangeSpy.mock.calls).toEqual([[true], [false]]);
});

it("calls onFocusChange(true) when you click it", async () => {
const focusChangeSpy = jest.fn();
render(
<Mafs width={200} height={200}>
<MovablePoint point={[0, 0]} onFocusChange={focusChangeSpy} />,
</Mafs>,
);

expect(focusChangeSpy).not.toHaveBeenCalled();

await userEvent.click(screen.getByTestId("movable-point"));

expect(focusChangeSpy.mock.calls).toEqual([[true]]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ import type {vec} from "mafs";

type Props = {
point: vec.Vector2;
onMove: (newPoint: vec.Vector2) => unknown;
onMove?: (newPoint: vec.Vector2) => unknown;
color?: string;
cursor?: CSSCursor | undefined;
constrain?: KeyboardMovementConstraint;
onFocusChange?: (isFocused: boolean) => unknown;
};

export const MovablePoint = (props: Props) => {
const {snapStep} = useGraphConfig();
const elementRef = useRef<SVGGElement>(null);
const {
point,
onMove,
onMove = () => {},
onFocusChange = () => {},
cursor,
color = WBColor.blue,
constrain = (p) => snap(snapStep, p),
Expand All @@ -43,7 +45,8 @@ export const MovablePoint = (props: Props) => {
point={point}
color={color}
dragging={dragging}
focusBehavior={{type: "uncontrolled", tabIndex: 0}}
focusBehavior={{type: "uncontrolled", tabIndex: 0, onFocusChange}}
onClick={() => elementRef.current?.focus()}
cursor={cursor}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
}

.MafsView
.movable-point:is(:focus-visible, .movable-point--focus)
.movable-point:is(:focus, .movable-point--focus)
.movable-point-focus-outline {
visibility: visible;
}
Expand Down

0 comments on commit a741067

Please sign in to comment.