fix(select): focus selected item in popovers (#23991)

This commit is contained in:
Amanda Smith
2021-10-01 13:33:53 -05:00
committed by GitHub
parent 86a77bd379
commit 2497a53255
4 changed files with 49 additions and 22 deletions

View File

@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Meth
import { getIonMode } from '../../global/ionic-global'; import { getIonMode } from '../../global/ionic-global';
import { ActionSheetButton, ActionSheetOptions, AlertInput, AlertOptions, CssClassMap, OverlaySelect, PopoverOptions, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, StyleEventDetail } from '../../interface'; import { ActionSheetButton, ActionSheetOptions, AlertInput, AlertOptions, CssClassMap, OverlaySelect, PopoverOptions, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, StyleEventDetail } from '../../interface';
import { findItemLabel, getAriaLabel, renderHiddenInput } from '../../utils/helpers'; import { findItemLabel, focusElement, getAriaLabel, renderHiddenInput } from '../../utils/helpers';
import { actionSheetController, alertController, popoverController } from '../../utils/overlays'; import { actionSheetController, alertController, popoverController } from '../../utils/overlays';
import { hostContext } from '../../utils/theme'; import { hostContext } from '../../utils/theme';
import { watchForOptions } from '../../utils/watch-options'; import { watchForOptions } from '../../utils/watch-options';
@ -179,11 +179,18 @@ export class Select implements ComponentInterface {
this.setFocus(); this.setFocus();
}); });
await overlay.present();
// focus selected option for popovers
if (this.interface === 'popover') { if (this.interface === 'popover') {
await (overlay as HTMLIonPopoverElement).presentFromTrigger(event, true); let indexOfSelected = this.childOpts.map(o => o.value).indexOf(this.value);
} else { indexOfSelected = indexOfSelected > -1 ? indexOfSelected : 0; // default to first option if nothing selected
await overlay.present(); const selectedEl = overlay.querySelector<HTMLElement>(`.select-interface-option:nth-child(${indexOfSelected + 1})`);
if (selectedEl) {
focusElement(selectedEl);
}
} }
return overlay; return overlay;
} }

View File

@ -48,12 +48,28 @@ test('select: basic', async () => {
select = await page.find('#customPopoverSelect'); select = await page.find('#customPopoverSelect');
await select.click(); await select.click();
const popover = await page.find('ion-popover'); let popover = await page.find('ion-popover');
await popover.waitForVisible(); await popover.waitForVisible();
await page.waitForTimeout(250); await page.waitForTimeout(250);
compares.push(await page.compareScreenshot('should open custom popover select')); compares.push(await page.compareScreenshot('should open custom popover select'));
// select has no value, so first option should be focused by default
const popoverOption1 = await popover.find('.select-interface-option:first-child');
expect(popoverOption1).toHaveClass('ion-focused');
let popoverOption2 = await popover.find('.select-interface-option:nth-child(2)');
await popoverOption2.click();
await page.waitForTimeout(500);
await select.click();
popover = await page.find('ion-popover');
await popover.waitForVisible();
await page.waitForTimeout(250);
popoverOption2 = await popover.find('.select-interface-option:nth-child(2)');
expect(popoverOption2).toHaveClass('ion-focused');
await popover.callMethod('dismiss'); await popover.callMethod('dismiss');
// Custom Action Sheet Select // Custom Action Sheet Select

View File

@ -174,6 +174,25 @@ export const findItemLabel = (componentEl: HTMLElement): HTMLIonLabelElement | n
return null; return null;
}; };
export const focusElement = (el: HTMLElement) => {
el.focus();
/**
* When programmatically focusing an element,
* the focus-visible utility will not run because
* it is expecting a keyboard event to have triggered this;
* however, there are times when we need to manually control
* this behavior so we call the `setFocus` method on ion-app
* which will let us explicitly set the elements to focus.
*/
if (el.classList.contains('ion-focusable')) {
const app = el.closest('ion-app');
if (app) {
app.setFocus([el]);
}
}
};
/** /**
* This method is used for Ionic's input components that use Shadow DOM. In * This method is used for Ionic's input components that use Shadow DOM. In
* order to properly label the inputs to work with screen readers, we need * order to properly label the inputs to work with screen readers, we need

View File

@ -3,7 +3,7 @@ import { getIonMode } from '../global/ionic-global';
import { ActionSheetOptions, AlertOptions, Animation, AnimationBuilder, BackButtonEvent, HTMLIonOverlayElement, IonicConfig, LoadingOptions, ModalOptions, OverlayInterface, PickerOptions, PopoverOptions, ToastOptions } from '../interface'; import { ActionSheetOptions, AlertOptions, Animation, AnimationBuilder, BackButtonEvent, HTMLIonOverlayElement, IonicConfig, LoadingOptions, ModalOptions, OverlayInterface, PickerOptions, PopoverOptions, ToastOptions } from '../interface';
import { OVERLAY_BACK_BUTTON_PRIORITY } from './hardware-back-button'; import { OVERLAY_BACK_BUTTON_PRIORITY } from './hardware-back-button';
import { addEventListener, componentOnReady, getElementRoot, removeEventListener } from './helpers'; import { addEventListener, componentOnReady, focusElement, getElementRoot, removeEventListener } from './helpers';
let lastId = 0; let lastId = 0;
@ -78,22 +78,7 @@ export const focusFirstDescendant = (ref: Element, overlay: HTMLIonOverlayElemen
} }
if (firstInput) { if (firstInput) {
firstInput.focus(); focusElement(firstInput);
/**
* When programmatically focusing an element,
* the focus-visible utility will not run because
* it is expecting a keyboard event to have triggered this;
* however, there are times when we need to manually control
* this behavior so we call the `setFocus` method on ion-app
* which will let us explicitly set the elements to focus.
*/
if (firstInput.classList.contains('ion-focusable')) {
const app = overlay.closest('ion-app');
if (app) {
app.setFocus([firstInput]);
}
}
} else { } else {
// Focus overlay instead of letting focus escape // Focus overlay instead of letting focus escape
overlay.focus(); overlay.focus();