-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
43 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,104 +1,103 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useRef, useLayoutEffect, useState } from '@wordpress/element'; | ||
import { useResizeObserver } from '@wordpress/compose'; | ||
import { useRef, useLayoutEffect, useState } from '@wordpress/element'; | ||
|
||
/** | ||
* Positions an element by the top of an objective element if it can fit or | ||
* Positions an element by the top of an anchor element if it can fit or | ||
* otherwise as close as possible within the window. | ||
* | ||
* @param {Element} objective Element to take position from. | ||
* @param {Element} anchor 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 useBudgeTopBy( objective, { isEnabled } ) { | ||
const subjectRef = useRef(); | ||
const objectiveRef = useRef(); | ||
export function useAlignTopWithinViewport( anchor, { isEnabled } ) { | ||
const ref = useRef(); | ||
const anchorRef = useRef(); | ||
const [ | ||
{ start: boundsStart, end: boundsEnd } = {}, | ||
setBounds, | ||
] = useState(); | ||
const boundsUpdaterRef = useRef(); | ||
|
||
const hasSubject = !! subjectRef.current; | ||
const hasObjective = !! objective; | ||
const objectiveHasChanged = objectiveRef.current !== objective; | ||
objectiveRef.current = objective; | ||
const hasRef = !! ref.current; | ||
const hasAnchor = !! anchor; | ||
const anchorHasChanged = anchorRef.current !== anchor; | ||
anchorRef.current = anchor; | ||
|
||
// Defines the bounds updating function a single time | ||
if ( ! boundsUpdaterRef.current ) { | ||
boundsUpdaterRef.current = () => { | ||
const { defaultView } = subjectRef.current.ownerDocument; | ||
const { defaultView } = ref.current.ownerDocument; | ||
setBounds( { start: 0, end: defaultView.innerHeight } ); | ||
}; | ||
} | ||
|
||
// Sets the bounds height and handles window resizes to update it | ||
useLayoutEffect( () => { | ||
if ( ! hasSubject || ! hasObjective || ! isEnabled ) { | ||
if ( ! hasRef || ! isEnabled ) { | ||
return; | ||
} | ||
boundsUpdaterRef.current(); | ||
const { defaultView } = objective.ownerDocument; | ||
const { defaultView } = ref.current.ownerDocument; | ||
const updateBounds = boundsUpdaterRef.current; | ||
defaultView.addEventListener( 'resize', updateBounds ); | ||
return () => defaultView.removeEventListener( 'resize', updateBounds ); | ||
}, [ hasObjective, hasSubject, isEnabled ] ); | ||
}, [ hasRef, isEnabled ] ); | ||
|
||
const [ resizeObserver, contentSize ] = useResizeObserver(); | ||
|
||
// Handles scrolling, if needed, to update subject position | ||
// Handles scrolling, if needed, to update positioning | ||
useLayoutEffect( () => { | ||
if ( ! isEnabled || ! hasSubject || ! hasObjective ) { | ||
if ( ! isEnabled || ! hasRef || ! hasAnchor ) { | ||
return; | ||
} | ||
// The subject fills the bounds so scroll handling is not needed. | ||
// Positions subject at the top of bounds and returns. | ||
// The element fills the bounds so scroll handling is not needed. | ||
// Positions it at the top of bounds and returns. | ||
if ( contentSize.height >= boundsEnd ) { | ||
// subjectRef.current.style.top = '0px'; | ||
subjectRef.current.style.setProperty( '--budge-top', '0px' ); | ||
ref.current.style.setProperty( '--align-top', '0px' ); | ||
return; | ||
} | ||
const layout = () => { | ||
const objectiveRect = objectiveRef.current.getBoundingClientRect(); | ||
let { top } = objectiveRect; | ||
const anchorRect = anchorRef.current.getBoundingClientRect(); | ||
let { top } = anchorRect; | ||
const height = contentSize.height; | ||
const bottom = top + 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.setProperty( '--budge-top', top + 'px' ); | ||
ref.current.style.setProperty( '--align-top', top + 'px' ); | ||
}; | ||
layout(); | ||
const { defaultView } = objective.ownerDocument; | ||
const { defaultView } = anchor.ownerDocument; | ||
const options = { capture: true, passive: true }; | ||
defaultView.addEventListener( 'scroll', layout, options ); | ||
return () => { | ||
defaultView.removeEventListener( 'scroll', layout, options ); | ||
}; | ||
}, [ | ||
hasSubject, | ||
objectiveHasChanged, | ||
hasRef, | ||
anchorHasChanged, | ||
contentSize.height, | ||
boundsStart, | ||
boundsEnd, | ||
isEnabled, | ||
] ); | ||
|
||
// Cleans up subject styles when not enabled | ||
// Cleans up styles when not enabled | ||
useLayoutEffect( () => { | ||
if ( isEnabled && !! subjectRef.current ) { | ||
const subject = subjectRef.current; | ||
if ( isEnabled && !! ref.current ) { | ||
const element = ref.current; | ||
return () => { | ||
subject.style.removeProperty( '--budge-top' ); | ||
element.style.removeProperty( '--align-top' ); | ||
}; | ||
} | ||
}, [ isEnabled ] ); | ||
|
||
return { ref: subjectRef, resizeObserver }; | ||
return { ref, resizeObserver }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters