]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(core): support v-show directive (#310)
authorlikui <2218301630@qq.com>
Mon, 25 Nov 2019 03:04:26 +0000 (11:04 +0800)
committerEvan You <yyx990803@gmail.com>
Mon, 25 Nov 2019 03:04:26 +0000 (22:04 -0500)
packages/compiler-dom/__tests__/transforms/__snapshots__/vShow.spec.ts.snap [new file with mode: 0644]
packages/compiler-dom/__tests__/transforms/vShow.spec.ts [new file with mode: 0644]
packages/compiler-dom/src/errors.ts
packages/compiler-dom/src/index.ts
packages/compiler-dom/src/runtimeHelpers.ts
packages/compiler-dom/src/transforms/vShow.ts
packages/runtime-dom/__tests__/directives/vShow.spec.ts [new file with mode: 0644]
packages/runtime-dom/src/directives/vShow.ts [new file with mode: 0644]
packages/runtime-dom/src/index.ts

diff --git a/packages/compiler-dom/__tests__/transforms/__snapshots__/vShow.spec.ts.snap b/packages/compiler-dom/__tests__/transforms/__snapshots__/vShow.spec.ts.snap
new file mode 100644 (file)
index 0000000..8ec0bbb
--- /dev/null
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`compiler: v-show transform simple expression 1`] = `
+"const _Vue = Vue
+
+return function render() {
+  with (this) {
+    const { vShow: _vShow, createVNode: _createVNode, withDirectives: _withDirectives, createBlock: _createBlock, openBlock: _openBlock } = _Vue
+    
+    return (_openBlock(), _withDirectives(_createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */), [
+      [_vShow, a]
+    ]))
+  }
+}"
+`;
diff --git a/packages/compiler-dom/__tests__/transforms/vShow.spec.ts b/packages/compiler-dom/__tests__/transforms/vShow.spec.ts
new file mode 100644 (file)
index 0000000..3bac6bb
--- /dev/null
@@ -0,0 +1,36 @@
+import { parse, transform, generate, CompilerOptions } from '@vue/compiler-core'
+import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
+import { transformShow } from '../../src/transforms/vShow'
+import { DOMErrorCodes } from '../../src/errors'
+
+function transformWithShow(template: string, options: CompilerOptions = {}) {
+  const ast = parse(template)
+  transform(ast, {
+    nodeTransforms: [transformElement],
+    directiveTransforms: {
+      show: transformShow
+    },
+    ...options
+  })
+  return ast
+}
+
+describe('compiler: v-show transform', () => {
+  test('simple expression', () => {
+    const ast = transformWithShow(`<div v-show="a"/>`)
+
+    expect(generate(ast).code).toMatchSnapshot()
+  })
+
+  test('should raise error if has no expression', () => {
+    const onError = jest.fn()
+    transformWithShow(`<div v-show/>`, { onError })
+
+    expect(onError).toHaveBeenCalledTimes(1)
+    expect(onError).toHaveBeenCalledWith(
+      expect.objectContaining({
+        code: DOMErrorCodes.X_V_SHOW_NO_EXPRESSION
+      })
+    )
+  })
+})
index b59ee0c02e5a168880119fde1da87805e874d892..7dd83fce3d6735dadb6bcfc009795d50ff324903 100644 (file)
@@ -27,7 +27,8 @@ export const enum DOMErrorCodes {
   X_V_TEXT_WITH_CHILDREN,
   X_V_MODEL_ON_INVALID_ELEMENT,
   X_V_MODEL_ARG_ON_ELEMENT,
-  X_V_MODEL_ON_FILE_INPUT_ELEMENT
+  X_V_MODEL_ON_FILE_INPUT_ELEMENT,
+  X_V_SHOW_NO_EXPRESSION
 }
 
 export const DOMErrorMessages: { [code: number]: string } = {
@@ -37,5 +38,6 @@ export const DOMErrorMessages: { [code: number]: string } = {
   [DOMErrorCodes.X_V_TEXT_WITH_CHILDREN]: `v-text will override element children.`,
   [DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT]: `v-model can only be used on <input>, <textarea> and <select> elements.`,
   [DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT]: `v-model argument is not supported on plain elements.`,
-  [DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT]: `v-model cannot used on file inputs since they are read-only. Use a v-on:change listener instead.`
+  [DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT]: `v-model cannot used on file inputs since they are read-only. Use a v-on:change listener instead.`,
+  [DOMErrorCodes.X_V_SHOW_NO_EXPRESSION]: `v-show is missing expression.`
 }
index 3b3d002dc51f5b0697473b7a3f1036141dae748c..e9b398ecf025a68b447b97638007b91532b0a70b 100644 (file)
@@ -7,6 +7,7 @@ import { transformVHtml } from './transforms/vHtml'
 import { transformVText } from './transforms/vText'
 import { transformModel } from './transforms/vModel'
 import { transformOn } from './transforms/vOn'
+import { transformShow } from './transforms/vShow'
 
 export function compile(
   template: string,
@@ -22,6 +23,7 @@ export function compile(
       text: transformVText,
       model: transformModel, // override compiler-core
       on: transformOn,
+      show: transformShow,
       ...(options.directiveTransforms || {})
     }
   })
index b9740ec8cfeb6b538abd69d4437f2beda26b5f31..7b61856eefdf06142288b151b0b31af1612ff5ae 100644 (file)
@@ -9,6 +9,8 @@ export const V_MODEL_DYNAMIC = Symbol(__DEV__ ? `vModelDynamic` : ``)
 export const V_ON_WITH_MODIFIERS = Symbol(__DEV__ ? `vOnModifiersGuard` : ``)
 export const V_ON_WITH_KEYS = Symbol(__DEV__ ? `vOnKeysGuard` : ``)
 
+export const V_SHOW = Symbol(__DEV__ ? `vShow` : ``)
+
 registerRuntimeHelpers({
   [V_MODEL_RADIO]: `vModelRadio`,
   [V_MODEL_CHECKBOX]: `vModelCheckbox`,
@@ -16,5 +18,6 @@ registerRuntimeHelpers({
   [V_MODEL_SELECT]: `vModelSelect`,
   [V_MODEL_DYNAMIC]: `vModelDynamic`,
   [V_ON_WITH_MODIFIERS]: `withModifiers`,
-  [V_ON_WITH_KEYS]: `withKeys`
+  [V_ON_WITH_KEYS]: `withKeys`,
+  [V_SHOW]: `vShow`
 })
index 70b786d12ed055a08b57f5cf47f717bf6a266301..7fe76d0ba771546bfd7189024a1343254c9c3093 100644 (file)
@@ -1 +1,17 @@
-// TODO
+import { DirectiveTransform } from '@vue/compiler-core'
+import { createDOMCompilerError, DOMErrorCodes } from '../errors'
+import { V_SHOW } from '../runtimeHelpers'
+
+export const transformShow: DirectiveTransform = (dir, node, context) => {
+  const { exp, loc } = dir
+  if (!exp) {
+    context.onError(
+      createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION, loc)
+    )
+  }
+
+  return {
+    props: [],
+    needRuntime: context.helper(V_SHOW)
+  }
+}
diff --git a/packages/runtime-dom/__tests__/directives/vShow.spec.ts b/packages/runtime-dom/__tests__/directives/vShow.spec.ts
new file mode 100644 (file)
index 0000000..bf093c0
--- /dev/null
@@ -0,0 +1,128 @@
+import {
+  withDirectives,
+  createComponent,
+  h,
+  nextTick,
+  VNode
+} from '@vue/runtime-core'
+import { createApp, vShow } from '@vue/runtime-dom'
+
+const withVShow = (node: VNode, exp: any) =>
+  withDirectives(node, [[vShow, exp]])
+
+let app: any, root: any
+
+beforeEach(() => {
+  app = createApp()
+  root = document.createElement('div') as any
+})
+
+describe('runtime-dom: v-show directive', () => {
+  test('should check show value is truthy', async () => {
+    const component = createComponent({
+      data() {
+        return { value: true }
+      },
+      render() {
+        return [withVShow(h('div'), this.value)]
+      }
+    })
+    app.mount(component, root)
+
+    const $div = root.querySelector('div')
+
+    expect($div.style.display).toEqual('')
+  })
+
+  test('should check show value is falsy', async () => {
+    const component = createComponent({
+      data() {
+        return { value: false }
+      },
+      render() {
+        return [withVShow(h('div'), this.value)]
+      }
+    })
+    app.mount(component, root)
+
+    const $div = root.querySelector('div')
+
+    expect($div.style.display).toEqual('none')
+  })
+
+  it('should update show value changed', async () => {
+    const component = createComponent({
+      data() {
+        return { value: true }
+      },
+      render() {
+        return [withVShow(h('div'), this.value)]
+      }
+    })
+    app.mount(component, root)
+
+    const $div = root.querySelector('div')
+    const data = root._vnode.component.data
+
+    expect($div.style.display).toEqual('')
+
+    data.value = false
+    await nextTick()
+    expect($div.style.display).toEqual('none')
+
+    data.value = {}
+    await nextTick()
+    expect($div.style.display).toEqual('')
+
+    data.value = 0
+    await nextTick()
+    expect($div.style.display).toEqual('none')
+
+    data.value = []
+    await nextTick()
+    expect($div.style.display).toEqual('')
+
+    data.value = null
+    await nextTick()
+    expect($div.style.display).toEqual('none')
+
+    data.value = '0'
+    await nextTick()
+    expect($div.style.display).toEqual('')
+
+    data.value = undefined
+    await nextTick()
+    expect($div.style.display).toEqual('none')
+
+    data.value = 1
+    await nextTick()
+    expect($div.style.display).toEqual('')
+  })
+
+  test('should respect display value in style attribute', async () => {
+    const component = createComponent({
+      data() {
+        return { value: true }
+      },
+      render() {
+        return [
+          withVShow(h('div', { style: { display: 'block' } }), this.value)
+        ]
+      }
+    })
+    app.mount(component, root)
+
+    const $div = root.querySelector('div')
+    const data = root._vnode.component.data
+
+    expect($div.style.display).toEqual('block')
+
+    data.value = false
+    await nextTick()
+    expect($div.style.display).toEqual('none')
+
+    data.value = true
+    await nextTick()
+    expect($div.style.display).toEqual('block')
+  })
+})
diff --git a/packages/runtime-dom/src/directives/vShow.ts b/packages/runtime-dom/src/directives/vShow.ts
new file mode 100644 (file)
index 0000000..5f2743a
--- /dev/null
@@ -0,0 +1,45 @@
+import { ObjectDirective } from '@vue/runtime-core'
+
+interface VShowElement extends HTMLElement {
+  // _vod = vue original display
+  _vod: string
+}
+
+export const vShow: ObjectDirective<VShowElement> = {
+  beforeMount(el, { value }, { transition }) {
+    el._vod = el.style.display === 'none' ? '' : el.style.display
+    if (transition && value) {
+      transition.beforeEnter(el)
+    } else {
+      setDisplay(el, value)
+    }
+  },
+  mounted(el, { value }, { transition }) {
+    if (transition && value) {
+      transition.enter(el)
+    }
+  },
+  updated(el, { value, oldValue }, { transition }) {
+    if (!value === !oldValue) return
+    if (transition) {
+      if (value) {
+        transition.beforeEnter(el)
+        setDisplay(el, true)
+        transition.enter(el)
+      } else {
+        transition.leave(el, () => {
+          setDisplay(el, false)
+        })
+      }
+    } else {
+      setDisplay(el, value)
+    }
+  },
+  beforeUnmount(el) {
+    setDisplay(el, true)
+  }
+}
+
+function setDisplay(el: VShowElement, value: unknown): void {
+  el.style.display = value ? el._vod : 'none'
+}
index cebfa555addc6c63ce3159e26ad3250eb27e0e59..7a0ea80d5ae994c734577668afab4c7a48db0043 100644 (file)
@@ -68,6 +68,8 @@ export { withModifiers, withKeys } from './directives/vOn'
 // DOM-only components
 export { Transition, TransitionProps } from './components/Transition'
 
+export { vShow } from './directives/vShow'
+
 // re-export everything from core
 // h, Component, reactivity API, nextTick, flags & types
 export * from '@vue/runtime-core'