Skip to content

Instantly share code, notes, and snippets.

@adinan-cenci
Last active March 7, 2021 01:40
Show Gist options
  • Save adinan-cenci/e2f55e355b60bfed2d85900fcce199ce to your computer and use it in GitHub Desktop.
Save adinan-cenci/e2f55e355b60bfed2d85900fcce199ce to your computer and use it in GitHub Desktop.
Using <slot> tags in custom elements without the shadow dom.

Using <slot> tags in custom elements without shadow dom

The <slot> tag is very useful when creating custom elements. However, this feature is only available with the use of shadow dom, the downside being that we can't use the parent document's css to style the shadow dom.

So we either copy the css into the shadow dom or don't use slots at all.

In this snippet we'll see how to circumvent this limitation with the use of a MutationObserver object.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
<script>
class CustomElement extends HTMLElement
{
constructor()
{
super();
this.slots = [];
}
connectedCallback()
{
this.innerHTML =
`<div class="first-frame">
<slot></slot>
</div>
<div class="second-frame">
<slot id="second-slot"></slot>
</div>`;
this.setupSlots();
this.setupObserver();
}
setupSlots()
{
this.querySelectorAll(':scope slot').forEach( (el) =>
{
var slot = {};
slot.id = el.getAttribute('id');
slot.parentNode = el.parentNode;
slot.previousSibling = el.previousSibling;
slot.children = [];
this.slots.push(slot);
el.remove();
});
}
setupObserver()
{
var config = {
childList : true,
attributes : false,
subtree : false
};
this.observer = new MutationObserver( this.observer.bind(this) );
this.observer.observe(this, config);
}
observer(mutationsList)
{
mutationsList.forEach( (mutation) =>
{
mutation.addedNodes.forEach( (el) =>
{
this.onElementAdded(el);
});
});
}
onElementAdded(el)
{
if (el.moved) {
return;
}
for (var sl of this.slots) {
if (! this.doesElementMatchSlot(sl, el)) {
continue;
}
el.moved = true;
this.appendToSlot(sl, el);
break;
}
}
doesElementMatchSlot(slot, element)
{
if (element.constructor.name == 'Text') {
return true;
}
var slotId = element.getAttribute('slot');
return slotId == slot.id;
}
appendToSlot(slot, element)
{
var appendTo = slot.parentNode;
var after = null;
var before = null;
if (slot.children.length) {
after = slot.children[ slot.children.length - 1 ];
} else if (slot.previousSibling) {
after = slot.previousSibling;
}
before = after && after.nextSibling ?
after.nextSibling :
null;
before ?
appendTo.insertBefore(element, before) :
appendTo.append(element);
slot.children.push(element);
}
}
customElements.define('custom-element', CustomElement);
</script>
<style>
custom-element { display: block; padding: 20px; background-color: pink; }
.first-frame { padding: 10px; margin-bottom: 20px; background-color: #ec972e; }
.second-frame { padding: 10px; background-color: #2b9eca; }
</style>
</head>
<body>
<custom-element>
<p>First paragraph</p>
<p slot="second-slot">Second paragraph</p>
<p slot="second-slot">Third paragraph</p>
<p>Fourth paragraph</p>
</custom-element>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment