import { IS_FOCUSABLE_ATTRIBUTE } from '@fluentui/accessibility' ; export const IS_VISIBLE_ATTRIBUTE = 'data-is-visible' ; export const FOCUSZONE_ID_ATTRIBUTE = 'data-focuszone-id' ; export const FOCUSZONE_SUB_ATTRIBUTE = 'data-is-sub-focuszone' ; export const HIDDEN_FROM_ACC_TREE = 'data-is-hidden-from-acc-tree' ; 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 ? , checkNode ? ) { return getNextElement ( rootElement , currentElement , checkNode , false , false , includeElementsInFocusZones , true ) ; } param rootElement param currentElement param includeElementsInFocusZones public export function getLastTabbable ( rootElement , currentElement , includeElementsInFocusZones ? , checkNode ? ) { return getPreviousElement ( rootElement , currentElement , checkNode , false , true , includeElementsInFocusZones , true ) ; } public export function getPreviousElement ( rootElement , currentElement , checkNode ? , suppressParentTraversal ? , traverseChildren ? , includeElementsInFocusZones ? , tabbable ? ) { if ( ! currentElement || currentElement === rootElement ) { return null ; } const isCurrentElementVisible = isElementVisible ( currentElement ) ; if ( traverseChildren && isCurrentElementVisible && ( includeElementsInFocusZones || ! ( isElementFocusZone ( currentElement ) || isElementFocusSubZone ( currentElement ) ) ) ) { const childMatch = getPreviousElement ( rootElement , currentElement . lastElementChild as HTMLElement , true , true , true , includeElementsInFocusZones , tabbable ) ; if ( childMatch ) { if ( ( tabbable && isElementTabbable ( childMatch , true ) ) || ! tabbable ) { return childMatch ; } const childMatchSiblingMatch = getPreviousElement ( rootElement , childMatch . previousElementSibling as HTMLElement , true , true , true , includeElementsInFocusZones , 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 , tabbable ) ; if ( childMatchParentMatch ) { return childMatchParentMatch ; } childMatchParent = childMatchParent . parentElement ; } } } if ( checkNode && isCurrentElementVisible && isElementTabbable ( currentElement , tabbable ) ) { return currentElement ; } const siblingMatch = getPreviousElement ( rootElement , currentElement . previousElementSibling as HTMLElement , true , true , true , includeElementsInFocusZones , tabbable ) ; if ( siblingMatch ) { return siblingMatch ; } if ( ! suppressParentTraversal ) { return getPreviousElement ( rootElement , currentElement . parentElement , true , false , false , includeElementsInFocusZones , tabbable ) ; } return null ; } public export function getNextElement ( rootElement , currentElement , checkNode ? , suppressParentTraversal ? , suppressChildTraversal ? , includeElementsInFocusZones ? , tabbable ? ) { if ( ! currentElement || ( currentElement === rootElement && suppressChildTraversal ) ) { return null ; } const 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 , tabbable ) ; if ( childMatch ) { return childMatch ; } } if ( currentElement === rootElement ) { return null ; } const siblingMatch = getNextElement ( rootElement , currentElement . nextElementSibling as HTMLElement , true , true , false , includeElementsInFocusZones , tabbable ) ; if ( siblingMatch ) { return siblingMatch ; } if ( ! suppressParentTraversal ) { return getNextElement ( rootElement , currentElement . parentElement , false , false , true , includeElementsInFocusZones , 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 ) ; } } const isFocusableAttribute = element . getAttribute ? element . getAttribute ( IS_FOCUSABLE_ATTRIBUTE ) : null ; const 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' ) ; } 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 ; } ) ; } } } public export function getWindow ( rootElement ? ) { return ( ( rootElement && rootElement . ownerDocument && rootElement . ownerDocument . defaultView ) || window ) ; } public export function getDocument ( rootElement ? ) { return ( rootElement && rootElement . ownerDocument ) || document ; } param child export function getParent ( child ) { return child && child . parentElement ; } /**
 * Finds the closest focusable element via an index path from a parent. See
 * `getElementIndexPath` for getting an index path from an element to a child.
 */ export function getFocusableByIndexPath ( parent , path ) { let element = parent ; for ( const index of path ) { const nextChild = element . children [ Math . min ( index , element . children . length - 1 ) ] as HTMLElement ; if ( ! nextChild ) { break ; } element = nextChild ; } element = isElementTabbable ( element ) && isElementVisible ( element ) ? element : getNextElement ( parent , element , true ) || getPreviousElement ( parent , element ) ! ; return element as HTMLElement ; } /**
 * Finds the element index path from a parent element to a child element.
 *
 * If you had this node structure: "A has children [B, C] and C has child D",
 * the index path from A to D would be [1, 0], or `parent.chidren[1].children[0]`.
 */ export function getElementIndexPath ( fromElement , toElement ) { const path = [ ] ; let currentElement = toElement ; while ( currentElement && fromElement && currentElement !== fromElement ) { const parent = getParent ( currentElement ) ; if ( parent === null ) { return [ ] ; } path . unshift ( Array . prototype . indexOf . call ( parent . children , currentElement ) ) ; currentElement = parent ; } return path ; }