mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
fix(select): focus selected item in popovers (#23991)
This commit is contained in:
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
Reference in New Issue
Block a user