123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- import React from 'react';
- import { findDOMNode } from 'react-dom';
- import PropTypes from 'prop-types';
- import BaseComponent, { BaseProps } from '../_base/baseComponent';
- /** A parallel type to `ResizeObserverEntry` (from resize-observer-polyfill). */
- export interface ResizeEntry {
- contentRect: DOMRectReadOnly;
- target: Element
- }
- export interface ReactResizeObserverProps extends BaseProps {
- onResize?: (entries: ResizeEntry[]) => void;
- observeParent?: boolean;
- observerProperty?: ObserverProperty;
- delayTick?: number
- }
- export enum ObserverProperty {
- Width='width',
- Height = "height",
- All = "all"
- }
- export default class ReactResizeObserver extends BaseComponent<ReactResizeObserverProps> {
- static propTypes = {
- onResize: PropTypes.func,
- observeParent: PropTypes.bool,
- observerProperty: PropTypes.string,
- delayTick: PropTypes.number
- };
- static defaultProps = {
- onResize: () => {}, // eslint-disable-line
- observeParent: false,
- observerProperty: "all",
- delayTick: 0
- };
- observer: ResizeObserver;
- childNode: any;
- element: Element;
- _parentNode: HTMLElement;
- formerPropertyValue: Map<Element, number> = new Map()
- constructor(props: ReactResizeObserverProps) {
- super(props);
- if (globalThis['ResizeObserver']) {
- this.observer = new ResizeObserver(this.handleResizeEventTriggered);
- }
- }
- componentDidMount() {
- this.observeElement?.();
- }
- componentDidUpdate(prevProps: ReactResizeObserverProps) {
- this.observeElement?.(this.props.observeParent !== prevProps.observeParent);
- }
- componentWillUnmount() {
- if (this.observer) {
- this.observer.disconnect();
- this.observer = null;
- this.element = null;
- }
- }
- getElement = () => {
- try {
- // using findDOMNode for two reasons:
- // 1. cloning to insert a ref is unwieldy and not performant.
- // 2. ensure that we resolve to an actual DOM node (instead of any JSX ref instance).
- return findDOMNode(this.childNode || this);
- } catch (error) {
- // swallow error if findDOMNode is run on unmounted component.
- return null;
- }
- };
- handleResizeEventTriggered = (entries: ResizeEntry[])=>{
- if (this.props.observerProperty === ObserverProperty.All) {
- this.props.onResize?.(entries);
- } else {
- const finalEntries: ResizeEntry[] = [];
- for (const entry of entries) {
- if (this.formerPropertyValue.has(entry.target)) {
- if (entry.contentRect[this.props.observerProperty]!==this.formerPropertyValue.get(entry.target)) {
- this.formerPropertyValue.set(entry.target, entry.contentRect[this.props.observerProperty]);
- finalEntries.push(entry);
- }
- } else {
- this.formerPropertyValue.set(entry.target, entry.contentRect[this.props.observerProperty]);
- finalEntries.push(entry);
- }
- }
- if (finalEntries.length>0) {
- this.props.onResize?.(finalEntries);
- }
- }
- }
- observeElement = (force = false)=>{
- const element = this.getElement();
- if (!this.observer) {
- this.observer = new ResizeObserver(this.handleResizeEventTriggered);
- }
- if (!(element && element instanceof Element)) {
- // stop everything if not defined
- this.observer.disconnect();
- return;
- }
- if (element === this.element && !force) {
- // abort if given same element -- nothing to update (unless forced)
- return;
- } else {
- // clear observer list if new element
- this.observer.disconnect();
- // remember element reference for next time
- this.element = element;
- }
- // observer callback is invoked immediately when observing new elements
- this.observer.observe(element);
- if (
- this.props.observeParent &&
- element.parentNode &&
- element.parentNode.ownerDocument &&
- element.parentNode.ownerDocument.defaultView &&
- element.parentNode instanceof element.parentNode.ownerDocument.defaultView.HTMLElement
- ) {
- this._parentNode = element.parentNode;
- this.observer.observe(this._parentNode);
- }
- }
- mergeRef = (ref: any, node: HTMLDivElement) => {
- this.childNode = node;
- if (typeof ref === 'function') {
- ref(node);
- } else if (typeof ref === 'object' && ref && 'current' in ref) {
- ref.current = node;
- }
- };
- render() {
- const child = React.Children.only(this.props.children);
- const { ref } = child as any;
- return React.cloneElement(child as React.ReactElement, {
- ref: (node: HTMLDivElement) => this.mergeRef(ref, node),
- });
- }
- }
|