]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: support `v-model` for `<details>` and `<dialog>`
authorAnthony Fu <anthonyfu117@hotmail.com>
Sat, 8 Apr 2023 10:55:36 +0000 (12:55 +0200)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Mon, 21 Aug 2023 08:13:11 +0000 (16:13 +0800)
packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
packages/compiler-dom/__tests__/transforms/vModel.spec.ts
packages/compiler-dom/src/runtimeHelpers.ts
packages/compiler-dom/src/transforms/vModel.ts
packages/compiler-ssr/__tests__/ssrElement.spec.ts
packages/compiler-ssr/src/transforms/ssrVModel.ts
packages/runtime-dom/src/directives/vModel.ts
packages/runtime-dom/src/index.ts

index d2c9aaa785f69f758b9b2772fdf8f77a42ea6e24..039d259b56406c6a6db563c52996cb666d5c4d40 100644 (file)
@@ -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
 
index dce8f09b02c19280524fa21ce33179fe2ee9bf12..2e43546bc89a5396a2f678bda11b2ade65ca60e0 100644 (file)
@@ -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" />')
 
index 19c595ef69ac21495553bd92e001849542d55302..d28b7b57447e0dfeb8273bad272c0f0af84de95a 100644 (file)
@@ -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`,
index 5dff390d3d89b234e6b1d2bc64adf2b2f64ae44c..cbe9784a09a8490a2d1e4dfba542174f0bdf6d69 100644 (file)
@@ -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()
index 65608bdd342740bb5b5b80bae3e6df8768780821..c34a3d316faf1022b4e8eb17aa1f76b2a88e57d0 100644 (file)
@@ -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>\`"
+        `)
+    })
+  })
 })
index bd587edcb9cab664041c74f37ad4fbfd57a73686..838edc232ab5fe41b5bec98818e80681c70a8efa 100644 (file)
@@ -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) {
index 2cf5f4cfc16a0eb013c9b634f7f246be64bfa7ca..056ccfb6f03dbc98dd8b37bec0e635cd5b6fb35a 100644 (file)
@@ -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)) {
index ad818a34bb7ea875b4e62216bb3acf4944c690fd..536cf1e834552b6b0b0dd0cc34cfa02ed0165330 100644 (file)
@@ -226,6 +226,8 @@ export {
   vModelCheckbox,
   vModelRadio,
   vModelSelect,
+  vModelDetails,
+  vModelDialog,
   vModelDynamic
 } from './directives/vModel'
 export { withModifiers, withKeys } from './directives/vOn'