UNPKG

@empoleon/solid-measure

Version:
143 lines (123 loc) 3.94 kB
import { Component, createSignal, onMount, onCleanup, splitProps, createMemo, } from "solid-js"; // @ts-ignore - ResizeObserver polyfill import { createResizeObserver } from "@solid-primitives/resize-observer"; import getTypes, { MeasureProps } from "./get-types"; import getContentRect, { ContentRectCalculations, MeasurementType, } from "./get-content-rect"; import getWindowOf from "./get-window-of"; export interface ContentRectState { entry: Partial<DOMRectReadOnly>; client: any; offset: any; scroll: any; bounds: any; margin: any; } export interface WithContentRectChildrenProps { measure: (entries?: ResizeObserverEntry[]) => void; measureRef: (node: Element) => void; contentRect: ContentRectState; } export interface WithContentRectProps extends MeasureProps { innerRef?: ((el: Element) => void) | { current?: Element | null }; onResize?: (contentRect: ContentRectCalculations) => void; } function withContentRect<T extends WithContentRectProps>( types?: MeasurementType[] ) { return function <P extends T>( WrappedComponent: Component<P & WithContentRectChildrenProps> ): Component<P> { const MeasureComponent: Component<P> = (props: P) => { const [local, others] = splitProps(props as P & WithContentRectProps, [ "innerRef", "onResize", ]); const [contentRect, setContentRect] = createSignal<ContentRectState>({ entry: {}, client: {}, offset: {}, scroll: {}, bounds: {}, margin: {}, }); let animationFrameID: number | null = null; let node: Element | null = null; let windowRef: Window | null = null; // createResizeObserver from @solid-primitives/resize-observer handles cleanup automatically const measure = (rect?: DOMRectReadOnly) => { if (!node) return; const contentRectCalc = getContentRect( node, types || getTypes(props as MeasureProps) ); const newContentRect: ContentRectState = { entry: rect || {}, client: contentRectCalc.client || {}, offset: contentRectCalc.offset || {}, scroll: contentRectCalc.scroll || {}, bounds: contentRectCalc.bounds || {}, margin: contentRectCalc.margin || {}, }; if (windowRef) { animationFrameID = windowRef.requestAnimationFrame(() => { setContentRect(newContentRect); if (typeof local.onResize === "function") { local.onResize(contentRectCalc); } }); } else { setContentRect(newContentRect); if (typeof local.onResize === "function") { local.onResize(contentRectCalc); } } }; const handleRef = (newNode: Element) => { node = newNode; windowRef = getWindowOf(node); if (local.innerRef) { if (typeof local.innerRef === "function") { local.innerRef(node); } else { local.innerRef.current = node; } } }; onMount(() => { if (node) { createResizeObserver(node, measure); } }); onCleanup(() => { if (windowRef && animationFrameID) { windowRef.cancelAnimationFrame(animationFrameID); } // createResizeObserver from @solid-primitives/resize-observer // handles cleanup automatically when the component is disposed }); // Create a memo to make the component props reactive const componentProps = createMemo( () => ({ ...others, measureRef: handleRef, measure, contentRect: contentRect(), } as P & WithContentRectChildrenProps) ); return WrappedComponent(componentProps()); }; return MeasureComponent; }; } export default withContentRect;