Skip to content

Commit 2f23ff0

Browse files
committed
fix(picker): manage focus to prevent a11y issues.
1 parent e64332b commit 2f23ff0

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

144195
/**
@@ -535,7 +586,10 @@ export class Picker implements ComponentInterface {
535586

536587
render() {
537588
return (
538-
<Host onPointerDown={(ev: PointerEvent) => this.onPointerDown(ev)} onClick={() => this.onClick()}>
589+
<Host
590+
onPointerDown={(ev: PointerEvent) => this.onPointerDown(ev)}
591+
onClick={(ev: PointerEvent) => this.onClick(ev)}
592+
>
539593
<input
540594
aria-hidden="true"
541595
tabindex={-1}

0 commit comments

Comments
 (0)