-
Notifications
You must be signed in to change notification settings - Fork 387
Description
I am trying to learn web components and I came to the realization that there are a fair number of use cases (such as any time each slotted item needs to be wrapped) that benefit from manual slot assignment. I found a tutorial on it at https://knowler.dev/blog/an-intro-to-manual-slot-assignment, made some modifications to handle pre-existing children, and ended up with the following custom element:
customElements.define("fruit-bowl", class extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: "open",
slotAssignment: "manual",
});
const observer = new MutationObserver(() => this.#setContent());
observer.observe(this, { childList: true });
this.#setContent();
}
#setContent () {
const { childNodes, shadowRoot } = this;
shadowRoot.innerHTML = "<ul>";
const list = shadowRoot.querySelector("ul");
for (const node of childNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
const listItem = document.createElement("li");
const slot = document.createElement("slot");
listItem.append(slot);
list.append(listItem);
slot.assign(node);
}
}
});
However, I want my custom elements to be usable within other custom elements, so I did some testing, creating a simple wrapper element with a slot to populate the fruit-bowl:
customElements.define("wrapper-bowl", class extends HTMLElement {
constructor () {
super();
const shadowRoot = this.attachShadow({mode: "open"});
shadowRoot.innerHTML = `
<fruit-bowl>
<slot></slot>
</fruit-bowl>
`;
}
});
This did not work as hoped. All children of wrapper-bowl were assigned to the same slot, and since the slot is the child element of fruit-bowl, rather than the elements themselves, only a single list item was created, containing all elements slotted into the wrapper bowl.
I thought that maybe I could fix the problem by getting assignedElements from child nodes that were slots and then manually assigning them. I modified fruit-bowl to do a quick and dirty test, thinking I could try adding observers or listeners if it worked. This is the modified element:
customElements.define("fruit-bowl", class extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: "open",
slotAssignment: "manual",
});
const observer = new MutationObserver(() => this.#setContent());
observer.observe(this, { childList: true });
this.#setContent();
}
#setContent () {
const { childNodes, shadowRoot } = this;
shadowRoot.innerHTML = "<ul>";
const list = shadowRoot.querySelector("ul");
for (const node of childNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
if (node.nodeName === "SLOT") {
for (const subNode of node.assignedElements({flatten: true})) {
const listItem = document.createElement("li");
const slot = document.createElement("slot");
listItem.append(slot);.
list.append(listItem);
slot.assign(subNode);
}
} else {
const listItem = document.createElement("li");
const slot = document.createElement("slot");
listItem.append(slot);
list.append(listItem);
slot.assign(node);
}
}
}
});
While the wrapped element now produces the correct number of list items, they do not render the slotted items. I presume because the nodes are already assigned to wrapper-bowl's slot.
While I am confident that I could get around the issue by also using manual slot assignment in wrapper-bowl, I am also confident that it shouldn't be necessary.
I am very new to web components, so if there is something I missed that makes this type of behavior achievable, I apologize and would appreciate being pointed in the right direction.