diff --git a/packages/compiler-ssr/src/transforms/ssrVModel.ts b/packages/compiler-ssr/src/transforms/ssrVModel.ts index 838edc232..ed98042ea 100644 --- a/packages/compiler-ssr/src/transforms/ssrVModel.ts +++ b/packages/compiler-ssr/src/transforms/ssrVModel.ts @@ -130,7 +130,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => { checkDuplicatedValue() node.children = [createInterpolation(model, model.loc)] } else if (node.tag === 'dialog' || node.tag === 'details') { - res.props = [createObjectProperty(`open`, model)] + 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 73b8f18af..9cedb6d00 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 056ccfb6f..b37ebd266 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -212,15 +212,15 @@ export const vModelSelect: ModelDirective = { export const vModelDetails: ModelDirective = { created(el, _, vnode) { + el._assign = getModelAssigner(vnode) addEventListener(el, 'toggle', () => { el._assign(el.open) }) - el._assign = getModelAssigner(vnode) }, mounted(el, { value }) { el.open = value }, - beforeUpdate(el, _binding, vnode) { + beforeUpdate(el, _, vnode) { el._assign = getModelAssigner(vnode) }, updated(el, { value }) { @@ -238,7 +238,7 @@ export const vModelDialog: ModelDirective = { mounted(el, { value }) { el.open = value }, - beforeUpdate(el, _binding, vnode) { + beforeUpdate(el, _, vnode) { el._assign = getModelAssigner(vnode) }, updated(el, { value }) { @@ -314,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':