diff --git a/docs/designers-developers/developers/data/data-core-edit-post.md b/docs/designers-developers/developers/data/data-core-edit-post.md index 05714b83bcda7b..85e5d162c0bf97 100644 --- a/docs/designers-developers/developers/data/data-core-edit-post.md +++ b/docs/designers-developers/developers/data/data-core-edit-post.md @@ -177,6 +177,18 @@ _Returns_ - `boolean`: Is active. +# **isInserterOpened** + +Returns true if the inserter is opened. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether the inserter is opened. + # **isMetaBoxLocationActive** Returns true if there is an active meta box in the given location, or false @@ -377,6 +389,18 @@ _Returns_ - `Object`: Action object. +# **setIsInserterOpened** + +Returns an action object used to open/close the inserter. + +_Parameters_ + +- _value_ `boolean`: A boolean representing whether the inserter should be opened or closed. + +_Returns_ + +- `Object`: Action object. + # **showBlockTypes** Returns an action object used in signalling that block types by the given diff --git a/packages/block-editor/src/components/block-list/block-popover.js b/packages/block-editor/src/components/block-list/block-popover.js index 7ae05705d1407b..3a367616d734d3 100644 --- a/packages/block-editor/src/components/block-list/block-popover.js +++ b/packages/block-editor/src/components/block-list/block-popover.js @@ -184,6 +184,7 @@ function BlockPopover( { ) } @@ -206,9 +207,10 @@ function BlockPopover( { { showEmptyBlockSideInserter && (
) } 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 6dbfeed8964e64..1a64d9cf674596 100644 --- a/packages/block-editor/src/components/block-list/insertion-point.js +++ b/packages/block-editor/src/components/block-list/insertion-point.js @@ -187,6 +187,7 @@ export default function InsertionPoint( { diff --git a/packages/block-editor/src/components/block-patterns-list/index.js b/packages/block-editor/src/components/block-patterns-list/index.js new file mode 100644 index 00000000000000..8905eece51430b --- /dev/null +++ b/packages/block-editor/src/components/block-patterns-list/index.js @@ -0,0 +1,59 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +import { parse } from '@wordpress/blocks'; +import { ENTER, SPACE } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import BlockPreview from '../block-preview'; + +function BlockPattern( { pattern, onClick } ) { + const { content, viewportWidth } = pattern; + const blocks = useMemo( () => parse( content ), [ content ] ); + + return ( +
onClick( pattern, blocks ) } + onKeyDown={ ( event ) => { + if ( ENTER === event.keyCode || SPACE === event.keyCode ) { + onClick( pattern, blocks ); + } + } } + tabIndex={ 0 } + aria-label={ pattern.title } + > + +
+ { pattern.title } +
+
+ ); +} + +function BlockPatternPlaceholder() { + return ( +
+ ); +} + +function BlockPatternList( { blockPatterns, shownPatterns, onClickPattern } ) { + return blockPatterns.map( ( pattern ) => { + const isShown = shownPatterns.includes( pattern ); + return isShown ? ( + + ) : ( + + ); + } ); +} + +export default BlockPatternList; diff --git a/packages/block-editor/src/components/block-patterns-list/style.scss b/packages/block-editor/src/components/block-patterns-list/style.scss new file mode 100644 index 00000000000000..f66019261d0b40 --- /dev/null +++ b/packages/block-editor/src/components/block-patterns-list/style.scss @@ -0,0 +1,30 @@ +.block-editor-block-patterns-list__item { + + border-radius: $radius-block-ui; + cursor: pointer; + margin-top: $grid-unit-20; + transition: all 0.05s ease-in-out; + position: relative; + border: $border-width solid transparent; + + &:hover { + border: $border-width solid var(--wp-admin-theme-color); + } + + &:focus { + box-shadow: inset 0 0 0 1px $white, 0 0 0 $border-width-focus var(--wp-admin-theme-color); + + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; + } + + &.is-placeholder { + min-height: 100px; + } +} + +.block-editor-block-patterns-list__item-title { + padding: $grid-unit-05; + font-size: 12px; + text-align: center; +} diff --git a/packages/block-editor/src/components/button-block-appender/index.js b/packages/block-editor/src/components/button-block-appender/index.js index 658e2ba4a57e21..9b9167f9a04e7f 100644 --- a/packages/block-editor/src/components/button-block-appender/index.js +++ b/packages/block-editor/src/components/button-block-appender/index.js @@ -31,6 +31,7 @@ function ButtonBlockAppender( position="bottom center" rootClientId={ rootClientId } __experimentalSelectBlockOnInsert={ selectBlockOnInsert } + __experimentalIsQuick renderToggle={ ( { onToggle, disabled, diff --git a/packages/block-editor/src/components/default-block-appender/index.js b/packages/block-editor/src/components/default-block-appender/index.js index 865f9a76f226e1..54f1cf78adf2fd 100644 --- a/packages/block-editor/src/components/default-block-appender/index.js +++ b/packages/block-editor/src/components/default-block-appender/index.js @@ -63,8 +63,9 @@ export function DefaultBlockAppender( { />
); diff --git a/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap index b2377049481051..c5d145e2670ac6 100644 --- a/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap @@ -27,8 +27,9 @@ exports[`DefaultBlockAppender should append a default block when input focused 1 value="Start writing or type / to choose a block" /> `; @@ -48,8 +49,9 @@ exports[`DefaultBlockAppender should match snapshot 1`] = ` value="Start writing or type / to choose a block" /> `; @@ -69,8 +71,9 @@ exports[`DefaultBlockAppender should optionally show without prompt 1`] = ` value="" /> `; diff --git a/packages/block-editor/src/components/inserter/block-patterns.js b/packages/block-editor/src/components/inserter/block-patterns-tab.js similarity index 67% rename from packages/block-editor/src/components/inserter/block-patterns.js rename to packages/block-editor/src/components/inserter/block-patterns-tab.js index 2da6a210fd0346..ffdf8a33384319 100644 --- a/packages/block-editor/src/components/inserter/block-patterns.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab.js @@ -7,65 +7,17 @@ import { fromPairs } from 'lodash'; * WordPress dependencies */ import { useMemo, useCallback } from '@wordpress/element'; -import { parse } from '@wordpress/blocks'; -import { ENTER, SPACE } from '@wordpress/keycodes'; import { __, _x } from '@wordpress/i18n'; import { useAsyncList } from '@wordpress/compose'; /** * Internal dependencies */ -import BlockPreview from '../block-preview'; import InserterPanel from './panel'; import { searchItems } from './search-items'; import InserterNoResults from './no-results'; import usePatternsState from './hooks/use-patterns-state'; - -function BlockPattern( { pattern, onClick } ) { - const { content, viewportWidth } = pattern; - const blocks = useMemo( () => parse( content ), [ content ] ); - - return ( -
onClick( pattern, blocks ) } - onKeyDown={ ( event ) => { - if ( ENTER === event.keyCode || SPACE === event.keyCode ) { - onClick( pattern, blocks ); - } - } } - tabIndex={ 0 } - aria-label={ pattern.title } - > - -
- { pattern.title } -
-
- ); -} - -function BlockPatternPlaceholder() { - return ( -
- ); -} - -function BlockPatternList( { patterns, shownPatterns, onClickPattern } ) { - return patterns.map( ( pattern ) => { - const isShown = shownPatterns.includes( pattern ); - return isShown ? ( - - ) : ( - - ); - } ); -} +import BlockPatternList from '../block-patterns-list'; function BlockPatternsSearchResults( { filterValue, onInsert } ) { const [ patterns, , onClick ] = usePatternsState( onInsert ); @@ -81,7 +33,7 @@ function BlockPatternsSearchResults( { filterValue, onInsert } ) { @@ -147,7 +99,7 @@ function BlockPatternsPerCategories( { onInsert } ) { > @@ -159,7 +111,7 @@ function BlockPatternsPerCategories( { onInsert } ) { @@ -168,7 +120,7 @@ function BlockPatternsPerCategories( { onInsert } ) { ); } -function BlockPatterns( { onInsert, filterValue } ) { +function BlockPatternsTabs( { onInsert, filterValue } ) { return filterValue ? ( item.name.split( '/' )[ 0 ]; const MAX_SUGGESTED_ITEMS = 6; -export function InserterBlockList( { +export function BlockTypesTab( { rootClientId, onInsert, onHover, @@ -244,4 +243,4 @@ export function InserterBlockList( { ); } -export default compose( withSpokenMessages )( InserterBlockList ); +export default withSpokenMessages( BlockTypesTab ); diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 929746ad967ba1..fd9e0d71646b7e 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -2,6 +2,8 @@ * External dependencies */ import { size } from 'lodash'; +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -18,6 +20,7 @@ import { plus } from '@wordpress/icons'; * Internal dependencies */ import InserterMenu from './menu'; +import QuickInserter from './quick-inserter'; const defaultRenderToggle = ( { onToggle, @@ -116,8 +119,22 @@ class Inserter extends Component { isAppender, showInserterHelpPanel, __experimentalSelectBlockOnInsert: selectBlockOnInsert, + + // This prop is experimental to give some time for the quick inserter to mature + // Feel free to make them stable after a few releases. + __experimentalIsQuick: isQuick, } = this.props; + if ( isQuick ) { + return ( + + ); + } return ( event.stopPropagation(); @@ -82,7 +83,7 @@ function InserterMenu( { const blocksTab = ( <>
- + ); // Disable reason (no-autofocus): The inserter menu is a modal display, not one which @@ -118,32 +119,24 @@ function InserterMenu( { onKeyDown={ onKeyDown } >
- - { showPatterns && ( - - { ( tab ) => { - if ( tab.name === 'blocks' ) { - return blocksTab; - } - return patternsTab; - } } - - ) } - { ! showPatterns && blocksTab } + { /* the following div is necessary to fix the sticky position of the search form */ } +
+ + { showPatterns && ( + + { ( tab ) => { + if ( tab.name === 'blocks' ) { + return blocksTab; + } + return patternsTab; + } } + + ) } + { ! showPatterns && blocksTab } +
{ showInserterHelpPanel && hoveredItem && (
diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js new file mode 100644 index 00000000000000..bd0489e1812b29 --- /dev/null +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -0,0 +1,218 @@ +/** + * WordPress dependencies + */ +import { useState, useMemo, useEffect } from '@wordpress/element'; +import { __, _n, sprintf } from '@wordpress/i18n'; +import { + VisuallyHidden, + Button, + withSpokenMessages, +} from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import BlockTypesList from '../block-types-list'; +import BlockPatternsList from '../block-patterns-list'; +import InserterSearchForm from './search-form'; +import InserterPanel from './panel'; +import InserterNoResults from './no-results'; +import useInsertionPoint from './hooks/use-insertion-point'; +import usePatternsState from './hooks/use-patterns-state'; +import useBlockTypesState from './hooks/use-block-types-state'; +import { searchBlockItems, searchItems } from './search-items'; + +const SEARCH_THRESHOLD = 6; +const SHOWN_BLOCK_TYPES = 6; +const SHOWN_BLOCK_PATTERNS = 2; + +const preventArrowKeysPropagation = ( event ) => { + if ( + [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].includes( event.keyCode ) + ) { + // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. + event.stopPropagation(); + } +}; +const stopKeyPropagation = ( event ) => event.stopPropagation(); + +function QuickInserterList( { + blockTypes, + blockPatterns, + onSelectBlockType, + onSelectBlockPattern, + onHover, +} ) { + const shownBlockTypes = useMemo( + () => blockTypes.slice( 0, SHOWN_BLOCK_TYPES ), + [ blockTypes ] + ); + const shownBlockPatterns = useMemo( + () => blockPatterns.slice( 0, SHOWN_BLOCK_PATTERNS ), + [ blockTypes ] + ); + return ( +
+ { ! shownBlockTypes.length && ! shownBlockPatterns.length && ( + + ) } + + { !! shownBlockTypes.length && ( + { __( 'Blocks' ) } + } + > + + + ) } + + { !! shownBlockTypes.length && !! shownBlockPatterns.length && ( +
+ ) } + + { !! shownBlockPatterns.length && ( + { __( 'Blocks' ) } + } + > +
+ +
+
+ ) } +
+ ); +} + +function QuickInserter( { + rootClientId, + clientId, + isAppender, + selectBlockOnInsert, + debouncedSpeak, +} ) { + const [ filterValue, setFilterValue ] = useState( '' ); + const [ + destinationRootClientId, + onInsertBlocks, + onToggleInsertionPoint, + ] = useInsertionPoint( { + rootClientId, + clientId, + isAppender, + selectBlockOnInsert, + } ); + const [ + blockTypes, + blockTypeCategories, + blockTypeCollections, + onSelectBlockType, + ] = useBlockTypesState( destinationRootClientId, onInsertBlocks ); + const [ patterns, , onSelectBlockPattern ] = usePatternsState( + onInsertBlocks + ); + const showPatterns = + ! destinationRootClientId && patterns.length && !! filterValue; + const showSearch = + ( showPatterns && patterns.length > SEARCH_THRESHOLD ) || + blockTypes.length > SEARCH_THRESHOLD; + + const filteredBlockTypes = useMemo( () => { + return searchBlockItems( + blockTypes, + blockTypeCategories, + blockTypeCollections, + filterValue + ); + }, [ filterValue, blockTypes, blockTypeCategories, blockTypeCollections ] ); + + const filteredBlockPatterns = useMemo( + () => searchItems( patterns, filterValue ), + [ filterValue, patterns ] + ); + + const setInsererIsOpened = useSelect( + ( select ) => + select( 'core/block-editor' ).getSettings() + .__experimentalSetIsInserterOpened, + [] + ); + + useEffect( () => { + if ( setInsererIsOpened ) { + setInsererIsOpened( false ); + } + }, [ setInsererIsOpened ] ); + + // Announce search results on change + useEffect( () => { + if ( ! filterValue ) { + return; + } + const count = filteredBlockTypes.length + filteredBlockPatterns.length; + const resultsFoundMessage = sprintf( + /* translators: %d: number of results. */ + _n( '%d result found.', '%d results found.', count ), + count + ); + debouncedSpeak( resultsFoundMessage ); + }, [ filterValue, debouncedSpeak ] ); + + // Disable reason (no-autofocus): The inserter menu is a modal display, not one which + // is always visible, and one which already incurs this behavior of autoFocus via + // Popover's focusOnMount. + // Disable reason (no-static-element-interactions): Navigational key-presses within + // the menu are prevented from triggering WritingFlow and ObserveTyping interactions. + /* eslint-disable jsx-a11y/no-autofocus, jsx-a11y/no-static-element-interactions */ + return ( +
+ { showSearch && ( + { + setFilterValue( value ); + } } + /> + ) } + + + + { setInsererIsOpened && ( + + ) } +
+ ); + /* eslint-enable jsx-a11y/no-autofocus, jsx-a11y/no-static-element-interactions */ +} + +export default withSpokenMessages( QuickInserter ); diff --git a/packages/block-editor/src/components/inserter/search-form.js b/packages/block-editor/src/components/inserter/search-form.js index 6c1f140a98ac7f..bae5092d95ecac 100644 --- a/packages/block-editor/src/components/inserter/search-form.js +++ b/packages/block-editor/src/components/inserter/search-form.js @@ -3,11 +3,13 @@ */ import { useInstanceId } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; -import { VisuallyHidden } from '@wordpress/components'; -import { Icon, search } from '@wordpress/icons'; +import { VisuallyHidden, Button } from '@wordpress/components'; +import { Icon, search, closeSmall } from '@wordpress/icons'; +import { useRef } from '@wordpress/element'; -function InserterSearchForm( { onChange } ) { +function InserterSearchForm( { onChange, value } ) { const instanceId = useInstanceId( InserterSearchForm ); + const searchInput = useRef(); // Disable reason (no-autofocus): The inserter menu is a modal display, not one which // is always visible, and one which already incurs this behavior of autoFocus via @@ -22,6 +24,7 @@ function InserterSearchForm( { onChange } ) { { __( 'Search for a block' ) } onChange( event.target.value ) } autoComplete="off" + value={ value || '' } /> - +
+ { !! value && ( +
); /* eslint-enable jsx-a11y/no-autofocus */ diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 65a9816e262273..ed456aaf782bf9 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -101,13 +101,27 @@ $block-inserter-tabs-height: 44px; &::placeholder { color: $dark-gray-400; } + + &::-webkit-search-decoration, + &::-webkit-search-cancel-button, + &::-webkit-search-results-button, + &::-webkit-search-results-decoration { + -webkit-appearance: none; + } } } .block-editor-inserter__search-icon { position: absolute; - top: $grid-unit-20 + ($grid-unit-60 - $icon-size) / 2; - right: $grid-unit-20 + ($grid-unit-60 - $icon-size) / 2; + top: 0; + right: $grid-unit-10 + ($grid-unit-60 - $icon-size) / 2; + bottom: 0; + display: flex; + align-items: center; + + > svg { + margin: $grid-unit-10; + } } .block-editor-inserter__tabs { @@ -248,32 +262,49 @@ $block-inserter-tabs-height: 44px; flex-shrink: 0; } -.block-editor-inserter__patterns-item { - border-radius: $radius-block-ui; - cursor: pointer; - margin-top: $grid-unit-20; - transition: all 0.05s ease-in-out; - position: relative; - border: $border-width solid transparent; +.block-editor-inserter__quick-inserter { + width: $block-inserter-width; +} - &:hover { - border: $border-width solid var(--wp-admin-theme-color); +.block-editor-inserter__quick-inserter-results { + padding-bottom: $grid-unit-20; + + .block-editor-inserter__panel-header { + height: 0; + padding: 0; + float: left; } +} - &:focus { - box-shadow: inset 0 0 0 1px $white, 0 0 0 $border-width-focus var(--wp-admin-theme-color); +.block-editor-inserter__quick-inserter-patterns { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: $grid-unit-10; +} - // Windows High Contrast mode will show this outline, but not the box-shadow. - outline: 2px solid transparent; - } +.block-editor-inserter__quick-inserter-separator { + border-top: $border-width solid $light-gray-500; +} - &.is-placeholder { - min-height: 100px; +.block-editor-inserter__popover.is-quick > .components-popover__content > div { + @include break-medium { + padding: 0; } } -.block-editor-inserter__patterns-item-title { - padding: $grid-unit-05; - font-size: 12px; - text-align: center; +.block-editor-inserter__quick-inserter-expand.components-button { + display: block; + background: $dark-gray-primary; + color: $white; + width: 100%; + height: ($button-size + $grid-unit-10); + border-radius: 0; + + &:hover { + color: $white; + } + + &:focus:not(:disabled) { + box-shadow: inset 0 0 0 $border-width-focus $dark-gray-primary, inset 0 0 0 2px $white; + } } diff --git a/packages/block-editor/src/components/inserter/tabs.js b/packages/block-editor/src/components/inserter/tabs.js new file mode 100644 index 00000000000000..11393b2a703728 --- /dev/null +++ b/packages/block-editor/src/components/inserter/tabs.js @@ -0,0 +1,29 @@ +/** + * WordPress dependencies + */ +import { TabPanel } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +function InserterTabs( { children } ) { + return ( + + { children } + + ); +} + +export default InserterTabs; diff --git a/packages/block-editor/src/components/inserter/test/block-list.js b/packages/block-editor/src/components/inserter/test/block-types-tab.js similarity index 98% rename from packages/block-editor/src/components/inserter/test/block-list.js rename to packages/block-editor/src/components/inserter/test/block-types-tab.js index 9d4f8134f6c6e1..80dc1b1ec6ad6c 100644 --- a/packages/block-editor/src/components/inserter/test/block-list.js +++ b/packages/block-editor/src/components/inserter/test/block-types-tab.js @@ -11,7 +11,7 @@ import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import { InserterBlockList as BaseInserterBlockList } from '../block-list'; +import { BlockTypesTab } from '../block-types-tab'; import items, { categories, collections } from './fixtures'; import useBlockTypesState from '../hooks/use-block-types-state'; @@ -36,9 +36,7 @@ jest.mock( '@wordpress/data/src/components/use-dispatch', () => { const debouncedSpeak = jest.fn(); function InserterBlockList( props ) { - return ( - - ); + return ; } const initializeAllClosedMenuState = ( propOverrides ) => { diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index f7648056f036c2..1ddb4f39f2fdd0 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -18,6 +18,7 @@ @import "./components/block-mover/style.scss"; @import "./components/block-navigation/style.scss"; @import "./components/block-parent-selector/style.scss"; +@import "./components/block-patterns-list/style.scss"; @import "./components/block-preview/style.scss"; @import "./components/block-settings-menu/style.scss"; @import "./components/block-styles/style.scss"; diff --git a/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js b/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js index b9bc3b058a0c1c..903761a848c6d1 100644 --- a/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js +++ b/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js @@ -12,7 +12,7 @@ export async function getAllBlockInserterItemTitles() { const inserterItemTitles = await page.evaluate( () => { return Array.from( document.querySelectorAll( - '.block-editor-inserter__block-list .block-editor-block-types-list__item-title' + '.block-editor-block-types-list__item-title' ) ).map( ( inserterItem ) => { return inserterItem.innerText; diff --git a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js index 454476b108ff38..5ab8f91bfa8766 100644 --- a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js +++ b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js @@ -110,14 +110,14 @@ describe( 'cpt locking', () => { '.wp-block-column .block-editor-button-block-appender' ); await page.type( '.block-editor-inserter__search-input', 'image' ); - await page.keyboard.press( 'Tab' ); + await pressKeyTimes( 'Tab', 2 ); await page.keyboard.press( 'Enter' ); await page.click( '.edit-post-header-toolbar__inserter-toggle' ); await page.type( '.block-editor-inserter__search-input', 'gallery' ); - await page.keyboard.press( 'Tab' ); + await pressKeyTimes( 'Tab', 2 ); await page.keyboard.press( 'Enter' ); expect( await page.$( '.wp-block-gallery' ) ).not.toBeNull(); } ); diff --git a/packages/e2e-tests/specs/editor/plugins/inner-blocks-render-appender.test.js b/packages/e2e-tests/specs/editor/plugins/inner-blocks-render-appender.test.js index 137ac319a6800a..182f224d0976f4 100644 --- a/packages/e2e-tests/specs/editor/plugins/inner-blocks-render-appender.test.js +++ b/packages/e2e-tests/specs/editor/plugins/inner-blocks-render-appender.test.js @@ -11,7 +11,8 @@ import { closeGlobalBlockInserter, } from '@wordpress/e2e-test-utils'; -const INSERTER_RESULTS_SELECTOR = '.block-editor-inserter__block-list'; +const INSERTER_RESULTS_SELECTOR = + '.block-editor-inserter__quick-inserter-results'; const QUOTE_INSERT_BUTTON_SELECTOR = '//button[.="Quote"]'; const APPENDER_SELECTOR = '.my-custom-awesome-appender'; const DYNAMIC_APPENDER_SELECTOR = 'my-dynamic-blocks-appender'; diff --git a/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js index eceecaaf95ae82..fe4ad65d21c786 100644 --- a/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js @@ -30,7 +30,7 @@ describe( 'Navigating the block hierarchy', () => { await page.keyboard.press( 'Tab' ); // Tab to inserter. await page.keyboard.press( 'Enter' ); // Activate inserter. await page.keyboard.type( 'Paragraph' ); - await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await pressKeyTimes( 'Tab', 2 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'First column' ); @@ -66,7 +66,7 @@ describe( 'Navigating the block hierarchy', () => { await page.keyboard.press( 'Tab' ); // Tab to inserter. await page.keyboard.press( 'Enter' ); // Activate inserter. await page.keyboard.type( 'Paragraph' ); - await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await pressKeyTimes( 'Tab', 2 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'Third column' ); @@ -82,7 +82,7 @@ describe( 'Navigating the block hierarchy', () => { await page.keyboard.press( 'Tab' ); // Tab to inserter. await page.keyboard.press( 'Enter' ); // Activate inserter. await page.keyboard.type( 'Paragraph' ); - await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await pressKeyTimes( 'Tab', 2 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'First column' ); @@ -109,7 +109,7 @@ describe( 'Navigating the block hierarchy', () => { await page.keyboard.press( 'Tab' ); // Tab to inserter. await page.keyboard.press( 'Enter' ); // Activate inserter. await page.keyboard.type( 'Paragraph' ); - await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await pressKeyTimes( 'Tab', 2 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'Third column' ); diff --git a/packages/e2e-tests/specs/editor/various/writing-flow.test.js b/packages/e2e-tests/specs/editor/various/writing-flow.test.js index d9a3b5c255df15..905e85ba9d016a 100644 --- a/packages/e2e-tests/specs/editor/various/writing-flow.test.js +++ b/packages/e2e-tests/specs/editor/various/writing-flow.test.js @@ -29,7 +29,7 @@ const addParagraphsAndColumnsDemo = async () => { await page.click( ':focus .block-editor-button-block-appender' ); await page.waitForSelector( ':focus.block-editor-inserter__search-input' ); await page.keyboard.type( 'Paragraph' ); - await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await pressKeyTimes( 'Tab', 2 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( '1st col' ); // If this text is too long, it may wrap to a new line and cause test failure. That's why we're using "1st" instead of "First" here. @@ -40,7 +40,7 @@ const addParagraphsAndColumnsDemo = async () => { await page.click( ':focus .block-editor-button-block-appender' ); await page.waitForSelector( ':focus.block-editor-inserter__search-input' ); await page.keyboard.type( 'Paragraph' ); - await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await pressKeyTimes( 'Tab', 2 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( '2nd col' ); // If this text is too long, it may wrap to a new line and cause test failure. That's why we're using "2nd" instead of "Second" here. diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index 80bd41351abca3..6c178f193ba162 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { useViewportMatch } from '@wordpress/compose'; -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { __, _x } from '@wordpress/i18n'; import { BlockToolbar, @@ -21,10 +21,12 @@ import { } from '@wordpress/components'; import { plus } from '@wordpress/icons'; -function HeaderToolbar( { onToggleInserter, isInserterOpen } ) { +function HeaderToolbar() { + const { setIsInserterOpened } = useDispatch( 'core/edit-post' ); const { hasFixedToolbar, isInserterEnabled, + isInserterOpened, isTextModeEnabled, previewDeviceType, } = useSelect( ( select ) => { @@ -45,6 +47,7 @@ function HeaderToolbar( { onToggleInserter, isInserterOpen } ) { hasInserterItems( getBlockRootClientId( getBlockSelectionEnd() ) ), + isInserterOpened: select( 'core/edit-post' ).isInserterOpened(), isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', previewDeviceType: select( @@ -72,8 +75,8 @@ function HeaderToolbar( { onToggleInserter, isInserterOpen } ) { as={ Button } className="edit-post-header-toolbar__inserter-toggle" isPrimary - isPressed={ isInserterOpen } - onClick={ onToggleInserter } + isPressed={ isInserterOpened } + onClick={ () => setIsInserterOpened( ! isInserterOpened ) } disabled={ ! isInserterEnabled } icon={ plus } label={ _x( diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index d4b52b0aa7cebc..6603eddf5ff7d7 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -17,11 +17,7 @@ import MoreMenu from './more-menu'; import PostPublishButtonOrToggle from './post-publish-button-or-toggle'; import { default as DevicePreview } from '../device-preview'; -function Header( { - onToggleInserter, - isInserterOpen, - setEntitiesSavedStatesCallback, -} ) { +function Header( { setEntitiesSavedStatesCallback } ) { const { hasActiveMetaboxes, isPublishSidebarOpened, isSaving } = useSelect( ( select ) => ( { hasActiveMetaboxes: select( 'core/edit-post' ).hasMetaBoxes(), @@ -39,10 +35,7 @@ function Header( {
- +
{ ! isPublishSidebarOpened && ( diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 13cd1009ae1676..5f606745b3940d 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -56,12 +56,13 @@ const interfaceLabels = { }; function Layout() { - const [ isInserterOpen, setIsInserterOpen ] = useState( false ); const isMobileViewport = useViewportMatch( 'medium', '<' ); const isHugeViewport = useViewportMatch( 'huge', '>=' ); - const { openGeneralSidebar, closeGeneralSidebar } = useDispatch( - 'core/edit-post' - ); + const { + openGeneralSidebar, + closeGeneralSidebar, + setIsInserterOpened, + } = useDispatch( 'core/edit-post' ); const { mode, isFullscreenActive, @@ -73,6 +74,7 @@ function Layout() { nextShortcut, hasBlockSelected, showMostUsedBlocks, + isInserterOpened, } = useSelect( ( select ) => { return { hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( @@ -89,6 +91,7 @@ function Layout() { showMostUsedBlocks: select( 'core/edit-post' ).isFeatureActive( 'mostUsedBlocks' ), + isInserterOpened: select( 'core/edit-post' ).isInserterOpened(), mode: select( 'core/edit-post' ).getEditorMode(), isRichEditingEnabled: select( 'core/editor' ).getEditorSettings() .richEditingEnabled, @@ -116,14 +119,14 @@ function Layout() { // Inserter and Sidebars are mutually exclusive useEffect( () => { if ( sidebarIsOpened && ! isHugeViewport ) { - setIsInserterOpen( false ); + setIsInserterOpened( false ); } }, [ sidebarIsOpened, isHugeViewport ] ); useEffect( () => { - if ( isInserterOpen && ! isHugeViewport ) { + if ( isInserterOpened && ! isHugeViewport ) { closeGeneralSidebar(); } - }, [ isInserterOpen, isHugeViewport ] ); + }, [ isInserterOpened, isHugeViewport ] ); // Local state for save panel. // Note 'thruthy' callback implies an open panel. @@ -157,10 +160,6 @@ function Layout() { labels={ interfaceLabels } header={
- setIsInserterOpen( ! isInserterOpen ) - } setEntitiesSavedStatesCallback={ setEntitiesSavedStatesCallback } @@ -168,13 +167,13 @@ function Layout() { } leftSidebar={ mode === 'visual' && - isInserterOpen && ( + isInserterOpened && (
@@ -186,7 +185,7 @@ function Layout() { showInserterHelpPanel onSelect={ () => { if ( isMobileViewport ) { - setIsInserterOpen( false ); + setIsInserterOpened( false ); } } } /> diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index ba930a0e4b2f63..a700d5ea42647a 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -46,6 +46,7 @@ class Editor extends Component { blockTypes, preferredStyleVariations, __experimentalLocalAutosaveInterval, + __experimentalSetIsInserterOpened, updatePreferredStyleVariations ) { settings = { @@ -57,6 +58,9 @@ class Editor extends Component { hasFixedToolbar, focusMode, __experimentalLocalAutosaveInterval, + + // This is marked as experimental to give time for the quick inserter to mature. + __experimentalSetIsInserterOpened, }; // Omit hidden block types if exists and non-empty. @@ -91,6 +95,7 @@ class Editor extends Component { blockTypes, preferredStyleVariations, __experimentalLocalAutosaveInterval, + setIsInserterOpened, updatePreferredStyleVariations, ...props } = this.props; @@ -107,6 +112,7 @@ class Editor extends Component { blockTypes, preferredStyleVariations, __experimentalLocalAutosaveInterval, + setIsInserterOpened, updatePreferredStyleVariations ); @@ -166,9 +172,13 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { updatePreferredStyleVariations } = dispatch( 'core/edit-post' ); + const { + updatePreferredStyleVariations, + setIsInserterOpened, + } = dispatch( 'core/edit-post' ); return { updatePreferredStyleVariations, + setIsInserterOpened, }; } ), ] )( Editor ); diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index b76b017a85e129..df9123616c2cf2 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -284,3 +284,16 @@ export function __experimentalSetPreviewDeviceType( deviceType ) { deviceType, }; } + +/** + * Returns an action object used to open/close the inserter. + * + * @param {boolean} value A boolean representing whether the inserter should be opened or closed. + * @return {Object} Action object. + */ +export function setIsInserterOpened( value ) { + return { + type: 'SET_IS_INSERTER_OPENED', + value, + }; +} diff --git a/packages/edit-post/src/store/reducer.js b/packages/edit-post/src/store/reducer.js index 6f77bb46e0937b..934de97aa73c08 100644 --- a/packages/edit-post/src/store/reducer.js +++ b/packages/edit-post/src/store/reducer.js @@ -238,6 +238,20 @@ export function deviceType( state = 'Desktop', action ) { return state; } +/** + * Reducer tracking whether the inserter is open. + * + * @param {boolean} state + * @param {Object} action + */ +function isInserterOpened( state = false, action ) { + switch ( action.type ) { + case 'SET_IS_INSERTER_OPENED': + return action.value; + } + return state; +} + const metaBoxes = combineReducers( { isSaving: isSavingMetaBoxes, locations: metaBoxLocations, @@ -250,4 +264,5 @@ export default combineReducers( { publishSidebarActive, removedPanels, deviceType, + isInserterOpened, } ); diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index 8b95f0a048b250..68dcc83671ed00 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -313,3 +313,14 @@ export function isSavingMetaBoxes( state ) { export function __experimentalGetPreviewDeviceType( state ) { return state.deviceType; } + +/** + * Returns true if the inserter is opened. + * + * @param {Object} state Global application state. + * + * @return {boolean} Whether the inserter is opened. + */ +export function isInserterOpened( state ) { + return state.isInserterOpened; +} diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 57cfb4af84da4d..7afca35eaf9c8e 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -125,6 +125,7 @@ class EditorProvider extends Component { '__experimentalGlobalStylesUserEntityId', '__experimentalGlobalStylesBase', '__experimentalPreferredStyleVariations', + '__experimentalSetIsInserterOpened', 'alignWide', 'allowedBlockTypes', 'availableLegacyWidgets',