UNPKG

@wordpress/components

Version:
223 lines (194 loc) 5.82 kB
/** * External dependencies */ import { noop } from 'lodash'; import useResizeAware from 'react-resize-aware'; /** * WordPress dependencies */ import { useEffect, useRef, useState } from '@wordpress/element'; const { clearTimeout, setTimeout } = typeof window !== 'undefined' ? window : {}; export const POSITIONS = { bottom: 'bottom', corner: 'corner', }; /** * @typedef {Object} UseResizeLabelProps * * @property {undefined|string} label The label value. * @property {Function} resizeListener Element to be rendered for resize listening events. */ /** * Custom hook that manages resize listener events. It also provides a label * based on current resize width x height values. * * @param {Object} props * @param {string} props.axis Only shows the label corresponding to the axis. * @param {number} props.fadeTimeout Duration (ms) before deactivating the resize label. * @param {boolean} props.onResize Callback when a resize occurs. Provides { width, height } callback. * @param {string} props.position Adjusts label value. * @param {boolean} props.showPx Whether to add `PX` to the label. * * @return {UseResizeLabelProps} Properties for hook. */ export function useResizeLabel( { axis, fadeTimeout = 180, onResize = noop, position = POSITIONS.bottom, showPx = false, } ) { /* * The width/height values derive from this special useResizeAware hook. * This custom hook uses injects an iFrame into the element, allowing it * to tap into the onResize (window) callback events. */ const [ resizeListener, sizes ] = useResizeAware(); /* * Indicates if the x/y axis is preferred. * If set, we will avoid resetting the moveX and moveY values. * This will allow for the preferred axis values to persist in the label. */ const isAxisControlled = !! axis; /* * The moveX and moveY values are used to track whether the label should * display width, height, or width x height. */ const [ moveX, setMoveX ] = useState( false ); const [ moveY, setMoveY ] = useState( false ); /* * Cached dimension values to check for width/height updates from the * sizes property from useResizeAware() */ const { width, height } = sizes; const heightRef = useRef( height ); const widthRef = useRef( width ); /* * This timeout is used with setMoveX and setMoveY to determine of * both width and height values have changed at (roughly) the same time. */ const moveTimeoutRef = useRef(); const unsetMoveXY = () => { /* * If axis is controlled, we will avoid resetting the moveX and moveY values. * This will allow for the preferred axis values to persist in the label. */ if ( isAxisControlled ) return; setMoveX( false ); setMoveY( false ); }; const debounceUnsetMoveXY = () => { if ( moveTimeoutRef.current ) { clearTimeout( moveTimeoutRef.current ); } moveTimeoutRef.current = setTimeout( unsetMoveXY, fadeTimeout ); }; useEffect( () => { /* * On the initial render of useResizeAware, the height and width values are * null. They are calculated then set using via an internal useEffect hook. */ const isRendered = width !== null || height !== null; if ( ! isRendered ) return; const didWidthChange = width !== widthRef.current; const didHeightChange = height !== heightRef.current; if ( ! didWidthChange && ! didHeightChange ) return; /* * After the initial render, the useResizeAware will set the first * width and height values. We'll sync those values with our * width and height refs. However, we shouldn't render our Tooltip * label on this first cycle. */ if ( width && ! widthRef.current && height && ! heightRef.current ) { widthRef.current = width; heightRef.current = height; return; } /* * After the first cycle, we can track width and height changes. */ if ( didWidthChange ) { setMoveX( true ); widthRef.current = width; } if ( didHeightChange ) { setMoveY( true ); heightRef.current = height; } onResize( { width, height } ); debounceUnsetMoveXY(); }, [ width, height ] ); const label = getSizeLabel( { axis, height, moveX, moveY, position, showPx, width, } ); return { label, resizeListener, }; } /** * Gets the resize label based on width and height values (as well as recent changes). * * @param {Object} props * @param {string} props.axis Only shows the label corresponding to the axis. * @param {number} props.height Height value. * @param {boolean} props.moveX Recent width (x axis) changes. * @param {boolean} props.moveY Recent width (y axis) changes. * @param {string} props.position Adjusts label value. * @param {boolean} props.showPx Whether to add `PX` to the label. * @param {number} props.width Width value. * * @return {undefined | string} The rendered label. */ function getSizeLabel( { axis, height, moveX = false, moveY = false, position = POSITIONS.bottom, showPx = false, width, } ) { if ( ! moveX && ! moveY ) return null; /* * Corner position... * We want the label to appear like width x height. */ if ( position === POSITIONS.corner ) { return `${ width } x ${ height }`; } /* * Other POSITIONS... * The label will combine both width x height values if both * values have recently been changed. * * Otherwise, only width or height will be displayed. * The `PX` unit will be added, if specified by the `showPx` prop. */ const labelUnit = showPx ? ' px' : ''; if ( axis ) { if ( axis === 'x' && moveX ) { return `${ width }${ labelUnit }`; } if ( axis === 'y' && moveY ) { return `${ height }${ labelUnit }`; } } if ( moveX && moveY ) { return `${ width } x ${ height }`; } if ( moveX ) { return `${ width }${ labelUnit }`; } if ( moveY ) { return `${ height }${ labelUnit }`; } return null; }