Просмотр исходного кода

feat: support `v-model` for `<details>` and `<dialog>`

Anthony Fu 3 лет назад
Родитель
Сommit
f8c2df7c09

+ 32 - 0
packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap

@@ -127,6 +127,38 @@ return function render(_ctx, _cache) {
 }"
 `;
 
+exports[`compiler: transform v-model > simple expression for details 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { vModelDetails: _vModelDetails, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+    return _withDirectives((_openBlock(), _createElementBlock(\\"details\\", {
+      \\"onUpdate:modelValue\\": $event => ((model) = $event)
+    }, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
+      [_vModelDetails, model]
+    ])
+  }
+}"
+`;
+
+exports[`compiler: transform v-model > simple expression for dialog 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { vModelDialog: _vModelDialog, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+    return _withDirectives((_openBlock(), _createElementBlock(\\"dialog\\", {
+      \\"onUpdate:modelValue\\": $event => ((model) = $event)
+    }, null, 8 /* PROPS */, [\\"onUpdate:modelValue\\"])), [
+      [_vModelDialog, model]
+    ])
+  }
+}"
+`;
+
 exports[`compiler: transform v-model > simple expression for input (checkbox) 1`] = `
 "const _Vue = Vue
 

+ 16 - 0
packages/compiler-dom/__tests__/transforms/vModel.spec.ts

@@ -9,6 +9,8 @@ import { transformElement } from '../../../compiler-core/src/transforms/transfor
 import { DOMErrorCodes } from '../../src/errors'
 import {
   V_MODEL_CHECKBOX,
+  V_MODEL_DETAILS,
+  V_MODEL_DIALOG,
   V_MODEL_DYNAMIC,
   V_MODEL_RADIO,
   V_MODEL_SELECT,
@@ -83,6 +85,20 @@ describe('compiler: transform v-model', () => {
     expect(generate(root).code).toMatchSnapshot()
   })
 
+  test('simple expression for details', () => {
+    const root = transformWithModel('<details v-model="model" />')
+
+    expect(root.helpers).toContain(V_MODEL_DETAILS)
+    expect(generate(root).code).toMatchSnapshot()
+  })
+
+  test('simple expression for dialog', () => {
+    const root = transformWithModel('<dialog v-model="model" />')
+
+    expect(root.helpers).toContain(V_MODEL_DIALOG)
+    expect(generate(root).code).toMatchSnapshot()
+  })
+
   test('simple expression for textarea', () => {
     const root = transformWithModel('<textarea v-model="model" />')
 

+ 4 - 0
packages/compiler-dom/src/runtimeHelpers.ts

@@ -4,6 +4,8 @@ export const V_MODEL_RADIO = Symbol(__DEV__ ? `vModelRadio` : ``)
 export const V_MODEL_CHECKBOX = Symbol(__DEV__ ? `vModelCheckbox` : ``)
 export const V_MODEL_TEXT = Symbol(__DEV__ ? `vModelText` : ``)
 export const V_MODEL_SELECT = Symbol(__DEV__ ? `vModelSelect` : ``)
+export const V_MODEL_DIALOG = Symbol(__DEV__ ? `vModelDialog` : ``)
+export const V_MODEL_DETAILS = Symbol(__DEV__ ? `vModelDetails` : ``)
 export const V_MODEL_DYNAMIC = Symbol(__DEV__ ? `vModelDynamic` : ``)
 
 export const V_ON_WITH_MODIFIERS = Symbol(__DEV__ ? `vOnModifiersGuard` : ``)
@@ -19,6 +21,8 @@ registerRuntimeHelpers({
   [V_MODEL_CHECKBOX]: `vModelCheckbox`,
   [V_MODEL_TEXT]: `vModelText`,
   [V_MODEL_SELECT]: `vModelSelect`,
+  [V_MODEL_DETAILS]: `vModelDetails`,
+  [V_MODEL_DIALOG]: `vModelDialog`,
   [V_MODEL_DYNAMIC]: `vModelDynamic`,
   [V_ON_WITH_MODIFIERS]: `withModifiers`,
   [V_ON_WITH_KEYS]: `withKeys`,

+ 9 - 1
packages/compiler-dom/src/transforms/vModel.ts

@@ -12,7 +12,9 @@ import {
   V_MODEL_RADIO,
   V_MODEL_SELECT,
   V_MODEL_TEXT,
-  V_MODEL_DYNAMIC
+  V_MODEL_DYNAMIC,
+  V_MODEL_DETAILS,
+  V_MODEL_DIALOG
 } from '../runtimeHelpers'
 
 export const transformModel: DirectiveTransform = (dir, node, context) => {
@@ -49,6 +51,8 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
     tag === 'input' ||
     tag === 'textarea' ||
     tag === 'select' ||
+    tag === 'details' ||
+    tag === 'dialog' ||
     isCustomElement
   ) {
     let directiveToUse = V_MODEL_TEXT
@@ -92,6 +96,10 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
       }
     } else if (tag === 'select') {
       directiveToUse = V_MODEL_SELECT
+    } else if (tag === 'dialog') {
+      directiveToUse = V_MODEL_DIALOG
+    } else if (tag === 'details') {
+      directiveToUse = V_MODEL_DETAILS
     } else {
       // textarea
       __DEV__ && checkDuplicatedValue()

+ 20 - 0
packages/compiler-ssr/__tests__/ssrElement.spec.ts

@@ -340,4 +340,24 @@ describe('ssr: element', () => {
       `)
     })
   })
+
+  describe('v-model', () => {
+    test('with details', () => {
+      expect(getCompiledString(`<details v-model="x">Foo</details>`))
+        .toMatchInlineSnapshot(`
+          "\`<details\${
+              (_ssrIncludeBooleanAttr(_ctx.x)) ? \\" open\\" : \\"\\"
+            }>Foo</details>\`"
+        `)
+    })
+
+    test('with dialog', () => {
+      expect(getCompiledString(`<dialog v-model="x">Foo</dialog>`))
+        .toMatchInlineSnapshot(`
+          "\`<dialog\${
+              (_ssrIncludeBooleanAttr(_ctx.x)) ? \\" open\\" : \\"\\"
+            }>Foo</dialog>\`"
+        `)
+    })
+  })
 })

+ 2 - 0
packages/compiler-ssr/src/transforms/ssrVModel.ts

@@ -129,6 +129,8 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
     } else if (node.tag === 'textarea') {
       checkDuplicatedValue()
       node.children = [createInterpolation(model, model.loc)]
+    } else if (node.tag === 'dialog' || node.tag === 'details') {
+      res.props = [createObjectProperty(`open`, model)]
     } else if (node.tag === 'select') {
       node.children.forEach(option => {
         if (option.type === NodeTypes.ELEMENT) {

+ 36 - 0
packages/runtime-dom/src/directives/vModel.ts

@@ -210,6 +210,42 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
   }
 }
 
+export const vModelDetails: ModelDirective<HTMLDetailsElement> = {
+  created(el, _, vnode) {
+    addEventListener(el, 'toggle', () => {
+      el._assign(el.open)
+    })
+    el._assign = getModelAssigner(vnode)
+  },
+  mounted(el, { value }) {
+    el.open = value
+  },
+  beforeUpdate(el, _binding, vnode) {
+    el._assign = getModelAssigner(vnode)
+  },
+  updated(el, { value }) {
+    el.open = value
+  }
+}
+
+export const vModelDialog: ModelDirective<HTMLDialogElement> = {
+  created(el, _, vnode) {
+    addEventListener(el, 'close', () => {
+      el._assign(false)
+    })
+    el._assign = getModelAssigner(vnode)
+  },
+  mounted(el, { value }) {
+    el.open = value
+  },
+  beforeUpdate(el, _binding, vnode) {
+    el._assign = getModelAssigner(vnode)
+  },
+  updated(el, { value }) {
+    el.open = value
+  }
+}
+
 function setSelected(el: HTMLSelectElement, value: any) {
   const isMultiple = el.multiple
   if (isMultiple && !isArray(value) && !isSet(value)) {

+ 2 - 0
packages/runtime-dom/src/index.ts

@@ -226,6 +226,8 @@ export {
   vModelCheckbox,
   vModelRadio,
   vModelSelect,
+  vModelDetails,
+  vModelDialog,
   vModelDynamic
 } from './directives/vModel'
 export { withModifiers, withKeys } from './directives/vOn'