Subscribe on changes!

Vue 3's v-on syntax does not support listening to custom events with capital letters

avatar
Feb 10th 2022

Version

3.2.30

Reproduction link

sfc.vuejs.org/

Steps to reproduce

Notice that the event named 'lower' is being handled by the Vue component, but the event named 'Upper' is not

What is expected?

Event names are an open set, and can include capital letters

What is actually happening?

The event named with a capital letter is not handled


This issue seems to also occur with capital letters in other parts of the event name too

I am a rank Vue novice, and so I may be making a simple error or oversight.

This came up when updating the web site custom-elements-everywhere.com which tracks library and framework support for custom elements. See https://github.com/webcomponents/custom-elements-everywhere/pull/1388

avatar
Feb 11th 2022
 <fires-events @Upper=inc1 @lower=inc2>

compiles to indistinguishable prop name onUpper onLower

this is unlikely to change.

to work around this limitation, either use a directive or a template ref to add an event listener directly to the dom element.

avatar
Feb 16th 2022

@LinusBorg If that's the case then the Vue doesn't get a perfect 100% as mentioned in the docs so the docs need to be updated to reflect that.

avatar
Feb 16th 2022

Sure, PRs are welcome.

However, consider that we don't prevent you from listening to this event - you can add the event listener with a small custom directive, as mentioned - it's just not supported via v-on as it collides with Vue's own event name semantics.

avatar
Feb 16th 2022

Sent over a docs PR, but if there's an ergonomic to support arbitrary event names, we could instead update the custom-elements-everywhere test for this feature: https://github.com/webcomponents/custom-elements-everywhere/blob/221ea4744373f34112331949c0d85d4b45e41d69/libraries/vue/src/components.js#L157-L163

avatar
Feb 16th 2022

@rictic thanks!

So here's the recommendation:

In Vue, one can define custom directives (like that internal ones, i.e. v-on) to wrap imperative DOM operations in a declarative way. These custom directives can be declared locally in each component or registered globally in an app to be available in all child components.

What we would want here is a basic way to add and remove event listeners while preserving the event name casing at all times. This can be achieved with a few lines of code. here's a demo with local registration: SFC Playground

Registering this directive globally (in one app's context) would look like this:

// v-event:arg"value"
app.directive('event', {
    beforeMount(el, { arg, value }) {
      el.addEventListener(arg, value)
    },
    beforeUnmount(el, { arg, value }) {
      el.removeEventListener(arg, value)
    }
  })

Would that be simple enough to be considered as a declarative solution for these tests?

avatar
Feb 17th 2022

That works well.

Given its small size, what do you think about adding the event directive into Vue by default, just so that it's a pattern that components can rely on existing, and that is more likely to be findable?

avatar
Feb 17th 2022

@rictic That's indeed something to consider. However I'm worrried that it's similarity to v-on would make it an easy point of confusion, and given that it's only real purpose is to work around a limitation in the handling of custom events, I'd be a bit hesitant to make it a new API on the same level as v-on. However that's just my gut feeling for now.

At the minimum, we should document the limitation and this pattern as a recommended solution in your section about "Vue & Custom Elements" here.

avatar
May 3rd 2022

The Vue docs still say that "Vue scores a perfect 100% in the Custom Elements Everywhere tests" but the tests actually show 91% score:

image
avatar
Aug 2nd 2022

@LinusBorg I’ve seen this become a point of friction in migrating from vue2 to vue3 if the app uses a lot of custom elements - you end up with either less idiomatic Vue code throughout by using custom directives for very common event handling, or waiting on publishers of the web components you use to update their library.

I find it surprising as a user that you can’t simply consume a web component and listen for the exact event you want - I know this leads to confusion for others, too. The pattern Vue already follows for props with the .prop modifier seems like it would make sense here; that prefixes the prop with . to indicate different treatment. That helps with setting properties on Web Components, and I’d expect a similar feature for CustomEvents, like v-on:fooBar.custom to indicate that you are trying to listen for this exact CustomEvent. I’d anticipate the resulting prop after transformation could be on.fooBar, so that existing case transformations don’t capitalize it, and in patchEvent you can check for . to know to use the following event name unchanged. Or it can be a different distinguishing character (like :).

Happy to open an RFC for this if you think it has any potential - I do think this feels like a gap that Vue itself should handle somehow.

avatar
Aug 2nd 2022
avatar
Oct 18th 2022

I believe this was fixed in 3.2.38 via https://github.com/vuejs/core/commit/0739f8909a0e56ae0fa760f233dfb8c113c9bde2.

Update: Running the original reproduction against the latest Vue still shows the reported problem. However, I believe that's because isCustomElement is not set correctly, which is also why it shows a warning. I'm not aware of any way to change that setting on the SFC Playground, so I've created a reproduction here: https://jsfiddle.net/skirtle/4ery6m1z/. I believe that shows that the original problem is fixed.

You may notice that it works even without the isCustomElement. However, if you move the customElements.define call to the bottom (rather than having it at the top) then it behaves the same as the SFC Playground example. In that case, isCustomElement does fix the problem. Here's the broken version, for comparison: https://jsfiddle.net/skirtle/y4z2n0w8/.

avatar
Oct 19th 2022

Just checked this and yes, the changed mentioned by @skirtle addresses this issue, though only the related issues in the docs repo were mentjoned in the commit message as this one was already closed at that point.

So this issue is now solved.

avatar
Mar 2nd 2023

As for now, the website https://custom-elements-everywhere.com is still showing Vue as 91% web component compliant.

I raise a PR to solve that: https://github.com/webcomponents/custom-elements-everywhere/pull/2074