Add FocusLock to emoji picker (#31146)
* Add focus lock to emoji picker and e2e test. * Remove direct use of FocusLock in favour of the ContextMenu prop. * Apply returnFocus for ContextMenu focusLocks * Remove import
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -405,7 +405,7 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil
|
||||
);
|
||||
|
||||
if (focusLock) {
|
||||
body = <FocusLock>{body}</FocusLock>;
|
||||
body = <FocusLock returnFocus>{body}</FocusLock>;
|
||||
}
|
||||
|
||||
// filter props that are invalid for DOM elements
|
||||
|
||||
@@ -735,7 +735,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||
if (this.state.reactionPickerDisplayed) {
|
||||
const buttonRect = (this.reactButtonRef.current as HTMLElement)?.getBoundingClientRect();
|
||||
reactionPicker = (
|
||||
<ContextMenu {...toRightOf(buttonRect)} onFinished={this.closeMenu} managed={false}>
|
||||
<ContextMenu {...toRightOf(buttonRect)} onFinished={this.closeMenu} managed={false} focusLock>
|
||||
<ReactionPicker mxEvent={mxEvent} onFinished={this.onCloseReactionPicker} reactions={reactions} />
|
||||
</ContextMenu>
|
||||
);
|
||||
|
||||
@@ -156,7 +156,7 @@ const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusC
|
||||
if (menuDisplayed && buttonRef.current) {
|
||||
const buttonRect = buttonRef.current.getBoundingClientRect();
|
||||
contextMenu = (
|
||||
<ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
|
||||
<ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false} focusLock>
|
||||
<ReactionPicker mxEvent={mxEvent} reactions={reactions} onFinished={closeMenu} />
|
||||
</ContextMenu>
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => {
|
||||
if (menuDisplayed && button.current) {
|
||||
const buttonRect = button.current.getBoundingClientRect();
|
||||
contextMenu = (
|
||||
<ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
|
||||
<ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false} focusLock>
|
||||
<ReactionPicker mxEvent={mxEvent} reactions={reactions} onFinished={closeMenu} />
|
||||
</ContextMenu>
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ export function EmojiButton({ addEmoji, menuPosition, className }: IEmojiButtonP
|
||||
};
|
||||
|
||||
contextMenu = (
|
||||
<ContextMenu {...position} onFinished={onFinished} managed={false}>
|
||||
<ContextMenu {...position} onFinished={onFinished} managed={false} focusLock>
|
||||
<EmojiPicker onChoose={addEmoji} onFinished={onFinished} />
|
||||
</ContextMenu>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user