Skip to content

Commit

Permalink
Delay focus until MCE init (squashed 11 commits)
Browse files Browse the repository at this point in the history
  • Loading branch information
ellatrix committed Feb 12, 2018
1 parent 9f160d3 commit 1e62288
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 28 deletions.
51 changes: 50 additions & 1 deletion blocks/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import {
} from 'lodash';
import { nodeListToReact } from 'dom-react';
import 'element-closest';
import scrollIntoView from 'dom-scroll-into-view';

/**
* WordPress dependencies
*/
import { createElement, Component, renderToString } from '@wordpress/element';
import { keycodes, createBlobURL } from '@wordpress/utils';
import { keycodes, createBlobURL, getScrollContainer } from '@wordpress/utils';
import { withSafeTimeout, Slot, Fill } from '@wordpress/components';

/**
Expand All @@ -38,6 +39,11 @@ import { EVENTS } from './constants';

const { BACKSPACE, DELETE, ENTER } = keycodes;

/**
* Holds the offset of the root node, to use across instances when needed.
*/
let offsetTop;

export function createTinyMCEElement( type, props, ...children ) {
if ( props[ 'data-mce-bogus' ] === 'all' ) {
return null;
Expand Down Expand Up @@ -103,6 +109,8 @@ export class RichText extends Component {
this.maybePropagateUndo = this.maybePropagateUndo.bind( this );
this.onPastePreProcess = this.onPastePreProcess.bind( this );
this.onPaste = this.onPaste.bind( this );
this.onTinyMCEMount = this.onTinyMCEMount.bind( this );
this.onFocus = this.onFocus.bind( this );

this.state = {
formats: {},
Expand Down Expand Up @@ -142,6 +150,7 @@ export class RichText extends Component {
} );

editor.on( 'init', this.onInit );
editor.on( 'focusin', this.onFocus );
editor.on( 'focusout', this.onChange );
editor.on( 'NewBlock', this.onNewBlock );
editor.on( 'nodechange', this.onNodeChange );
Expand Down Expand Up @@ -204,6 +213,25 @@ export class RichText extends Component {
} );
}

onFocus() {
// For virtual keyboards, always scroll the focussed editor into view.
// Unfortunately we cannot detect virtual keyboards, so we check UA.
if ( /iPad|iPhone|iPod|Android/i.test( window.navigator.userAgent ) ) {
const rootNode = this.editor.getBody();
const rootRect = rootNode.getBoundingClientRect();
const caretRect = this.editor.selection.getRng().getClientRects()[ 0 ];
const offset = caretRect ? caretRect.top - rootRect.top : 0;

scrollIntoView( rootNode, getScrollContainer( rootNode ), {
// Give enough room for toolbar. Must be top.
// Unfortunately we cannot scroll to bottom as the virtual
// keyboard does not change the window size.
offsetTop: 100 - offset,
alignWithTop: true,
} );
}
}

/**
* Handles the global selection change event.
*/
Expand Down Expand Up @@ -559,6 +587,11 @@ export class RichText extends Component {
if ( event.shiftKey || ! this.props.onSplit ) {
this.editor.execCommand( 'InsertLineBreak', false, event );
} else {
// For type writing offect, save the root node offset so it
// can the position can be scrolled to in the next focussed
// instance.
offsetTop = rootNode.getBoundingClientRect().top;

this.splitContent();
}
}
Expand Down Expand Up @@ -770,6 +803,21 @@ export class RichText extends Component {
this.editor.setDirty( true );
}

onTinyMCEMount( node ) {
if ( ! offsetTop ) {
return;
}

// When a new instance is created, scroll the root node into the
// position of the root node that captured ENTER.
scrollIntoView( node, getScrollContainer( node ), {
offsetTop,
alignWithTop: true,
} );

offsetTop = null;
}

render() {
const {
tagName: Tagname = 'div',
Expand Down Expand Up @@ -830,6 +878,7 @@ export class RichText extends Component {
{ ...ariaProps }
className={ className }
key={ key }
onMount={ this.onTinyMCEMount }
/>
{ isPlaceholderVisible &&
<Tagname
Expand Down
4 changes: 4 additions & 0 deletions blocks/rich-text/tinymce.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import { diffAriaProps, pickAriaProps } from './aria';

export default class TinyMCE extends Component {
componentDidMount() {
if ( this.props.onMount ) {
this.props.onMount( this.editorNode );
}

this.initialize();
}

Expand Down
2 changes: 2 additions & 0 deletions edit-post/components/layout/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@

.edit-post-layout__content {
position: relative;
// Pad the scroll box so content on the bottom can be scrolled up.
padding-bottom: 100vh;

// on mobile the main content area has to scroll
// otherwise you can invoke the overscroll bounce on the non-scrolling container, causing (ノಠ益ಠ)ノ彡┻━┻
Expand Down
27 changes: 1 addition & 26 deletions editor/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { get, reduce, size, castArray, noop } from 'lodash';
* WordPress dependencies
*/
import { Component, findDOMNode, compose } from '@wordpress/element';
import { keycodes } from '@wordpress/utils';
import { keycodes, getScrollContainer } from '@wordpress/utils';
import {
BlockEdit,
createBlock,
Expand Down Expand Up @@ -70,31 +70,6 @@ import {

const { BACKSPACE, ESCAPE, DELETE, ENTER, UP, RIGHT, DOWN, LEFT } = keycodes;

/**
* Given a DOM node, finds the closest scrollable container node.
*
* @param {Element} node Node from which to start.
*
* @return {?Element} Scrollable container node, if found.
*/
function getScrollContainer( node ) {
if ( ! node ) {
return;
}

// Scrollable if scrollable height exceeds displayed...
if ( node.scrollHeight > node.clientHeight ) {
// ...except when overflow is defined to be hidden or visible
const { overflowY } = window.getComputedStyle( node );
if ( /(auto|scroll)/.test( overflowY ) ) {
return node;
}
}

// Continue traversing
return getScrollContainer( node.parentNode );
}

export class BlockListBlock extends Component {
constructor() {
super( ...arguments );
Expand Down
7 changes: 6 additions & 1 deletion editor/components/writing-flow/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,12 @@ class WritingFlow extends Component {
const blockContainer = this.container.querySelector( `[data-block="${ this.props.selectedBlock.uid }"]` );
if ( blockContainer && ! blockContainer.contains( document.activeElement ) ) {
const target = this.getInnerTabbable( blockContainer, this.props.initialPosition === -1 );
target.focus();

// If there is a contenteditable element, it will move focus by itself.
if ( ! target.querySelector( '[contenteditable="true"]' ) ) {
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.
Expand Down
24 changes: 24 additions & 0 deletions utils/get-scroll-container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Given a DOM node, finds the closest scrollable container node.
*
* @param {Element} node Node from which to start.
*
* @return {?Element} Scrollable container node, if found.
*/
export function getScrollContainer( node ) {
if ( ! node || node === document.body ) {
return window;
}

// Scrollable if scrollable height exceeds displayed...
if ( node.scrollHeight > node.clientHeight ) {
// ...except when overflow is defined to be hidden or visible
const { overflowY } = window.getComputedStyle( node );
if ( /(auto|scroll)/.test( overflowY ) ) {
return node;
}
}

// Continue traversing
return getScrollContainer( node.parentNode );
}
1 change: 1 addition & 0 deletions utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export { decodeEntities };
export * from './blob-cache';
export * from './mediaupload';
export * from './terms';
export * from './get-scroll-container';

export { viewPort };

0 comments on commit 1e62288

Please sign in to comment.