58 lines
1.4 KiB
TypeScript
58 lines
1.4 KiB
TypeScript
// Copyright 2020 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import type { Ref } from 'react';
|
|
import { useEffect, useState } from 'react';
|
|
import { first, last, noop } from 'lodash';
|
|
|
|
function getBottom(element: Readonly<Element>): number {
|
|
return element.getBoundingClientRect().bottom;
|
|
}
|
|
|
|
function getTop(element: Readonly<Element>): number {
|
|
return element.getBoundingClientRect().top;
|
|
}
|
|
|
|
function isWrapped(element: Readonly<null | HTMLElement>): boolean {
|
|
if (!element) {
|
|
return false;
|
|
}
|
|
|
|
const { children } = element;
|
|
const firstChild = first(children);
|
|
const lastChild = last(children);
|
|
|
|
return Boolean(
|
|
firstChild &&
|
|
lastChild &&
|
|
firstChild !== lastChild &&
|
|
getBottom(firstChild) <= getTop(lastChild)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* A hook that returns a ref (to put on your element) and a boolean. The boolean will be
|
|
* `true` if the element's children have different `top`s, and `false` otherwise.
|
|
*/
|
|
export function useHasWrapped<T extends HTMLElement>(): [Ref<T>, boolean] {
|
|
const [element, setElement] = useState<null | T>(null);
|
|
|
|
const [hasWrapped, setHasWrapped] = useState(isWrapped(element));
|
|
|
|
useEffect(() => {
|
|
if (!element) {
|
|
return noop;
|
|
}
|
|
|
|
const observer = new ResizeObserver(() => {
|
|
setHasWrapped(isWrapped(element));
|
|
});
|
|
observer.observe(element);
|
|
|
|
return () => {
|
|
observer.disconnect();
|
|
};
|
|
}, [element]);
|
|
|
|
return [setElement, hasWrapped];
|
|
}
|