Skip to content

Commit

Permalink
main 🧊 add use measure hook
Browse files Browse the repository at this point in the history
  • Loading branch information
debabin committed Jun 30, 2024
1 parent 1ac4ae7 commit e0b6dac
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export { useFavicon } from './useFavicon/useFavicon';
export { useField } from './useField/useField';
export { useFps } from './useFps/useFps';
export { useFullscreen } from './useFullscreen/useFullscreen';
export { useGeolocation } from './useGeolocation/useGeolocation';
export { useHash } from './useHash/useHash';
export { useHotkeys } from './useHotkeys/useHotkeys';
export { useHover } from './useHover/useHover';
Expand Down
3 changes: 1 addition & 2 deletions src/hooks/useFullscreen/useFullscreen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import screenfull from 'screenfull';
import { useUnmount } from '../useUnmount/useUnmount';

/** The use fullscreen target element type */
type UseFullScreenTarget = RefObject<Element | null> | (() => Element) | Element;
export type UseFullScreenTarget = RefObject<Element | null> | (() => Element) | Element;

/** Function to get target element based on its type */
const getElement = (target: UseFullScreenTarget) => {
if (typeof target === 'function') {
return target();
Expand Down
31 changes: 31 additions & 0 deletions src/hooks/useMeasure/useMeasure.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useMeasure } from './useMeasure';

const Demo = () => {
const measure = useMeasure<HTMLDivElement>();

return (
<div
ref={measure.ref}
style={{
width: 200,
resize: 'horizontal',
overflow: 'auto'
}}
>
<p>
width: <code>{measure.width}</code>
</p>
<p>
height: <code>{measure.height}</code>
</p>
<p>
right: <code>{measure.right}</code>
</p>
<p>
bottom: <code>{measure.bottom}</code>
</p>
</div>
);
};

export default Demo;
86 changes: 86 additions & 0 deletions src/hooks/useMeasure/useMeasure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { RefObject } from 'react';
import { useRef, useState } from 'react';

import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect/useIsomorphicLayoutEffect';

/** The use measure target element type */
export type UseMeasureTarget = RefObject<Element | null> | (() => Element) | Element;

const getElement = (target: UseMeasureTarget) => {
if (typeof target === 'function') {
return target();
}

if (target instanceof Element) {
return target;
}

return target.current;
};

/** The use measure return type */
export type UseMeasureReturn = Pick<
DOMRectReadOnly,
'x' | 'y' | 'top' | 'left' | 'right' | 'bottom' | 'height' | 'width'
>;

export type UseMeasureScreen = {
<Target extends UseMeasureTarget>(target: Target): UseMeasureReturn;

<Target extends UseMeasureTarget>(target?: never): UseMeasureReturn & { ref: RefObject<Target> };
};

/**
* @name useMeasure
* @description - Hook to measure the size and position of an element
* @category Browser
*
* @overload
* @template Target The element to measure
* @param {Target} [target] The element to measure
* @returns {UseMeasureReturn} The element's size and position
*
* @example
* const { x, y, width, height, top, left, bottom, right } = useMeasure(ref);
*
* @overload
* @template Target The element to measure
* @returns {UseMeasureReturn & { ref: RefObject<Target> }} The element's size and position
*
* @example
* const { ref, x, y, width, height, top, left, bottom, right } = useMeasure();
*/
export const useMeasure = (<Target extends UseMeasureTarget>(target?: Target) => {
const internalRef = useRef<Element>(null);
const [rect, setRect] = useState({
x: 0,
y: 0,
width: 0,
height: 0,
top: 0,
left: 0,
bottom: 0,
right: 0
});

useIsomorphicLayoutEffect(() => {
const element = target ? getElement(target) : internalRef.current;
console.log('@element', internalRef.current);
if (!element) return;

const observer = new window.ResizeObserver(([entry]) => {
if (!entry) return;

const { x, y, width, height, top, left, bottom, right } = entry.contentRect;
setRect({ x, y, width, height, top, left, bottom, right });
});
observer.observe(element);

return () => {
observer.disconnect();
};
}, [target]);

if (target) return rect;
return { ref: internalRef, ...rect };
}) as UseMeasureScreen;

0 comments on commit e0b6dac

Please sign in to comment.