fix(picker): manage focus to prevent a11y issues.

This commit is contained in:
José Rio
2025-07-17 16:49:28 +01:00
parent 74710d4cf6
commit 131de8b8ea

View File

@@ -135,12 +135,63 @@ export class Picker implements ComponentInterface {
* function that has been set in onPointerDown
* so that we enter/exit input mode correctly.
*/
private onClick = () => {
private onClick = (ev: PointerEvent) => {
const { actionOnClick } = this;
if (actionOnClick) {
actionOnClick();
this.actionOnClick = undefined;
}
/**
* In order to avoid a11y issues we must manage focus
* on the picker columns and picker itself.
* This is because once picker is clicked we got an issue/warning because
* picker input is being focused, and once it has tabindex -1 it can't be focused,
* which ends on focusing the picker itself.
* During the process above we fall into issues since there is an element
* with tabindex -1 and aria-hidden='true' that is focused, which is not allowed.
* That said and since onClick is being propagated to the picker itself, we need to
* manage focus on the picker columns and picker itself to avoid the issue.
*/
const clickedTarget = ev.target as HTMLElement;
let elementToFocus: HTMLElement | null = null;
switch (clickedTarget.tagName) {
case 'ION-PICKER':
/**
* If the user clicked the picker itself
* then we should focus the first picker options
* so that users can scroll through them.
*/
const ionPickerColumn = this.el.querySelector('ion-picker-column');
elementToFocus = ionPickerColumn?.shadowRoot?.querySelector('.picker-opts') as HTMLElement | null;
break;
case 'ION-PICKER-COLUMN':
/**
* If the user clicked a picker column
* then we should focus its own picker options
* so that users can scroll through them.
*/
elementToFocus = clickedTarget.shadowRoot?.querySelector('.picker-opts') as HTMLElement | null;
break;
case 'ION-PICKER-COLUMN-OPTION':
/**
* If the user clicked a picker column option
* then we should focus its picker options parent so that
* users can scroll through them.
*/
const ionPickerColumnOption = clickedTarget.closest('ion-picker-column');
if (ionPickerColumnOption) {
elementToFocus = ionPickerColumnOption.shadowRoot?.querySelector('.picker-opts') as HTMLElement | null;
}
break;
}
if (elementToFocus) {
elementToFocus.focus();
}
};
/**
@@ -537,7 +588,10 @@ export class Picker implements ComponentInterface {
render() {
return (
<Host onPointerDown={(ev: PointerEvent) => this.onPointerDown(ev)} onClick={() => this.onClick()}>
<Host
onPointerDown={(ev: PointerEvent) => this.onPointerDown(ev)}
onClick={(ev: PointerEvent) => this.onClick(ev)}
>
<input
aria-hidden="true"
tabindex={-1}