MM-61566 - focus first focusable element (#30294)

* MM-61566 - focus first focusable element

* adjust e2e tests to new focused element

* adjust real-events library to use with cypress
Этот коммит содержится в:
Pablo Vélez
2025-03-12 23:10:07 +01:00
коммит произвёл GitHub
родитель cc92ee79c9
Коммит 2102391672
7 изменённых файлов: 73 добавлений и 18 удалений

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

@@ -23,7 +23,7 @@ import Search from 'components/search/index';
import RhsPlugin from 'plugins/rhs_plugin';
import a11yController from 'utils/a11y_controller_instance';
import {focusElement} from 'utils/a11y_utils';
import {focusElement, getFirstFocusableChild} from 'utils/a11y_utils';
import Constants from 'utils/constants';
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard';
import {isMac} from 'utils/user_agent';
@@ -163,7 +163,16 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
// Focus the sidebar after a tick
setTimeout(() => {
if (this.sidebarRight.current) {
focusElement(this.sidebarRight, false);
const rhsContainer = this.sidebarRight.current.querySelector('#rhsContainer') as HTMLElement;
const searchContainer = this.sidebarRight.current.querySelector('#searchContainer') as HTMLElement;
if (rhsContainer || searchContainer) {
const firstFocusable = getFirstFocusableChild(rhsContainer || searchContainer);
focusElement(firstFocusable || rhsContainer, true);
} else {
// Fallback: if rhsContainer isn't found, use sidebarRight.current directly.
const firstFocusable = getFirstFocusableChild(this.sidebarRight.current);
focusElement(firstFocusable || this.sidebarRight.current, true);
}
}
}, 0);
} else if (!this.props.isOpen && wasOpen) {
@@ -173,7 +182,7 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
} else {
setTimeout(() => {
if (this.previousActiveElement) {
focusElement(this.previousActiveElement, false);
focusElement(this.previousActiveElement, true);
this.previousActiveElement = null;
}
}, 0);

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

@@ -60,3 +60,32 @@ export function focusElement(
}, 0);
}
}
/**
* Returns the first focusable child within a given container element,
* or null if none is found.
*
* Focusable elements generally include:
* - <a href="...">
* - <button>, <input>, <select>, <textarea> (unless disabled)
* - Elements with a non-negative tabindex.
*/
export function getFirstFocusableChild(container: HTMLElement): HTMLElement | null {
if (!container) {
return null;
}
// Common selectors for focusable elements:
const focusableSelectors = [
'a[href]',
'button:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'[tabindex]:not([tabindex="-1"])',
];
// Use querySelector to find the first match
const focusable = container.querySelector(focusableSelectors.join(', ')) as HTMLElement | null;
return focusable || null;
}