ui icon indicating copy to clipboard operation
ui copied to clipboard

SelectMenu emits invalid native Event on @change (missing target.value)

Open tosbatti opened this issue 4 months ago • 2 comments

Environment

Is this bug related to Nuxt or Vue?

Nuxt

Package

v3.x

Version

v3.2.0

Reproduction

Just exec: new Event('change', { target: { value: 'foo' } }) as the source code does here.

The created event has a null target (and obviously no accessible value).

Description

When listening to the @change event emitted by SelectMenu, the handler receives a native Event instance that has no usable payload — event.target is null, and therefore event.target.value is undefined.

The component currently creates the event using:

const event = new Event('change', { target: { value } })
emit('change', event)

However, the second argument of the Event constructor only accepts { bubbles, cancelable, composed }. As a result, the target property is ignored, and the emitted event is essentially empty.

Expected behavior

The @change event should provide access to the selected value or a proper payload.

Additional notes

In a sense, this seems like a known bug — or perhaps even an intentional behavior (given the @ts-expect-error) — however, I don’t see the point of emitting an event that’s effectively useless.

Moreover, my (legitimate, I believe) intent was to distinguish an event triggered by user interaction from a value change caused by other factors. There also seems to be (at least as far as I can tell) an intention in the source code to give the change event this specific meaning:

if (toRaw(props.modelValue) === value) {
	return
}

but in practice, the event ends up being useless — it can’t even be used as a simple trigger to read the model value, since the model hasn’t been updated yet at that point.

Additional context

No response

Logs


tosbatti avatar Oct 06 '25 11:10 tosbatti

The bug is valid, but I have to admit I misunderstood one point.

It seems that the update:modelValue event is also emitted only as a result of user actions, meaning that a programmatic change to the model does not trigger the event. In that sense, it should be equivalent to the change event. Could you please confirm this point? Thank you.

In this example, reset does not emit update:modelValue:

<script setup lang="ts">
const people = [
    'Wade Cooper',
    'Arlene Mccoy',
    'Devon Webb',
    'Tom Cook',
    'Tanya Fox',
    'Hellen Schmidt',
    'Caroline Schultz',
    'Mason Heaney',
    'Claudie Smitham',
    'Emil Schaefer',
];

const selected = ref(people[0]);

const onChange = (value: any): void => {
    console.log('change', value);
};

const onUpdateModel = (value: any): void => {
    console.log('updateModel', value);
};

const onReset = (): void => {
    selected.value = people[0];
};
</script>

<template>
    <div>
        <USelectMenu v-model="selected" :items="people" @change="onChange" @update:model-value="onUpdateModel" />
        <UButton label="reset" @click="onReset" />
    </div>
</template>

tosbatti avatar Oct 06 '25 12:10 tosbatti

I believe I have encountered this with the RadioGroup component as well where the change event's target is null due to this same line of code: https://github.com/nuxt/ui/blob/fda3c98ab798f045e6e3d781ec482ebe5f360c4e/src/runtime/components/RadioGroup.vue#L165

Looks like this may be an issue on several components that have that exact same line of code in them. I can't get the change event to bring back any data in the event.target object so listening for that event seems useless at the moment.

My specific problem with this relates to a 3rd party library (vee-validate) that is also listening to the value being passed into the v-model prop and when it changes it starts to run validation silently even though I don't want it to. With the Input component, I can listen to the @input event and bind the value to model-value="value" and based on this handle everything myself without issue.

jawa-the-hutt avatar Nov 06 '25 02:11 jawa-the-hutt