UNPKG

@wordpress/compose

Version:
8 lines (7 loc) 3.85 kB
{ "version": 3, "sources": ["../../../src/hooks/use-constrained-tabbing/index.js"], "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { focus } from '@wordpress/dom';\n\n/**\n * Internal dependencies\n */\nimport useRefEffect from '../use-ref-effect';\n\n/**\n * In Dialogs/modals, the tabbing must be constrained to the content of\n * the wrapper element. This hook adds the behavior to the returned ref.\n *\n * @return {React.RefCallback<Element>} Element Ref.\n *\n * @example\n * ```js\n * import { useConstrainedTabbing } from '@wordpress/compose';\n *\n * const ConstrainedTabbingExample = () => {\n * const constrainedTabbingRef = useConstrainedTabbing()\n * return (\n * <div ref={ constrainedTabbingRef }>\n * <Button />\n * <Button />\n * </div>\n * );\n * }\n * ```\n */\nfunction useConstrainedTabbing() {\n\treturn useRefEffect( ( /** @type {HTMLElement} */ node ) => {\n\t\tfunction onKeyDown( /** @type {KeyboardEvent} */ event ) {\n\t\t\tconst { key, shiftKey, target } = event;\n\n\t\t\tif ( key !== 'Tab' ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst action = shiftKey ? 'findPrevious' : 'findNext';\n\t\t\tconst nextElement =\n\t\t\t\tfocus.tabbable[ action ](\n\t\t\t\t\t/** @type {HTMLElement} */ ( target )\n\t\t\t\t) || null;\n\n\t\t\t// When the target element contains the element that is about to\n\t\t\t// receive focus, for example when the target is a tabbable\n\t\t\t// container, browsers may disagree on where to move focus next.\n\t\t\t// In this case we can't rely on native browsers behavior. We need\n\t\t\t// to manage focus instead.\n\t\t\t// See https://github.com/WordPress/gutenberg/issues/46041.\n\t\t\tif (\n\t\t\t\t/** @type {HTMLElement} */ ( target ).contains( nextElement )\n\t\t\t) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tnextElement?.focus();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If the element that is about to receive focus is inside the\n\t\t\t// area, rely on native browsers behavior and let tabbing follow\n\t\t\t// the native tab sequence.\n\t\t\tif ( node.contains( nextElement ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If the element that is about to receive focus is outside the\n\t\t\t// area, move focus to a div and insert it at the start or end of\n\t\t\t// the area, depending on the direction. Without preventing default\n\t\t\t// behaviour, the browser will then move focus to the next element.\n\t\t\tconst domAction = shiftKey ? 'append' : 'prepend';\n\t\t\tconst { ownerDocument } = node;\n\t\t\tconst trap = ownerDocument.createElement( 'div' );\n\n\t\t\ttrap.tabIndex = -1;\n\t\t\tnode[ domAction ]( trap );\n\n\t\t\t// Remove itself when the trap loses focus.\n\t\t\ttrap.addEventListener( 'blur', () => node.removeChild( trap ) );\n\n\t\t\ttrap.focus();\n\t\t}\n\n\t\tnode.addEventListener( 'keydown', onKeyDown );\n\t\treturn () => {\n\t\t\tnode.removeEventListener( 'keydown', onKeyDown );\n\t\t};\n\t}, [] );\n}\n\nexport default useConstrainedTabbing;\n"], "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,iBAAsB;AAKtB,4BAAyB;AAuBzB,SAAS,wBAAwB;AAChC,aAAO,sBAAAA,SAAc,CAA6B,SAAU;AAC3D,aAAS,UAAwC,OAAQ;AACxD,YAAM,EAAE,KAAK,UAAU,OAAO,IAAI;AAElC,UAAK,QAAQ,OAAQ;AACpB;AAAA,MACD;AAEA,YAAM,SAAS,WAAW,iBAAiB;AAC3C,YAAM,cACL,iBAAM,SAAU,MAAO;AAAA;AAAA,QACO;AAAA,MAC9B,KAAK;AAQN;AAAA;AAAA,QAC8B,OAAS,SAAU,WAAY;AAAA,QAC3D;AACD,cAAM,eAAe;AACrB,qBAAa,MAAM;AACnB;AAAA,MACD;AAKA,UAAK,KAAK,SAAU,WAAY,GAAI;AACnC;AAAA,MACD;AAMA,YAAM,YAAY,WAAW,WAAW;AACxC,YAAM,EAAE,cAAc,IAAI;AAC1B,YAAM,OAAO,cAAc,cAAe,KAAM;AAEhD,WAAK,WAAW;AAChB,WAAM,SAAU,EAAG,IAAK;AAGxB,WAAK,iBAAkB,QAAQ,MAAM,KAAK,YAAa,IAAK,CAAE;AAE9D,WAAK,MAAM;AAAA,IACZ;AAEA,SAAK,iBAAkB,WAAW,SAAU;AAC5C,WAAO,MAAM;AACZ,WAAK,oBAAqB,WAAW,SAAU;AAAA,IAChD;AAAA,EACD,GAAG,CAAC,CAAE;AACP;AAEA,IAAO,kCAAQ;", "names": ["useRefEffect"] }