/**
* The floating layer an outside element belongs to, or null when it is part of the
* page. A layer is the nearest of:
* - an element carrying `data-focus-trap-allow` (an opt-in for anything else);
* - an element with `position: fixed` (toasts, popovers, context menus, palettes,
* another overlay that did not trap focus) - unless it was already on the page and
* stacks below the trap (`floor`): a fixed header under a modal is page, not a layer;
* - a direct child of
that is absolutely positioned (portalled pickers and
* dropdowns), or that was put there after `since` was taken - something opened
* while the trap was on top.
* @param {Element} target
* @param {Set} [since] - the children had when the trap activated
* @param {number} [floor] - the trap's own stacking level (see `stackLevel()`); 0 = none
* @returns {Element|null}
*/
export function layerOf(target: Element, since?: Set, floor?: number): Element | null;
/**
* The highest z-index on an element or its ancestors - roughly the stacking layer it
* is drawn in. 0 when nothing on the way sets one.
* @param {Element} el
* @returns {number}
*/
export function stackLevel(el: Element): number;
/**
* The tabbable elements in some containers, in Tab order: positive tabindex first (in
* ascending order), then the rest in document order. Of a radio group only the checked
* button - or the first, when none is - takes part, as the browser does.
* @param {Element|Element[]} containers
* @param {{shadow?: boolean}} [opts] - shadow: false skips shadow roots
* @returns {HTMLElement[]}
*/
export function tabbables(containers: Element | Element[], opts?: {
shadow?: boolean;
}): HTMLElement[];
/**
* A trap round some containers - the small util other components use.
* @param {FocusTarget|Element|Array} containers
* @param {FocusTrapOptions} [options]
* @returns {FocusTrap}
*/
export function createFocusTrap(containers: FocusTarget | Element | Array, options?: FocusTrapOptions): FocusTrap;
/** Marks an element (and what is inside it) as a layer any trap lets focus into. */
export const ALLOW_ATTR: "data-focus-trap-allow";
export class FocusTrap extends Component {
/** @type {FocusTrapOptions} */
static defaults: FocusTrapOptions;
/**
* @param {FocusTarget|Element|Array} containers
* @returns {HTMLElement[]}
* @protected
*/
protected static _resolve(containers: FocusTarget | Element | Array): HTMLElement[];
/**
* The trap that currently listens: the newest active one.
* @returns {FocusTrap|null}
*/
static top(): FocusTrap | null;
/**
* @param {FocusTarget|Element|Array} containers - one container, or several
* @param {FocusTrapOptions} [options]
*/
constructor(containers: FocusTarget | Element | Array, options?: FocusTrapOptions);
_containers: HTMLElement[];
_bodyAtStart: Set | null;
_floor: number;
_pendingTab: {
shift: any;
fromInside: boolean;
} | null;
_active: boolean;
_paused: boolean;
_returnTo: Element | null;
_lastFocused: any;
_madeFocusable: any[];
_inerted: any[];
_bound: boolean;
/** Capture phase: Escape, and a note of the Tab that is on its way. */
_onKeyDown(e: any): void;
/** Bubble phase on window: move focus, unless something on the way used the key. */
_onTab(e: any): void;
_onFocusIn(e: any): void;
_onPointerDown(e: any): void;
_onClick(e: any): void;
/**
* Capture phase: an event aimed inside the trap opens a short window in which focus may
* leave - until the handlers of this event, and anything they chain with
* Promise.then, have run. A Tab never opens it (Tab is the trap's own business).
*/
_onInsideEvent(e: any): void;
/**
* Start trapping. Pauses the trap that was on top, if any, and moves focus in.
* @param {{initialFocus?: FocusTrapOptions['initialFocus']}} [opts] - overrides the option for this activation
* @returns {this}
*/
activate(opts?: {
initialFocus?: FocusTrapOptions["initialFocus"];
}): this;
/**
* Stop trapping. Unpauses the trap beneath, and returns focus unless told not to.
* @param {{returnFocus?: boolean|FocusTarget}} [opts]
* @returns {this}
*/
deactivate(opts?: {
returnFocus?: boolean | FocusTarget;
}): this;
/**
* Stop listening without deactivating - another trap has taken over.
* @returns {this}
*/
pause(): this;
/**
* Listen again after pause(). Focus comes back into the containers if it is outside.
* @param {{focus?: boolean}} [opts] - focus: false leaves focus where it is
* @returns {this}
*/
unpause(opts?: {
focus?: boolean;
}): this;
/**
* Trap a different set of containers (takes effect at the next key press).
* @param {FocusTarget|Element|Array} containers
* @returns {this}
*/
updateContainers(containers: FocusTarget | Element | Array): this;
/** @returns {HTMLElement[]} the trapped containers */
containers(): HTMLElement[];
/** @returns {boolean} active (paused or not) */
isActive(): boolean;
/** @returns {boolean} active but paused by a trap above it */
isPaused(): boolean;
/**
* The tabbable elements in the containers right now, in Tab order.
* @returns {HTMLElement[]}
*/
tabbables(): HTMLElement[];
_destroyed: boolean | undefined;
_contains(node: any): boolean;
_eventInside(e: any): boolean;
_bind(): void;
_unbind(): void;
/** A container that can hold focus itself, for when there is nothing tabbable. */
_fallback(): any;
_clearFocusable(): void;
_firstTarget(): HTMLElement;
_focusInitial(initial: any): void;
_focus(el: any, scroll?: boolean): void;
/**
* A move Tab asked for: scrolls the element into view as native Tab does
* (`preventScroll` is for activation and return only). Should the element not take
* focus after all, the next candidate in `list` is tried, so Tab never sticks.
*/
_tabTo(el: any, list: any, back?: boolean): void;
/**
* Containers that left the page (a view removed the overlay without closing it)
* leave nothing to trap: the trap lets go rather than hold focus nowhere.
* @returns {boolean} true when it let go
*/
_gone(): boolean;
/** Whether focus may stay on an element outside the containers. */
_mayFocusOutside(target: any, e: any): boolean;
/** The tabbable after (or before) a non-tabbable element inside the containers. */
_nextFrom(active: any, list: any, back: any): any;
/**
* Tab inside a floating layer the trap let focus into (a popover opened from the
* overlay, a toast's button): it moves through the layer, and off either end it
* comes back to the overlay as though the layer sat just after what had focus
* before - Shift+Tab to that element, Tab to the one after it.
*/
_tabInLayer(e: any, layer: any, active: any): void;
_scriptTurn: {} | null | undefined;
/**
* `inert` on every sibling of the containers and of their ancestors, up to , so
* the rest of the page cannot be reached by anything - pointer, Tab or a screen
* reader's virtual cursor. What was inert already is left alone and stays so.
*/
_applyInert(): void;
_clearInert(): void;
}
export default FocusTrap;
/**
* A container or element: a CSS selector or the element itself.
*/
export type FocusTarget = string | HTMLElement;
export type FocusTrapOptions = {
/**
* Where focus goes on activate: a selector, an element, a function returning one, or false to leave it. Default: focus already inside stays, else the first tabbable, else the container.
*/
initialFocus?: false | FocusTarget | (() => HTMLElement | null) | undefined;
/**
* Where focus goes when nothing is tabbable (default: the first container, given tabindex="-1")
*/
fallbackFocus?: FocusTarget | (() => HTMLElement | null) | undefined;
/**
* true (default): back to what had focus before activate(); or an element / selector; false: nowhere
*/
returnFocus?: boolean | FocusTarget | undefined;
/**
* Escape deactivates (default false - the owning overlay decides)
*/
escapeDeactivates?: boolean | ((e: KeyboardEvent) => boolean) | undefined;
/**
* A press outside deactivates (default false)
*/
clickOutsideDeactivates?: boolean | ((e: MouseEvent) => boolean) | undefined;
/**
* Let clicks outside through (default false: they are blocked)
*/
allowOutsideClick?: boolean | ((e: MouseEvent) => boolean) | undefined;
/**
* Let a click, key or input handler on something inside the trap move focus outside on purpose - e.g. "Insert" focusing the editor behind a slideover before closing it (default false; the overlays turn it on)
*/
allowScriptFocus?: boolean | undefined;
/**
* Let focus stay outside: true, 'layers' (floating layers such as toasts and popovers, never the page or a trap beneath), or a function (default false)
*/
allowOutsideFocus?: boolean | "layers" | ((el: Element, e: Event | null) => boolean) | undefined;
/**
* false: keep the trap out of E.get(container) (default true)
*/
register?: boolean | undefined;
/**
* Set `inert` on everything outside the containers while active (default false)
*/
inert?: boolean | undefined;
/**
* Walk open shadow roots (default true)
*/
shadow?: boolean | undefined;
/**
* Focus without scrolling (default true)
*/
preventScroll?: boolean | undefined;
onActivate?: ((trap: FocusTrapInstance) => void) | null | undefined;
onDeactivate?: ((trap: FocusTrapInstance) => void) | null | undefined;
onPause?: ((trap: FocusTrapInstance) => void) | null | undefined;
onUnpause?: ((trap: FocusTrapInstance) => void) | null | undefined;
};
export type FocusTrapInstance = FocusTrap;
/**
* `E.focusTrap(containers, options)` - null when nothing matched - with its helpers.
*/
export type FocusTrapFactory = ((containers: FocusTarget | FocusTarget[], options?: FocusTrapOptions) => FocusTrapInstance | null) & {
tabbables: (containers: Element | Element[], opts?: {
shadow?: boolean;
}) => HTMLElement[];
top: () => FocusTrapInstance | null;
};
import Component from '../component.js';