import React, { CSSProperties } from "react" /** Props for AutoSizeComponent. */ export interface AutoSizeComponentProps { /** True to inject width */ injectWidth?: boolean /** True to inject height */ injectHeight?: boolean children?: (size: { width?: number; height?: number }) => React.ReactElement | null } interface AutoSizeComponentState { /** Current width of the container. */ width?: number /** Current height of the container. */ height?: number } /** Automatically injects the width or height of the DOM element into the child component. */ export default class AutoSizeComponent extends React.Component { state: AutoSizeComponentState = {} private container: HTMLDivElement | null = null private resizeObserver: ResizeObserver | null = null private rafId: number | null = null componentDidMount() { if (this.container) { this.measure() if (typeof ResizeObserver !== "undefined") { this.resizeObserver = new ResizeObserver(this.measure) this.resizeObserver.observe(this.container) } else { window.addEventListener("resize", this.measure) } } } componentWillUnmount() { if (this.resizeObserver) { this.resizeObserver.disconnect() } else { window.removeEventListener("resize", this.measure) } if (this.rafId != null) { cancelAnimationFrame(this.rafId) this.rafId = null } } measure = () => { // Coalesce measurements into a single animation frame so a resize can't // synchronously re-trigger this and spin. offsetWidth/offsetHeight are already // integer pixels, so subpixel jitter (fractional sizes fed back as layout sizes) // can't cause an endless measure/setState cycle. if (this.rafId != null) { return } this.rafId = requestAnimationFrame(() => { this.rafId = null if (!this.container) { return } // Measure the layout box, not getBoundingClientRect, which is post-transform. A scaled-down // ancestor (see the belowMinimumWidth="scale" handling in dashboards) would otherwise make us // inject a shrunken width into children that are themselves inside that same transform, and // ResizeObserver does not fire when a transform changes, so we would never recover from it. const width = this.container.offsetWidth const height = this.container.offsetHeight // A 0x0 layout box means we are hidden (display:none) or detached, not that we are genuinely // zero-sized. Keep the last known size: injecting 0 makes children render collapsed, and once // hidden nothing guarantees another notification that would let us restore a real size. if (width === 0 && height === 0) { return } if (width !== this.state.width || height !== this.state.height) { this.setState({ width, height }) } }) } setContainer = (container: HTMLDivElement | null) => { this.container = container } render() { const { width, height } = this.state let innerElem const style: CSSProperties = {} if (this.props.injectWidth) { style.width = "100%" } if (this.props.injectHeight) { style.height = "100%" } // Return placeholder until width/height known if (width == null || height == null) { return
} const overrides: any = {} if (this.props.injectWidth) { overrides.width = width } if (this.props.injectHeight) { overrides.height = height } if (typeof this.props.children === "function") { innerElem = this.props.children(overrides) } else { // DEPRECATED innerElem = React.cloneElement(React.Children.only(this.props.children as any), overrides) } // Call children to get element if function return
{innerElem}
} }