diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js
index 75b442085fbb62..151038a7aac7f4 100644
--- a/packages/block-editor/src/components/block-styles/index.js
+++ b/packages/block-editor/src/components/block-styles/index.js
@@ -82,7 +82,12 @@ const useGenericPreviewBlock = ( block, type ) =>
[ type.example ? block.name : block, type ]
);
-function BlockStyles( { clientId, onSwitch = noop, onHoverClassName = noop } ) {
+function BlockStyles( {
+ clientId,
+ onSwitch = noop,
+ onHoverClassName = noop,
+ itemRole,
+} ) {
const selector = ( select ) => {
const { getBlock } = select( 'core/block-editor' );
const { getBlockStyles } = select( 'core/blocks' );
@@ -141,6 +146,7 @@ function BlockStyles( { clientId, onSwitch = noop, onHoverClassName = noop } ) {
onHover={ () => onHoverClassName( styleClassName ) }
style={ style }
styleClassName={ styleClassName }
+ itemRole={ itemRole }
/>
);
} ) }
@@ -156,6 +162,7 @@ function BlockStyleItem( {
onHover,
onSelect,
styleClassName,
+ itemRole,
} ) {
const previewBlocks = useMemo( () => {
return {
@@ -182,7 +189,7 @@ function BlockStyleItem( {
} }
onMouseEnter={ onHover }
onMouseLeave={ onBlur }
- role="button"
+ role={ itemRole || 'button' }
tabIndex="0"
aria-label={ style.label || style.name }
>
diff --git a/packages/block-editor/src/components/block-styles/style.scss b/packages/block-editor/src/components/block-styles/style.scss
index 8ae9248313c87f..ff58284eabf929 100644
--- a/packages/block-editor/src/components/block-styles/style.scss
+++ b/packages/block-editor/src/components/block-styles/style.scss
@@ -53,5 +53,5 @@
.block-editor-block-styles__item-label {
text-align: center;
- padding: 4px 2px;
+ padding: 4px 0;
}
diff --git a/packages/block-editor/src/components/block-switcher/block-transformations-menu.js b/packages/block-editor/src/components/block-switcher/block-transformations-menu.js
new file mode 100644
index 00000000000000..d01663a41bd2d1
--- /dev/null
+++ b/packages/block-editor/src/components/block-switcher/block-transformations-menu.js
@@ -0,0 +1,40 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { MenuGroup, MenuItem } from '@wordpress/components';
+import { getBlockMenuDefaultClassName } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import BlockIcon from '../block-icon';
+
+const BlockTransformationsMenu = ( {
+ className,
+ possibleBlockTransformations,
+ onSelect,
+} ) => {
+ return (
+
+ { possibleBlockTransformations.map( ( item ) => {
+ const { name, icon, title } = item;
+ return (
+ }
+ onClick={ ( event ) => {
+ event.preventDefault();
+ onSelect( name );
+ } }
+ >
+ { title }
+
+ );
+ } ) }
+
+ );
+};
+
+export default BlockTransformationsMenu;
diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js
index 3f2a57fc6665e8..b0005b2e629243 100644
--- a/packages/block-editor/src/components/block-switcher/index.js
+++ b/packages/block-editor/src/components/block-switcher/index.js
@@ -1,17 +1,19 @@
/**
* External dependencies
*/
-import { castArray, filter, first, mapKeys, orderBy, uniq, map } from 'lodash';
+import { castArray, filter, mapKeys, orderBy, uniq, map } from 'lodash';
/**
* WordPress dependencies
*/
import { __, _n, sprintf } from '@wordpress/i18n';
import {
- Dropdown,
+ DropdownMenu,
ToolbarButton,
ToolbarGroup,
+ __experimentalToolbarItem as ToolbarItem,
MenuGroup,
+ Popover,
} from '@wordpress/components';
import {
getBlockType,
@@ -21,7 +23,6 @@ import {
getBlockFromExample,
} from '@wordpress/blocks';
import { Component } from '@wordpress/element';
-import { DOWN } from '@wordpress/keycodes';
import { withSelect, withDispatch } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { layout } from '@wordpress/icons';
@@ -32,12 +33,47 @@ import { layout } from '@wordpress/icons';
import BlockIcon from '../block-icon';
import BlockStyles from '../block-styles';
import BlockPreview from '../block-preview';
-import BlockTypesList from '../block-types-list';
+import BlockTransformationsMenu from './block-transformations-menu';
-const POPOVER_PROPS = {
- position: 'bottom right',
- isAlternate: true,
-};
+function PreviewBlockPopover( { hoveredBlock, hoveredClassName } ) {
+ const hoveredBlockType = getBlockType( hoveredBlock.name );
+ return (
+
+
+
+
+
+ { __( 'Preview' ) }
+
+
+
+
+
+
+ );
+}
export class BlockSwitcher extends Component {
constructor() {
@@ -61,13 +97,11 @@ export class BlockSwitcher extends Component {
} = this.props;
const { hoveredClassName } = this.state;
- if ( ! blocks || ! blocks.length ) {
+ if ( ! Array.isArray( blocks ) || ! blocks.length ) {
return null;
}
- const hoveredBlock = blocks[ 0 ];
- const hoveredBlockType = getBlockType( hoveredBlock.name );
-
+ const [ hoveredBlock ] = blocks;
const itemsByName = mapKeys( inserterItems, ( { name } ) => name );
const possibleBlockTransformations = orderBy(
filter(
@@ -85,14 +119,16 @@ export class BlockSwitcher extends Component {
let icon;
if ( isSelectionOfSameType ) {
- const sourceBlockName = blocks[ 0 ].name;
+ const sourceBlockName = hoveredBlock.name;
const blockType = getBlockType( sourceBlockName );
icon = blockType.icon;
} else {
icon = layout;
}
- if ( ! hasBlockStyles && ! possibleBlockTransformations.length ) {
+ const hasPossibleBlockTransformations = !! possibleBlockTransformations.length;
+
+ if ( ! hasBlockStyles && ! hasPossibleBlockTransformations ) {
return (
{
- const openOnArrowDown = ( event ) => {
- if ( ! isOpen && event.keyCode === DOWN ) {
- event.preventDefault();
- event.stopPropagation();
- onToggle();
- }
- };
- const label =
- 1 === blocks.length
- ? __( 'Change block type or style' )
- : sprintf(
- /* translators: %s: number of blocks. */
- _n(
- 'Change type of %d block',
- 'Change type of %d blocks',
- blocks.length
- ),
- blocks.length
- );
+ const blockSwitcherLabel =
+ 1 === blocks.length
+ ? __( 'Change block type or style' )
+ : sprintf(
+ /* translators: %s: number of blocks. */
+ _n(
+ 'Change type of %d block',
+ 'Change type of %d blocks',
+ blocks.length
+ ),
+ blocks.length
+ );
- return (
-
- }
- />
-
- );
- } }
- renderContent={ ( { onClose } ) => (
- <>
- { ( hasBlockStyles ||
- possibleBlockTransformations.length !== 0 ) && (
-
- { hasBlockStyles && (
-
-
- { __( 'Styles' ) }
-
-
-
- ) }
- { possibleBlockTransformations.length !== 0 && (
-
-
- { __( 'Transform to' ) }
-
- ( {
- id:
- destinationBlockType.name,
- icon:
- destinationBlockType.icon,
- title:
- destinationBlockType.title,
- } )
- ) }
- onSelect={ ( item ) => {
- onTransform( blocks, item.id );
- onClose();
- } }
- />
-
- ) }
-
- ) }
- { hoveredClassName !== null && (
-
-
- { __( 'Preview' ) }
-
-
+
+ { ( toggleProps ) => (
+
-
- ) }
- >
- ) }
- />
+ }
+ toggleProps={ toggleProps }
+ menuProps={ { orientation: 'both' } }
+ >
+ { ( { onClose } ) =>
+ ( hasBlockStyles ||
+ hasPossibleBlockTransformations ) && (
+
+ { hoveredClassName !== null && (
+
+ ) }
+ { hasBlockStyles && (
+
+
+
+ ) }
+ { hasPossibleBlockTransformations && (
+
{
+ onTransform( blocks, name );
+ onClose();
+ } }
+ />
+ ) }
+
+ )
+ }
+
+ ) }
+
+
);
}
}
@@ -237,7 +238,7 @@ export default compose(
} = select( 'core/block-editor' );
const { getBlockStyles } = select( 'core/blocks' );
const rootClientId = getBlockRootClientId(
- first( castArray( clientIds ) )
+ castArray( clientIds )[ 0 ]
);
const blocks = getBlocksByClientId( clientIds );
const firstBlock = blocks && blocks.length === 1 ? blocks[ 0 ] : null;
diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss
index 7e08ab10143329..bb214b10f35776 100644
--- a/packages/block-editor/src/components/block-switcher/style.scss
+++ b/packages/block-editor/src/components/block-switcher/style.scss
@@ -65,54 +65,31 @@
}
}
+.components-popover.block-editor-block-switcher__popover .components-popover__content {
+ min-width: 300px;
+}
-// We double the max-width for it to fit both the preview & content but we keep the min width the same for the border to work.
+// We keep the min width the same for the border to work.
.components-popover.block-editor-block-switcher__popover .components-popover__content > div {
- min-width: 300px;
- max-width: calc(340px * 2);
+ min-width: auto;
display: flex;
background: $white;
padding: 0;
.components-menu-group {
- padding: $grid-unit-20 $grid-unit-30;
- }
-
- .components-menu-group + .components-menu-group {
- border-color: $light-gray-secondary;
+ padding: $grid-unit-20;
+ margin: 0;
}
+}
- .block-editor-block-switcher__container {
- min-width: 300px;
- max-width: 340px;
- width: 50%;
- }
+.block-editor-block-switcher__popover .components-popover__content {
- .block-editor-block-switcher__label {
- margin-bottom: $grid-unit-15;
- color: $medium-gray-text;
- text-transform: uppercase;
- font-size: 11px;
- font-weight: 500;
+ .block-editor-block-styles {
+ margin: 0 -3px; // Remove the panel body padding while keeping it for the title.
}
- @include break-medium {
- position: relative;
-
- .block-editor-block-switcher__preview {
- border-left: $border-width solid $light-gray-500;
- margin-top: -$grid-unit-15;
- margin-bottom: -$grid-unit-15;
- background: $white;
- width: 300px;
- height: auto;
- // Sticky helps us keep the preview at the top when scrolling
- position: sticky;
- align-self: stretch;
- // For sticky to work we need top
- top: 0;
- padding: 10px;
- }
+ .block-editor-block-styles__item-label {
+ text-align: left;
}
// Hide the bottom border on the last panel so it stacks with the popover.
@@ -129,12 +106,39 @@
}
}
-.block-editor-block-switcher__popover .block-editor-block-styles {
- margin: 0 -3px; // Remove the panel body padding while keeping it for the title.
+.block-editor-block-switcher__popover__preview__parent {
+ .block-editor-block-switcher__popover__preview__container {
+ position: absolute;
+ top: -$grid-unit-15;
+ left: calc(100% + #{$grid-unit-40});
+ }
}
-.block-editor-block-switcher__popover .block-editor-block-types-list {
- margin: $grid-unit-15 0 0 0;
+.block-editor-block-switcher__preview__popover {
+ display: none;
+
+ // Position correctly. Needs specificity.
+ &.components-popover {
+ margin-left: $grid-unit-05;
+ margin-top: $grid-unit-15 - $border-width;
+ }
+
+ @include break-medium() {
+ display: block;
+ }
+
+ .components-popover__content {
+ box-shadow: none;
+ border: $border-width solid $dark-gray-primary;
+ background: $white;
+ border-radius: $radius-block-ui;
+ }
+
+ .block-editor-block-switcher__preview {
+ width: 300px;
+ height: auto;
+ padding: $grid-unit-20;
+ }
}
.block-editor-block-switcher__preview-title {
diff --git a/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap
index c8463783bda49e..9cf29a6a7c411e 100644
--- a/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap
+++ b/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap
@@ -26,31 +26,17 @@ exports[`BlockSwitcher should render disabled block switcher with multi block of
`;
exports[`BlockSwitcher should render enabled block switcher with multi block when transforms exist 1`] = `
-
+
+
+
+
+
`;
exports[`BlockSwitcher should render switcher with blocks 1`] = `
-
+
+
+
+
+
`;
diff --git a/packages/block-editor/src/components/block-switcher/test/index.js b/packages/block-editor/src/components/block-switcher/test/index.js
index 824bf47d882d17..a5879995c271d2 100644
--- a/packages/block-editor/src/components/block-switcher/test/index.js
+++ b/packages/block-editor/src/components/block-switcher/test/index.js
@@ -1,14 +1,14 @@
/**
* External dependencies
*/
-import { shallow } from 'enzyme';
+import { shallow, mount } from 'enzyme';
/**
* WordPress dependencies
*/
import { registerBlockType, unregisterBlockType } from '@wordpress/blocks';
import { DOWN } from '@wordpress/keycodes';
-import { ToolbarButton } from '@wordpress/components';
+import { Button } from '@wordpress/components';
/**
* Internal dependencies
@@ -154,7 +154,7 @@ describe( 'BlockSwitcher', () => {
const onTransformStub = jest.fn();
const getDropdown = () => {
- const blockSwitcher = shallow(
+ const blockSwitcher = mount(
{
} );
test( 'should simulate a keydown event, which should call onToggle and open transform toggle.', () => {
- const toggleClosed = shallow(
+ const toggleClosed = mount(
getDropdown().props().renderToggle( {
onToggle: onToggleStub,
isOpen: false,
} )
);
- const iconButtonClosed = toggleClosed.find( ToolbarButton );
+ const iconButtonClosed = toggleClosed.find( Button );
iconButtonClosed.simulate( 'keydown', mockKeyDown );
@@ -195,13 +195,13 @@ describe( 'BlockSwitcher', () => {
} );
test( 'should simulate a click event, which should call onToggle.', () => {
- const toggleOpen = shallow(
+ const toggleOpen = mount(
getDropdown().props().renderToggle( {
onToggle: onToggleStub,
isOpen: true,
} )
);
- const iconButtonOpen = toggleOpen.find( ToolbarButton );
+ const iconButtonOpen = toggleOpen.find( Button );
iconButtonOpen.simulate( 'keydown', mockKeyDown );
@@ -219,8 +219,10 @@ describe( 'BlockSwitcher', () => {
.renderContent( { onClose: onCloseStub } ) }
);
- const blockList = content.find( 'BlockTypesList' );
- expect( blockList.prop( 'items' ) ).toHaveLength( 1 );
+ const blockList = content.find( 'BlockTransformationsMenu' );
+ expect(
+ blockList.prop( 'possibleBlockTransformations' )
+ ).toHaveLength( 1 );
} );
} );
} );
diff --git a/packages/dom/src/focusable.js b/packages/dom/src/focusable.js
index 12688755cfe445..1022bf827d2923 100644
--- a/packages/dom/src/focusable.js
+++ b/packages/dom/src/focusable.js
@@ -47,6 +47,21 @@ function isVisible( element ) {
);
}
+/**
+ * Returns true if the specified element should be skipped from focusable elements.
+ * For now it rather specific for `iframes` and if tabindex attribute is set to -1.
+ *
+ * @param {Element} element DOM element to test.
+ *
+ * @return {boolean} Whether element should be skipped from focusable elements.
+ */
+function skipFocus( element ) {
+ return (
+ element.nodeName.toLowerCase() === 'iframe' &&
+ element.getAttribute( 'tabindex' ) === '-1'
+ );
+}
+
/**
* Returns true if the specified area element is a valid focusable element, or
* false otherwise. Area is only focusable if within a map where a named map
@@ -77,7 +92,7 @@ export function find( context ) {
const elements = context.querySelectorAll( SELECTOR );
return Array.from( elements ).filter( ( element ) => {
- if ( ! isVisible( element ) ) {
+ if ( ! isVisible( element ) || skipFocus( element ) ) {
return false;
}
diff --git a/packages/e2e-test-utils/src/get-available-block-transforms.js b/packages/e2e-test-utils/src/get-available-block-transforms.js
index eeadabea18f360..6a029210dda117 100644
--- a/packages/e2e-test-utils/src/get-available-block-transforms.js
+++ b/packages/e2e-test-utils/src/get-available-block-transforms.js
@@ -22,5 +22,5 @@ export const getAvailableBlockTransforms = async () => {
return button.textContent;
}
);
- }, '.block-editor-block-switcher__popover .block-editor-block-types-list .block-editor-block-types-list__list-item button' );
+ }, '.block-editor-block-switcher__popover .block-editor-block-switcher__transforms__menugroup button' );
};