123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- /* @flow */
- // Provides transition support for a single element/component.
- // supports transition mode (out-in / in-out)
- import { warn } from 'core/util/index'
- import { camelize, extend, isPrimitive } from 'shared/util'
- import {
- mergeVNodeHook,
- isAsyncPlaceholder,
- getFirstComponentChild
- } from 'core/vdom/helpers/index'
- export const transitionProps = {
- name: String,
- appear: Boolean,
- css: Boolean,
- mode: String,
- type: String,
- enterClass: String,
- leaveClass: String,
- enterToClass: String,
- leaveToClass: String,
- enterActiveClass: String,
- leaveActiveClass: String,
- appearClass: String,
- appearActiveClass: String,
- appearToClass: String,
- duration: [Number, String, Object]
- }
- // in case the child is also an abstract component, e.g. <keep-alive>
- // we want to recursively retrieve the real component to be rendered
- function getRealChild (vnode: ?VNode): ?VNode {
- const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
- if (compOptions && compOptions.Ctor.options.abstract) {
- return getRealChild(getFirstComponentChild(compOptions.children))
- } else {
- return vnode
- }
- }
- export function extractTransitionData (comp: Component): Object {
- const data = {}
- const options: ComponentOptions = comp.$options
- // props
- for (const key in options.propsData) {
- data[key] = comp[key]
- }
- // events.
- // extract listeners and pass them directly to the transition methods
- const listeners: ?Object = options._parentListeners
- for (const key in listeners) {
- data[camelize(key)] = listeners[key]
- }
- return data
- }
- function placeholder (h: Function, rawChild: VNode): ?VNode {
- if (/\d-keep-alive$/.test(rawChild.tag)) {
- return h('keep-alive', {
- props: rawChild.componentOptions.propsData
- })
- }
- }
- function hasParentTransition (vnode: VNode): ?boolean {
- while ((vnode = vnode.parent)) {
- if (vnode.data.transition) {
- return true
- }
- }
- }
- function isSameChild (child: VNode, oldChild: VNode): boolean {
- return oldChild.key === child.key && oldChild.tag === child.tag
- }
- const isNotTextNode = (c: VNode) => c.tag || isAsyncPlaceholder(c)
- const isVShowDirective = d => d.name === 'show'
- export default {
- name: 'transition',
- props: transitionProps,
- abstract: true,
- render (h: Function) {
- let children: any = this.$slots.default
- if (!children) {
- return
- }
- // filter out text nodes (possible whitespaces)
- children = children.filter(isNotTextNode)
- /* istanbul ignore if */
- if (!children.length) {
- return
- }
- // warn multiple elements
- if (process.env.NODE_ENV !== 'production' && children.length > 1) {
- warn(
- '<transition> can only be used on a single element. Use ' +
- '<transition-group> for lists.',
- this.$parent
- )
- }
- const mode: string = this.mode
- // warn invalid mode
- if (process.env.NODE_ENV !== 'production' &&
- mode && mode !== 'in-out' && mode !== 'out-in'
- ) {
- warn(
- 'invalid <transition> mode: ' + mode,
- this.$parent
- )
- }
- const rawChild: VNode = children[0]
- // if this is a component root node and the component's
- // parent container node also has transition, skip.
- if (hasParentTransition(this.$vnode)) {
- return rawChild
- }
- // apply transition data to child
- // use getRealChild() to ignore abstract components e.g. keep-alive
- const child: ?VNode = getRealChild(rawChild)
- /* istanbul ignore if */
- if (!child) {
- return rawChild
- }
- if (this._leaving) {
- return placeholder(h, rawChild)
- }
- // ensure a key that is unique to the vnode type and to this transition
- // component instance. This key will be used to remove pending leaving nodes
- // during entering.
- const id: string = `__transition-${this._uid}-`
- child.key = child.key == null
- ? child.isComment
- ? id + 'comment'
- : id + child.tag
- : isPrimitive(child.key)
- ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
- : child.key
- const data: Object = (child.data || (child.data = {})).transition = extractTransitionData(this)
- const oldRawChild: VNode = this._vnode
- const oldChild: VNode = getRealChild(oldRawChild)
- // mark v-show
- // so that the transition module can hand over the control to the directive
- if (child.data.directives && child.data.directives.some(isVShowDirective)) {
- child.data.show = true
- }
- if (
- oldChild &&
- oldChild.data &&
- !isSameChild(child, oldChild) &&
- !isAsyncPlaceholder(oldChild) &&
- // #6687 component root is a comment node
- !(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)
- ) {
- // replace old child transition data with fresh one
- // important for dynamic transitions!
- const oldData: Object = oldChild.data.transition = extend({}, data)
- // handle transition mode
- if (mode === 'out-in') {
- // return placeholder node and queue update when leave finishes
- this._leaving = true
- mergeVNodeHook(oldData, 'afterLeave', () => {
- this._leaving = false
- this.$forceUpdate()
- })
- return placeholder(h, rawChild)
- } else if (mode === 'in-out') {
- if (isAsyncPlaceholder(child)) {
- return oldRawChild
- }
- let delayedLeave
- const performLeave = () => { delayedLeave() }
- mergeVNodeHook(data, 'afterEnter', performLeave)
- mergeVNodeHook(data, 'enterCancelled', performLeave)
- mergeVNodeHook(oldData, 'delayLeave', leave => { delayedLeave = leave })
- }
- }
- return rawChild
- }
- }
|