@wordpress/block-editor
Version:
8 lines (7 loc) • 11.6 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../../src/components/navigable-toolbar/index.js"],
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { NavigableMenu, Toolbar } from '@wordpress/components';\nimport {\n\tuseState,\n\tuseRef,\n\tuseLayoutEffect,\n\tuseEffect,\n\tuseCallback,\n} from '@wordpress/element';\nimport { useSelect } from '@wordpress/data';\nimport deprecated from '@wordpress/deprecated';\nimport { focus } from '@wordpress/dom';\nimport { useShortcut } from '@wordpress/keyboard-shortcuts';\nimport { ESCAPE } from '@wordpress/keycodes';\n\n/**\n * Internal dependencies\n */\nimport { store as blockEditorStore } from '../../store';\nimport { unlock } from '../../lock-unlock';\n\nfunction hasOnlyToolbarItem( elements ) {\n\tconst dataProp = 'toolbarItem';\n\treturn ! elements.some( ( element ) => ! ( dataProp in element.dataset ) );\n}\n\nfunction getAllFocusableToolbarItemsIn( container ) {\n\treturn Array.from(\n\t\tcontainer.querySelectorAll( '[data-toolbar-item]:not([disabled])' )\n\t);\n}\n\nfunction hasFocusWithin( container ) {\n\treturn container.contains( container.ownerDocument.activeElement );\n}\n\nfunction focusFirstTabbableIn( container ) {\n\tconst [ firstTabbable ] = focus.tabbable.find( container );\n\n\tif ( firstTabbable ) {\n\t\tfirstTabbable.focus( {\n\t\t\t// When focusing newly mounted toolbars,\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\nfunction useIsAccessibleToolbar( toolbarRef ) {\n\t/*\n\t * By default, we'll assume the starting accessible state of the Toolbar\n\t * is true, as it seems to be the most common case.\n\t *\n\t * Transitioning from an (initial) false to true state causes the\n\t * <Toolbar /> component to mount twice, which is causing undesired\n\t * side-effects. These side-effects appear to only affect certain\n\t * E2E tests.\n\t *\n\t * This was initial discovered in this pull-request:\n\t * https://github.com/WordPress/gutenberg/pull/23425\n\t */\n\tconst initialAccessibleToolbarState = true;\n\n\t// By default, it's gonna render NavigableMenu. If all the tabbable elements\n\t// inside the toolbar are ToolbarItem components (or derived components like\n\t// ToolbarButton), then we can wrap them with the accessible Toolbar\n\t// component.\n\tconst [ isAccessibleToolbar, setIsAccessibleToolbar ] = useState(\n\t\tinitialAccessibleToolbarState\n\t);\n\n\tconst determineIsAccessibleToolbar = useCallback( () => {\n\t\tconst tabbables = focus.tabbable.find( toolbarRef.current );\n\t\tconst onlyToolbarItem = hasOnlyToolbarItem( tabbables );\n\t\tif ( ! onlyToolbarItem ) {\n\t\t\tdeprecated( 'Using custom components as toolbar controls', {\n\t\t\t\tsince: '5.6',\n\t\t\t\talternative:\n\t\t\t\t\t'ToolbarItem, ToolbarButton or ToolbarDropdownMenu components',\n\t\t\t\tlink: 'https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols',\n\t\t\t} );\n\t\t}\n\t\tsetIsAccessibleToolbar( onlyToolbarItem );\n\t}, [ toolbarRef ] );\n\n\tuseLayoutEffect( () => {\n\t\t// Toolbar buttons may be rendered asynchronously, so we use\n\t\t// MutationObserver to check if the toolbar subtree has been modified.\n\t\tconst observer = new window.MutationObserver(\n\t\t\tdetermineIsAccessibleToolbar\n\t\t);\n\t\tobserver.observe( toolbarRef.current, {\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t} );\n\t\treturn () => observer.disconnect();\n\t}, [ determineIsAccessibleToolbar, isAccessibleToolbar, toolbarRef ] );\n\n\treturn isAccessibleToolbar;\n}\n\nfunction useToolbarFocus( {\n\ttoolbarRef,\n\tfocusOnMount,\n\tisAccessibleToolbar,\n\tdefaultIndex,\n\tonIndexChange,\n\tshouldUseKeyboardFocusShortcut,\n\tfocusEditorOnEscape,\n} ) {\n\t// Make sure we don't use modified versions of this prop.\n\tconst [ initialFocusOnMount ] = useState( focusOnMount );\n\tconst [ initialIndex ] = useState( defaultIndex );\n\n\tconst focusToolbar = useCallback( () => {\n\t\tfocusFirstTabbableIn( toolbarRef.current );\n\t}, [ toolbarRef ] );\n\n\tconst focusToolbarViaShortcut = () => {\n\t\tif ( shouldUseKeyboardFocusShortcut ) {\n\t\t\tfocusToolbar();\n\t\t}\n\t};\n\n\t// Focus on toolbar when pressing alt+F10 when the toolbar is visible.\n\tuseShortcut( 'core/block-editor/focus-toolbar', focusToolbarViaShortcut );\n\n\tuseEffect( () => {\n\t\tif ( initialFocusOnMount ) {\n\t\t\tfocusToolbar();\n\t\t}\n\t}, [ isAccessibleToolbar, initialFocusOnMount, focusToolbar ] );\n\n\tuseEffect( () => {\n\t\t// Store ref so we have access on useEffect cleanup: https://legacy.reactjs.org/blog/2020/08/10/react-v17-rc.html#effect-cleanup-timing\n\t\tconst navigableToolbarRef = toolbarRef.current;\n\t\t// If initialIndex is passed, we focus on that toolbar item when the\n\t\t// toolbar gets mounted and initial focus is not forced.\n\t\t// We have to wait for the next browser paint because block controls aren't\n\t\t// rendered right away when the toolbar gets mounted.\n\t\tlet raf = 0;\n\n\t\t// If the toolbar already had focus before the render, we don't want to move it.\n\t\t// https://github.com/WordPress/gutenberg/issues/58511\n\t\tif (\n\t\t\t! initialFocusOnMount &&\n\t\t\t! hasFocusWithin( navigableToolbarRef )\n\t\t) {\n\t\t\traf = window.requestAnimationFrame( () => {\n\t\t\t\tconst items =\n\t\t\t\t\tgetAllFocusableToolbarItemsIn( navigableToolbarRef );\n\t\t\t\tconst index = initialIndex || 0;\n\t\t\t\tif ( items[ index ] && hasFocusWithin( navigableToolbarRef ) ) {\n\t\t\t\t\titems[ index ].focus( {\n\t\t\t\t\t\t// When focusing newly mounted toolbars,\n\t\t\t\t\t\t// the position of the popover is often not right on the first render\n\t\t\t\t\t\t// This prevents the layout shifts when focusing the dialogs.\n\t\t\t\t\t\tpreventScroll: true,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\t\treturn () => {\n\t\t\twindow.cancelAnimationFrame( raf );\n\t\t\tif ( ! onIndexChange || ! navigableToolbarRef ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// When the toolbar element is unmounted and onIndexChange is passed, we\n\t\t\t// pass the focused toolbar item index so it can be hydrated later.\n\t\t\tconst items = getAllFocusableToolbarItemsIn( navigableToolbarRef );\n\t\t\tconst index = items.findIndex( ( item ) => item.tabIndex === 0 );\n\t\t\tonIndexChange( index );\n\t\t};\n\t}, [ initialIndex, initialFocusOnMount, onIndexChange, toolbarRef ] );\n\n\tconst { getLastFocus } = unlock( useSelect( blockEditorStore ) );\n\t/**\n\t * Handles returning focus to the block editor canvas when pressing escape.\n\t */\n\tuseEffect( () => {\n\t\tconst navigableToolbarRef = toolbarRef.current;\n\n\t\tif ( focusEditorOnEscape ) {\n\t\t\tconst handleKeyDown = ( event ) => {\n\t\t\t\tconst lastFocus = getLastFocus();\n\t\t\t\tif ( event.keyCode === ESCAPE && lastFocus?.current ) {\n\t\t\t\t\t// Focus the last focused element when pressing escape.\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tlastFocus.current.focus();\n\t\t\t\t}\n\t\t\t};\n\t\t\tnavigableToolbarRef.addEventListener( 'keydown', handleKeyDown );\n\t\t\treturn () => {\n\t\t\t\tnavigableToolbarRef.removeEventListener(\n\t\t\t\t\t'keydown',\n\t\t\t\t\thandleKeyDown\n\t\t\t\t);\n\t\t\t};\n\t\t}\n\t}, [ focusEditorOnEscape, getLastFocus, toolbarRef ] );\n}\n\nexport default function NavigableToolbar( {\n\tchildren,\n\tfocusOnMount,\n\tfocusEditorOnEscape = false,\n\tshouldUseKeyboardFocusShortcut = true,\n\t__experimentalInitialIndex: initialIndex,\n\t__experimentalOnIndexChange: onIndexChange,\n\torientation = 'horizontal',\n\t...props\n} ) {\n\tconst toolbarRef = useRef();\n\tconst isAccessibleToolbar = useIsAccessibleToolbar( toolbarRef );\n\n\tuseToolbarFocus( {\n\t\ttoolbarRef,\n\t\tfocusOnMount,\n\t\tdefaultIndex: initialIndex,\n\t\tonIndexChange,\n\t\tisAccessibleToolbar,\n\t\tshouldUseKeyboardFocusShortcut,\n\t\tfocusEditorOnEscape,\n\t} );\n\n\tif ( isAccessibleToolbar ) {\n\t\treturn (\n\t\t\t<Toolbar\n\t\t\t\tlabel={ props[ 'aria-label' ] }\n\t\t\t\tref={ toolbarRef }\n\t\t\t\torientation={ orientation }\n\t\t\t\t{ ...props }\n\t\t\t>\n\t\t\t\t{ children }\n\t\t\t</Toolbar>\n\t\t);\n\t}\n\n\treturn (\n\t\t<NavigableMenu\n\t\t\torientation={ orientation }\n\t\t\trole=\"toolbar\"\n\t\t\tref={ toolbarRef }\n\t\t\t{ ...props }\n\t\t>\n\t\t\t{ children }\n\t\t</NavigableMenu>\n\t);\n}\n"],
"mappings": "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",
"names": []
}