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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions