mirror of
https://github.com/zammad/zammad
synced 2026-05-24 09:48:36 +00:00
45 lines
1.5 KiB
TypeScript
45 lines
1.5 KiB
TypeScript
// Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
|
|
|
|
export interface FocusableOptions {
|
|
ignoreTabindex?: boolean
|
|
}
|
|
|
|
const FOCUSABLE_QUERY =
|
|
'button, a[href]:not([href=""]), input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
|
|
export const isElementVisible = (el: HTMLElement) => {
|
|
// In Vitest, a visibility check is unreliable due to the used JSDOM test environment.
|
|
// Therefore, we always assume the element is visible.
|
|
if (import.meta.env.VITEST) return true
|
|
return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length) // from jQuery
|
|
}
|
|
|
|
const isNegativeTabIndex = (el: HTMLElement) => {
|
|
const tabIndex = el.getAttribute('tabindex')
|
|
return tabIndex && parseInt(tabIndex, 10) < 0
|
|
}
|
|
|
|
export const getFocusableElements = (
|
|
container?: Maybe<HTMLElement>,
|
|
options: FocusableOptions = {},
|
|
) => {
|
|
return Array.from<HTMLElement>(container?.querySelectorAll(FOCUSABLE_QUERY) || []).filter(
|
|
(el) =>
|
|
isElementVisible(el) &&
|
|
(options.ignoreTabindex || !isNegativeTabIndex(el)) &&
|
|
!el.hasAttribute('disabled') &&
|
|
el.getAttribute('aria-disabled') !== 'true',
|
|
)
|
|
}
|
|
|
|
export const getFirstFocusableElement = (container?: Maybe<HTMLElement>) => {
|
|
return getFocusableElements(container)[0]
|
|
}
|
|
|
|
export const getPreviousFocusableElement = (currentElement?: Maybe<HTMLElement>) => {
|
|
if (!currentElement) return null
|
|
|
|
const focusableElements = getFocusableElements(document.body)
|
|
|
|
return focusableElements[focusableElements.indexOf(currentElement) - 1]
|
|
}
|