diff --git a/core/src/components/select/select.tsx b/core/src/components/select/select.tsx index 639d8e22a3..6c63cc495f 100644 --- a/core/src/components/select/select.tsx +++ b/core/src/components/select/select.tsx @@ -316,29 +316,46 @@ export class Select implements ComponentInterface { // focus selected option for popovers if (this.interface === 'popover') { - let indexOfSelected = this.childOpts.map((o) => o.value).indexOf(this.value); - indexOfSelected = indexOfSelected > -1 ? indexOfSelected : 0; // default to first option if nothing selected - const selectedItem = overlay.querySelector( - `.select-interface-option:nth-child(${indexOfSelected + 1})` - ); + const indexOfSelected = this.childOpts.map((o) => o.value).indexOf(this.value); - if (selectedItem) { - focusElement(selectedItem); + if (indexOfSelected > -1) { + const selectedItem = overlay.querySelector( + `.select-interface-option:nth-child(${indexOfSelected + 1})` + ); + if (selectedItem) { + focusElement(selectedItem); + + /** + * Browsers such as Firefox do not + * correctly delegate focus when manually + * focusing an element with delegatesFocus. + * We work around this by manually focusing + * the interactive element. + * ion-radio and ion-checkbox are the only + * elements that ion-select-popover uses, so + * we only need to worry about those two components + * when focusing. + */ + const interactiveEl = selectedItem.querySelector('ion-radio, ion-checkbox'); + if (interactiveEl) { + interactiveEl.focus(); + } + } + } else { /** - * Browsers such as Firefox do not - * correctly delegate focus when manually - * focusing an element with delegatesFocus. - * We work around this by manually focusing - * the interactive element. - * ion-radio and ion-checkbox are the only - * elements that ion-select-popover uses, so - * we only need to worry about those two components - * when focusing. + * If no value is set then focus the first enabled option. */ - const interactiveEl = selectedItem.querySelector('ion-radio, ion-checkbox'); - if (interactiveEl) { - interactiveEl.focus(); + const firstEnabledOption = overlay.querySelector( + 'ion-radio:not(.radio-disabled), ion-checkbox:not(.checkbox-disabled)' + ); + if (firstEnabledOption) { + focusElement(firstEnabledOption.closest('ion-item')!); + + /** + * Focus the option for the same reason as we do above. + */ + firstEnabledOption.focus(); } } } diff --git a/core/src/components/select/test/disabled/select.e2e.ts b/core/src/components/select/test/disabled/select.e2e.ts new file mode 100644 index 0000000000..831d471d70 --- /dev/null +++ b/core/src/components/select/test/disabled/select.e2e.ts @@ -0,0 +1,36 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('select: disabled options'), () => { + test('should not focus a disabled option when no value is set', async ({ page, skip }) => { + // TODO (FW-2979) + skip.browser('webkit', 'Safari 16 only allows text fields and pop-up menus to be focused.'); + + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/28284', + }); + + await page.setContent( + ` + + A + B + + `, + config + ); + + const select = page.locator('ion-select'); + const popover = page.locator('ion-popover'); + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + + await select.click(); + await ionPopoverDidPresent.next(); + + const popoverOption = popover.locator('.select-interface-option:nth-of-type(2) ion-radio'); + await expect(popoverOption).toBeFocused(); + }); + }); +});