mirror of https://github.com/vuejs/core.git
wip(vapor): v-show work on components
This commit is contained in:
parent
250127c13d
commit
d51403c1d3
|
@ -13,8 +13,8 @@ export const vShowHidden: unique symbol = Symbol('_vsh')
|
||||||
*/
|
*/
|
||||||
export interface VShowElement extends HTMLElement {
|
export interface VShowElement extends HTMLElement {
|
||||||
// _vod = vue original display
|
// _vod = vue original display
|
||||||
[vShowOriginalDisplay]: string
|
[vShowOriginalDisplay]?: string
|
||||||
[vShowHidden]: boolean
|
[vShowHidden]?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const vShow: ObjectDirective<VShowElement> & { name?: 'show' } = {
|
export const vShow: ObjectDirective<VShowElement> & { name?: 'show' } = {
|
||||||
|
@ -58,7 +58,7 @@ if (__DEV__) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDisplay(el: VShowElement, value: unknown): void {
|
function setDisplay(el: VShowElement, value: unknown): void {
|
||||||
el.style.display = value ? el[vShowOriginalDisplay] : 'none'
|
el.style.display = value ? el[vShowOriginalDisplay]! : 'none'
|
||||||
el[vShowHidden] = !value
|
el[vShowHidden] = !value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import {
|
import {
|
||||||
|
applyVShow,
|
||||||
children,
|
children,
|
||||||
createComponent,
|
createComponent,
|
||||||
|
createIf,
|
||||||
|
defineVaporComponent,
|
||||||
on,
|
on,
|
||||||
template,
|
template,
|
||||||
// @ts-expect-error
|
|
||||||
vShow,
|
|
||||||
// @ts-expect-error
|
|
||||||
withDirectives,
|
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { nextTick, ref } from 'vue'
|
import { type VShowElement, nextTick, ref } from 'vue'
|
||||||
import { describe, expect, test } from 'vitest'
|
import { describe, expect, test } from 'vitest'
|
||||||
import { makeRender } from '../_utils'
|
import { makeRender } from '../_utils'
|
||||||
|
|
||||||
|
@ -26,12 +25,12 @@ const createDemo = (defaultValue: boolean) =>
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const n1 = children(n0, 0)
|
const n1 = children(n0, 0)
|
||||||
const n2 = children(n0, 1)
|
const n2 = children(n0, 1)
|
||||||
withDirectives(n2, [[vShow, () => visible.value]])
|
applyVShow(n2 as VShowElement, () => visible.value)
|
||||||
on(n1 as HTMLElement, 'click', () => handleClick)
|
on(n1 as HTMLElement, 'click', () => handleClick)
|
||||||
return n0
|
return n0
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.todo('directive: v-show', () => {
|
describe('directive: v-show', () => {
|
||||||
test('basic', async () => {
|
test('basic', async () => {
|
||||||
const { host } = createDemo(true).render()
|
const { host } = createDemo(true).render()
|
||||||
const btn = host.querySelector('button')
|
const btn = host.querySelector('button')
|
||||||
|
@ -56,36 +55,87 @@ describe.todo('directive: v-show', () => {
|
||||||
|
|
||||||
test('should work on component', async () => {
|
test('should work on component', async () => {
|
||||||
const t0 = template('<div>child</div>')
|
const t0 = template('<div>child</div>')
|
||||||
const t1 = template('<button>toggle</button>')
|
|
||||||
const n0 = t0()
|
|
||||||
const visible = ref(true)
|
const visible = ref(true)
|
||||||
|
|
||||||
function handleClick() {
|
|
||||||
visible.value = !visible.value
|
|
||||||
}
|
|
||||||
const { component: Child } = define({
|
const { component: Child } = define({
|
||||||
render() {
|
setup() {
|
||||||
return n0
|
return t0()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { host } = define({
|
const { host } = define({
|
||||||
render() {
|
setup() {
|
||||||
const n1 = t1()
|
const n1 = createComponent(Child, null, null, true)
|
||||||
const n2 = createComponent(Child, null, null, true)
|
applyVShow(n1, () => visible.value)
|
||||||
withDirectives(n2, [[vShow, () => visible.value]])
|
return n1
|
||||||
on(n1 as HTMLElement, 'click', () => handleClick)
|
|
||||||
return [n1, n2]
|
|
||||||
},
|
},
|
||||||
}).render()
|
}).render()
|
||||||
|
|
||||||
expect(host.innerHTML).toBe('<button>toggle</button><div>child</div>')
|
expect(host.innerHTML).toBe('<div>child</div>')
|
||||||
|
|
||||||
const btn = host.querySelector('button')
|
visible.value = !visible.value
|
||||||
btn?.click()
|
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()
|
await nextTick()
|
||||||
expect(host.innerHTML).toBe(
|
expect(host.innerHTML).toBe(
|
||||||
'<button>toggle</button><div style="display: none;">child</div>',
|
'<div style="display: none;">child</div><!--if-->',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
childIf.value = !childIf.value
|
||||||
|
await nextTick()
|
||||||
|
expect(host.innerHTML).toBe(
|
||||||
|
'<span style="display: none;">child</span><!--if-->',
|
||||||
|
)
|
||||||
|
|
||||||
|
visible.value = !visible.value
|
||||||
|
await nextTick()
|
||||||
|
expect(host.innerHTML).toBe('<span style="">child</span><!--if-->')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,15 +2,55 @@ import {
|
||||||
type VShowElement,
|
type VShowElement,
|
||||||
vShowHidden,
|
vShowHidden,
|
||||||
vShowOriginalDisplay,
|
vShowOriginalDisplay,
|
||||||
|
warn,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { renderEffect } from '../renderEffect'
|
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 {
|
export function applyVShow(target: Block, source: () => any): void {
|
||||||
el[vShowOriginalDisplay] = el.style.display === 'none' ? '' : el.style.display
|
if (isVaporComponent(target)) {
|
||||||
renderEffect(() => setDisplay(el, source()))
|
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 {
|
function setDisplay(target: Block, value: unknown): void {
|
||||||
el.style.display = value ? el[vShowOriginalDisplay] : 'none'
|
if (isVaporComponent(target)) {
|
||||||
el[vShowHidden] = !value
|
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.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue