diff --git a/packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
index d2c9aaa785f..039d259b564 100644
--- a/packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
+++ b/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
diff --git a/packages/compiler-dom/__tests__/transforms/vModel.spec.ts b/packages/compiler-dom/__tests__/transforms/vModel.spec.ts
index dce8f09b02c..2e43546bc89 100644
--- a/packages/compiler-dom/__tests__/transforms/vModel.spec.ts
+++ b/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(' ')
+
+ expect(root.helpers).toContain(V_MODEL_DETAILS)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('simple expression for dialog', () => {
+ const root = transformWithModel('')
+
+ expect(root.helpers).toContain(V_MODEL_DIALOG)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
test('simple expression for textarea', () => {
const root = transformWithModel('')
diff --git a/packages/compiler-dom/src/runtimeHelpers.ts b/packages/compiler-dom/src/runtimeHelpers.ts
index 19c595ef69a..d28b7b57447 100644
--- a/packages/compiler-dom/src/runtimeHelpers.ts
+++ b/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`,
diff --git a/packages/compiler-dom/src/transforms/vModel.ts b/packages/compiler-dom/src/transforms/vModel.ts
index 5dff390d3d8..cbe9784a09a 100644
--- a/packages/compiler-dom/src/transforms/vModel.ts
+++ b/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()
diff --git a/packages/compiler-ssr/__tests__/ssrElement.spec.ts b/packages/compiler-ssr/__tests__/ssrElement.spec.ts
index 65608bdd342..c34a3d316fa 100644
--- a/packages/compiler-ssr/__tests__/ssrElement.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrElement.spec.ts
@@ -340,4 +340,24 @@ describe('ssr: element', () => {
`)
})
})
+
+ describe('v-model', () => {
+ test('with details', () => {
+ expect(getCompiledString(`Foo `))
+ .toMatchInlineSnapshot(`
+ "\`Foo \`"
+ `)
+ })
+
+ test('with dialog', () => {
+ expect(getCompiledString(``))
+ .toMatchInlineSnapshot(`
+ "\`\`"
+ `)
+ })
+ })
})
diff --git a/packages/compiler-ssr/src/transforms/ssrVModel.ts b/packages/compiler-ssr/src/transforms/ssrVModel.ts
index bd587edcb9c..ed98042ea43 100644
--- a/packages/compiler-ssr/src/transforms/ssrVModel.ts
+++ b/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) {
diff --git a/packages/runtime-dom/__tests__/directives/vModel.spec.ts b/packages/runtime-dom/__tests__/directives/vModel.spec.ts
index 73b8f18afc0..9cedb6d00c8 100644
--- a/packages/runtime-dom/__tests__/directives/vModel.spec.ts
+++ b/packages/runtime-dom/__tests__/directives/vModel.spec.ts
@@ -9,8 +9,8 @@ import {
ref
} from '@vue/runtime-dom'
-const triggerEvent = (type: string, el: Element) => {
- const event = new Event(type)
+const triggerEvent = (type: string, el: Element, arg?: any) => {
+ const event = new Event(type, arg)
el.dispatchEvent(event)
}
@@ -1172,4 +1172,82 @@ describe('vModel', () => {
await nextTick()
expect(data.value).toEqual('使用拼音输入')
})
+
+ it('should work with details', async () => {
+ const manualListener = vi.fn()
+ const component = defineComponent({
+ data() {
+ return { value: false }
+ },
+ render() {
+ return [
+ withVModel(
+ h('details', {
+ 'onUpdate:modelValue': setValue.bind(this),
+ onToggle: () => {
+ manualListener(data.value)
+ }
+ }),
+ this.value
+ )
+ ]
+ }
+ })
+ render(h(component), root)
+
+ const details = root.querySelector('details')! as HTMLDetailsElement
+ const data = root._vnode.component.data
+ expect(details.open).toEqual(false)
+
+ details.open = true
+ triggerEvent('toggle', details, { target: details })
+ await nextTick()
+ expect(data.value).toEqual(true)
+ expect(manualListener).toHaveBeenCalledWith(true)
+
+ data.value = false
+ await nextTick()
+ expect(details.open).toEqual(false)
+
+ data.value = true
+ await nextTick()
+ expect(details.open).toEqual(true)
+ })
+
+ it('should work with dialog', async () => {
+ const manualListener = vi.fn()
+ const component = defineComponent({
+ data() {
+ return { value: false }
+ },
+ render() {
+ return [
+ withVModel(
+ h('dialog', {
+ 'onUpdate:modelValue': setValue.bind(this),
+ onClose: () => {
+ manualListener(data.value)
+ }
+ }),
+ this.value
+ )
+ ]
+ }
+ })
+ render(h(component), root)
+
+ const dialog = root.querySelector('dialog')! as HTMLDialogElement
+ const data = root._vnode.component.data
+ expect(dialog.open).toEqual(false)
+
+ data.value = true
+ await nextTick()
+ expect(dialog.open).toEqual(true)
+
+ dialog.open = false
+ triggerEvent('close', dialog)
+ await nextTick()
+ expect(data.value).toEqual(false)
+ expect(manualListener).toHaveBeenCalledWith(false)
+ })
})
diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts
index 2cf5f4cfc16..c7b88b78a35 100644
--- a/packages/runtime-dom/src/directives/vModel.ts
+++ b/packages/runtime-dom/src/directives/vModel.ts
@@ -210,6 +210,42 @@ export const vModelSelect: ModelDirective = {
}
}
+export const vModelDetails: ModelDirective = {
+ created(el, _, vnode) {
+ el._assign = getModelAssigner(vnode)
+ addEventListener(el, 'toggle', () => {
+ el._assign(el.open)
+ })
+ },
+ mounted(el, { value }) {
+ el.open = value
+ },
+ beforeUpdate(el, _, vnode) {
+ el._assign = getModelAssigner(vnode)
+ },
+ updated(el, { value }) {
+ el.open = value
+ }
+}
+
+export const vModelDialog: ModelDirective = {
+ created(el, _, vnode) {
+ el._assign = getModelAssigner(vnode)
+ addEventListener(el, 'close', () => {
+ el._assign(false)
+ })
+ },
+ mounted(el, { value }) {
+ el.open = value
+ },
+ beforeUpdate(el, _, 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)) {
@@ -278,6 +314,10 @@ function resolveDynamicModel(tagName: string, type: string | undefined) {
return vModelSelect
case 'TEXTAREA':
return vModelText
+ case 'DETAILS':
+ return vModelDetails
+ case 'DIALOG':
+ return vModelDialog
default:
switch (type) {
case 'checkbox':
diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts
index ad818a34bb7..536cf1e8345 100644
--- a/packages/runtime-dom/src/index.ts
+++ b/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'