MM-62226 - set focus back to search button (#29669)
* MM-62226 - set focus back to search button * fix linter
Этот коммит содержится в:
@@ -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
|
||||
*/
|
||||
|
||||
7
webapp/channels/src/utils/a11y_controller_instance.ts
Обычный файл
7
webapp/channels/src/utils/a11y_controller_instance.ts
Обычный файл
@@ -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;
|
||||
Ссылка в новой задаче
Block a user