Skip to content

Manual slot assignment and slot elements in consuming components #1115

@Schommer475

Description

@Schommer475

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions