packageDocumentation module import * as React from "react" ; import { Logger } from "@itwin/core-bentley" ; import { UiCore } from "../UiCore" ; function isFocusable ( element ) { if ( ! element || element . tabIndex < 0 ) return false ; if ( element . classList && element . classList . contains ( "core-focus-trap-ignore-initial" ) ) return false ; if ( element . getAttribute && ( typeof element . getAttribute === "function" ) && element . getAttribute ( "disabled" ) !== null ) return false ; if ( element . tabIndex > 0 || ( element . tabIndex === 0 && element . getAttribute ( "tabIndex" ) !== null ) ) return true ; switch ( element . nodeName ) { case "A" : const anchorElement = element as HTMLAnchorElement ; return ! ! anchorElement . href && anchorElement . rel !== "ignore" ; case "INPUT" : const inputElement = element as HTMLInputElement ; return inputElement . type !== "hidden" && inputElement . type !== "file" ; case "BUTTON" : case "SELECT" : case "TEXTAREA" : return true ; default : return false ; } } function processFindFocusableDescendant ( element ) { if ( ! element ) return null ; for ( const child of element . childNodes ) { if ( isFocusable ( child as HTMLElement ) ) return child as HTMLElement ; const focusable = processFindFocusableDescendant ( child as HTMLElement ) ; if ( focusable ) return focusable ; } return null ; } function findFirstFocusableDescendant ( focusContainer ) { return processFindFocusableDescendant ( focusContainer ) ; } function processFindLastFocusableDescendant ( element ) { for ( let i = element . childNodes . length - 1 ; i >= 0 ; i -- ) { const child = element . childNodes [ i ] as HTMLElement ; const focusable = processFindLastFocusableDescendant ( child ) ; if ( focusable ) return focusable ; if ( isFocusable ( child ) ) return child ; } return null ; } function findLastFocusableDescendant ( focusContainer ) { return processFindLastFocusableDescendant ( focusContainer ) ; } function getInitialFocusElement ( focusContainer , initialFocusSpec ) { if ( ! focusContainer ) return null ; if ( initialFocusSpec ) { if ( typeof initialFocusSpec === "string" ) { const node = focusContainer . querySelector ( initialFocusSpec ) ; if ( node ) { return node as HTMLElement ; } else { Logger . logError ( `${ UiCore . packageName }.FocusTrap` , `Unable to locate element via selector ${ initialFocusSpec }` ) ; } } else { return initialFocusSpec . current ; } } return findFirstFocusableDescendant ( focusContainer ) ; } function attemptFocus ( element , preventScroll ) { if ( ! isFocusable ( element ) ) return false ; try { if ( document . activeElement !== element ) element . focus ( { preventScroll : preventScroll ? true : false } ) ; } catch ( e ) { return false ; } return ( document . activeElement === element ) ; } internal export function focusIntoContainer ( focusContainer , initialFocusElement ? ) { let result = false ; const focusElement = getInitialFocusElement ( focusContainer , initialFocusElement ) ; if ( focusElement ) { setTimeout ( ( ) => { attemptFocus ( focusElement , true ) ; } , 60 ) ; result = true ; } return result ; } internal export interface FocusTrapProps extends React . AllHTMLAttributes < any > { /** child components */ children : React . ReactNode ; /** if active is not true then no trapping of focus is attempted. */ active ? : boolean ; /** restore focus to element that had focus before trap was activated */ returnFocusOnDeactivate : boolean ; /** Optional reference to element to receive focus when trap is activated. Optionally a CSS Selector
     * string can be supplied to locate an element in the FocusTrap container. If no specification is defined
     * the first focusable element is used.
     */ initialFocusElement ? : React . RefObject < HTMLElement > | string ; } internal export function FocusTrap ( props ) { const restoreFocusElement = React . useRef < Element | null > ( null ) ; const initialFocusElement = React . useRef < Element | null > ( null ) ; const focusContainer = React . useRef < HTMLDivElement | null > ( null ) ; const isInitialMount = React . useRef ( true ) ; const timeoutRef = React . useRef < number | undefined > ( ) ; React . useEffect ( ( ) => { if ( isInitialMount . current ) { isInitialMount . current = false ; if ( props . active ) { if ( props . returnFocusOnDeactivate ) { restoreFocusElement . current = document . activeElement ; } initialFocusElement . current = getInitialFocusElement ( focusContainer . current , props . initialFocusElement ) ; if ( initialFocusElement . current ) { timeoutRef . current = window . setTimeout ( ( ) => { attemptFocus ( ( initialFocusElement . current as HTMLElement ) , true ) ; } , 60 ) ; } } } } , [ props . children , props . initialFocusElement , props . active , props . returnFocusOnDeactivate ] ) ; React . useEffect ( ( ) => { return ( ) => { window . clearTimeout ( timeoutRef . current ) ; if ( restoreFocusElement . current ) ( restoreFocusElement . current as HTMLElement ) . focus ( { preventScroll : true } ) ; } ; } , [ ] ) ; const cycleFocusToEnd = React . useCallback ( ( event ) => { if ( ! props . active ) return ; if ( focusContainer . current && event . target === focusContainer . current ) { event . stopPropagation ( ) ; event . preventDefault ( ) ; const focusable = findLastFocusableDescendant ( focusContainer . current ) ; if ( focusable ) { focusable . focus ( ) ; } else { if ( initialFocusElement . current && initialFocusElement . current !== document . activeElement ) attemptFocus ( ( initialFocusElement . current as HTMLElement ) , true ) ; } } } , [ props . active ] ) ; const cycleFocusToStart = React . useCallback ( ( event ) => { if ( ! props . active ) return ; event . stopPropagation ( ) ; if ( initialFocusElement . current && initialFocusElement . current !== document . activeElement ) ( initialFocusElement . current as HTMLElement ) . focus ( ) ; } , [ props . active ] ) ; if ( ! props . children ) return null ; return ( < > { } < div data - testid = "focus-trap-div" onFocus = { cycleFocusToEnd } ref = { focusContainer } tabIndex = { 0 } style = { { outline : "none" , WebkitTapHighlightColor : "rgba(0,0,0,0)" } } > { props . children } < /div> { } < div data - testid = "focus-trap-limit-div" onFocus = { cycleFocusToStart } tabIndex = { 0 } / > < />); }