From a787f32d746cd8b0b062508f640ea00ec671873b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 15 Apr 2019 13:32:57 +0100 Subject: [PATCH 1/5] Update the block component to use react hooks instead --- .../src/components/block-list/block.js | 880 +++++++++--------- packages/eslint-plugin/configs/react.js | 1 - 2 files changed, 431 insertions(+), 450 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 5e12d4ef18d45..00d0d007e497c 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -7,7 +7,7 @@ import { get, reduce, size, first, last } from 'lodash'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { useRef, useEffect, useState } from '@wordpress/element'; import { focus, isTextField, @@ -48,100 +48,172 @@ import Inserter from '../inserter'; import HoverArea from './hover-area'; import { isInsideRootBlock } from '../../utils/dom'; -export class BlockListBlock extends Component { - constructor() { - super( ...arguments ); - - this.setBlockListRef = this.setBlockListRef.bind( this ); - this.bindBlockNode = this.bindBlockNode.bind( this ); - this.setAttributes = this.setAttributes.bind( this ); - this.maybeHover = this.maybeHover.bind( this ); - this.forceFocusedContextualToolbar = this.forceFocusedContextualToolbar.bind( this ); - this.hideHoverEffects = this.hideHoverEffects.bind( this ); - this.onFocus = this.onFocus.bind( this ); - this.preventDrag = this.preventDrag.bind( this ); - this.onPointerDown = this.onPointerDown.bind( this ); - this.deleteOrInsertAfterWrapper = this.deleteOrInsertAfterWrapper.bind( this ); - this.onBlockError = this.onBlockError.bind( this ); - this.onTouchStart = this.onTouchStart.bind( this ); - this.onClick = this.onClick.bind( this ); - this.onDragStart = this.onDragStart.bind( this ); - this.onDragEnd = this.onDragEnd.bind( this ); - this.selectOnOpen = this.selectOnOpen.bind( this ); - this.hadTouchStart = false; - - this.state = { - error: null, - dragging: false, - isHovered: false, - }; - this.isForcingContextualToolbar = false; - } +/** + * Prevents default dragging behavior within a block to allow for multi- + * selection to take effect unhampered. + * + * @param {DragEvent} event Drag event. + * + * @return {void} + */ +const preventDrag = ( event ) => { + event.preventDefault(); +}; + +function BlockListBlock( { + blockRef, + mode, + isFocusMode, + hasFixedToolbar, + isLocked, + clientId, + rootClientId, + isSelected, + isPartOfMultiSelection, + isFirstMultiSelected, + isTypingWithinBlock, + isCaretWithinFormattedText, + isEmptyDefaultBlock, + isMovable, + isParentOfSelectedBlock, + isDraggable, + isSelectionEnabled, + className, + name, + isValid, + attributes, + initialPosition, + wrapperProps, + onMetaChange, + onReplace, + onChange, + onInsertBlocksAfter, + onMerge, + onSelect, + onRemove, + onInsertDefaultBlockAfter, + toggleSelection, + onShiftSelection, + onSelectionStart, +} ) { + // Random state used to rerender the component if needed, ideally we don't need this + const [ , updateRerenderState ] = useState( {} ); + const rerender = () => updateRerenderState( {} ); + + // Reference of the wrapper ------------------------------------------------- + const wrapper = useRef( null ); + useEffect( () => { + blockRef( wrapper.current, clientId ); + // We need to rerender to trigger a rerendering of HoverArea. + rerender(); + }, [ blockRef, clientId ] ); + + // Reference to the block edit node ----------------------------------------- + const blockNodeRef = useRef(); + + // Keep track of touchstart to disable hover on iOS ------------------------- + const hadTouchStart = useRef( false ); + const onTouchStart = () => { + hadTouchStart.current = true; + }; + const onTouchStop = () => { + // Clear touchstart detection + // Browser will try to emulate mouse events also see https://www.html5rocks.com/en/mobile/touchandmouse/ + hadTouchStart.current = false; + }; - componentDidMount() { - if ( this.props.isSelected ) { - this.focusTabbable(); - } - } + // Handling isHovered ------------------------------------------------------- + const [ isBlockHovered, setBlockHoveredState ] = useState( false ); - componentDidUpdate( prevProps ) { - if ( this.isForcingContextualToolbar ) { - // The forcing of contextual toolbar should only be true during one update, - // after the first update normal conditions should apply. - this.isForcingContextualToolbar = false; + /** + * Sets the block state as unhovered if currently hovering. There are cases + * where mouseleave may occur but the block is not hovered (multi-select), + * so to avoid unnecesary renders, the state is only set if hovered. + */ + const hideHoverEffects = () => { + if ( isBlockHovered ) { + setBlockHoveredState( false ); } - if ( this.props.isTypingWithinBlock || this.props.isSelected ) { - this.hideHoverEffects(); + }; + /** + * A mouseover event handler to apply hover effect when a pointer device is + * placed within the bounds of the block. The mouseover event is preferred + * over mouseenter because it may be the case that a previous mouseenter + * event was blocked from being handled by a IgnoreNestedEvents component, + * therefore transitioning out of a nested block to the bounds of the block + * would otherwise not trigger a hover effect. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Events/mouseenter + */ + const maybeHover = () => { + if ( + isBlockHovered || + isPartOfMultiSelection || + isSelected || + hadTouchStart.current + ) { + return; } + setBlockHoveredState( true ); + }; - if ( this.props.isSelected && ! prevProps.isSelected ) { - this.focusTabbable( true ); + // Set hover to false once we start typing or select the block. + useEffect( () => { + if ( isTypingWithinBlock || isSelected ) { + hideHoverEffects(); } + } ); - // When triggering a multi-selection, move the focus to the wrapper of the first selected block. - // This ensures that it is not possible to continue editing the initially selected block - // when a multi-selection is triggered. - if ( this.props.isFirstMultiSelected && ! prevProps.isFirstMultiSelected ) { - this.wrapperNode.focus(); - } - } + // Handling the dragging state ---------------------------------------------- + const [ isDragging, setBlockDraggingState ] = useState( false ); + const onDragStart = () => { + setBlockDraggingState( true ); + }; + const onDragEnd = () => { + setBlockDraggingState( false ); + }; - setBlockListRef( node ) { - this.wrapperNode = node; - this.props.blockRef( node, this.props.clientId ); + // Handling the error state ------------------------------------------------- + const [ hasError, setErrorState ] = useState( false ); + const onBlockError = () => setErrorState( false ); - // We need to rerender to trigger a rerendering of HoverArea - // it depents on this.wrapperNode but we can't keep this.wrapperNode in state - // Because we need it to be immediately availeble for `focusableTabbable` to work. - this.forceUpdate(); - } + // Handling of forceContextualToolbarFocus ---------------------------------- + const isForcingContextualToolbar = useRef( false ); + useEffect( () => { + if ( isForcingContextualToolbar.current ) { + // The forcing of contextual toolbar should only be true during one update, + // after the first update normal conditions should apply. + isForcingContextualToolbar.current = false; + } + } ); + const forceFocusedContextualToolbar = () => { + isForcingContextualToolbar.current = true; + // trigger a re-render + rerender(); + }; - bindBlockNode( node ) { - this.node = node; - } + // Handing the focus of the block on creation and update -------------------- /** * When a block becomes selected, transition focus to an inner tabbable. * * @param {boolean} ignoreInnerBlocks Should not focus inner blocks. */ - focusTabbable( ignoreInnerBlocks ) { - const { initialPosition } = this.props; - + const focusTabbable = ( ignoreInnerBlocks ) => { // Focus is captured by the wrapper node, so while focus transition // should only consider tabbables within editable display, since it // may be the wrapper itself or a side control which triggered the // focus event, don't unnecessary transition to an inner tabbable. - if ( this.wrapperNode.contains( document.activeElement ) ) { + if ( wrapper.current.contains( document.activeElement ) ) { return; } // Find all tabbables within node. const textInputs = focus.tabbable - .find( this.node ) + .find( blockNodeRef.current ) .filter( isTextField ) // Exclude inner blocks - .filter( ( node ) => ! ignoreInnerBlocks || isInsideRootBlock( this.node, node ) ); + .filter( ( node ) => ! ignoreInnerBlocks || isInsideRootBlock( blockNodeRef.current, node ) ); // If reversed (e.g. merge via backspace), use the last in the set of // tabbables. @@ -149,20 +221,35 @@ export class BlockListBlock extends Component { const target = ( isReverse ? last : first )( textInputs ); if ( ! target ) { - this.wrapperNode.focus(); + wrapper.current.focus(); return; } placeCaretAtHorizontalEdge( target, isReverse ); - } + }; - setAttributes( attributes ) { - const { clientId, name, onChange } = this.props; - const type = getBlockType( name ); - onChange( clientId, attributes ); + // Focus the selected block's wrapper or inner input on mount and update + const isMounting = useRef( true ); + useEffect( () => { + if ( isSelected ) { + focusTabbable( ! isMounting.current ); + } + isMounting.current = false; + }, [ focusTabbable, isSelected ] ); + // Focus the first multi selected block + useEffect( () => { + if ( isFirstMultiSelected ) { + wrapper.current.focus(); + } + }, [ isFirstMultiSelected ] ); + + // Other event handlers ----------------------------------------------------- + const setAttributes = ( newAttributes ) => { + const type = getBlockType( name ); + onChange( clientId, newAttributes ); const metaAttributes = reduce( - attributes, + newAttributes, ( result, value, key ) => { if ( get( type, [ 'attributes', key, 'source' ] ) === 'meta' ) { result[ type.attributes[ key ].meta ] = value; @@ -174,57 +261,9 @@ export class BlockListBlock extends Component { ); if ( size( metaAttributes ) ) { - this.props.onMetaChange( metaAttributes ); - } - } - - onTouchStart() { - // Detect touchstart to disable hover on iOS - this.hadTouchStart = true; - } - - onClick() { - // Clear touchstart detection - // Browser will try to emulate mouse events also see https://www.html5rocks.com/en/mobile/touchandmouse/ - this.hadTouchStart = false; - } - - /** - * A mouseover event handler to apply hover effect when a pointer device is - * placed within the bounds of the block. The mouseover event is preferred - * over mouseenter because it may be the case that a previous mouseenter - * event was blocked from being handled by a IgnoreNestedEvents component, - * therefore transitioning out of a nested block to the bounds of the block - * would otherwise not trigger a hover effect. - * - * @see https://developer.mozilla.org/en-US/docs/Web/Events/mouseenter - */ - maybeHover() { - const { isPartOfMultiSelection, isSelected } = this.props; - const { isHovered } = this.state; - - if ( - isHovered || - isPartOfMultiSelection || - isSelected || - this.hadTouchStart - ) { - return; + onMetaChange( metaAttributes ); } - - this.setState( { isHovered: true } ); - } - - /** - * Sets the block state as unhovered if currently hovering. There are cases - * where mouseleave may occur but the block is not hovered (multi-select), - * so to avoid unnecesary renders, the state is only set if hovered. - */ - hideHoverEffects() { - if ( this.state.isHovered ) { - this.setState( { isHovered: false } ); - } - } + }; /** * Marks the block as selected when focused and not already selected. This @@ -233,56 +272,11 @@ export class BlockListBlock extends Component { * * @return {void} */ - onFocus() { - if ( ! this.props.isSelected && ! this.props.isPartOfMultiSelection ) { - this.props.onSelect(); - } - } - - /** - * Prevents default dragging behavior within a block to allow for multi- - * selection to take effect unhampered. - * - * @param {DragEvent} event Drag event. - * - * @return {void} - */ - preventDrag( event ) { - event.preventDefault(); - } - - /** - * Begins tracking cursor multi-selection when clicking down within block. - * - * @param {MouseEvent} event A mousedown event. - * - * @return {void} - */ - onPointerDown( event ) { - // Not the main button. - // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button - if ( event.button !== 0 ) { - return; + const onFocus = () => { + if ( ! isSelected && ! isPartOfMultiSelection ) { + onSelect(); } - - if ( event.shiftKey ) { - if ( ! this.props.isSelected ) { - this.props.onShiftSelection(); - event.preventDefault(); - } - } else { - this.props.onSelectionStart( this.props.clientId ); - - // Allow user to escape out of a multi-selection to a singular - // selection of a block via click. This is handled here since - // onFocus excludes blocks involved in a multiselection, as - // focus can be incurred by starting a multiselection (focus - // moved to first block's multi-controls). - if ( this.props.isPartOfMultiSelection ) { - this.props.onSelect(); - } - } - } + }; /** * Interprets keydown event intent to remove or insert after block if key @@ -292,15 +286,15 @@ export class BlockListBlock extends Component { * * @param {KeyboardEvent} event Keydown event. */ - deleteOrInsertAfterWrapper( event ) { + const deleteOrInsertAfterWrapper = ( event ) => { const { keyCode, target } = event; // These block shortcuts should only trigger if the wrapper of the block is selected // And when it's not a multi-selection to avoid conflicting with RichText/Inputs and multiselection. if ( - ! this.props.isSelected || - target !== this.wrapperNode || - this.props.isLocked + ! isSelected || + target !== wrapper.current || + isLocked ) { return; } @@ -309,304 +303,292 @@ export class BlockListBlock extends Component { case ENTER: // Insert default block after current block if enter and event // not already handled by descendant. - this.props.onInsertDefaultBlockAfter(); + onInsertDefaultBlockAfter(); event.preventDefault(); break; case BACKSPACE: case DELETE: // Remove block on backspace. - const { clientId, onRemove } = this.props; onRemove( clientId ); event.preventDefault(); break; } - } + }; - onBlockError( error ) { - this.setState( { error } ); - } + /** + * Begins tracking cursor multi-selection when clicking down within block. + * + * @param {MouseEvent} event A mousedown event. + * + * @return {void} + */ + const onPointerDown = ( event ) => { + // Not the main button. + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + if ( event.button !== 0 ) { + return; + } - onDragStart() { - this.setState( { dragging: true } ); - } + if ( event.shiftKey ) { + if ( ! isSelected ) { + onShiftSelection(); + event.preventDefault(); + } + } else { + onSelectionStart( clientId ); - onDragEnd() { - this.setState( { dragging: false } ); - } + // Allow user to escape out of a multi-selection to a singular + // selection of a block via click. This is handled here since + // onFocus excludes blocks involved in a multiselection, as + // focus can be incurred by starting a multiselection (focus + // moved to first block's multi-controls). + if ( isPartOfMultiSelection ) { + onSelect(); + } + } + }; - selectOnOpen( open ) { - if ( open && ! this.props.isSelected ) { - this.props.onSelect(); + const selectOnOpen = ( open ) => { + if ( open && ! isSelected ) { + onSelect(); } - } + }; - forceFocusedContextualToolbar() { - this.isForcingContextualToolbar = true; - // trigger a re-render - this.setState( () => ( {} ) ); - } + return ( + + { ( { hoverArea } ) => { + const isHovered = isBlockHovered && ! isPartOfMultiSelection; + const blockType = getBlockType( name ); + // translators: %s: Type of block (i.e. Text, Image etc) + const blockLabel = sprintf( __( 'Block: %s' ), blockType.title ); + // The block as rendered in the editor is composed of general block UI + // (mover, toolbar, wrapper) and the display of the block content. + + const isUnregisteredBlock = name === getUnregisteredTypeHandlerName(); + + // If the block is selected and we're typing the block should not appear. + // Empty paragraph blocks should always show up as unselected. + const showEmptyBlockSideInserter = + ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; + const shouldAppearSelected = + ! isFocusMode && + ! showEmptyBlockSideInserter && + isSelected && + ! isTypingWithinBlock; + const shouldAppearHovered = + ! isFocusMode && + ! hasFixedToolbar && + isHovered && + ! isEmptyDefaultBlock; + // We render block movers and block settings to keep them tabbale even if hidden + const shouldRenderMovers = + ( isSelected || hoverArea === 'left' ) && + ! showEmptyBlockSideInserter && + ! isPartOfMultiSelection && + ! isTypingWithinBlock; + const shouldShowBreadcrumb = + ! isFocusMode && isHovered && ! isEmptyDefaultBlock; + const shouldShowContextualToolbar = + ! hasFixedToolbar && + ! showEmptyBlockSideInserter && + ( ( isSelected && + ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) || + isFirstMultiSelected ); + const shouldShowMobileToolbar = shouldAppearSelected; + + // Insertion point can only be made visible if the block is at the + // the extent of a multi-selection, or not in a multi-selection. + const shouldShowInsertionPoint = + ( isPartOfMultiSelection && isFirstMultiSelected ) || + ! isPartOfMultiSelection; + + // The wp-block className is important for editor styles. + // Generate the wrapper class names handling the different states of the block. + const wrapperClassName = classnames( + 'wp-block editor-block-list__block block-editor-block-list__block', + { + 'has-warning': ! isValid || !! hasError || isUnregisteredBlock, + 'is-selected': shouldAppearSelected, + 'is-multi-selected': isPartOfMultiSelection, + 'is-hovered': shouldAppearHovered, + 'is-reusable': isReusableBlock( blockType ), + 'is-dragging': isDragging, + 'is-typing': isTypingWithinBlock, + 'is-focused': + isFocusMode && ( isSelected || isParentOfSelectedBlock ), + 'is-focus-mode': isFocusMode, + }, + className + ); + + // Determine whether the block has props to apply to the wrapper. + let blockWrapperProps = wrapperProps; + if ( blockType.getEditWrapperProps ) { + blockWrapperProps = { + ...blockWrapperProps, + ...blockType.getEditWrapperProps( attributes ), + }; + } + const blockElementId = `block-${ clientId }`; + + // We wrap the BlockEdit component in a div that hides it when editing in + // HTML mode. This allows us to render all of the ancillary pieces + // (InspectorControls, etc.) which are inside `BlockEdit` but not + // `BlockHTML`, even in HTML mode. + let blockEdit = ( + + ); + if ( mode !== 'visual' ) { + blockEdit =
{ blockEdit }
; + } - render() { - return ( - - { ( { hoverArea } ) => { - const { - mode, - isFocusMode, - hasFixedToolbar, - isLocked, - clientId, - rootClientId, - isSelected, - isPartOfMultiSelection, - isFirstMultiSelected, - isTypingWithinBlock, - isCaretWithinFormattedText, - isEmptyDefaultBlock, - isMovable, - isParentOfSelectedBlock, - isDraggable, - className, - name, - isValid, - attributes, - } = this.props; - const isHovered = this.state.isHovered && ! isPartOfMultiSelection; - const blockType = getBlockType( name ); - // translators: %s: Type of block (i.e. Text, Image etc) - const blockLabel = sprintf( __( 'Block: %s' ), blockType.title ); - // The block as rendered in the editor is composed of general block UI - // (mover, toolbar, wrapper) and the display of the block content. - - const isUnregisteredBlock = name === getUnregisteredTypeHandlerName(); - - // If the block is selected and we're typing the block should not appear. - // Empty paragraph blocks should always show up as unselected. - const showEmptyBlockSideInserter = - ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; - const shouldAppearSelected = - ! isFocusMode && - ! showEmptyBlockSideInserter && - isSelected && - ! isTypingWithinBlock; - const shouldAppearHovered = - ! isFocusMode && - ! hasFixedToolbar && - isHovered && - ! isEmptyDefaultBlock; - // We render block movers and block settings to keep them tabbale even if hidden - const shouldRenderMovers = - ( isSelected || hoverArea === 'left' ) && - ! showEmptyBlockSideInserter && - ! isPartOfMultiSelection && - ! isTypingWithinBlock; - const shouldShowBreadcrumb = - ! isFocusMode && isHovered && ! isEmptyDefaultBlock; - const shouldShowContextualToolbar = - ! hasFixedToolbar && - ! showEmptyBlockSideInserter && - ( ( isSelected && - ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) || - isFirstMultiSelected ); - const shouldShowMobileToolbar = shouldAppearSelected; - const { error, dragging } = this.state; - - // Insertion point can only be made visible if the block is at the - // the extent of a multi-selection, or not in a multi-selection. - const shouldShowInsertionPoint = - ( isPartOfMultiSelection && isFirstMultiSelected ) || - ! isPartOfMultiSelection; - - // The wp-block className is important for editor styles. - // Generate the wrapper class names handling the different states of the block. - const wrapperClassName = classnames( - 'wp-block editor-block-list__block block-editor-block-list__block', - { - 'has-warning': ! isValid || !! error || isUnregisteredBlock, - 'is-selected': shouldAppearSelected, - 'is-multi-selected': isPartOfMultiSelection, - 'is-hovered': shouldAppearHovered, - 'is-reusable': isReusableBlock( blockType ), - 'is-dragging': dragging, - 'is-typing': isTypingWithinBlock, - 'is-focused': - isFocusMode && ( isSelected || isParentOfSelectedBlock ), - 'is-focus-mode': isFocusMode, - }, - className - ); - - const { onReplace } = this.props; - - // Determine whether the block has props to apply to the wrapper. - let wrapperProps = this.props.wrapperProps; - if ( blockType.getEditWrapperProps ) { - wrapperProps = { - ...wrapperProps, - ...blockType.getEditWrapperProps( attributes ), - }; - } - const blockElementId = `block-${ clientId }`; - - // We wrap the BlockEdit component in a div that hides it when editing in - // HTML mode. This allows us to render all of the ancillary pieces - // (InspectorControls, etc.) which are inside `BlockEdit` but not - // `BlockHTML`, even in HTML mode. - let blockEdit = ( - + { shouldShowInsertionPoint && ( + + ) } + - ); - if ( mode !== 'visual' ) { - blockEdit =
{ blockEdit }
; - } - - // Disable reasons: - // - // jsx-a11y/mouse-events-have-key-events: - // - onMouseOver is explicitly handling hover effects - // - // jsx-a11y/no-static-element-interactions: - // - Each block can be selected by clicking on it - - /* eslint-disable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ - - return ( - - { shouldShowInsertionPoint && ( - + ) } +
+ { shouldRenderMovers && ( + + ) } + { shouldShowBreadcrumb && ( + ) } - - { isFirstMultiSelected && ( - + { ( shouldShowContextualToolbar || + isForcingContextualToolbar.current ) && ( + ) } -
- { shouldRenderMovers && ( - + { ! shouldShowContextualToolbar && + isSelected && + ! hasFixedToolbar && + ! isEmptyDefaultBlock && ( + + ) } + + + { isValid && blockEdit } + { isValid && mode === 'html' && ( + + ) } + { ! isValid && [ + , +
+ { getSaveElement( blockType, attributes ) } +
, + ] } +
+ { shouldShowMobileToolbar && ( + ) } - { shouldShowBreadcrumb && ( - } +
+
+ { showEmptyBlockSideInserter && ( + <> +
+ - ) } - { ( shouldShowContextualToolbar || - this.isForcingContextualToolbar ) && ( - - ) } - { ! shouldShowContextualToolbar && - isSelected && - ! hasFixedToolbar && - ! isEmptyDefaultBlock && ( - +
+ - ) } - - - { isValid && blockEdit } - { isValid && mode === 'html' && ( - - ) } - { ! isValid && [ - , -
- { getSaveElement( blockType, attributes ) } -
, - ] } -
- { shouldShowMobileToolbar && ( - - ) } - { !! error && } -
-
- { showEmptyBlockSideInserter && ( - <> -
- -
-
- -
- - ) } - - ); - /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ - } } - - ); - } +
+ + ) } + + ); + /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + } } + + ); } const applyWithSelect = withSelect( diff --git a/packages/eslint-plugin/configs/react.js b/packages/eslint-plugin/configs/react.js index 4947d15fb5746..8f88b208df2e3 100644 --- a/packages/eslint-plugin/configs/react.js +++ b/packages/eslint-plugin/configs/react.js @@ -26,6 +26,5 @@ module.exports = { 'react/prop-types': 'off', 'react/react-in-jsx-scope': 'off', 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', }, }; From 9fd864068758d4f4ff34f3ae827712833600d0b2 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 16 Apr 2019 09:53:21 +0100 Subject: [PATCH 2/5] Move setAttributes handler to the withDispatch HoC --- .../src/components/block-list/block.js | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 00d0d007e497c..2dbe4510871af 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -84,9 +84,8 @@ function BlockListBlock( { attributes, initialPosition, wrapperProps, - onMetaChange, + setAttributes, onReplace, - onChange, onInsertBlocksAfter, onMerge, onSelect, @@ -245,25 +244,6 @@ function BlockListBlock( { }, [ isFirstMultiSelected ] ); // Other event handlers ----------------------------------------------------- - const setAttributes = ( newAttributes ) => { - const type = getBlockType( name ); - onChange( clientId, newAttributes ); - const metaAttributes = reduce( - newAttributes, - ( result, value, key ) => { - if ( get( type, [ 'attributes', key, 'source' ] ) === 'meta' ) { - result[ type.attributes[ key ].meta ] = value; - } - - return result; - }, - {} - ); - - if ( size( metaAttributes ) ) { - onMetaChange( metaAttributes ); - } - }; /** * Marks the block as selected when focused and not already selected. This @@ -663,11 +643,31 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { mergeBlocks, replaceBlocks, toggleSelection, + } = dispatch( 'core/block-editor' ); return { - onChange( clientId, attributes ) { - updateBlockAttributes( clientId, attributes ); + setAttributes( newAttributes ) { + const { name, clientId } = ownProps; + const type = getBlockType( name ); + updateBlockAttributes( clientId, newAttributes ); + const metaAttributes = reduce( + newAttributes, + ( result, value, key ) => { + if ( get( type, [ 'attributes', key, 'source' ] ) === 'meta' ) { + result[ type.attributes[ key ].meta ] = value; + } + + return result; + }, + {} + ); + + if ( size( metaAttributes ) ) { + const { getSettings } = select( 'core/block-editor' ); + const onChangeMeta = getSettings().__experimentalMetaSource.onChange; + onChangeMeta( metaAttributes ); + } }, onSelect( clientId = ownProps.clientId, initialPosition ) { selectBlock( clientId, initialPosition ); @@ -717,11 +717,6 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { onReplace( blocks ) { replaceBlocks( [ ownProps.clientId ], blocks ); }, - onMetaChange( updatedMeta ) { - const { getSettings } = select( 'core/block-editor' ); - const onChangeMeta = getSettings().__experimentalMetaSource.onChange; - onChangeMeta( updatedMeta ); - }, onShiftSelection() { if ( ! ownProps.isSelectionEnabled ) { return; From c791a7292209939a872109b6694eeda971d37dd7 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 7 May 2019 10:35:47 +0100 Subject: [PATCH 3/5] Remove dashes from comments --- .../src/components/block-list/block.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 2dbe4510871af..94076901cd751 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -99,7 +99,7 @@ function BlockListBlock( { const [ , updateRerenderState ] = useState( {} ); const rerender = () => updateRerenderState( {} ); - // Reference of the wrapper ------------------------------------------------- + // Reference of the wrapper const wrapper = useRef( null ); useEffect( () => { blockRef( wrapper.current, clientId ); @@ -107,10 +107,10 @@ function BlockListBlock( { rerender(); }, [ blockRef, clientId ] ); - // Reference to the block edit node ----------------------------------------- + // Reference to the block edit node const blockNodeRef = useRef(); - // Keep track of touchstart to disable hover on iOS ------------------------- + // Keep track of touchstart to disable hover on iOS const hadTouchStart = useRef( false ); const onTouchStart = () => { hadTouchStart.current = true; @@ -121,7 +121,7 @@ function BlockListBlock( { hadTouchStart.current = false; }; - // Handling isHovered ------------------------------------------------------- + // Handling isHovered const [ isBlockHovered, setBlockHoveredState ] = useState( false ); /** @@ -163,7 +163,7 @@ function BlockListBlock( { } } ); - // Handling the dragging state ---------------------------------------------- + // Handling the dragging state const [ isDragging, setBlockDraggingState ] = useState( false ); const onDragStart = () => { setBlockDraggingState( true ); @@ -172,11 +172,11 @@ function BlockListBlock( { setBlockDraggingState( false ); }; - // Handling the error state ------------------------------------------------- + // Handling the error state const [ hasError, setErrorState ] = useState( false ); const onBlockError = () => setErrorState( false ); - // Handling of forceContextualToolbarFocus ---------------------------------- + // Handling of forceContextualToolbarFocus const isForcingContextualToolbar = useRef( false ); useEffect( () => { if ( isForcingContextualToolbar.current ) { @@ -191,7 +191,7 @@ function BlockListBlock( { rerender(); }; - // Handing the focus of the block on creation and update -------------------- + // Handing the focus of the block on creation and update /** * When a block becomes selected, transition focus to an inner tabbable. @@ -243,7 +243,7 @@ function BlockListBlock( { } }, [ isFirstMultiSelected ] ); - // Other event handlers ----------------------------------------------------- + // Other event handlers /** * Marks the block as selected when focused and not already selected. This From ec801e5322fca1605d94af4da4c192cd3fdcecdf Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 7 May 2019 10:38:34 +0100 Subject: [PATCH 4/5] Fix effect dependencies --- packages/block-editor/src/components/block-list/block.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 94076901cd751..97b02d838b0b4 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -105,7 +105,7 @@ function BlockListBlock( { blockRef( wrapper.current, clientId ); // We need to rerender to trigger a rerendering of HoverArea. rerender(); - }, [ blockRef, clientId ] ); + }, [] ); // Reference to the block edit node const blockNodeRef = useRef(); @@ -234,7 +234,7 @@ function BlockListBlock( { focusTabbable( ! isMounting.current ); } isMounting.current = false; - }, [ focusTabbable, isSelected ] ); + }, [ isSelected ] ); // Focus the first multi selected block useEffect( () => { From 709cf532920e1da2bdc74ee68f715bc6ab8f0647 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 7 May 2019 10:39:35 +0100 Subject: [PATCH 5/5] Remove useless comment from docs --- packages/block-editor/src/components/block-list/block.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 97b02d838b0b4..87b3bccdc2bed 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -53,8 +53,6 @@ import { isInsideRootBlock } from '../../utils/dom'; * selection to take effect unhampered. * * @param {DragEvent} event Drag event. - * - * @return {void} */ const preventDrag = ( event ) => { event.preventDefault(); @@ -249,8 +247,6 @@ function BlockListBlock( { * Marks the block as selected when focused and not already selected. This * specifically handles the case where block does not set focus on its own * (via `setFocus`), typically if there is no focusable input in the block. - * - * @return {void} */ const onFocus = () => { if ( ! isSelected && ! isPartOfMultiSelection ) { @@ -300,8 +296,6 @@ function BlockListBlock( { * Begins tracking cursor multi-selection when clicking down within block. * * @param {MouseEvent} event A mousedown event. - * - * @return {void} */ const onPointerDown = ( event ) => { // Not the main button.