UNPKG

sanity

Version:

Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches

118 lines (92 loc) 2.88 kB
import {useCallback, useEffect, useMemo, useState} from 'react' /** * A function that creates a DOMRect from an array of elements. */ export function createDomRectFromElements(elements: Element[]): DOMRect | null { if (!elements || !elements.length) return null const rects = elements.map((el) => el.getBoundingClientRect()) const minX = Math.min(...rects.map((r) => r.x)) || 0 const minY = Math.min(...rects.map((r) => r.y)) || 0 const maxRight = Math.max(...rects.map((r) => r.right)) || 0 const maxBottom = Math.max(...rects.map((r) => r.bottom)) || 0 return { x: minX, y: minY, width: maxRight - minX, height: maxBottom - minY, top: minY, right: maxRight, bottom: maxBottom, left: minX, } as DOMRect } interface RectFromElementsHookOptions { scrollElement: HTMLElement | null disabled: boolean selector: string } /** * A hook that returns a DOMRect from an array of elements. * This is needed because the Range Decoration API might render se */ function useRectFromElements(props: RectFromElementsHookOptions): DOMRect | null { const {scrollElement, disabled, selector} = props const [rect, setRect] = useState<DOMRect | null>(null) const handleSetRect = useCallback(() => { const elements = document?.querySelectorAll(selector) if (!elements) return const nextRect = createDomRectFromElements(Array.from(elements)) setRect(nextRect) }, [selector]) useEffect(() => { if (disabled) return undefined const timeout = setTimeout(() => { handleSetRect() }, 1) return () => { clearTimeout(timeout) } }, [handleSetRect, disabled]) useEffect(() => { if (disabled || !scrollElement) return undefined scrollElement.addEventListener('wheel', handleSetRect) return () => { scrollElement.removeEventListener('wheel', handleSetRect) } }, [handleSetRect, disabled, scrollElement]) return rect } export interface ReferenceElementHookOptions { scrollElement: HTMLElement | null disabled: boolean selector: string } /** * A hook that returns a reference element that can be used to position a popover. */ export function useAuthoringReferenceElement( props: ReferenceElementHookOptions, ): HTMLElement | null { const {scrollElement, disabled, selector} = props const rect = useRectFromElements({ scrollElement, disabled, selector, }) const element = useMemo((): HTMLElement | null => { if (!rect) return null return { getBoundingClientRect: () => rect, } as HTMLElement }, [rect]) return element } export function getSelectionBoundingRect(): DOMRect | null { const selection = window.getSelection() const range = selection?.getRangeAt(0) const rect = range?.getBoundingClientRect() return rect || null } export function isRangeInvalid(): boolean { return false }