diff --git a/lib/client-assets.php b/lib/client-assets.php index 6abd0c1c5f0d5..2306b390f258d 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -670,7 +670,7 @@ function gutenberg_extend_settings_block_patterns( $settings ) { * @return array Filtered editor settings. */ function gutenberg_extend_settings_custom_line_height( $settings ) { - $settings['__experimentalDisableCustomLineHeight'] = get_theme_support( 'disable-custom-line-height' ); + $settings['enableCustomLineHeight'] = get_theme_support( 'custom-line-height' ); return $settings; } add_filter( 'block_editor_settings', 'gutenberg_extend_settings_custom_line_height' ); @@ -684,7 +684,7 @@ function gutenberg_extend_settings_custom_line_height( $settings ) { * @return array Filtered editor settings. */ function gutenberg_extend_settings_custom_units( $settings ) { - $settings['__experimentalDisableCustomUnits'] = get_theme_support( 'experimental-custom-units' ); + $settings['enableCustomUnits'] = get_theme_support( 'custom-units' ); return $settings; } add_filter( 'block_editor_settings', 'gutenberg_extend_settings_custom_units' ); diff --git a/package-lock.json b/package-lock.json index aca7405956d8e..f90270128f884 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10997,6 +10997,7 @@ "memize": "^1.1.0", "react-autosize-textarea": "^3.0.2", "react-spring": "^8.0.19", + "reakit": "1.1.0", "redux-multi": "^0.1.12", "refx": "^3.0.0", "rememo": "^3.0.0", diff --git a/packages/block-directory/src/components/downloadable-blocks-panel/index.js b/packages/block-directory/src/components/downloadable-blocks-panel/index.js index e78077c99e47f..835141927c7ba 100644 --- a/packages/block-directory/src/components/downloadable-blocks-panel/index.js +++ b/packages/block-directory/src/components/downloadable-blocks-panel/index.js @@ -22,18 +22,10 @@ function DownloadableBlocksPanel( { debouncedSpeak, } ) { if ( false === hasPermission ) { - debouncedSpeak( - __( - 'No blocks found in your library. Please contact your site administrator to install new blocks.' - ) - ); + debouncedSpeak( __( 'No blocks found in your library.' ) ); return (

{ __( 'No blocks found in your library.' ) } -
- { __( - 'Please contact your site administrator to install new blocks.' - ) }

); } diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index f4b164a9a58e9..bc3f7f0465455 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -481,6 +481,7 @@ _Properties_ - _disableCustomColors_ `boolean`: Whether or not the custom colors are disabled - _fontSizes_ `Array`: Available font sizes - _disableCustomFontSizes_ `boolean`: Whether or not the custom font sizes are disabled +- _imageEditing_ `boolean`: Image Editing settings set to false to disable. - _imageSizes_ `Array`: Available image sizes - _maxWidth_ `number`: Max width to constraint resizing - _allowedBlockTypes_ `(boolean|Array)`: Allowed block types diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 8c1aae896af33..67bffa822e97e 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -60,6 +60,7 @@ "memize": "^1.1.0", "react-autosize-textarea": "^3.0.2", "react-spring": "^8.0.19", + "reakit": "1.1.0", "redux-multi": "^0.1.12", "refx": "^3.0.0", "rememo": "^3.0.0", diff --git a/packages/block-editor/src/components/block-list/block-wrapper.js b/packages/block-editor/src/components/block-list/block-wrapper.js index b80a4bbb210c1..7e933794b0fa2 100644 --- a/packages/block-editor/src/components/block-list/block-wrapper.js +++ b/packages/block-editor/src/components/block-list/block-wrapper.js @@ -71,6 +71,7 @@ const BlockComponent = forwardRef( initialPosition: isSelected ? getSelectedBlocksInitialCaretPosition() : undefined, + isNavigationMode: _isNavigationMode, }; }, [ isSelected ] diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index ef1f48c8e559e..5d143e4925fa2 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -6,7 +6,12 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useState, createContext, useMemo } from '@wordpress/element'; +import { + useState, + createContext, + useMemo, + useCallback, +} from '@wordpress/element'; import { getBlockType, getSaveElement, @@ -105,7 +110,7 @@ function BlockListBlock( { [ clientId ] ); const { removeBlock } = useDispatch( 'core/block-editor' ); - const onRemove = () => removeBlock( clientId ); + const onRemove = useCallback( () => removeBlock( clientId ), [ clientId ] ); // Handling the error state const [ hasError, setErrorState ] = useState( false ); diff --git a/packages/block-editor/src/components/block-list/insertion-point.js b/packages/block-editor/src/components/block-list/insertion-point.js index b1cab6c6d5e00..c7eb0e8d6ee80 100644 --- a/packages/block-editor/src/components/block-list/insertion-point.js +++ b/packages/block-editor/src/components/block-list/insertion-point.js @@ -18,57 +18,139 @@ import Inserter from '../inserter'; import { getClosestTabbable } from '../writing-flow'; import { getBlockDOMNode } from '../../utils/dom'; -function Indicator( { clientId } ) { - const showInsertionPoint = useSelect( +function InsertionPointInserter( { + clientId, + setIsInserterForced, + containerRef, +} ) { + const ref = useRef(); + // Hide the inserter above the selected block and during multi-selection. + const isInserterHidden = useSelect( ( select ) => { const { - getBlockIndex, - getBlockInsertionPoint, - isBlockInsertionPointVisible, - getBlockRootClientId, + getMultiSelectedBlockClientIds, + getSelectedBlockClientId, + hasMultiSelection, } = select( 'core/block-editor' ); - const rootClientId = getBlockRootClientId( clientId ); - const blockIndex = getBlockIndex( clientId, rootClientId ); - const insertionPoint = getBlockInsertionPoint(); - return ( - isBlockInsertionPointVisible() && - insertionPoint.index === blockIndex && - insertionPoint.rootClientId === rootClientId - ); + const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds(); + const selectedBlockClientId = getSelectedBlockClientId(); + + return hasMultiSelection() + ? multiSelectedBlockClientIds.includes( clientId ) + : clientId === selectedBlockClientId; }, [ clientId ] ); - if ( ! showInsertionPoint ) { - return null; + function focusClosestTabbable( event ) { + const { clientX, clientY, target } = event; + + // Only handle click on the wrapper specifically, and not an event + // bubbled from the inserter itself. + if ( target !== ref.current ) { + return; + } + + const targetRect = target.getBoundingClientRect(); + const isReverse = clientY < targetRect.top + targetRect.height / 2; + const blockNode = getBlockDOMNode( clientId ); + const container = isReverse ? containerRef.current : blockNode; + const closest = + getClosestTabbable( blockNode, true, container ) || blockNode; + const rect = new window.DOMRect( clientX, clientY, 0, 16 ); + + placeCaretAtVerticalEdge( closest, isReverse, rect, false ); } return ( -
+ /* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ +
setIsInserterForced( true ) } + onBlur={ () => setIsInserterForced( false ) } + onClick={ focusClosestTabbable } + // While ideally it would be enough to capture the + // bubbling focus event from the Inserter, due to the + // characteristics of click focusing of `button`s in + // Firefox and Safari, it is not reliable. + // + // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + tabIndex={ -1 } + className={ classnames( + 'block-editor-block-list__insertion-point-inserter', + { + 'is-inserter-hidden': isInserterHidden, + } + ) } + > + +
); } -export default function InsertionPoint( { - hasMultiSelection, - selectedBlockClientId, - children, +function InsertionPointPopover( { + clientId, + isInserterShown, + isInserterForced, + setIsInserterForced, containerRef, + showInsertionPoint, } ) { + const element = getBlockDOMNode( clientId ); + + return ( + +
+ { showInsertionPoint && ( +
+ ) } + { ( isInserterShown || isInserterForced ) && ( + + ) } +
+ + ); +} + +export default function InsertionPoint( { children, containerRef } ) { const [ isInserterShown, setIsInserterShown ] = useState( false ); const [ isInserterForced, setIsInserterForced ] = useState( false ); - const [ inserterElement, setInserterElement ] = useState( null ); const [ inserterClientId, setInserterClientId ] = useState( null ); - const ref = useRef(); - const { multiSelectedBlockClientIds, isMultiSelecting } = useSelect( + const { isMultiSelecting, isInserterVisible, selectedClientId } = useSelect( ( select ) => { const { - getMultiSelectedBlockClientIds, isMultiSelecting: _isMultiSelecting, + isBlockInsertionPointVisible, + getBlockInsertionPoint, + getBlockOrder, } = select( 'core/block-editor' ); + const insertionPoint = getBlockInsertionPoint(); + const order = getBlockOrder( insertionPoint.rootClientId ); + return { isMultiSelecting: _isMultiSelecting(), - multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(), + isInserterVisible: isBlockInsertionPointVisible(), + selectedClientId: order[ insertionPoint.index ], }; }, [] @@ -117,80 +199,24 @@ export default function InsertionPoint( { } setIsInserterShown( true ); - setInserterElement( element ); setInserterClientId( clientId ); } - function focusClosestTabbable( event ) { - const { clientX, clientY, target } = event; - - // Only handle click on the wrapper specifically, and not an event - // bubbled from the inserter itself. - if ( target !== ref.current ) { - return; - } - - const targetRect = target.getBoundingClientRect(); - const isReverse = clientY < targetRect.top + targetRect.height / 2; - const blockNode = getBlockDOMNode( inserterClientId ); - const container = isReverse ? containerRef.current : blockNode; - const closest = - getClosestTabbable( blockNode, true, container ) || blockNode; - const rect = new window.DOMRect( clientX, clientY, 0, 16 ); - - placeCaretAtVerticalEdge( closest, isReverse, rect, false ); - } - - // Hide the inserter above the selected block and during multi-selection. - const isInserterHidden = hasMultiSelection - ? multiSelectedBlockClientIds.includes( inserterClientId ) - : inserterClientId === selectedBlockClientId; + const isVisible = isInserterShown || isInserterForced || isInserterVisible; return ( <> - { ! isMultiSelecting && ( isInserterShown || isInserterForced ) && ( - -
- - { /* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } -
setIsInserterForced( true ) } - onBlur={ () => setIsInserterForced( false ) } - onClick={ focusClosestTabbable } - // While ideally it would be enough to capture the - // bubbling focus event from the Inserter, due to the - // characteristics of click focusing of `button`s in - // Firefox and Safari, it is not reliable. - // - // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus - tabIndex={ -1 } - className={ classnames( - 'block-editor-block-list__insertion-point-inserter', - { - 'is-inserter-hidden': isInserterHidden, - } - ) } - > - -
-
-
+ { ! isMultiSelecting && isVisible && ( + ) }
+
{ ! isMultiToolbar && (
@@ -117,7 +120,6 @@ export default function BlockToolbar( { hideDragHandle } ) { onDraggableEnd, } ) => (
{ - toggleBlockHighlight( clientId, isFocused ); - }, - [ clientId ] - ); - - useEffect( () => { - // On mount, we make sure to cancel any pending animation frame request - // that hasn't been completed yet. Components like NavigableToolbar may - // mount and unmount quickly. - if ( requestAnimationFrameId ) { - cancelAnimationFrame( requestAnimationFrameId ); - } - return () => { - // Sequences state change to enable editor updates (e.g. cursor - // position) to render correctly. - requestAnimationFrameId = requestAnimationFrame( () => { - updateBlockHighlight( false ); - } ); - }; - }, [] ); - - return updateBlockHighlight; -} diff --git a/packages/block-editor/src/components/block-types-list/index.js b/packages/block-editor/src/components/block-types-list/index.js index 60eb915c925a6..a6b6b734bc1bc 100644 --- a/packages/block-editor/src/components/block-types-list/index.js +++ b/packages/block-editor/src/components/block-types-list/index.js @@ -1,7 +1,13 @@ +/** + * External dependencies + */ +import { Composite, useCompositeState } from 'reakit'; + /** * WordPress dependencies */ import { getBlockMenuDefaultClassName } from '@wordpress/blocks'; +import { useEffect } from '@wordpress/element'; /** * Internal dependencies @@ -14,8 +20,19 @@ function BlockTypesList( { onSelect, onHover = () => {}, children, + label, } ) { + const composite = useCompositeState(); const normalizedItems = includeVariationsInInserterItems( items ); + const orderId = normalizedItems.reduce( + ( acc, item ) => acc + '--' + item.id, + '' + ); + + // This ensures the composite state refreshes when the list order changes. + useEffect( () => { + composite.unstable_sort(); + }, [ composite.unstable_sort, orderId ] ); return ( /* @@ -23,7 +40,12 @@ function BlockTypesList( { * Safari+VoiceOver won't announce the list otherwise. */ /* eslint-disable jsx-a11y/no-redundant-roles */ -
    + { normalizedItems.map( ( item ) => { return ( onHover( null ) } isDisabled={ item.isDisabled } title={ item.title } + composite={ composite } /> ); } ) } { children } -
+ /* eslint-enable jsx-a11y/no-redundant-roles */ ); } diff --git a/packages/block-editor/src/components/inserter-list-item/index.js b/packages/block-editor/src/components/inserter-list-item/index.js index b4c8e40cf5efe..7dfd41cf98cbe 100644 --- a/packages/block-editor/src/components/inserter-list-item/index.js +++ b/packages/block-editor/src/components/inserter-list-item/index.js @@ -2,6 +2,7 @@ * External dependencies */ import classnames from 'classnames'; +import { CompositeItem } from 'reakit'; /** * WordPress dependencies @@ -19,6 +20,7 @@ function InserterListItem( { isDisabled, title, className, + composite, ...props } ) { const itemIconStyle = icon @@ -29,8 +31,11 @@ function InserterListItem( { : {}; return ( -
  • - -
  • + +
    ); } diff --git a/packages/block-editor/src/components/inserter/block-types-tab.js b/packages/block-editor/src/components/inserter/block-types-tab.js index 48ada534c313c..911be7d10bdd4 100644 --- a/packages/block-editor/src/components/inserter/block-types-tab.js +++ b/packages/block-editor/src/components/inserter/block-types-tab.js @@ -129,6 +129,7 @@ export function BlockTypesTab( { items={ filteredItems } onSelect={ onSelectItem } onHover={ onHover } + label={ __( 'Child Blocks' ) } /> ) } @@ -142,6 +143,7 @@ export function BlockTypesTab( { items={ suggestedItems } onSelect={ onSelectItem } onHover={ onHover } + label={ _x( 'Most used', 'blocks' ) } /> ) } @@ -162,6 +164,7 @@ export function BlockTypesTab( { items={ categoryItems } onSelect={ onSelectItem } onHover={ onHover } + label={ category.title } /> ); @@ -176,6 +179,7 @@ export function BlockTypesTab( { items={ uncategorizedItems } onSelect={ onSelectItem } onHover={ onHover } + label={ __( 'Uncategorized' ) } /> ) } @@ -197,6 +201,7 @@ export function BlockTypesTab( { items={ collectionItems } onSelect={ onSelectItem } onHover={ onHover } + label={ collection.title } /> ); diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 9b45803404931..61d565e08a62b 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -135,6 +135,7 @@ class Inserter extends Component { /> ); } + return (
    { showInserterHelpPanel && hoveredItem && ( -
    - -
    + ) }
    ); diff --git a/packages/block-editor/src/components/inserter/preview-panel.js b/packages/block-editor/src/components/inserter/preview-panel.js index 763edbbd50637..b27dedbcf2442 100644 --- a/packages/block-editor/src/components/inserter/preview-panel.js +++ b/packages/block-editor/src/components/inserter/preview-panel.js @@ -18,7 +18,7 @@ import BlockPreview from '../block-preview'; function InserterPreviewPanel( { item } ) { const hoveredItemBlockType = getBlockType( item.name ); return ( -
    +
    { isReusableBlock( item ) || hoveredItemBlockType.example ? (
    diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js index b0de5fe607ce0..6131ffe69cec9 100644 --- a/packages/block-editor/src/components/inserter/quick-inserter.js +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -78,6 +78,7 @@ function QuickInserterList( { items={ shownBlockTypes } onSelect={ onSelectBlockType } onHover={ onHover } + label={ __( 'Blocks' ) } /> ) } diff --git a/packages/block-editor/src/components/inserter/reusable-blocks-tab.js b/packages/block-editor/src/components/inserter/reusable-blocks-tab.js index ebec296e75526..b9aec00ff32a3 100644 --- a/packages/block-editor/src/components/inserter/reusable-blocks-tab.js +++ b/packages/block-editor/src/components/inserter/reusable-blocks-tab.js @@ -67,6 +67,11 @@ function ReusableBlocksList( { items={ filteredItems } onSelect={ onSelectItem } onHover={ onHover } + label={ + filterValue + ? __( 'Search Results' ) + : __( 'Reusable blocks' ) + } /> ); diff --git a/packages/block-editor/src/components/inserter/search-items.js b/packages/block-editor/src/components/inserter/search-items.js index 83b4ba3364dae..5e5a0dabd44ae 100644 --- a/packages/block-editor/src/components/inserter/search-items.js +++ b/packages/block-editor/src/components/inserter/search-items.js @@ -96,7 +96,7 @@ export const searchBlockItems = ( * @param {Object} config Search Config. * @return {Array} Filtered item list. */ -export const searchItems = ( items, searchTerm, config = {} ) => { +export const searchItems = ( items = [], searchTerm = '', config = {} ) => { const normalizedSearchTerms = normalizeSearchTerm( searchTerm ); if ( normalizedSearchTerms.length === 0 ) { return items; diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 4fbe6a8720ed7..2e5271ac821d1 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -19,10 +19,6 @@ $block-inserter-tabs-height: 44px; .block-editor-inserter__popover .block-editor-inserter__menu { margin: -$grid-unit-15; - .block-editor-inserter__search { - top: -$grid-unit-15; - } - .block-editor-inserter__tabs .components-tab-panel__tabs { top: $grid-unit-10 + $grid-unit-20 + $grid-unit-60 - $grid-unit-15; } @@ -31,6 +27,10 @@ $block-inserter-tabs-height: 44px; overflow: visible; height: auto; } + + .block-editor-inserter__preview-container { + display: none; + } } .block-editor-inserter__toggle.components-button { diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js index fa63839248267..d0816a6236890 100644 --- a/packages/block-editor/src/components/navigable-toolbar/index.js +++ b/packages/block-editor/src/components/navigable-toolbar/index.js @@ -35,11 +35,27 @@ function focusFirstTabbableIn( container ) { } function useIsAccessibleToolbar( ref ) { + /* + * By default, we'll assume the starting accessible state of the Toolbar + * is true, as it seems to be the most common case. + * + * Transitioning from an (initial) false to true state causes the + * component to mount twice, which is causing undesired + * side-effects. These side-effects appear to only affect certain + * E2E tests. + * + * This was initial discovered in this pull-request: + * https://github.com/WordPress/gutenberg/pull/23425 + */ + const initialAccessibleToolbarState = true; + // By default, it's gonna render NavigableMenu. If all the tabbable elements // inside the toolbar are ToolbarItem components (or derived components like // ToolbarButton), then we can wrap them with the accessible Toolbar // component. - const [ isAccessibleToolbar, setIsAccessibleToolbar ] = useState( false ); + const [ isAccessibleToolbar, setIsAccessibleToolbar ] = useState( + initialAccessibleToolbarState + ); const determineIsAccessibleToolbar = useCallback( () => { const tabbables = focus.tabbable.find( ref.current ); diff --git a/packages/block-editor/src/components/unit-control/index.js b/packages/block-editor/src/components/unit-control/index.js index 14e2204d55f4d..b6a37665a4f90 100644 --- a/packages/block-editor/src/components/unit-control/index.js +++ b/packages/block-editor/src/components/unit-control/index.js @@ -2,11 +2,7 @@ * WordPress dependencies */ import { __experimentalUnitControl as BaseUnitControl } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import useEditorFeature from '../use-editor-feature'; +import { useSelect } from '@wordpress/data'; export default function UnitControl( { units: unitsProp, ...props } ) { const units = useCustomUnits( unitsProp ); @@ -36,17 +32,21 @@ function filterUnitsWithSettings( settings = [], units = [] ) { * @return {Array} Filtered units based on settings. */ export function useCustomUnits( unitsProp ) { - const settings = useEditorFeature( '__experimentalDisableCustomUnits' ); - const isDisabled = !! settings; - - // Adjust units based on add_theme_support( 'experimental-custom-units' ); + const settings = useSelect( + ( select ) => + select( 'core/block-editor' ).getSettings().enableCustomUnits, + [] + ); + const isDisabled = ! settings; + + // Adjust units based on add_theme_support( 'custom-units' ); let units; /** * Handle extra arguments for add_theme_support * - * Example: add_theme_support( 'experimental-custom-units', 'rem' ); - * Or: add_theme_support( 'experimental-custom-units', 'px, 'rem', 'em' ); + * Example: add_theme_support( 'custom-units', 'rem' ); + * Or: add_theme_support( 'custom-units', 'px, 'rem', 'em' ); * * Note: If there are unit argument (e.g. 'em'), these units are enabled * within the control. diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index adbc88e018f5e..b567bc343243a 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -64,10 +64,10 @@ class URLInput extends Component { if ( showSuggestions && selectedSuggestion !== null && + this.suggestionNodes[ selectedSuggestion ] && ! this.scrollingIntoView ) { this.scrollingIntoView = true; - scrollIntoView( this.suggestionNodes[ selectedSuggestion ], this.autocompleteRef.current, diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js index de3e130df3edf..de2368d333ca3 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/index.js @@ -50,8 +50,8 @@ export function getNearestBlockIndex( elements, position, orientation ) { let candidateDistance; elements.forEach( ( element, index ) => { - // Ensure the element is a block. It should have the `data-block` attribute. - if ( ! element.dataset.block ) { + // Ensure the element is a block. It should have the `wp-block` class. + if ( ! element.classList.contains( 'wp-block' ) ) { return; } @@ -260,7 +260,10 @@ export default function useBlockDropZone( { return; } - const sourceBlockIndex = getBlockIndex( sourceClientIds[ 0 ] ); + const sourceBlockIndex = getBlockIndex( + sourceClientIds[ 0 ], + sourceRootClientId + ); // If the user is dropping to the same position, return early. if ( @@ -322,17 +325,14 @@ export default function useBlockDropZone( { useEffect( () => { if ( position ) { const blockElements = Array.from( element.current.children ); + const targetIndex = getNearestBlockIndex( blockElements, position, orientation ); - if ( targetIndex === undefined ) { - return; - } - - setTargetBlockIndex( targetIndex ); + setTargetBlockIndex( targetIndex === undefined ? 0 : targetIndex ); } }, [ position ] ); diff --git a/packages/block-editor/src/components/use-block-drop-zone/test/index.js b/packages/block-editor/src/components/use-block-drop-zone/test/index.js index ba95d4c7521eb..c0aac7b5827d9 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/test/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/test/index.js @@ -31,10 +31,10 @@ const elementData = [ }, ]; -const createMockClassList = ( isDragging ) => { +const createMockClassList = ( classes ) => { return { - contains() { - return isDragging; + contains( textToMatch ) { + return classes.includes( textToMatch ); }, }; }; @@ -60,7 +60,7 @@ const mapElements = ( orientation ) => ( right: bottom, }; }, - classList: createMockClassList( false ), + classList: createMockClassList( 'wp-block' ), }; }; @@ -83,8 +83,10 @@ describe( 'getNearestBlockIndex', () => { expect( result ).toBeUndefined(); } ); - it( 'returns `undefined` if the elements do not have the `data-block` attribute', () => { - const nonBlockElements = [ { dataset: {} } ]; + it( 'returns `undefined` if the elements do not have the `wp-block` class', () => { + const nonBlockElements = [ + { classList: createMockClassList( 'some-other-class' ) }, + ]; const position = { x: 0, y: 0 }; const orientation = 'horizontal'; @@ -208,14 +210,14 @@ describe( 'getNearestBlockIndex', () => { expect( result ).toBe( 4 ); } ); - it( 'skips the block being dragged', () => { + it( 'skips the block being dragged by checking for the `is-dragging` classname', () => { const position = { x: 0, y: 450 }; const verticalElementsWithDraggedBlock = [ ...verticalElements.slice( 0, 2 ), { ...verticalElements[ 2 ], - classList: createMockClassList( true ), + classList: createMockClassList( 'wp-block is-dragging' ), }, ...verticalElements.slice( 3, 4 ), ]; @@ -341,14 +343,14 @@ describe( 'getNearestBlockIndex', () => { expect( result ).toBe( 4 ); } ); - it( 'skips the block being dragged', () => { + it( 'skips the block being dragged by checking for the `is-dragging` classname', () => { const position = { x: 450, y: 0 }; const horizontalElementsWithDraggedBlock = [ ...horizontalElements.slice( 0, 2 ), { ...horizontalElements[ 2 ], - classList: createMockClassList( true ), + classList: createMockClassList( 'wp-block is-dragging' ), }, ...horizontalElements.slice( 3, 4 ), ]; diff --git a/packages/block-editor/src/hooks/line-height.js b/packages/block-editor/src/hooks/line-height.js index 21698a886a8f4..8881fdbbcb17f 100644 --- a/packages/block-editor/src/hooks/line-height.js +++ b/packages/block-editor/src/hooks/line-height.js @@ -59,7 +59,7 @@ export function useIsLineHeightDisabled( { name: blockName } = {} ) { const isDisabled = useSelect( ( select ) => { const editorSettings = select( 'core/block-editor' ).getSettings(); - return editorSettings.__experimentalDisableCustomLineHeight; + return ! editorSettings.enableCustomLineHeight; } ); return ( diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 35865c8680cde..6acb482ae5243 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -17,6 +17,7 @@ export const PREFERENCES_DEFAULTS = { * @property {boolean} disableCustomColors Whether or not the custom colors are disabled * @property {Array} fontSizes Available font sizes * @property {boolean} disableCustomFontSizes Whether or not the custom font sizes are disabled + * @property {boolean} imageEditing Image Editing settings set to false to disable. * @property {Array} imageSizes Available image sizes * @property {number} maxWidth Max width to constraint resizing * @property {boolean|Array} allowedBlockTypes Allowed block types @@ -131,6 +132,9 @@ export const SETTINGS_DEFAULTS = { { slug: 'full', name: __( 'Full Size' ) }, ], + // Allow plugin to disable Image Editor if need be + imageEditing: true, + // This is current max width of the block inner area // It's used to constraint image resizing and this value could be overridden later by themes maxWidth: 580, diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index bf9e38f151d27..529cd32036434 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1630,14 +1630,21 @@ export function automaticChangeStatus( state, action ) { * @return {string} Updated state. */ export function highlightedBlock( state, action ) { - const { clientId, isHighlighted } = action; + switch ( action.type ) { + case 'TOGGLE_BLOCK_HIGHLIGHT': + const { clientId, isHighlighted } = action; - if ( action.type === 'TOGGLE_BLOCK_HIGHLIGHT' ) { - if ( isHighlighted ) { - return clientId; - } else if ( state === clientId ) { - return null; - } + if ( isHighlighted ) { + return clientId; + } else if ( state === clientId ) { + return null; + } + + return state; + case 'SELECT_BLOCK': + if ( action.clientId !== state ) { + return null; + } } return state; diff --git a/packages/block-library/src/classic/edit.js b/packages/block-library/src/classic/edit.js index b29766aa75b02..08cff4baae64d 100644 --- a/packages/block-library/src/classic/edit.js +++ b/packages/block-library/src/classic/edit.js @@ -131,19 +131,22 @@ export default class ClassicEdit extends Component { bookmark = null; } ); - editor.on( - 'Paste Change input Undo Redo', - debounce( () => { - const value = editor.getContent(); - - if ( value !== editor._lastChange ) { - editor._lastChange = value; - setAttributes( { - content: value, - } ); - } - }, 250 ) - ); + const debouncedOnChange = debounce( () => { + const value = editor.getContent(); + + if ( value !== editor._lastChange ) { + editor._lastChange = value; + setAttributes( { + content: value, + } ); + } + }, 250 ); + editor.on( 'Paste Change input Undo Redo', debouncedOnChange ); + + // We need to cancel the debounce call because when we remove + // the editor (onUnmount) this callback is executed in + // another tick. This results in setting the content to empty. + editor.on( 'remove', debouncedOnChange.cancel ); editor.on( 'keydown', ( event ) => { if ( isKeyboardEvent.primary( event, 'z' ) ) { diff --git a/packages/block-library/src/embed/core-embeds.js b/packages/block-library/src/embed/core-embeds.js index a21e3bf6e4ac7..e03ded6d0dbf6 100644 --- a/packages/block-library/src/embed/core-embeds.js +++ b/packages/block-library/src/embed/core-embeds.js @@ -194,16 +194,6 @@ export const others = [ }, patterns: [ /^https?:\/\/(www\.)?dailymotion\.com\/.+/i ], }, - { - name: 'core-embed/hulu', - settings: { - title: 'Hulu', - icon: embedVideoIcon, - keywords: [ __( 'video' ) ], - description: __( 'Embed Hulu content.' ), - }, - patterns: [ /^https?:\/\/(www\.)?hulu\.com\/.+/i ], - }, { name: 'core-embed/imgur', settings: { diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 87fd783ae5e63..18c06e5c9ebc7 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -87,17 +87,22 @@ export default function Image( { }, [ id, isSelected ] ); - const { maxWidth, isRTL, imageSizes, mediaUpload } = useSelect( - ( select ) => { - const { getSettings } = select( 'core/block-editor' ); - return pick( getSettings(), [ - 'imageSizes', - 'isRTL', - 'maxWidth', - 'mediaUpload', - ] ); - } - ); + const { + imageEditing, + imageSizes, + isRTL, + maxWidth, + mediaUpload, + } = useSelect( ( select ) => { + const { getSettings } = select( 'core/block-editor' ); + return pick( getSettings(), [ + 'imageEditing', + 'imageSizes', + 'isRTL', + 'maxWidth', + 'mediaUpload', + ] ); + } ); const { toggleSelection } = useDispatch( 'core/block-editor' ); const { createErrorNotice, createSuccessNotice } = useDispatch( 'core/notices' @@ -226,7 +231,7 @@ export default function Image( { } }, [ isSelected ] ); - const canEditImage = id && naturalWidth && naturalHeight; + const canEditImage = id && naturalWidth && naturalHeight && imageEditing; const controls = ( <> diff --git a/packages/components/src/box-control/styles/box-control-visualizer-styles.js b/packages/components/src/box-control/styles/box-control-visualizer-styles.js index 0b0e2b614fbfe..f6bea27b758e9 100644 --- a/packages/components/src/box-control/styles/box-control-visualizer-styles.js +++ b/packages/components/src/box-control/styles/box-control-visualizer-styles.js @@ -31,7 +31,8 @@ export const Container = styled.div` export const Side = styled.div` box-sizing: border-box; - background: ${ color( 'ui.brand' ) }; + background: ${ color( 'blue.wordpress.700' ) }; + background: ${ color( 'ui.theme' ) }; filter: brightness( 1 ); opacity: 0; position: absolute; diff --git a/packages/components/src/range-control/index.js b/packages/components/src/range-control/index.js index 34c8c16a43f97..536b0d84ba0a8 100644 --- a/packages/components/src/range-control/index.js +++ b/packages/components/src/range-control/index.js @@ -42,7 +42,7 @@ function RangeControl( beforeIcon, className, currentInput, - color: colorProp = color( 'ui.brand' ), + color: colorProp = color( 'ui.theme' ), disabled = false, help, initialPosition, diff --git a/packages/components/src/range-control/stories/index.js b/packages/components/src/range-control/stories/index.js index daba7c7555398..d3dca6a1ca1a3 100644 --- a/packages/components/src/range-control/stories/index.js +++ b/packages/components/src/range-control/stories/index.js @@ -32,7 +32,7 @@ const DefaultExample = () => { afterIcon: text( 'afterIcon', '' ), allowReset: boolean( 'allowReset', false ), beforeIcon: text( 'beforeIcon', '' ), - color: text( 'color', color( 'ui.brand' ) ), + color: text( 'color', color( 'ui.theme' ) ), disabled: boolean( 'disabled', false ), help: text( 'help', '' ), label: text( 'label', 'Range Label' ), diff --git a/packages/components/src/range-control/styles/range-control-styles.js b/packages/components/src/range-control/styles/range-control-styles.js index f820c3e2dbf05..0fa3fbbeb233d 100644 --- a/packages/components/src/range-control/styles/range-control-styles.js +++ b/packages/components/src/range-control/styles/range-control-styles.js @@ -33,6 +33,7 @@ const wrapperMargin = ( { marks } ) => export const Wrapper = styled.span` box-sizing: border-box; + color: ${ color( 'blue.medium.focus' ) }; display: block; flex: 1; padding-top: 15px; diff --git a/packages/components/src/toolbar/toolbar-container.js b/packages/components/src/toolbar/toolbar-container.js index 2ea7cfc7fee8c..5141e6fe69f95 100644 --- a/packages/components/src/toolbar/toolbar-container.js +++ b/packages/components/src/toolbar/toolbar-container.js @@ -12,12 +12,17 @@ import { forwardRef } from '@wordpress/element'; * Internal dependencies */ import ToolbarContext from '../toolbar-context'; +import { getRTL } from '../utils/rtl'; function ToolbarContainer( { accessibilityLabel, ...props }, ref ) { // https://reakit.io/docs/basic-concepts/#state-hooks // Passing baseId for server side rendering (which includes snapshots) // If an id prop is passed to Toolbar, toolbar items will use it as a base for their ids - const toolbarState = useToolbarState( { loop: true, baseId: props.id } ); + const toolbarState = useToolbarState( { + loop: true, + baseId: props.id, + rtl: getRTL(), + } ); return ( // This will provide state for `ToolbarButton`'s diff --git a/packages/components/src/tooltip/style.scss b/packages/components/src/tooltip/style.scss index e6e1093cff198..a7f4b09a1ffa7 100644 --- a/packages/components/src/tooltip/style.scss +++ b/packages/components/src/tooltip/style.scss @@ -17,9 +17,6 @@ font-size: 12px; box-shadow: none; - // This prevents quickly hovering the tooltip from blocking hovering something else. - pointer-events: none; - > div { padding: $grid-unit-05 $grid-unit-10; } diff --git a/packages/components/src/utils/colors-values.js b/packages/components/src/utils/colors-values.js index 1965ccfef028e..863033bb545f3 100644 --- a/packages/components/src/utils/colors-values.js +++ b/packages/components/src/utils/colors-values.js @@ -133,13 +133,18 @@ export const ALERT = { green: '#4ab866', }; +export const ADMIN = { + theme: `var( --wp-admin-theme-color, ${ BLUE.wordpress[ 700 ] })`, + themeDark10: `var( --wp-admin-theme-color-darker-10, ${ BLUE.medium.focus })`, +}; + // Namespaced values for raw colors hex codes export const UI = { + theme: ADMIN.theme, background: BASE.white, backgroundDisabled: LIGHT_GRAY[ 200 ], - brand: BLUE.wordpress[ 700 ], border: G2.darkGray.primary, - borderFocus: BLUE.medium.focus, + borderFocus: ADMIN.themeDark10, borderDisabled: DARK_GRAY[ 700 ], borderLight: LIGHT_GRAY[ 600 ], label: DARK_GRAY[ 500 ], @@ -158,6 +163,7 @@ export const COLORS = { lightGrayLight: LIGHT_OPACITY_LIGHT, blue: merge( {}, BLUE, G2.blue ), alert: ALERT, + admin: ADMIN, ui: UI, }; diff --git a/packages/compose/src/hooks/use-copy-on-click/index.js b/packages/compose/src/hooks/use-copy-on-click/index.js index 950006787b3a6..7071ea20bec88 100644 --- a/packages/compose/src/hooks/use-copy-on-click/index.js +++ b/packages/compose/src/hooks/use-copy-on-click/index.js @@ -32,12 +32,17 @@ export default function useCopyOnClick( ref, text, timeout = 4000 ) { container: ref.current, } ); - clipboard.current.on( 'success', ( { clearSelection } ) => { + clipboard.current.on( 'success', ( { clearSelection, trigger } ) => { // Clearing selection will move focus back to the triggering button, // ensuring that it is not reset to the body, and further that it is // kept within the rendered node. clearSelection(); + // Handle ClipboardJS focus bug, see https://github.com/zenorocha/clipboard.js/issues/680 + if ( trigger ) { + trigger.focus(); + } + if ( timeout ) { setHasCopied( true ); clearTimeout( timeoutId ); diff --git a/packages/e2e-tests/specs/editor/various/adding-blocks.test.js b/packages/e2e-tests/specs/editor/various/adding-blocks.test.js index aab95bc526917..535631af50e37 100644 --- a/packages/e2e-tests/specs/editor/various/adding-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/adding-blocks.test.js @@ -158,7 +158,6 @@ describe( 'adding blocks', () => { // Tab to the block list await page.keyboard.press( 'Tab' ); - await page.keyboard.press( 'Tab' ); // Expect the block list to be the active element. activeElementClassList = await page.evaluate( diff --git a/packages/edit-post/src/components/sidebar/post-link/index.js b/packages/edit-post/src/components/sidebar/post-link/index.js index 5bf5b6bba6997..3a73af2e6da7d 100644 --- a/packages/edit-post/src/components/sidebar/post-link/index.js +++ b/packages/edit-post/src/components/sidebar/post-link/index.js @@ -23,26 +23,30 @@ function PostLink( { onTogglePanel, isEditable, postLink, - permalinkParts, + permalinkPrefix, + permalinkSuffix, editPermalink, forceEmptyField, setState, postSlug, postTypeLabel, } ) { - const { prefix, suffix } = permalinkParts; let prefixElement, postNameElement, suffixElement; if ( isEditable ) { - prefixElement = prefix && ( - { prefix } + prefixElement = permalinkPrefix && ( + + { permalinkPrefix } + ); postNameElement = postSlug && ( { postSlug } ); - suffixElement = suffix && ( - { suffix } + suffixElement = permalinkSuffix && ( + + { permalinkSuffix } + ); } @@ -137,21 +141,24 @@ export default compose( [ const postTypeName = getEditedPostAttribute( 'type' ); const postType = getPostType( postTypeName ); + const permalinkParts = getPermalinkParts(); return { postLink: link, isEditable: isPermalinkEditable(), isPublished: isCurrentPostPublished(), isOpened: isEditorPanelOpened( PANEL_NAME ), - permalinkParts: getPermalinkParts(), isEnabled: isEditorPanelEnabled( PANEL_NAME ), isViewable: get( postType, [ 'viewable' ], false ), postSlug: safeDecodeURIComponent( getEditedPostSlug() ), postTypeLabel: get( postType, [ 'labels', 'view_item' ] ), + hasPermalinkParts: !! permalinkParts, + permalinkPrefix: permalinkParts?.prefix, + permalinkSuffix: permalinkParts?.suffix, }; } ), - ifCondition( ( { isEnabled, postLink, isViewable, permalinkParts } ) => { - return isEnabled && postLink && isViewable && permalinkParts; + ifCondition( ( { isEnabled, postLink, isViewable, hasPermalinkParts } ) => { + return isEnabled && postLink && isViewable && hasPermalinkParts; } ), withDispatch( ( dispatch ) => { const { toggleEditorPanelOpened } = dispatch( 'core/edit-post' ); diff --git a/packages/edit-widgets/src/components/header/index.js b/packages/edit-widgets/src/components/header/index.js index e2185b9777180..24feb8b2a5757 100644 --- a/packages/edit-widgets/src/components/header/index.js +++ b/packages/edit-widgets/src/components/header/index.js @@ -10,6 +10,7 @@ import { } from '@wordpress/block-editor'; import { PinnedItems } from '@wordpress/interface'; import { useViewportMatch } from '@wordpress/compose'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -22,6 +23,13 @@ const inserterToggleProps = { isPrimary: true }; function Header( { isCustomizer } ) { const isLargeViewport = useViewportMatch( 'medium' ); + const rootClientId = useSelect( ( select ) => { + const { getBlockRootClientId, getBlockSelectionEnd } = select( + 'core/block-editor' + ); + return getBlockRootClientId( getBlockSelectionEnd() ); + }, [] ); + return ( <>
    @@ -30,6 +38,7 @@ function Header( { isCustomizer } ) { position="bottom right" showInserterHelpPanel toggleProps={ inserterToggleProps } + rootClientId={ rootClientId } /> diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index edbeaaace3395..99b1495152ecc 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -114,8 +114,6 @@ class EditorProvider extends Component { '__experimentalBlockDirectory', '__experimentalBlockPatterns', '__experimentalBlockPatternCategories', - '__experimentalDisableCustomUnits', - '__experimentalDisableCustomLineHeight', '__experimentalEnableCustomSpacing', '__experimentalEnableLegacyWidgetBlock', '__experimentalEnableLinkColor', @@ -135,11 +133,14 @@ class EditorProvider extends Component { 'disableCustomColors', 'disableCustomFontSizes', 'disableCustomGradients', + 'enableCustomUnits', + 'enableCustomLineHeight', 'focusMode', 'fontSizes', 'gradients', 'hasFixedToolbar', 'hasPermissionsToManageWidgets', + 'imageEditing', 'imageSizes', 'imageDimensions', 'isRTL', diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 7a392dcc956ef..4b2529a3b4780 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -1106,7 +1106,7 @@ function RichText( { const editableProps = { // Overridable props. role: 'textbox', - 'aria-multiline': '', + 'aria-multiline': true, 'aria-label': placeholder, ...ariaProps, ref,