diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 1a14decd06a..722b4d9b44c 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -269,33 +269,35 @@ export const vModelDynamic: ObjectDirective< } } -function callModelHook( - el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, - binding: DirectiveBinding, - vnode: VNode, - prevVNode: VNode | null, - hook: keyof ObjectDirective -) { - let modelToUse: ObjectDirective - switch (el.tagName) { +function resolveDynamicModel(tagName: string, type: string | undefined) { + switch (tagName) { case 'SELECT': - modelToUse = vModelSelect - break + return vModelSelect case 'TEXTAREA': - modelToUse = vModelText - break + return vModelText default: - switch (vnode.props && vnode.props.type) { + switch (type) { case 'checkbox': - modelToUse = vModelCheckbox - break + return vModelCheckbox case 'radio': - modelToUse = vModelRadio - break + return vModelRadio default: - modelToUse = vModelText + return vModelText } } +} + +function callModelHook( + el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, + binding: DirectiveBinding, + vnode: VNode, + prevVNode: VNode | null, + hook: keyof ObjectDirective +) { + const modelToUse = resolveDynamicModel( + el.tagName, + vnode.props && vnode.props.type + ) const fn = modelToUse[hook] as DirectiveHook fn && fn(el, binding, vnode, prevVNode) } @@ -324,4 +326,18 @@ export function initVModelForSSR() { return { checked: true } } } + + vModelDynamic.getSSRProps = (binding, vnode) => { + if (typeof vnode.type !== 'string') { + return + } + const modelToUse = resolveDynamicModel( + // resolveDynamicModel expects an uppercase tag name, but vnode.type is lowercase + vnode.type.toUpperCase(), + vnode.props && vnode.props.type + ) + if (modelToUse.getSSRProps) { + return modelToUse.getSSRProps(binding, vnode) + } + } } diff --git a/packages/server-renderer/__tests__/ssrDirectives.spec.ts b/packages/server-renderer/__tests__/ssrDirectives.spec.ts index 3e8bd2e0f67..74b01204d31 100644 --- a/packages/server-renderer/__tests__/ssrDirectives.spec.ts +++ b/packages/server-renderer/__tests__/ssrDirectives.spec.ts @@ -11,6 +11,7 @@ import { vModelText, vModelRadio, vModelCheckbox, + vModelDynamic, resolveDirective } from 'vue' import { ssrGetDirectiveProps, ssrRenderAttrs } from '../src' @@ -376,6 +377,100 @@ describe('ssr: directives', () => { }) }) + describe('vnode v-model dynamic', () => { + test('text', async () => { + expect( + await renderToString( + createApp({ + render() { + return withDirectives(h('input'), [[vModelDynamic, 'hello']]) + } + }) + ) + ).toBe(``) + }) + + test('radio', async () => { + expect( + await renderToString( + createApp({ + render() { + return withDirectives( + h('input', { type: 'radio', value: 'hello' }), + [[vModelDynamic, 'hello']] + ) + } + }) + ) + ).toBe(``) + + expect( + await renderToString( + createApp({ + render() { + return withDirectives( + h('input', { type: 'radio', value: 'hello' }), + [[vModelDynamic, 'foo']] + ) + } + }) + ) + ).toBe(``) + }) + + test('checkbox', async () => { + expect( + await renderToString( + createApp({ + render() { + return withDirectives(h('input', { type: 'checkbox' }), [ + [vModelDynamic, true] + ]) + } + }) + ) + ).toBe(``) + + expect( + await renderToString( + createApp({ + render() { + return withDirectives(h('input', { type: 'checkbox' }), [ + [vModelDynamic, false] + ]) + } + }) + ) + ).toBe(``) + + expect( + await renderToString( + createApp({ + render() { + return withDirectives( + h('input', { type: 'checkbox', value: 'foo' }), + [[vModelDynamic, ['foo']]] + ) + } + }) + ) + ).toBe(``) + + expect( + await renderToString( + createApp({ + render() { + return withDirectives( + h('input', { type: 'checkbox', value: 'foo' }), + [[vModelDynamic, []]] + ) + } + }) + ) + ).toBe(``) + }) + }) + test('custom directive w/ getSSRProps (vdom)', async () => { expect( await renderToString(