import { FocusZoneDirection , FocusZoneTabbableElements , IS_FOCUSABLE_ATTRIBUTE , } from '@fluentui/accessibility' ; import * as React from 'react' ; import cx from 'classnames' ; import * as _ from 'lodash' ; import * as keyboardKey from 'keyboard-key' ; import * as ReactDOM from 'react-dom' ; import * as PropTypes from 'prop-types' ; import getElementType from '../utils/getElementType' ; import getUnhandledProps from '../utils/getUnhandledProps' ; import { FocusZoneProps , IFocusZone } from './FocusZone.types' ; import { getNextElement , getPreviousElement , isElementFocusZone , isElementFocusSubZone , isElementTabbable , getWindow , getDocument , getElementIndexPath , getFocusableByIndexPath , getParent , FOCUSZONE_ID_ATTRIBUTE , } from './focusUtilities' ; const TABINDEX = 'tabindex' ; const LARGE_DISTANCE_FROM_CENTER = 999999999 ; const _allInstances = { } ; const _outerZones = new Set ( ) ; interface Point { left : number ; top : number ; } const ALLOWED_INPUT_TYPES = [ 'text' , 'number' , 'password' , 'email' , 'tel' , 'url' , 'search' ] ; export default class FocusZone extends React . Component < FocusZoneProps > implements IFocusZone { static propTypes = { className : PropTypes . string , direction : PropTypes . number , defaultTabbableElement : PropTypes . func , shouldFocusOnMount : PropTypes . bool , shouldResetActiveElementWhenTabFromZone : PropTypes . bool , shouldFocusInnerElementWhenReceivedFocus : PropTypes . bool , disabled : PropTypes . bool , as : PropTypes . elementType as PropTypes . Requireable < React . ElementType > , isCircularNavigation : PropTypes . bool , shouldEnterInnerZone : PropTypes . func , onActiveElementChanged : PropTypes . func , shouldReceiveFocus : PropTypes . func , handleTabKey : PropTypes . number , shouldInputLoseFocusOnArrowKey : PropTypes . func , stopFocusPropagation : PropTypes . bool , onFocus : PropTypes . func , preventDefaultWhenHandled : PropTypes . bool , isRtl : PropTypes . bool , restoreFocusFromRoot : PropTypes . bool , } ; static defaultProps : FocusZoneProps = { isCircularNavigation : false , direction : FocusZoneDirection . bidirectional , as : 'div' , preventDefaultWhenHandled : true , } ; static displayName = 'FocusZone' ; static className = 'ms-FocusZone' ; /** Used for testing purposes only. */ static getOuterZones ( ) { return _outerZones . size ; } _root : { current : HTMLElement | null ; } = { current : null } ; _id : string ; /** The most recently focused child element. */ _activeElement : HTMLElement | null ; /**
     * The index path to the last focused child element.
     */ _lastIndexPath : number [ ] | undefined ; /**
     * Flag to define when we've intentionally parked focus on the root element to temporarily
     * hold focus until items appear within the zone.
     */ _isParked : boolean = false ; _parkedTabIndex : string | null | undefined ; /** The child element with tabindex=0. */ _defaultFocusElement : HTMLElement | null ; _focusAlignment : Point ; _isInnerZone : boolean ; /** Used to allow us to move to next focusable element even when we're focusing on a input element when pressing tab */ _processingTabKey : boolean ; windowElement : Window | null ; constructor ( props ) { super ( props ) ; this . _id = _ . uniqueId ( 'FocusZone' ) ; this . _focusAlignment = { left : 0 , top : 0 , } ; this . _processingTabKey = false ; } componentDidMount ( ) { _allInstances [ this . _id ] = this ; this . setRef ( this ) ; if ( ! this . _root . current ) { return ; } this . windowElement = getWindow ( this . _root . current ) ; let parentElement = getParent ( this . _root . current ) ; const doc = getDocument ( this . _root . current ) ; while ( parentElement && parentElement !== doc . body && parentElement . nodeType === 1 ) { if ( isElementFocusZone ( parentElement ) ) { this . _isInnerZone = true ; break ; } parentElement = getParent ( parentElement ) ; } if ( ! this . _isInnerZone ) { _outerZones . add ( this ) ; if ( this . windowElement && _outerZones . size === 1 ) { this . windowElement . addEventListener ( 'keydown' , this . _onKeyDownCapture , true ) ; } } this . _root . current . addEventListener ( 'blur' , this . _onBlur , true ) ; this . updateTabIndexes ( ) ; if ( this . props . shouldFocusOnMount ) { this . focus ( ) ; } } componentDidUpdate ( ) { if ( ! this . _root . current ) { return ; } const doc = getDocument ( this . _root . current ) ; if ( doc && this . _lastIndexPath && ( doc . activeElement === doc . body || ( this . props . restoreFocusFromRoot && doc . activeElement === this . _root . current ) ) ) { const elementToFocus = getFocusableByIndexPath ( this . _root . current as HTMLElement , this . _lastIndexPath ) ; if ( elementToFocus ) { this . setActiveElement ( elementToFocus , true ) ; elementToFocus . focus ( ) ; this . setParkedFocus ( false ) ; } else { this . setParkedFocus ( true ) ; } } } componentWillUnmount ( ) { delete _allInstances [ this . _id ] ; if ( ! this . _isInnerZone ) { _outerZones . delete ( this ) ; if ( this . windowElement && _outerZones . size === 0 ) { this . windowElement . removeEventListener ( 'keydown' , this . _onKeyDownCapture , true ) ; } } if ( this . _root . current ) { this . _root . current . removeEventListener ( 'blur' , this . _onBlur , true ) ; } this . _activeElement = null ; this . _defaultFocusElement = null ; } render ( ) { const { className } = this . props ; const ElementType = getElementType ( this . props ) ; const unhandledProps = getUnhandledProps ( _ . keys ( FocusZone . propTypes ) as any , this . props ) ; this . evaluateFocusBeforeRender ( ) ; return ( < ElementType { ... unhandledProps } className = { cx ( FocusZone . className , className ) } data - focuszone - id = { this . _id } onKeyDown = { this . _onKeyDown } onFocus = { this . _onFocus } onMouseDownCapture = { this . _onMouseDown } > { this . props . children } < /ElementType>); } param forceIntoFirstElement returns focus ( forceIntoFirstElement = false ) { if ( this . _root . current ) { if ( ! forceIntoFirstElement && this . _root . current . getAttribute ( IS_FOCUSABLE_ATTRIBUTE ) === 'true' && this . _isInnerZone ) { const ownerZoneElement = this . getOwnerZone ( this . _root . current ) as HTMLElement ; if ( ownerZoneElement !== this . _root . current ) { const ownerZone = _allInstances [ ownerZoneElement . getAttribute ( FOCUSZONE_ID_ATTRIBUTE ) as string ] ; return ! ! ownerZone && ownerZone . focusElement ( this . _root . current ) ; } return false ; } if ( ! forceIntoFirstElement && this . _activeElement && this . _root . current . contains ( this . _activeElement ) && isElementTabbable ( this . _activeElement ) ) { this . _activeElement . focus ( ) ; return true ; } const firstChild = this . _root . current . firstChild as HTMLElement ; return this . focusElement ( getNextElement ( this . _root . current , firstChild , true ) as HTMLElement ) ; } return false ; } returns focusLast ( ) { if ( this . _root . current ) { const lastChild = this . _root . current && ( this . _root . current . lastChild as HTMLElement | null ) ; return this . focusElement ( getPreviousElement ( this . _root . current , lastChild , true , true , true ) as HTMLElement ) ; } return false ; } param element returns focusElement ( element ) { const { shouldReceiveFocus } = this . props ; if ( shouldReceiveFocus && ! shouldReceiveFocus ( element ) ) { return false ; } if ( element ) { this . setActiveElement ( element ) ; if ( this . _activeElement ) { this . _activeElement . focus ( ) ; } return true ; } return false ; } setRef = ( elem ) : void => { this . _root . current = ReactDOM . findDOMNode ( elem ) as HTMLElement ; } ; evaluateFocusBeforeRender ( ) { if ( ! this . _root . current ) { return ; } const doc = getDocument ( this . _root . current ) ; if ( ! doc ) { return ; } const focusedElement = doc . activeElement as HTMLElement ; if ( focusedElement !== this . _root . current ) { const shouldRestoreFocus = this . _root . current . contains ( focusedElement ) ; this . _lastIndexPath = shouldRestoreFocus ? getElementIndexPath ( this . _root . current as HTMLElement , doc . activeElement as HTMLElement ) : undefined ; } } /**
     * When focus is in the zone at render time but then all focusable elements are removed,
     * we "park" focus temporarily on the root. Once we update with focusable children, we restore
     * focus to the closest path from previous. If the user tabs away from the parked container,
     * we restore focusability to the pre-parked state.
     */ setParkedFocus ( isParked ) { if ( this . _root . current && this . _isParked !== isParked ) { this . _isParked = isParked ; if ( isParked ) { this . _parkedTabIndex = this . _root . current . getAttribute ( 'tabindex' ) ; this . _root . current . setAttribute ( 'tabindex' , '-1' ) ; this . _root . current . focus ( ) ; } else if ( this . _parkedTabIndex ) { this . _root . current . setAttribute ( 'tabindex' , this . _parkedTabIndex ) ; this . _parkedTabIndex = undefined ; } else { this . _root . current . removeAttribute ( 'tabindex' ) ; } } } _onBlur = ( ) => { this . setParkedFocus ( false ) ; } ; _onFocus = ( ev ) : void => { const { onActiveElementChanged , stopFocusPropagation , shouldFocusInnerElementWhenReceivedFocus , defaultTabbableElement , } = this . props ; let newActiveElement ; const isImmediateDescendant = this . isImmediateDescendantOfZone ( ev . target as HTMLElement ) ; if ( isImmediateDescendant ) { newActiveElement = ev . target as HTMLElement ; } else { let parentElement = ev . target as HTMLElement ; while ( parentElement && parentElement !== this . _root . current ) { if ( isElementTabbable ( parentElement ) && this . isImmediateDescendantOfZone ( parentElement ) ) { newActiveElement = parentElement ; break ; } parentElement = getParent ( parentElement ) as HTMLElement ; } } if ( shouldFocusInnerElementWhenReceivedFocus && ev . target === this . _root . current ) { const maybeElementToFocus = defaultTabbableElement && defaultTabbableElement ( this . _root . current ) ; if ( maybeElementToFocus && isElementTabbable ( maybeElementToFocus ) ) { newActiveElement = maybeElementToFocus ; maybeElementToFocus . focus ( ) ; } else { this . focus ( true ) ; if ( this . _activeElement ) { newActiveElement = null ; } } } if ( newActiveElement && newActiveElement !== this . _activeElement ) { this . _activeElement = newActiveElement ; if ( isImmediateDescendant ) { this . setFocusAlignment ( this . _activeElement ) ; this . updateTabIndexes ( ) ; } } if ( onActiveElementChanged ) { onActiveElementChanged ( this . _activeElement as HTMLElement , ev ) ; } if ( stopFocusPropagation ) { ev . stopPropagation ( ) ; } _ . invoke ( this . props , 'onFocus' , ev ) ; } ; /**
     * Handle global tab presses so that we can patch tabindexes on the fly.
     */ _onKeyDownCapture = ( ev ) => { if ( keyboardKey . getCode ( ev ) === keyboardKey . Tab ) { _outerZones . forEach ( zone => zone . updateTabIndexes ( ) ) ; } } ; _onMouseDown = ( ev ) : void => { const { disabled } = this . props ; if ( disabled ) { return ; } let target = ev . target as HTMLElement ; const path = [ ] ; while ( target && target !== this . _root . current ) { path . push ( target ) ; target = getParent ( target ) as HTMLElement ; } while ( path . length ) { target = path . pop ( ) as HTMLElement ; if ( target && isElementTabbable ( target ) ) { this . setActiveElement ( target , true ) ; } if ( isElementFocusZone ( target ) ) { break ; } } } ; setActiveElement ( element , forceAlignemnt ? ) { const previousActiveElement = this . _activeElement ; this . _activeElement = element ; if ( previousActiveElement ) { if ( isElementFocusZone ( previousActiveElement ) ) { this . updateTabIndexes ( previousActiveElement ) ; } previousActiveElement . tabIndex = - 1 ; } if ( this . _activeElement ) { if ( ! this . _focusAlignment || forceAlignemnt ) { this . setFocusAlignment ( element , true , true ) ; } this . _activeElement . tabIndex = 0 ; } } preventDefaultWhenHandled ( ev ) { this . props . preventDefaultWhenHandled && ev . preventDefault ( ) ; } /**
     * Handle the keystrokes.
     */ _onKeyDown = ( ev ) : boolean | undefined => { const { direction , disabled , shouldEnterInnerZone } = this . props ; if ( disabled ) { return undefined ; } const doc = getDocument ( this . _root . current ) ; if ( this . props . onKeyDown ) { this . props . onKeyDown ( ev ) ; } if ( doc . activeElement === this . _root . current && this . _isInnerZone ) { return undefined ; } if ( ev . isDefaultPrevented ( ) ) { return undefined ; } if ( shouldEnterInnerZone && shouldEnterInnerZone ( ev ) && this . isImmediateDescendantOfZone ( ev . target as HTMLElement ) ) { const innerZone = this . getFirstInnerZone ( ) ; if ( innerZone ) { if ( ! innerZone . focus ( true ) ) { return undefined ; } } else if ( isElementFocusSubZone ( ev . target as HTMLElement ) ) { if ( ! this . focusElement ( getNextElement ( ev . target as HTMLElement , ( ev . target as HTMLElement ) . firstChild as HTMLElement , true ) as HTMLElement ) ) { return undefined ; } } else { return undefined ; } } else if ( ev . altKey ) { return undefined ; } else { switch ( keyboardKey . getCode ( ev ) ) { case keyboardKey . Spacebar : if ( this . tryInvokeClickForFocusable ( ev . target as HTMLElement ) ) { break ; } return undefined ; case keyboardKey . ArrowLeft : if ( direction !== FocusZoneDirection . vertical ) { this . preventDefaultWhenHandled ( ev ) ; if ( this . moveFocusLeft ( ) ) { break ; } } return undefined ; case keyboardKey . ArrowRight : if ( direction !== FocusZoneDirection . vertical ) { this . preventDefaultWhenHandled ( ev ) ; if ( this . moveFocusRight ( ) ) { break ; } } return undefined ; case keyboardKey . ArrowUp : if ( direction !== FocusZoneDirection . horizontal ) { this . preventDefaultWhenHandled ( ev ) ; if ( this . moveFocusUp ( ) ) { break ; } } return undefined ; case keyboardKey . ArrowDown : if ( direction !== FocusZoneDirection . horizontal ) { this . preventDefaultWhenHandled ( ev ) ; if ( this . moveFocusDown ( ) ) { break ; } } return undefined ; case keyboardKey . Tab : if ( this . props . handleTabKey === FocusZoneTabbableElements . all || ( this . props . handleTabKey === FocusZoneTabbableElements . inputOnly && this . isElementInput ( ev . target as HTMLElement ) ) ) { let focusChanged = false ; this . _processingTabKey = true ; if ( direction === FocusZoneDirection . vertical ) { focusChanged = ev . shiftKey ? this . moveFocusUp ( ) : this . moveFocusDown ( ) ; } else if ( direction === FocusZoneDirection . horizontal || direction === FocusZoneDirection . bidirectional ) { const tabWithDirection = this . props . isRtl ? ! ev . shiftKey : ev . shiftKey ; focusChanged = tabWithDirection ? this . moveFocusLeft ( ) : this . moveFocusRight ( ) ; } this . _processingTabKey = false ; if ( focusChanged ) { break ; } } else if ( this . props . shouldResetActiveElementWhenTabFromZone ) { this . _activeElement = null ; } return undefined ; case keyboardKey . Home : if ( this . isContentEditableElement ( ev . target as HTMLElement ) || ( this . isElementInput ( ev . target as HTMLElement ) && ! this . shouldInputLoseFocus ( ev . target as HTMLInputElement , false ) ) ) { return false ; } const firstChild = this . _root . current && ( this . _root . current . firstChild as HTMLElement | null ) ; if ( this . _root . current && firstChild && this . focusElement ( getNextElement ( this . _root . current , firstChild , true ) as HTMLElement ) ) { break ; } return undefined ; case keyboardKey . End : if ( this . isContentEditableElement ( ev . target as HTMLElement ) || ( this . isElementInput ( ev . target as HTMLElement ) && ! this . shouldInputLoseFocus ( ev . target as HTMLInputElement , false ) ) ) { return false ; } const lastChild = this . _root . current && ( this . _root . current . lastChild as HTMLElement | null ) ; if ( this . _root . current && this . focusElement ( getPreviousElement ( this . _root . current , lastChild , true , true , true ) as HTMLElement ) ) { break ; } return undefined ; case keyboardKey . Enter : if ( this . tryInvokeClickForFocusable ( ev . target as HTMLElement ) ) { break ; } return undefined ; default : return undefined ; } } ev . preventDefault ( ) ; ev . stopPropagation ( ) ; return undefined ; } ; /**
     * Walk up the dom try to find a focusable element.
     * TODO
     */ tryInvokeClickForFocusable ( ) { return false ; } /**
     * Traverse to find first child zone.
     */ getFirstInnerZone ( forRootElement ? ) { const rootElement = forRootElement || this . _activeElement || this . _root . current ; if ( ! rootElement ) { return null ; } if ( isElementFocusZone ( rootElement ) ) { return _allInstances [ rootElement . getAttribute ( FOCUSZONE_ID_ATTRIBUTE ) as string ] ; } let child = rootElement . firstElementChild as HTMLElement | null ; while ( child ) { if ( isElementFocusZone ( child ) ) { return _allInstances [ child . getAttribute ( FOCUSZONE_ID_ATTRIBUTE ) as string ] ; } const match = this . getFirstInnerZone ( child ) ; if ( match ) { return match ; } child = child . nextElementSibling as HTMLElement | null ; } return null ; } moveFocus ( isForward , getDistanceFromCenter , ev ? , useDefaultWrap = true ) { let element = this . _activeElement ; let candidateDistance = - 1 ; let candidateElement = undefined ; let changedFocus = false ; const isBidirectional = this . props . direction === FocusZoneDirection . bidirectional ; if ( ! element || ! this . _root . current ) { return false ; } if ( this . isElementInput ( element ) ) { if ( ! this . shouldInputLoseFocus ( element as HTMLInputElement , isForward ) ) { return false ; } } const activeRect = isBidirectional ? element . getBoundingClientRect ( ) : null ; do { element = ( isForward ? getNextElement ( this . _root . current , element ) : getPreviousElement ( this . _root . current , element ) ) as HTMLElement ; if ( isBidirectional ) { if ( element ) { const targetRect = element . getBoundingClientRect ( ) ; const elementDistance = getDistanceFromCenter ( activeRect as ClientRect , targetRect ) ; if ( elementDistance === - 1 && candidateDistance === - 1 ) { candidateElement = element ; break ; } if ( elementDistance > - 1 && ( candidateDistance === - 1 || elementDistance < candidateDistance ) ) { candidateDistance = elementDistance ; candidateElement = element ; } if ( candidateDistance >= 0 && elementDistance < 0 ) { break ; } } } else { candidateElement = element ; break ; } } while ( element ) ; if ( candidateElement && candidateElement !== this . _activeElement ) { changedFocus = true ; this . focusElement ( candidateElement ) ; } else if ( this . props . isCircularNavigation && useDefaultWrap ) { if ( isForward ) { return this . focusElement ( getNextElement ( this . _root . current , this . _root . current . firstElementChild as HTMLElement , true ) as HTMLElement ) ; } return this . focusElement ( getPreviousElement ( this . _root . current , this . _root . current . lastElementChild as HTMLElement , true , true , true ) as HTMLElement ) ; } return changedFocus ; } moveFocusDown ( ) { let targetTop = - 1 ; const leftAlignment = this . _focusAlignment . left ; if ( this . moveFocus ( true , ( activeRect , targetRect ) => { let distance = - 1 ; const targetRectTop = Math . floor ( targetRect . top ) ; const activeRectBottom = Math . floor ( activeRect . bottom ) ; if ( targetRectTop < activeRectBottom ) { return LARGE_DISTANCE_FROM_CENTER ; } if ( ( targetTop === - 1 && targetRectTop >= activeRectBottom ) || targetRectTop === targetTop ) { targetTop = targetRectTop ; if ( leftAlignment >= targetRect . left && leftAlignment <= targetRect . left + targetRect . width ) { distance = 0 ; } else { distance = Math . abs ( targetRect . left + targetRect . width / 2 - leftAlignment ) ; } } return distance ; } ) ) { this . setFocusAlignment ( this . _activeElement as HTMLElement , true , true ) ; return true ; } return false ; } moveFocusUp ( ) { let targetTop = - 1 ; const leftAlignment = this . _focusAlignment . left ; if ( this . moveFocus ( false , ( activeRect , targetRect ) => { let distance = - 1 ; const targetRectBottom = Math . floor ( targetRect . bottom ) ; const targetRectTop = Math . floor ( targetRect . top ) ; const activeRectTop = Math . floor ( activeRect . top ) ; if ( targetRectBottom > activeRectTop ) { return LARGE_DISTANCE_FROM_CENTER ; } if ( ( targetTop === - 1 && targetRectBottom <= activeRectTop ) || targetRectTop === targetTop ) { targetTop = targetRectTop ; if ( leftAlignment >= targetRect . left && leftAlignment <= targetRect . left + targetRect . width ) { distance = 0 ; } else { distance = Math . abs ( targetRect . left + targetRect . width / 2 - leftAlignment ) ; } } return distance ; } ) ) { this . setFocusAlignment ( this . _activeElement as HTMLElement , true , true ) ; return true ; } return false ; } moveFocusLeft ( ) { if ( this . moveFocus ( this . props . isRtl , ( activeRect , targetRect ) => { let distance = - 1 ; let topBottomComparison ; if ( this . props . isRtl ) { topBottomComparison = parseFloat ( targetRect . top . toFixed ( 3 ) ) < parseFloat ( activeRect . bottom . toFixed ( 3 ) ) ; } else { topBottomComparison = parseFloat ( targetRect . bottom . toFixed ( 3 ) ) > parseFloat ( activeRect . top . toFixed ( 3 ) ) ; } if ( topBottomComparison && targetRect . right <= activeRect . right && this . props . direction !== FocusZoneDirection . vertical ) { distance = activeRect . right - targetRect . right ; } return distance ; } , undefined , true ) ) { this . setFocusAlignment ( this . _activeElement as HTMLElement , true , false ) ; return true ; } return false ; } moveFocusRight ( ) { if ( this . moveFocus ( ! this . props . isRtl , ( activeRect , targetRect ) => { let distance = - 1 ; let topBottomComparison ; if ( this . props . isRtl ) { topBottomComparison = parseFloat ( targetRect . bottom . toFixed ( 3 ) ) > parseFloat ( activeRect . top . toFixed ( 3 ) ) ; } else { topBottomComparison = parseFloat ( targetRect . top . toFixed ( 3 ) ) < parseFloat ( activeRect . bottom . toFixed ( 3 ) ) ; } if ( topBottomComparison && targetRect . left >= activeRect . left && this . props . direction !== FocusZoneDirection . vertical ) { distance = targetRect . left - activeRect . left ; } return distance ; } , undefined , true ) ) { this . setFocusAlignment ( this . _activeElement as HTMLElement , true , false ) ; return true ; } return false ; } setFocusAlignment ( element , isHorizontal ? , isVertical ? ) { if ( this . props . direction === FocusZoneDirection . bidirectional && ( ! this . _focusAlignment || isHorizontal || isVertical ) ) { const rect = element . getBoundingClientRect ( ) ; const left = rect . left + rect . width / 2 ; const top = rect . top + rect . height / 2 ; if ( ! this . _focusAlignment ) { this . _focusAlignment = { left , top } ; } if ( isHorizontal ) { this . _focusAlignment . left = left ; } if ( isVertical ) { this . _focusAlignment . top = top ; } } } isImmediateDescendantOfZone ( element ? ) { return this . getOwnerZone ( element ) === this . _root . current ; } getOwnerZone ( element ? ) { const doc = getDocument ( this . _root . current ) ; let parentElement = getParent ( element as HTMLElement ) ; while ( parentElement && parentElement !== this . _root . current && parentElement !== doc . body ) { if ( isElementFocusZone ( parentElement ) ) { return parentElement ; } parentElement = getParent ( parentElement ) ; } return this . _root . current ; } updateTabIndexes ( onElement ? ) { let element = onElement ; if ( ! this . _activeElement && this . props . defaultTabbableElement ) { this . _activeElement = this . props . defaultTabbableElement ( this . _root . current ) ; } if ( ! element && this . _root . current ) { this . _defaultFocusElement = null ; element = this . _root . current ; if ( this . _activeElement && ! element . contains ( this . _activeElement ) ) { this . _activeElement = null ; } } if ( this . _activeElement && ! isElementTabbable ( this . _activeElement ) ) { this . _activeElement = null ; } const childNodes = element && element . children ; for ( let childIndex = 0 ; childNodes && childIndex < childNodes . length ; childIndex ++ ) { const child = childNodes [ childIndex ] as HTMLElement ; if ( ! isElementFocusZone ( child ) ) { if ( child . getAttribute && child . getAttribute ( IS_FOCUSABLE_ATTRIBUTE ) === 'false' ) { child . setAttribute ( TABINDEX , '-1' ) ; } if ( isElementTabbable ( child ) ) { if ( this . props . disabled ) { child . setAttribute ( TABINDEX , '-1' ) ; } else if ( ! this . _isInnerZone && ( ( ! this . _activeElement && ! this . _defaultFocusElement ) || this . _activeElement === child ) ) { this . _defaultFocusElement = child ; if ( child . getAttribute ( TABINDEX ) !== '0' ) { child . setAttribute ( TABINDEX , '0' ) ; } } else if ( child . getAttribute ( TABINDEX ) !== '-1' ) { child . setAttribute ( TABINDEX , '-1' ) ; } } else if ( child . tagName === 'svg' && child . getAttribute ( 'focusable' ) !== 'false' ) { child . setAttribute ( 'focusable' , 'false' ) ; } } else if ( child . getAttribute ( IS_FOCUSABLE_ATTRIBUTE ) === 'true' ) { if ( ! this . _isInnerZone && ( ( ! this . _activeElement && ! this . _defaultFocusElement ) || this . _activeElement === child ) ) { this . _defaultFocusElement = child ; if ( child . getAttribute ( TABINDEX ) !== '0' ) { child . setAttribute ( TABINDEX , '0' ) ; } } else if ( child . getAttribute ( TABINDEX ) !== '-1' ) { child . setAttribute ( TABINDEX , '-1' ) ; } } this . updateTabIndexes ( child ) ; } } isContentEditableElement ( element ) { return element && element . getAttribute ( 'contenteditable' ) === 'true' ; } isElementInput ( element ) { if ( element && element . tagName && ( element . tagName . toLowerCase ( ) === 'input' || element . tagName . toLowerCase ( ) === 'textarea' ) ) { return true ; } return false ; } shouldInputLoseFocus ( element , isForward ? ) { if ( ! this . _processingTabKey && element && element . type && ALLOWED_INPUT_TYPES . indexOf ( element . type . toLowerCase ( ) ) > - 1 ) { const selectionStart = element . selectionStart ; const selectionEnd = element . selectionEnd ; const isRangeSelected = selectionStart !== selectionEnd ; const inputValue = element . value ; if ( isRangeSelected || ( selectionStart ! > 0 && ! isForward ) || ( selectionStart !== inputValue . length && isForward ) || ( ! ! this . props . handleTabKey && ! ( this . props . shouldInputLoseFocusOnArrowKey && this . props . shouldInputLoseFocusOnArrowKey ( element ) ) ) ) { return false ; } } return true ; } }