Przeglądaj źródła

support transition on component with v-show in root node (fix #3431)

Evan You 9 lat temu
rodzic
commit
aab560e0d9

+ 27 - 7
src/platforms/web/runtime/directives/show.js

@@ -3,16 +3,36 @@
 import { isIE9 } from 'web/util/index'
 import { enter, leave } from '../modules/transition'
 
-// recursively search for possible transition defined inside the component root
-function locateNode (vnode: VNode): VNodeWithData {
-  return vnode.child && (!vnode.data || !vnode.data.transition)
-    ? locateNode(vnode.child._vnode)
-    : vnode
+// The v-show directive may appear on a component's parent vnode or its
+// inner vnode, and the transition wrapper may be defined outside or inside
+// the component. To cater for all possible cases, recursively search for
+// possible transition defined on component parent placeholder or inside
+// child component.
+function detectTransitionNode (vnode: VNode): VNodeWithData {
+  let parent = vnode
+  while ((parent = parent.parent)) {
+    if (hasTransition(parent)) {
+      return parent
+    }
+  }
+  if (hasTransition(vnode)) {
+    return vnode
+  }
+  while (vnode.child && (vnode = vnode.child._vnode)) {
+    if (hasTransition(vnode)) {
+      return vnode
+    }
+  }
+  return vnode
+}
+
+function hasTransition (vnode) {
+  return vnode.data && vnode.data.transition
 }
 
 export default {
   bind (el: any, { value }: VNodeDirective, vnode: VNodeWithData) {
-    vnode = locateNode(vnode)
+    vnode = detectTransitionNode(vnode)
     const transition = vnode.data && vnode.data.transition
     if (value && transition && transition.appear && !isIE9) {
       enter(vnode)
@@ -24,7 +44,7 @@ export default {
   update (el: any, { value, oldValue }: VNodeDirective, vnode: VNodeWithData) {
     /* istanbul ignore if */
     if (value === oldValue) return
-    vnode = locateNode(vnode)
+    vnode = detectTransitionNode(vnode)
     const transition = vnode.data && vnode.data.transition
     if (transition && !isIE9) {
       if (value) {

+ 39 - 0
test/unit/features/transition/transition.spec.js

@@ -558,6 +558,45 @@ if (!isIE9) {
       }).then(done)
     })
 
+    fit('transition with v-show, with transition outside and v-show inside', done => {
+      const vm = new Vue({
+        template: `
+          <div>
+            <transition name="test">
+              <test :ok="ok"></test>
+            </transition>
+          </div>
+        `,
+        data: { ok: true },
+        components: {
+          test: {
+            props: ['ok'],
+            template: `<div v-show="ok" class="test">foo</div>`
+          }
+        }
+      }).$mount(el)
+
+      // should not apply transition on initial render by default
+      expect(vm.$el.textContent).toBe('foo')
+      expect(vm.$el.children[0].style.display).toBe('')
+      vm.ok = false
+      waitForUpdate(() => {
+        expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
+      }).thenWaitFor(nextFrame).then(() => {
+        expect(vm.$el.children[0].className).toBe('test test-leave-active')
+      }).thenWaitFor(duration + 10).then(() => {
+        expect(vm.$el.children[0].style.display).toBe('none')
+        vm.ok = true
+      }).then(() => {
+        expect(vm.$el.children[0].style.display).toBe('')
+        expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
+      }).thenWaitFor(nextFrame).then(() => {
+        expect(vm.$el.children[0].className).toBe('test test-enter-active')
+      }).thenWaitFor(duration + 10).then(() => {
+        expect(vm.$el.children[0].className).toBe('test')
+      }).then(done)
+    })
+
     it('leaveCancelled (v-show only)', done => {
       const spy = jasmine.createSpy('leaveCancelled')
       const vm = new Vue({