Skip to content

Commit

Permalink
Fix: Separate state of internal business logic toggle and render togg…
Browse files Browse the repository at this point in the history
…le. The business logic is time-free to track what functions should or should not execute whereas the render toggle is time-dependent and relies on other processes--in this case the duration of animations. Closes #21.
  • Loading branch information
jasonkuhrt committed Apr 15, 2015
1 parent b2ef063 commit e8d7aa3
Showing 1 changed file with 69 additions and 44 deletions.
113 changes: 69 additions & 44 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ export default createClass({
getInitialState() {
return {
standing: 'above',
exited: true,
exiting: false
exited: true, // for animation-dependent rendering, should popover close/open?
exiting: false, // for tracking in-progress animations
toggle: false // for business logic tracking, should popover close/open?
}
},
getDefaultProps() {
Expand Down Expand Up @@ -137,7 +138,6 @@ export default createClass({

/* If the popover dose not fit into frameCrossLength then just position it to the `frameCrossStart`.
`popoverCrossLength` will now be forced to overflow into the `Frame` */
console.log(this.p2[axis.cross.end], hangingBufferLength)
if (pos.crossLength > frameCrossLength) {
log('popoverCrossLength does not fit frame.')
pos[axis.cross.start] = 0
Expand Down Expand Up @@ -221,7 +221,6 @@ export default createClass({
},
measureTargetBounds() {
let targetBounds = calcBounds(this.targetEl)
//log('measured target:', targetBounds)
if (!this.p1) {
this.p2 = targetBounds
} else if (!equalCoords(targetBounds, this.p2)) {
Expand All @@ -232,20 +231,51 @@ export default createClass({
},
componentDidMount() {
this.targetEl = findDOMNode(this)
this.trackPopover()
if (this.props.isOpen) this.enter()
},
componentDidUpdate() {
log('Component did update!')
this.trackPopover()
componentWillReceiveProps(propsNext) {
//log('Component received props!', propsNext)
let willOpen = !this.props.isOpen && propsNext.isOpen
let willClose = this.props.isOpen && !propsNext.isOpen

if (willOpen) {
if (this.state.exiting) this.animateExitStop()
this.setState({ toggle: true, exited: false })
} else if (willClose) {
/* TODO?: we currently do not setup any `entering` state flag because
nothing would really need to depend on it. Stopping animations is currently nothing
more than clearing some timeouts which are safe to clear even if undefined. The
primary reason for `exiting` state is for the `layerRender` logic. */
this.animateEnterStop()
this.setState({ toggle: false })
}

if (this.props.isOpen && this.state.exiting) {
clearTimeout(this.exitingAnimationTimer1)
clearTimeout(this.exitingAnimationTimer2)
return
} else if (this.props.isOpen || this.state.exiting || this.state.exited) return
},
componentDidUpdate(propsPrev, statePrev) {
//log('Component did update!')
let didOpen = !statePrev.toggle && this.state.toggle
let didClose = statePrev.toggle && !this.state.toggle

if (didOpen) this.enter()
else if (didClose) this.exit()
},
enter() {
log('enter!')
this.trackPopover()
this.animateEnter()
},
exit() {
log('exit!')
this.animateExit()
this.untrackPopover()
},
animateExitStop() {
clearTimeout(this.exitingAnimationTimer1)
clearTimeout(this.exitingAnimationTimer2)
this.setState({ exiting: false })
},
animateExit() {
this.setState({ exiting: true })

this.exitingAnimationTimer2 = setTimeout(() => {
setTimeout(() => {
this.containerEl.style.transform = `${popoverTranslateFuncDict[this.zone.flow]}(${this.zone.order * 50}px)`
Expand All @@ -257,17 +287,32 @@ export default createClass({
this.setState({ exited: true, exiting: false })
}, this.props.enterExitTransitionDurationMs)
},
animateEnterStop() {
clearTimeout(this.enteringAnimationTimer1)
clearTimeout(this.enteringAnimationTimer2)
},
animateEnter() {
/* Prepare `entering` style so that we can then animate it toward `entered`. */

this.containerEl.style.transform = `${popoverTranslateFuncDict[this.zone.flow]}(${this.zone.order * 50}px)`
this.containerEl.style.opacity = '0'

/* After initial layout apply transition animations. */

this.enteringAnimationTimer1 = setTimeout(() => {
this.tipEl.style.transition = 'transform 150ms ease-in'
this.containerEl.style.transitionProperty = 'top, left, opacity, transform'
this.containerEl.style.transitionDuration = '500ms'
this.containerEl.style.transitionTimingFunction = 'cubic-bezier(0.230, 1.000, 0.320, 1.000)'
this.enteringAnimationTimer2 = setTimeout(() => {
this.containerEl.style.opacity = '1'
this.containerEl.style.transform = 'translateY(0)'
}, 0)
}, 0)
},
trackPopover() {
let refreshIntervalMs = 200

if (!this.layerReactComponent) {
if (this.tracking) this.untrackPopover()
return
}
if (this.tracking) return
this.tracking = true
this.setState({ exited: false })

/* Get references to DOM elements. */

this.containerEl = findDOMNode(this.layerReactComponent)
Expand Down Expand Up @@ -298,28 +343,8 @@ export default createClass({
this.measureFrameBounds()
this.measureTargetBounds()
this.resolvePopoverLayout()

/* Prepare `entering` style so that we can then animate it toward `entered`. */

this.containerEl.style.transform = `${popoverTranslateFuncDict[this.zone.flow]}(${this.zone.order * 50}px)`
this.containerEl.style.opacity = '0'

/* After initial layout apply transition animations. */

setTimeout(() => {
this.tipEl.style.transition = 'transform 150ms ease-in'
this.containerEl.style.transitionProperty = 'top, left, opacity, transform'
this.containerEl.style.transitionDuration = '500ms'
this.containerEl.style.transitionTimingFunction = 'cubic-bezier(0.230, 1.000, 0.320, 1.000)'
setTimeout(() => {
this.containerEl.style.opacity = '1'
this.containerEl.style.transform = 'translateY(0)'
}, 0)
}, 0)
},
},
untrackPopover() {
if (!this.tracking) return
this.tracking = false
clearInterval(this.checkLayoutInterval)
this.frameEl.removeEventListener('scroll', this.onFrameScroll)
resizeEvent.off(this.frameEl, this.onFrameBoundsChange)
Expand Down Expand Up @@ -353,7 +378,7 @@ export default createClass({
clearInterval(this.checkLayoutInterval)
},
renderLayer() {
if (!this.props.isOpen && this.state.exited) return null
if (this.state.exited) return null

let { className = '', style = {} } = this.props

Expand Down

0 comments on commit e8d7aa3

Please sign in to comment.