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

Avoid block unselection by focus leave #836

Merged
merged 3 commits into from
May 18, 2017
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
34 changes: 20 additions & 14 deletions editor/modes/visual-editor/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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)
Expand All @@ -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 ) {
Expand All @@ -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(
Expand Down Expand Up @@ -178,10 +185,9 @@ class VisualEditorBlock extends wp.element.Component {
return (
<div
ref={ this.bindBlockNode }
onClick={ onSelect }
onClick={ this.selectAndStopPropagation }
onFocus={ onSelect }
onBlur={ this.maybeDeselect }
onKeyDown={ this.removeOnBackspace }
onKeyDown={ this.removeOrDeselect }
onMouseEnter={ onHover }
onMouseMove={ this.maybeHover }
onMouseLeave={ onMouseLeave }
Expand Down
29 changes: 24 additions & 5 deletions editor/modes/visual-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
*/
import { connect } from 'react-redux';

/**
* WordPress dependencies
*/
import { __ } from 'i18n';

/**
* Internal dependencies
*/
Expand All @@ -12,18 +17,32 @@ import VisualEditorBlock from './block';
import PostTitle from '../../post-title';
import { getBlockUids } from '../../selectors';

function VisualEditor( { blocks } ) {
function VisualEditor( { blocks, clearSelectedBlock } ) {
// Disable reason: Focus transfer between blocks and key events are handled
// by focused block element. Consider unhandled click bubbling as unselect.

/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
return (
<div className="editor-visual-editor">
<div
role="region"
aria-label={ __( 'Visual Editor' ) }
onClick={ clearSelectedBlock }
className="editor-visual-editor">
<PostTitle />
{ blocks.map( ( uid ) => (
<VisualEditorBlock key={ uid } uid={ uid } />
) ) }
<Inserter position="top right" />
</div>
);
/* 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 );
2 changes: 2 additions & 0 deletions editor/modes/visual-editor/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
3 changes: 3 additions & 0 deletions editor/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions editor/test/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down