2
0
Эх сурвалжийг харах

feat(runtime-vapor): `v-show` for component (#188)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
Jevon 2 жил өмнө
parent
commit
cd582949f2

+ 44 - 1
packages/runtime-vapor/__tests__/directives/vShow.spec.ts

@@ -1,4 +1,11 @@
-import { children, on, template, vShow, withDirectives } from '../../src'
+import {
+  children,
+  createComponent,
+  on,
+  template,
+  vShow,
+  withDirectives,
+} from '../../src'
 import { nextTick, ref } from 'vue'
 import { describe, expect, test } from 'vitest'
 import { makeRender } from '../_utils'
@@ -43,4 +50,40 @@ describe('directive: v-show', () => {
     await nextTick()
     expect(h1?.style.display).toBe('')
   })
+
+  test('should work on component', async () => {
+    const t0 = template('<div>child</div>')
+    const t1 = template('<button>toggle</button>')
+    const n0 = t0()
+    const visible = ref(true)
+
+    function handleClick() {
+      visible.value = !visible.value
+    }
+    const { component: Child } = define({
+      render() {
+        return n0
+      },
+    })
+
+    const { instance, host } = define({
+      render() {
+        const n1 = t1()
+        const n2 = createComponent(Child, [], null, null, true)
+        withDirectives(n2, [[vShow, () => visible.value]])
+        on(n1 as HTMLElement, 'click', () => handleClick)
+        return [n1, n2]
+      },
+    }).render()
+
+    expect(host.innerHTML).toBe('<button>toggle</button><div>child</div>')
+    expect(instance.dirs.get(n0)![0].dir).toBe(vShow)
+
+    const btn = host.querySelector('button')
+    btn?.click()
+    await nextTick()
+    expect(host.innerHTML).toBe(
+      '<button>toggle</button><div style="display: none;">child</div>',
+    )
+  })
 })

+ 36 - 6
packages/runtime-vapor/src/directives.ts

@@ -1,8 +1,14 @@
 import { isFunction } from '@vue/shared'
-import { type ComponentInternalInstance, currentInstance } from './component'
+import {
+  type ComponentInternalInstance,
+  currentInstance,
+  isVaporComponent,
+} from './component'
 import { pauseTracking, resetTracking, traverse } from '@vue/reactivity'
 import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
 import { renderEffect } from './renderEffect'
+import { warn } from './warning'
+import { normalizeBlock } from './dom/element'
 
 export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
 
@@ -62,13 +68,22 @@ export type DirectiveArguments = Array<
     ]
 >
 
-export function withDirectives<T extends Node>(
-  node: T,
+export function withDirectives<T extends ComponentInternalInstance | Node>(
+  nodeOrComponent: T,
   directives: DirectiveArguments,
 ): T {
   if (!currentInstance) {
-    // TODO warning
-    return node
+    __DEV__ && warn(`withDirectives can only be used inside render functions.`)
+    return nodeOrComponent
+  }
+
+  let node: Node
+  if (isVaporComponent(nodeOrComponent)) {
+    const root = getComponentNode(nodeOrComponent)
+    if (!root) return nodeOrComponent
+    node = root
+  } else {
+    node = nodeOrComponent
   }
 
   const instance = currentInstance
@@ -109,7 +124,22 @@ export function withDirectives<T extends Node>(
     }
   }
 
-  return node
+  return nodeOrComponent
+}
+
+function getComponentNode(component: ComponentInternalInstance) {
+  if (!component.block) return
+
+  const nodes = normalizeBlock(component.block)
+  if (nodes.length !== 1) {
+    warn(
+      `Runtime directive used on component with non-element root node. ` +
+        `The directives will not function as intended.`,
+    )
+    return
+  }
+
+  return nodes[0]
 }
 
 export function invokeDirectiveHook(