/**
* @fileoverview:
* Helper functions and constants to aid a11y-related issues.
*
* @namespace FocusManager.Utilities
*/
/**
* Used to keep track of focus. Defaults to body.
* @memberof FocusManager.Utilities
* @type {HTMLElement}
* @private
*/
let _focusStorage = document.body;
/**
* Constants to prevent hardcoded logic in the actual code.
* @memberof FocusManager.Utilities
* @type {object}
* @property {{NAME: string, VALUE: string}} INERT
* @property {{NAME: string, VALUE: string}} ARIA_HIDDEN
* @property {string} SECTION_SELECTOR
*/
export const ATTRIBUTES = {
INERT: {
NAME: 'inert',
VALUE: '',
},
ARIA_HIDDEN: {
NAME: 'aria-hidden',
VALUE: 'hidden',
},
SECTION_SELECTOR: '[data-focus-section]',
};
/**
* Set the aria-hidden state to an element.
* @memberof FocusManager.Utilities
* @param {HTMLElement} element - DOM node where we set state.
* @param {boolean} flag - Whether the DOM node should be hidden.
* @see {@link https://goo.gl/qwzZk7 | Google Developers}
*/
export function ariaHidden(element, flag) {
if (flag) {
element.setAttribute(ATTRIBUTES.ARIA_HIDDEN.NAME, ATTRIBUTES.ARIA_HIDDEN.VALUE);
} else {
element.removeAttribute(ATTRIBUTES.ARIA_HIDDEN.NAME);
}
}
/**
* Apply inert to an element.
* @memberof FocusManager.Utilities
* @param {HTMLElement} element - DOM node where we set state.
* @param {boolean} flag - Whether the DOM node should have inert.
*/
export function inert(element, flag) {
if (flag) {
element.setAttribute(ATTRIBUTES.INERT.NAME, ATTRIBUTES.INERT.VALUE);
} else {
element.removeAttribute(ATTRIBUTES.INERT.NAME);
}
}
/**
* Save the currently focused element.
* Useful if you want to save and restore focus to a specific element after e.g an alert or dialog.
* @memberof FocusManager.Utilities
*/
export function saveFocus() {
_focusStorage = /** @type {HTMLElement} */ (document.activeElement);
}
/**
* Restore the focus to an element.
* @memberof FocusManager.Utilities
* @param {boolean} [waitAFrame=false] - Whether we should wait a frame before restoring
* focus to an element. Useful when dealing with toggling elements.
* @return {HTMLElement} - Returns the saved element as a convenience.
*/
export function restoreFocus(waitAFrame = false) {
if (waitAFrame) {
requestAnimationFrame(() => requestAnimationFrame(() => {
_focusStorage.focus();
return _focusStorage;
}));
} else {
_focusStorage.focus();
return _focusStorage;
}
}
/**
* Set an element to be focusable or not.
* @memberof FocusManager.Utilities
* @param {HTMLElement} element
* @param {boolean} shouldBeFocusable - Whether the element is allowed focus or not.
*/
export function focusable(element, shouldBeFocusable) {
const giveFocus = !shouldBeFocusable;
ariaHidden(element, giveFocus);
inert(element, giveFocus);
}
/**
* Whether the given element is focusable or not.
* @memberof FocusManager.Utilities
* @param {HTMLElement} element
* @return {boolean}
*/
export function isHidden(element) {
return element.hasAttribute(ATTRIBUTES.INERT.NAME);
}