From afdee37c8df7ffca2386bf11d3791c59d582d1a7 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Tue, 12 Feb 2019 12:49:44 -0600 Subject: [PATCH 01/10] EuiResizeObserver, shared logic with EuiMutationObserver; import updates --- src/components/accordion/accordion.js | 2 +- src/components/index.js | 6 +- .../mutation_observer/mutation_observer.js | 57 ------------------- .../{ => observer}/mutation_observer/index.js | 0 .../mutation_observer/mutation_observer.js | 28 +++++++++ .../mutation_observer.test.js | 0 src/components/observer/observer.js | 53 +++++++++++++++++ .../observer/resize_observer/index.js | 1 + .../resize_observer/resize_observer.js | 37 ++++++++++++ .../resize_observer/resize_observer.test.js | 47 +++++++++++++++ src/components/popover/popover.js | 2 +- src/components/tool_tip/tool_tip.js | 2 +- 12 files changed, 174 insertions(+), 61 deletions(-) delete mode 100644 src/components/mutation_observer/mutation_observer.js rename src/components/{ => observer}/mutation_observer/index.js (100%) create mode 100644 src/components/observer/mutation_observer/mutation_observer.js rename src/components/{ => observer}/mutation_observer/mutation_observer.test.js (100%) create mode 100644 src/components/observer/observer.js create mode 100644 src/components/observer/resize_observer/index.js create mode 100644 src/components/observer/resize_observer/resize_observer.js create mode 100644 src/components/observer/resize_observer/resize_observer.test.js diff --git a/src/components/accordion/accordion.js b/src/components/accordion/accordion.js index a710acf3a39..11da510e179 100644 --- a/src/components/accordion/accordion.js +++ b/src/components/accordion/accordion.js @@ -15,7 +15,7 @@ import { import { EuiMutationObserver, -} from '../mutation_observer'; +} from '../observer/mutation_observer'; const paddingSizeToClassNameMap = { none: null, diff --git a/src/components/index.js b/src/components/index.js index 795d2997184..7f816c3488b 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -227,7 +227,7 @@ export { export { EuiMutationObserver, -} from './mutation_observer'; +} from './observer/mutation_observer'; export { EuiNavDrawer, @@ -278,6 +278,10 @@ export { EuiProgress, } from './progress'; +export { + EuiResizeObserver, +} from './observer/resize_observer'; + export { EuiSearchBar, Query, diff --git a/src/components/mutation_observer/mutation_observer.js b/src/components/mutation_observer/mutation_observer.js deleted file mode 100644 index 2ad0c1d720e..00000000000 --- a/src/components/mutation_observer/mutation_observer.js +++ /dev/null @@ -1,57 +0,0 @@ -import { Component } from 'react'; -import PropTypes from 'prop-types'; - -class EuiMutationObserver extends Component { - constructor(...args) { - super(...args); - this.childNode = null; - this.observer = null; - } - - componentDidMount() { - if (this.childNode == null) { - throw new Error('EuiMutationObserver did not receive a ref'); - } - } - - updateChildNode = ref => { - if (this.childNode === ref) return; // node hasn't changed - - this.childNode = ref; - - // if there's an existing observer disconnect it - if (this.observer != null) { - this.observer.disconnect(); - this.observer = null; - } - - if (this.childNode != null) { - this.observer = new MutationObserver(this.onMutation); - this.observer.observe(this.childNode, this.props.observerOptions); - } - } - - onMutation = (...args) => { - this.props.onMutation(...args); - } - - render() { - return this.props.children(this.updateChildNode); - } -} - -EuiMutationObserver.propTypes = { - 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, - attributes: PropTypes.bool, - characterData: PropTypes.bool, - characterDataOldValue: PropTypes.bool, - childList: PropTypes.bool, - subtree: PropTypes.bool, - }).isRequired, - onMutation: PropTypes.func.isRequired, -}; - -export { EuiMutationObserver }; diff --git a/src/components/mutation_observer/index.js b/src/components/observer/mutation_observer/index.js similarity index 100% rename from src/components/mutation_observer/index.js rename to src/components/observer/mutation_observer/index.js diff --git a/src/components/observer/mutation_observer/mutation_observer.js b/src/components/observer/mutation_observer/mutation_observer.js new file mode 100644 index 00000000000..8550f86e2d9 --- /dev/null +++ b/src/components/observer/mutation_observer/mutation_observer.js @@ -0,0 +1,28 @@ +// import { Component } from 'react'; +import PropTypes from 'prop-types'; + +import { EuiObserver } from '../observer'; + +class EuiMutationObserver extends EuiObserver { + beginObserve = () => { + this.name = 'EuiMutationObserver'; + this.observer = new MutationObserver(this.props.onMutation); + this.observer.observe(this.childNode, this.props.observerOptions); + } +} + +EuiMutationObserver.propTypes = { + 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, + attributes: PropTypes.bool, + characterData: PropTypes.bool, + characterDataOldValue: PropTypes.bool, + childList: PropTypes.bool, + subtree: PropTypes.bool, + }).isRequired, + onMutation: PropTypes.func.isRequired, +}; + +export { EuiMutationObserver }; diff --git a/src/components/mutation_observer/mutation_observer.test.js b/src/components/observer/mutation_observer/mutation_observer.test.js similarity index 100% rename from src/components/mutation_observer/mutation_observer.test.js rename to src/components/observer/mutation_observer/mutation_observer.test.js diff --git a/src/components/observer/observer.js b/src/components/observer/observer.js new file mode 100644 index 00000000000..360c94ed896 --- /dev/null +++ b/src/components/observer/observer.js @@ -0,0 +1,53 @@ +import { Component } from 'react'; +import PropTypes from 'prop-types'; + +class EuiObserver extends Component { + constructor(...args) { + super(...args); + this.name = 'EuiObserver'; + this.childNode = null; + this.observer = null; + } + + componentDidMount() { + if (this.childNode == null) { + throw new Error(`${this.name} did not receive a ref`); + } + } + + componentWillUnmount() { + if (this.observer != null) { + this.observer.disconnect(); + } + } + + updateChildNode = ref => { + if (this.childNode === ref) return; // node hasn't changed + + this.childNode = ref; + + // if there's an existing observer disconnect it + if (this.observer != null) { + this.observer.disconnect(); + this.observer = null; + } + + if (this.childNode != null) { + this.beginObserve(); + } + } + + beginObserve = () => { + throw new Error('EuiObserver has no default observation method'); + } + + render() { + return this.props.children(this.updateChildNode); + } +} + +EuiObserver.propTypes = { + children: PropTypes.func.isRequired +}; + +export { EuiObserver }; diff --git a/src/components/observer/resize_observer/index.js b/src/components/observer/resize_observer/index.js new file mode 100644 index 00000000000..13179582c9a --- /dev/null +++ b/src/components/observer/resize_observer/index.js @@ -0,0 +1 @@ +export { EuiResizeObserver } from './resize_observer'; diff --git a/src/components/observer/resize_observer/resize_observer.js b/src/components/observer/resize_observer/resize_observer.js new file mode 100644 index 00000000000..3848268d2e5 --- /dev/null +++ b/src/components/observer/resize_observer/resize_observer.js @@ -0,0 +1,37 @@ +// import { Component } from 'react'; +import PropTypes from 'prop-types'; + +import { EuiObserver } from '../observer'; + +class EuiResizeObserver extends EuiObserver { + constructor(...args) { + super(...args); + this.name = 'EuiResizeObserver'; + // Chrome is the only browser that supports `ResizeObserver` at the time of writing + this.hasResizeObserver = typeof ResizeObserver !== 'undefined'; + } + + beginObserve = () => { + let observerOptions; + if (this.hasResizeObserver) { + this.observer = new ResizeObserver(this.props.onResize); + } else { + // MutationObserver fallback + observerOptions = { // [MutationObserverInit](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit) + attributes: true, // Account for style changes from `className` or `style` + characterData: true, // Account text content size differences + childList: true, // Account for adding/removing child nodes + subtree: true // Accound for deep child nodes + }; + this.observer = new MutationObserver(this.props.onResize); + } + this.observer.observe(this.childNode, observerOptions); + } +} + +EuiResizeObserver.propTypes = { + children: PropTypes.func.isRequired, + onResize: PropTypes.func.isRequired, +}; + +export { EuiResizeObserver }; diff --git a/src/components/observer/resize_observer/resize_observer.test.js b/src/components/observer/resize_observer/resize_observer.test.js new file mode 100644 index 00000000000..a8292b00994 --- /dev/null +++ b/src/components/observer/resize_observer/resize_observer.test.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { EuiResizeObserver } from './resize_observer'; + +async function sleep(duration) { + return new Promise(resolve => { + setTimeout(resolve, duration); + }); +} + +export async function waitforResizeObserver(period = 30) { + // `period` defaults to 30 because its the delay used by the ResizeObserver polyfill + await sleep(period); +} + +describe('EuiResizeObserver', () => { + it('watches for a resize', async () => { + expect.assertions(1); + const onResize = jest.fn(); + + function Wrapper({ children }) { + return ( + + {resizeRef =>
{children}
} +
+ ); + } + + const component = mount( + Hello World + } + /> + ); + + component.setProps({ children: ( +
+
Hello World
+
Hello Again
+
+ ) }); + + await waitforResizeObserver(); + + expect(onResize).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js index 7b2945e6375..d935c59f019 100644 --- a/src/components/popover/popover.js +++ b/src/components/popover/popover.js @@ -16,7 +16,7 @@ import { EuiPanel, SIZES } from '../panel'; import { EuiPortal } from '../portal'; -import { EuiMutationObserver } from '../mutation_observer'; +import { EuiMutationObserver } from '../observer/mutation_observer'; import { findPopoverPosition, getElementZIndex } from '../../services/popover/popover_positioning'; import { EuiI18n } from '../i18n'; diff --git a/src/components/tool_tip/tool_tip.js b/src/components/tool_tip/tool_tip.js index 4718464dcd6..4cae6271385 100644 --- a/src/components/tool_tip/tool_tip.js +++ b/src/components/tool_tip/tool_tip.js @@ -11,7 +11,7 @@ import { EuiToolTipPopover } from './tool_tip_popover'; import { findPopoverPosition } from '../../services'; import makeId from '../form/form_row/make_id'; -import { EuiMutationObserver } from '../mutation_observer'; +import { EuiMutationObserver } from '../observer/mutation_observer'; const positionsToClassNameMap = { top: 'euiToolTip--top', From 0034e25b11570c7ed5a5d8a869a1d7df0a3a7ebd Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Tue, 12 Feb 2019 12:50:49 -0600 Subject: [PATCH 02/10] use EuiResizeObserver inside EuiContextMenuPanel instances --- .../__snapshots__/context_menu.test.js.snap | 200 ++++----- .../context_menu_panel.test.js.snap | 400 ++++++++++-------- .../context_menu/context_menu_panel.js | 17 +- 3 files changed, 332 insertions(+), 285 deletions(-) diff --git a/src/components/context_menu/__snapshots__/context_menu.test.js.snap b/src/components/context_menu/__snapshots__/context_menu.test.js.snap index b33bede4715..46daea752f8 100644 --- a/src/components/context_menu/__snapshots__/context_menu.test.js.snap +++ b/src/components/context_menu/__snapshots__/context_menu.test.js.snap @@ -55,7 +55,9 @@ exports[`EuiContextMenu props panels and initialPanelId allows you to click the
- 2 +
+ 2 +
@@ -109,7 +111,9 @@ exports[`EuiContextMenu props panels and initialPanelId allows you to click the
- 2 +
+ 2 +
@@ -154,111 +158,113 @@ exports[`EuiContextMenu props panels and initialPanelId allows you to click the
- - + - + + + + +
@@ -310,7 +316,9 @@ exports[`EuiContextMenu props panels and initialPanelId renders the referenced p
- 2 +
+ 2 +
diff --git a/src/components/context_menu/__snapshots__/context_menu_panel.test.js.snap b/src/components/context_menu/__snapshots__/context_menu_panel.test.js.snap index 06dc4953a64..12177b62015 100644 --- a/src/components/context_menu/__snapshots__/context_menu_panel.test.js.snap +++ b/src/components/context_menu/__snapshots__/context_menu_panel.test.js.snap @@ -55,7 +55,9 @@ exports[`EuiContextMenu props panels and initialPanelId allows you to click the
- 2 +
+ 2 +
@@ -109,7 +111,9 @@ exports[`EuiContextMenu props panels and initialPanelId allows you to click the
- 2 +
+ 2 +
@@ -154,111 +158,113 @@ exports[`EuiContextMenu props panels and initialPanelId allows you to click the
- - + - + + + + +
@@ -310,7 +316,9 @@ exports[`EuiContextMenu props panels and initialPanelId renders the referenced p
- 2 +
+ 2 +
@@ -325,7 +333,9 @@ exports[`EuiContextMenuPanel is rendered 1`] = ` tabindex="0" >
- Hello +
+ Hello +
`; @@ -371,7 +381,9 @@ exports[`EuiContextMenuPanel props onClose renders a button as a title 1`] = ` -
+
+
+
`; @@ -389,7 +401,9 @@ exports[`EuiContextMenuPanel props title is rendered 1`] = ` Title
-
+
+
+
`; @@ -398,7 +412,9 @@ exports[`EuiContextMenuPanel props transitionDirection next with transitionType class="euiContextMenuPanel euiContextMenuPanel-txInLeft" tabindex="0" > -
+
+
+
`; @@ -407,7 +423,9 @@ exports[`EuiContextMenuPanel props transitionDirection next with transitionType class="euiContextMenuPanel euiContextMenuPanel-txOutLeft" tabindex="0" > -
+
+
+
`; @@ -416,7 +434,9 @@ exports[`EuiContextMenuPanel props transitionDirection previous with transitionT class="euiContextMenuPanel euiContextMenuPanel-txInRight" tabindex="0" > -
+
+
+
`; @@ -425,7 +445,9 @@ exports[`EuiContextMenuPanel props transitionDirection previous with transitionT class="euiContextMenuPanel euiContextMenuPanel-txOutRight" tabindex="0" > -
+
+
+
`; @@ -433,24 +455,28 @@ exports[`EuiContextMenuPanel updating items and content updates to items should "
- - - - - - + +
+ + + + + + +
+
" @@ -460,24 +486,28 @@ exports[`EuiContextMenuPanel updating items and content updates to items should "
- - - - - - + +
+ + + + + + +
+
" @@ -487,7 +517,11 @@ exports[`EuiContextMenuPanel updating items and content updates to items should "
- Hello World + +
+ Hello World +
+
" @@ -497,7 +531,11 @@ exports[`EuiContextMenuPanel updating items and content updates to items should "
- More Salutations + +
+ More Salutations +
+
" @@ -507,24 +545,28 @@ exports[`EuiContextMenuPanel updating items and content updates to items should "
- - - - - - + +
+ + + + + + +
+
" @@ -534,24 +576,28 @@ exports[`EuiContextMenuPanel updating items and content updates to items should "
- - - - - - + +
+ + + + + + +
+
" diff --git a/src/components/context_menu/context_menu_panel.js b/src/components/context_menu/context_menu_panel.js index 1118135e6c2..4ad0e9ce758 100644 --- a/src/components/context_menu/context_menu_panel.js +++ b/src/components/context_menu/context_menu_panel.js @@ -8,6 +8,7 @@ import tabbable from 'tabbable'; import { EuiIcon } from '../icon'; import { EuiPopoverTitle } from '../popover'; +import { EuiResizeObserver } from '../observer/resize_observer'; import { cascadingMenuKeyCodes } from '../../services'; const transitionDirectionAndTypeToClassNameMap = { @@ -323,17 +324,7 @@ export class EuiContextMenuPanel extends Component { } } - componentDidUpdate(prevProps) { - if (prevProps.items.length > 0 || this.props.items.length > 0) { - // content comes from items - if (this.didItemsChange(prevProps.items, this.props.items)) { - this.updateHeight(); - } - } else { - // content comes from children - this.updateHeight(); - } - + componentDidUpdate() { this.updateFocus(); } @@ -435,7 +426,9 @@ export class EuiContextMenuPanel extends Component { {panelTitle}
- {content} + this.updateHeight()}> + {resizeRef =>
{content}
} +
); From 7b64bc9a152eee64e0e5ff65c7bb6bb65b79ec02 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Tue, 12 Feb 2019 12:53:20 -0600 Subject: [PATCH 03/10] add dynamic content example to EuiContextMenu doc --- .../context_menu/context_menu_with_content.js | 142 ++++++++++++------ 1 file changed, 95 insertions(+), 47 deletions(-) diff --git a/src-docs/src/views/context_menu/context_menu_with_content.js b/src-docs/src/views/context_menu/context_menu_with_content.js index dff84d974c3..a414e6cbd40 100644 --- a/src-docs/src/views/context_menu/context_menu_with_content.js +++ b/src-docs/src/views/context_menu/context_menu_with_content.js @@ -7,9 +7,12 @@ import { EuiContextMenu, EuiIcon, EuiPopover, + EuiSpacer, EuiText } from '../../../../src/components'; +import EuiTabsExample from '../tabs/tabbed_content'; + function flattenPanelTree(tree, array = []) { array.push(tree); @@ -33,41 +36,45 @@ export default class extends Component { isPopoverOpen: false, }; - const panelTree = { - id: 0, - title: 'View options', - items: [{ - name: 'Show fullscreen', - icon: ( - - ), - onClick: () => { this.closePopover(); window.alert('Show fullscreen'); }, - }, { - name: 'See more', - icon: 'plusInCircle', - panel: { - id: 1, - width: 400, - title: 'See more', - content: ( - -

- -

Context panels can contain anything

-

- You can stuff just about anything into these panels. Be mindful of size though. - This panel is set to 400px and the height will grow as space allows. -

-
- ) - }, - }], + this.createPanelTree = (Content) => { + return flattenPanelTree({ + id: 0, + title: 'View options', + items: [{ + name: 'Show fullscreen', + icon: ( + + ), + onClick: () => { this.closePopover(); window.alert('Show fullscreen'); }, + }, { + name: 'See more', + icon: 'plusInCircle', + panel: { + id: 1, + width: 400, + title: 'See more', + content: + }, + }], + }); }; - this.panels = flattenPanelTree(panelTree); + this.panels = this.createPanelTree(() => ( + +

+ +

Context panels can contain anything

+

+ You can stuff just about anything into these panels. Be mindful of size though. + This panel is set to 400px and the height will grow as space allows. +

+
+ )); + + this.dynamicPanels = this.createPanelTree(EuiTabsExample); } onButtonClick = () => { @@ -76,12 +83,24 @@ export default class extends Component { })); }; + onDynamicButtonClick = () => { + this.setState(prevState => ({ + isDynamicPopoverOpen: !prevState.isDynamicPopoverOpen, + })); + }; + closePopover = () => { this.setState({ isPopoverOpen: false, }); }; + closeDynamicPopover = () => { + this.setState({ + isDynamicPopoverOpen: false, + }); + }; + render() { const button = ( ); - return ( - - - + Click me to load dynamic mixed content menu + + ); + + return ( + + + + + + + + + + + ); } } From 907c9ffdc509c3c8a38b1617c4b252591b329e76 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 13 Feb 2019 14:32:45 -0600 Subject: [PATCH 04/10] clean up comments --- src/components/observer/resize_observer/resize_observer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/observer/resize_observer/resize_observer.js b/src/components/observer/resize_observer/resize_observer.js index 3848268d2e5..12b5e93847c 100644 --- a/src/components/observer/resize_observer/resize_observer.js +++ b/src/components/observer/resize_observer/resize_observer.js @@ -7,7 +7,7 @@ class EuiResizeObserver extends EuiObserver { constructor(...args) { super(...args); this.name = 'EuiResizeObserver'; - // Chrome is the only browser that supports `ResizeObserver` at the time of writing + // Only Chrome and Opera support the `ResizeObserver` API at the time of writing this.hasResizeObserver = typeof ResizeObserver !== 'undefined'; } @@ -19,9 +19,9 @@ class EuiResizeObserver extends EuiObserver { // MutationObserver fallback observerOptions = { // [MutationObserverInit](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit) attributes: true, // Account for style changes from `className` or `style` - characterData: true, // Account text content size differences + characterData: true, // Account for text content size differences childList: true, // Account for adding/removing child nodes - subtree: true // Accound for deep child nodes + subtree: true // Account for deep child nodes }; this.observer = new MutationObserver(this.props.onResize); } From 0d79c88d5c462813ec0e8dba4049b7d192a35a01 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 13 Feb 2019 15:00:25 -0600 Subject: [PATCH 05/10] type defs; constructor clean up --- src/components/index.d.ts | 2 ++ .../observer/mutation_observer/index.d.ts | 20 +++++++++++++++++++ .../mutation_observer/mutation_observer.js | 6 ++++-- .../observer/resize_observer/index.d.ts | 19 ++++++++++++++++++ .../resize_observer/resize_observer.js | 1 - 5 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 src/components/observer/mutation_observer/index.d.ts create mode 100644 src/components/observer/resize_observer/index.d.ts diff --git a/src/components/index.d.ts b/src/components/index.d.ts index 7a5d0e37230..3a65c074660 100644 --- a/src/components/index.d.ts +++ b/src/components/index.d.ts @@ -18,6 +18,8 @@ /// /// /// +/// +/// /// /// /// diff --git a/src/components/observer/mutation_observer/index.d.ts b/src/components/observer/mutation_observer/index.d.ts new file mode 100644 index 00000000000..57bd6d9923d --- /dev/null +++ b/src/components/observer/mutation_observer/index.d.ts @@ -0,0 +1,20 @@ +import { CommonProps } from '../../common'; + +import { SFC } from 'react'; + +declare module '@elastic/eui' { + + /** + * MutationObserver type defs + * + * @see './mutation_observer.js' + */ + export interface EuiMutationObserverProps { + observerOptions: MutationObserverInit, // [MutationObserverInit](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit) + onMutation: () => void + } + + export const EuiMutationObserver: SFC< + CommonProps & EuiMutationObserverProps + >; +} diff --git a/src/components/observer/mutation_observer/mutation_observer.js b/src/components/observer/mutation_observer/mutation_observer.js index 8550f86e2d9..6a034f95127 100644 --- a/src/components/observer/mutation_observer/mutation_observer.js +++ b/src/components/observer/mutation_observer/mutation_observer.js @@ -1,11 +1,13 @@ -// import { Component } from 'react'; import PropTypes from 'prop-types'; import { EuiObserver } from '../observer'; class EuiMutationObserver extends EuiObserver { - beginObserve = () => { + constructor(...args) { + super(...args); this.name = 'EuiMutationObserver'; + } + beginObserve = () => { this.observer = new MutationObserver(this.props.onMutation); this.observer.observe(this.childNode, this.props.observerOptions); } diff --git a/src/components/observer/resize_observer/index.d.ts b/src/components/observer/resize_observer/index.d.ts new file mode 100644 index 00000000000..651d75f861a --- /dev/null +++ b/src/components/observer/resize_observer/index.d.ts @@ -0,0 +1,19 @@ +import { CommonProps } from '../../common'; + +import { SFC } from 'react'; + +declare module '@elastic/eui' { + + /** + * ResizeObserver type defs + * + * @see './resize_observer.js' + */ + export interface EuiResizeObserverProps { + onResize: () => void + } + + export const EuiResizeObserver: SFC< + CommonProps & EuiResizeObserverProps + >; +} diff --git a/src/components/observer/resize_observer/resize_observer.js b/src/components/observer/resize_observer/resize_observer.js index 12b5e93847c..36d24e0f966 100644 --- a/src/components/observer/resize_observer/resize_observer.js +++ b/src/components/observer/resize_observer/resize_observer.js @@ -1,4 +1,3 @@ -// import { Component } from 'react'; import PropTypes from 'prop-types'; import { EuiObserver } from '../observer'; From 8517a7501bb7644109b359d333df3f7e56983ebe Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 13 Feb 2019 16:41:05 -0600 Subject: [PATCH 06/10] doc examples for resizeObserver --- src-docs/src/routes.js | 4 + .../views/resize_observer/resize_observer.js | 90 +++++++++++++++++++ .../resize_observer_example.js | 53 +++++++++++ 3 files changed, 147 insertions(+) create mode 100644 src-docs/src/views/resize_observer/resize_observer.js create mode 100644 src-docs/src/views/resize_observer/resize_observer_example.js diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index ec984247185..1b71f5ad114 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -204,6 +204,9 @@ import { ProgressExample } import { RangeControlExample } from './views/range/range_example'; +import { ResizeObserverExample } + from './views/resize_observer/resize_observer_example'; + import { ResponsiveExample } from './views/responsive/responsive_example'; @@ -437,6 +440,7 @@ const navigation = [{ MutationObserverExample, OutsideClickDetectorExample, PortalExample, + ResizeObserverExample, ResponsiveExample, ToggleExample, WindowEventExample, diff --git a/src-docs/src/views/resize_observer/resize_observer.js b/src-docs/src/views/resize_observer/resize_observer.js new file mode 100644 index 00000000000..cecb36e0c24 --- /dev/null +++ b/src-docs/src/views/resize_observer/resize_observer.js @@ -0,0 +1,90 @@ +import React, { + Component, +} from 'react'; + +import { + EuiButton, + EuiButtonEmpty, + EuiCode, + EuiIcon, + EuiResizeObserver, + EuiPanel, + EuiSpacer, + EuiText +} from '../../../../src/components'; + +export class ResizeObserverExample extends Component { + state = { + hasResizeObserver: typeof ResizeObserver !== 'undefined', + lastMutation: 'no resize detected', + paddingSize: 's', + items: ['Item 1', 'Item 2', 'Item 3'], + }; + + togglePaddingSize = () => { + this.setState(({ paddingSize }) => ({ + paddingSize: paddingSize === 's' ? 'l' : 's' + })); + } + + addItem = () => { + this.setState(({ items }) => ({ + items: [...items, `Item ${items.length + 1}`] + })); + } + + onResize = ([entry]) => { + let lastMutation; + if (this.state.hasResizeObserver) { + lastMutation = {`height: ${entry.contentRect.height}; width: ${entry.contentRect.width}`}; + } else { + lastMutation = entry.type === 'attributes' + ? 'class name changed' + : 'DOM tree changed'; + } + this.setState({ + lastMutation + }); + } + + render() { + return ( +
+ + {this.state.hasResizeObserver ? ( +

Browser supports ResizeObserver API.

+ ) : ( +

+ Browser does not support ResizeObserver API. Using MutationObserver. +

+ )} +

{this.state.lastMutation}

+
+ + + + + Toggle container padding + + + + + + {resizeRef => ( +
+ +
    + {this.state.items.map(item =>
  • {item}
  • )} +
+ + add item +
+
+ )} +
+
+ ); + } +} diff --git a/src-docs/src/views/resize_observer/resize_observer_example.js b/src-docs/src/views/resize_observer/resize_observer_example.js new file mode 100644 index 00000000000..87a196cc1e9 --- /dev/null +++ b/src-docs/src/views/resize_observer/resize_observer_example.js @@ -0,0 +1,53 @@ +import React from 'react'; + +import { renderToHtml } from '../../services'; + +import { + GuideSectionTypes, +} from '../../components'; + +import { + EuiCode, + EuiLink, + EuiResizeObserver, +} from '../../../../src/components'; + +import { ResizeObserverExample as ResizeObserver } from './resize_observer'; +const resizeObserverSource = require('!!raw-loader!./resize_observer'); +const resizeObserverHtml = renderToHtml(ResizeObserver); + +export const ResizeObserverExample = { + title: 'ResizeObserver', + sections: [{ + title: 'ResizeObserver', + source: [{ + type: GuideSectionTypes.JS, + code: resizeObserverSource, + }, { + type: GuideSectionTypes.HTML, + code: resizeObserverHtml, + }], + text: ( + +

+ ResizeObserver is a wrapper around the + Resizer Observer API + which allows watching for changes to the content rectangle of DOM elements. + Unlike MutationObserver, ResizeObserver does not take parameters, + but it does fire a more efficient and informative callback when resize events occur. +

+

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

+

+ Due to limited browser support (currently supported in Chrome and Opera), EuiResizeObserver will + fallback to using the MutationObserver API with a default + set of paramters that appoximate the results of MutationObserver. +

+
+ ), + components: { EuiResizeObserver }, + demo: , + }], +}; From 7d7c9f713ee81cfb01dda5465565053aa67f5da5 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Thu, 14 Feb 2019 08:48:50 -0600 Subject: [PATCH 07/10] typo --- src-docs/src/views/resize_observer/resize_observer_example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-docs/src/views/resize_observer/resize_observer_example.js b/src-docs/src/views/resize_observer/resize_observer_example.js index 87a196cc1e9..6c66637798b 100644 --- a/src-docs/src/views/resize_observer/resize_observer_example.js +++ b/src-docs/src/views/resize_observer/resize_observer_example.js @@ -43,7 +43,7 @@ export const ResizeObserverExample = {

Due to limited browser support (currently supported in Chrome and Opera), EuiResizeObserver will fallback to using the MutationObserver API with a default - set of paramters that appoximate the results of MutationObserver. + set of paramters that approximate the results of MutationObserver.

), From ef3bb02695fa0440db6967375d6669b093870aff Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Thu, 14 Feb 2019 09:28:27 -0600 Subject: [PATCH 08/10] #1559 changelog entry --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86e45fa4662..aa89d773552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ## [`master`](https://github.com/elastic/eui/tree/master) -No public interface changes since `7.0.0`. +- Added `EuiResizeObserver` to expose ResizeObserver API to React components; falls back to MutationObserver API in unsupported browsers ([#1559](https://github.com/elastic/eui/pull/1559)) + +**Bug fixes** + +- Fixed content cut off in `EuiContextMenuPanel` when height changes dynamically ([#1559](https://github.com/elastic/eui/pull/1559)) ## [`7.0.0`](https://github.com/elastic/eui/tree/v7.0.0) From c0ccad27809726b7970c774d1866e12af252150d Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Thu, 14 Feb 2019 14:09:44 -0600 Subject: [PATCH 09/10] resizeObserver returns object of height and width; update mount event and test --- .../views/resize_observer/resize_observer.js | 18 ++++++------------ .../resize_observer/resize_observer.js | 14 ++++++++++++-- .../resize_observer/resize_observer.test.js | 3 ++- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src-docs/src/views/resize_observer/resize_observer.js b/src-docs/src/views/resize_observer/resize_observer.js index cecb36e0c24..a8dcd954e26 100644 --- a/src-docs/src/views/resize_observer/resize_observer.js +++ b/src-docs/src/views/resize_observer/resize_observer.js @@ -16,7 +16,8 @@ import { export class ResizeObserverExample extends Component { state = { hasResizeObserver: typeof ResizeObserver !== 'undefined', - lastMutation: 'no resize detected', + height: 0, + width: 0, paddingSize: 's', items: ['Item 1', 'Item 2', 'Item 3'], }; @@ -33,17 +34,10 @@ export class ResizeObserverExample extends Component { })); } - onResize = ([entry]) => { - let lastMutation; - if (this.state.hasResizeObserver) { - lastMutation = {`height: ${entry.contentRect.height}; width: ${entry.contentRect.width}`}; - } else { - lastMutation = entry.type === 'attributes' - ? 'class name changed' - : 'DOM tree changed'; - } + onResize = ({ height, width }) => { this.setState({ - lastMutation + height, + width }); } @@ -58,7 +52,7 @@ export class ResizeObserverExample extends Component { Browser does not support ResizeObserver API. Using MutationObserver.

)} -

{this.state.lastMutation}

+

{`height: ${this.state.height}; width: ${this.state.width}`}

diff --git a/src/components/observer/resize_observer/resize_observer.js b/src/components/observer/resize_observer/resize_observer.js index 36d24e0f966..73dda4b528d 100644 --- a/src/components/observer/resize_observer/resize_observer.js +++ b/src/components/observer/resize_observer/resize_observer.js @@ -10,10 +10,19 @@ class EuiResizeObserver extends EuiObserver { this.hasResizeObserver = typeof ResizeObserver !== 'undefined'; } + onResize = () => { + // Eventually use `clientRect` on the `entries[]` returned natively + const { height, width } = this.childNode.getBoundingClientRect(); + this.props.onResize({ + height, + width + }); + } + beginObserve = () => { let observerOptions; if (this.hasResizeObserver) { - this.observer = new ResizeObserver(this.props.onResize); + this.observer = new ResizeObserver(this.onResize); } else { // MutationObserver fallback observerOptions = { // [MutationObserverInit](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit) @@ -22,7 +31,8 @@ class EuiResizeObserver extends EuiObserver { childList: true, // Account for adding/removing child nodes subtree: true // Account for deep child nodes }; - this.observer = new MutationObserver(this.props.onResize); + this.observer = new MutationObserver(this.onResize); + requestAnimationFrame(this.onResize); // Mimic ResizeObserver behavior of triggering a resize event on init } this.observer.observe(this.childNode, observerOptions); } diff --git a/src/components/observer/resize_observer/resize_observer.test.js b/src/components/observer/resize_observer/resize_observer.test.js index a8292b00994..ed45204f778 100644 --- a/src/components/observer/resize_observer/resize_observer.test.js +++ b/src/components/observer/resize_observer/resize_observer.test.js @@ -42,6 +42,7 @@ describe('EuiResizeObserver', () => { await waitforResizeObserver(); - expect(onResize).toHaveBeenCalledTimes(1); + // Expect 2 calls because it's called once on mount + expect(onResize).toHaveBeenCalledTimes(2); }); }); From 2825e4a9a11d2433409e05943a71eefe964c8546 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 15 Feb 2019 12:35:19 -0600 Subject: [PATCH 10/10] observer component type definition fixes --- src/components/observer/mutation_observer/index.d.ts | 2 +- src/components/observer/resize_observer/index.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/observer/mutation_observer/index.d.ts b/src/components/observer/mutation_observer/index.d.ts index 57bd6d9923d..a6d00133892 100644 --- a/src/components/observer/mutation_observer/index.d.ts +++ b/src/components/observer/mutation_observer/index.d.ts @@ -11,7 +11,7 @@ declare module '@elastic/eui' { */ export interface EuiMutationObserverProps { observerOptions: MutationObserverInit, // [MutationObserverInit](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit) - onMutation: () => void + onMutation: MutationCallback } export const EuiMutationObserver: SFC< diff --git a/src/components/observer/resize_observer/index.d.ts b/src/components/observer/resize_observer/index.d.ts index 651d75f861a..759a2c6f813 100644 --- a/src/components/observer/resize_observer/index.d.ts +++ b/src/components/observer/resize_observer/index.d.ts @@ -10,7 +10,7 @@ declare module '@elastic/eui' { * @see './resize_observer.js' */ export interface EuiResizeObserverProps { - onResize: () => void + onResize: (dimensions: { width: number, height: number }) => void } export const EuiResizeObserver: SFC<