diff --git a/packages/block-editor/src/components/block-breadcrumb/index.js b/packages/block-editor/src/components/block-breadcrumb/index.js
index b1fd13dbf3475a..70f038181237b4 100644
--- a/packages/block-editor/src/components/block-breadcrumb/index.js
+++ b/packages/block-editor/src/components/block-breadcrumb/index.js
@@ -12,6 +12,8 @@ import { chevronRightSmall, Icon } from '@wordpress/icons';
import BlockTitle from '../block-title';
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../lock-unlock';
+import { __unstableUseBlockRef as useBlockRef } from '../block-list/use-block-props/use-block-refs';
+import getEditorRegion from '../../utils/get-editor-region';
/**
* Block breadcrumb component, displaying the hierarchy of the current block selection as a breadcrumb.
@@ -37,6 +39,10 @@ function BlockBreadcrumb( { rootLabelText } ) {
}, [] );
const rootLabel = rootLabelText || __( 'Document' );
+ // We don't care about this specific ref, but this is a way
+ // to get a ref within the editor canvas so we can focus it later.
+ const blockRef = useBlockRef( clientId );
+
/*
* Disable reason: The `list` ARIA role is redundant but
* Safari+VoiceOver won't announce the list otherwise.
@@ -60,7 +66,16 @@ function BlockBreadcrumb( { rootLabelText } ) {
diff --git a/packages/block-editor/src/components/block-patterns-paging/style.scss b/packages/block-editor/src/components/block-patterns-paging/style.scss
index ce57f96cd327a5..383d4d72a8e38a 100644
--- a/packages/block-editor/src/components/block-patterns-paging/style.scss
+++ b/packages/block-editor/src/components/block-patterns-paging/style.scss
@@ -42,4 +42,22 @@
}
}
}
+
+ @media screen and (min-width: $break-large) {
+ .block-editor-patterns__grid-pagination {
+ flex-direction: row;
+ .block-editor-patterns__grid-pagination-previous,
+ .block-editor-patterns__grid-pagination-next {
+ flex-direction: row;
+ }
+ }
+ }
+}
+
+.block-editor-block-patterns-list .block-editor-patterns__grid-pagination {
+ flex-direction: column;
+ .block-editor-patterns__grid-pagination-previous,
+ .block-editor-patterns__grid-pagination-next {
+ flex-direction: column;
+ }
}
diff --git a/packages/block-editor/src/components/block-tools/block-selection-button.js b/packages/block-editor/src/components/block-tools/block-selection-button.js
index d4ec0f8cf79fb6..805e41c580f950 100644
--- a/packages/block-editor/src/components/block-tools/block-selection-button.js
+++ b/packages/block-editor/src/components/block-tools/block-selection-button.js
@@ -9,7 +9,7 @@ import clsx from 'clsx';
import { dragHandle, trash } from '@wordpress/icons';
import { Button, Flex, FlexItem, ToolbarButton } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
-import { useEffect, useRef } from '@wordpress/element';
+import { forwardRef, useEffect } from '@wordpress/element';
import {
BACKSPACE,
DELETE,
@@ -48,10 +48,11 @@ import Shuffle from '../block-toolbar/shuffle';
*
* @param {string} props Component props.
* @param {string} props.clientId Client ID of block.
+ * @param {Object} ref Reference to the component.
*
* @return {Component} The component to be rendered.
*/
-function BlockSelectionButton( { clientId, rootClientId } ) {
+function BlockSelectionButton( { clientId, rootClientId }, ref ) {
const selected = useSelect(
( select ) => {
const {
@@ -125,7 +126,6 @@ function BlockSelectionButton( { clientId, rootClientId } ) {
canMove,
} = selected;
const { setNavigationMode, removeBlock } = useDispatch( blockEditorStore );
- const ref = useRef();
// Focus the breadcrumb in navigation mode.
useEffect( () => {
@@ -164,11 +164,6 @@ function BlockSelectionButton( { clientId, rootClientId } ) {
const isEnter = keyCode === ENTER;
const isSpace = keyCode === SPACE;
const isShift = event.shiftKey;
- if ( isEscape && editorMode === 'navigation' ) {
- setNavigationMode( false );
- event.preventDefault();
- return;
- }
if ( keyCode === BACKSPACE || keyCode === DELETE ) {
removeBlock( clientId );
@@ -368,4 +363,4 @@ function BlockSelectionButton( { clientId, rootClientId } ) {
);
}
-export default BlockSelectionButton;
+export default forwardRef( BlockSelectionButton );
diff --git a/packages/block-editor/src/components/block-tools/block-toolbar-breadcrumb.js b/packages/block-editor/src/components/block-tools/block-toolbar-breadcrumb.js
index 0ae67e1be0001e..ae03bdb4f51647 100644
--- a/packages/block-editor/src/components/block-tools/block-toolbar-breadcrumb.js
+++ b/packages/block-editor/src/components/block-tools/block-toolbar-breadcrumb.js
@@ -3,6 +3,11 @@
*/
import clsx from 'clsx';
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
/**
* Internal dependencies
*/
@@ -11,10 +16,7 @@ import { PrivateBlockPopover } from '../block-popover';
import useBlockToolbarPopoverProps from './use-block-toolbar-popover-props';
import useSelectedBlockToolProps from './use-selected-block-tool-props';
-export default function BlockToolbarBreadcrumb( {
- clientId,
- __unstableContentRef,
-} ) {
+function BlockToolbarBreadcrumb( { clientId, __unstableContentRef }, ref ) {
const {
capturingClientId,
isInsertionPointVisible,
@@ -38,9 +40,12 @@ export default function BlockToolbarBreadcrumb( {
{ ...popoverProps }
>
);
}
+
+export default forwardRef( BlockToolbarBreadcrumb );
diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js
index ad744a81cca623..5cde3cccaf57e5 100644
--- a/packages/block-editor/src/components/block-tools/index.js
+++ b/packages/block-editor/src/components/block-tools/index.js
@@ -25,6 +25,7 @@ import usePopoverScroll from '../block-popover/use-popover-scroll';
import ZoomOutModeInserters from './zoom-out-mode-inserters';
import { useShowBlockTools } from './use-show-block-tools';
import { unlock } from '../../lock-unlock';
+import getEditorRegion from '../../utils/get-editor-region';
function selector( select ) {
const {
@@ -81,6 +82,7 @@ export default function BlockTools( {
} = useShowBlockTools();
const {
+ clearSelectedBlock,
duplicateBlocks,
removeBlocks,
replaceBlocks,
@@ -92,6 +94,8 @@ export default function BlockTools( {
expandBlock,
} = unlock( useDispatch( blockEditorStore ) );
+ const blockSelectionButtonRef = useRef();
+
function onKeyDown( event ) {
if ( event.defaultPrevented ) {
return;
@@ -152,6 +156,13 @@ export default function BlockTools( {
// block so that focus is directed back to the beginning of the selection.
// In effect, to the user this feels like deselecting the multi-selection.
selectBlock( clientIds[ 0 ] );
+ } else if (
+ clientIds.length === 1 &&
+ event.target === blockSelectionButtonRef?.current
+ ) {
+ event.preventDefault();
+ clearSelectedBlock();
+ getEditorRegion( __unstableContentRef.current ).focus();
}
} else if ( isMatch( 'core/block-editor/collapse-list-view', event ) ) {
// If focus is currently within a text field, such as a rich text block or other editable field,
@@ -182,7 +193,6 @@ export default function BlockTools( {
}
}
}
-
const blockToolbarRef = usePopoverScroll( __unstableContentRef );
const blockToolbarAfterRef = usePopoverScroll( __unstableContentRef );
@@ -213,6 +223,7 @@ export default function BlockTools( {
{ showBreadcrumb && (
diff --git a/packages/block-editor/src/utils/get-editor-region.js b/packages/block-editor/src/utils/get-editor-region.js
new file mode 100644
index 00000000000000..7edc57d1157fb4
--- /dev/null
+++ b/packages/block-editor/src/utils/get-editor-region.js
@@ -0,0 +1,31 @@
+/**
+ * Gets the editor region for a given editor canvas element or
+ * returns the passed element if no region is found
+ *
+ * @param { Object } editor The editor canvas element.
+ * @return { Object } The editor region or given editor element
+ */
+export default function getEditorRegion( editor ) {
+ if ( ! editor ) {
+ return null;
+ }
+
+ // If there are multiple editors, we need to find the iframe that contains our contentRef to make sure
+ // we're focusing the region that contains this editor.
+ const editorCanvas =
+ Array.from(
+ document.querySelectorAll( 'iframe[name="editor-canvas"]' ).values()
+ ).find( ( iframe ) => {
+ // Find the iframe that contains our contentRef
+ const iframeDocument =
+ iframe.contentDocument || iframe.contentWindow.document;
+
+ return iframeDocument === editor.ownerDocument;
+ } ) ?? editor;
+
+ // The region is provivided by the editor, not the block-editor.
+ // We should send focus to the region if one is available to reuse the
+ // same interface for navigating landmarks. If no region is available,
+ // use the canvas instead.
+ return editorCanvas?.closest( '[role="region"]' ) ?? editorCanvas;
+}
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index cbf32c9eab93ba..728151987c9ef4 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -2,6 +2,12 @@
## Unreleased
+### Internal
+
+- `CustomSelectControlV2`: add root element wrapper. ([#62803](https://github.com/WordPress/gutenberg/pull/62803))
+- `CustomSelectControlV2`: fix popover styles. ([#62821](https://github.com/WordPress/gutenberg/pull/62821))
+- `CustomSelectControlV2`: fix trigger text alignment in RTL languages ([#62869](https://github.com/WordPress/gutenberg/pull/62869)).
+
## 28.2.0 (2024-06-26)
### Enhancements
diff --git a/packages/components/src/custom-select-control-v2/custom-select.tsx b/packages/components/src/custom-select-control-v2/custom-select.tsx
index 414a805eccfb1a..f76c7f67ea77b4 100644
--- a/packages/components/src/custom-select-control-v2/custom-select.tsx
+++ b/packages/components/src/custom-select-control-v2/custom-select.tsx
@@ -88,11 +88,13 @@ function _CustomSelect(
label,
size,
store,
+ className,
...restProps
} = props;
return (
- <>
+ // Where should `restProps` be forwarded to?
+
{ hideLabelFromVision ? ( // TODO: Replace with BaseControl
{ label }
) : (
@@ -116,7 +118,7 @@ function _CustomSelect(
- >
+
);
}
diff --git a/packages/components/src/custom-select-control-v2/legacy-component/index.tsx b/packages/components/src/custom-select-control-v2/legacy-component/index.tsx
index e2b9a8a7471e5f..209483775db9e4 100644
--- a/packages/components/src/custom-select-control-v2/legacy-component/index.tsx
+++ b/packages/components/src/custom-select-control-v2/legacy-component/index.tsx
@@ -3,6 +3,7 @@
*/
// eslint-disable-next-line no-restricted-imports
import * as Ariakit from '@ariakit/react';
+import clsx from 'clsx';
/**
* Internal dependencies
@@ -21,6 +22,7 @@ function CustomSelectControl( props: LegacyCustomSelectProps ) {
onChange,
size = 'default',
value,
+ className: classNameProp,
...restProps
} = props;
@@ -122,6 +124,10 @@ function CustomSelectControl( props: LegacyCustomSelectProps ) {
}
size={ translatedSize }
store={ store }
+ className={ clsx(
+ 'components-custom-select-control',
+ classNameProp
+ ) }
{ ...restProps }
>
{ children }
diff --git a/packages/components/src/custom-select-control-v2/styles.ts b/packages/components/src/custom-select-control-v2/styles.ts
index c75a9a79c71c5f..c806bbee794d1a 100644
--- a/packages/components/src/custom-select-control-v2/styles.ts
+++ b/packages/components/src/custom-select-control-v2/styles.ts
@@ -92,7 +92,7 @@ export const Select = styled( Ariakit.Select, {
cursor: pointer;
font-family: inherit;
font-size: ${ CONFIG.fontSize };
- text-align: left;
+ text-align: start;
width: 100%;
&[data-focus-visible] {
@@ -105,10 +105,20 @@ export const Select = styled( Ariakit.Select, {
} );
export const SelectPopover = styled( Ariakit.SelectPopover )`
+ display: flex;
+ flex-direction: column;
+
+ background-color: ${ COLORS.theme.background };
border-radius: 2px;
- background: ${ COLORS.theme.background };
border: 1px solid ${ COLORS.theme.foreground };
+ /* z-index(".components-popover") */
+ z-index: 1000000;
+
+ max-height: min( var( --popover-available-height, 400px ), 400px );
+ overflow: auto;
+ overscroll-behavior: contain;
+
&[data-focus-visible] {
outline: none; // outline will be on the trigger, rather than the popover
}
diff --git a/packages/components/src/custom-select-control-v2/types.ts b/packages/components/src/custom-select-control-v2/types.ts
index 12b41ba54f4a20..3c192cfa56711f 100644
--- a/packages/components/src/custom-select-control-v2/types.ts
+++ b/packages/components/src/custom-select-control-v2/types.ts
@@ -50,6 +50,10 @@ export type CustomSelectButtonProps = {
};
export type _CustomSelectProps = CustomSelectButtonProps & {
+ /**
+ * Additional className added to the root wrapper element.
+ */
+ className?: string;
/**
* The child elements. This should be composed of `CustomSelectItem` components.
*/
diff --git a/packages/components/src/higher-order/navigate-regions/style.scss b/packages/components/src/higher-order/navigate-regions/style.scss
index b3a4a0c1a9d1b5..5c3767e310b8f4 100644
--- a/packages/components/src/higher-order/navigate-regions/style.scss
+++ b/packages/components/src/higher-order/navigate-regions/style.scss
@@ -1,22 +1,35 @@
// Allow the position to be easily overridden to e.g. fixed.
+
+@mixin region-selection-outline {
+ outline: 4px solid $components-color-accent;
+ outline-offset: -4px;
+}
+
+@mixin region-selection-focus {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ content: "";
+ pointer-events: none;
+ @include region-selection-outline;
+ z-index: z-index(".is-focusing-regions {region} :focus::after");
+}
+
[role="region"] {
position: relative;
+
+ // Handles the focus when we programatically send focus to this region
+ &.interface-interface-skeleton__content:focus-visible::after {
+ @include region-selection-focus;
+ }
}
.is-focusing-regions {
[role="region"]:focus::after {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- content: "";
- pointer-events: none;
- outline: 4px solid $components-color-accent;
- outline-offset: -4px;
- z-index: z-index(".is-focusing-regions {region} :focus::after");
+ @include region-selection-focus;
}
-
// Fixes for edge cases.
// Some of the regions are currently used for layout purposes as 'interface skeleton'
// items. When they're absolutely positioned or when they contain absolutely
@@ -33,7 +46,6 @@
.interface-interface-skeleton__actions .editor-layout__toggle-publish-panel,
.interface-interface-skeleton__actions .editor-layout__toggle-entities-saved-states-panel,
.editor-post-publish-panel {
- outline: 4px solid $components-color-accent;
- outline-offset: -4px;
+ @include region-selection-outline;
}
}
diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss
index 5844e0c9133369..20190623a3da81 100644
--- a/packages/dataviews/src/style.scss
+++ b/packages/dataviews/src/style.scss
@@ -759,7 +759,7 @@
padding: 0 $grid-unit-15;
height: $grid-unit-40;
background: $gray-100;
- color: $gray-700;
+ color: $gray-800;
position: relative;
display: flex;
align-items: center;
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index 8c1819b3a7c674..b01394c7f846a1 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -216,7 +216,7 @@ interface ViewBase {
/**
* The hidden fields.
*/
- hiddenFields: string[];
+ hiddenFields?: string[];
}
export interface ViewTable extends ViewBase {
diff --git a/packages/dataviews/src/view-grid.tsx b/packages/dataviews/src/view-grid.tsx
index 77ac3c92738523..4538ab145d2134 100644
--- a/packages/dataviews/src/view-grid.tsx
+++ b/packages/dataviews/src/view-grid.tsx
@@ -206,7 +206,7 @@ export default function ViewGrid< Item extends AnyItem >( {
const { visibleFields, badgeFields } = fields.reduce(
( accumulator: Record< string, NormalizedField< Item >[] >, field ) => {
if (
- view.hiddenFields.includes( field.id ) ||
+ view.hiddenFields?.includes( field.id ) ||
[ view.layout.mediaField, view.layout.primaryField ].includes(
field.id
)
diff --git a/packages/dataviews/src/view-list.tsx b/packages/dataviews/src/view-list.tsx
index 295c3d28856ebe..eb2b9c6c077a6a 100644
--- a/packages/dataviews/src/view-list.tsx
+++ b/packages/dataviews/src/view-list.tsx
@@ -329,7 +329,7 @@ export default function ViewList< Item extends AnyItem >(
);
const visibleFields = fields.filter(
( field ) =>
- ! view.hiddenFields.includes( field.id ) &&
+ ! view.hiddenFields?.includes( field.id ) &&
! [ view.layout.primaryField, view.layout.mediaField ].includes(
field.id
)
diff --git a/packages/dataviews/src/view-table.tsx b/packages/dataviews/src/view-table.tsx
index 2ddb09e2640a01..66e59a8ebb4230 100644
--- a/packages/dataviews/src/view-table.tsx
+++ b/packages/dataviews/src/view-table.tsx
@@ -223,9 +223,9 @@ const _HeaderMenu = forwardRef( function HeaderMenu< Item extends AnyItem >(
onHide( field );
onChangeView( {
...view,
- hiddenFields: view.hiddenFields.concat(
- field.id
- ),
+ hiddenFields: (
+ view.hiddenFields ?? []
+ ).concat( field.id ),
} );
} }
>
@@ -473,7 +473,7 @@ function ViewTable< Item extends AnyItem >( {
};
const visibleFields = fields.filter(
( field ) =>
- ! view.hiddenFields.includes( field.id ) &&
+ ! view.hiddenFields?.includes( field.id ) &&
! [ view.layout.mediaField ].includes( field.id )
);
const hasData = !! data?.length;
diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js
index 7080ff8f185f4f..0a6a4469cef4e7 100644
--- a/packages/edit-site/src/components/page-patterns/index.js
+++ b/packages/edit-site/src/components/page-patterns/index.js
@@ -76,7 +76,6 @@ const DEFAULT_VIEW = {
search: '',
page: 1,
perPage: 20,
- hiddenFields: [],
layout: {
...defaultConfigPerViewType[ LAYOUT_GRID ],
},
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index edf67bb1da9244..0783478126a1eb 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -223,7 +223,11 @@ const trashPostAction = {
} else if ( items[ 0 ].type === 'page' ) {
successMessage = sprintf(
/* translators: The number of items. */
- __( '%s items moved to trash.' ),
+ _n(
+ '%s item moved to trash.',
+ '%s items moved to trash.',
+ items.length
+ ),
items.length
);
} else {
diff --git a/packages/editor/src/components/post-featured-image/style.scss b/packages/editor/src/components/post-featured-image/style.scss
index 052f6943012595..d3a4fbbcaef689 100644
--- a/packages/editor/src/components/post-featured-image/style.scss
+++ b/packages/editor/src/components/post-featured-image/style.scss
@@ -20,6 +20,21 @@
opacity: 1;
}
}
+
+ .components-drop-zone__content {
+ border-radius: $radius-block-ui;
+ }
+
+ // Align text and icons horizontally to avoid clipping when the featured image is not set.
+ &:has(.editor-post-featured-image__toggle) .components-drop-zone .components-drop-zone__content-inner {
+ display: flex;
+ align-items: center;
+ gap: $grid-unit-10;
+
+ .components-drop-zone__content-icon {
+ margin: 0;
+ }
+ }
}
.editor-post-featured-image__toggle,
diff --git a/test/e2e/specs/editor/various/writing-flow.spec.js b/test/e2e/specs/editor/various/writing-flow.spec.js
index 1af46a80896f07..bd1552ad4cb66a 100644
--- a/test/e2e/specs/editor/various/writing-flow.spec.js
+++ b/test/e2e/specs/editor/various/writing-flow.spec.js
@@ -958,7 +958,7 @@ test.describe( 'Writing Flow (@firefox, @webkit)', () => {
` );
} );
- test( 'escape should toggle between edit and navigation modes', async ( {
+ test( 'escape should set select mode and then focus the canvas', async ( {
page,
writingFlowUtils,
} ) => {
@@ -975,15 +975,13 @@ test.describe( 'Writing Flow (@firefox, @webkit)', () => {
.poll( writingFlowUtils.getActiveBlockName )
.toBe( 'core/paragraph' );
- // Second escape Toggles back to Edit Mode
+ // Second escape should send focus to the canvas
await page.keyboard.press( 'Escape' );
+ // The navigation button should be hidden.
await expect( navigationButton ).toBeHidden();
- const blockToolbar = page.getByLabel( 'Block tools' );
-
- await expect( blockToolbar ).toBeVisible();
- await expect
- .poll( writingFlowUtils.getActiveBlockName )
- .toBe( 'core/paragraph' );
+ await expect(
+ page.getByRole( 'region', { name: 'Editor content' } )
+ ).toBeFocused();
} );
// Checks for regressions of https://github.com/WordPress/gutenberg/issues/40091.