diff --git a/packages/vee-validate/src/utils/assertions.ts b/packages/vee-validate/src/utils/assertions.ts
index 6da184711..ab5ccc381 100644
--- a/packages/vee-validate/src/utils/assertions.ts
+++ b/packages/vee-validate/src/utils/assertions.ts
@@ -54,7 +54,14 @@ export function isNotNestedPath(path: string) {
* Checks if an element is a native HTML5 multi-select input element
*/
export function isNativeMultiSelect(el: HTMLElement): el is HTMLSelectElement {
- return el.tagName === 'SELECT' && (el as HTMLSelectElement).multiple;
+ return isNativeSelect(el) && el.multiple;
+}
+
+/**
+ * Checks if an element is a native HTML5 select input element
+ */
+export function isNativeSelect(el: HTMLElement): el is HTMLSelectElement {
+ return el.tagName === 'SELECT';
}
/**
diff --git a/packages/vee-validate/src/utils/events.ts b/packages/vee-validate/src/utils/events.ts
index 63cd35781..53b91bcaf 100644
--- a/packages/vee-validate/src/utils/events.ts
+++ b/packages/vee-validate/src/utils/events.ts
@@ -1,4 +1,4 @@
-import { hasCheckedAttr, isNativeMultiSelect, isEvent } from './assertions';
+import { hasCheckedAttr, isNativeMultiSelect, isNativeSelect, isEvent } from './assertions';
import { getBoundValue, hasValueBinding } from './vnode';
export function normalizeEventValue(value: Event | unknown): unknown {
@@ -23,5 +23,13 @@ export function normalizeEventValue(value: Event | unknown): unknown {
.map(getBoundValue);
}
+ // makes sure we get the actual `option` bound value
+ // #3440
+ if (isNativeSelect(input)) {
+ const selectedOption = Array.from(input.options).find(opt => opt.selected);
+
+ return selectedOption ? getBoundValue(selectedOption) : input.value;
+ }
+
return input.value;
}
diff --git a/packages/vee-validate/tests/Field.spec.ts b/packages/vee-validate/tests/Field.spec.ts
index cc6f70f38..c80de9566 100644
--- a/packages/vee-validate/tests/Field.spec.ts
+++ b/packages/vee-validate/tests/Field.spec.ts
@@ -1004,4 +1004,38 @@ describe('', () => {
await flushPromises();
expect((form as any).field).toBe('hello');
});
+
+ // #3440
+ test('should preserve select input options value type', async () => {
+ const value = ref();
+
+ const wrapper = mountWithHoc({
+ setup() {
+ return {
+ value,
+ };
+ },
+ template: `
+
+
+
+
+ `,
+ });
+
+ await flushPromises();
+ const select = document.querySelector('select') as HTMLSelectElement;
+ const optTrue = document.querySelector('#true') as HTMLOptionElement;
+ const optFalse = document.querySelector('#false') as HTMLOptionElement;
+
+ optTrue.selected = true;
+ dispatchEvent(select, 'change');
+ await flushPromises();
+ expect(value.value).toBe(true);
+
+ optFalse.selected = true;
+ dispatchEvent(select, 'change');
+ await flushPromises();
+ expect(value.value).toBe(false);
+ });
});
diff --git a/packages/vee-validate/tests/helpers/index.ts b/packages/vee-validate/tests/helpers/index.ts
index 19606c032..f8a91154d 100644
--- a/packages/vee-validate/tests/helpers/index.ts
+++ b/packages/vee-validate/tests/helpers/index.ts
@@ -41,9 +41,9 @@ export function setChecked(node: HTMLInputElement, status?: boolean) {
node.dispatchEvent(new window.Event('input'));
}
-export function dispatchEvent(node: ComponentPublicInstance | HTMLInputElement, eventName: string) {
+export function dispatchEvent(node: ComponentPublicInstance | HTMLElement, eventName: string) {
if (HTML_TAGS.includes((node as any).tagName)) {
- const input = node as HTMLInputElement;
+ const input = node as HTMLElement;
input.dispatchEvent(new window.Event(eventName));
return;
}