import { useLayoutEffect, useMemo, useRef } from 'react' import mergeRefs from './mergeRefs' import useForceUpdate from './useForceUpdate' const isPreceding = (a: Node, b: Node) => Boolean(b.compareDocumentPosition(a) & Node.DOCUMENT_POSITION_PRECEDING) export interface Descendant { element: HTMLElement props: T } class DescendantsManager { items: Descendant[] = [] register({ element, ...props }: Descendant) { if (!element) return // already registered if (this.items.find((item) => item.element === element)) return const index = this.items.findIndex((item) => isPreceding(element, item.element)) const newItem = { element, ...props } if (index === -1) { this.items.push(newItem) } else { this.items.splice(index, 0, newItem) } } unregister(element: HTMLElement) { const index = this.items.findIndex((item) => item.element === element) if (index !== -1) this.items.splice(index, 1) } } const useDescendants = () => { const descendants = useMemo(() => new DescendantsManager(), []) return descendants } const useDescendant = (descendants: DescendantsManager, props: T) => { const forceUpdate = useForceUpdate() const ref = useRef() const element = ref.current useLayoutEffect(() => { return () => { if (ref.current) descendants.unregister(ref.current) } }, []) const callback = (element: any) => descendants.register({ element, props }) const index = element ? descendants.items.findIndex((item) => element === item.element) : -1 return { ref: mergeRefs(ref, callback), index } } export { useDescendants, useDescendant, DescendantsManager }