Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Toolbar & Popover component - Prevent sticky position from causing permanently obscured areas of the selected block. #33981

Merged
merged 19 commits into from
Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ function BlockPopover( {
// Observe movement for block animations (especially horizontal).
__unstableObserveElement={ node }
shouldAnchorIncludePadding
// Used to safeguard sticky position behavior against cases where it would permanently
// obscure specific sections of a block.
__unstableEditorCanvasWrapper={ __unstableContentRef?.current }
>
{ ( shouldShowContextualToolbar || isToolbarForced ) && (
<div
Expand Down
4 changes: 3 additions & 1 deletion packages/components/src/popover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ const Popover = (
__unstableBoundaryParent,
__unstableForcePosition,
__unstableForceXAlignment,
__unstableEditorCanvasWrapper,
/* eslint-enable no-unused-vars */
...contentProps
},
Expand Down Expand Up @@ -352,7 +353,8 @@ const Popover = (
relativeOffsetTop,
boundaryElement,
__unstableForcePosition,
__unstableForceXAlignment
__unstableForceXAlignment,
__unstableEditorCanvasWrapper
);

if (
Expand Down
99 changes: 65 additions & 34 deletions packages/components/src/popover/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,18 +159,18 @@ export function computePopoverXAxisPosition(
/**
* 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 {string} corner Desired corner.
* @param {boolean} stickyBoundaryElement The boundary element to use when
* switching between sticky and normal
* position.
* @param {Element} anchorRef The anchor element.
* @param {Element} relativeOffsetTop If applicable, top offset of the
* relative positioned parent container.
* @param {boolean} forcePosition Don't adjust position based on anchor.
*
* @param {Object} anchorRect Anchor Rect.
* @param {Object} contentSize Content Size.
* @param {string} yAxis Desired yAxis.
* @param {string} corner Desired corner.
* @param {boolean} stickyBoundaryElement The boundary element to use when switching between sticky
* and normal position.
* @param {Element} anchorRef The anchor element.
* @param {Element} relativeOffsetTop If applicable, top offset of the relative positioned
* parent container.
* @param {boolean} forcePosition Don't adjust position based on anchor.
* @param {Element|null} editorWrapper Element that wraps the editor content. Used to access
* scroll position to determine sticky behavior.
* @return {Object} Popover xAxis position and constraints.
*/
export function computePopoverYAxisPosition(
Expand All @@ -181,18 +181,47 @@ export function computePopoverYAxisPosition(
stickyBoundaryElement,
anchorRef,
relativeOffsetTop,
forcePosition
forcePosition,
editorWrapper
) {
const { height } = contentSize;

if ( stickyBoundaryElement ) {
const stickyRect = stickyBoundaryElement.getBoundingClientRect();
const stickyPosition = stickyRect.top + height - relativeOffsetTop;

if ( anchorRect.top <= stickyPosition ) {
const stickyPositionTop = stickyRect.top + height - relativeOffsetTop;
const stickyPositionBottom =
stickyRect.bottom - height - relativeOffsetTop;
Copons marked this conversation as resolved.
Show resolved Hide resolved

if ( anchorRect.top <= stickyPositionTop ) {
if ( editorWrapper ) {
// If a popover cannot be positioned above the anchor, even after scrolling, we must
// ensure we use the bottom position instead of the popover slot. This prevents the
// popover from always restricting block content and interaction while selected if the
// block is near the top of the site editor.

const isRoomAboveInCanvas =
height + HEIGHT_OFFSET <
editorWrapper.scrollTop + anchorRect.top;
if ( ! isRoomAboveInCanvas ) {
return {
yAxis: 'bottom',
// If the bottom of the block is also below the bottom sticky position (ex -
// block is also taller than the editor window), return the bottom sticky
// position instead. We do this instead of the top sticky position both to
// allow a smooth transition and more importantly to ensure every section of
// the block can be free from popover obscuration at some point in the
// scroll position.
popoverTop: Math.min(
anchorRect.bottom,
stickyPositionBottom
),
};
}
}
// Default sticky behavior.
return {
yAxis,
popoverTop: Math.min( anchorRect.bottom, stickyPosition ),
popoverTop: Math.min( anchorRect.bottom, stickyPositionTop ),
};
}
}
Expand Down Expand Up @@ -274,22 +303,22 @@ export function computePopoverYAxisPosition(
}

/**
* 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 {boolean} stickyBoundaryElement The boundary element to use when
* switching between sticky and normal
* position.
* @param {Element} anchorRef The anchor element.
* @param {number} relativeOffsetTop If applicable, top offset of the
* relative positioned parent container.
* @param {Element} boundaryElement Boundary element.
* @param {boolean} forcePosition Don't adjust position based on anchor.
* @param {boolean} forceXAlignment Don't adjust alignment based on 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 {boolean} stickyBoundaryElement The boundary element to use when switching between
* sticky and normal position.
* @param {Element} anchorRef The anchor element.
* @param {number} relativeOffsetTop If applicable, top offset of the relative positioned
* parent container.
* @param {Element} boundaryElement Boundary element.
* @param {boolean} forcePosition Don't adjust position based on anchor.
* @param {boolean} forceXAlignment Don't adjust alignment based on YAxis
* @param {Element|null} editorWrapper Element that wraps the editor content. Used to access
* scroll position to determine sticky behavior.
* @return {Object} Popover position and constraints.
*/
export function computePopoverPosition(
Expand All @@ -301,7 +330,8 @@ export function computePopoverPosition(
relativeOffsetTop,
boundaryElement,
forcePosition,
forceXAlignment
forceXAlignment,
editorWrapper
) {
const [ yAxis, xAxis = 'center', corner ] = position.split( ' ' );

Expand All @@ -313,7 +343,8 @@ export function computePopoverPosition(
stickyBoundaryElement,
anchorRef,
relativeOffsetTop,
forcePosition
forcePosition,
editorWrapper
);
const xAxisPosition = computePopoverXAxisPosition(
anchorRect,
Expand Down