84 lines
3.0 KiB
JavaScript
84 lines
3.0 KiB
JavaScript
(function (root, factory) {
|
|
const createMobileSearchModal = factory();
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = createMobileSearchModal;
|
|
else root.createMobileSearchModal = createMobileSearchModal;
|
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
const FOCUSABLE = [
|
|
'a[href]', 'button', 'input', 'select', 'textarea',
|
|
'[tabindex]:not([tabindex="-1"])', '[contenteditable="true"]',
|
|
].join(',');
|
|
|
|
return function createMobileSearchModal(options) {
|
|
const documentRef = options.document;
|
|
const backgrounds = Array.from(options.backgrounds || [
|
|
documentRef.querySelector?.('body > header'),
|
|
documentRef.querySelector?.('main'),
|
|
documentRef.querySelector?.('#mobile-task-dock'),
|
|
]).filter(Boolean);
|
|
let surface = null;
|
|
let opener = null;
|
|
let priorInert = null;
|
|
|
|
function focusable() {
|
|
if (!surface || typeof surface.querySelectorAll !== 'function') return [];
|
|
return Array.from(surface.querySelectorAll(FOCUSABLE)).filter(element =>
|
|
!element.disabled && !element.hidden && element.getAttribute?.('aria-hidden') !== 'true' &&
|
|
(typeof element.matches !== 'function' || element.matches(':not([hidden])')) &&
|
|
(typeof element.getClientRects !== 'function' || element.getClientRects().length > 0)
|
|
);
|
|
}
|
|
|
|
function focus(element) {
|
|
if (element?.isConnected !== false && typeof element?.focus === 'function') element.focus();
|
|
}
|
|
|
|
function onKeydown(event) {
|
|
if (!surface || event.key !== 'Tab') return;
|
|
const controls = focusable();
|
|
if (!controls.length) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
const first = controls[0];
|
|
const last = controls[controls.length - 1];
|
|
if (event.shiftKey && documentRef.activeElement === first) {
|
|
event.preventDefault();
|
|
focus(last);
|
|
} else if (!event.shiftKey && documentRef.activeElement === last) {
|
|
event.preventDefault();
|
|
focus(first);
|
|
} else if (!controls.includes(documentRef.activeElement)) {
|
|
event.preventDefault();
|
|
focus(event.shiftKey ? last : first);
|
|
}
|
|
}
|
|
|
|
function transition(nextSurface, transitionOptions = {}) {
|
|
surface = nextSurface;
|
|
focus(transitionOptions.initialFocus || focusable()[0]);
|
|
}
|
|
|
|
function activate(nextSurface, activateOptions = {}) {
|
|
if (!surface) {
|
|
opener = activateOptions.opener || documentRef.activeElement || null;
|
|
priorInert = backgrounds.map(element => element.inert === true);
|
|
backgrounds.forEach(element => { element.inert = true; });
|
|
}
|
|
transition(nextSurface, activateOptions);
|
|
}
|
|
|
|
function deactivate() {
|
|
if (!surface) return;
|
|
backgrounds.forEach((element, index) => { element.inert = priorInert[index]; });
|
|
surface = null;
|
|
priorInert = null;
|
|
const restore = opener;
|
|
opener = null;
|
|
focus(restore);
|
|
}
|
|
|
|
documentRef.addEventListener('keydown', onKeydown);
|
|
return { activate, transition, deactivate, current: () => surface };
|
|
};
|
|
});
|