vuejs / core

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
https://vuejs.org/
MIT License
47.91k stars 8.36k forks source link

Using v-model on a custom Element (from defineCustomElement): prop/event mismatch #4428

Closed sawden closed 3 months ago

sawden commented 3 years ago

Version

3.2.4

Reproduction link

CodeSandbox Link

Steps to reproduce

Create a custom input as a CustomElement and use it in a Vue component.

What is expected?

The value specified in the v-model changes

What is actually happening?

v-model is ignored.

LinusBorg commented 3 years ago

Strictly speaking this is not really a bug as the custom element does what it's supposed to do: it emits the component event as a custom event. The problem is that v-model listens for input but the custom element emits an update:modelValue event.

However we should discuss how we can better support this common usecase.

LinusBorg commented 3 years ago

On second thought, I'm inclined to rate this as a bug. There's a few different aspects to this, the thing to discuss is wethe we want to support v-model on a custom element at all. We currently support it on distinct native input elements only (and components, but we are dealing with a custom DOM element here).

If we decided to do this, we would have to make the v-model listener deal with custom events, where the emitted value is found in a detail property. This would complicate things quite a bit.

But regardless of that discussion, the thing that makes it a bug right now, though, is that there's no way to actually listen to the update:modelValue event though - because we explicitly skip these special events on DOM elements (because they get special treatment on native inputs).

https://github.com/vuejs/vue-next/blob/b40845153cd4dbdd76bfb74816f4e6b109c9f049/packages/runtime-dom/src/patchProp.ts#L30-L32

So even if the consumer doesn't use v-model and tries the following, they will fail:

<custom-element :model-value="value" @update:model-value="value = $event.detail"

...because the event listener is never being applied to the custom element. Which is desirable for normal DOM elements, of course, but poses an issue here.

I'll think a bit about it and write more thoughts later

sawden commented 3 years ago

The styles of child vue component are not applied if the parent component is a custom element. #4309 Because of this, I have to define my custom input as a custom element, otherwise I cannot use any styles.

I thank you for your help and hope that a solution/workaround will be found soon. 🙂

✌ue is awesome!

mattickx commented 2 years ago

I didn't want to create another issue for it, as this seems related:

v-model does not work on <component :is="..."> components either. Reproduction: https://codesandbox.io/s/customelement-v-model-bug-forked-w4p21s?file=/src/App.vue

For <component is="input"> one could use @input="value = $event.target.value" But I would have rather used v-model which worked in Vue 2

Bumped into this as my use case for this was single component for both textarea and input.

padcom commented 1 year ago

@LinusBorg Did you think about it? It's November 2023 and this is still an issue. It's also a regression from Vue 2.x (https://www.digitalocean.com/community/tutorials/how-to-add-v-model-support-to-custom-vue-js-components) and is blocking me personally from achieving my goal of creating custom elements that do support v-model (like a custom for example).

jcbond92 commented 11 months ago

I have a PR up that addresses this issue (at least partially) for custom elements.

It allows you to specify a v-model type for different custom elements (like checkbox, radio, etc.). Library authors will need to mirror native input props (e.g. value, checked) and input/change events (while also mirroring changes to value/checked on the host element so that updated values are in event.target), but it'll be possible to make v-model form bindings work across different types of custom elements.

yyx990803 commented 3 months ago

First of all, a general opinion: Vue custom elements are first and foremost custom elements, so they should strive to behave like native DOM elements where applicable. It is discouraged to author Vue custom elements that only work in Vue applications.

With that in mind, a Vue custom input element should behave like a native <input> element as much as possible - i.e. expose a value property, and emit input events. This makes it possible to both work with v-model when used inside a Vue app, and also be usable when it's used in a non-Vue environment.

When v-model is used on a custom element, Vue should treat it the same way whether the custom element is a Vue one or not. So it will listen for the updateevent and attempts to read the .value property.

Therefore, to make a Vue custom element work with v-model, it should:

  1. Declare a value prop
  2. When the value changes, (1) update the .value property on the host element and (2) emit an input event.

Before 3.5, retrieving the host element can be a bit difficult. That's why 3.5 will ship with a new useHost() helper and expose this.$host for Options API.

Here's a working example using the minor branch (to be published as 3.5).

jcbond92 commented 3 months ago

Thanks for adding useHost() that will make updating the host element much easier. With regard to using radio and checkbox custom elements with v-model, is the recommendation to use type="checkbox" or type="radio" to have Vue apply the correct type of v-model?

I'd be happy to raise a PR to mention this in the Vue docs, if so!


There is a bit more detail in this feature discussion around why I think this would be a helpful thing to document or have an official solution for, https://github.com/vuejs/rfcs/discussions/617:

Setting app.config.compilerOptions.isCustomElement works well for text input components where I can emit an input event and use a value prop, but I am unable to support checkboxes and radio buttons since V_MODEL_DYNAMIC is looking for event.target.value instead of event.target.checked.

The workaround here is to set a type on the host element like this:

<template>
  <custom-checkbox type="checkbox" v-model="value">Label</custom-checkbox>
</template>

<script>
import "@custom-element-package/checkbox.js"
export default {
  data() {
    return {
      value: true
    };
  },
};
</script>