From a3ddc0f50e34192e058a6ccc6283141acea46e85 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 16 Feb 2018 10:00:19 -0500 Subject: [PATCH 1/2] Writing Flow: Move selected block focus to BlockListBlock --- editor/components/block-list/block.js | 53 +++++++++++++++++++++++-- editor/components/writing-flow/index.js | 42 -------------------- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/editor/components/block-list/block.js b/editor/components/block-list/block.js index 48139b59678b8..3c6e820fa2f1c 100644 --- a/editor/components/block-list/block.js +++ b/editor/components/block-list/block.js @@ -3,13 +3,14 @@ */ import { connect } from 'react-redux'; import classnames from 'classnames'; -import { get, reduce, size, castArray, noop } from 'lodash'; +import { get, reduce, size, castArray, noop, first } from 'lodash'; +import tinymce from 'tinymce'; /** * WordPress dependencies */ import { Component, findDOMNode, compose } from '@wordpress/element'; -import { keycodes } from '@wordpress/utils'; +import { keycodes, focus } from '@wordpress/utils'; import { BlockEdit, createBlock, @@ -25,7 +26,11 @@ import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ -import { getScrollContainer } from '../../utils/dom'; +import { + getScrollContainer, + placeCaretAtHorizontalEdge, + placeCaretAtVerticalEdge, +} from '../../utils/dom'; import BlockMover from '../block-mover'; import BlockDropZone from '../block-drop-zone'; import BlockSettingsMenu from '../block-settings-menu'; @@ -67,6 +72,7 @@ import { isTyping, getBlockMode, getCurrentPostType, + getSelectedBlocksInitialCaretPosition, } from '../../store/selectors'; const { BACKSPACE, ESCAPE, DELETE, ENTER, UP, RIGHT, DOWN, LEFT } = keycodes; @@ -126,6 +132,10 @@ export class BlockListBlock extends Component { document.addEventListener( 'mousemove', this.stopTypingOnMouseMove ); } document.addEventListener( 'selectionchange', this.onSelectionChange ); + + if ( this.props.isSelected ) { + this.focusTabbable(); + } } componentWillReceiveProps( newProps ) { @@ -158,6 +168,10 @@ export class BlockListBlock extends Component { this.removeStopTypingListener(); } } + + if ( this.props.isSelected && ! prevProps.isSelected ) { + this.focusTabbable(); + } } componentWillUnmount() { @@ -189,6 +203,38 @@ export class BlockListBlock extends Component { this.node = findDOMNode( node ); } + /** + * When a block becomces selected, transition focus to an inner tabbable. + */ + focusTabbable() { + if ( this.node.contains( document.activeElement ) ) { + return; + } + + const target = first( focus.tabbable.find( this.node ) ); + if ( ! target ) { + return; + } + + target.focus(); + + if ( this.props.initialPosition !== -1 ) { + return; + } + + // Special casing RichText components because the placeCaret utilities + // are not working correctly. When merging two sibling paragraph blocks + // (backspacing), the focus is not moved to the correct position. + const editor = tinymce.get( target.getAttribute( 'id' ) ); + if ( editor ) { + editor.selection.select( editor.getBody(), true ); + editor.selection.collapse( false ); + } else { + placeCaretAtHorizontalEdge( target, true ); + placeCaretAtVerticalEdge( target, true ); + } + } + setAttributes( attributes ) { const { block, onChange } = this.props; const type = getBlockType( block.name ); @@ -625,6 +671,7 @@ const mapStateToProps = ( state, { uid, rootUID } ) => { mode: getBlockMode( state, uid ), isSelectionEnabled: isSelectionEnabled( state ), postType: getCurrentPostType( state ), + initialPosition: getSelectedBlocksInitialCaretPosition( state ), isSelected, }; }; diff --git a/editor/components/writing-flow/index.js b/editor/components/writing-flow/index.js index 7fe3a5ee3f60f..8acea7fe330a6 100644 --- a/editor/components/writing-flow/index.js +++ b/editor/components/writing-flow/index.js @@ -4,7 +4,6 @@ import { connect } from 'react-redux'; import 'element-closest'; import { find, last, reverse, get } from 'lodash'; -import tinymce from 'tinymce'; /** * WordPress dependencies @@ -29,7 +28,6 @@ import { getMultiSelectedBlocksStartUid, getMultiSelectedBlocks, getSelectedBlock, - getSelectedBlocksInitialCaretPosition, } from '../../store/selectors'; import { multiSelect, @@ -110,19 +108,6 @@ class WritingFlow extends Component { } ); } - getInnerTabbable( target, isReverse ) { - let focusableNodes = this.getVisibleTabbables(); - if ( isReverse ) { - focusableNodes = reverse( focusableNodes ); - } - - const innerItem = find( focusableNodes, ( node ) => { - return target !== node && target.contains( node ); - } ); - - return innerItem ? innerItem : target; - } - isInLastNonEmptyBlock( target ) { const tabbables = this.getVisibleTabbables(); @@ -252,32 +237,6 @@ class WritingFlow extends Component { } } - componentDidUpdate( prevProps ) { - // When selecting a new block, we focus its first editable or the container - if ( - this.props.selectedBlockUID && - ( ! prevProps.selectedBlockUID || this.props.selectedBlockUID !== prevProps.selectedBlockUID ) - ) { - const blockContainer = this.container.querySelector( `[data-block="${ this.props.selectedBlockUID }"]` ); - if ( blockContainer && ! blockContainer.contains( document.activeElement ) ) { - const target = this.getInnerTabbable( blockContainer, this.props.initialPosition === -1 ); - target.focus(); - if ( this.props.initialPosition === -1 ) { - // Special casing RichText components because the two functions at the bottom are not working as expected. - // When merging two sibling paragraph blocks (backspacing) the focus is not moved to the right position. - const editor = tinymce.get( target.getAttribute( 'id' ) ); - if ( editor ) { - editor.selection.select( editor.getBody(), true ); - editor.selection.collapse( false ); - } else { - placeCaretAtHorizontalEdge( target, true ); - placeCaretAtVerticalEdge( target, true ); - } - } - } - } - } - render() { const { children } = this.props; @@ -304,7 +263,6 @@ export default connect( selectionStart: getMultiSelectedBlocksStartUid( state ), hasMultiSelection: getMultiSelectedBlocks( state ).length > 1, selectedBlockUID: get( getSelectedBlock( state ), [ 'uid' ] ), - initialPosition: getSelectedBlocksInitialCaretPosition( state ), } ), { onMultiSelect: multiSelect, From 410e385373ef2719927b96b65f512389b412e4f6 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 16 Feb 2018 10:36:44 -0500 Subject: [PATCH 2/2] Writing Flow: Account for initial position in tabbable target --- editor/components/block-list/block.js | 40 ++++++++++++++++----------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/editor/components/block-list/block.js b/editor/components/block-list/block.js index 3c6e820fa2f1c..631829059f6cb 100644 --- a/editor/components/block-list/block.js +++ b/editor/components/block-list/block.js @@ -3,7 +3,7 @@ */ import { connect } from 'react-redux'; import classnames from 'classnames'; -import { get, reduce, size, castArray, noop, first } from 'lodash'; +import { get, reduce, size, castArray, noop, first, last } from 'lodash'; import tinymce from 'tinymce'; /** @@ -207,31 +207,39 @@ export class BlockListBlock extends Component { * When a block becomces selected, transition focus to an inner tabbable. */ focusTabbable() { + const { initialPosition } = this.props; + if ( this.node.contains( document.activeElement ) ) { return; } - const target = first( focus.tabbable.find( this.node ) ); + // Find all tabbables within node. + const tabbables = focus.tabbable.find( this.node ); + + // If reversed (e.g. merge via backspace), use the last in the set of + // tabbables. + const isReverse = -1 === initialPosition; + const target = ( isReverse ? last : first )( tabbables ); + if ( ! target ) { return; } target.focus(); - if ( this.props.initialPosition !== -1 ) { - return; - } - - // Special casing RichText components because the placeCaret utilities - // are not working correctly. When merging two sibling paragraph blocks - // (backspacing), the focus is not moved to the correct position. - const editor = tinymce.get( target.getAttribute( 'id' ) ); - if ( editor ) { - editor.selection.select( editor.getBody(), true ); - editor.selection.collapse( false ); - } else { - placeCaretAtHorizontalEdge( target, true ); - placeCaretAtVerticalEdge( target, true ); + // In reverse case, need to explicitly place caret position. + if ( isReverse ) { + // Special case RichText component because the placeCaret utilities + // aren't working correctly. When merging two paragraph blocks, the + // focus is not moved to the correct position. + const editor = tinymce.get( target.getAttribute( 'id' ) ); + if ( editor ) { + editor.selection.select( editor.getBody(), true ); + editor.selection.collapse( false ); + } else { + placeCaretAtHorizontalEdge( target, true ); + placeCaretAtVerticalEdge( target, true ); + } } }