]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(vapor): v-show work on components
authorEvan You <evan@vuejs.org>
Sun, 2 Feb 2025 04:18:51 +0000 (12:18 +0800)
committerEvan You <evan@vuejs.org>
Sun, 2 Feb 2025 04:18:51 +0000 (12:18 +0800)
packages/runtime-dom/src/directives/vShow.ts
packages/runtime-vapor/__tests__/directives/vShow.spec.ts
packages/runtime-vapor/src/directives/vShow.ts

index 2303d8197349aa6b6c11e615722141a41619b008..531c47c8c8964ba1fa2af05001fedd7e012bb4d9 100644 (file)
@@ -13,8 +13,8 @@ export const vShowHidden: unique symbol = Symbol('_vsh')
  */
 export interface VShowElement extends HTMLElement {
   // _vod = vue original display
-  [vShowOriginalDisplay]: string
-  [vShowHidden]: boolean
+  [vShowOriginalDisplay]?: string
+  [vShowHidden]?: boolean
 }
 
 export const vShow: ObjectDirective<VShowElement> & { name?: 'show' } = {
@@ -58,7 +58,7 @@ if (__DEV__) {
 }
 
 function setDisplay(el: VShowElement, value: unknown): void {
-  el.style.display = value ? el[vShowOriginalDisplay] : 'none'
+  el.style.display = value ? el[vShowOriginalDisplay]! : 'none'
   el[vShowHidden] = !value
 }
 
index 7a22ebffeb0462437c6f585ffdd4c786beb0e9c1..2877e5c144296af99aa38db5cbe4a8254317fa0b 100644 (file)
@@ -1,14 +1,13 @@
 import {
+  applyVShow,
   children,
   createComponent,
+  createIf,
+  defineVaporComponent,
   on,
   template,
-  // @ts-expect-error
-  vShow,
-  // @ts-expect-error
-  withDirectives,
 } from '../../src'
-import { nextTick, ref } from 'vue'
+import { type VShowElement, nextTick, ref } from 'vue'
 import { describe, expect, test } from 'vitest'
 import { makeRender } from '../_utils'
 
@@ -26,12 +25,12 @@ const createDemo = (defaultValue: boolean) =>
     const n0 = t0()
     const n1 = children(n0, 0)
     const n2 = children(n0, 1)
-    withDirectives(n2, [[vShow, () => visible.value]])
+    applyVShow(n2 as VShowElement, () => visible.value)
     on(n1 as HTMLElement, 'click', () => handleClick)
     return n0
   })
 
-describe.todo('directive: v-show', () => {
+describe('directive: v-show', () => {
   test('basic', async () => {
     const { host } = createDemo(true).render()
     const btn = host.querySelector('button')
@@ -56,36 +55,87 @@ describe.todo('directive: v-show', () => {
 
   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
+      setup() {
+        return t0()
       },
     })
 
     const { 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]
+      setup() {
+        const n1 = createComponent(Child, null, null, true)
+        applyVShow(n1, () => visible.value)
+        return n1
       },
     }).render()
 
-    expect(host.innerHTML).toBe('<button>toggle</button><div>child</div>')
+    expect(host.innerHTML).toBe('<div>child</div>')
 
-    const btn = host.querySelector('button')
-    btn?.click()
+    visible.value = !visible.value
+    await nextTick()
+    expect(host.innerHTML).toBe('<div style="display: none;">child</div>')
+  })
+
+  test('warn on non-single-element-root component', () => {
+    const Child = defineVaporComponent({
+      setup() {
+        return document.createTextNode('b')
+      },
+    })
+    define({
+      setup() {
+        const n1 = createComponent(Child)
+        applyVShow(n1, () => true)
+        return n1
+      },
+    }).render()
+    expect(
+      'v-show used on component with non-single-element root node',
+    ).toHaveBeenWarned()
+  })
+
+  test('should work on component with dynamic fragment root', async () => {
+    const t0 = template('<div>child</div>')
+    const t1 = template('<span>child</span>')
+    const childIf = ref(true)
+    const visible = ref(true)
+
+    const { component: Child } = define({
+      setup() {
+        return createIf(
+          () => childIf.value,
+          () => t0(),
+          () => t1(),
+        )
+      },
+    })
+
+    const { host } = define({
+      setup() {
+        const n1 = createComponent(Child, null, null, true)
+        applyVShow(n1, () => visible.value)
+        return n1
+      },
+    }).render()
+
+    expect(host.innerHTML).toBe('<div>child</div><!--if-->')
+
+    visible.value = !visible.value
+    await nextTick()
+    expect(host.innerHTML).toBe(
+      '<div style="display: none;">child</div><!--if-->',
+    )
+
+    childIf.value = !childIf.value
     await nextTick()
     expect(host.innerHTML).toBe(
-      '<button>toggle</button><div style="display: none;">child</div>',
+      '<span style="display: none;">child</span><!--if-->',
     )
+
+    visible.value = !visible.value
+    await nextTick()
+    expect(host.innerHTML).toBe('<span style="">child</span><!--if-->')
   })
 })
index 6a21e979c18abe4f65c8079a0653a477b8094fa7..ac4c066b71d6cbc78ed3d36fe1daf7bf0c2ba4b7 100644 (file)
@@ -2,15 +2,55 @@ import {
   type VShowElement,
   vShowHidden,
   vShowOriginalDisplay,
+  warn,
 } from '@vue/runtime-dom'
 import { renderEffect } from '../renderEffect'
+import { isVaporComponent } from '../component'
+import { type Block, DynamicFragment } from '../block'
+import { isArray } from '@vue/shared'
 
-export function applyVShow(el: VShowElement, source: () => any): void {
-  el[vShowOriginalDisplay] = el.style.display === 'none' ? '' : el.style.display
-  renderEffect(() => setDisplay(el, source()))
+export function applyVShow(target: Block, source: () => any): void {
+  if (isVaporComponent(target)) {
+    return applyVShow(target.block, source)
+  }
+
+  if (isArray(target) && target.length === 1) {
+    return applyVShow(target[0], source)
+  }
+
+  if (target instanceof DynamicFragment) {
+    const update = target.update
+    target.update = (render, key) => {
+      update.call(target, render, key)
+      setDisplay(target, source())
+    }
+  }
+
+  renderEffect(() => setDisplay(target, source()))
 }
 
-function setDisplay(el: VShowElement, value: unknown): void {
-  el.style.display = value ? el[vShowOriginalDisplay] : 'none'
-  el[vShowHidden] = !value
+function setDisplay(target: Block, value: unknown): void {
+  if (isVaporComponent(target)) {
+    return setDisplay(target, value)
+  }
+  if (isArray(target) && target.length === 1) {
+    return setDisplay(target[0], value)
+  }
+  if (target instanceof DynamicFragment) {
+    return setDisplay(target.nodes, value)
+  }
+  if (target instanceof Element) {
+    const el = target as VShowElement
+    if (!(vShowOriginalDisplay in el)) {
+      el[vShowOriginalDisplay] =
+        el.style.display === 'none' ? '' : el.style.display
+    }
+    el.style.display = value ? el[vShowOriginalDisplay]! : 'none'
+    el[vShowHidden] = !value
+  } else if (__DEV__) {
+    warn(
+      `v-show used on component with non-single-element root node ` +
+        `and will be ignored.`,
+    )
+  }
 }