-
-
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
Custom element without shadow DOM #1748
Comments
Hi @nxtwrld , yes this would be really useful and important for me too. |
I am trying to tweak the svelte compiler and add a new option
I will post more info when I am done and it will pass or the tests. |
Hey @nxtwrld did you finish your implementation of the shadowDom option? I can pick this up if needed. I can't move forward with Svelte without this, for the reasons stated and also because we have to support IE 11 which has no support for Shadow DOM |
By the way, perhaps the API should instead be:
I understand for backward compatibility it might be best to make "open" the default, but really I think a more natural default would be "none". Since this would go into a new major version, perhaps we could consider adding this breaking change also? |
Just wanna chime in, this is a good idea and there are numerous reasons to want it, especially if you can still use Svelte's own support for the For my use cases, this functionality is needed to work around existing limitations in and issues with Shadow DOM so that Web Components can be effectively used throughout the transitional period while incompatibilities decrease, the platform fills in the gaps, etc. The issues I've encountered or heard of that need workarounds include:
In any case where a component's interface would require slots, but Shadow DOM in its current incarnation has issues that make it not viable to use, you're between a rock and hard place. Either you get no slots, or you get no form participation, accessibility, or styles (perhaps at all, perhaps only with major expense). This can lead to no Web Components. In the long term, my hope and assumption is that these issues will be fixed by increased support, new specs, or evolutions to the current specs and that eventually using Shadow DOM will have none of these significant downsides. |
I ran into this same issue with a component I was building for one of our experimental projects. I went ahead and started a PR to add the the option to disable shadow DOM. I'm not super familiar with Svelte internals at this point (or WCs generally), but we'll see where we get. |
Is there any progress? |
Are you still working on this? |
Hi there, I've opened this PR #4073. I was wondering if any of the maintainers can give me some feedback on the approach/whether this is something Svelte wants? |
Hi there, problem is, when you need to create classic html form (not submited via ajax) with form element created custom element, then you need light dom. Custom element cannot contain shadowed html forms. I think that this is most important complexity feature comparing to Stencil. StencilJS is perfect for large projects. But svelte is svelte... Please merge... |
Use of the Light DOM is a legit use case and I want to add my voice to the number of people for whom this blocker eliminates svelte from consideration. I hope it can be resolved/merged soon. |
Wanted to make a small authentication component with the help of svelte. Got stuck after finding out that google reCaptcha cannot work in shadow dom. Is there any other way out? When can we expect making custom components without having shadow dom? |
I don't think we need something very complex here. We just have to consider each component as a new Svelte app. I guess the store is available too, I didn't test so far. This is the "connect" utility I use to render a Svelte component into a CustomElement (without shadowDom): /**
* Connect Web Component attributes to Svelte Component properties
* @param {string} name Name of the Web Component
* @param {*} Component Svelte Component
* @param {string[]} attributes Which attributes will be passed as properties
*/
export default function connect(name, Component, attributes = []) {
return customElements.define(name, class extends HTMLElement {
constructor() {
super();
this.component = undefined;
}
static get observedAttributes() {
return attributes;
}
attributeChangedCallback(name, oldValue, newValue) {
if (this.component && oldValue !== newValue) {
this.component.$set({ [name]: newValue });
}
}
connectedCallback() {
let props = {};
for (const attr of attributes) {
props[attr] = this.getAttribute(attr) || undefined;
}
this.component = new Component({
target: this,
props,
});
}
});
} |
I also cobbled together something similar in the meantime, though in addition I pass in an option of shadow true/false and if true also embed a link to the svelte's compiled stylesheet. I think the svelte maintainers should decide if they really want to compile to web-components with all their complexity, or just promote using wrappers like these. What would help here, and with integration with other frameworks would be to make top level slots accessible outside of shadow dom. I realise they'd need to be "simulated", but grabbing the content of the mounted dom node and parsing it for named slots should be feasible, though admittedly not ideal. I have two pull requests dealing with these two issues, was hoping for some feedback, even if just to say no thanks. The slots pull request is still a WIP. |
Hey @cedeber, can you provide a small example on how to integrate your connector? |
Hi @rac0316 Here is my Svelte boileplate: https://github.com/cedeber/eukolia/tree/master/boilerplates/rollup-svelte I modified it to integrate the connector. In this commit, you can see the needed changes: cedeber/frontafino@1e1742d |
This may be a little "crude" but it seems to work for light dom slots: customElements.define('element-details',
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById('element-details-template').content;
// render into light dom
this.appendChild(template.cloneNode(true));
// grab all slots
const slots = this.querySelectorAll('slot');
slots.forEach(slot => {
// replace slots with their element counterpart
const el = this.querySelector(`[slot="${slot.name}"]`)
if (!el) slot.parentNode.removeChild(slot)
slot.parentNode.replaceChild(el, slot)
});
}
}
); Adapted from the demo at https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots |
I got this which also supports slots. The only restriction is that the main slot must be wrapped in a tag (eg. export default class KinElement extends HTMLElement {
constructor() {
super();
this.elTpl; // elementTemplate
this.initialized = false;
this.initTimeoutId = null;
}
connectedCallback() {
this.initTimeoutId = window.setTimeout(this.init.bind(this), 0);
}
disconnectedCallback() {
this.initTimeoutId = window.clearTimeout(this.initTimeoutId);
}
attributeChangedCallback() {
if (this.initialized) {
// may be defined by a child class
this.updateElem && this.updateElem();
}
}
init() {
if (this.initialized) {
return;
}
// must be defined by a child class (e.g. Button) and create this.elTpl
this.initElem();
this.initSlots();
this.appendChild(this.elTpl);
this.initialized = true;
}
initSlots() {
const mainSlot = this.elTpl.querySelector('slot:not([name])');
const namedSlots = this.elTpl.querySelectorAll('slot[name]');
const namedSlotsMap = {};
const mainSlotNodes = [];
const namedSlotNodes = [];
namedSlots.forEach(slot => {
namedSlotsMap[slot.name] = slot;
});
this.childNodes.forEach(child => {
if (child.slot) {
namedSlotNodes.push(child);
} else {
mainSlotNodes.push(child);
}
});
if (mainSlot) {
mainSlotNodes.forEach((node, index)=>{
node = this.removeChild(node);
if (index) {
mainSlotNodes[index - 1].after(node);
} else {
mainSlot.replaceWith(node);
}
});
}
namedSlotNodes.forEach(node => {
const slot = namedSlotsMap[node.slot];
node = this.removeChild(node);
if (slot) {
slot.replaceWith(node);
}
});
}
} |
I am also stuck with same issue trying develop a custom element with google reCaptcha
Did you got any solution for this i am also stuck with this.. |
@varun-etc I didn't find any way with having it as a custom component. But I solved my problem by not making it as a custom component, in that way there is no shadow dom and it's as good as any other app (I have to be carefull with my class names though, so as to not override styles and also not leak them to the host app). |
This comment has been minimized.
This comment has been minimized.
Finally i was able to achieve this in svelte you can refer build file here.. |
please share your source files |
source files github link |
I created a wrapper to solve this issue a while ago. Been using it internally for a few months and just quickly uploaded something to npm / github. It supports default and named slots, attributes, shadow dom, light dom, and embedding css. Hopefully someone else finds it useful. |
S |
Lots of accessibility tools like Jaws do have very very very poor support for shadow dom. Like a |
would love to see this. are there are other technologies out there that do something similar? |
Another use case I just encountered: rendering a custom element SVG filter. That filter can't be addressed by the rest of the page while in shadow (even if it's open). It must be rendered into light dom. |
Just getting started with Svelte and SvelteKit and really liking it so far! One of few attractions to Svelte was support for web components. In one of our use cases, all worked well, and the ShadowDom worked as expected. In another case.. well, not so much. We have now probably spent more time fighting this then it would have taken to just create a plan web component and just NOT added the shadow dom. My issue resembles others discussed in this thread. Anyway, an idea occurred to me. If the <svelte:options tag="my-custom-thing" /> is present, and there are no <style> tags, default to not adding a shadowdom. Seems like this might help preserve "intent", as I can see further confusion when both are present .. (though, traditional css rules have worked pretty well so far..) Not sure if this is possible, just a thought. I am another that would like support for this.. |
I use TailwindCSS and it would be great to have Svelte-driven custom elements that can be styled but with a global CSS. |
Could you please help me? I would like to use Svelte Custom Elements together with TailwinCSS, but there is a problem with Shadow Dom. Thank you |
like @Youhan said, yes, please 🙏 |
Would it be better to set this inside |
I would like a clarification on this feature. Shadow DOM is usually presented as a way to prevent CSS leaking into a component, and to prevent CSS leaking out of a component. Along with a lot of other people, I would rather that CSS DOES leak into a component (or at least have that option). I'm assuming that when using Svelte, there is NEVER a problem with CSS leaking OUT of a component since Svelte adds mangled class names. Is that correct? P.S. This thread started in 2018. I'm very surprised that it doesn't seem to be resolved yet. But... I don't know anything about Svelte internals - maybe it's just such a difficult thing to implement??? |
For those watching this issue but not already aware, it appears this is slated for implementation in Svelte v4 via #8457! 🎉 It appears that migration from @crisward's For migration from import SvelteTag from 'svelte-tag'
import MyComponent from './MyComponent.svelte'
new SvelteTag({ component: MyComponent, tagname: "my-component" ) // etc... You would still keep the import MyComponent from './MyComponent.svelte'
export { MyComponent } And then, finally add the standard Edit: Disclaimer: I'm new to Svelte myself, so there's a good chance I'm wrong on some specifics with migration and compatibility, so please refer to #8457 for compatibility and breaking changes! 😅 |
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 via the new |
FWIW, I forked
But importantly, in contrast to both Svelte 3 & 4, it also has:
It’s hard to underscore how important that last point is when in the context of custom elements. This can be a pain when combining Svelte, custom elements and slots, particularly since we cannot know what slots are available to a custom element until we’ve waited for This should hopefully not only reduce CLS but also TTI (time to interactive) without the “jank” usually seen when loading pages that compartmentalize component frameworks into custom elements. 😊 |
Hi,
I would like to get your thoughts on having a switch for customElement behaviour.
Current custom elements are based on shadow DOM which can be really handy in some cases.
But in our project we heavily depend on cascading - components style depends on its context. (e.g. Button colors are different for different types of containers)
It would be handy, if we could choose between using shadow DOM and a simpler version without it. In the similar way we attach slots in connectedCallback, we could import the component itself into the the main document.
Here is a very crude example of the custom element without shadowDOM:
Would someone else also find this useful?
The text was updated successfully, but these errors were encountered: