Skip to content

Commit

Permalink
Writing Flow: Force tab stop at focusable wrapper when inner blocks e…
Browse files Browse the repository at this point in the history
…xist
  • Loading branch information
aduth committed May 24, 2018
1 parent e650954 commit 3a0aac3
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 2 deletions.
16 changes: 14 additions & 2 deletions editor/components/writing-flow/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import './style.scss';
import {
isBlockFocusStop,
isInSameBlock,
hasInnerBlocksContext,
} from '../../utils/dom';

/**
Expand Down Expand Up @@ -105,11 +106,22 @@ class WritingFlow extends Component {
return false;
}

// Prefer text fields, but settle for block focus stop.
if ( ! isTextField( node ) && ! isBlockFocusStop( node ) ) {
// Prefer text fields...
if ( isTextField( node ) ) {
return true;
}

// ...but settle for block focus stop.
if ( ! isBlockFocusStop( node ) ) {
return false;
}

// If element contains inner blocks, stop immediately at its focus
// wrapper.
if ( hasInnerBlocksContext( node ) ) {
return true;
}

// If navigating out of a block (in reverse), don't consider its
// block focus stop.
if ( node.contains( target ) ) {
Expand Down
12 changes: 12 additions & 0 deletions editor/utils/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,15 @@ export function isBlockFocusStop( element ) {
export function isInSameBlock( a, b ) {
return a.closest( '[data-block]' ) === b.closest( '[data-block]' );
}

/**
* Returns true if the given HTMLElement contains inner blocks (an InnerBlocks
* element).
*
* @param {HTMLElement} element Element to test.
*
* @return {boolean} Whether element contains inner blocks.
*/
export function hasInnerBlocksContext( element ) {
return !! element.querySelector( '.editor-block-list__layout' );
}
37 changes: 37 additions & 0 deletions editor/utils/test/dom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Internal dependencies
*/
import { hasInnerBlocksContext } from '../dom';

describe( 'hasInnerBlocksContext()', () => {
it( 'should return false for a block node which has no inner blocks', () => {
const wrapper = document.createElement( 'div' );
wrapper.innerHTML = (
'<div class="editor-block-list__block" data-type="core/paragraph" tabindex="0">' +
' <div class="editor-block-list__block-edit" aria-label="Block: Paragraph">' +
' <p contenteditable="true">This is a test.</p>' +
' </div>' +
'</div>'
);

const blockNode = wrapper.firstChild;
expect( hasInnerBlocksContext( blockNode ) ).toBe( false );
} );

it( 'should return true for a block node which contains inner blocks', () => {
const wrapper = document.createElement( 'div' );
wrapper.innerHTML = (
'<div class="editor-block-list__block" data-type="core/columns" tabindex="0">' +
' <div class="editor-block-list__block-edit" aria-label="Block: Columns (beta)">' +
' <div class="wp-block-columns has-2-columns">' +
' <div class="editor-block-list__layout layout-column-1"></div>' +
' <div class="editor-block-list__layout layout-column-2"></div>' +
' </div>' +
' </div>' +
'</div>'
);

const blockNode = wrapper.firstChild;
expect( hasInnerBlocksContext( blockNode ) ).toBe( true );
} );
} );
23 changes: 23 additions & 0 deletions test/e2e/specs/__snapshots__/writing-flow.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`adding blocks Should navigate inner blocks with arrow keys 1`] = `
"<!-- wp:paragraph -->
<p>First paragraph</p>
<!-- /wp:paragraph -->
<!-- wp:columns -->
<div class=\\"wp-block-columns has-2-columns\\">
<!-- wp:paragraph {\\"layout\\":\\"column-1\\"} -->
<p class=\\"layout-column-1\\">First column paragraph</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph {\\"layout\\":\\"column-2\\"} -->
<p class=\\"layout-column-2\\">Second column paragraph</p>
<!-- /wp:paragraph -->
</div>
<!-- /wp:columns -->
<!-- wp:paragraph -->
<p>Second paragraph</p>
<!-- /wp:paragraph -->"
`;
67 changes: 67 additions & 0 deletions test/e2e/specs/writing-flow.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Internal dependencies
*/
import '../support/bootstrap';
import {
newPost,
newDesktopBrowserPage,
getHTMLFromCodeEditor,
} from '../support/utils';

describe( 'adding blocks', () => {
beforeAll( async () => {
await newDesktopBrowserPage();
} );

beforeEach( async () => {
await newPost();
} );

it( 'Should navigate inner blocks with arrow keys', async () => {
let activeElementText;

// Add demo content
await page.click( '.editor-default-block-appender__content' );
await page.keyboard.type( 'First paragraph' );
await page.keyboard.press( 'Enter' );
await page.keyboard.type( '/columns' );
await page.keyboard.press( 'Enter' );
await page.keyboard.type( 'First column paragraph' );

// Arrow down should navigate through layouts in columns block (to
// its default appender).
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.type( 'Second column paragraph' );

// Arrow down from last of layouts exits nested context to default
// appender of root level.
await page.keyboard.press( 'ArrowDown' );
await page.keyboard.type( 'Second paragraph' );

// Arrow up into nested context focuses last text input
await page.keyboard.press( 'ArrowUp' );
activeElementText = await page.evaluate( () => document.activeElement.textContent );
expect( activeElementText ).toBe( 'Second column paragraph' );

// Arrow up in inner blocks should navigate through text fields.
await page.keyboard.press( 'ArrowUp' );
activeElementText = await page.evaluate( () => document.activeElement.textContent );
expect( activeElementText ).toBe( 'First column paragraph' );

// Arrow up from first text field in nested context focuses wrapper
// before escaping out.
await page.keyboard.press( 'ArrowUp' );
const activeElementBlockType = await page.evaluate( () => (
document.activeElement.getAttribute( 'data-type' )
) );
expect( activeElementBlockType ).toBe( 'core/columns' );

// Arrow up from focused (columns) block wrapper exits nested context
// to prior text input.
await page.keyboard.press( 'ArrowUp' );
activeElementText = await page.evaluate( () => document.activeElement.textContent );
expect( activeElementText ).toBe( 'First paragraph' );

expect( await getHTMLFromCodeEditor() ).toMatchSnapshot();
} );
} );

0 comments on commit 3a0aac3

Please sign in to comment.