EranGrin / vue-web-component-wrapper

vue3 - web component wrapper plugin
https://erangrin.github.io/vue-web-component-wrapper/
50 stars 5 forks source link

How to emit events to the outside? #8

Closed boga001 closed 7 months ago

boga001 commented 1 year ago

Hello,

I was wondering how to emit the events outside of the webcomponent?

Without this plugin I managed to get it to work and Vue produces CustomEvent, to which I can subscribe with addEventListener.

What would be the solution to use it?

My understanding is that the createWebComponent gets rootComponent and elementName which are binding the event emitting on one and I am listening on another element.

Thanks

EranGrin commented 12 months ago

Hi There,

This is indeed a very interesting use case, I haven't tried or tested the custom event on with the web component, but the structure should support it. With that being said, I might need to add minor changes in case it doesn't work.

For your ref

Kindly let me know if you manage to make it work Thanks

boga001 commented 11 months ago

Hey,

Sorry for the late reply.

I managed to do it without the plugin as I've decided to code everything on my own. Vue already produces CustomEvent when you build it as a webcomponent.

But in my opinion, the Vue component that is actually converted into the CustomElement should just emit to the outside and not be wrapped into anything since emit works only for parent and does not propagate to grandparent.

Thank you for your reply 😃

EranGrin commented 11 months ago

Hi, Not quite sure what do you refer to

But in my opinion, the Vue component that is actually converted into the CustomElement should just emit to the outside and not be wrapped into anything since emit works only for parent and does not propagate to grandparent.

But glad to hear you found your desired solution :-)

The plugin "vue-web-component-wrapper" attempts to solve issues of plugin integration and shadow DOM style injection but if your use case doesn't suffer from these issues, then using vue built in webComponent is obviously a better choice

zAlweNy26 commented 9 months ago

Hi, I would like to emit events to the outside using with plugin. How can I achieve this?

EranGrin commented 9 months ago

HI @zAlweNy26, I might need to release another version, which add bubbles: true to the custom element. I'll let you know when it's ready

zAlweNy26 commented 9 months ago

Ok thanks @EranGrin, for the moment I found a workaround using the default "defineCustomElement" available in Vue:

const Widget = defineCustomElement(App)

class ExposedWidget extends Widget {
    constructor(props?: Record<string, any>) {
        super(props)
    }

    public toggle() {
        const inst = (this as any)._instance
        if (!inst) throw new Error('Component not mounted')
        inst.exposed.toggle()
    }

    public refresh() {
        const inst = (this as any)._instance
        if (!inst) throw new Error('Component not mounted')
        inst.exposed.refresh()
    }
}

customElements.define('chat-widget', ExposedWidget)

And for the styles I just did:

<style>
@import './style.css';
</style>

The events are emitted (without touching anything) but as I wanted to expose some methods too, I added this code. I hope it helps.

EranGrin commented 8 months ago

This seems to be working just fine

Here is a vue component that will be wrapped into the web-component

import { storeToRefs } from 'pinia'
import { useCounterStore } from '../pinia/counter.store'

const counterStore = useCounterStore()

// Use the standard event dispatching for custom elements
const dispatchEvent = (eventName: string, detail: { count: number; }) => {
  const event = new CustomEvent(eventName, {
    detail,
  })
  window.dispatchEvent(event)
}

const { count, name, doubleCount } = storeToRefs(counterStore)
const { increment: originalIncrement, reset } = counterStore

const increment = () => {
  originalIncrement()
  dispatchEvent('incremented', { count: count.value })
}
</script>

<template>
  <div class="flex flex-col justify-center items-center">
    <div class="flex flex-col justify-center items-center text-2xl bg-blue-300 rounded-xl p-4">
      <h1>I am a route 3 (and Support Pinia!)</h1>
      <p>Check localstorage</p>
    </div>

    <p>{{ name }}</p>

    <p>Count: {{ count }}</p>
    <p>Doble Count: {{ doubleCount }}</p>
  </div>

  <div class="flex py-2">
    <button class="p-2 bg-blue-300 text-black border-cyan-300 rounded-l-lg rounded-r-none w-full" @click="increment">
      Increment
    </button>
    <button
      :class="{ 'bg-slate-300 text-slate-500': count == 0 }"
      class="p-2 bg-red-600 text-white border-cyan-300 rounded-r-lg w-full duration-300"
      :disabled="count == 0"
      @click="reset"
    >
      Reset
    </button>
  </div>
</template>

Here is the parent host of the web component

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite 4 + Vue 3.3 + Typescript 5 + Web Component</title>
  </head>

  <body>
    <h1 class="text-test" style="text-align:center;">Vite 4 + Vue 3.3 + Typescript 5 + Web Component</h1>
    <pre style="margin-left: 3%;">
      <code>&lt;my-web-component
        lang="de"
        route="/route1"
        class="my-web-component"
        api-token="++++++++++++++++++++++++"
        base-uri="https://my.base.uri"
      &gt;
      &lt;/my-web-component&gt;
      </code>
    </pre>
    <my-web-component
      lang="de"
      route="/route1"
      class="my-web-component"
      api-token="++++++++++++++++++++++++"
      base-uri="https://my.base.uri"
    ></my-web-component>

    <script type="module" src="./src/main.ts"></script>

    <script>
      document.addEventListener('DOMContentLoaded', function() {
        window.addEventListener('incremented', function(event) {
          console.log('Counter was incremented, current count:', event.detail.count);
          // You can also handle the event here as needed
        });
      });
    </script>
  </body>
  <style>
    .text-test {
      color: red;
      font-size: 2rem;
    }
  </style>
</html>
boga001 commented 8 months ago

Nice example. I would just like to add that Vue already makes a custom event if you emit from root component. Then you would listen to the my-web-component and not on window. This event name would have to be really unique not to conflict with anything.

EranGrin commented 8 months ago

Ok, nice never tried this, can you give an example?

EranGrin commented 7 months ago

The issue appears to be inactive.

EranGrin commented 7 months ago

New version is up, with support for event emit https://www.npmjs.com/package/vue-web-component-wrapper