Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessibility: Move focus back to the modal dialog to More Menu when it was used to get there #16964

Merged
merged 2 commits into from
Sep 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/components/src/dropdown/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,14 @@ class Dropdown extends Component {
* Closes the dropdown if a focus leaves the dropdown wrapper. This is
* intentionally distinct from `onClose` since focus loss from the popover
* is expected to occur when using the Dropdown's toggle button, in which
* case the correct behavior is to keep the dropdown closed.
* case the correct behavior is to keep the dropdown closed. The same applies
* in case when focus is moved to the modal dialog.
*/
closeIfFocusOutside() {
if ( ! this.containerRef.current.contains( document.activeElement ) ) {
if (
! this.containerRef.current.contains( document.activeElement ) &&
! document.activeElement.closest( '[role="dialog"]' )
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea whether we can assume that focus can be moved to the element with [role="dialog"] in all cases. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It definitely feels like there needs to be a more general way to track that a modal or popover was opened from the context of another modal or popover, and exclude it from the focus outside check.

There's a similar issue here with the inline link popover, where it needs to check whether focus is in the autocomplete list to avoid closing:

onFocusOutside() {
// The autocomplete suggestions list renders in a separate popover (in a portal),
// so onFocusOutside fails to detect that a click on a suggestion occurred in the
// LinkContainer. Detect clicks on autocomplete suggestions using a ref here, and
// return to avoid the popover being closed.
const autocompleteElement = this.autocompleteRef.current;
if ( autocompleteElement && autocompleteElement.contains( document.activeElement ) ) {
return;
}
this.resetState();
}

Having said that, I can't think of a case where this logic fails, so I think it'd be pragmatic to ship this, and then consider what a more general solution might be.

) {
this.close();
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/e2e-test-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,10 @@ running the test is not already the admin user).
Switches the current user to whichever user we should be
running the tests as (if we're not already that user).

<a name="toggleMoreMenu" href="#toggleMoreMenu">#</a> **toggleMoreMenu**

Toggles the More Menu.

<a name="toggleScreenOption" href="#toggleScreenOption">#</a> **toggleScreenOption**

Toggles the screen option with the given label.
Expand Down
9 changes: 6 additions & 3 deletions packages/e2e-test-utils/src/click-on-more-menu-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
*/
import { first } from 'lodash';

/**
* Internal dependencies
*/
import { toggleMoreMenu } from './toggle-more-menu';

/**
* Clicks on More Menu item, searches for the button with the text provided and clicks it.
*
* @param {string} buttonLabel The label to search the button for.
*/
export async function clickOnMoreMenuItem( buttonLabel ) {
await expect( page ).toClick(
'.edit-post-more-menu [aria-label="More tools & options"]'
);
await toggleMoreMenu();
const moreMenuContainerSelector =
'//*[contains(concat(" ", @class, " "), " edit-post-more-menu__content ")]';
let elementToClick = first( await page.$x(
Expand Down
1 change: 1 addition & 0 deletions packages/e2e-test-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export { switchEditorModeTo } from './switch-editor-mode-to';
export { disableNavigationMode } from './keyboard-mode';
export { switchUserToAdmin } from './switch-user-to-admin';
export { switchUserToTest } from './switch-user-to-test';
export { toggleMoreMenu } from './toggle-more-menu';
export { toggleScreenOption } from './toggle-screen-option';
export { transformBlockTo } from './transform-block-to';
export { uninstallPlugin } from './uninstall-plugin';
Expand Down
13 changes: 10 additions & 3 deletions packages/e2e-test-utils/src/switch-editor-mode-to.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
/**
* Internal dependencies
*/
import { toggleMoreMenu } from './toggle-more-menu';

/**
* Switches editor mode.
*
* @param {string} mode String editor mode.
*/
export async function switchEditorModeTo( mode ) {
await page.click(
'.edit-post-more-menu [aria-label="More tools & options"]'
);
await toggleMoreMenu();
const [ button ] = await page.$x(
`//button[contains(text(), '${ mode } Editor')]`
);
await button.click( 'button' );
await toggleMoreMenu();
if ( mode === 'Code' ) {
await page.waitForSelector( '.editor-post-text-editor' );
}
}
8 changes: 8 additions & 0 deletions packages/e2e-test-utils/src/toggle-more-menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Toggles the More Menu.
*/
export async function toggleMoreMenu() {
await expect( page ).toClick(
'.edit-post-more-menu [aria-label="More tools & options"]'
);
}
5 changes: 4 additions & 1 deletion packages/e2e-test-utils/src/toggle-screen-option.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/**
* Internal dependencies
*/
import { clickOnCloseModalButton } from './click-on-close-modal-button';
import { clickOnMoreMenuItem } from './click-on-more-menu-item';
import { toggleMoreMenu } from './toggle-more-menu';

/**
* Toggles the screen option with the given label.
Expand All @@ -22,5 +24,6 @@ export async function toggleScreenOption( label, shouldBeChecked = undefined ) {
await handle.click();
}

await page.click( 'button[aria-label="Close dialog"]' );
await clickOnCloseModalButton();
await toggleMoreMenu();
}
6 changes: 2 additions & 4 deletions packages/e2e-tests/specs/adding-blocks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
insertBlock,
getEditedPostContent,
pressKeyTimes,
switchEditorModeTo,
} from '@wordpress/e2e-test-utils';

describe( 'adding blocks', () => {
Expand Down Expand Up @@ -96,10 +97,7 @@ describe( 'adding blocks', () => {
await page.keyboard.press( 'Enter' );
await page.keyboard.type( 'Second paragraph' );

// Switch to Text Mode to check HTML Output
await page.click( '.edit-post-more-menu [aria-label="More tools & options"]' );
const codeEditorButton = ( await page.$x( "//button[contains(text(), 'Code Editor')]" ) )[ 0 ];
await codeEditorButton.click( 'button' );
await switchEditorModeTo( 'Code' );

expect( await getEditedPostContent() ).toMatchSnapshot();
} );
Expand Down
4 changes: 0 additions & 4 deletions packages/e2e-tests/specs/editor-modes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,7 @@ describe( 'Editing modes (visual/HTML)', () => {
let blockInspectorTab = await page.$( '.edit-post-sidebar__panel-tab.is-active[data-label="Block"]' );
expect( blockInspectorTab ).not.toBeNull();

// Switch to Code Editor and hide More Menu
await switchEditorModeTo( 'Code' );
await page.click(
'.edit-post-more-menu [aria-label="More tools & options"]'
);

// The Block inspector should not be active anymore
blockInspectorTab = await page.$( '.edit-post-sidebar__panel-tab.is-active[data-label="Block"]' );
Expand Down
2 changes: 2 additions & 0 deletions packages/e2e-tests/specs/fullscreen-mode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import {
createNewPost,
clickOnMoreMenuItem,
toggleMoreMenu,
} from '@wordpress/e2e-test-utils';

describe( 'Fullscreen Mode', () => {
Expand All @@ -13,6 +14,7 @@ describe( 'Fullscreen Mode', () => {

it( 'should open the fullscreen mode from the more menu', async () => {
await clickOnMoreMenuItem( 'Fullscreen Mode' );
await toggleMoreMenu();

const isFullscreenEnabled = await page.$eval( 'body', ( body ) => {
return body.classList.contains( 'is-fullscreen-mode' );
Expand Down
11 changes: 3 additions & 8 deletions packages/e2e-tests/specs/plugins/allowed-blocks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import {
activatePlugin,
clickOnMoreMenuItem,
createNewPost,
deactivatePlugin,
searchForBlock,
Expand Down Expand Up @@ -40,15 +41,9 @@ describe( 'Allowed Blocks Filter', () => {
} );

it( 'should remove not allowed blocks from the block manager', async () => {
const BLOCK_LABEL_SELECTOR = '.edit-post-manage-blocks-modal__checklist-item .components-checkbox-control__label';
await page.click(
'.edit-post-more-menu [aria-label="More tools & options"]'
);
const [ button ] = await page.$x(
`//button[contains(text(), 'Block Manager')]`
);
await button.click( 'button' );
await clickOnMoreMenuItem( 'Block Manager' );

const BLOCK_LABEL_SELECTOR = '.edit-post-manage-blocks-modal__checklist-item .components-checkbox-control__label';
await page.waitForSelector( BLOCK_LABEL_SELECTOR );
const blocks = await page.evaluate(
( selector ) => {
Expand Down
1 change: 0 additions & 1 deletion packages/e2e-tests/specs/plugins/container-blocks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ describe( 'InnerBlocks Template Sync', () => {
`;
await insertBlock( blockName );
await switchEditorModeTo( 'Code' );
await page.waitForSelector( '.editor-post-text-editor' );
await page.$eval( '.editor-post-text-editor', ( element, _paragraph, _blockSlug ) => {
const blockDelimiter = `<!-- /wp:${ _blockSlug } -->`;
element.value = element.value.replace( blockDelimiter, `${ _paragraph }${ blockDelimiter }` );
Expand Down
3 changes: 2 additions & 1 deletion packages/e2e-tests/specs/preview.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { parse } from 'url';
* WordPress dependencies
*/
import {
clickOnCloseModalButton,
createNewPost,
createURL,
publishPost,
Expand Down Expand Up @@ -78,7 +79,7 @@ async function toggleCustomFieldsOption( shouldBeChecked ) {
return;
}

await page.click( '.edit-post-options-modal button[aria-label="Close dialog"]' );
await clickOnCloseModalButton( '.edit-post-options-modal' );
}

describe( 'Preview', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const MoreMenu = () => (
<PluginMoreMenuGroup.Slot fillProps={ { onClose } } />
<ToolsMoreMenuGroup.Slot fillProps={ { onClose } } />
<MenuGroup>
<OptionsMenuItem onSelect={ onClose } />
<OptionsMenuItem />
</MenuGroup>
</>
) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { withDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { MenuItem } from '@wordpress/components';

export function OptionsMenuItem( { openModal, onSelect } ) {
export function OptionsMenuItem( { openModal } ) {
return (
<MenuItem
onClick={ () => {
onSelect();
openModal( 'edit-post/options' );
} }
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import { withDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { displayShortcut } from '@wordpress/keycodes';

export function KeyboardShortcutsHelpMenuItem( { openModal, onSelect } ) {
export function KeyboardShortcutsHelpMenuItem( { openModal } ) {
return (
<MenuItem
onClick={ () => {
onSelect();
openModal( 'edit-post/keyboard-shortcut-help' );
} }
shortcut={ displayShortcut.access( 'h' ) }
Expand Down
14 changes: 4 additions & 10 deletions packages/edit-post/src/plugins/manage-blocks-menu-item/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
/**
* External dependencies
*/
import { flow } from 'lodash';

/**
* WordPress dependencies
*/
import { MenuItem } from '@wordpress/components';
import { withDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';

export function ManageBlocksMenuItem( { onSelect, openModal } ) {
export function ManageBlocksMenuItem( { openModal } ) {
return (
<MenuItem
onClick={ flow( [
onSelect,
() => openModal( 'edit-post/manage-blocks' ),
] ) }
onClick={ () => {
openModal( 'edit-post/manage-blocks' );
} }
>
{ __( 'Block Manager' ) }
</MenuItem>
Expand Down