import { FOCUSABLE_ELEMENTS, toArray } from '@uppy/core/utils'
import getActiveOverlayEl from './getActiveOverlayEl.js'
function focusOnFirstNode(event: Event, nodes: HTMLElement[]) {
const node = nodes[0]
if (node) {
node.focus()
event.preventDefault()
}
}
function focusOnLastNode(event: Event, nodes: HTMLElement[]) {
const node = nodes[nodes.length - 1]
if (node) {
node.focus()
event.preventDefault()
}
}
// ___Why not just use (focusedItemIndex === -1)?
// Firefox thinks
is focusable, but we don't have s in our FOCUSABLE_ELEMENTS. Which means that if we tab into
// the , code will think that we are not in the active overlay, and we should focusOnFirstNode() of the currently
// active overlay!
function isFocusInOverlay(activeOverlayEl: HTMLElement) {
return activeOverlayEl.contains(document.activeElement)
}
function trapFocus(
event: KeyboardEvent,
activeOverlayType: string | null | undefined,
dashboardEl: HTMLElement,
): void {
const activeOverlayEl = getActiveOverlayEl(dashboardEl, activeOverlayType)
const focusableNodes = toArray(
activeOverlayEl.querySelectorAll(FOCUSABLE_ELEMENTS.join(',')),
)
const focusedItemIndex = focusableNodes.indexOf(
document.activeElement as HTMLElement,
)
// If we pressed tab, and focus is not yet within the current overlay - focus on
// the first element within the current overlay.
// This is a safety measure (for when user returns from another tab e.g.), most
// plugins will try to focus on some important element as it loads.
if (!isFocusInOverlay(activeOverlayEl)) {
focusOnFirstNode(event, focusableNodes)
// If we pressed shift + tab, and we're on the first element of a modal
} else if (event.shiftKey && focusedItemIndex === 0) {
focusOnLastNode(event, focusableNodes)
// If we pressed tab, and we're on the last element of the modal
} else if (
!event.shiftKey &&
focusedItemIndex === focusableNodes.length - 1
) {
focusOnFirstNode(event, focusableNodes)
}
}
// Traps focus inside of the currently open overlay (e.g. Dashboard),
// never lets focus disappear from the modal.
export { trapFocus as forModal }
// Traps focus inside of the currently open overlay, unless overlay is null - then let the user tab away.
export function forInline(
event: KeyboardEvent,
activeOverlayType: string | null,
dashboardEl: HTMLElement,
): void {
// ___When we're in the bare 'Drop files here, paste, browse or import from' screen
if (activeOverlayType === null) {
// Do nothing and let the browser handle it, user can tab away from Uppy to other elements on the page
// ___When there is some overlay with 'Done' button
} else {
// Trap the focus inside this overlay!
// User can close the overlay (click 'Done') if they want to travel away from Uppy.
trapFocus(event, activeOverlayType, dashboardEl)
}
}