MM-62226 - set focus back to search button (#29669)

* MM-62226 - set focus back to search button

* fix linter
Этот коммит содержится в:
Pablo Vélez
2025-01-09 08:39:02 -05:00
коммит произвёл GitHub
родитель e8beefd423
Коммит 882654d898
5 изменённых файлов: 84 добавлений и 19 удалений

Просмотреть файл

@@ -14,7 +14,9 @@ import {getSearchTerms, getSearchType} from 'selectors/rhs';
import Popover from 'components/widgets/popover';
import Constants from 'utils/constants';
import a11yController from 'utils/a11y_controller_instance';
import type {A11yFocusEventDetail} from 'utils/constants';
import Constants, {A11yCustomEventTypes} from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import {isServerVersionGreaterThanOrEqualTo} from 'utils/server_version';
import {isDesktopApp, getDesktopVersion, isMacApp} from 'utils/user_agent';
@@ -102,6 +104,7 @@ const NewSearch = (): JSX.Element => {
const [focused, setFocused] = useState<boolean>(false);
const [currentChannel, setCurrentChannel] = useState('');
const searchBoxRef = useRef<HTMLDivElement | null>(null);
const searchButtonRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const isDesktop = isDesktopApp() && isServerVersionGreaterThanOrEqualTo(getDesktopVersion(), '4.7.0');
@@ -160,10 +163,24 @@ const NewSearch = (): JSX.Element => {
const closeSearchBox = useCallback(() => {
setFocused(false);
setCurrentChannel('');
if (searchButtonRef.current) {
document.dispatchEvent(
new CustomEvent<A11yFocusEventDetail>(A11yCustomEventTypes.FOCUS, {
detail: {
target: searchButtonRef.current,
keyboardOnly: false,
},
}),
);
a11yController.resetOriginElement();
}
}, []);
const openSearchBox = useCallback(() => {
setFocused(true);
if (searchButtonRef.current) {
a11yController.storeOriginElement(searchButtonRef.current);
}
}, []);
const openSearchBoxOnKeyPress = useCallback(
@@ -220,6 +237,7 @@ const NewSearch = (): JSX.Element => {
id='searchFormContainer'
role='button'
className='a11y__region'
ref={searchButtonRef}
>
<i className='icon icon-magnify'/>
{(searchType === 'messages' || searchType === 'files') && (

Просмотреть файл

@@ -31,7 +31,7 @@ import SidebarMobileRightMenu from 'components/sidebar_mobile_right_menu';
import webSocketClient from 'client/web_websocket_client';
import {initializePlugins} from 'plugins';
import A11yController from 'utils/a11y_controller';
import 'utils/a11y_controller_instance';
import {PageLoadContext, SCHEDULED_POST_URL_SUFFIX} from 'utils/constants';
import DesktopApp from 'utils/desktop_api';
import {EmojiIndicesByAlias} from 'utils/emoji';
@@ -92,8 +92,6 @@ interface State {
export default class Root extends React.PureComponent<Props, State> {
// The constructor adds a bunch of event listeners,
// so we do need this.
private a11yController: A11yController;
constructor(props: Props) {
super(props);
@@ -107,8 +105,6 @@ export default class Root extends React.PureComponent<Props, State> {
this.state = {
shouldMountAppRoutes: false,
};
this.a11yController = new A11yController();
}
setRudderConfig = () => {

Просмотреть файл

@@ -21,6 +21,7 @@ import RhsThread from 'components/rhs_thread';
import Search from 'components/search/index';
import RhsPlugin from 'plugins/rhs_plugin';
import a11yController from 'utils/a11y_controller_instance';
import type {A11yFocusEventDetail} from 'utils/constants';
import Constants, {A11yCustomEventTypes} from 'utils/constants';
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard';
@@ -170,19 +171,23 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
} else if (!this.props.isOpen && wasOpen) {
// RHS just was closed, restore focus to the previous element had it
// this will have to change for upcoming work specially for search and probalby plugins
requestAnimationFrame(() => {
if (this.previousActiveElement) {
document.dispatchEvent(
new CustomEvent<A11yFocusEventDetail>(A11yCustomEventTypes.FOCUS, {
detail: {
target: this.previousActiveElement,
keyboardOnly: false,
},
}),
);
this.previousActiveElement = null;
}
});
if (a11yController.originElement) {
a11yController.restoreOriginFocus();
} else {
requestAnimationFrame(() => {
if (this.previousActiveElement) {
document.dispatchEvent(
new CustomEvent<A11yFocusEventDetail>(A11yCustomEventTypes.FOCUS, {
detail: {
target: this.previousActiveElement,
keyboardOnly: false,
},
}),
);
this.previousActiveElement = null;
}
});
}
}
}

Просмотреть файл

@@ -42,6 +42,7 @@ export default class A11yController {
lKeyIsPressed = false;
escKeyIsPressed = false;
windowIsFocused = true;
originElement?: HTMLElement | null = null;
// used to reset navigation whenever navigation within a region occurs (section or element)
resetNavigation = false;
@@ -71,6 +72,27 @@ export default class A11yController {
window.removeEventListener(EventTypes.BLUR, this.handleWindowBlur, listenerOptions);
}
/**
* Stores a reference to the provided DOM element as the "origin element".
* The origin element is the element that triggered or last caused a certain
* navigation or focus event. This allows restoring focus back to this element
* once certain UI components (like RHS, panels, or menus) are closed.
*
* @param {HTMLElement} element - The DOM element to store as the origin element.
*/
storeOriginElement(element: HTMLElement) {
this.originElement = element;
}
/**
* Resets the stored origin element reference back to null.
* This is typically called after focus has been restored to the origin element,
* indicating that the controller no longer needs to keep track of it.
*/
resetOriginElement() {
this.originElement = null;
}
// convenience getter/setters
/**
@@ -370,6 +392,23 @@ export default class A11yController {
this.resetNavigation = true;
}
restoreOriginFocus() {
if (this.originElement && this.isElementValid(this.originElement)) {
// Dispatch a focus event to manually focus this element
document.dispatchEvent(
new CustomEvent(A11yCustomEventTypes.FOCUS, {
detail: {
target: this.originElement,
keyboardOnly: false,
},
}),
);
setTimeout(() => {
this.originElement = null;
}, 0);
}
}
/**
* Resets the a11y navigation controller, active region/section/element, clears focus and resets user interraction states
*/

Просмотреть файл

@@ -0,0 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import A11yController from 'utils/a11y_controller';
const a11yController = new A11yController();
export default a11yController;