+
{ mode === 'visual' && isValid && (
<>
- { blockClientIds.length === 1 &&
}
diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss
index 7c4be56fe335f..e4d1a00429875 100644
--- a/packages/block-library/src/columns/editor.scss
+++ b/packages/block-library/src/columns/editor.scss
@@ -9,6 +9,13 @@
}
}
+// Ideally all block toolbars should be positioned the same.
+.components-popover.block-editor-block-list__block-popover
+.components-popover__content
+.block-editor-block-contextual-toolbar[data-type="core/column"] {
+ margin-left: 0;
+}
+
.wp-block-columns {
display: block;
@@ -91,10 +98,6 @@
right: 0;
}
- > .block-editor-block-contextual-toolbar {
- margin-left: -$border-width;
- }
-
// Zero out margins.
> [data-block] {
margin-top: 0;
diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js
index 223bbb5032afb..438dd20c0d685 100644
--- a/packages/components/src/popover/index.js
+++ b/packages/components/src/popover/index.js
@@ -224,6 +224,10 @@ const Popover = ( {
animate = true,
onClickOutside,
onFocusOutside,
+ __unstableSticky,
+ __unstableSlotName = SLOT_NAME,
+ __unstableAllowVerticalSubpixelPosition,
+ __unstableAllowHorizontalSubpixelPosition,
/* eslint-enable no-unused-vars */
...contentProps
} ) => {
@@ -252,7 +256,7 @@ const Popover = ( {
return;
}
- const refresh = () => {
+ const refresh = ( { subpixels } = {} ) => {
const anchor = computeAnchorRect(
anchorRefFallback,
anchorRect,
@@ -276,13 +280,27 @@ const Popover = ( {
yAxis,
contentHeight,
contentWidth,
- } = computePopoverPosition( anchor, contentRect.current, position );
+ } = computePopoverPosition( anchor, contentRect.current, position, __unstableSticky, anchorRef );
+
+ if ( typeof popoverTop === 'number' && typeof popoverLeft === 'number' ) {
+ if ( subpixels && __unstableAllowVerticalSubpixelPosition ) {
+ setStyle( containerEl, 'left', popoverLeft + 'px' );
+ setStyle( containerEl, 'top' );
+ setStyle( containerEl, 'transform', `translateY(${ popoverTop }px)` );
+ } else if ( subpixels && __unstableAllowHorizontalSubpixelPosition ) {
+ setStyle( containerEl, 'top', popoverTop + 'px' );
+ setStyle( containerEl, 'left' );
+ setStyle( containerEl, 'transform', `translate(${ popoverLeft }px)` );
+ } else {
+ setStyle( containerEl, 'top', popoverTop + 'px' );
+ setStyle( containerEl, 'left', popoverLeft + 'px' );
+ setStyle( containerEl, 'transform' );
+ }
+ }
setClass( containerEl, 'is-without-arrow', noArrow || ( xAxis === 'center' && yAxis === 'middle' ) );
setAttribute( containerEl, 'data-x-axis', xAxis );
setAttribute( containerEl, 'data-y-axis', yAxis );
- setStyle( containerEl, 'top', typeof popoverTop === 'number' ? popoverTop + 'px' : '' );
- setStyle( containerEl, 'left', typeof popoverLeft === 'number' ? popoverLeft + 'px' : '' );
setStyle( contentEl, 'maxHeight', typeof contentHeight === 'number' ? contentHeight + 'px' : '' );
setStyle( contentEl, 'maxWidth', typeof contentWidth === 'number' ? contentWidth + 'px' : '' );
@@ -323,12 +341,28 @@ const Popover = ( {
window.addEventListener( 'resize', refresh );
window.addEventListener( 'scroll', refresh, true );
+ let observer;
+
+ const observeElement = (
+ __unstableAllowVerticalSubpixelPosition ||
+ __unstableAllowHorizontalSubpixelPosition
+ );
+
+ if ( observeElement ) {
+ observer = new window.MutationObserver( () => refresh( { subpixels: true } ) );
+ observer.observe( observeElement, { attributes: true } );
+ }
+
return () => {
window.clearTimeout( timeoutId );
window.clearInterval( intervalHandle );
window.removeEventListener( 'resize', refresh );
window.removeEventListener( 'scroll', refresh, true );
window.addEventListener( 'click', refreshOnAnimationFrame );
+
+ if ( observer ) {
+ observer.disconnect();
+ }
};
}, [
isExpanded,
@@ -337,6 +371,9 @@ const Popover = ( {
anchorRef,
shouldAnchorIncludePadding,
position,
+ __unstableSticky,
+ __unstableAllowVerticalSubpixelPosition,
+ __unstableAllowHorizontalSubpixelPosition,
] );
useFocusContentOnMount( focusOnMount, contentRef );
@@ -452,8 +489,8 @@ const Popover = ( {
{ ( { getSlot } ) => {
// In case there is no slot context in which to render,
// default to an in-place rendering.
- if ( getSlot && getSlot( SLOT_NAME ) ) {
- content =
{ content };
+ if ( getSlot && getSlot( __unstableSlotName ) ) {
+ content =
{ content };
}
return (
@@ -469,6 +506,7 @@ const Popover = ( {
const PopoverContainer = Popover;
-PopoverContainer.Slot = () =>
;
+PopoverContainer.Slot = ( { name = SLOT_NAME } ) =>
+
;
export default PopoverContainer;
diff --git a/packages/components/src/popover/style.scss b/packages/components/src/popover/style.scss
index f9b89cd371184..a077de513f4a0 100644
--- a/packages/components/src/popover/style.scss
+++ b/packages/components/src/popover/style.scss
@@ -4,7 +4,8 @@ $arrow-size: 8px;
.components-popover {
position: fixed;
z-index: z-index(".components-popover");
- left: 50%;
+ top: 0;
+ left: 0;
// Hide the popover element until the position has been calculated. The position
// cannot be calculated until the popover element is rendered because the
diff --git a/packages/components/src/popover/utils.js b/packages/components/src/popover/utils.js
index 9379e6d2786c0..971ec4919d1c9 100644
--- a/packages/components/src/popover/utils.js
+++ b/packages/components/src/popover/utils.js
@@ -1,29 +1,44 @@
+/**
+ * WordPress dependencies
+ */
+import { getScrollContainer } from '@wordpress/dom';
/**
* Module constants
*/
const HEIGHT_OFFSET = 10; // used by the arrow and a bit of empty space
-const isRTL = () => document.documentElement.dir === 'rtl';
/**
* Utility used to compute the popover position over the xAxis
*
- * @param {Object} anchorRect Anchor Rect.
- * @param {Object} contentSize Content Size.
- * @param {string} xAxis Desired xAxis.
- * @param {string} chosenYAxis yAxis to be used.
+ * @param {Object} anchorRect Anchor Rect.
+ * @param {Object} contentSize Content Size.
+ * @param {string} xAxis Desired xAxis.
+ * @param {string} corner Desired corner.
+ * @param {boolean} sticky Whether or not to stick the popover to the
+ * scroll container edge when part of the anchor
+ * leaves view.
+ * @param {string} chosenYAxis yAxis to be used.
*
* @return {Object} Popover xAxis position and constraints.
*/
-export function computePopoverXAxisPosition( anchorRect, contentSize, xAxis, chosenYAxis ) {
+export function computePopoverXAxisPosition( anchorRect, contentSize, xAxis, corner, sticky, chosenYAxis ) {
const { width } = contentSize;
+ const isRTL = document.documentElement.dir === 'rtl';
+
// Correct xAxis for RTL support
- if ( xAxis === 'left' && isRTL() ) {
+ if ( xAxis === 'left' && isRTL ) {
xAxis = 'right';
- } else if ( xAxis === 'right' && isRTL() ) {
+ } else if ( xAxis === 'right' && isRTL ) {
xAxis = 'left';
}
+ if ( corner === 'left' && isRTL ) {
+ corner = 'right';
+ } else if ( corner === 'right' && isRTL ) {
+ corner = 'left';
+ }
+
// x axis alignment choices
const anchorMidPoint = Math.round( anchorRect.left + ( anchorRect.width / 2 ) );
const centerAlignment = {
@@ -33,30 +48,48 @@ export function computePopoverXAxisPosition( anchorRect, contentSize, xAxis, cho
( anchorMidPoint + ( width / 2 ) > window.innerWidth ? window.innerWidth - anchorMidPoint : ( width / 2 ) )
),
};
- const leftAlignmentX = chosenYAxis === 'middle' ? anchorRect.left : anchorMidPoint;
+
+ let leftAlignmentX = anchorRect.left;
+
+ if ( corner === 'right' ) {
+ leftAlignmentX = anchorRect.right;
+ } else if ( chosenYAxis !== 'middle' ) {
+ leftAlignmentX = anchorMidPoint;
+ }
+
+ let rightAlignmentX = anchorRect.right;
+
+ if ( corner === 'left' ) {
+ rightAlignmentX = anchorRect.left;
+ } else if ( chosenYAxis !== 'middle' ) {
+ rightAlignmentX = anchorMidPoint;
+ }
+
const leftAlignment = {
popoverLeft: leftAlignmentX,
contentWidth: leftAlignmentX - width > 0 ? width : leftAlignmentX,
};
- const rightAlignmentX = chosenYAxis === 'middle' ? anchorRect.right : anchorMidPoint;
const rightAlignment = {
popoverLeft: rightAlignmentX,
contentWidth: rightAlignmentX + width > window.innerWidth ? window.innerWidth - rightAlignmentX : width,
};
// Choosing the x axis
- let chosenXAxis;
+ let chosenXAxis = xAxis;
let contentWidth = null;
- if ( xAxis === 'center' && centerAlignment.contentWidth === width ) {
- chosenXAxis = 'center';
- } else if ( xAxis === 'left' && leftAlignment.contentWidth === width ) {
- chosenXAxis = 'left';
- } else if ( xAxis === 'right' && rightAlignment.contentWidth === width ) {
- chosenXAxis = 'right';
- } else {
- chosenXAxis = leftAlignment.contentWidth > rightAlignment.contentWidth ? 'left' : 'right';
- const chosenWidth = chosenXAxis === 'left' ? leftAlignment.contentWidth : rightAlignment.contentWidth;
- contentWidth = chosenWidth !== width ? chosenWidth : null;
+
+ if ( ! sticky ) {
+ if ( xAxis === 'center' && centerAlignment.contentWidth === width ) {
+ chosenXAxis = 'center';
+ } else if ( xAxis === 'left' && leftAlignment.contentWidth === width ) {
+ chosenXAxis = 'left';
+ } else if ( xAxis === 'right' && rightAlignment.contentWidth === width ) {
+ chosenXAxis = 'right';
+ } else {
+ chosenXAxis = leftAlignment.contentWidth > rightAlignment.contentWidth ? 'left' : 'right';
+ const chosenWidth = chosenXAxis === 'left' ? leftAlignment.contentWidth : rightAlignment.contentWidth;
+ contentWidth = chosenWidth !== width ? chosenWidth : null;
+ }
}
let popoverLeft;
@@ -78,17 +111,55 @@ export function computePopoverXAxisPosition( anchorRect, contentSize, xAxis, cho
/**
* Utility used to compute the popover position over the yAxis
*
- * @param {Object} anchorRect Anchor Rect.
- * @param {Object} contentSize Content Size.
- * @param {string} yAxis Desired yAxis.
+ * @param {Object} anchorRect Anchor Rect.
+ * @param {Object} contentSize Content Size.
+ * @param {string} yAxis Desired yAxis.
+ * @param {string} corner Desired corner.
+ * @param {boolean} sticky Whether or not to stick the popover to the
+ * scroll container edge when part of the anchor
+ * leaves view.
+ * @param {Element} anchorRef The anchor element.
*
* @return {Object} Popover xAxis position and constraints.
*/
-export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis ) {
+export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis, corner, sticky, anchorRef ) {
const { height } = contentSize;
+ if ( sticky ) {
+ let topEl = anchorRef;
+ let bottomEl = anchorRef;
+
+ if ( typeof sticky === 'string' ) {
+ const elements = document.querySelectorAll( sticky );
+
+ if ( elements.length ) {
+ topEl = elements[ 0 ];
+ bottomEl = elements[ elements.length - 1 ];
+ }
+ }
+
+ const scrollContainerEl = getScrollContainer( topEl ) || document.body;
+ const scrollRect = scrollContainerEl.getBoundingClientRect();
+ const topRect = topEl.getBoundingClientRect();
+ const bottomRect = bottomEl.getBoundingClientRect();
+
+ if ( topRect.top - height <= scrollRect.top ) {
+ return {
+ yAxis,
+ popoverTop: Math.min( bottomRect.bottom, scrollRect.top + height ),
+ };
+ }
+ }
+
// y axis alignment choices
- const anchorMidPoint = anchorRect.top + ( anchorRect.height / 2 );
+ let anchorMidPoint = anchorRect.top + ( anchorRect.height / 2 );
+
+ if ( corner === 'bottom' ) {
+ anchorMidPoint = anchorRect.bottom;
+ } else if ( corner === 'top' ) {
+ anchorMidPoint = anchorRect.top;
+ }
+
const middleAlignment = {
popoverTop: anchorMidPoint,
contentHeight: (
@@ -96,6 +167,7 @@ export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis ) {
( anchorMidPoint + ( height / 2 ) > window.innerHeight ? window.innerHeight - anchorMidPoint : ( height / 2 ) )
),
};
+
const topAlignment = {
popoverTop: anchorRect.top,
contentHeight: anchorRect.top - HEIGHT_OFFSET - height > 0 ? height : anchorRect.top - HEIGHT_OFFSET,
@@ -106,18 +178,21 @@ export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis ) {
};
// Choosing the y axis
- let chosenYAxis;
+ let chosenYAxis = yAxis;
let contentHeight = null;
- if ( yAxis === 'middle' && middleAlignment.contentHeight === height ) {
- chosenYAxis = 'middle';
- } else if ( yAxis === 'top' && topAlignment.contentHeight === height ) {
- chosenYAxis = 'top';
- } else if ( yAxis === 'bottom' && bottomAlignment.contentHeight === height ) {
- chosenYAxis = 'bottom';
- } else {
- chosenYAxis = topAlignment.contentHeight > bottomAlignment.contentHeight ? 'top' : 'bottom';
- const chosenHeight = chosenYAxis === 'top' ? topAlignment.contentHeight : bottomAlignment.contentHeight;
- contentHeight = chosenHeight !== height ? chosenHeight : null;
+
+ if ( ! sticky ) {
+ if ( yAxis === 'middle' && middleAlignment.contentHeight === height ) {
+ chosenYAxis = 'middle';
+ } else if ( yAxis === 'top' && topAlignment.contentHeight === height ) {
+ chosenYAxis = 'top';
+ } else if ( yAxis === 'bottom' && bottomAlignment.contentHeight === height ) {
+ chosenYAxis = 'bottom';
+ } else {
+ chosenYAxis = topAlignment.contentHeight > bottomAlignment.contentHeight ? 'top' : 'bottom';
+ const chosenHeight = chosenYAxis === 'top' ? topAlignment.contentHeight : bottomAlignment.contentHeight;
+ contentHeight = chosenHeight !== height ? chosenHeight : null;
+ }
}
let popoverTop;
@@ -140,17 +215,21 @@ export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis ) {
* Utility used to compute the popover position and the content max width/height for a popover
* given its anchor rect and its content size.
*
- * @param {Object} anchorRect Anchor Rect.
- * @param {Object} contentSize Content Size.
- * @param {string} position Position.
+ * @param {Object} anchorRect Anchor Rect.
+ * @param {Object} contentSize Content Size.
+ * @param {string} position Position.
+ * @param {boolean} sticky Whether or not to stick the popover to the
+ * scroll container edge when part of the anchor
+ * leaves view.
+ * @param {Element} anchorRef The anchor element.
*
* @return {Object} Popover position and constraints.
*/
-export function computePopoverPosition( anchorRect, contentSize, position = 'top' ) {
- const [ yAxis, xAxis = 'center' ] = position.split( ' ' );
+export function computePopoverPosition( anchorRect, contentSize, position = 'top', sticky, anchorRef ) {
+ const [ yAxis, xAxis = 'center', corner ] = position.split( ' ' );
- const yAxisPosition = computePopoverYAxisPosition( anchorRect, contentSize, yAxis );
- const xAxisPosition = computePopoverXAxisPosition( anchorRect, contentSize, xAxis, yAxisPosition.yAxis );
+ const yAxisPosition = computePopoverYAxisPosition( anchorRect, contentSize, yAxis, corner, sticky, anchorRef );
+ const xAxisPosition = computePopoverXAxisPosition( anchorRect, contentSize, xAxis, corner, sticky, yAxisPosition.yAxis );
return {
...xAxisPosition,
diff --git a/packages/e2e-test-utils/src/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js
index 5c105363ca74b..2bf0b5d559a35 100644
--- a/packages/e2e-test-utils/src/transform-block-to.js
+++ b/packages/e2e-test-utils/src/transform-block-to.js
@@ -1,20 +1,13 @@
-/**
- * Internal dependencies
- */
-import { pressKeyWithModifier } from './press-key-with-modifier';
-
/**
* Converts editor's block type.
*
* @param {string} name Block name.
*/
export async function transformBlockTo( name ) {
- // Transition to block toolbar by key combination.
- await pressKeyWithModifier( 'alt', 'F10' );
-
- // Press Enter in the focused toggle button.
- const switcherToggle = await page.waitForSelector( '.block-editor-block-switcher__toggle:focus' );
- await switcherToggle.press( 'Enter' );
+ await page.mouse.move( 0, 0 );
+ await page.mouse.move( 10, 10 );
+ const switcherToggle = await page.waitForSelector( '.block-editor-block-switcher__toggle' );
+ await switcherToggle.click();
// Find the block button option within the switcher popover.
const switcher = await page.$( '.block-editor-block-switcher__container' );
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 2dc5dc1f6cc54..77fe92cbb0db3 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
@@ -24,7 +24,7 @@ describe( 'Navigating the block hierarchy', () => {
await page.click( '[aria-label="Two columns; equal split"]' );
// Add a paragraph in the first column.
- await pressKeyTimes( 'Tab', 3 ); // Tab to inserter.
+ 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.
@@ -51,7 +51,7 @@ describe( 'Navigating the block hierarchy', () => {
await lastColumnsBlockMenuItem.click();
// Insert text in the last column block.
- await pressKeyTimes( 'Tab', 3 ); // Tab to inserter.
+ 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.
@@ -66,7 +66,7 @@ describe( 'Navigating the block hierarchy', () => {
await page.click( '[aria-label="Two columns; equal split"]' );
// Add a paragraph in the first column.
- await pressKeyTimes( 'Tab', 3 ); // Tab to inserter.
+ 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.
@@ -94,7 +94,7 @@ describe( 'Navigating the block hierarchy', () => {
await page.waitForSelector( '.is-selected[data-type="core/column"]' );
// Insert text in the last column block
- await pressKeyTimes( 'Tab', 3 ); // Tab to inserter.
+ 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.
diff --git a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js
index 5135f5cd0dd68..4543f0efd374d 100644
--- a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js
+++ b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js
@@ -23,15 +23,15 @@ const navigateToContentEditorTop = async () => {
};
const tabThroughParagraphBlock = async ( paragraphText ) => {
+ await tabThroughBlockMoverControl();
+ await tabThroughBlockToolbar();
+
await page.keyboard.press( 'Tab' );
await expect( await getActiveLabel() ).toBe( 'Block: Paragraph' );
await page.keyboard.press( 'Tab' );
await expect( await getActiveLabel() ).toBe( 'Add block' );
- await tabThroughBlockMoverControl();
- await tabThroughBlockToolbar();
-
await page.keyboard.press( 'Tab' );
await expect( await getActiveLabel() ).toBe( 'Paragraph block' );
await expect( await page.evaluate( () =>
diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js
index dcbb360ee3cc1..34fc6fd96928d 100644
--- a/packages/edit-post/src/components/layout/index.js
+++ b/packages/edit-post/src/components/layout/index.js
@@ -104,6 +104,7 @@ function Layout() {
content={
<>
+
{ ( mode === 'text' || ! isRichEditingEnabled ) &&
}
{ isRichEditingEnabled && mode === 'visual' &&
}