/* @flow */
|
|
import { inBrowser, isIE9 } from 'core/util/index'
|
import { addClass, removeClass } from './class-util'
|
import { remove, extend, cached } from 'shared/util'
|
|
export function resolveTransition (def?: string | Object): ?Object {
|
if (!def) {
|
return
|
}
|
/* istanbul ignore else */
|
if (typeof def === 'object') {
|
const res = {}
|
if (def.css !== false) {
|
extend(res, autoCssTransition(def.name || 'v'))
|
}
|
extend(res, def)
|
return res
|
} else if (typeof def === 'string') {
|
return autoCssTransition(def)
|
}
|
}
|
|
const autoCssTransition: (name: string) => Object = cached(name => {
|
return {
|
enterClass: `${name}-enter`,
|
enterToClass: `${name}-enter-to`,
|
enterActiveClass: `${name}-enter-active`,
|
leaveClass: `${name}-leave`,
|
leaveToClass: `${name}-leave-to`,
|
leaveActiveClass: `${name}-leave-active`
|
}
|
})
|
|
export const hasTransition = inBrowser && !isIE9
|
const TRANSITION = 'transition'
|
const ANIMATION = 'animation'
|
|
// Transition property/event sniffing
|
export let transitionProp = 'transition'
|
export let transitionEndEvent = 'transitionend'
|
export let animationProp = 'animation'
|
export let animationEndEvent = 'animationend'
|
if (hasTransition) {
|
/* istanbul ignore if */
|
if (window.ontransitionend === undefined &&
|
window.onwebkittransitionend !== undefined
|
) {
|
transitionProp = 'WebkitTransition'
|
transitionEndEvent = 'webkitTransitionEnd'
|
}
|
if (window.onanimationend === undefined &&
|
window.onwebkitanimationend !== undefined
|
) {
|
animationProp = 'WebkitAnimation'
|
animationEndEvent = 'webkitAnimationEnd'
|
}
|
}
|
|
// binding to window is necessary to make hot reload work in IE in strict mode
|
const raf = inBrowser
|
? window.requestAnimationFrame
|
? window.requestAnimationFrame.bind(window)
|
: setTimeout
|
: /* istanbul ignore next */ fn => fn()
|
|
export function nextFrame (fn: Function) {
|
raf(() => {
|
raf(fn)
|
})
|
}
|
|
export function addTransitionClass (el: any, cls: string) {
|
const transitionClasses = el._transitionClasses || (el._transitionClasses = [])
|
if (transitionClasses.indexOf(cls) < 0) {
|
transitionClasses.push(cls)
|
addClass(el, cls)
|
}
|
}
|
|
export function removeTransitionClass (el: any, cls: string) {
|
if (el._transitionClasses) {
|
remove(el._transitionClasses, cls)
|
}
|
removeClass(el, cls)
|
}
|
|
export function whenTransitionEnds (
|
el: Element,
|
expectedType: ?string,
|
cb: Function
|
) {
|
const { type, timeout, propCount } = getTransitionInfo(el, expectedType)
|
if (!type) return cb()
|
const event: string = type === TRANSITION ? transitionEndEvent : animationEndEvent
|
let ended = 0
|
const end = () => {
|
el.removeEventListener(event, onEnd)
|
cb()
|
}
|
const onEnd = e => {
|
if (e.target === el) {
|
if (++ended >= propCount) {
|
end()
|
}
|
}
|
}
|
setTimeout(() => {
|
if (ended < propCount) {
|
end()
|
}
|
}, timeout + 1)
|
el.addEventListener(event, onEnd)
|
}
|
|
const transformRE = /\b(transform|all)(,|$)/
|
|
export function getTransitionInfo (el: Element, expectedType?: ?string): {
|
type: ?string;
|
propCount: number;
|
timeout: number;
|
hasTransform: boolean;
|
} {
|
const styles: any = window.getComputedStyle(el)
|
// JSDOM may return undefined for transition properties
|
const transitionDelays: Array<string> = (styles[transitionProp + 'Delay'] || '').split(', ')
|
const transitionDurations: Array<string> = (styles[transitionProp + 'Duration'] || '').split(', ')
|
const transitionTimeout: number = getTimeout(transitionDelays, transitionDurations)
|
const animationDelays: Array<string> = (styles[animationProp + 'Delay'] || '').split(', ')
|
const animationDurations: Array<string> = (styles[animationProp + 'Duration'] || '').split(', ')
|
const animationTimeout: number = getTimeout(animationDelays, animationDurations)
|
|
let type: ?string
|
let timeout = 0
|
let propCount = 0
|
/* istanbul ignore if */
|
if (expectedType === TRANSITION) {
|
if (transitionTimeout > 0) {
|
type = TRANSITION
|
timeout = transitionTimeout
|
propCount = transitionDurations.length
|
}
|
} else if (expectedType === ANIMATION) {
|
if (animationTimeout > 0) {
|
type = ANIMATION
|
timeout = animationTimeout
|
propCount = animationDurations.length
|
}
|
} else {
|
timeout = Math.max(transitionTimeout, animationTimeout)
|
type = timeout > 0
|
? transitionTimeout > animationTimeout
|
? TRANSITION
|
: ANIMATION
|
: null
|
propCount = type
|
? type === TRANSITION
|
? transitionDurations.length
|
: animationDurations.length
|
: 0
|
}
|
const hasTransform: boolean =
|
type === TRANSITION &&
|
transformRE.test(styles[transitionProp + 'Property'])
|
return {
|
type,
|
timeout,
|
propCount,
|
hasTransform
|
}
|
}
|
|
function getTimeout (delays: Array<string>, durations: Array<string>): number {
|
/* istanbul ignore next */
|
while (delays.length < durations.length) {
|
delays = delays.concat(delays)
|
}
|
|
return Math.max.apply(null, durations.map((d, i) => {
|
return toMs(d) + toMs(delays[i])
|
}))
|
}
|
|
// Old versions of Chromium (below 61.0.3163.100) formats floating pointer numbers
|
// in a locale-dependent way, using a comma instead of a dot.
|
// If comma is not replaced with a dot, the input will be rounded down (i.e. acting
|
// as a floor function) causing unexpected behaviors
|
function toMs (s: string): number {
|
return Number(s.slice(0, -1).replace(',', '.')) * 1000
|
}
|