diff --git a/CHANGELOG.md b/CHANGELOG.md index c163e593fd9..66e0ee132e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Removed calls to deprecated `findDOMNode` ([#1285](https://github.com/elastic/eui/pull/1285)) + +**Breaking changes** + +- Changed `EuiMutationObserver` to a render prop component ([#1285](https://github.com/elastic/eui/pull/1285)) +- `EuiPortal` no longer accepts a React node for `insert.sibling` value ([#1285](https://github.com/elastic/eui/pull/1285)) +- `popover_positioning` service's methods no longer accept React node values ([#1285](https://github.com/elastic/eui/pull/1285)) + **Bug fixes** - Added TypeScript definitions for tab components ([#1288](https://github.com/elastic/eui/pull/1288)) @@ -10,6 +18,10 @@ **Bug fixes** +- Added TypeScript definitions for `EuiToolTip`'s `delay` prop. ([#1284](https://github.com/elastic/eui/pull/1284)) + +**Bug fixes** + - Added TypeScript definitions for `EuiToolTip`'s `delay` prop. ([#1284](https://github.com/elastic/eui/pull/1284)) - Added TypeScript definitions for step components, and some checkbox definition fixes ([#1263](https://github.com/elastic/eui/pull/1263)) diff --git a/src-docs/src/views/mutation_observer/mutation_observer.js b/src-docs/src/views/mutation_observer/mutation_observer.js index d51c470ad03..de85662d2b1 100644 --- a/src-docs/src/views/mutation_observer/mutation_observer.js +++ b/src-docs/src/views/mutation_observer/mutation_observer.js @@ -54,25 +54,29 @@ export class MutationObserver extends Component { observerOptions={{ subtree: true, attributes: true, childList: true }} onMutation={this.onMutation} > + {mutationRef => ( +
- - Toggle button color - + + Toggle button color + - + - - - -
    - {this.state.items.map(item =>
  • {item}
  • )} -
- - add item -
-
-
+ + + +
    + {this.state.items.map(item =>
  • {item}
  • )} +
+ + add item +
+
+
+
+ )} ); diff --git a/src-docs/src/views/mutation_observer/mutation_observer_example.js b/src-docs/src/views/mutation_observer/mutation_observer_example.js index 9357ea629fd..033ad0509a0 100644 --- a/src-docs/src/views/mutation_observer/mutation_observer_example.js +++ b/src-docs/src/views/mutation_observer/mutation_observer_example.js @@ -28,14 +28,20 @@ export const MutationObserverExample = { code: mutationObserverHtml, }], text: ( -

- MutationObserver is a wrapper around the - Mutation Observer API - which allows watching for DOM changes to elements and their children. - MutationObserver takes the same configuration object - as the browser API to describe what to watch for, and fires the - callback when that mutation happens. -

+ +

+ MutationObserver is a wrapper around the + Mutation Observer API + which allows watching for DOM changes to elements and their children. + MutationObserver takes the same configuration object + as the browser API to describe what to watch for, and fires the + callback when that mutation happens. +

+

+ This is a render prop component, MutationObserver will pass a ref + callback which you must put on the element you wish to observe the mutations. +

+
), components: { EuiMutationObserver }, demo: , diff --git a/src-docs/src/views/popover/popover_htmlelement_anchor.js b/src-docs/src/views/popover/popover_htmlelement_anchor.js index 344782d46cc..a218a2152cf 100644 --- a/src-docs/src/views/popover/popover_htmlelement_anchor.js +++ b/src-docs/src/views/popover/popover_htmlelement_anchor.js @@ -3,7 +3,7 @@ import React, { Component, } from 'react'; -import { findDOMNode, render, unmountComponentAtNode } from 'react-dom'; +import { render, unmountComponentAtNode } from 'react-dom'; import { EuiWrappingPopover, @@ -49,9 +49,9 @@ class PopoverApp extends Component { } export default class extends Component { + componentDidMount() { - const thisNode = findDOMNode(this); - const thisAnchor = thisNode.querySelector('button'); + const thisAnchor = document.querySelector('#popoverAnchorButton'); // `container` can be created here or use an existing DOM element // the popover DOM is positioned independently of where the container exists @@ -71,7 +71,7 @@ export default class extends Component { render() { return (
+ ` }} diff --git a/src/components/accordion/accordion.js b/src/components/accordion/accordion.js index 5e4bd73a8f5..36e08dc17ab 100644 --- a/src/components/accordion/accordion.js +++ b/src/components/accordion/accordion.js @@ -152,11 +152,13 @@ export class EuiAccordion extends Component { observerOptions={{ childList: true, subtree: true }} onMutation={this.setChildContentHeight} > -
-
- {children} + {mutationRef => ( +
{this.setChildContentRef(ref); mutationRef(ref);}}> +
+ {children} +
-
+ )}
diff --git a/src/components/form/super_select/__snapshots__/super_select.test.js.snap b/src/components/form/super_select/__snapshots__/super_select.test.js.snap index 3822e4c74e7..c748d1448f1 100644 --- a/src/components/form/super_select/__snapshots__/super_select.test.js.snap +++ b/src/components/form/super_select/__snapshots__/super_select.test.js.snap @@ -139,63 +139,65 @@ exports[`EuiSuperSelect props custom display is propagated to dropdown 1`] = `
- -
- - + +
@@ -276,70 +278,72 @@ exports[`EuiSuperSelect props more props are propogated to each option 1`] = `
- -
- - + +
@@ -615,18 +619,7 @@ exports[`EuiSuperSelect props more props are propogated to each option 2`] = ` } onMutation={[Function]} > - +

- -

- +
@@ -896,63 +876,65 @@ exports[`EuiSuperSelect props options are rendered when select is open 1`] = `
- -
- - + +
diff --git a/src/components/mutation_observer/mutation_observer.js b/src/components/mutation_observer/mutation_observer.js index bf7a3e29d22..2ad0c1d720e 100644 --- a/src/components/mutation_observer/mutation_observer.js +++ b/src/components/mutation_observer/mutation_observer.js @@ -1,37 +1,6 @@ -import React, { Component } from 'react'; -import { findDOMNode } from 'react-dom'; +import { Component } from 'react'; import PropTypes from 'prop-types'; -/** - * EuiMutationObserver watches its children with the MutationObserver API - * There are a couple constraints which inform how this component works - * - * 1. React refs cannot be added to functional components - * 2. findDOMNode will only return the first element from an array of children - * or from a fragment. - * - * Because of #1, we can't blindly attach refs to children and expect them to work in all cases - * Because of #2, we can't observe all children for mutations, only the first - * - * When only one child is passed its found by findDOMNode and the mutation observer is attached - * When children is an array the render function maps over them wrapping each child - * with another EuiMutationObserver, e.g.: - * - * - *
First
- *
Second
- *
- * - * becomes - * - * - *
First
- *
Second
- *
- * - * each descendant-Observer has only one child and can independently watch for mutations, - * triggering the parent's onMutation callback when an event is observed - */ class EuiMutationObserver extends Component { constructor(...args) { super(...args); @@ -40,31 +9,26 @@ class EuiMutationObserver extends Component { } componentDidMount() { - this.updateChildNode(); + if (this.childNode == null) { + throw new Error('EuiMutationObserver did not receive a ref'); + } } - updateChildNode() { - if (Array.isArray(this.props.children) === false) { - const currentNode = findDOMNode(this); - if (this.childNode !== currentNode) { - // if there's an existing observer disconnect it - if (this.observer != null) { - this.observer.disconnect(); - this.observer = null; - } + updateChildNode = ref => { + if (this.childNode === ref) return; // node hasn't changed + + this.childNode = ref; - this.childNode = currentNode; - if (this.childNode != null) { - this.observer = new MutationObserver(this.onMutation); - this.observer.observe(this.childNode, this.props.observerOptions); - } - } + // if there's an existing observer disconnect it + if (this.observer != null) { + this.observer.disconnect(); + this.observer = null; } - } - componentDidUpdate() { - // in case the child element was changed - this.updateChildNode(); + if (this.childNode != null) { + this.observer = new MutationObserver(this.onMutation); + this.observer.observe(this.childNode, this.props.observerOptions); + } } onMutation = (...args) => { @@ -72,27 +36,12 @@ class EuiMutationObserver extends Component { } render() { - const { children, ...rest } = this.props; - if (Array.isArray(children)) { - return React.Children.map( - children, - child => ( - - {child} - - ) - ); - } else { - return children; - } + return this.props.children(this.updateChildNode); } } EuiMutationObserver.propTypes = { - children: PropTypes.oneOfType([ - PropTypes.node, - PropTypes.arrayOf(PropTypes.node) - ]), + children: PropTypes.func.isRequired, observerOptions: PropTypes.shape({ // matches a [MutationObserverInit](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit) attributeFilter: PropTypes.arrayOf(PropTypes.string), attributeOldValue: PropTypes.bool, diff --git a/src/components/mutation_observer/mutation_observer.test.js b/src/components/mutation_observer/mutation_observer.test.js index ddfc61da4c1..08df927d7bc 100644 --- a/src/components/mutation_observer/mutation_observer.test.js +++ b/src/components/mutation_observer/mutation_observer.test.js @@ -21,43 +21,10 @@ describe('EuiMutationObserver', () => { function Wrapper({ value }) { return ( - + {mutationRef =>
Hello World
}
); } - function Child({ value }) { - return ( -
Hello World
- ); - } - - const component = mount(); - - component.setProps({ value: 6 }); - - await waitforMutationObserver(); - - expect(onMutation).toHaveBeenCalledTimes(1); - }); - - it('watches for mutation against multiple children', async () => { - expect.assertions(1); - const onMutation = jest.fn(); - - function Wrapper({ value }) { - return ( - - - - - - ); - } - function Child({ value }) { - return ( -
Hello World
- ); - } const component = mount(); diff --git a/src/components/popover/__snapshots__/popover.test.js.snap b/src/components/popover/__snapshots__/popover.test.js.snap index 9250a8d08cd..38945f4a404 100644 --- a/src/components/popover/__snapshots__/popover.test.js.snap +++ b/src/components/popover/__snapshots__/popover.test.js.snap @@ -101,6 +101,7 @@ exports[`EuiPopover props isOpen renders true 1`] = ` class="euiPopover__panelArrow euiPopover__panelArrow--bottom" style="left: 10px; top: 0px;" /> +
@@ -128,6 +129,7 @@ exports[`EuiPopover props ownFocus defaults to false 1`] = ` class="euiPopover__panelArrow euiPopover__panelArrow--bottom" style="left: 10px; top: 0px;" /> +
@@ -162,6 +164,7 @@ exports[`EuiPopover props ownFocus renders true 1`] = ` class="euiPopover__panelArrow euiPopover__panelArrow--bottom" style="left: 10px; top: 0px;" /> +
@@ -189,6 +192,7 @@ exports[`EuiPopover props panelClassName is rendered 1`] = ` class="euiPopover__panelArrow euiPopover__panelArrow--bottom" style="left: 10px; top: 0px;" /> +
@@ -216,6 +220,7 @@ exports[`EuiPopover props panelPaddingSize is rendered 1`] = ` class="euiPopover__panelArrow euiPopover__panelArrow--bottom" style="left: 10px; top: 0px;" /> +
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js index fa168cddbd5..1d6e0afdf4e 100644 --- a/src/components/popover/popover.js +++ b/src/components/popover/popover.js @@ -453,24 +453,17 @@ export class EuiPopover extends Component { style={this.state.popoverStyles} >
- { - children - ? ( - - {children} - - ) - : null - - } + + {mutationRef =>
{children}
} +
@@ -507,10 +500,7 @@ EuiPopover.propTypes = { panelPaddingSize: PropTypes.oneOf(SIZES), popoverRef: PropTypes.func, hasArrow: PropTypes.bool, - container: PropTypes.oneOfType([ - PropTypes.node, - PropTypes.instanceOf(HTMLElement) - ]), + container: PropTypes.instanceOf(HTMLElement), /** When `true`, the popover's position is re-calculated when the user scrolls, this supports having fixed-position popover anchors. */ repositionOnScroll: PropTypes.bool, /** By default, popover content inherits the z-index of the anchor component; pass zIndex to override */ diff --git a/src/components/popover/wrapping_popover.js b/src/components/popover/wrapping_popover.js index ef94772e8c2..4e6811476c0 100644 --- a/src/components/popover/wrapping_popover.js +++ b/src/components/popover/wrapping_popover.js @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import { findDOMNode } from 'react-dom'; import PropTypes from 'prop-types'; import { EuiPopover } from './popover'; import { EuiPortal } from '../portal'; @@ -14,14 +13,11 @@ export class EuiWrappingPopover extends Component { super(...args); this.portal = null; - this.contentParent = this.props.button.parentNode; + this.anchor = null; } componentDidMount() { - const thisDomNode = findDOMNode(this); - const placeholderAnchor = thisDomNode.querySelector('.euiWrappingPopover__anchor'); - - placeholderAnchor.insertAdjacentElement( + this.anchor.insertAdjacentElement( 'beforebegin', this.props.button ); @@ -40,6 +36,10 @@ export class EuiWrappingPopover extends Component { this.portal = node; }; + setAnchorRef = node => { + this.anchor = node; + } + render() { const { button, // eslint-disable-line no-unused-vars @@ -52,7 +52,7 @@ export class EuiWrappingPopover extends Component { > } + button={
} /> ); diff --git a/src/components/portal/portal.js b/src/components/portal/portal.js index c7f3f8acc38..f20025447f3 100644 --- a/src/components/portal/portal.js +++ b/src/components/portal/portal.js @@ -5,7 +5,7 @@ import { Component } from 'react'; import PropTypes from 'prop-types'; -import { createPortal, findDOMNode } from 'react-dom'; +import { createPortal } from 'react-dom'; export const insertPositions = { 'after': 'afterend', @@ -30,7 +30,7 @@ export class EuiPortal extends Component { document.body.appendChild(this.portalNode); } else { // inserting before or after an element - findDOMNode(insert.sibling).insertAdjacentElement( + insert.sibling.insertAdjacentElement( insertPositions[insert.position], this.portalNode ); @@ -63,13 +63,10 @@ export class EuiPortal extends Component { EuiPortal.propTypes = { children: PropTypes.node, - /** `{sibling: ReactNode|HTMLElement, position: 'before'|'after'}` */ + /** `{sibling: HTMLElement, position: 'before'|'after'}` */ insert: PropTypes.shape({ - sibling: PropTypes.oneOfType([ - PropTypes.node, - PropTypes.instanceOf(HTMLElement) - ]).isRequired, - position: PropTypes.oneOf(INSERT_POSITIONS), + sibling: PropTypes.instanceOf(HTMLElement).isRequired, + position: PropTypes.oneOf(INSERT_POSITIONS).isRequired, }), portalRef: PropTypes.func, }; diff --git a/src/components/tool_tip/tool_tip.js b/src/components/tool_tip/tool_tip.js index 79fadd97dbe..4718464dcd6 100644 --- a/src/components/tool_tip/tool_tip.js +++ b/src/components/tool_tip/tool_tip.js @@ -205,7 +205,7 @@ export class EuiToolTip extends Component { observerOptions={{ subtree: true, childList: true, characterData: true, attributes: true }} onMutation={this.positionToolTip} > - {content} + {mutationRef =>
{content}
} diff --git a/src/services/popover/popover_positioning.js b/src/services/popover/popover_positioning.js index d19b17169f0..0764db0cd2f 100644 --- a/src/services/popover/popover_positioning.js +++ b/src/services/popover/popover_positioning.js @@ -1,5 +1,3 @@ -import { findDOMNode } from 'react-dom'; - const relatedDimension = { top: 'height', right: 'width', @@ -31,15 +29,15 @@ const positionSubstitutes = { /** * Calculates the absolute positioning (relative to document.body) to place a popover element * - * @param anchor {HTMLElement|React.Component} Element to anchor the popover to - * @param popover {HTMLElement|React.Component} Element containing the popover content + * @param anchor {HTMLElement} Element to anchor the popover to + * @param popover {HTMLElement} Element containing the popover content * @param position {string} Position the user wants. One of ["top", "right", "bottom", "left"] * @param [forcePosition] {boolean} If true, use only the provided `position` value and don't try any other position * @param [align] {string} Cross-axis alignment. One of ["top", "right", "bottom", "left"] * @param [buffer=16] {number} Minimum distance between the popover and the bounding container * @param [offset=0] {number} Distance between the popover and the anchor * @param [allowCrossAxis=true] {boolean} Whether to allow the popover to be positioned on the cross-axis - * @param [container] {HTMLElement|React.Component} Element the popover must be constrained to fit within + * @param [container] {HTMLElement} Element the popover must be constrained to fit within * @param [arrowConfig] {{arrowWidth: number, arrowBuffer: number}} If present, describes the size & constraints for an arrow element, and the function return value will include an `arrow` param with position details * * @returns {{top: number, left: number, position: string, fit: number, arrow?: {left: number, top: number}}|null} absolute page coordinates for the popover, @@ -57,8 +55,6 @@ export function findPopoverPosition({ container, arrowConfig }) { - container = findDOMNode(container); // resolve any React abstractions - // find the screen-relative bounding boxes of the anchor, popover, and container const anchorBoundingBox = getElementBoundingBox(anchor); const popoverBoundingBox = getElementBoundingBox(popover); @@ -446,12 +442,10 @@ function getPrimaryAxisPosition({ * Finds the client pixel coordinate of each edge for the element's bounding box, * and the bounding box's width & height * - * @param {HTMLElement|React.Component} element + * @param {HTMLElement} element * @returns {{top: number, right: number, bottom: number, left: number, height: number, width: number}} */ export function getElementBoundingBox(element) { - element = findDOMNode(element); // resolve any React abstractions - const rect = element.getBoundingClientRect(); return { top: rect.top, @@ -527,14 +521,11 @@ export function intersectBoundingBoxes(firstBox, secondBox) { /** * Returns the top-most defined z-index in the element's ancestor hierarchy * relative to the `target` element; if no z-index is defined, returns "0" - * @param element {HTMLElement|React.Component} - * @param cousin {HTMLElement|React.Component} + * @param element {HTMLElement} + * @param cousin {HTMLElement} * @returns {string} */ export function getElementZIndex(element, cousin) { - element = findDOMNode(element); - cousin = findDOMNode(cousin); - /** * finding the z-index of `element` is not the full story * its the CSS stacking context that is important diff --git a/src/services/popover/popover_positioning.test.js b/src/services/popover/popover_positioning.test.js index 6ed6c225b81..093021cbd91 100644 --- a/src/services/popover/popover_positioning.test.js +++ b/src/services/popover/popover_positioning.test.js @@ -1,5 +1,3 @@ -import React, { Component } from 'react'; -import { mount } from 'enzyme'; import { findPopoverPosition, getAvailableSpace, @@ -36,23 +34,6 @@ describe('popover_positioning', () => { expect(boundingBox).not.toBe(clientRect); expect(boundingBox).toEqual(clientRect); }); - - it('works for React HTML and Component refs', () => { - class App extends Component { - render() { - const { nested } = this.props; - return ( -
- - {nested ? : null} -
- ); - } - } - const component = mount(); - expect(getElementBoundingBox(component.ref('spanRef'))).toEqual(clientRect); - expect(getElementBoundingBox(component.ref('appRef'))).toEqual(clientRect); - }); }); describe('getAvailableSpace', () => {