fix(ui/core): rerender direct host child with right slot attr when root is switched

This commit is contained in:
Thijs Louisse 2025-01-16 22:24:13 +01:00 committed by Thijs Louisse
parent 81e2a1d2d1
commit 5344fdeb66
4 changed files with 71 additions and 1 deletions

View file

@ -0,0 +1,5 @@
---
'@lion/ui': patch
---
[core] rerender direct host child with right slot attr when root is switched

View file

@ -182,6 +182,21 @@ const SlotMixinImplementation = superclass =>
// Providing all options breaks Safari: we keep host and creationScope // Providing all options breaks Safari: we keep host and creationScope
const { creationScope, host } = this.renderOptions; const { creationScope, host } = this.renderOptions;
render(template, rerenderTarget, { creationScope, host, renderBefore }); render(template, rerenderTarget, { creationScope, host, renderBefore });
// Assume we had this config:
// `'my-slot': () => ({ template: myBool ? html`<div id=a></div>` : html`<span id=b></span>`, renderAsDirectHostChild: true })`
// If myBool started as true, <div id=a></div> would be rendered in first render above, a slot would be applied,
// resulting in <div id=a slot=my-slot></div>
// However, when myBool changes to false, the <span id=b></span> would be rendered as root instead...
// We need to make sure that this "replaced root" gets the slot applied as well => <span id=b slot=my-slot></span>
const isRerenderingRootOfTemplate =
renderAsDirectHostChild &&
renderBefore.previousElementSibling &&
!renderBefore.previousElementSibling.slot;
if (isRerenderingRootOfTemplate) {
renderBefore.previousElementSibling.slot = slotName;
}
} }
/** /**

View file

@ -345,6 +345,56 @@ describe('SlotMixin', () => {
expect(isActiveElement(el._focusableNode._buttonNode, { deep: true })).to.be.true; expect(isActiveElement(el._focusableNode._buttonNode, { deep: true })).to.be.true;
}); });
it('allows for switching template root in slot as a direct child', async () => {
const tagName = defineCE(
// @ts-expect-error
class extends SlotMixin(LitElement) {
static properties = { isSwitched: Boolean };
constructor() {
super();
this.isSwitched = false;
}
get slots() {
return {
...super.slots,
'my-root-switcher-node': () => ({
template: this.isSwitched
? html`<div id="is-switched"></div>`
: html`<span id="is-not-switched"> </span> `,
renderAsDirectHostChild: true,
}),
};
}
render() {
return html`<slot name="my-root-switcher-node"></slot>`;
}
get _myRootSwitcherNode() {
return /** @type HTMLSpanElement */ (
Array.from(this.children).find(elm => elm.slot === 'my-root-switcher-node')
);
}
},
);
const el = /** @type {* & SlotHost} */ (await fixture(`<${tagName}></${tagName}>`));
expect(el._myRootSwitcherNode.id).to.equal('is-not-switched');
expect(el.innerHTML).to.equal(
`<!--_start_slot_my-root-switcher-node_--><!----><span id="is-not-switched" slot="my-root-switcher-node"> </span> <!--_end_slot_my-root-switcher-node_-->`,
);
el.isSwitched = true;
await el.updateComplete;
expect(el._myRootSwitcherNode.id).to.equal('is-switched');
expect(el.innerHTML).to.equal(
`<!--_start_slot_my-root-switcher-node_--><!----><div id="is-switched" slot="my-root-switcher-node"></div><!--_end_slot_my-root-switcher-node_-->`,
);
});
describe('firstRenderOnConnected (for backwards compatibility)', () => { describe('firstRenderOnConnected (for backwards compatibility)', () => {
it('does render on connected when firstRenderOnConnected:true', async () => { it('does render on connected when firstRenderOnConnected:true', async () => {
// Start with elem that does not render on connectedCallback // Start with elem that does not render on connectedCallback

View file

@ -181,7 +181,7 @@ export class LionInputTelDropdown extends LionInputTel {
return { return {
template: templates.dropdown(this._templateDataDropdown), template: templates.dropdown(this._templateDataDropdown),
renderAsDirectHostChild: Boolean, renderAsDirectHostChild: true,
}; };
}, },
}; };