import * as React from 'react'; import { useId, useRef, useInsertionEffect } from 'react'; /** * Measurement functionality has to be within a separate component * to leverage snapshot lifecycle. */ class PopChildMeasure extends React.Component { getSnapshotBeforeUpdate(prevProps) { const element = this.props.childRef.current; if (element && prevProps.isPresent && !this.props.isPresent) { const size = this.props.sizeRef.current; size.height = element.offsetHeight || 0; size.width = element.offsetWidth || 0; size.top = element.offsetTop; size.left = element.offsetLeft; } return null; } /** * Required with getSnapshotBeforeUpdate to stop React complaining. */ componentDidUpdate() { } render() { return this.props.children; } } function PopChild({ children, isPresent }) { const id = useId(); const ref = useRef(null); const size = useRef({ width: 0, height: 0, top: 0, left: 0, }); /** * We create and inject a style block so we can apply this explicit * sizing in a non-destructive manner by just deleting the style block. * * We can't apply size via render as the measurement happens * in getSnapshotBeforeUpdate (post-render), likewise if we apply the * styles directly on the DOM node, we might be overwriting * styles set via the style prop. */ useInsertionEffect(() => { const { width, height, top, left } = size.current; if (isPresent || !ref.current || !width || !height) return; ref.current.dataset.motionPopId = id; const style = document.createElement("style"); document.head.appendChild(style); if (style.sheet) { style.sheet.insertRule(` [data-motion-pop-id="${id}"] { position: absolute !important; width: ${width}px !important; height: ${height}px !important; top: ${top}px !important; left: ${left}px !important; } `); } return () => { document.head.removeChild(style); }; }, [isPresent]); return (React.createElement(PopChildMeasure, { isPresent: isPresent, childRef: ref, sizeRef: size }, React.cloneElement(children, { ref }))); } export { PopChild };