diff --git a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap index 33fb37a58f9..561004cfd07 100644 --- a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap +++ b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap @@ -1,5 +1,15 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`stringify static html > Select elements can preserve data types when stringified 1`] = ` +"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue + +const _hoisted_1 = /*#__PURE__*/_createStaticVNode("", 5) + +return function render(_ctx, _cache) { + return _hoisted_1 +}" +`; + exports[`stringify static html > should bail on bindings that are hoisted but not stringifiable 1`] = ` "const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue diff --git a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts index b0eb515c254..b005e0645bd 100644 --- a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts +++ b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts @@ -485,4 +485,17 @@ describe('stringify static html', () => { expect(code).toMatch(`text1`) expect(code).toMatchSnapshot() }) + + // #6568 + test('Select elements can preserve data types when stringified', () => { + const { code } = compileWithStringify(` + + + + + + `) + + expect(code).toMatchSnapshot() + }) }) diff --git a/packages/compiler-dom/src/transforms/stringifyStatic.ts b/packages/compiler-dom/src/transforms/stringifyStatic.ts index a5ff770f4e8..83ca9f7dc21 100644 --- a/packages/compiler-dom/src/transforms/stringifyStatic.ts +++ b/packages/compiler-dom/src/transforms/stringifyStatic.ts @@ -318,6 +318,12 @@ function stringifyElement( res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml( evaluated, )}"` + if (typeof evaluated !== 'object' && node.tag === 'option') { + res += ` v-stringify-type='${typeof evaluated}'` + } + } + if (evaluated === null && node.tag === 'option') { + res += ` v-stringify-type='${'null'}'` } } else if (p.name === 'html') { // #5439 v-html with constant value diff --git a/packages/runtime-dom/__tests__/directives/vModel.spec.ts b/packages/runtime-dom/__tests__/directives/vModel.spec.ts index 05ee59d9010..7ee76679ab5 100644 --- a/packages/runtime-dom/__tests__/directives/vModel.spec.ts +++ b/packages/runtime-dom/__tests__/directives/vModel.spec.ts @@ -1,5 +1,6 @@ import { type VNode, + createStaticVNode, defineComponent, h, nextTick, @@ -1227,4 +1228,61 @@ describe('vModel', () => { await nextTick() expect(data.value).toEqual('使用拼音输入') }) + + it(`After the select tag is stringified, + v-model can get the correct type`, async () => { + const hoist = createStaticVNode( + "", + 5, + ) + const component = defineComponent({ + data() { + return { value: '' } + }, + render() { + return [ + withVModel( + h( + 'select', + { + value: null, + 'onUpdate:modelValue': setValue.bind(this), + }, + [hoist], + ), + this.value, + ), + ] + }, + }) + render(h(component), root) + + await nextTick() + const input = root.querySelector('select') + const optionList = root.querySelectorAll('option') + const data = root._vnode.component.data + + optionList[0].selected = true + triggerEvent('change', input) + await nextTick() + expect(data.value).toBe('string') + + optionList[0].selected = false + optionList[1].selected = true + triggerEvent('change', input) + await nextTick() + expect(data.value).toBe(false) + + optionList[1].selected = false + optionList[2].selected = true + triggerEvent('change', input) + await nextTick() + expect(data.value).toBe(1) + + optionList[2].selected = false + optionList[4].selected = true + triggerEvent('change', input) + await nextTick() + expect(data.value).toBe(null) + }) }) diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 7b387096b68..f719af2fbbc 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -246,7 +246,18 @@ function setSelected(el: HTMLSelectElement, value: any) { // retrieve raw value set via :value bindings function getValue(el: HTMLOptionElement | HTMLInputElement) { - return '_value' in el ? (el as any)._value : el.value + let value = '_value' in el ? (el as any)._value : el.value + let stringifyType = el.getAttribute('v-stringify-type') + if (stringifyType === 'number') { + return Number(value) + } + if (stringifyType === 'boolean') { + return value !== 'false' + } + if (stringifyType === 'null') { + return null + } + return value } // retrieve raw value for true-value and false-value set via :true-value or :false-value bindings