Resources:
Before submitting an issue, please consult our docs.
Stencil version:
@stencil/[email protected]
I'm submitting a ...
[x] bug report
[ ] feature request
Current behavior:
When applying dynamic lightdom content (later than the initial render cycle), the current slot implementation doesn't distribute correctly, even throw errors when applied.
I tried to make tests to reproduce using jest but it doesn't seem that it's possible at the moment, even with static content (though this actually works when rendering in a browser).
it('should distribute slots correctly', async () => {
const element = await render({
components: [MyElement],
html: '<my-element>slotted</my-element>'
});
const expected = `<div><div>top</div>slotted<div>bottom</div></div>`;
await flush(element);
expect(element.innerHTML).toEqual(expected);
});
/*
Expected value to equal:
"<div><div>top</div>slotted<div>bottom</div></div>"
Received:
"slotted<div><div>top</div><div>button</div></div>"
*/
Expected behavior:
Light dom in slots to be distributed correctly
Steps to reproduce:
see related code
Related code:
import { Component } from '@stencil/core';
@Component({
tag: 'my-element'
})
export class MyElement {
render() {
return (
<div>
<div>top</div>
<slot></slot>
<div>bottom</div>
</div>
);
}
}
Static content composition is fine.
<!-- implementation -->
<my-element>slotted</my-element>
<!-- resulting composed dom -->
<my-element>
<div>
<div>top</div>
slotted
<div>bottom</div>
</div>
<my-element>
Slightly defering the dom manipulation composes correctly, I guess due to async rendering.
<!-- implementation -->
<my-element>original</my-element>
<script>
const root = document.querySelector('my-element');
const dynamicElement = document.createElement('div');
dynamicElement.textContent = 'dynamic';
setTimeout(() => {
root.appendChild(dynamicElement);
}, 90);
</script>
<!-- resulting composed dom -->
<my-element>
<div>
<div>top</div>
original
<div>dynamic</div>
<div>bottom</div>
</div>
<my-element>
Once we defer lightdom manipulation a little more things starts to get wacky.
<!-- implementation -->
<my-element>original</my-element>
<script>
const root = document.querySelector('my-element');
const dynamicElement = document.createElement('div');
dynamicElement.textContent = 'dynamic';
setTimeout(() => {
root.appendChild(dynamicElement);
}, 102);
</script>
<!-- resulting composed dom -->
<my-element>
<div>dynamic</div>
<div>
<div>top</div>
<!---->
static
<div>bottom</div>
</div>
<my-element>
Defering more than 105ms (at least on my machine) produces an error:
Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': The new child element contains the parent.
$insertBefore: function insertBefore(parentNode, childNode, referenceNode) {
parentNode.insertBefore(childNode, referenceNode);
}
referenceNode is null at this time.
<!-- implementation -->
<my-element>original</my-element>
<script>
const root = document.querySelector('my-element');
const dynamicElement = document.createElement('div');
dynamicElement.textContent = 'dynamic';
setTimeout(() => {
root.appendChild(dynamicElement);
}, 150);
</script>
<!-- resulting composed dom -->
<my-element>
<div>dynamic</div>
<div>
<div>top</div>
<!---->
static
<div>bottom</div>
</div>
<my-element>
Other information:
None at this time
This is a current limitation of the slot system, and without investing in a larger codebase and introducing new bugs I think we'll have to close this for now. However, we will be shipping shadow DOM as the default soon so you'll be able to use that instead. Thanks for the bug report!
Ah ok, well shadowDOM should fix that issue, I'm happy to hear
@adamdbradley how would you test the content of a slot in jest?
e.g.
given an element in a before each callback as such
beforeEach(async () => {
element = await render({
components: [Grid],
html: '<grid></grid>',
});
});
how would I set the content between the grid items?
Most helpful comment
Ah ok, well shadowDOM should fix that issue, I'm happy to hear