Skip to content

Commit

Permalink
Block List: Display sibling inserter between blocks
Browse files Browse the repository at this point in the history
Block List: Render inserter only while hovered, focused

Inserter: Style insertion point below between-serter

Inserter: Position between-serter as centered below

Block: Refactor insertion point within sibling inserter

Block List: Accumuate block list by Array#reduce

Significant performance degredation when children as tuple. Instead, flatten into single array

Inserter: Disallow container tabbable when visible

Prevents shift-tab focus trap which can occur while button is focused, since focusFirstTabbable would continue to keep button focused
  • Loading branch information
aduth committed Oct 16, 2017
1 parent b79966e commit 7784c0f
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 71 deletions.
1 change: 1 addition & 0 deletions components/icon-button/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
overflow: hidden;

.dashicon {
display: block;
flex: 0 0 auto;
}

Expand Down
1 change: 1 addition & 0 deletions editor/assets/stylesheets/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ $block-padding: 14px;
$block-mover-margin: 18px;
$block-mover-padding-hidden: 10px;
$block-mover-padding-visible: 32px;
$block-spacing: 4px;

/* Media Queries */

Expand Down
2 changes: 1 addition & 1 deletion editor/assets/stylesheets/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ $z-layers: (
'.editor-visual-editor__block {core/image aligned left or right}': 20,
'.editor-block-toolbar': 10,
'.editor-visual-editor__block-warning': 1,
'.editor-visual-editor .editor-visual-editor__block .editor-inserter': 1,
'.editor-visual-editor__sibling-inserter': 1,
'.components-form-toggle__input': 1,
'.editor-format-list__menu': 1,
'.editor-inserter__tabs': 1,
Expand Down
55 changes: 16 additions & 39 deletions editor/modes/visual-editor/block-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ import { serialize, getDefaultBlockName, createBlock } from '@wordpress/blocks';
* Internal dependencies
*/
import VisualEditorBlock from './block';
import VisualEditorSiblingInserter from './sibling-inserter';
import BlockDropZone from './block-drop-zone';
import {
getBlockUids,
getBlockInsertionPoint,
isBlockInsertionPointVisible,
getMultiSelectedBlocksStartUid,
getMultiSelectedBlocksEndUid,
getMultiSelectedBlocks,
Expand All @@ -28,8 +27,6 @@ import {
} from '../../selectors';
import { insertBlock, startMultiSelect, stopMultiSelect, multiSelect, selectBlock } from '../../actions';

const INSERTION_POINT_PLACEHOLDER = '[[insertion-point]]';

class VisualEditorBlockList extends Component {
constructor( props ) {
super( props );
Expand Down Expand Up @@ -195,42 +192,24 @@ class VisualEditorBlockList extends Component {
}

render() {
const {
blocks,
showInsertionPoint,
insertionPoint,
} = this.props;

const blocksWithInsertionPoint = showInsertionPoint
? [
...blocks.slice( 0, insertionPoint ),
INSERTION_POINT_PLACEHOLDER,
...blocks.slice( insertionPoint ),
]
: blocks;
const { blocks } = this.props;

return (
<div>
{ !! blocks.length && blocksWithInsertionPoint.map( ( uid ) => {
if ( uid === INSERTION_POINT_PLACEHOLDER ) {
return (
<div
key={ INSERTION_POINT_PLACEHOLDER }
className="editor-visual-editor__insertion-point"
/>
);
}

return (
<VisualEditorBlock
key={ uid }
uid={ uid }
blockRef={ ( ref ) => this.setBlockRef( ref, uid ) }
onSelectionStart={ this.onSelectionStart }
onShiftSelection={ this.onShiftSelection }
/>
);
} ) }
{ !! blocks.length && <VisualEditorSiblingInserter insertIndex={ 0 } /> }
{ blocks.reduce( ( result, uid, index ) => result.concat(
<VisualEditorBlock
key={ 'block-' + uid }
uid={ uid }
blockRef={ ( ref ) => this.setBlockRef( ref, uid ) }
onSelectionStart={ this.onSelectionStart }
onShiftSelection={ this.onShiftSelection }
/>,
<VisualEditorSiblingInserter
key={ 'sibling-inserter-' + uid }
insertIndex={ index + 1 }
/>,
), [] ) }
{ ! blocks.length &&
<div className="editor-visual-editor__placeholder">
<BlockDropZone />
Expand All @@ -252,8 +231,6 @@ class VisualEditorBlockList extends Component {
export default connect(
( state ) => ( {
blocks: getBlockUids( state ),
insertionPoint: getBlockInsertionPoint( state ),
showInsertionPoint: isBlockInsertionPointVisible( state ),
selectionStart: getMultiSelectedBlocksStartUid( state ),
selectionEnd: getMultiSelectedBlocksEndUid( state ),
multiSelectedBlocks: getMultiSelectedBlocks( state ),
Expand Down
10 changes: 2 additions & 8 deletions editor/modes/visual-editor/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import BlockHtml from './block-html';
import BlockMover from '../../block-mover';
import BlockRightMenu from '../../block-settings-menu';
import BlockToolbar from '../../block-toolbar';
import Inserter from '../../inserter';
import {
clearSelectedBlock,
editPost,
Expand Down Expand Up @@ -291,7 +290,7 @@ class VisualEditorBlock extends Component {
}

render() {
const { block, multiSelectedBlockUids, order, mode, nextBlock } = this.props;
const { block, multiSelectedBlockUids, order, mode } = this.props;
const { name: blockName, isValid } = block;
const blockType = getBlockType( blockName );
// translators: %s: Type of block (i.e. Text, Image etc)
Expand Down Expand Up @@ -324,7 +323,7 @@ class VisualEditorBlock extends Component {
'is-hovered': isProperlyHovered,
} );

const { onMouseLeave, onSelect, onFocus, onReplace } = this.props;
const { onMouseLeave, onFocus, onReplace } = this.props;

// Determine whether the block has props to apply to the wrapper.
let wrapperProps;
Expand Down Expand Up @@ -402,11 +401,6 @@ class VisualEditorBlock extends Component {
] }
</BlockCrashBoundary>
</div>
{ ( showUI || isHovered ) && !! nextBlock && (
<Inserter
onToggle={ ( isOpen ) => isOpen ? onSelect() : null }
insertIndex={ order + 1 } />
) }
{ !! error && <BlockCrashWarning /> }
</div>
);
Expand Down
136 changes: 136 additions & 0 deletions editor/modes/visual-editor/sibling-inserter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* External dependencies
*/
import { connect } from 'react-redux';
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { focus } from '@wordpress/utils';

/**
* Internal dependencies
*/
import Inserter from '../../inserter';
import {
getBlockInsertionPoint,
isBlockInsertionPointVisible,
} from '../../selectors';

class VisualEditorSiblingInserter extends Component {
constructor() {
super( ...arguments );

this.bindNode = this.bindNode.bind( this );
this.focusFirstTabbable = this.focusFirstTabbable.bind( this );
this.show = this.toggleVisible.bind( this, true );
this.hide = this.toggleVisible.bind( this, false );
this.showAndFocus = this.showAndFocus.bind( this );
this.suspendToggleVisible = this.suspendToggleVisible.bind( this );

this.state = {
isVisible: false,
isToggleVisibleSuspended: false,
};
}

componentDidUpdate( prevProps, prevState ) {
const { visibleViaFocus, state } = this;
const { isVisible, isToggleVisibleSuspended } = state;
if ( isVisible && ! prevState.isVisible && visibleViaFocus ) {
this.focusFirstTabbable();

// Reset for next toggle visible
this.visibleViaFocus = false;
}

// If inserter is closed, we must check to see if focus is still within
// the inserter, since it may have been closed by clicking outside. We
// want to retain visible if still focused, or hide otherwise.
if ( ! isToggleVisibleSuspended && prevState.isToggleVisibleSuspended &&
! this.node.contains( document.activeElement ) ) {
this.toggleVisible( false );
}
}

bindNode( node ) {
this.node = node;
}

focusFirstTabbable() {
// Sibling inserter doesn't render its inserter button until after it
// becomes visible by focus or hover. If visible by focus, move focus
// into the now-visible button.
const tabbable = focus.tabbable.find( this.node );
if ( tabbable.length ) {
tabbable[ 0 ].focus();
}
}

toggleVisible( isVisible ) {
if ( ! this.state.isToggleVisibleSuspended ) {
this.setState( { isVisible } );
}
}

showAndFocus() {
this.toggleVisible( true );
this.visibleViaFocus = true;
}

suspendToggleVisible( isOpen ) {
// Prevent mouseout and blur while navigating the open inserter menu
// from causing the inserter to be unmounted.
this.setState( { isToggleVisibleSuspended: isOpen } );
}

render() {
const { insertIndex, showInsertionPoint } = this.props;
const { isVisible } = this.state;

const classes = classnames( 'editor-visual-editor__sibling-inserter', {
'is-visible': isVisible || showInsertionPoint,
} );

return (
<div
ref={ this.bindNode }
data-insert-index={ insertIndex }
className={ classes }
onFocus={ this.showAndFocus }
onBlur={ this.hide }
onMouseEnter={ this.show }
onMouseLeave={ this.hide }
tabIndex={ isVisible ? -1 : 0 }>
{ showInsertionPoint && (
<div className="editor-visual-editor__insertion-point" />
) }
{ isVisible && [
<hr
key="rule"
className="editor-visual-editor__sibling-inserter-rule"
/>,
<Inserter
key="inserter"
position="bottom"
insertIndex={ insertIndex }
onToggle={ this.suspendToggleVisible }
/>,
] }
</div>
);
}
}

export default connect(
( state, ownProps ) => {
return {
showInsertionPoint: (
isBlockInsertionPointVisible( state ) &&
getBlockInsertionPoint( state ) === ownProps.insertIndex
),
};
}
)( VisualEditorSiblingInserter );
83 changes: 60 additions & 23 deletions editor/modes/visual-editor/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
.editor-visual-editor__block {
margin-left: auto;
margin-right: auto;
margin-bottom: 5px;
margin-bottom: $block-spacing;
max-width: $visual-editor-max-width + ( 2 * $block-mover-padding-visible );
position: relative;

Expand Down Expand Up @@ -282,26 +282,6 @@
border-bottom: 3px solid $blue-medium-500;
}
}

.editor-visual-editor & .editor-inserter {
position: absolute;
left: $block-padding + $block-mover-padding-visible;
top: 100%;
transform: translate( -50%, -50% );
margin-top: 0;
margin-bottom: 0;
z-index: z-index( '.editor-visual-editor .editor-visual-editor__block .editor-inserter' );
}

.editor-visual-editor & .editor-inserter__toggle {
padding: 4px;
background-color: white;

&,
& .dashicon {
display: block;
}
}
}

.editor-visual-editor .editor-inserter {
Expand All @@ -325,8 +305,8 @@

&:before {
position: absolute;
top: 6px;
height: 3px;
top: -1px;
height: 2px;
left: 0;
right: 0;
background: $blue-medium-500;
Expand Down Expand Up @@ -461,3 +441,60 @@
box-shadow: none;
}
}

.editor-visual-editor__sibling-inserter {
z-index: z-index( '.editor-visual-editor__sibling-inserter' );
position: relative;
max-width: $visual-editor-max-width + ( 2 * $block-mover-padding-visible );
margin: 0 auto;
opacity: 0;
transition: 0.1s opacity;

&:not( [data-insert-index="0"] ) {
top: #{ -1 * ( $block-spacing / 2 ) };
}

&.is-visible {
opacity: 1;
}

&::before {
content: '';
position: absolute;
width: 100%;
height: 10px;
transition: 0.1s height;
transform: translateY( -50% );
}

&:hover::before {
height: 44px;
}

.editor-inserter {
position: absolute;
top: 50%;
left: 50%;
transform: translate( -50%, -50% );
margin: 0;
}

.editor-inserter__toggle.components-button {
display: block;
margin: 0;
padding: 4px;
background-color: white;
}
}

.editor-visual-editor__sibling-inserter-rule {
position: absolute;
left: 50%;
top: 50%;
width: 80px;
margin: 0;
margin-top: -1px;
margin-left: -40px;
border: none;
border-top: 2px solid $blue-medium-500;
}

0 comments on commit 7784c0f

Please sign in to comment.