From f1b08ac4a587bca7d787d34212905619d18db6e7 Mon Sep 17 00:00:00 2001 From: iseulde Date: Fri, 20 Sep 2019 16:47:53 +0300 Subject: [PATCH] Smooth selection --- .../src/components/block-list/block.js | 29 +++- .../src/components/block-list/index.js | 136 ++---------------- .../src/components/block-list/style.scss | 13 +- .../src/components/rich-text/index.js | 8 +- .../src/components/rich-text/style.scss | 4 - .../src/components/warning/style.scss | 5 - 6 files changed, 47 insertions(+), 148 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 777cb54933a80b..93921d07f5e288 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -63,7 +63,6 @@ const preventDrag = ( event ) => { }; function BlockListBlock( { - blockRef, mode, isFocusMode, hasFixedToolbar, @@ -97,6 +96,7 @@ function BlockListBlock( { toggleSelection, onShiftSelection, onSelectionStart, + onSelectionEnd, animateOnChange, enableAnimation, isNavigationMode, @@ -108,9 +108,6 @@ function BlockListBlock( { // Reference of the wrapper const wrapper = useRef( null ); - useEffect( () => { - blockRef( wrapper.current, clientId ); - }, [] ); // Reference to the block edit node const blockNodeRef = useRef(); @@ -332,12 +329,14 @@ function BlockListBlock( { } }; + const isPointerDown = useRef( false ); + /** * Begins tracking cursor multi-selection when clicking down within block. * * @param {MouseEvent} event A mousedown event. */ - const onPointerDown = ( event ) => { + const onMouseDown = ( event ) => { // Not the main button. // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button if ( event.button !== 0 ) { @@ -353,7 +352,7 @@ function BlockListBlock( { // Avoid triggering multi-selection if we click toolbars/inspectors // and all elements that are outside the Block Edit DOM tree. } else if ( blockNodeRef.current.contains( event.target ) ) { - onSelectionStart( clientId ); + isPointerDown.current = true; // Allow user to escape out of a multi-selection to a singular // selection of a block via click. This is handled here since @@ -366,6 +365,19 @@ function BlockListBlock( { } }; + const onMouseUp = () => { + onSelectionEnd( clientId ); + isPointerDown.current = false; + }; + + const onMouseLeave = () => { + if ( isPointerDown.current ) { + onSelectionStart( clientId ); + } + + isPointerDown.current = false; + }; + const selectOnOpen = ( open ) => { if ( open && ! isSelected ) { onSelect(); @@ -431,6 +443,7 @@ function BlockListBlock( { 'is-selected': shouldAppearSelected, 'is-navigate-mode': isNavigationMode, 'is-multi-selected': isPartOfMultiSelection, + 'is-multi-selected-first': isFirstMultiSelected, 'is-hovered': shouldAppearHovered, 'is-reusable': isReusableBlock( blockType ), 'is-dragging': isDragging, @@ -558,7 +571,9 @@ function BlockListBlock( { diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 36f9b765d546d8..470620077ccc5e 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -1,13 +1,6 @@ /** * External dependencies */ -import { - findLast, - invert, - mapValues, - sortBy, - throttle, -} from 'lodash'; import classnames from 'classnames'; /** @@ -27,7 +20,6 @@ import { compose } from '@wordpress/compose'; import BlockAsyncModeProvider from './block-async-mode-provider'; import BlockListBlock from './block'; import BlockListAppender from '../block-list-appender'; -import { getBlockDOMNode } from '../../utils/dom'; /** * If the block count exceeds the threshold, we disable the reordering animation @@ -49,23 +41,6 @@ class BlockList extends Component { this.onSelectionStart = this.onSelectionStart.bind( this ); this.onSelectionEnd = this.onSelectionEnd.bind( this ); - this.setBlockRef = this.setBlockRef.bind( this ); - this.setLastClientY = this.setLastClientY.bind( this ); - this.onPointerMove = throttle( this.onPointerMove.bind( this ), 100 ); - // Browser does not fire `*move` event when the pointer position changes - // relative to the document, so fire it with the last known position. - this.onScroll = () => this.onPointerMove( { clientY: this.lastClientY } ); - - this.lastClientY = 0; - this.nodes = {}; - } - - componentDidMount() { - window.addEventListener( 'mousemove', this.setLastClientY ); - } - - componentWillUnmount() { - window.removeEventListener( 'mousemove', this.setLastClientY ); } componentDidUpdate() { @@ -99,48 +74,6 @@ class BlockList extends Component { selection.addRange( range ); } - setLastClientY( { clientY } ) { - this.lastClientY = clientY; - } - - setBlockRef( node, clientId ) { - if ( node === null ) { - delete this.nodes[ clientId ]; - } else { - this.nodes = { - ...this.nodes, - [ clientId ]: node, - }; - } - } - - /** - * Handles a pointer move event to update the extent of the current cursor - * multi-selection. - * - * @param {MouseEvent} event A mousemove event object. - */ - onPointerMove( { clientY } ) { - // We don't start multi-selection until the mouse starts moving, so as - // to avoid dispatching multi-selection actions on an in-place click. - if ( ! this.props.isMultiSelecting ) { - this.props.onStartMultiSelect(); - } - - const blockContentBoundaries = getBlockDOMNode( this.selectionAtStart ).getBoundingClientRect(); - - // prevent multi-selection from triggering when the selected block is a float - // and the cursor is still between the top and the bottom of the block. - if ( clientY >= blockContentBoundaries.top && clientY <= blockContentBoundaries.bottom ) { - return; - } - - const y = clientY - blockContentBoundaries.top; - const key = findLast( this.coordMapKeys, ( coordY ) => coordY < y ); - - this.onSelectionChange( this.coordMap[ key ] ); - } - /** * Binds event handlers to the document for tracking a pending multi-select * in response to a mousedown event occurring in a rendered block. @@ -152,73 +85,22 @@ class BlockList extends Component { return; } - const boundaries = this.nodes[ clientId ].getBoundingClientRect(); - - // Create a clientId to Y coördinate map. - const clientIdToCoordMap = mapValues( this.nodes, ( node ) => - node.getBoundingClientRect().top - boundaries.top ); - - // Cache a Y coördinate to clientId map for use in `onPointerMove`. - this.coordMap = invert( clientIdToCoordMap ); - // Cache an array of the Y coördinates for use in `onPointerMove`. - // Sort the coördinates, as `this.nodes` will not necessarily reflect - // the current block sequence. - this.coordMapKeys = sortBy( Object.values( clientIdToCoordMap ) ); - this.selectionAtStart = clientId; - - window.addEventListener( 'mousemove', this.onPointerMove ); - // Capture scroll on all elements. - window.addEventListener( 'scroll', this.onScroll, true ); - window.addEventListener( 'mouseup', this.onSelectionEnd ); + this.onSelectionStart.clientId = clientId; + this.props.onStartMultiSelect(); } /** - * Handles multi-selection changes in response to pointer move. + * Handles a mouseup event to end the current cursor multi-selection. * - * @param {string} clientId Client ID of block under cursor in multi-select - * drag. + * @param {string} clientId Client ID of block where mouseup occurred. */ - onSelectionChange( clientId ) { - const { onMultiSelect, selectionStart, selectionEnd } = this.props; - const { selectionAtStart } = this; - const isAtStart = selectionAtStart === clientId; - - if ( ! selectionAtStart || ! this.props.isSelectionEnabled ) { + onSelectionEnd( clientId ) { + if ( ! this.props.isMultiSelecting ) { return; } - // If multi-selecting and cursor extent returns to the start of - // selection, cancel multi-select. - if ( isAtStart && selectionStart ) { - onMultiSelect( null, null ); - } - - // Expand multi-selection to block under cursor. - if ( ! isAtStart && selectionEnd !== clientId ) { - onMultiSelect( selectionAtStart, clientId ); - } - } - - /** - * Handles a mouseup event to end the current cursor multi-selection. - */ - onSelectionEnd() { - // Cancel throttled calls. - this.onPointerMove.cancel(); - - delete this.coordMap; - delete this.coordMapKeys; - delete this.selectionAtStart; - - window.removeEventListener( 'mousemove', this.onPointerMove ); - window.removeEventListener( 'scroll', this.onScroll, true ); - window.removeEventListener( 'mouseup', this.onSelectionEnd ); - - // We may or may not be in a multi-selection when mouseup occurs (e.g. - // an in-place mouse click), so only trigger stop if multi-selecting. - if ( this.props.isMultiSelecting ) { - this.props.onStopMultiSelect(); - } + this.props.onMultiSelect( this.onSelectionStart.clientId, clientId ); + this.props.onStopMultiSelect(); } render() { @@ -255,8 +137,8 @@ class BlockList extends Component { .block-editor-block-list__block-edit::before { // Use opacity to work in various editor styles. border-color: $dark-opacity-light-800; @@ -157,6 +156,18 @@ } } + // Selected style. + &.is-multi-selected { + > .block-editor-block-list__block-edit::before { + border-left-color: $dark-opacity-light-800; + box-shadow: inset $block-left-border-width 0 0 0 $dark-gray-500; + } + + &.is-multi-selected-first > .block-editor-block-list__block-edit::before { + border-top-color: $dark-opacity-light-800; + } + } + // Hover style. &.is-hovered:not(.is-navigate-mode) > .block-editor-block-list__block-edit::before { box-shadow: -$block-left-border-width 0 0 0 $dark-opacity-light-500; diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 4978d090968f45..c90014ea6c8f8d 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -352,7 +352,7 @@ class RichTextWrapper extends Component { undo, placeholder, keepPlaceholderOnFocus, - hasMultiSelection, + isMultiSelecting, // eslint-disable-next-line no-unused-vars allowedFormats, withoutInteractiveFormatting, @@ -421,7 +421,7 @@ class RichTextWrapper extends Component { __unstableMarkAutomaticChange={ markAutomaticChange } __unstableDidAutomaticChange={ didAutomaticChange } __unstableUndo={ undo } - __unstableContentEditable={ ! hasMultiSelection } + __unstableContentEditable={ ! isMultiSelecting } > { ( { isSelected, value, onChange, Editable } ) => <> @@ -493,7 +493,7 @@ const RichTextContainer = compose( [ getSelectionEnd, getSettings, didAutomaticChange, - hasMultiSelection, + isMultiSelecting, } = select( 'core/block-editor' ); const selectionStart = getSelectionStart(); @@ -515,7 +515,7 @@ const RichTextContainer = compose( [ selectionEnd: isSelected ? selectionEnd.offset : undefined, isSelected, didAutomaticChange: didAutomaticChange(), - hasMultiSelection: hasMultiSelection(), + isMultiSelecting: isMultiSelecting(), }; } ), withDispatch( ( dispatch, { diff --git a/packages/block-editor/src/components/rich-text/style.scss b/packages/block-editor/src/components/rich-text/style.scss index 8d3eeb019834d2..bb4818a6e47d02 100644 --- a/packages/block-editor/src/components/rich-text/style.scss +++ b/packages/block-editor/src/components/rich-text/style.scss @@ -14,10 +14,6 @@ background: $light-gray-200; font-family: $editor-html-font; font-size: inherit; // This is necessary to override upstream CSS. - - .is-multi-selected & { - background: darken($blue-medium-highlight, 15%); - } } &:focus { diff --git a/packages/block-editor/src/components/warning/style.scss b/packages/block-editor/src/components/warning/style.scss index 3f6c78825c68b2..da6306932af853 100644 --- a/packages/block-editor/src/components/warning/style.scss +++ b/packages/block-editor/src/components/warning/style.scss @@ -8,11 +8,6 @@ text-align: left; padding: 10px $block-padding $block-padding; - // Avoid conflict with the multi-selection highlight color. - .has-warning.is-multi-selected & { - background-color: transparent; - } - .is-selected & { // Use opacity to work in various editor styles. border-color: $dark-opacity-light-800;