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

Try stickier block toolbars in Customize Widgets #32490

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 0 additions & 3 deletions packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,6 @@ $z-layers: (

// Appear under the customizer heading UI, but over anything else.
".customize-widgets__topbar": 8,

// Appear under the topbar.
".customize-widgets__block-toolbar": 7,
);

@function z-index( $key ) {
Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ _Parameters_
- _$0_ `Object`: Props.
- _$0.children_ `Object`: The block content and style container.
- _$0.\_\_unstableContentRef_ `Object`: Ref holding the content scroll container.
- _$0.\_\_experimentalStickyTop_ `number`: Top sticky position offset of floating and top toolbar.
- _$0.\_\_experimentalStickier_ `boolean`: Favor sticky position even if the block is out of view.

<a name="BlockVerticalAlignmentControl" href="#BlockVerticalAlignmentControl">#</a> **BlockVerticalAlignmentControl**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ import NavigableToolbar from '../navigable-toolbar';
import BlockToolbar from '../block-toolbar';
import { store as blockEditorStore } from '../../store';

function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
function BlockContextualToolbar( {
focusOnMount,
isFixed,
__experimentalStickyTop,
style = {},
...props
} ) {
const { blockType, hasParents, showParentSelector } = useSelect(
( select ) => {
const {
Expand Down Expand Up @@ -59,12 +65,17 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
'is-fixed': isFixed,
} );

if ( __experimentalStickyTop ) {
style.top = __experimentalStickyTop;
}

return (
<NavigableToolbar
focusOnMount={ focusOnMount }
className={ classes }
/* translators: accessibility text for the block toolbar */
aria-label={ __( 'Block tools' ) }
style={ style }
{ ...props }
>
<BlockToolbar hideDragHandle={ isFixed } />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ function BlockPopover( {
capturingClientId,
__unstablePopoverSlot,
__unstableContentRef,
__experimentalStickyTop,
__experimentalStickier,
} ) {
const {
isNavigationMode,
Expand Down Expand Up @@ -213,6 +215,8 @@ function BlockPopover( {
// Observe movement for block animations (especially horizontal).
__unstableObserveElement={ node }
shouldAnchorIncludePadding
__experimentalStickyTop={ __experimentalStickyTop }
__experimentalStickier={ __experimentalStickier }
>
{ ( shouldShowContextualToolbar || isToolbarForced ) && (
<div
Expand Down Expand Up @@ -324,6 +328,8 @@ function wrapperSelector( select ) {
export default function WrappedBlockPopover( {
__unstablePopoverSlot,
__unstableContentRef,
__experimentalStickyTop,
__experimentalStickier,
} ) {
const selected = useSelect( wrapperSelector, [] );

Expand Down Expand Up @@ -353,6 +359,8 @@ export default function WrappedBlockPopover( {
capturingClientId={ capturingClientId }
__unstablePopoverSlot={ __unstablePopoverSlot }
__unstableContentRef={ __unstableContentRef }
__experimentalStickyTop={ __experimentalStickyTop }
__experimentalStickier={ __experimentalStickier }
/>
);
}
28 changes: 22 additions & 6 deletions packages/block-editor/src/components/block-tools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,20 @@ import { usePopoverScroll } from './use-popover-scroll';
* insertion point and a slot for the inline rich text toolbar). Must be wrapped
* around the block content and editor styles wrapper or iframe.
*
* @param {Object} $0 Props.
* @param {Object} $0.children The block content and style container.
* @param {Object} $0.__unstableContentRef Ref holding the content scroll container.
* @param {Object} $0 Props.
* @param {Object} $0.children The block content and style container.
* @param {Object} $0.__unstableContentRef Ref holding the content scroll container.
* @param {number} $0.__experimentalStickyTop Top sticky position offset of floating and
* top toolbar.
* @param {boolean} $0.__experimentalStickier Favor sticky position even if the block is
out of view.
*/
export default function BlockTools( { children, __unstableContentRef } ) {
export default function BlockTools( {
children,
__unstableContentRef,
__experimentalStickyTop,
__experimentalStickier,
} ) {
const isLargeViewport = useViewportMatch( 'medium' );
const hasFixedToolbar = useSelect(
( select ) => select( blockEditorStore ).getSettings().hasFixedToolbar,
Expand All @@ -33,11 +42,18 @@ export default function BlockTools( { children, __unstableContentRef } ) {
return (
<InsertionPoint __unstableContentRef={ __unstableContentRef }>
{ ( hasFixedToolbar || ! isLargeViewport ) && (
<BlockContextualToolbar isFixed />
<BlockContextualToolbar
isFixed
__experimentalStickyTop={ __experimentalStickyTop }
/>
) }
{ /* Even if the toolbar is fixed, the block popover is still
needed for navigation mode. */ }
<BlockPopover __unstableContentRef={ __unstableContentRef } />
<BlockPopover
__unstableContentRef={ __unstableContentRef }
__experimentalStickyTop={ __experimentalStickyTop }
__experimentalStickier={ __experimentalStickier }
/>
{ /* Used for the inline rich text toolbar. */ }
<Popover.Slot
name="block-toolbar"
Expand Down
14 changes: 14 additions & 0 deletions packages/block-editor/src/components/block-tools/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,20 @@
}
}

// The data-away attribute is used to hide the toolbar when its block is out of view.
&[data-away] {
transition: opacity 0.1s linear;

&[data-away="true"] {
opacity: 0;

// Allow clicking through the transparent toolbar
.block-editor-block-contextual-toolbar {
pointer-events: none;
}
}
}

// Hide the block toolbar if the insertion point is shown.
&.is-insertion-point-visible {
visibility: hidden;
Expand Down
53 changes: 52 additions & 1 deletion packages/components/src/popover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ const Popover = (
__unstableBoundaryParent,
__unstableForcePosition,
__unstableForceXAlignment,
__experimentalStickyTop,
__experimentalStickier,
/* eslint-enable no-unused-vars */
...contentProps
},
Expand Down Expand Up @@ -351,7 +353,9 @@ const Popover = (
relativeOffsetTop,
boundaryElement,
__unstableForcePosition,
__unstableForceXAlignment
__unstableForceXAlignment,
__experimentalStickyTop,
__experimentalStickier
);

if (
Expand Down Expand Up @@ -444,6 +448,47 @@ const Popover = (
observer.observe( __unstableObserveElement, { attributes: true } );
}

let inViewObserver;

// When stickier, observes the anchor’s intersection with the sticky
// boundary element and sets attributes for styling.
if ( __experimentalStickier ) {
const isAnchorCompound = anchorRef.constructor === Object;
inViewObserver = new defaultView.IntersectionObserver(
( [ entry ] ) => {
let isInView;
// If the anchor is singular, it is in view if the entry
// reports it so. Otherwise, there are two elements to
// consider and unless both are outside the same side of
// the bounds the anchor is still in view.
if ( ! isAnchorCompound ) {
isInView = entry.isIntersecting;
} else {
const rect = entry.boundingClientRect;
isInView =
entry.target === anchorRef.top
? rect.top < entry.rootBounds.bottom
: rect.bottom > entry.rootBounds.top;
}
setAttribute(
containerRef.current,
'data-away',
! isInView ? 'true' : 'false'
);
},
{
root: __unstableStickyBoundaryElement,
rootMargin: `-${ __experimentalStickyTop }px 0px 0px 0px`,
}
);
if ( isAnchorCompound ) {
inViewObserver.observe( anchorRef.top );
inViewObserver.observe( anchorRef.bottom );
} else {
inViewObserver.observe( anchorRef );
}
}

return () => {
defaultView.clearInterval( intervalHandle );
defaultView.removeEventListener( 'resize', refresh );
Expand All @@ -466,6 +511,10 @@ const Popover = (
if ( observer ) {
observer.disconnect();
}

// Stickier related cleanup
inViewObserver?.disconnect();
setAttribute( containerRef.current, 'data-away' );
};
}, [
isExpanded,
Expand All @@ -476,6 +525,8 @@ const Popover = (
position,
contentSize,
__unstableStickyBoundaryElement,
__experimentalStickyTop,
__experimentalStickier,
__unstableObserveElement,
__unstableBoundaryParent,
] );
Expand Down
39 changes: 30 additions & 9 deletions packages/components/src/popover/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,13 @@ export function computePopoverXAxisPosition(
* switching between sticky and normal
* position.
* @param {Element} anchorRef The anchor element.
* @param {Element} relativeOffsetTop If applicable, top offset of the
* @param {number} relativeOffsetTop If applicable, top offset of the
* relative positioned parent container.
* @param {boolean} forcePosition Don't adjust position based on anchor.
* @param {number} stickyTop Sticky top position offset from the
* boundaryElement.
* @param {boolean} stickier Favor sticky position even if the
* anchor is out of view.
*
* @return {Object} Popover xAxis position and constraints.
*/
Expand All @@ -180,18 +184,27 @@ export function computePopoverYAxisPosition(
stickyBoundaryElement,
anchorRef,
relativeOffsetTop,
forcePosition
forcePosition,
stickyTop,
stickier
) {
const { height } = contentSize;

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

if ( anchorRect.top <= stickyPosition ) {
const top = stickyTop + stickyRect.top + height - relativeOffsetTop;
const bottom = stickyRect.bottom - relativeOffsetTop;
if ( anchorRect.top < top ) {
const end = stickier ? Infinity : anchorRect.bottom;
return {
yAxis,
popoverTop: Math.min( anchorRect.bottom, stickyPosition ),
popoverTop: Math.min( end, top ),
};
} else if ( anchorRect.top > bottom ) {
const start = stickier ? -Infinity : anchorRect.top;
return {
yAxis,
popoverTop: Math.max( start, bottom ),
};
}
}
Expand Down Expand Up @@ -287,7 +300,11 @@ export function computePopoverYAxisPosition(
* 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 {boolean} forceXAlignment Don't adjust alignment based on YAxis.
* @param {number} stickyTop Sticky top position offset from the
* boundaryElement.
* @param {boolean} stickier Favor sticky position even if the
* anchor is out of view.
*
* @return {Object} Popover position and constraints.
*/
Expand All @@ -300,7 +317,9 @@ export function computePopoverPosition(
relativeOffsetTop,
boundaryElement,
forcePosition,
forceXAlignment
forceXAlignment,
stickyTop = 0,
stickier = false
) {
const [ yAxis, xAxis = 'center', corner ] = position.split( ' ' );

Expand All @@ -312,7 +331,9 @@ export function computePopoverPosition(
stickyBoundaryElement,
anchorRef,
relativeOffsetTop,
forcePosition
forcePosition,
stickyTop,
stickier
);
const xAxisPosition = computePopoverXAxisPosition(
anchorRect,
Expand Down
3 changes: 3 additions & 0 deletions packages/customize-widgets/src/components/header/style.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
.customize-widgets-header {
position: sticky;
top: 0;

@include break-medium() {
// Make space for the floating toolbar.
margin-bottom: $grid-unit-60 + $default-block-margin;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ import { store as customizeWidgetsStore } from '../../store';
import WelcomeGuide from '../welcome-guide';
import KeyboardShortcuts from '../keyboard-shortcuts';

// Used to offset the block toolbar’s sticky top
const headerHeight = 49;

export default function SidebarBlockEditor( {
blockEditorSettings,
sidebar,
Expand Down Expand Up @@ -113,7 +116,10 @@ export default function SidebarBlockEditor( {
isFixedToolbarActive={ isFixedToolbarActive }
/>

<BlockTools>
<BlockTools
__experimentalStickyTop={ headerHeight }
__experimentalStickier
>
<BlockSelectionClearer>
<WritingFlow>
<ObserveTyping>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@

// Scroll sideways.
overflow-y: auto;
z-index: z-index(".customize-widgets__block-toolbar");
}

.customize-control-sidebar_block_editor .block-editor-block-list__block-popover {
position: fixed;
z-index: z-index(".customize-widgets__block-toolbar");
}
4 changes: 2 additions & 2 deletions packages/customize-widgets/src/controls/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
// Override the inline styles added via JS that make the section title
// sticky feature work. The customize widget block-editor disables this
// sticky title.
padding-top: 10px !important;
padding-top: 12px !important;

.customize-section-title {
// Disable the sticky title. `!important` as this overrides inline
// styles added via JavaScript.
position: static !important;
top: 0 !important;
width: unset !important;
margin-top: -12px !important;
}
}