UNPKG

@wordpress/compose

Version:
8 lines (7 loc) 3.76 kB
{ "version": 3, "sources": ["../../../src/hooks/use-focus-on-mount/index.ts"], "sourcesContent": ["import { focus } from '@wordpress/dom';\nimport { useEffect, useRef } from '@wordpress/element';\nimport useRefEffect from '../use-ref-effect';\n\n/**\n * Determines focus behavior when the element mounts.\n *\n * @param focusOnMount Behavioral mode. Defaults to `\"firstElement\"` which focuses the\n * first tabbable element within; `\"firstInputElement\"` focuses the\n * first value control within; `true` focuses the element itself;\n * `false` does nothing.\n * @return Ref callback.\n *\n * @example\n * ```js\n * import { useFocusOnMount } from '@wordpress/compose';\n *\n * const WithFocusOnMount = () => {\n * const ref = useFocusOnMount()\n * return (\n * <div ref={ ref }>\n * <Button />\n * <Button />\n * </div>\n * );\n * }\n * ```\n */\nexport function useFocusOnMount(\n\tfocusOnMount: useFocusOnMount.Mode = 'firstElement'\n) {\n\tconst focusOnMountRef = useRef( focusOnMount );\n\n\t/**\n\t * Sets focus on a DOM element.\n\t *\n\t * @param target The DOM element to set focus to.\n\t */\n\tconst setFocus = ( target: HTMLElement ): void => {\n\t\ttarget.focus( {\n\t\t\t// When focusing newly mounted dialogs,\n\t\t\t// the position of the popover is often not right on the first render\n\t\t\t// This prevents the layout shifts when focusing the dialogs.\n\t\t\tpreventScroll: true,\n\t\t} );\n\t};\n\n\tuseEffect( () => {\n\t\tfocusOnMountRef.current = focusOnMount;\n\t}, [ focusOnMount ] );\n\n\treturn useRefEffect< HTMLElement >( ( node ) => {\n\t\tif ( focusOnMountRef.current === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( node.contains( node.ownerDocument?.activeElement ?? null ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\tfocusOnMountRef.current !== 'firstElement' &&\n\t\t\tfocusOnMountRef.current !== 'firstInputElement'\n\t\t) {\n\t\t\tsetFocus( node );\n\t\t\treturn;\n\t\t}\n\n\t\tconst timerId = setTimeout( () => {\n\t\t\t// For 'firstInputElement' mode, try to find a form input element first\n\t\t\tif ( focusOnMountRef.current === 'firstInputElement' ) {\n\t\t\t\tconst formInput = node.querySelector< HTMLElement >(\n\t\t\t\t\t'input:not([type=\"hidden\"]):not([disabled]), select:not([disabled]), textarea:not([disabled])'\n\t\t\t\t);\n\n\t\t\t\tif ( formInput ) {\n\t\t\t\t\tsetFocus( formInput );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fallback to the first tabbable element\n\t\t\tconst firstTabbable = focus.tabbable.find( node )[ 0 ];\n\t\t\tif ( firstTabbable ) {\n\t\t\t\tsetFocus( firstTabbable );\n\t\t\t}\n\t\t}, 0 );\n\n\t\treturn () => {\n\t\t\tclearTimeout( timerId );\n\t\t};\n\t}, [] );\n}\n\nexport namespace useFocusOnMount {\n\texport type Mode = boolean | 'firstElement' | 'firstInputElement';\n}\n"], "mappings": ";AAAA,SAAS,aAAa;AACtB,SAAS,WAAW,cAAc;AAClC,OAAO,kBAAkB;AA0BlB,SAAS,gBACf,eAAqC,gBACpC;AACD,QAAM,kBAAkB,OAAQ,YAAa;AAO7C,QAAM,WAAW,CAAE,WAA+B;AACjD,WAAO,MAAO;AAAA;AAAA;AAAA;AAAA,MAIb,eAAe;AAAA,IAChB,CAAE;AAAA,EACH;AAEA,YAAW,MAAM;AAChB,oBAAgB,UAAU;AAAA,EAC3B,GAAG,CAAE,YAAa,CAAE;AAEpB,SAAO,aAA6B,CAAE,SAAU;AAC/C,QAAK,gBAAgB,YAAY,OAAQ;AACxC;AAAA,IACD;AAEA,QAAK,KAAK,SAAU,KAAK,eAAe,iBAAiB,IAAK,GAAI;AACjE;AAAA,IACD;AAEA,QACC,gBAAgB,YAAY,kBAC5B,gBAAgB,YAAY,qBAC3B;AACD,eAAU,IAAK;AACf;AAAA,IACD;AAEA,UAAM,UAAU,WAAY,MAAM;AAEjC,UAAK,gBAAgB,YAAY,qBAAsB;AACtD,cAAM,YAAY,KAAK;AAAA,UACtB;AAAA,QACD;AAEA,YAAK,WAAY;AAChB,mBAAU,SAAU;AACpB;AAAA,QACD;AAAA,MACD;AAGA,YAAM,gBAAgB,MAAM,SAAS,KAAM,IAAK,EAAG,CAAE;AACrD,UAAK,eAAgB;AACpB,iBAAU,aAAc;AAAA,MACzB;AAAA,IACD,GAAG,CAAE;AAEL,WAAO,MAAM;AACZ,mBAAc,OAAQ;AAAA,IACvB;AAAA,EACD,GAAG,CAAC,CAAE;AACP;", "names": [] }