index.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import React from 'react';
  2. import { findDOMNode } from 'react-dom';
  3. import PropTypes from 'prop-types';
  4. import BaseComponent, { BaseProps } from '../_base/baseComponent';
  5. import ResizeObserver from 'resize-observer-polyfill';
  6. /** A parallel type to `ResizeObserverEntry` (from resize-observer-polyfill). */
  7. export interface ResizeEntry {
  8. contentRect: DOMRectReadOnly;
  9. target: Element
  10. }
  11. export interface ReactResizeObserverProps extends BaseProps {
  12. onResize?: (entries: ResizeEntry[]) => void;
  13. observeParent?: boolean
  14. }
  15. export default class ReactResizeObserver extends BaseComponent<ReactResizeObserverProps> {
  16. static propTypes = {
  17. onResize: PropTypes.func,
  18. observeParent: PropTypes.bool,
  19. };
  20. static defaultProps = {
  21. onResize: () => {}, // eslint-disable-line
  22. observeParent: false,
  23. };
  24. observer: ResizeObserver;
  25. childNode: any;
  26. element: Element;
  27. _parentNode: HTMLElement;
  28. constructor(props: ReactResizeObserverProps) {
  29. super(props);
  30. this.observer = new ResizeObserver(props.onResize);
  31. }
  32. componentDidMount() {
  33. this.observeElement();
  34. }
  35. componentDidUpdate(prevProps: ReactResizeObserverProps) {
  36. this.observeElement(this.props.observeParent !== prevProps.observeParent);
  37. }
  38. componentWillUnmount() {
  39. if (this.observer) {
  40. this.observer.disconnect();
  41. this.observer = null;
  42. this.element = null;
  43. }
  44. }
  45. getElement = () => {
  46. try {
  47. // using findDOMNode for two reasons:
  48. // 1. cloning to insert a ref is unwieldy and not performant.
  49. // 2. ensure that we resolve to an actual DOM node (instead of any JSX ref instance).
  50. // eslint-disable-next-line
  51. return findDOMNode(this.childNode || this);
  52. } catch (error) {
  53. // swallow error if findDOMNode is run on unmounted component.
  54. return null;
  55. }
  56. };
  57. observeElement(force = false) {
  58. const element = this.getElement();
  59. if (!this.observer) {
  60. this.observer = new ResizeObserver(this.props.onResize);
  61. }
  62. if (!(element && element instanceof Element)) {
  63. // stop everything if not defined
  64. this.observer.disconnect();
  65. return;
  66. }
  67. if (element === this.element && !force) {
  68. // abort if given same element -- nothing to update (unless forced)
  69. return;
  70. } else {
  71. // clear observer list if new element
  72. this.observer.disconnect();
  73. // remember element reference for next time
  74. this.element = element;
  75. }
  76. // observer callback is invoked immediately when observing new elements
  77. this.observer.observe(element);
  78. if (
  79. this.props.observeParent &&
  80. element.parentNode &&
  81. element.parentNode.ownerDocument &&
  82. element.parentNode.ownerDocument.defaultView &&
  83. element.parentNode instanceof element.parentNode.ownerDocument.defaultView.HTMLElement
  84. ) {
  85. this._parentNode = element.parentNode;
  86. this.observer.observe(this._parentNode);
  87. }
  88. }
  89. mergeRef = (ref: any, node: HTMLDivElement) => {
  90. this.childNode = node;
  91. if (typeof ref === 'function') {
  92. ref(node);
  93. } else if (typeof ref === 'object' && ref && 'current' in ref) {
  94. ref.current = node;
  95. }
  96. };
  97. render() {
  98. const child = React.Children.only(this.props.children);
  99. const { ref } = child as any;
  100. return React.cloneElement(child as React.ReactElement, {
  101. ref: (node: HTMLDivElement) => this.mergeRef(ref, node),
  102. });
  103. }
  104. }