Skip to content

Commit 131de8b

Browse files
committed
fix(picker): manage focus to prevent a11y issues.
1 parent 74710d4 commit 131de8b

File tree

1 file changed

+56
-2
lines changed

1 file changed

+56
-2
lines changed

core/src/components/picker/picker.tsx

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,63 @@ export class Picker implements ComponentInterface {
135135
* function that has been set in onPointerDown
136136
* so that we enter/exit input mode correctly.
137137
*/
138-
private onClick = () => {
138+
private onClick = (ev: PointerEvent) => {
139139
const { actionOnClick } = this;
140140
if (actionOnClick) {
141141
actionOnClick();
142142
this.actionOnClick = undefined;
143143
}
144+
145+
/**
146+
* In order to avoid a11y issues we must manage focus
147+
* on the picker columns and picker itself.
148+
* This is because once picker is clicked we got an issue/warning because
149+
* picker input is being focused, and once it has tabindex -1 it can't be focused,
150+
* which ends on focusing the picker itself.
151+
* During the process above we fall into issues since there is an element
152+
* with tabindex -1 and aria-hidden='true' that is focused, which is not allowed.
153+
* That said and since onClick is being propagated to the picker itself, we need to
154+
* manage focus on the picker columns and picker itself to avoid the issue.
155+
*/
156+
const clickedTarget = ev.target as HTMLElement;
157+
let elementToFocus: HTMLElement | null = null;
158+
159+
switch (clickedTarget.tagName) {
160+
case 'ION-PICKER':
161+
/**
162+
* If the user clicked the picker itself
163+
* then we should focus the first picker options
164+
* so that users can scroll through them.
165+
*/
166+
const ionPickerColumn = this.el.querySelector('ion-picker-column');
167+
elementToFocus = ionPickerColumn?.shadowRoot?.querySelector('.picker-opts') as HTMLElement | null;
168+
break;
169+
170+
case 'ION-PICKER-COLUMN':
171+
/**
172+
* If the user clicked a picker column
173+
* then we should focus its own picker options
174+
* so that users can scroll through them.
175+
*/
176+
elementToFocus = clickedTarget.shadowRoot?.querySelector('.picker-opts') as HTMLElement | null;
177+
break;
178+
179+
case 'ION-PICKER-COLUMN-OPTION':
180+
/**
181+
* If the user clicked a picker column option
182+
* then we should focus its picker options parent so that
183+
* users can scroll through them.
184+
*/
185+
const ionPickerColumnOption = clickedTarget.closest('ion-picker-column');
186+
if (ionPickerColumnOption) {
187+
elementToFocus = ionPickerColumnOption.shadowRoot?.querySelector('.picker-opts') as HTMLElement | null;
188+
}
189+
break;
190+
}
191+
192+
if (elementToFocus) {
193+
elementToFocus.focus();
194+
}
144195
};
145196

146197
/**
@@ -537,7 +588,10 @@ export class Picker implements ComponentInterface {
537588

538589
render() {
539590
return (
540-
<Host onPointerDown={(ev: PointerEvent) => this.onPointerDown(ev)} onClick={() => this.onClick()}>
591+
<Host
592+
onPointerDown={(ev: PointerEvent) => this.onPointerDown(ev)}
593+
onClick={(ev: PointerEvent) => this.onClick(ev)}
594+
>
541595
<input
542596
aria-hidden="true"
543597
tabindex={-1}

0 commit comments

Comments
 (0)