diff --git a/playwright/e2e/composer/CIDER.spec.ts b/playwright/e2e/composer/CIDER.spec.ts index af512b9c09..af6843f89a 100644 --- a/playwright/e2e/composer/CIDER.spec.ts +++ b/playwright/e2e/composer/CIDER.spec.ts @@ -92,6 +92,41 @@ test.describe("Composer", () => { }); }); + test("should have focus lock in emoji picker", async ({ page, app }) => { + const emojiButton = app.getComposer(false).getByRole("button", { name: "Emoji" }); + + // Open emoji picker by clicking the button + await emojiButton.click(); + + // Wait for emoji picker to be visible + const emojiPicker = page.getByTestId("mx_EmojiPicker"); + await expect(emojiPicker).toBeVisible(); + + // Get initial focused element (should be search input) + const searchInput = emojiPicker.getByRole("textbox", { name: "Search" }); + await expect(searchInput).toBeFocused(); + + // Try to tab multiple times - focus should stay within emoji picker + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + + // Verify we're still within the emoji picker (not back to composer) + const focusedElement = await page.evaluate(() => document.activeElement?.closest(".mx_EmojiPicker")); + expect(focusedElement).not.toBeNull(); + + // Close with Escape key + await page.keyboard.press("Escape"); + + // Verify emoji picker is closed + await expect(emojiPicker).not.toBeVisible(); + + // Verify focus returns to emoji button + await expect(emojiButton).toBeFocused(); + }); + test.describe("when Control+Enter is required to send", () => { test.beforeEach(async ({ app }) => { await app.settings.setValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true); diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index eebd432e10..03538a21bf 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -405,7 +405,7 @@ export default class ContextMenu extends React.PureComponent{body}; + body = {body}; } // filter props that are invalid for DOM elements diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 8d6f6cc6eb..39139be0db 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -735,7 +735,7 @@ export default class MessageContextMenu extends React.Component if (this.state.reactionPickerDisplayed) { const buttonRect = (this.reactButtonRef.current as HTMLElement)?.getBoundingClientRect(); reactionPicker = ( - + ); diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index aa3a5efe56..528e7f1abf 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -156,7 +156,7 @@ const ReactButton: React.FC = ({ mxEvent, reactions, onFocusC if (menuDisplayed && buttonRef.current) { const buttonRect = buttonRef.current.getBoundingClientRect(); contextMenu = ( - + ); diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx index 0c5dbdd1e2..32f37f4c24 100644 --- a/src/components/views/messages/ReactionsRow.tsx +++ b/src/components/views/messages/ReactionsRow.tsx @@ -34,7 +34,7 @@ const ReactButton: React.FC = ({ mxEvent, reactions }) => { if (menuDisplayed && button.current) { const buttonRect = button.current.getBoundingClientRect(); contextMenu = ( - + ); diff --git a/src/components/views/rooms/EmojiButton.tsx b/src/components/views/rooms/EmojiButton.tsx index 9da01ae92f..7eff845a9c 100644 --- a/src/components/views/rooms/EmojiButton.tsx +++ b/src/components/views/rooms/EmojiButton.tsx @@ -34,7 +34,7 @@ export function EmojiButton({ addEmoji, menuPosition, className }: IEmojiButtonP }; contextMenu = ( - + );