UNPKG

ink

Version:
89 lines (85 loc) • 3.34 kB
import { useState, useEffect, useCallback, useMemo, use, } from 'react'; import { addLayoutListener } from '../dom.js'; import measureElement from '../measure-element.js'; import RootNodeContext from '../components/RootNodeContext.js'; const emptyMetrics = { width: 0, height: 0, left: 0, top: 0, clientWidth: 0, clientHeight: 0, }; /** A React hook that returns the current layout metrics for a tracked box element. It updates when layout changes (for example terminal resize, sibling/content changes, or position changes). The hook returns zeros for all metrics until the first layout pass completes. It also returns zeros when the tracked ref is detached. Use `hasMeasured` to detect when the currently tracked element has been measured. @example ```tsx import {useRef} from 'react'; import {Box, Text, useBoxMetrics} from 'ink'; const Example = () => { const ref = useRef(null); const {width, height, left, top, hasMeasured} = useBoxMetrics(ref); return ( <Box ref={ref}> <Text> {hasMeasured ? `${width}x${height} at ${left},${top}` : 'Measuring...'} </Text> </Box> ); }; ``` */ const useBoxMetrics = ( /* eslint-disable-next-line @typescript-eslint/no-restricted-types -- Creating a ref object with an initial null, especially when the ref object will be passed to a DOM node's ref attribute, is common in React. */ ref) => { const rootNode = use(RootNodeContext); const [metrics, setMetrics] = useState(emptyMetrics); const [hasMeasured, setHasMeasured] = useState(false); const updateMetrics = useCallback(() => { const node = ref.current; const layout = node?.yogaNode?.getComputedLayout(); // `measureElement()` also returns `x`/`y`, which aren't part of `BoxMetrics` and must not enter the change detection below. let nextMetrics = emptyMetrics; if (node && layout) { const { width, height, clientWidth, clientHeight } = measureElement(node); nextMetrics = { width, height, left: layout.left, top: layout.top, clientWidth, clientHeight, }; } setMetrics(previousMetrics => { const hasChanged = Object.keys(nextMetrics).some(key => previousMetrics[key] !== nextMetrics[key]); return hasChanged ? nextMetrics : previousMetrics; }); setHasMeasured(Boolean(node)); }, [ref]); // Runs after every render of this component. // This keeps metrics fresh when local state/props in this subtree change. useEffect(updateMetrics); // Subscribe to root layout commits so memoized components still receive // sibling-driven position/size updates, even when they skip re-rendering. useEffect(() => { if (!rootNode) { return; } return addLayoutListener(rootNode, () => { // React attaches refs after the layout notification, during the same commit. queueMicrotask(updateMetrics); }); }, [rootNode, updateMetrics]); return useMemo(() => ({ ...metrics, hasMeasured, }), [metrics, hasMeasured]); }; export default useBoxMetrics; //# sourceMappingURL=use-box-metrics.js.map