Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eventStack): support for different targets #2094

Merged
merged 5 commits into from
Sep 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/addons/Portal/Portal.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,8 @@ class Portal extends Component {

// when re-rendering, first remove listeners before re-adding them to the new node
if (this.portalNode) {
this.portalNode.removeEventListener('mouseleave', this.handlePortalMouseLeave)
this.portalNode.removeEventListener('mouseenter', this.handlePortalMouseEnter)
eventStack.unsub('mouseleave', this.handlePortalMouseLeave, { target: this.portalNode })
eventStack.unsub('mouseenter', this.handlePortalMouseEnter, { target: this.portalNode })
}

ReactDOM.unstable_renderSubtreeIntoContainer(
Expand All @@ -360,8 +360,8 @@ class Portal extends Component {
() => {
this.portalNode = this.rootNode.firstElementChild

this.portalNode.addEventListener('mouseleave', this.handlePortalMouseLeave)
this.portalNode.addEventListener('mouseenter', this.handlePortalMouseEnter)
eventStack.sub('mouseleave', this.handlePortalMouseLeave, { target: this.portalNode })
eventStack.sub('mouseenter', this.handlePortalMouseEnter, { target: this.portalNode })
},
)
}
Expand Down Expand Up @@ -397,8 +397,8 @@ class Portal extends Component {
ReactDOM.unmountComponentAtNode(this.rootNode)
this.rootNode.parentNode.removeChild(this.rootNode)

this.portalNode.removeEventListener('mouseleave', this.handlePortalMouseLeave)
this.portalNode.removeEventListener('mouseenter', this.handlePortalMouseEnter)
eventStack.unsub('mouseleave', this.handlePortalMouseLeave, { target: this.portalNode })
eventStack.unsub('mouseenter', this.handlePortalMouseEnter, { target: this.portalNode })

this.rootNode = null
this.portalNode = null
Expand Down
4 changes: 2 additions & 2 deletions src/addons/Responsive/Responsive.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ export default class Responsive extends Component {
}

componentDidMount() {
eventStack.sub('resize', this.handleResize)
eventStack.sub('resize', this.handleResize, { target: 'window' })
}

componentWillUnmount() {
eventStack.unsub('resize', this.handleResize)
eventStack.unsub('resize', this.handleResize, { target: 'window' })
}

// ----------------------------------------
Expand Down
9 changes: 4 additions & 5 deletions src/behaviors/Visibility/Visibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import React, { Component } from 'react'

import {
eventStack,
customPropTypes,
getElementType,
getUnhandledProps,
Expand Down Expand Up @@ -196,20 +197,18 @@ export default class Visibility extends Component {

componentDidMount() {
if (!isBrowser) return

const { context, fireOnMount } = this.props

this.pageYOffset = window.pageYOffset
context.addEventListener('scroll', this.handleScroll)
eventStack.sub('scroll', this.handleScroll, { target: context })

if (fireOnMount) this.update()
}

componentWillUnmount() {
if (!isBrowser) return

const { context } = this.props
context.removeEventListener('scroll', this.handleScroll)

eventStack.unsub('scroll', this.handleScroll, { target: context })
}

// ----------------------------------------
Expand Down
25 changes: 9 additions & 16 deletions src/lib/eventStack.js → src/lib/eventStack/EventTarget.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import _ from 'lodash'
import isBrowser from './isBrowser'

const windowEvents = ['resize']

class EventStack {
export default class EventTarget {
_handlers = {}
_pools = {}

constructor(target) {
this.target = target
}

// ------------------------------------
// Utils
// ------------------------------------
Expand All @@ -33,28 +34,26 @@ class EventStack {
_listen = (name) => {
if (_.has(this._handlers, name)) return
const handler = this._emit(name)
const target = _.includes(windowEvents, name) ? window : document

target.addEventListener(name, handler)
this.target.addEventListener(name, handler)
this._handlers[name] = handler
}

_unlisten = (name) => {
if (_.some(this._pools, name)) return
const { [name]: handler } = this._handlers
const target = _.includes(windowEvents, name) ? window : document

target.removeEventListener(name, handler)
this.target.removeEventListener(name, handler)
delete this._handlers[name]
}

// ------------------------------------
// Pub/sub
// ------------------------------------

sub = (name, handlers, pool = 'default') => {
if (!isBrowser) return
empty = () => _.isEmpty(this._handlers)

sub = (name, handlers, pool = 'default') => {
const events = _.uniq([
..._.get(this._pools, `${pool}.${name}`, []),
...this._normalize(handlers),
Expand All @@ -65,8 +64,6 @@ class EventStack {
}

unsub = (name, handlers, pool = 'default') => {
if (!isBrowser) return

const events = _.without(
_.get(this._pools, `${pool}.${name}`, []),
...this._normalize(handlers),
Expand All @@ -81,7 +78,3 @@ class EventStack {
this._unlisten(name)
}
}

const instance = new EventStack()

export default instance
61 changes: 61 additions & 0 deletions src/lib/eventStack/eventStack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import _ from 'lodash'

import isBrowser from '../isBrowser'
import EventTarget from './EventTarget'
import normalizeTarget from './normalizeTarget'

class EventStack {
_eventTargets = {}
_targets = []

// ------------------------------------
// Target utils
// ------------------------------------

_find = (target) => {
const normalized = normalizeTarget(target)
let index = this._targets.indexOf(normalized)

if (index !== -1) return this._eventTargets[index]

index = this._targets.push(normalized) - 1
this._eventTargets[index] = new EventTarget(normalized)

return this._eventTargets[index]
}

_remove = (target) => {
const normalized = normalizeTarget(target)
const index = this._targets.indexOf(normalized)

this._targets = _.without(this._targets, normalized)
delete this._eventTargets[index]
}

// ------------------------------------
// Pub/sub
// ------------------------------------

sub = (name, handlers, options = {}) => {
if (!isBrowser) return

const { target = document, pool = 'default' } = options
const eventTarget = this._find(target)

eventTarget.sub(name, handlers, pool)
}

unsub = (name, handlers, options = {}) => {
if (!isBrowser) return

const { target = document, pool = 'default' } = options
const eventTarget = this._find(target)

eventTarget.unsub(name, handlers, pool)
if (eventTarget.empty()) this._remove(target)
}
}

const instance = new EventStack()

export default instance
1 change: 1 addition & 0 deletions src/lib/eventStack/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default from './eventStack'
13 changes: 13 additions & 0 deletions src/lib/eventStack/normalizeTarget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Normalizes `target` for EventStack, because `target` can be passed as `boolean` or `string`.
*
* @param {boolean|string|HTMLElement|Window} target Value for normalization.
* @return {HTMLElement|Window} A DOM node.
*/
const normalizeTarget = (target) => {
if (target === 'document') return document
if (target === 'window') return window
return target || document
}

export default normalizeTarget
19 changes: 10 additions & 9 deletions src/modules/Popup/Popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'
import React, { Component } from 'react'

import {
eventStack,
childrenUtils,
customPropTypes,
getElementType,
Expand Down Expand Up @@ -276,7 +277,8 @@ export default class Popup extends Component {

hideOnScroll = () => {
this.setState({ closed: true })
window.removeEventListener('scroll', this.hideOnScroll)

eventStack.unsub('scroll', this.hideOnScroll, { target: window })
setTimeout(() => this.setState({ closed: false }), 50)
}

Expand All @@ -296,19 +298,18 @@ export default class Popup extends Component {

handlePortalMount = (e) => {
debug('handlePortalMount()')
if (this.props.hideOnScroll) {
window.addEventListener('scroll', this.hideOnScroll)
}
const { hideOnScroll } = this.props

const { onMount } = this.props
if (onMount) onMount(e, this.props)
if (hideOnScroll) eventStack.sub('scroll', this.hideOnScroll, { target: window })
_.invoke(this.props, 'onMount', e, this.props)
}

handlePortalUnmount = (e) => {
debug('handlePortalUnmount()')
window.removeEventListener('scroll', this.hideOnScroll)
const { onUnmount } = this.props
if (onUnmount) onUnmount(e, this.props)
const { hideOnScroll } = this.props

if (hideOnScroll) eventStack.unsub('scroll', this.hideOnScroll, { target: window })
_.invoke(this.props, 'onUnmount', e, this.props)
}

handlePopupRef = (popupRef) => {
Expand Down
5 changes: 3 additions & 2 deletions src/modules/Sticky/Sticky.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import React, { Component } from 'react'

import {
eventStack,
customPropTypes,
getElementType,
getUnhandledProps,
Expand Down Expand Up @@ -128,13 +129,13 @@ export default class Sticky extends Component {
addListener = () => {
const { scrollContext } = this.props

scrollContext.addEventListener('scroll', this.handleUpdate)
eventStack.sub('scroll', this.handleUpdate, { target: scrollContext })
}

removeListener = () => {
const { scrollContext } = this.props

scrollContext.removeEventListener('scroll', this.handleUpdate)
eventStack.unsub('scroll', this.handleUpdate, { target: scrollContext })
}

// ----------------------------------------
Expand Down
Loading