Hi All,
I've a question relating to component lifecycle and listening to slotchange events.
I have created a basic component to describe my findings.
The component has two slots; one default and another named slot called "contents". I have added a slotchange event to both slots as I'm interested to know if the slots have changed.
In both Chrome and Firefox the slotchange events are handled as I expected.
Safari seems to handle the component lifecycle a little differently. Safari handles the slotchange event before the connectedCallback is called.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Component Test</title>
</head>
<body>
<script src="./dist/LitElement.js"></script>
<my-lit-element>
<div>I'm in the <b>default</b> slot</div>
<div slot="contents">I'm in the <b>named</b> slot</div>
</my-lit-element>
</body>
</html>
CustomLitElement.js
import {html, render} from 'lit-html';
class CustomLitElement extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
render(this.template(), this.shadowRoot);
}
template() {
return html`
<h3>Custom Component using lit-html</h3>
<div class="default__slot">
<slot id="default-slot" @slotchange=${this}></slot>
</div>
<div class="named__slot">
<slot id="named-slot" name="contents" @slotchange=${this}></slot>
</div> `;
}
handleEvent(event) {
if (event.target.id === 'default-slot') {
console.log(`slotchange event on default slot`);
}
if (event.target.id === 'named-slot') {
console.log(`slotchange event on named slot`);
}
}
connectedCallback() {
console.log('connectedCallback - my-lit-element');
}
}
window.customElements.define('my-lit-element', CustomLitElement);
__Chrome__
__Firefox__
__Safari__
If I move the render function from the constructor to the connectedCallback I will not get the slotchange event, as the mutation (in Safari) happens __before__ the connectedCallback, which is before I've called the render, and registered interest in the slotchange event.
What is the correct order?
Where should I perform the initial render?
Is Safari handling things incorrectly ?
Any help would be appreciated :)
This might be a Safari bug. In general if you want to get light DOM children, we've found you need to get an initial set with assignedNodes() as well as listen to slotchange
Actually it is a Safari Bug, https://github.com/whatwg/dom/issues/447
Thanks @justinfagnani, that pointer really helped.
Thanks too, @mzeiher I'll keep an eye on this.
Out of curiosity should I perform the initial render in the constructor or in the connectedCallback?
This might be a Safari bug. In general if you want to get light DOM children, we've found you need to get an initial set with
assignedNodes()as well as listen toslotchange
@katejeffreys note to self for docs
Just my humble opinion, but I think Safari got it right and the issue is page load. When the html gets parsed, and before any user interaction or external scripts can be run on that element, how does one know what elements were part of the page and which may have been added later? The answer in Safari is - "you know when connectedCallback fires", but for other browsers, you don't know without a more hack-ish solution.
It sounds like the spec has been updated since this bug was filed, so browsers should be firing slotchange consistently. And the LitElement API has changed as well, so unless anyone feels strongly that there's something to document here, I'm going to mark this as stale.
OK, we don't have "Stale" or "wontfix" labels, so I'm going to label it "More Info Needed." :D
Most helpful comment
This might be a Safari bug. In general if you want to get light DOM children, we've found you need to get an initial set with
assignedNodes()as well as listen toslotchange