Emoji Picker: Focused emoji does not move with the arrow keys (#30893)
* We should focus the node in the DOM so that the browser focus(with outline) matches the our internal RovingIndex state * Don't move focus from the input if we are in "virtual" focus(via active descendant)
This commit is contained in:
@@ -187,6 +187,11 @@ class EmojiPicker extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
if (focusNode) {
|
||||
// Only move actual DOM focus if an emoji already has focus
|
||||
// If the input has focus, keep using aria-activedescendant for virtual focus
|
||||
if (document.activeElement !== document.querySelector(".mx_EmojiPicker_search input")) {
|
||||
focusNode?.focus();
|
||||
}
|
||||
dispatch({
|
||||
type: Type.SetFocus,
|
||||
payload: { node: focusNode },
|
||||
|
||||
@@ -12,10 +12,23 @@ import userEvent from "@testing-library/user-event";
|
||||
|
||||
import EmojiPicker from "../../../../../src/components/views/emojipicker/EmojiPicker";
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
|
||||
describe("EmojiPicker", function () {
|
||||
stubClient();
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear recent emojis to prevent test pollution
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => {
|
||||
if (settingName === "recent_emoji") return [] as any;
|
||||
return jest.requireActual("../../../../../src/settings/SettingsStore").default.getValue(settingName);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should not mangle default order after filtering", async () => {
|
||||
const ref = createRef<EmojiPicker>();
|
||||
const { container } = render(
|
||||
@@ -90,4 +103,64 @@ describe("EmojiPicker", function () {
|
||||
expect(onChoose).toHaveBeenCalledWith("📫️");
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should move actual focus when navigating between emojis after Tab", async () => {
|
||||
// mock offsetParent
|
||||
Object.defineProperty(HTMLElement.prototype, "offsetParent", {
|
||||
get() {
|
||||
return this.parentNode;
|
||||
},
|
||||
});
|
||||
|
||||
const onChoose = jest.fn();
|
||||
const onFinished = jest.fn();
|
||||
const { container } = render(<EmojiPicker onChoose={onChoose} onFinished={onFinished} />);
|
||||
|
||||
const input = container.querySelector("input")!;
|
||||
expect(input).toHaveFocus();
|
||||
|
||||
// Wait for emojis to render
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('[role="gridcell"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
function getEmoji(): string {
|
||||
return document.activeElement?.textContent || "";
|
||||
}
|
||||
|
||||
function getVirtuallyFocusedEmoji(): string {
|
||||
const activeDescendant = input.getAttribute("aria-activedescendant");
|
||||
if (!activeDescendant) return "";
|
||||
return container.querySelector("#" + activeDescendant)?.textContent || "";
|
||||
}
|
||||
|
||||
// Initially, arrow keys use virtual focus (aria-activedescendant)
|
||||
// The first emoji is virtually focused by default
|
||||
expect(input).toHaveFocus();
|
||||
expect(getVirtuallyFocusedEmoji()).toEqual("😀");
|
||||
expect(getEmoji()).toEqual(""); // No actual emoji has focus
|
||||
|
||||
await userEvent.keyboard("[ArrowDown]");
|
||||
expect(input).toHaveFocus(); // Input still has focus
|
||||
expect(getVirtuallyFocusedEmoji()).toEqual("🙂"); // Virtual focus moved
|
||||
expect(getEmoji()).toEqual(""); // No actual emoji has focus
|
||||
|
||||
// Tab to move actual focus to the emoji
|
||||
await userEvent.keyboard("[Tab]");
|
||||
expect(input).not.toHaveFocus();
|
||||
expect(getEmoji()).toEqual("🙂"); // Now emoji has actual focus
|
||||
|
||||
// Arrow keys now move actual DOM focus between emojis
|
||||
await userEvent.keyboard("[ArrowDown]");
|
||||
expect(getEmoji()).toEqual("🤩"); // Actual focus moved down one row
|
||||
expect(input).not.toHaveFocus();
|
||||
|
||||
await userEvent.keyboard("[ArrowUp]");
|
||||
expect(getEmoji()).toEqual("🙂"); // Actual focus moved back up
|
||||
expect(input).not.toHaveFocus();
|
||||
|
||||
await userEvent.keyboard("[ArrowRight]");
|
||||
expect(getEmoji()).toEqual("🙃"); // Actual focus moved right
|
||||
expect(input).not.toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user