mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 18:17:31 +08:00
fix(focusVisibleElement): set focus on custom appRootSelector (#30218)
This commit is contained in:
1
core/src/components.d.ts
vendored
1
core/src/components.d.ts
vendored
@ -334,6 +334,7 @@ export namespace Components {
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* Used to set focus on an element that uses `ion-focusable`. Do not use this if focusing the element as a result of a keyboard event as the focus utility should handle this for us. This method should be used when we want to programmatically focus an element as a result of another user action. (Ex: We focus the first element inside of a popover when the user presents it, but the popover is not always presented as a result of keyboard action.)
|
||||
* @param elements - The elements to set focus on.
|
||||
*/
|
||||
"setFocus": (elements: HTMLElement[]) => Promise<void>;
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Component, Element, Host, Method, h } from '@stencil/core';
|
||||
import { getOrInitFocusVisibleUtility } from '@utils/focus-visible';
|
||||
import { focusElements } from '@utils/focus-visible';
|
||||
|
||||
import { config } from '../../global/config';
|
||||
import { getIonTheme } from '../../global/ionic-global';
|
||||
@ -24,11 +24,16 @@ export class App implements ComponentInterface {
|
||||
* a result of another user action. (Ex: We focus the first element
|
||||
* inside of a popover when the user presents it, but the popover is not always
|
||||
* presented as a result of keyboard action.)
|
||||
*
|
||||
* @param elements - The elements to set focus on.
|
||||
*/
|
||||
@Method()
|
||||
async setFocus(elements: HTMLElement[]) {
|
||||
const focusVisible = getOrInitFocusVisibleUtility();
|
||||
focusVisible.setFocus(elements);
|
||||
/**
|
||||
* The focus-visible utility is used to set focus on an
|
||||
* element that uses `ion-focusable`.
|
||||
*/
|
||||
focusElements(elements);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -30,6 +30,22 @@ export const getOrInitFocusVisibleUtility = () => {
|
||||
return focusVisibleUtility;
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to set focus on an element that uses `ion-focusable`.
|
||||
* Do not use this if focusing the element as a result of a keyboard
|
||||
* event as the focus utility should handle this for us. This method
|
||||
* should be used when we want to programmatically focus an element as
|
||||
* a result of another user action. (Ex: We focus the first element
|
||||
* inside of a popover when the user presents it, but the popover is not always
|
||||
* presented as a result of keyboard action.)
|
||||
*
|
||||
* @param elements - The elements to set focus on.
|
||||
*/
|
||||
export const focusElements = (elements: Element[]) => {
|
||||
const focusVisible = getOrInitFocusVisibleUtility();
|
||||
focusVisible.setFocus(elements);
|
||||
};
|
||||
|
||||
export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility => {
|
||||
let currentFocus: Element[] = [];
|
||||
let keyboardMode = true;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { EventEmitter } from '@stencil/core';
|
||||
import { focusElements } from '@utils/focus-visible';
|
||||
|
||||
import type { Side } from '../components/menu/menu-interface';
|
||||
import { config } from '../global/config';
|
||||
@ -255,6 +256,17 @@ export const hasShadowDom = (el: HTMLElement) => {
|
||||
return !!el.shadowRoot && !!(el as any).attachShadow;
|
||||
};
|
||||
|
||||
/**
|
||||
* Focuses a given element while ensuring proper focus management
|
||||
* within the Ionic framework. If the element is marked as `ion-focusable`,
|
||||
* this function will delegate focus handling to `ion-app` or manually
|
||||
* apply focus when a custom app root is used.
|
||||
*
|
||||
* This function helps maintain accessibility and expected focus behavior
|
||||
* in both standard and custom root environments.
|
||||
*
|
||||
* @param el - The element to focus.
|
||||
*/
|
||||
export const focusVisibleElement = (el: HTMLElement) => {
|
||||
el.focus();
|
||||
|
||||
@ -267,10 +279,35 @@ export const focusVisibleElement = (el: HTMLElement) => {
|
||||
* which will let us explicitly set the elements to focus.
|
||||
*/
|
||||
if (el.classList.contains('ion-focusable')) {
|
||||
const appRootSelector = config.get('appRootSelector', 'ion-app');
|
||||
const appRootSelector: string = config.get('appRootSelector', 'ion-app');
|
||||
const app = el.closest(appRootSelector) as HTMLIonAppElement | null;
|
||||
if (app) {
|
||||
app.setFocus([el]);
|
||||
if (appRootSelector === 'ion-app') {
|
||||
/**
|
||||
* If the app root is the default, then it will be
|
||||
* in charge of setting focus. This is because the
|
||||
* focus-visible utility is attached to the app root
|
||||
* and will handle setting focus on the correct element.
|
||||
*/
|
||||
app.setFocus([el]);
|
||||
} else {
|
||||
/**
|
||||
* When using a custom app root selector, the focus-visible
|
||||
* utility is not available to manage focus automatically.
|
||||
* If we set focus immediately, the element may not be fully
|
||||
* rendered or interactive, especially if it was just added
|
||||
* to the DOM. Using requestAnimationFrame ensures that focus
|
||||
* is applied on the next frame, allowing the DOM to settle
|
||||
* before changing focus.
|
||||
*/
|
||||
requestAnimationFrame(() => {
|
||||
/**
|
||||
* The focus-visible utility is used to set focus on an
|
||||
* element that uses `ion-focusable`.
|
||||
*/
|
||||
focusElements([el]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user