Skip to content

Commit

Permalink
Simplify wide dialog positioning hook and adjust styling
Browse files Browse the repository at this point in the history
  • Loading branch information
stokesman committed Jul 6, 2021
1 parent 7699075 commit c3e54b2
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 87 deletions.
4 changes: 2 additions & 2 deletions packages/widgets/src/blocks/legacy-widget/edit/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { closeSmall } from '@wordpress/icons';
* Internal dependencies
*/
import Control from './control';
import { useBudgeYAxisBy } from './utils';
import { useBudgeTopBy } from './utils';

export default function Form( {
title,
Expand Down Expand Up @@ -131,7 +131,7 @@ function WideFormDialog( { isVisible, children } ) {
const {
ref: budgeRef,
resizeObserver,
} = useBudgeYAxisBy( containerRef.current, { isEnabled: isVisible } );
} = useBudgeTopBy( containerRef.current, { isEnabled: isVisible } );
return (
<div
ref={ containerRef }
Expand Down
102 changes: 30 additions & 72 deletions packages/widgets/src/blocks/legacy-widget/edit/utils.js
Original file line number Diff line number Diff line change
@@ -1,133 +1,92 @@
/**
* WordPress dependencies
*/
import {
useRef,
useEffect,
useLayoutEffect,
useState,
} from '@wordpress/element';
import { useRef, useLayoutEffect, useState } from '@wordpress/element';
import { useResizeObserver } from '@wordpress/compose';

function getScrollContext( node ) {
let at = node.parentNode;
while ( at && at.clientHeight === at.scrollHeight ) {
at = at.parentNode;
}
return at;
}

/**
* Positions an element by the vertical edges of an objective element if it can
* fit or otherwise as close as possible within the bounds of the objective’s
* scrolling context.
* Positions an element by the top of an objective element if it can fit or
* otherwise as close as possible within the window.
*
* @param {Element} objective Element to take position from.
* @param {Object} $1 Options.
* @param {boolean} $1.isEnabled Whether to apply the hook or not.
*
* @return {Object} A ref and resize observer element.
*/
export function useBudgeYAxisBy( objective, { isEnabled } ) {
export function useBudgeTopBy( objective, { isEnabled } ) {
const subjectRef = useRef();
const objectiveRef = useRef();
const scrollContextRef = useRef();
const [ scrollBounds = {}, setScrollBounds ] = useState();
const [
{ start: boundsStart, end: boundsEnd } = {},
setBounds,
] = useState();
const boundsUpdaterRef = useRef();

const hasSubject = !! subjectRef.current;
const hasObjective = !! objective;
const objectiveHasChanged = objectiveRef.current !== objective;

let scrollContextHasChanged;
if ( objectiveHasChanged ) {
objectiveRef.current = objective;
const scrollContext = hasObjective
? getScrollContext( objective )
: null;
scrollContextHasChanged = scrollContextRef.current !== scrollContext;
scrollContextRef.current = scrollContext;
}
const hasScrollContext = !! scrollContextRef.current;
objectiveRef.current = objective;

// Defines the bounds updating function a single time
if ( ! boundsUpdaterRef.current ) {
boundsUpdaterRef.current = () => {
const rect = scrollContextRef.current.getBoundingClientRect();
subjectRef.current.style.maxHeight = rect.height + 'px';
setScrollBounds( rect );
const { defaultView } = subjectRef.current.ownerDocument;
setBounds( { start: 0, end: defaultView.innerHeight } );
};
}

// Handles window resizes to update scroll bounds
useEffect( () => {
// Sets the bounds height and handles window resizes to update it
useLayoutEffect( () => {
if ( ! hasSubject || ! hasObjective || ! isEnabled ) {
return;
}
boundsUpdaterRef.current();
const { defaultView } = objective.ownerDocument;
const updateBounds = boundsUpdaterRef.current;
defaultView.addEventListener( 'resize', updateBounds );
return () => defaultView.removeEventListener( 'resize', updateBounds );
}, [ hasObjective, hasSubject, isEnabled ] );

// Handles mutations on the scrolling element to update scroll bounds
useLayoutEffect( () => {
if ( ! hasScrollContext || ! isEnabled ) {
return;
}
boundsUpdaterRef.current();
const { MutationObserver } = objective.ownerDocument.defaultView;
const observer = new MutationObserver( boundsUpdaterRef.current );
observer.observe( scrollContextRef.current, { attributes: true } );
return () => observer.disconnect();
}, [ scrollContextHasChanged, isEnabled ] );

const [ resizeObserver, contentSize ] = useResizeObserver();

// Handles scrolling, if needed, to update subject position
useLayoutEffect( () => {
if (
! isEnabled ||
! hasSubject ||
! hasObjective ||
! hasScrollContext
) {
if ( ! isEnabled || ! hasSubject || ! hasObjective ) {
return;
}
// The subject fills the bounds so scroll handling is not needed.
// Positions subject at the top of bounds and returns.
if ( contentSize.height >= scrollBounds.height ) {
subjectRef.current.style.top = scrollBounds.top + 'px';
if ( contentSize.height >= boundsEnd ) {
// subjectRef.current.style.top = '0px';
subjectRef.current.style.setProperty( '--budge-top', '0px' );
return;
}
const layout = () => {
const objectiveRect = objectiveRef.current.getBoundingClientRect();
let { top } = objectiveRect;
const height = contentSize.height;
const bottom = top + height;
const { bottom: boundsBottom, top: boundsTop } = scrollBounds;
if ( top < boundsTop || bottom > boundsBottom ) {
const fromTop = Math.abs( boundsTop - top );
const fromBottom = Math.abs( boundsBottom - bottom );
top = fromTop < fromBottom ? boundsTop : boundsBottom - height;
if ( top < boundsStart || bottom > boundsEnd ) {
const fromTop = Math.abs( boundsStart - top );
const fromBottom = Math.abs( boundsEnd - bottom );
top = fromTop < fromBottom ? boundsStart : boundsEnd - height;
}
subjectRef.current.style.top = top + 'px';
subjectRef.current.style.setProperty( '--budge-top', top + 'px' );
};
layout();
const scrollNode = scrollContextRef.current;
const options = { passive: true };
scrollNode.addEventListener( 'scroll', layout, options );
const { defaultView } = objective.ownerDocument;
const options = { capture: true, passive: true };
defaultView.addEventListener( 'scroll', layout, options );
return () => {
scrollNode.removeEventListener( 'scroll', layout, options );
defaultView.removeEventListener( 'scroll', layout, options );
};
}, [
hasSubject,
hasScrollContext,
objectiveHasChanged,
contentSize.height,
scrollBounds.height,
scrollBounds.top,
scrollBounds.bottom,
boundsStart,
boundsEnd,
isEnabled,
] );

Expand All @@ -136,8 +95,7 @@ export function useBudgeYAxisBy( objective, { isEnabled } ) {
if ( isEnabled && !! subjectRef.current ) {
const subject = subjectRef.current;
return () => {
subject.style.top = '';
subject.style.maxHeight = '';
subject.style.removeProperty( '--budge-top' );
};
}
}, [ isEnabled ] );
Expand Down
48 changes: 35 additions & 13 deletions packages/widgets/src/blocks/legacy-widget/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,37 @@ $wide-header-height: 46px;
background: $white;
border-radius: $radius-block-ui;
border: 1px solid $gray-900;
padding: $grid-unit-15 - 1px; //Subtract the border width.

&.is-wide {
flex-direction: column;
position: fixed;
top: 0;
top: max(#{$wide-header-height}, var(--budge-top, 0));
left: 299px;
min-width: 300px;
width: calc(100vw - 299px);
max-width: 660px;
max-height: 100%;
max-height: calc(100% - #{$wide-header-height});
padding: 0;
background: none;
border-radius: 0;
border: 1px solid #dcdcde;
border: none;

// Include the top and bottom border in the resizeObserver’s height.
> iframe {
margin: -1px 0;
height: calc(100% + 2px) !important;
@media (min-width: 641px) {
// The customizer sidebar has a footer and the bottom of the form
// is offset from it.
padding-bottom: $wide-header-height;
// Allows pointer interaction to pass through.
pointer-events: none;

// Restores pointer interaction for the content.
.wp-block-legacy-widget__edit-form-header,
.wp-block-legacy-widget__edit-form-body {
pointer-events: auto;
}
}

.wp-block-legacy-widget__edit-form-header {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 1;
Expand All @@ -45,22 +52,33 @@ $wide-header-height: 46px;
background: #f0f0f1;
height: $wide-header-height;
padding: 0 5px 0 21px;
border-bottom: 1px solid #dcdcde;
margin: 0;
border: 1px solid #dcdcde;
}

.wp-block-legacy-widget__edit-form-body {
overflow: auto;
height: 100%;
// Add the header height to the top and subtract the border width from horizontal sides.
padding: ($wide-header-height + $grid-unit-15) ($grid-unit-15 - 1px) $grid-unit-15;
margin-top: $wide-header-height;
border: 1px solid #dcdcde;
border-top-width: 0;
background: #fff;
}
}

.wp-block-legacy-widget__edit-form-header {
margin: 0 0 $grid-unit-15 0;
padding: $grid-unit-20 21px 0;
}
.wp-block-legacy-widget__edit-form-body {
padding: 0 $grid-unit-15 $grid-unit-05;
}

.wp-block-legacy-widget__edit-form-title {
color: $black;
font-family: $default-font;
font-size: 14px;
font-weight: 600;
margin: 0;
}

.widget-inside.widget-inside {
Expand Down Expand Up @@ -121,6 +139,10 @@ $wide-header-height: 46px;
}
}

.widget {
margin-bottom: 0;
}

// Reset z-index set on https://github.com/WordPress/wordpress-develop/commit/f26d4d37351a55fd1fc5dad0f5fef8f0f964908c.
.widget.open,
.widget.open:focus-within {
Expand Down

0 comments on commit c3e54b2

Please sign in to comment.