@ionic/core
Version:
Base components for Ionic
113 lines (112 loc) • 4.94 kB
JavaScript
/*!
* (C) Ionic http://ionicframework.com - MIT License
*/
import { config } from "../../global/config";
import { printIonWarning } from "../logging/index";
/**
* Moves focus to a specified element. Note that we do not remove the tabindex
* because that can result in an unintentional blur. Non-focusables can't be
* focused, so the body will get focused again.
*/
const moveFocus = (el) => {
el.tabIndex = -1;
el.focus();
};
/**
* Elements that are hidden using `display: none` should not be focused even if
* they are present in the DOM.
*/
const isVisible = (el) => {
return el.offsetParent !== null;
};
/**
* The focus controller allows us to manage focus within a view so assistive
* technologies can inform users of changes to the navigation state. Traditional
* native apps have a way of informing assistive technology about a navigation
* state change. Mobile browsers have this too, but only when doing a full page
* load. In a single page app we do not do that, so we need to build this
* integration ourselves.
*/
export const createFocusController = () => {
const saveViewFocus = (referenceEl) => {
const focusManagerEnabled = config.get('focusManagerPriority', false);
/**
* When going back to a previously visited page focus should typically be moved
* back to the element that was last focused when the user was on this view.
*/
if (focusManagerEnabled) {
const activeEl = document.activeElement;
if (activeEl !== null && (referenceEl === null || referenceEl === void 0 ? void 0 : referenceEl.contains(activeEl))) {
activeEl.setAttribute(LAST_FOCUS, 'true');
}
}
};
const setViewFocus = (referenceEl) => {
const focusManagerPriorities = config.get('focusManagerPriority', false);
/**
* If the focused element is a descendant of the referenceEl then it's possible
* that the app developer manually moved focus, so we do not want to override that.
* This can happen with inputs the are focused when a view transitions in.
*/
if (Array.isArray(focusManagerPriorities) && !referenceEl.contains(document.activeElement)) {
/**
* When going back to a previously visited view focus should always be moved back
* to the element that the user was last focused on when they were on this view.
*/
const lastFocus = referenceEl.querySelector(`[${LAST_FOCUS}]`);
if (lastFocus && isVisible(lastFocus)) {
moveFocus(lastFocus);
return;
}
for (const priority of focusManagerPriorities) {
/**
* For each recognized case (excluding the default case) make sure to return
* so that the fallback focus behavior does not run.
*
* We intentionally query for specific roles/semantic elements so that the
* transition manager can work with both Ionic and non-Ionic UI components.
*
* If new selectors are added, be sure to remove the outline ring by adding
* new selectors to rule in core.scss.
*/
switch (priority) {
case 'content':
const content = referenceEl.querySelector('main, [role="main"]');
if (content && isVisible(content)) {
moveFocus(content);
return;
}
break;
case 'heading':
const headingOne = referenceEl.querySelector('h1, [role="heading"][aria-level="1"]');
if (headingOne && isVisible(headingOne)) {
moveFocus(headingOne);
return;
}
break;
case 'banner':
const header = referenceEl.querySelector('header, [role="banner"]');
if (header && isVisible(header)) {
moveFocus(header);
return;
}
break;
default:
printIonWarning(`Unrecognized focus manager priority value ${priority}`);
break;
}
}
/**
* If there is nothing to focus then focus the page so focus at least moves to
* the correct view. The browser will then determine where within the page to
* move focus to.
*/
moveFocus(referenceEl);
}
};
return {
saveViewFocus,
setViewFocus,
};
};
const LAST_FOCUS = 'ion-last-focus';