mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-13 23:35:51 +00:00
Keep keyboard shortcuts within Element Call's root
The call's shortcuts were listened for on the window and allowed whenever focus was inside the standalone app's `#root` — which, for a component embedded in a host, is the host's own root, or nothing. So m, v and space fired while the user typed in the host's composer, and two Element Calls on one page both answered every key. Listen on the element Element Call treats as its root instead (the body standalone, so nothing changes there), and judge whether a key press is spoken for by what has focus — a dialog or a text field — rather than by where it sits in the DOM, since the modals are now portalled to that same root. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
608f107f8d
commit
bc58aed0d7
@@ -18,6 +18,7 @@ import {
|
||||
ReactionsRowSize,
|
||||
} from "./reactions";
|
||||
import { type Controls } from "./controls";
|
||||
import { RootElementProvider } from "./RootElementContext";
|
||||
|
||||
// Test Explanation:
|
||||
// - The main objective is to test `useCallViewKeyboardShortcuts`.
|
||||
@@ -48,10 +49,11 @@ const TestComponent: FC<TestComponentProps> = ({
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div id={initialModalOpen ? "root" : undefined}>
|
||||
<div>
|
||||
<Button onClick={onButtonClick}>TEST</Button>
|
||||
</div>
|
||||
{/*// modal lives outside of the root*/}
|
||||
{/* A dialog, which is what claims key presses for itself; where it
|
||||
lives in the DOM does not matter */}
|
||||
{modalOpen && (
|
||||
<dialog
|
||||
open
|
||||
@@ -165,7 +167,7 @@ test("unmuting happens in place of the default action", async () => {
|
||||
// container element that can be interactive and receive focus / keydown
|
||||
// events. <video> is kind of a weird choice, but it'll do the job.
|
||||
render(
|
||||
<div id="root">
|
||||
<div>
|
||||
<video
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => defaultPrevented(e.isDefaultPrevented())}
|
||||
@@ -210,3 +212,58 @@ test("escape button does not trigger back if sth else is focused", async () => {
|
||||
await user.keyboard("[Escape]");
|
||||
expect(window.controls.onBackButtonPressed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("typing in a text field is not a shortcut", async () => {
|
||||
const user = userEvent.setup();
|
||||
const toggleHandRaised = vi.fn();
|
||||
const { getByRole } = render(
|
||||
<>
|
||||
<input type="text" aria-label="Name" />
|
||||
<TestComponent toggleHandRaised={toggleHandRaised} />
|
||||
</>,
|
||||
);
|
||||
|
||||
getByRole("textbox", { name: "Name" }).focus();
|
||||
await user.keyboard("h");
|
||||
expect(toggleHandRaised).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
/**
|
||||
* Element Call embedded in a host's page: the root is a container, with the
|
||||
* rest of the page around it.
|
||||
*/
|
||||
const Embedded: FC<{ toggleHandRaised: () => void }> = ({
|
||||
toggleHandRaised,
|
||||
}) => {
|
||||
const [root, setRoot] = useState<HTMLDivElement | null>(null);
|
||||
return (
|
||||
<>
|
||||
<button>Host button</button>
|
||||
<div ref={setRoot} data-testid="root">
|
||||
{root !== null && (
|
||||
<RootElementProvider value={root}>
|
||||
<TestComponent toggleHandRaised={toggleHandRaised} />
|
||||
</RootElementProvider>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
test("only hears keys pressed within its root element", async () => {
|
||||
const user = userEvent.setup();
|
||||
const toggleHandRaised = vi.fn();
|
||||
const { getByRole } = render(
|
||||
<Embedded toggleHandRaised={toggleHandRaised} />,
|
||||
);
|
||||
|
||||
// Focus on the host's page: none of Element Call's business
|
||||
getByRole("button", { name: "Host button" }).focus();
|
||||
await user.keyboard("h");
|
||||
expect(toggleHandRaised).not.toHaveBeenCalled();
|
||||
|
||||
// Focus inside the root: a shortcut
|
||||
getByRole("button", { name: "TEST" }).focus();
|
||||
await user.keyboard("h");
|
||||
expect(toggleHandRaised).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user