Skip to content

Commit

Permalink
Writing Flow: Redirect click below editor to last text field
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed Mar 12, 2018
1 parent 6e12185 commit de0cf1c
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 27 deletions.
13 changes: 6 additions & 7 deletions edit-post/components/visual-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
WritingFlow,
ObserveTyping,
EditorGlobalKeyboardShortcuts,
BlockSelectionClearer,
MultiSelectScrollIntoView,
} from '@wordpress/editor';
import { Fragment, compose } from '@wordpress/element';
Expand All @@ -23,12 +22,12 @@ import BlockInspectorButton from './block-inspector-button';

function VisualEditor( { hasFixedToolbar, isLargeViewport } ) {
return (
<BlockSelectionClearer className="edit-post-visual-editor">
<div className="edit-post-visual-editor">
<EditorGlobalKeyboardShortcuts />
<CopyHandler />
<MultiSelectScrollIntoView />
<ObserveTyping>
<WritingFlow>
<WritingFlow>
<ObserveTyping>
<PostTitle />
<BlockList
showContextualToolbar={ ! isLargeViewport || ! hasFixedToolbar }
Expand All @@ -39,9 +38,9 @@ function VisualEditor( { hasFixedToolbar, isLargeViewport } ) {
</Fragment>
) }
/>
</WritingFlow>
</ObserveTyping>
</BlockSelectionClearer>
</ObserveTyping>
</WritingFlow>
</div>
);
}

Expand Down
14 changes: 14 additions & 0 deletions edit-post/components/visual-editor/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
}
}

.edit-post-visual-editor .editor-writing-flow__click-redirect {
// Collapse to minimum height of 50px, to fully occupy editor bottom pad.
height: 50px;
width: $visual-editor-max-width;
// Offset for: Visual editor padding, block (default appender) margin.
margin: #{ -1 * $block-spacing } auto -50px;
}

.edit-post-visual-editor .editor-block-list__block {
margin-left: auto;
margin-right: auto;
Expand Down Expand Up @@ -89,6 +97,12 @@
margin-right: auto;
position: relative;

&[data-root-uid=""] .editor-default-block-appender__content:hover {
// Outline on root-level default block appender is redundant with the
// WritingFlow click redirector.
outline: 1px solid transparent;
}

@include break-small() {
padding: 0 $block-mover-padding-visible; // don't subtract 1px border because it's a border not an outline

Expand Down
14 changes: 12 additions & 2 deletions editor/components/default-block-appender/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,25 @@ import { getBlock, getBlockCount } from '../../store/selectors';
import InserterWithShortcuts from '../inserter-with-shortcuts';
import Inserter from '../inserter';

export function DefaultBlockAppender( { isLocked, isVisible, onAppend, showPrompt, placeholder, layout, rootUID } ) {
export function DefaultBlockAppender( {
isLocked,
isVisible,
onAppend,
showPrompt,
placeholder,
layout,
rootUID,
} ) {
if ( isLocked || ! isVisible ) {
return null;
}

const value = placeholder || __( 'Write your story' );

return (
<div className="editor-default-block-appender">
<div
data-root-uid={ rootUID || '' }
className="editor-default-block-appender">
<BlockDropZone />
<input
className="editor-default-block-appender__content"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exports[`DefaultBlockAppender should append a default block when input focused 1`] = `
<div
className="editor-default-block-appender"
data-root-uid=""
>
<Connect(WrappedComponent) />
<input
Expand Down Expand Up @@ -42,6 +43,7 @@ exports[`DefaultBlockAppender should append a default block when input focused 1
exports[`DefaultBlockAppender should match snapshot 1`] = `
<div
className="editor-default-block-appender"
data-root-uid=""
>
<Connect(WrappedComponent) />
<input
Expand All @@ -63,6 +65,7 @@ exports[`DefaultBlockAppender should match snapshot 1`] = `
exports[`DefaultBlockAppender should optionally show without prompt 1`] = `
<div
className="editor-default-block-appender"
data-root-uid=""
>
<Connect(WrappedComponent) />
<input
Expand Down
72 changes: 62 additions & 10 deletions editor/components/writing-flow/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import { connect } from 'react-redux';
import { find, reverse, get } from 'lodash';
import { overEvery, find, findLast, reverse, get } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -22,6 +22,8 @@ import {
/**
* Internal dependencies
*/
import './style.scss';
import BlockSelectionClearer from '../block-selection-clearer';
import {
getPreviousBlockUid,
getNextBlockUid,
Expand All @@ -38,18 +40,39 @@ import {
isInSameBlock,
} from '../../utils/dom';

/**
* Browser dependencies
*/

const { DOMRect } = window;

/**
* Module Constants
*/
const { UP, DOWN, LEFT, RIGHT } = keycodes;

/**
* Given an element, returns true if the element is a tabbable text field, or
* false otherwise.
*
* @param {Element} element Element to test.
*
* @return {boolean} Whether element is a tabbable text field.
*/
const isTabbableTextField = overEvery( [
isTextField,
focus.tabbable.isTabbableIndex,
] );

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

this.onKeyDown = this.onKeyDown.bind( this );
this.bindContainer = this.bindContainer.bind( this );
this.clearVerticalRect = this.clearVerticalRect.bind( this );
this.focusLastTextField = this.focusLastTextField.bind( this );

this.verticalRect = null;
}

Expand Down Expand Up @@ -194,29 +217,58 @@ class WritingFlow extends Component {
this.moveSelection( isReverse );
} else if ( isVertical && isVerticalEdge( target, isReverse, isShift ) ) {
const closestTabbable = this.getClosestTabbable( target, isReverse );
placeCaretAtVerticalEdge( closestTabbable, isReverse, this.verticalRect );
event.preventDefault();
if ( closestTabbable ) {
placeCaretAtVerticalEdge( closestTabbable, isReverse, this.verticalRect );
event.preventDefault();
}
} else if ( isHorizontal && isHorizontalEdge( target, isReverse, isShift ) ) {
const closestTabbable = this.getClosestTabbable( target, isReverse );
placeCaretAtHorizontalEdge( closestTabbable, isReverse );
event.preventDefault();
}
}

/**
* Shifts focus to the last tabbable text field — if one exists — at the
* given mouse event's X coordinate.
*
* @param {MouseEvent} event Mouse event to align caret X offset.
*/
focusLastTextField( event ) {
const focusableNodes = focus.focusable.find( this.container );
const target = findLast( focusableNodes, isTabbableTextField );
if ( ! target ) {
return;
}

// Emulate a rect at which caret should be placed using mouse event.
const rect = target.getBoundingClientRect();
const targetRect = new DOMRect( event.clientX, rect.top, 0, rect.height );

placeCaretAtVerticalEdge( target, false, targetRect );
}

render() {
const { children } = this.props;

// Disable reason: Wrapper itself is non-interactive, but must capture
// bubbling events from children to determine focus transition intents.
/* eslint-disable jsx-a11y/no-static-element-interactions */
return (
<div
ref={ this.bindContainer }
onKeyDown={ this.onKeyDown }
onMouseDown={ this.clearVerticalRect }
>
{ children }
</div>
<BlockSelectionClearer className="editor-writing-flow">
<div
ref={ this.bindContainer }
onKeyDown={ this.onKeyDown }
onMouseDown={ this.clearVerticalRect }
>
{ children }
</div>
<div
aria-hidden
onClick={ this.focusLastTextField }
className="editor-writing-flow__click-redirect"
/>
</BlockSelectionClearer>
);
/* eslint-disable jsx-a11y/no-static-element-interactions */
}
Expand Down
10 changes: 10 additions & 0 deletions editor/components/writing-flow/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.editor-writing-flow {
height: 100%;
display: flex;
flex-direction: column;
}

.editor-writing-flow__click-redirect {
flex-basis: 100%;
cursor: text;
}
12 changes: 4 additions & 8 deletions test/e2e/integration/002-adding-blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,15 @@ describe( 'Adding blocks', () => {
} );

it( 'Should insert content using the placeholder and the regular inserter', () => {
const lastBlockSelector = '.editor-block-list__block-edit:last';
// Default block appender is provisional
cy.get( '.editor-default-block-appender' ).click();
cy.get( '.editor-post-title__input' ).click();
cy.get( '[data-type="core/paragraph"]' ).should( 'have.length', 0 );

// Using the placeholder
cy.get( '.editor-default-block-appender' ).click();
cy.focused().type( 'Paragraph block' );

// Default block appender is provisional
cy.get( lastBlockSelector ).then( ( firstBlock ) => {
cy.get( '.editor-default-block-appender' ).click();
cy.get( '.editor-post-title__input' ).click();
cy.get( lastBlockSelector ).should( 'have.text', firstBlock.text() );
} );

// Using the slash command
// TODO: Test omitted because Cypress doesn't update the selection
// object properly, so the slash inserter is not showing up.
Expand Down

0 comments on commit de0cf1c

Please sign in to comment.