diff --git a/lib/index.js b/lib/index.js index 3cc360f..b19bb88 100644 --- a/lib/index.js +++ b/lib/index.js @@ -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() { @@ -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 @@ -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)) { @@ -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)` @@ -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) @@ -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) @@ -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