From eb63976ea6a8baf9c545b48f647286973741a8f0 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Tue, 27 Feb 2018 14:44:30 -0500 Subject: [PATCH 1/2] Block List: Extract scroll preservation as non-visual component --- edit-post/components/layout/index.js | 2 + editor/components/block-list/block.js | 25 +----- editor/components/index.js | 1 + .../preserve-scroll-in-reorder/index.js | 80 +++++++++++++++++++ editor/store/selectors.js | 26 ++++++ editor/utils/dom.js | 12 +++ 6 files changed, 122 insertions(+), 24 deletions(-) create mode 100644 editor/components/preserve-scroll-in-reorder/index.js create mode 100644 editor/utils/dom.js diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index e1daf676225092..2fd28173346442 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -16,6 +16,7 @@ import { EditorNotices, PostPublishPanel, DocumentTitle, + PreserveScrollInReorder, } from '@wordpress/editor'; /** @@ -83,6 +84,7 @@ function Layout( {
+
{ mode === 'text' && } diff --git a/editor/components/block-list/block.js b/editor/components/block-list/block.js index c79b405b540887..869e82bac8fc2e 100644 --- a/editor/components/block-list/block.js +++ b/editor/components/block-list/block.js @@ -13,7 +13,6 @@ import { Component, findDOMNode, compose } from '@wordpress/element'; import { keycodes, focus, - getScrollContainer, placeCaretAtHorizontalEdge, placeCaretAtVerticalEdge, } from '@wordpress/utils'; @@ -100,8 +99,6 @@ export class BlockListBlock extends Component { this.onClick = this.onClick.bind( this ); this.selectOnOpen = this.selectOnOpen.bind( this ); this.onSelectionChange = this.onSelectionChange.bind( this ); - - this.previousOffset = null; this.hadTouchStart = false; this.state = { @@ -140,31 +137,12 @@ export class BlockListBlock extends Component { } componentWillReceiveProps( newProps ) { - if ( - this.props.order !== newProps.order && - ( newProps.isSelected || newProps.isFirstMultiSelected ) - ) { - this.previousOffset = this.node.getBoundingClientRect().top; - } - if ( newProps.isTyping || newProps.isSelected ) { this.hideHoverEffects(); } } componentDidUpdate( prevProps ) { - // Preserve scroll prosition when block rearranged - if ( this.previousOffset ) { - const scrollContainer = getScrollContainer( this.node ); - if ( scrollContainer ) { - scrollContainer.scrollTop = scrollContainer.scrollTop + - this.node.getBoundingClientRect().top - - this.previousOffset; - } - - this.previousOffset = null; - } - // Bind or unbind mousemove from page when user starts or stops typing if ( this.props.isTyping !== prevProps.isTyping ) { if ( this.props.isTyping ) { @@ -201,8 +179,7 @@ export class BlockListBlock extends Component { bindBlockNode( node ) { // Disable reason: The block element uses a component to manage event - // nesting, but we rely on a raw DOM node for focusing and preserving - // scroll offset on move. + // nesting, but we rely on a raw DOM node for focusing. // // eslint-disable-next-line react/no-find-dom-node this.node = findDOMNode( node ); diff --git a/editor/components/index.js b/editor/components/index.js index 46350d874cef02..f874a9e7c16be3 100644 --- a/editor/components/index.js +++ b/editor/components/index.js @@ -65,6 +65,7 @@ export { default as Inserter } from './inserter'; export { default as MultiBlocksSwitcher } from './block-switcher/multi-blocks-switcher'; export { default as MultiSelectScrollIntoView } from './multi-select-scroll-into-view'; export { default as NavigableToolbar } from './navigable-toolbar'; +export { default as PreserveScrollInReorder } from './preserve-scroll-in-reorder'; export { default as Warning } from './warning'; export { default as WritingFlow } from './writing-flow'; diff --git a/editor/components/preserve-scroll-in-reorder/index.js b/editor/components/preserve-scroll-in-reorder/index.js new file mode 100644 index 00000000000000..400243b2fbc0b9 --- /dev/null +++ b/editor/components/preserve-scroll-in-reorder/index.js @@ -0,0 +1,80 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { withSelect } from '@wordpress/data'; +import { getScrollContainer } from '@wordpress/utils'; + +/** + * Internal dependencies + */ +import { getBlockDOMNode } from '../../utils/dom'; + +/** + * Non-visual component which preserves offset of selected block within nearest + * scrollable container while reordering. + * + * @example + * + * ```jsx + * + * ``` + */ +class PreserveScrollInReorder extends Component { + componentWillUpdate( nextProps ) { + const { blockOrder, selectionStart } = nextProps; + if ( blockOrder !== this.props.blockOrder && selectionStart ) { + this.setPreviousOffset( selectionStart ); + } + } + + componentDidUpdate() { + if ( this.previousOffset ) { + this.restorePreviousOffset(); + } + } + + /** + * Given the block UID of the start of the selection, saves the block's + * top offset as an instance property before a reorder is to occur. + * + * @param {string} selectionStart UID of selected block. + */ + setPreviousOffset( selectionStart ) { + const blockNode = getBlockDOMNode( selectionStart ); + if ( ! blockNode ) { + return; + } + + this.previousOffset = blockNode.getBoundingClientRect().top; + } + + /** + * After a block reordering, restores the previous viewport top offset. + */ + restorePreviousOffset() { + const { selectionStart } = this.props; + const blockNode = getBlockDOMNode( selectionStart ); + if ( blockNode ) { + const scrollContainer = getScrollContainer( blockNode ); + if ( scrollContainer ) { + scrollContainer.scrollTop = scrollContainer.scrollTop + + blockNode.getBoundingClientRect().top - + this.previousOffset; + } + } + + delete this.previousOffset; + } + + render() { + return null; + } +} + +export default withSelect( ( select ) => { + return { + blockOrder: select( 'core/editor' ).getBlockOrder(), + selectionStart: select( 'core/editor' ).getBlockSelectionStart(), + }; +} )( PreserveScrollInReorder ); diff --git a/editor/store/selectors.js b/editor/store/selectors.js index 14ce95bed18fb0..e6268e764acb0d 100644 --- a/editor/store/selectors.js +++ b/editor/store/selectors.js @@ -464,6 +464,32 @@ export function getBlockCount( state, rootUID ) { return getBlockOrder( state, rootUID ).length; } +/** + * Returns the current block selection start. This value may be null, and it + * may represent either a singular block selection or multi-selection start. + * A selection is singular if its start and end match. + * + * @param {Object} state Global application state. + * + * @return {?string} UID of block selection start. + */ +export function getBlockSelectionStart( state ) { + return state.blockSelection.start; +} + +/** + * Returns the current block selection end. This value may be null, and it + * may represent either a singular block selection or multi-selection end. + * A selection is singular if its start and end match. + * + * @param {Object} state Global application state. + * + * @return {?string} UID of block selection end. + */ +export function getBlockSelectionEnd( state ) { + return state.blockSelection.end; +} + /** * Returns the number of blocks currently selected in the post. * diff --git a/editor/utils/dom.js b/editor/utils/dom.js new file mode 100644 index 00000000000000..21208bec1f52e2 --- /dev/null +++ b/editor/utils/dom.js @@ -0,0 +1,12 @@ +/** + * Given a block UID, returns the corresponding DOM node for the block, if + * exists. As much as possible, this helper should be avoided, and used only + * in cases where isolated behaviors need remote access to a block node. + * + * @param {string} uid Block UID. + * + * @return {Element} Block DOM node. + */ +export function getBlockDOMNode( uid ) { + return document.querySelector( '[data-block="' + uid + '"]' ); +} From fad28e208b9db5bbab123eb84a2f3cbe81e8bb0d Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Tue, 27 Feb 2018 14:47:01 -0500 Subject: [PATCH 2/2] Editor: Replace ad hoc block query selects --- editor/components/multi-select-scroll-into-view/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/editor/components/multi-select-scroll-into-view/index.js b/editor/components/multi-select-scroll-into-view/index.js index 4be0e9579d6fd8..6eafd423ab8e47 100644 --- a/editor/components/multi-select-scroll-into-view/index.js +++ b/editor/components/multi-select-scroll-into-view/index.js @@ -10,6 +10,11 @@ import { Component } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import { getScrollContainer } from '@wordpress/utils'; +/** + * Internal dependencies + */ +import { getBlockDOMNode } from '../../utils/dom'; + class MultiSelectScrollIntoView extends Component { componentDidUpdate() { // Relies on expectation that `componentDidUpdate` will only be called @@ -29,7 +34,7 @@ class MultiSelectScrollIntoView extends Component { return; } - const extentNode = document.querySelector( '[data-block="' + extentUID + '"]' ); + const extentNode = getBlockDOMNode( extentUID ); if ( ! extentNode ) { return; }