-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Events are not emitted from components compiled to a custom element #3119
Comments
I ran into a similar problem today and found a workaround for now.
svelte/src/runtime/internal/dom.ts Lines 254 to 257 in a0e0f01
Now, for custom elements, the custom event by default does not go past the boundaries of the To make it cross the boundaries of
Link: Firing events from Custom Element in Svelte Note: I am new to Svelte and may be terribly wrong with my analysis 😅 Probably @Rich-Harris can clear it out. |
Thanks @asifahmedfw for your feedbacks. Just discovered inside Svelte codebase that an deprecated APi is called by Svelte : svelte/src/runtime/internal/dom.ts Line 255 in 767ce22
https://developer.mozilla.org/fr/docs/Web/API/CustomEvent/initCustomEvent cc @Rich-Harris |
See #2101 for why we're using that API. |
@Conduitry ok i understand better |
Is this the reason why Svelte 3 custom events do not reach the custom element itself? I have encountered the same problem as described by the original poster and am looking for a clean solution. Hopefully without resorting to creating |
I am thinking about using Svelte in a large company, to build features wrapped in WebComponents almost exclusively, because web-components are their strategy for the future. I am proposing Svelte 3 instead of lit-element, their current choice, because lit-element lacks a lot of functionality present in modern front-end frameworks. Also because I think Svelte 3 is the easiest to learn and maintain, powerful front-end framework to date (having used VueJs 2 since 2016 and AngularJs since 2012). However, there is an issue that I have encountered, which could prevent the adoption of Svelte 3 in this company. The problem is that custom events emitted from within a Svelte 3 feature wrapped as a web-component do not bubble up to the web-component itself as normal DOM events and can not be handled in the usual manner within the template, for example This does not work <my-feature onmy-special-event="handler()"> Workaround 1Instead we have to write special code in JS that looks up the window.addEventListener('load', () => {
document.querySelector('my-feature').$on('my-special-event', handler)
}) Although doable, this is a cumbersome and non-standard way to add event handlers to the custom web-component element. Workaround 2Same method as mentioned above, which involves creating a native DOM Event let event = new Event('my-special-event', {
detail: {abc: 123},
bubbles: true,
composed: true
}) Svelte template event handler <my-input on:my-click={()=>el.dispatchEvent(event)} /> RequestCan we automatically add the required functionality, when compiling to a web-component target, to auto forward real DOM events to the custom element for each forwarded Svelte event? I'm happy to help, but don't know where to start in the codebase. |
Anyone looking into this.. Exposing custom events from custom elements must be a thing that is needed? Then |
In Svelte 3 I'm working around this with import { createEventDispatcher } from "svelte"
import { get_current_component } from "svelte/internal"
const component = get_current_component()
const svelteDispatch = createEventDispatcher()
const dispatch = (name, detail) => {
svelteDispatch(name, detail)
component.dispatchEvent && component.dispatchEvent(new CustomEvent(name, { detail }))
} |
One possible solution might be to use return new CustomEvent(type, { detail }) when targeting custom elements, and use the current const e = document.createEvent('CustomEvent')
e.initCustomEvent(type, false, false, detail)
return e method otherwise. |
I believe the main issue here isn't that the event is instantiated without |
I face the same issue and it's a dealbreaker :( I confirm what @TehShrike said and his solution proves that it could work import { createEventDispatcher } from "svelte"
import { get_current_component } from "svelte/internal"
const component = get_current_component()
const svelteDispatch = createEventDispatcher()
const dispatch = (name, detail) => {
svelteDispatch(name, detail)
component.dispatchEvent && component.dispatchEvent(new CustomEvent(name, { detail }))
} |
Any update on this issue? we are looking into using custom elements as a way to slowing convert our legacy AngularJS components. ideally i would like to keep our svelte components clean and without work around's required only for legacy code integration. It also looks like we can't use event forwarding. It would unfortunate to have to not use native svelte features just to support legacy code. We really want to be designing clean base components for a framework that we will be using moving forward both in svelte and our legacy app. |
Using the workaround mentioned in #3091 (Specifically the Stackoverflow answer), we are able to emit events as follows (after onMount()): $: host = element && element.parentNode.host // element is reference to topmost/wrapper DOM element
...
host.dispatchEvent(new CustomEvent('hello', {
detail: 'world',
cancelable: true,
bubbles: true, // bubble up to parent/ancestor element/application
composed: true // jump shadow DOM boundary
})); Anyone see any red flags? This seemed straightforward enough. EDIT: This failed in Storybook. Had to be a little smarter: function elementParent(element) {
if (!element) {
return undefined;
}
// check if shadow root (99.9% of the time)
if (element.parentNode && element.parentNode.host) {
return element.parentNode.host;
}
// assume storybook (TODO storybook magically avoids shadow DOM)
return element.parentNode;
}
let componentContainer
$: host = elementParent(componentContainer);
...
host.dispatchEvent(new CustomEvent('hello', {
detail: 'world',
cancelable: true,
bubbles: true, // bubble up to parent/ancestor element/application
composed: true // jump shadow DOM boundary
})); |
TehShrike's solution worked for me. <!-- Good.svelte -->
<svelte:options tag={null} />
<script>
import { createEventDispatcher } from 'svelte';
import { get_current_component } from 'svelte/internal';
const svelteDispatch = createEventDispatcher();
const component = get_current_component();
const dispatch = (name, detail) => {
svelteDispatch(name, detail);
component?.dispatchEvent(new CustomEvent(name, { detail }));
};
function sayGood() {
dispatch('good', { text: 'Good!' });
}
</script>
<button on:click="{sayGood}">Good</button> <!-- Test.vue -->
<template>
<div>
<cpn-good @good="log"></cpn-good>
</div>
</template>
<script>
import Good from '~/components/Good'; // import the compiled Good.svelte
customElements.get('cpn-good') || customElements.define('cpn-good', Good);
export default {
methods: {
log(evt) {
console.log(evt.detail.text); // output: Good
},
},
};
</script> |
import { createEventDispatcher } from "svelte";
import { get_current_component } from 'svelte/internal';
const component = get_current_component();
const svelteDispatch = createEventDispatcher();
function sayHello() {
dispatch("message", {
text: "Hello!",
});
}
const dispatch = (name, detail) => {
console.log(`svelte: ${name}`);
svelteDispatch(name, detail);
component.dispatchEvent &&
component.dispatchEvent(new CustomEvent(name, { detail }));
}; This works in angular also. <svelte-test
(message)="svelteClick($event.detail)">
</svelte-test> |
I did something similar as well...https://github.com/tricinel/svelte-timezone-picker/blob/7003e52887067c945ad1d0070a1505cd76c696f0/src/Picker.svelte#L118. I wanted two separate builds for web and for svelte, that's why I did that I ended up removing it :) |
I noticed today that Svelte (3.31.0) custom events lack the This is a tiny detail, and I wish not stir the issue much. Just that when features get done, it would be nice that also this detail be included/considered. |
try it import App from './App.svelte';
App.prototype.addEventListener = function(...arg){
this.$on(arg[0],arg[1])
document.addEventListener.call(this,...arg)
} |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
If anyone is having trouble with |
Any update regarding this issue? It's still a problem, but IE11 nowadays shouldn't hold back new feature. Would it be acceptable to review #2101 solution and use in only when legacy mode is enabled, and use |
Here's a short utility for defining typed dispatch function that works in custom elements import { get_current_component } from 'svelte/internal'
export const defineDispatch = <T extends DispatchOpts>() => {
const component = get_current_component()
return <N extends keyof T, D extends T[N]>(name: N, detail?: D) => {
component.dispatchEvent(
new CustomEvent(name as string, {
detail,
bubbles: true,
composed: true
})
)
}
}
type DispatchOpts = Record<string, any> Usage const dispatch = defineDispatch<{
myBooleanEmit: boolean
myStringEmit: string
}>()
dispatch('myBooleanEmit', true)
dispatch('myNumberEmit', 22) |
This is an overhaul of custom elements in Svelte. Instead of compiling to a custom element class, the Svelte component class is mostly preserved as-is. Instead a wrapper is introduced which wraps a Svelte component constructor and returns a HTML element constructor. This has a couple of advantages: - component can be used both as a custom element as well as a regular component. This allows creating one wrapper custom element and using regular Svelte components inside. Fixes #3594, fixes #3128, fixes #4274, fixes #5486, fixes #3422, fixes #2969, helps with sveltejs/kit#4502 - all components are compiled with injected styles (inlined through Javascript), fixes #4274 - the wrapper instantiates the component in `connectedCallback` and disconnects it in `disconnectedCallback` (but only after one tick, because this could be a element move). Mount/destroy works as expected inside, fixes #5989, fixes #8191 - the wrapper forwards `addEventListener` calls to `component.$on`, which allows to listen to custom events, fixes #3119, closes #4142 - some things are hard to auto-configure, like attribute hyphen preferences or whether or not setting a property should reflect back to the attribute. This is why `<svelte:options customElement={..}>` can also take an object to modify such aspects. This option allows to specify whether setting a prop should be reflected back to the attribute (default `false`), what to use when converting the property to the attribute value and vice versa (through `type`, default `String`, or when `export let prop = false` then `Boolean`), and what the corresponding attribute for the property is (`attribute`, default lowercased prop name). These options are heavily inspired by lit: https://lit.dev/docs/components/properties. Closes #7638, fixes #5705 - adds a `shadowdom` option to control whether or not encapsulate the custom element. Closes #4330, closes #1748 Breaking changes: - Wrapped Svelte component now stays as a regular Svelte component (invokeing it like before with `new Component({ target: ..})` won't create a custom element). Its custom element constructor is now a static property named `element` on the class (`Component.element`) and should be regularly invoked through setting it in the html. - The timing of mount/destroy/update is different. Mount/destroy/updating a prop all happen after a tick, so `shadowRoot.innerHTML` won't immediately reflect the change (Lit does this too). If you rely on it, you need to await a promise
Closed via #8457, to be released in Svelte 4 |
The native Svelte syntax for listening events on:mycustomevent doesn't works with events dispatched by a Svelte component exported to Custom Element.
May be related to this ?
svelte/src/runtime/internal/Component.ts
Lines 162 to 171 in a0e0f01
Here is a reproduction repository :
https://github.com/vogloblinsky/svelte-3-wc-debug
svelte3-raw
Example using just Svelte syntax. Inner component dispatch a custom event 'message'. App component listen to it using on:message
It works !
svelte3-wc
Example using just Svelte syntax and exporting component to Web Components. Inner component dispatch a custom event 'message'. App component listen to it using on:message
Same syntax doesn't work.
Vanilla JS works fine in public/index.html
The text was updated successfully, but these errors were encountered: