From 55535bbd4995f8ddb7eb670b462a0b7a1f62725a Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Mon, 20 Aug 2018 10:31:36 -0400 Subject: [PATCH 01/11] Adds new EuiWindowEvent component --- src/components/index.js | 4 ++ src/components/window_event/index.js | 1 + src/components/window_event/window_event.js | 51 ++++++++++++++++++ .../window_event/window_event.test.js | 53 +++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 src/components/window_event/index.js create mode 100644 src/components/window_event/window_event.js create mode 100644 src/components/window_event/window_event.test.js diff --git a/src/components/index.js b/src/components/index.js index 7d34c6382f2..6c17511ae77 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -320,6 +320,10 @@ export { EuiToolTip, } from './tool_tip'; +export { + EuiWindowEvent +} from './window_event'; + export { EuiHideFor, EuiShowFor, diff --git a/src/components/window_event/index.js b/src/components/window_event/index.js new file mode 100644 index 00000000000..a426a83af0b --- /dev/null +++ b/src/components/window_event/index.js @@ -0,0 +1 @@ +export { default as EuiWindowEvent } from './window_event'; \ No newline at end of file diff --git a/src/components/window_event/window_event.js b/src/components/window_event/window_event.js new file mode 100644 index 00000000000..394381c2839 --- /dev/null +++ b/src/components/window_event/window_event.js @@ -0,0 +1,51 @@ +import { Component } from 'react'; +import PropTypes from 'prop-types'; + +/** + * Adds and removes window events for you (renders null) + * Usage: + * + */ +export default class WindowEvent extends Component { + + componentDidMount() { + this.addEvent(this.props); + } + + componentDidUpdate(prevProps) { + if (prevProps.event !== this.props.event || prevProps.handler !== this.props.handler) { + this.removeEvent(prevProps); + this.addEvent(this.props); + } + } + + componentWillUnmount() { + this.removeEvent(this.props); + } + + addEvent({ event, handler }) { + window.addEventListener(event, handler); + } + + removeEvent({ event, handler }) { + window.removeEventListener(event, handler); + } + + render() { + return null; + } + +} + +WindowEvent.displayName = 'WindowEvent'; + +WindowEvent.propTypes = { + /** + * Type of event + */ + event: PropTypes.string.isRequired, + /** + * Event callback function + */ + handler: PropTypes.func.isRequired +}; diff --git a/src/components/window_event/window_event.test.js b/src/components/window_event/window_event.test.js new file mode 100644 index 00000000000..aa21a725e5a --- /dev/null +++ b/src/components/window_event/window_event.test.js @@ -0,0 +1,53 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { EuiWindowEvent } from '.'; + +describe('EuiWindowEvent', () => { + + beforeEach(() => { + window.addEventListener = jest.fn(); + window.removeEventListener = jest.fn(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('attaches handler to window event on mount', () => { + const handler = () => null; + shallow(); + expect(window.addEventListener).toHaveBeenCalledTimes(1); + expect(window.addEventListener).toHaveBeenCalledWith('click', handler); + }); + + test('removes handler on unmount', () => { + const handler = () => null; + const wrapper = shallow(); + wrapper.unmount(); + expect(window.removeEventListener).toHaveBeenLastCalledWith('click', handler); + }); + + test('removes and re-attaches handler to window event on update', () => { + const handler1 = () => null; + const handler2 = () => null; + const wrapper = shallow(); + + expect(window.addEventListener).toHaveBeenLastCalledWith('click', handler1); + + wrapper.setProps({ event: 'hover', handler: handler2 }); + + expect(window.removeEventListener).toHaveBeenLastCalledWith('click', handler1); + expect(window.addEventListener).toHaveBeenLastCalledWith('hover', handler2); + }); + + test('does not remove or re-attach handler if update is irrelevant', () => { + const handler = () => null; + const wrapper = shallow(); + expect(window.addEventListener).toHaveBeenCalledTimes(1); + + wrapper.setProps({ whatever: 'ugh' }); + expect(window.addEventListener).toHaveBeenCalledTimes(1); + expect(window.removeEventListener).not.toHaveBeenCalled(); + }); + +}); \ No newline at end of file From 48608b7cc6ffae6c0947c9cee6feba8c81aab6f8 Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Mon, 20 Aug 2018 10:42:21 -0400 Subject: [PATCH 02/11] Switches flyout close on ESC to window event --- src/components/flyout/flyout.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/flyout/flyout.js b/src/components/flyout/flyout.js index 251b8961066..9cf2ecbeb0c 100644 --- a/src/components/flyout/flyout.js +++ b/src/components/flyout/flyout.js @@ -7,6 +7,7 @@ import { keyCodes } from '../../services'; import { EuiOverlayMask } from '../overlay_mask'; import { EuiButtonIcon } from '../button'; +import { EuiWindowEvent } from '../window_event'; const sizeToClassNameMap = { s: 'euiFlyout--small', @@ -20,7 +21,6 @@ export class EuiFlyout extends Component { onKeyDown = event => { if (event.keyCode === keyCodes.ESCAPE) { event.preventDefault(); - event.stopPropagation(); this.props.onClose(); } }; @@ -90,6 +90,7 @@ export class EuiFlyout extends Component { return ( + {optionalOverlay} {/* Trap focus even when ownFocus={false}, otherwise closing the flyout won't return focus to the originating button */} From dbe6c7165bd9ee713e2472340b05a11ebd6a3ced Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Mon, 20 Aug 2018 11:04:08 -0400 Subject: [PATCH 03/11] Removes old event listener on Flyout --- src/components/flyout/flyout.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/flyout/flyout.js b/src/components/flyout/flyout.js index 9cf2ecbeb0c..574740bfd5c 100644 --- a/src/components/flyout/flyout.js +++ b/src/components/flyout/flyout.js @@ -72,7 +72,6 @@ export class EuiFlyout extends Component { }} className={classes} tabIndex={0} - onKeyDown={this.onKeyDown} style={newStyle || style} {...rest} > From 6acd4532b0b1c1d66b8dec9744e4b52cb3e1aac9 Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Mon, 20 Aug 2018 12:21:59 -0400 Subject: [PATCH 04/11] Adds EuiWindowEvent example to docs --- src-docs/src/routes.js | 4 ++ .../src/views/window_event/window_event.js | 51 +++++++++++++++++++ .../window_event/window_event_example.js | 37 ++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 src-docs/src/views/window_event/window_event.js create mode 100644 src-docs/src/views/window_event/window_event_example.js diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index b5286e43229..f8a8bd64a72 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -216,6 +216,9 @@ import { ToolTipExample } import { ToggleExample } from './views/toggle/toggle_example'; +import { WindowEventExample } + from './views/window_event/window_event_example'; + import { XYChartExample } from './views/series_chart/series_chart_example'; @@ -395,6 +398,7 @@ const navigation = [{ ToggleExample, UtilityClassesExample, MutationObserverExample, + WindowEventExample, ].map(example => createExample(example)), }, { name: 'Package', diff --git a/src-docs/src/views/window_event/window_event.js b/src-docs/src/views/window_event/window_event.js new file mode 100644 index 00000000000..68502e58588 --- /dev/null +++ b/src-docs/src/views/window_event/window_event.js @@ -0,0 +1,51 @@ +import React, { Component } from 'react'; + +import { + EuiWindowEvent, + EuiIcon, + EuiButton, + EuiSpacer, +} from '../../../../src/components'; + +export class WindowEvent extends Component { + constructor(props) { + super(props); + + this.state = { + stars: [] + }; + this.add = this.add.bind(this); + this.remove = this.remove.bind(this); + } + + add() { + this.setState((state) => ({ stars: [...state.stars, ] })); + } + + remove({ key }) { + if (key === 'Backspace' || key === 'Delete') { + this.setState((state) => ({ stars: state.stars.slice(0, -1) })); + return; + } + } + + render() { + const { stars } = this.state; + return ( +
+ + Add a Star + + + +

To remove a star, press the backspace or delete key.

+ + + +
+ {stars.map((star, i) => {star})} +
+
+ ); + } +} diff --git a/src-docs/src/views/window_event/window_event_example.js b/src-docs/src/views/window_event/window_event_example.js new file mode 100644 index 00000000000..2d6531ee4a7 --- /dev/null +++ b/src-docs/src/views/window_event/window_event_example.js @@ -0,0 +1,37 @@ +import React from 'react'; + +import { renderToHtml } from '../../services'; + +import { + GuideSectionTypes, +} from '../../components'; + +import { + EuiCode, + EuiWindowEvent, +} from '../../../../src/components'; + +import { WindowEvent } from './window_event'; +const source = require('!!raw-loader!./window_event'); +const html = renderToHtml(WindowEvent); + +export const WindowEventExample = { + title: 'Window Event', + sections: [{ + title: 'Window Event', + source: [{ + type: GuideSectionTypes.JS, + code: source, + }, { + type: GuideSectionTypes.HTML, + code: html, + }], + text: ( +

+ Use an EuiWindowEvent to safely manage adding and auto-removing event listeners to window. +

+ ), + components: { EuiWindowEvent }, + demo: , + }], +}; From 300fcb3262e4c7383eeb31bae3e0e8a57f2f7b1b Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Mon, 20 Aug 2018 12:25:46 -0400 Subject: [PATCH 05/11] Updates changelog for EuiWindowEvent and Flyout changes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d7ed3a013a..952b6fb0a14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Added a new `EuiWindowEvent` component for declarative, safe management of `window` event listeners +- Changed `Flyout` component to close on ESC keypress even if the flyout does not have focus + +## [`master`](https://github.com/elastic/eui/tree/master) + - Added `zIndexAdjustment` to `EuiPopover` which allows tweaking the popover content's `z-index` ([#1097](https://github.com/elastic/eui/pull/1097)) - Added new `EuiSuperSelect` component and `hasArrow` prop to `EuiPopover` ([#921](https://github.com/elastic/eui/pull/921)) From b703e5f31298d2467775d440f6caea9e8b7859b8 Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Mon, 20 Aug 2018 12:44:48 -0400 Subject: [PATCH 06/11] Adds more clarification to WindowEvent example --- .../views/window_event/window_event_example.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src-docs/src/views/window_event/window_event_example.js b/src-docs/src/views/window_event/window_event_example.js index 2d6531ee4a7..0d8c52f4144 100644 --- a/src-docs/src/views/window_event/window_event_example.js +++ b/src-docs/src/views/window_event/window_event_example.js @@ -16,9 +16,8 @@ const source = require('!!raw-loader!./window_event'); const html = renderToHtml(WindowEvent); export const WindowEventExample = { - title: 'Window Event', + title: 'Window Events', sections: [{ - title: 'Window Event', source: [{ type: GuideSectionTypes.JS, code: source, @@ -27,9 +26,16 @@ export const WindowEventExample = { code: html, }], text: ( -

- Use an EuiWindowEvent to safely manage adding and auto-removing event listeners to window. -

+
+

+ Use an EuiWindowEvent to safely and declaratively manage adding and auto-removing + event listeners to the window. +

+

+ This is preferable to setting up your own window event listeners because it will remove old + events when your component unmounts, preventing you from accidentally leaving them around forever. +

+
), components: { EuiWindowEvent }, demo: , From a348dbbc3ba6e3dac161011c0a378d512e2cf2d4 Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Mon, 20 Aug 2018 12:56:18 -0400 Subject: [PATCH 07/11] Moves WindowEvent to services folder --- src-docs/src/views/window_event/window_event.js | 5 ++++- src/components/flyout/flyout.js | 3 +-- src/components/index.js | 4 ---- src/services/index.js | 4 ++++ src/{components => services}/window_event/index.js | 0 src/{components => services}/window_event/window_event.js | 0 .../window_event/window_event.test.js | 0 7 files changed, 9 insertions(+), 7 deletions(-) rename src/{components => services}/window_event/index.js (100%) rename src/{components => services}/window_event/window_event.js (100%) rename src/{components => services}/window_event/window_event.test.js (100%) diff --git a/src-docs/src/views/window_event/window_event.js b/src-docs/src/views/window_event/window_event.js index 68502e58588..3c60d6b9e73 100644 --- a/src-docs/src/views/window_event/window_event.js +++ b/src-docs/src/views/window_event/window_event.js @@ -1,12 +1,15 @@ import React, { Component } from 'react'; import { - EuiWindowEvent, EuiIcon, EuiButton, EuiSpacer, } from '../../../../src/components'; +import { + EuiWindowEvent, +} from '../../../../src/services'; + export class WindowEvent extends Component { constructor(props) { super(props); diff --git a/src/components/flyout/flyout.js b/src/components/flyout/flyout.js index 574740bfd5c..866a3715bba 100644 --- a/src/components/flyout/flyout.js +++ b/src/components/flyout/flyout.js @@ -3,11 +3,10 @@ import classnames from 'classnames'; import PropTypes from 'prop-types'; import FocusTrap from 'focus-trap-react'; -import { keyCodes } from '../../services'; +import { keyCodes, EuiWindowEvent } from '../../services'; import { EuiOverlayMask } from '../overlay_mask'; import { EuiButtonIcon } from '../button'; -import { EuiWindowEvent } from '../window_event'; const sizeToClassNameMap = { s: 'euiFlyout--small', diff --git a/src/components/index.js b/src/components/index.js index 6c17511ae77..7d34c6382f2 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -320,10 +320,6 @@ export { EuiToolTip, } from './tool_tip'; -export { - EuiWindowEvent -} from './window_event'; - export { EuiHideFor, EuiShowFor, diff --git a/src/services/index.js b/src/services/index.js index 9bdbd73d641..de7a563cc2a 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -67,3 +67,7 @@ export { calculatePopoverPosition, findPopoverPosition, } from './popover'; + +export { + EuiWindowEvent +} from './window_event'; diff --git a/src/components/window_event/index.js b/src/services/window_event/index.js similarity index 100% rename from src/components/window_event/index.js rename to src/services/window_event/index.js diff --git a/src/components/window_event/window_event.js b/src/services/window_event/window_event.js similarity index 100% rename from src/components/window_event/window_event.js rename to src/services/window_event/window_event.js diff --git a/src/components/window_event/window_event.test.js b/src/services/window_event/window_event.test.js similarity index 100% rename from src/components/window_event/window_event.test.js rename to src/services/window_event/window_event.test.js From 73f0ed0693985d558292f01f354d9c477eeb48ca Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Mon, 20 Aug 2018 12:58:34 -0400 Subject: [PATCH 08/11] Updates changelog to group changes correctly --- CHANGELOG.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 952b6fb0a14..7f922e59efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,9 @@ ## [`master`](https://github.com/elastic/eui/tree/master) -- Added a new `EuiWindowEvent` component for declarative, safe management of `window` event listeners -- Changed `Flyout` component to close on ESC keypress even if the flyout does not have focus - -## [`master`](https://github.com/elastic/eui/tree/master) - - Added `zIndexAdjustment` to `EuiPopover` which allows tweaking the popover content's `z-index` ([#1097](https://github.com/elastic/eui/pull/1097)) - Added new `EuiSuperSelect` component and `hasArrow` prop to `EuiPopover` ([#921](https://github.com/elastic/eui/pull/921)) +- Added a new `EuiWindowEvent` component for declarative, safe management of `window` event listeners ([#1127](https://github.com/elastic/eui/pull/1127)) +- Changed `Flyout` component to close on ESC keypress even if the flyout does not have focus, using new Window Event component ([#1127](https://github.com/elastic/eui/pull/1127)) **Bug fixes** From e4d90d9d6713f18eb0abb233ccf1d6995522a1f9 Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Mon, 20 Aug 2018 13:10:11 -0400 Subject: [PATCH 09/11] Adds more about how to use the EuiWindowEvent in example docs --- .../src/views/window_event/window_event_example.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src-docs/src/views/window_event/window_event_example.js b/src-docs/src/views/window_event/window_event_example.js index 0d8c52f4144..43689a76a6c 100644 --- a/src-docs/src/views/window_event/window_event_example.js +++ b/src-docs/src/views/window_event/window_event_example.js @@ -6,9 +6,12 @@ import { GuideSectionTypes, } from '../../components'; +import { + EuiWindowEvent +} from '../../../../src/services'; + import { EuiCode, - EuiWindowEvent, } from '../../../../src/components'; import { WindowEvent } from './window_event'; @@ -28,12 +31,13 @@ export const WindowEventExample = { text: (

- Use an EuiWindowEvent to safely and declaratively manage adding and auto-removing - event listeners to the window. + Use an EuiWindowEvent to safely and declaratively manage adding and auto-removing event listeners + to the window. This is preferable to setting up your own window event listeners because it will remove + old listeners when your component unmounts, preventing you from accidentally leaving them around forever.

- This is preferable to setting up your own window event listeners because it will remove old - events when your component unmounts, preventing you from accidentally leaving them around forever. + To add a window event listener, render this component with two props: the DOM event name (e.g. click, keydown) + and a handler function that will be called when the event is triggered on the window element.

), From f8b6f3e8698534c704eb8ece7b7b68ea53b8dfc4 Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Tue, 21 Aug 2018 12:24:43 -0400 Subject: [PATCH 10/11] Rewrites examples for WindowEvent docs --- .../views/window_event/basic_window_event.js | 31 +++++ .../window_event/modal_example_container.js | 50 +++++++ .../src/views/window_event/mouse_position.js | 43 ++++++ .../src/views/window_event/window_event.js | 54 -------- .../window_event/window_event_conflict.js | 65 +++++++++ .../window_event/window_event_example.js | 123 ++++++++++++++---- 6 files changed, 285 insertions(+), 81 deletions(-) create mode 100644 src-docs/src/views/window_event/basic_window_event.js create mode 100644 src-docs/src/views/window_event/modal_example_container.js create mode 100644 src-docs/src/views/window_event/mouse_position.js delete mode 100644 src-docs/src/views/window_event/window_event.js create mode 100644 src-docs/src/views/window_event/window_event_conflict.js diff --git a/src-docs/src/views/window_event/basic_window_event.js b/src-docs/src/views/window_event/basic_window_event.js new file mode 100644 index 00000000000..27d5d0b7c79 --- /dev/null +++ b/src-docs/src/views/window_event/basic_window_event.js @@ -0,0 +1,31 @@ +import React from 'react'; + +import { + EuiModal, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask +} from '../../../../src/components'; + +import { ModalExample } from './modal_example_container'; + +const BasicModal = ({ onClose }) => ( + + + + + Example modal + + + +

This modal closes when you press ESC, using a window event listener.

+
+
+
+); + +export const BasicWindowEvent = () => ; diff --git a/src-docs/src/views/window_event/modal_example_container.js b/src-docs/src/views/window_event/modal_example_container.js new file mode 100644 index 00000000000..565b6e4d1ae --- /dev/null +++ b/src-docs/src/views/window_event/modal_example_container.js @@ -0,0 +1,50 @@ +import React, { Component } from 'react'; +import { + EuiButton, +} from '../../../../src/components'; + +import { + EuiWindowEvent, +} from '../../../../src/services'; + +export class ModalExample extends Component { + constructor(props) { + super(props); + + this.state = { + open: false + }; + + this.open = this.open.bind(this); + this.close = this.close.bind(this); + this.closeOnEscape = this.closeOnEscape.bind(this); + } + + open() { + this.setState({ open: true }); + } + + close() { + if (this.state.open) { + this.setState({ open: false }); + } + } + + closeOnEscape({ key }) { + if (key === 'Escape') { + this.close(); + } + } + + render() { + const { modal: Modal, buttonText = 'Open Modal' } = this.props; + const button = {buttonText}; + + return ( +
+ + {this.state.open ? : button} +
+ ); + } +} \ No newline at end of file diff --git a/src-docs/src/views/window_event/mouse_position.js b/src-docs/src/views/window_event/mouse_position.js new file mode 100644 index 00000000000..8116203e2ee --- /dev/null +++ b/src-docs/src/views/window_event/mouse_position.js @@ -0,0 +1,43 @@ +import React, { Component } from 'react'; + +import { + EuiSwitch, + EuiDescriptionList, + EuiSpacer +} from '../../../../src/components'; + +import { + EuiWindowEvent, +} from '../../../../src/services'; + +export class MousePosition extends Component { + + state = { + tracking: false, + coordinates: {} + }; + + onSwitchChange = () => this.setState((state) => ({ tracking: !state.tracking })); + + onMouseMove = ({ clientX, clientY }) => this.setState({ coordinates: { clientX, clientY } }); + + render() { + const listItems = [ + { title: 'Position X', description: this.state.coordinates.clientX || '??' }, + { title: 'Position Y', description: this.state.coordinates.clientY || '??' } + ]; + return ( +
+ + {this.state.tracking ? : null} + + + +
+ ); + } +} \ No newline at end of file diff --git a/src-docs/src/views/window_event/window_event.js b/src-docs/src/views/window_event/window_event.js deleted file mode 100644 index 3c60d6b9e73..00000000000 --- a/src-docs/src/views/window_event/window_event.js +++ /dev/null @@ -1,54 +0,0 @@ -import React, { Component } from 'react'; - -import { - EuiIcon, - EuiButton, - EuiSpacer, -} from '../../../../src/components'; - -import { - EuiWindowEvent, -} from '../../../../src/services'; - -export class WindowEvent extends Component { - constructor(props) { - super(props); - - this.state = { - stars: [] - }; - this.add = this.add.bind(this); - this.remove = this.remove.bind(this); - } - - add() { - this.setState((state) => ({ stars: [...state.stars, ] })); - } - - remove({ key }) { - if (key === 'Backspace' || key === 'Delete') { - this.setState((state) => ({ stars: state.stars.slice(0, -1) })); - return; - } - } - - render() { - const { stars } = this.state; - return ( -
- - Add a Star - - - -

To remove a star, press the backspace or delete key.

- - - -
- {stars.map((star, i) => {star})} -
-
- ); - } -} diff --git a/src-docs/src/views/window_event/window_event_conflict.js b/src-docs/src/views/window_event/window_event_conflict.js new file mode 100644 index 00000000000..5c683b5081d --- /dev/null +++ b/src-docs/src/views/window_event/window_event_conflict.js @@ -0,0 +1,65 @@ +import React from 'react'; + +import { + EuiModal, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, + EuiFieldText, + EuiSpacer +} from '../../../../src/components'; + +import { ModalExample } from './modal_example_container'; + +class ConflictModal extends React.Component { + + constructor(props) { + super(props); + + this.state = { + inputValue: '' + }; + } + + updateInputValue = e => this.setState({ inputValue: e.target.value }); + + clearInputValueOnEscape = e => { + if (e.key === 'Escape') { + this.setState({ inputValue: '' }); + e.stopPropagation(); + } + } + + render() { + return ( + + + + + Example modal + + + + + +

While typing in this field, ESC will clear the field.

+ +

Otherwise, the event bubbles up to the window and ESC closes the modal.

+
+
+
+ ); + } +} + +export const WindowEventConflict = () => ( + +); diff --git a/src-docs/src/views/window_event/window_event_example.js b/src-docs/src/views/window_event/window_event_example.js index 43689a76a6c..ef3ecf48379 100644 --- a/src-docs/src/views/window_event/window_event_example.js +++ b/src-docs/src/views/window_event/window_event_example.js @@ -12,36 +12,105 @@ import { import { EuiCode, + EuiCallOut, + EuiSpacer } from '../../../../src/components'; -import { WindowEvent } from './window_event'; -const source = require('!!raw-loader!./window_event'); -const html = renderToHtml(WindowEvent); +import { BasicWindowEvent } from './basic_window_event'; +const basicSource = require('!!raw-loader!./basic_window_event'); +const basicHtml = renderToHtml(BasicWindowEvent); + +import { WindowEventConflict } from './window_event_conflict'; +const conflictSource = require('!!raw-loader!./window_event_conflict'); +const conflictHtml = renderToHtml(WindowEventConflict); + +import { MousePosition } from './mouse_position'; +const mousePositionSource = require('!!raw-loader!./mouse_position'); +const mousePositionHtml = renderToHtml(MousePosition); export const WindowEventExample = { title: 'Window Events', - sections: [{ - source: [{ - type: GuideSectionTypes.JS, - code: source, - }, { - type: GuideSectionTypes.HTML, - code: html, - }], - text: ( -
-

- Use an EuiWindowEvent to safely and declaratively manage adding and auto-removing event listeners - to the window. This is preferable to setting up your own window event listeners because it will remove - old listeners when your component unmounts, preventing you from accidentally leaving them around forever. -

-

- To add a window event listener, render this component with two props: the DOM event name (e.g. click, keydown) - and a handler function that will be called when the event is triggered on the window element. -

-
- ), - components: { EuiWindowEvent }, - demo: , - }], + sections: [ + { + title: 'Basic example: closing a modal on escape', + source: [{ + type: GuideSectionTypes.JS, + code: basicSource, + }, { + type: GuideSectionTypes.HTML, + code: basicHtml, + }], + text: ( +
+

+ Use an EuiWindowEvent to safely and declaratively manage adding and auto-removing event listeners + to the window. This is preferable to setting up your own window event listeners because it will remove + old listeners when your component unmounts, preventing you from accidentally leaving them around forever. +

+

+ This modal example registers a listener on the keydown event and listens for ESC key presses, + which closes the open modal. +

+
+ ), + components: { EuiWindowEvent }, + demo: , + }, + { + title: 'Avoiding event conflicts', + source: [{ + type: GuideSectionTypes.JS, + code: conflictSource, + }, { + type: GuideSectionTypes.HTML, + code: conflictHtml, + }], + text: ( +
+ +

+ Since window event listeners are global, they can conflict with other event listeners if you aren't careful. +

+
+ +

+ The safest and best way to avoid these conflicts is to use event.stopPropagation() at the + lowest, most specific level where you are responding to a DOM event. This will prevent the event from bubbling + up to the window, and the WindowEvent listener will never be triggered, avoiding the conflict. +

+
+ ), + components: { EuiWindowEvent }, + demo: , + }, + { + title: 'Tracking mouse position', + source: [{ + type: GuideSectionTypes.JS, + code: mousePositionSource, + }, { + type: GuideSectionTypes.HTML, + code: mousePositionHtml, + }], + text: ( +
+

+ For some DOM events, you have to listen on the window. One example of this is tracking mouse position. Below, + when you click the toggle switch, your mouse position is tracked. When you toggle off, tracking stops. +

+

+ If you were manually attaching window listeners, you might forget to remove the listener and be silently + responding to mouse events in the background for the life of your app. The WindowEvent component + manages that unmount/unregister process for you. +

+
+ ), + components: { EuiWindowEvent }, + demo: , + } + ], }; From 870a759a6990a38bfa69583ef7ee4dfd14534640 Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Tue, 21 Aug 2018 23:18:36 -0400 Subject: [PATCH 11/11] Adds EuiWindowEvent props to docs --- src-docs/src/views/window_event/window_event_example.js | 1 + src/services/window_event/window_event.js | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src-docs/src/views/window_event/window_event_example.js b/src-docs/src/views/window_event/window_event_example.js index ef3ecf48379..159a1f88f74 100644 --- a/src-docs/src/views/window_event/window_event_example.js +++ b/src-docs/src/views/window_event/window_event_example.js @@ -54,6 +54,7 @@ export const WindowEventExample = { ), components: { EuiWindowEvent }, + props: { EuiWindowEvent }, demo: , }, { diff --git a/src/services/window_event/window_event.js b/src/services/window_event/window_event.js index 394381c2839..8bf407863ce 100644 --- a/src/services/window_event/window_event.js +++ b/src/services/window_event/window_event.js @@ -1,11 +1,6 @@ import { Component } from 'react'; import PropTypes from 'prop-types'; -/** - * Adds and removes window events for you (renders null) - * Usage: - * - */ export default class WindowEvent extends Component { componentDidMount() { @@ -41,7 +36,7 @@ WindowEvent.displayName = 'WindowEvent'; WindowEvent.propTypes = { /** - * Type of event + * Type of valid DOM event */ event: PropTypes.string.isRequired, /**