Is it possible to automatically or programmatically slot nested web components or elements of a specific type without having to specify the slot
attribute on them?
Consider some structure like this:
<parent-element>
<child-element>Child 1</child-element>
<child-element>Child 2</child-element>
<p>Content</p>
</parent-element>
With the <parent-element>
having a Shadow DOM like this:
<div id="child-elements">
<slot name="child-elements">
<child-element>Default child</child-element>
</slot>
</div>
<div id="content">
<slot></slot>
</div>
The expected result is:
<parent-element>
<#shadow-root>
<div id="child-elements">
<slot name="child-elements">
<child-element>Child 1</child-element>
<child-element>Child 2</child-element>
</slot>
</div>
<div id="content">
<slot>
<p>Content</p>
</slot>
</div>
</parent-element>
In words, I want to enforce that <child-element>
s are only allowed within a <parent-element>
similar to <td>
elements only being allowed within a <tr>
element. And I want them to be placed within the <slot name="child-elements">
element. Having to specify a slot
attribute on each of them to place them within a specific slot of the <parent-element>
seems redundant.
At the same time, the rest of the content within the <parent-element>
should automatically be slotted into the second <slot>
element.
I've first searched for a way to define this when registering the parent element, though CustomElementRegistry.define()
currently only supports extends
as option.
Then I thought, maybe there's a function allowing to slot the elements manually, i.e. something like childElement.slot('child-elements')
, but that doesn't seem to exist.
I've then tried to achive this programmatically in the constructor of the parent element like this:
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(template.content.cloneNode(true));
const childElements = this.getElementsByTagName('child-element');
const childElementSlot = this.shadowRoot.querySelector('[name="child-elements"]');
for (let i = 0; i < childElements.length; i++) {
childElementSlot.appendChild(childElements[i]);
}
}
Though this doesn't move the child elements to the <slot name="child-elements">
, so all of them still get slotted in the second <slot>
element.
Your unnamed default <slot></slot>
will capture all elements not assigned to a named slot;
so a slotchange
Event can capture those and force child-element
into the correct slot:
customElements.define('parent-element', class extends HTMLElement {
constructor() {
super().attachShadow({mode:'open'})
.append(document.getElementById(this.nodeName).content.cloneNode(true));
this.shadowRoot.addEventListener("slotchange", (evt) => {
if (evt.target.name == "") {// <slot></slot> captures
[...evt.target.assignedElements()]
.filter(el => el.nodeName == 'CHILD-ELEMENT') //process child-elements
.map(el => el.slot = "child-elements"); // force them to their own slot
} else console.log(`SLOT: ${evt.target.name} got:`,evt.target.assignedNodes())
})}});
customElements.define('child-element', class extends HTMLElement {
connectedCallback(parent = this.closest("parent-element")) {
// or check and force slot name here
if (this.parentNode != parent) {
if (parent) parent.append(this); // Child 3 !!!
else console.error(this.innerHTML, "wants a PARENT-ELEMENT!");
}}});
child-element { color: red; display: block; } /* style lightDOM in global CSS! */
<template id=PARENT-ELEMENT>
<style>
:host { display: inline-block; border: 2px solid red; }
::slotted(child-element) { background: lightgreen }
div { border:3px dashed rebeccapurple }
</style>
<div><slot name=child-elements></slot></div>
<slot></slot>
</template>
<parent-element>
<child-element>Child 1</child-element>
<child-element>Child 2</child-element>
<b>Content</b>
<div><child-element>Child 3 !!!</child-element></div>
</parent-element>
<child-element>Child 4 !!!</child-element>
Note the logic for processing <child-element>
not being a direct child of <parent-element>
, you probably want to rewrite this to your own needs
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With