diff --git a/editor/modes/visual-editor/block.js b/editor/modes/visual-editor/block.js index e3230769e08e7..f612e32760921 100644 --- a/editor/modes/visual-editor/block.js +++ b/editor/modes/visual-editor/block.js @@ -37,11 +37,11 @@ class VisualEditorBlock extends wp.element.Component { super( ...arguments ); this.bindBlockNode = this.bindBlockNode.bind( this ); this.setAttributes = this.setAttributes.bind( this ); - this.maybeDeselect = this.maybeDeselect.bind( this ); this.maybeHover = this.maybeHover.bind( this ); this.maybeStartTyping = this.maybeStartTyping.bind( this ); - this.removeOnBackspace = this.removeOnBackspace.bind( this ); + this.removeOrDeselect = this.removeOrDeselect.bind( this ); this.mergeBlocks = this.mergeBlocks.bind( this ); + this.selectAndStopPropagation = this.selectAndStopPropagation.bind( this ); this.previousOffset = null; } @@ -76,14 +76,6 @@ class VisualEditorBlock extends wp.element.Component { } } - maybeDeselect( event ) { - // Annoyingly React does not support focusOut and we're forced to check - // related target to ensure it's not a child when blur fires. - if ( ! event.currentTarget.contains( event.relatedTarget ) ) { - this.props.onDeselect(); - } - } - maybeStartTyping() { // We do not want to dispatch start typing if... // - State value already reflects that we're typing (dispatch noise) @@ -96,14 +88,21 @@ class VisualEditorBlock extends wp.element.Component { } } - removeOnBackspace( event ) { + removeOrDeselect( event ) { const { keyCode, target } = event; + + // Remove block on backspace if ( 8 /* Backspace */ === keyCode && target === this.node ) { this.props.onRemove( this.props.uid ); if ( this.props.previousBlock ) { this.props.onFocus( this.props.previousBlock.uid, { offset: -1 } ); } } + + // Deselect on escape + if ( 27 /* Escape */ === event.keyCode ) { + this.props.onDeselect(); + } } mergeBlocks( forward = false ) { @@ -124,6 +123,14 @@ class VisualEditorBlock extends wp.element.Component { } } + selectAndStopPropagation( event ) { + this.props.onSelect(); + + // Visual editor infers click as intent to clear the selected block, so + // prevent bubbling when occurring on block where selection is intended + event.stopPropagation(); + } + componentDidUpdate( prevProps ) { if ( this.previousOffset ) { window.scrollTo( @@ -178,10 +185,9 @@ class VisualEditorBlock extends wp.element.Component { return (
+
{ blocks.map( ( uid ) => ( @@ -22,8 +35,14 @@ function VisualEditor( { blocks } ) {
); + /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } -export default connect( ( state ) => ( { - blocks: getBlockUids( state ), -} ) )( VisualEditor ); +export default connect( + ( state ) => ( { + blocks: getBlockUids( state ), + } ), + ( dispatch ) => ( { + clearSelectedBlock: () => dispatch( { type: 'CLEAR_SELECTED_BLOCK' } ), + } ) +)( VisualEditor ); diff --git a/editor/modes/visual-editor/style.scss b/editor/modes/visual-editor/style.scss index 5e4793d7ec90b..354c865004fed 100644 --- a/editor/modes/visual-editor/style.scss +++ b/editor/modes/visual-editor/style.scss @@ -79,6 +79,8 @@ margin-top: -$block-controls-height - $item-spacing; margin-bottom: $item-spacing + 20px; // 20px is the offset from the bottom of the selected block where it stops sticking height: $block-controls-height; + width: 0; + white-space: nowrap; top: $header-height + $admin-bar-height-big + $item-spacing; diff --git a/editor/state.js b/editor/state.js index 9e1a73e612d72..0c9bd344f0f75 100644 --- a/editor/state.js +++ b/editor/state.js @@ -197,6 +197,9 @@ export function selectedBlock( state = {}, action ) { focus: action.uid === state.uid ? state.focus : {}, }; + case 'CLEAR_SELECTED_BLOCK': + return {}; + case 'MOVE_BLOCK_UP': case 'MOVE_BLOCK_DOWN': return action.uid === state.uid diff --git a/editor/test/state.js b/editor/test/state.js index 1d5b9daac64c1..4808202f2c34b 100644 --- a/editor/test/state.js +++ b/editor/test/state.js @@ -408,6 +408,15 @@ describe( 'state', () => { expect( state ).to.eql( { uid: 'kumquat', typing: false, focus: {} } ); } ); + it( 'returns an empty object when clearing selected block', () => { + const original = deepFreeze( { uid: 'kumquat', typing: false, focus: {} } ); + const state = selectedBlock( original, { + type: 'CLEAR_SELECTED_BLOCK', + } ); + + expect( state ).to.eql( {} ); + } ); + it( 'should not update the state if already selected and not typing', () => { const original = deepFreeze( { uid: 'kumquat', typing: false, focus: {} } ); const state = selectedBlock( original, {