Skip to content
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

Component-patterns with web-components (v4) #8690

Closed
KenAJoh opened this issue Jun 3, 2023 · 5 comments
Closed

Component-patterns with web-components (v4) #8690

KenAJoh opened this issue Jun 3, 2023 · 5 comments

Comments

@KenAJoh
Copy link

KenAJoh commented Jun 3, 2023

Describe the problem

The new changes made to custom-elements 🎉 changed how get_current_component works, and with it comes some changes to how one create a component-api with purely web-components. (as far as my current testing has lead me to believe, but might be mistaken here)

A constant hassle when working with shadow dom is how one passes values between the shadow-dom, and in v3 this could be "solved" by using get_current_component().shadowRoot to access both parent and childrens within slots. This allowed the following component-pattern:

<wc-accordion>     // <- shadowroot open
    <wc-accordion-item>   //  <- shadowroot open
        <wc-accordion-heading>   // <- shadowroot open
			Heading text
        </wc-accordion-heading>    
        <wc-accordion-content>   // <- shadowroot open
			Content text
        </wc-accordion-content>
    </wc-accordion-item>
</wc-accordion>

where accordion-item stored and passed both the "open"-state and the "ontoggle"-handler down to accordion-heading and accordion-content. This "worked", but was not pain-free...

One could sort-of solve this now by using the following pattern:

<wc-accordion>
    <wc-accordion-item heading="heading-text">
			content
    </wc-accordion-item>
</wc-accordion>
// or
<wc-accordion>
    <wc-accordion-item >
           <span slotname="heading">heading</span>
           <div slotname="content">content</div>
    </wc-accordion-item>
</wc-accordion>

But that doesn't solve the core issue...

One could dispatch events from accordion-header up to item, following the 'properties down, events up'-pattern, but what is the expected and preferred way to pass the open-prop down across the nested shadow-dom?
This closed issue adds context-support for web-components, possibly solving this. But so far with 4.0.0-next.0 i have yet to get this working (can set context, but getContext is always undefined)

What i'm getting to is: How does Sveltes custom-elements want and expect a good component-pattern to look and work with 2-way communication between components?

Describe the proposed solution

Stencil solves this by making it possible to get and change child web-components with querySelector directly on the parent element even its if its nested shadow-doms

this.accordionHeader = this.el.querySelector("wc-accordion-header");
this.accordionHeader.open = this.open;

How this is implemented i'm not aware of, and might only be possible with virtual-dom (as stencil uses)

Lit solves this with giving access to this.children or this.defaultNodes (not 100% on defaultNodes, but thats how spectrum web-components does it)

Alternatives considered

In v4 one could disable shadow-dom for the nested elements, and only use shadow-dom on the wrapper. This could a viable pattern, but have yet to do any testing with it for now. accordion-item will still be outside the wrappers shadow-dom, so not viable.

The easiest way i have found for now is to make queries on the document, but this adds a different complexity with checking for the correct dom-element and adding unique identifiers to each component instance.

This issue under the 4.x milestone is related and would solve this to a certain degree. Found a PR on this, but its 3y old now..

Importance

would make my life easier

@KenAJoh KenAJoh changed the title Optimal component-pattern with web-components Component-pattern with web-components Jun 4, 2023
@KenAJoh KenAJoh changed the title Component-pattern with web-components Component-patterns with web-components Jun 4, 2023
@KenAJoh KenAJoh changed the title Component-patterns with web-components Component-patterns with web-components (v4) Jun 4, 2023
@benmccann benmccann added this to the 4.x milestone Jun 6, 2023
@dummdidumm
Copy link
Member

To clarify: The issue is that you want to create web components that should be used together in a way that these components know of their children so they can put them into specific slots without you having to explicitly define those slots as a user?

@KenAJoh
Copy link
Author

KenAJoh commented Jun 15, 2023

A potential solution would as a side-effect allow this to be done i guess. My main issue is how to access and manipulate state of child web-components

// html
<wc-accordion>
	<wc-accordion-item>
		...
	</wc-accordion-item>
</wc-accordion>
// svelte code (wc-accordion)
<script>
	let ref: HTMLDivElement;

    onMount(() => {
		ref.querySelector("wc-accordion-item") // <- null
		ref.parentElement  // <- null
		ref.shadowRoot  // <- null
    });
</script>

<div bind:this={ref}>
	<slot />
</div>
// dom
<wc-accordion>
	#shadow-root (open)
		<div></div>		   <- Can only access this
	<wc-accordion-item>    <- and not this since its outside shadow-root
		...
	</wc-accordion-item>
</wc-accordion>

Making it hard to pass state down to all children like this

<wc-accordion variant="neutral">
--
const item = ref.querySelector("wc-accordion-item");
item.variant = "neutral"

while it makes sense that you cant access elements outside the shadow-root with querySelector directly on the element, having access to either parentElement or shadowRoot would potentially open up an easier way to do this.

@dummdidumm
Copy link
Member

Use getRootNode and host, then you can query the other elements: ref.getRootNode().host.querySelector('wc-accordion-item').

Given that this is now possible using public APIs (the previous version with get_current_component is merely a hack using internal APIs which is brittle) I'm marking this as a docs issue - not sure where in the new docs to put it yet.

@dummdidumm dummdidumm removed this from the 4.x milestone Jun 15, 2023
@KenAJoh
Copy link
Author

KenAJoh commented Jun 15, 2023

Thanks for the help! ref.getRootNode().host did the trick for us ✔️ Getting som ts-errors with host: Property 'host' does not exist on type 'Node'., but closing this issue as completed as it still works.

@KenAJoh KenAJoh closed this as completed Jun 15, 2023
@patricknelson
Copy link

patricknelson commented Oct 9, 2023

The easiest way i have found for now is to make queries on the document, but this adds a different complexity with checking for the correct dom-element and adding unique identifiers to each component instance.

I concur; ideally you would just stay entirely in Svelte to intuitively get/set context and could just compose your custom elements any way you want. The goal would be for context to "just work" in the child element, as long as it is nested somehow under the parent element.

Like... this: https://svelte.dev/repl/b404a1addaf348eabcff3f6089707297?version=4.2.1

The advantage of using svelte-retag in this way is that you also have plain Svelte components as well... like this: https://svelte.dev/repl/7a26be30148644b6895469274bb69a32?version=4.2.1

To me this is a pretty useful pattern! Check out svelte-retag if you want to try this approach to get context working cleanly in custom elements. Also, check out this tab-based demo here too: https://svelte-retag.vercel.app/

Edit: p.s. As you can see in the first demo above, you can also incorporate the custom elements directly into Svelte components too (note the <custom-parent> included in the App.svelte file); this is a native feature of Svelte anyway. So... you can still mix and match it as you see fit, which I also think is pretty cool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants