import { elementContainsAttribute , elementContains , getDocument , getWindow } from './dom' ; const IS_FOCUSABLE_ATTRIBUTE = 'data-is-focusable' ; const IS_VISIBLE_ATTRIBUTE = 'data-is-visible' ; const FOCUSZONE_ID_ATTRIBUTE = 'data-focuszone-id' ; const FOCUSZONE_SUB_ATTRIBUTE = 'data-is-sub-focuszone' ; public export function getFirstFocusable ( rootElement , currentElement , includeElementsInFocusZones ? ) { return getNextElement ( rootElement , currentElement , true , false , false , includeElementsInFocusZones ) ; } public export function getLastFocusable ( rootElement , currentElement , includeElementsInFocusZones ? ) { return getPreviousElement ( rootElement , currentElement , true , false , true , includeElementsInFocusZones ) ; } param rootElement param currentElement param includeElementsInFocusZones public export function getFirstTabbable ( rootElement , currentElement , includeElementsInFocusZones ? ) { return getNextElement ( rootElement , currentElement , true , false , false , includeElementsInFocusZones , false , true ) ; } param rootElement param currentElement param includeElementsInFocusZones public export function getLastTabbable ( rootElement , currentElement , includeElementsInFocusZones ? ) { return getPreviousElement ( rootElement , currentElement , true , false , true , includeElementsInFocusZones , false , true ) ; } public param rootElement returns export function focusFirstChild ( rootElement ) { let element = getNextElement ( rootElement , rootElement , true , false , false , true ) ; if ( element ) { focusAsync ( element ) ; return true ; } return false ; } public export function getPreviousElement ( rootElement , currentElement , checkNode ? , suppressParentTraversal ? , traverseChildren ? , includeElementsInFocusZones ? , allowFocusRoot ? , tabbable ? ) { if ( ! currentElement || ( ! allowFocusRoot && currentElement === rootElement ) ) { return null ; } let isCurrentElementVisible = isElementVisible ( currentElement ) ; if ( traverseChildren && isCurrentElementVisible && ( includeElementsInFocusZones || ! ( isElementFocusZone ( currentElement ) || isElementFocusSubZone ( currentElement ) ) ) ) { const childMatch = getPreviousElement ( rootElement , currentElement . lastElementChild as HTMLElement , true , true , true , includeElementsInFocusZones , allowFocusRoot , tabbable ) ; if ( childMatch ) { if ( ( tabbable && isElementTabbable ( childMatch , true ) ) || ! tabbable ) { return childMatch ; } const childMatchSiblingMatch = getPreviousElement ( rootElement , childMatch . previousElementSibling as HTMLElement , true , true , true , includeElementsInFocusZones , allowFocusRoot , tabbable ) ; if ( childMatchSiblingMatch ) { return childMatchSiblingMatch ; } let childMatchParent = childMatch . parentElement ; while ( childMatchParent && childMatchParent !== currentElement ) { const childMatchParentMatch = getPreviousElement ( rootElement , childMatchParent . previousElementSibling as HTMLElement , true , true , true , includeElementsInFocusZones , allowFocusRoot , tabbable ) ; if ( childMatchParentMatch ) { return childMatchParentMatch ; } childMatchParent = childMatchParent . parentElement ; } } } if ( checkNode && isCurrentElementVisible && isElementTabbable ( currentElement ) ) { return currentElement ; } const siblingMatch = getPreviousElement ( rootElement , currentElement . previousElementSibling as HTMLElement , true , true , true , includeElementsInFocusZones , allowFocusRoot , tabbable ) ; if ( siblingMatch ) { return siblingMatch ; } if ( ! suppressParentTraversal ) { return getPreviousElement ( rootElement , currentElement . parentElement , true , false , false , includeElementsInFocusZones , allowFocusRoot , tabbable ) ; } return null ; } public export function getNextElement ( rootElement , currentElement , checkNode ? , suppressParentTraversal ? , suppressChildTraversal ? , includeElementsInFocusZones ? , allowFocusRoot ? , tabbable ? ) { if ( ! currentElement || ( currentElement === rootElement && suppressChildTraversal && ! allowFocusRoot ) ) { return null ; } let isCurrentElementVisible = isElementVisible ( currentElement ) ; if ( checkNode && isCurrentElementVisible && isElementTabbable ( currentElement , tabbable ) ) { return currentElement ; } if ( ! suppressChildTraversal && isCurrentElementVisible && ( includeElementsInFocusZones || ! ( isElementFocusZone ( currentElement ) || isElementFocusSubZone ( currentElement ) ) ) ) { const childMatch = getNextElement ( rootElement , currentElement . firstElementChild as HTMLElement , true , true , false , includeElementsInFocusZones , allowFocusRoot , tabbable ) ; if ( childMatch ) { return childMatch ; } } if ( currentElement === rootElement ) { return null ; } const siblingMatch = getNextElement ( rootElement , currentElement . nextElementSibling as HTMLElement , true , true , false , includeElementsInFocusZones , allowFocusRoot , tabbable ) ; if ( siblingMatch ) { return siblingMatch ; } if ( ! suppressParentTraversal ) { return getNextElement ( rootElement , currentElement . parentElement , false , false , true , includeElementsInFocusZones , allowFocusRoot , tabbable ) ; } return null ; } public export function isElementVisible ( element ) { if ( ! element || ! element . getAttribute ) { return false ; } const visibilityAttribute = element . getAttribute ( IS_VISIBLE_ATTRIBUTE ) ; if ( visibilityAttribute !== null && visibilityAttribute !== undefined ) { return visibilityAttribute === 'true' ; } return ( element . offsetHeight !== 0 || element . offsetParent !== null || ( element as any ) . isVisible === true ) ; } public export function isElementTabbable ( element , checkTabIndex ? ) { if ( ! element || ( element as HTMLButtonElement ) . disabled ) { return false ; } let tabIndex = 0 ; let tabIndexAttributeValue = null ; if ( element && element . getAttribute ) { tabIndexAttributeValue = element . getAttribute ( 'tabIndex' ) ; if ( tabIndexAttributeValue ) { tabIndex = parseInt ( tabIndexAttributeValue , 10 ) ; } } let isFocusableAttribute = element . getAttribute ? element . getAttribute ( IS_FOCUSABLE_ATTRIBUTE ) : null ; let isTabIndexSet = tabIndexAttributeValue !== null && tabIndex >= 0 ; const result = ! ! element && isFocusableAttribute !== 'false' && ( element . tagName === 'A' || element . tagName === 'BUTTON' || element . tagName === 'INPUT' || element . tagName === 'TEXTAREA' || isFocusableAttribute === 'true' || isTabIndexSet || ( element . getAttribute && element . getAttribute ( 'role' ) === 'button' ) ) ; return checkTabIndex ? tabIndex !== - 1 && result : result ; } public export function isElementFocusZone ( element ? ) { return ! ! ( element && element . getAttribute && ! ! element . getAttribute ( FOCUSZONE_ID_ATTRIBUTE ) ) ; } public export function isElementFocusSubZone ( element ? ) { return ! ! ( element && element . getAttribute && element . getAttribute ( FOCUSZONE_SUB_ATTRIBUTE ) === 'true' ) ; } public export function doesElementContainFocus ( element ) { let document = getDocument ( element ) ; let currentActiveElement = document && ( document . activeElement as HTMLElement ) ; if ( currentActiveElement && elementContains ( element , currentActiveElement ) ) { return true ; } return false ; } param element param noWrapDataAttribute returns export function shouldWrapFocus ( element , noWrapDataAttribute ) { return elementContainsAttribute ( element , noWrapDataAttribute ) === 'true' ? false : true ; } let targetToFocusOnNextRepaint = undefined ; param element export function focusAsync ( element ) { if ( element ) { if ( targetToFocusOnNextRepaint ) { targetToFocusOnNextRepaint = element ; return ; } targetToFocusOnNextRepaint = element ; const win = getWindow ( element as Element ) ; if ( win ) { win . requestAnimationFrame ( ( ) => { targetToFocusOnNextRepaint && targetToFocusOnNextRepaint . focus ( ) ; targetToFocusOnNextRepaint = undefined ; } ) ; } } }