Merge pull request #2219 from ing-bank/feat/allowFirstSlotRenderOnConnected
feat: allow SlotRerenderObject to to first render on connectedCallbac…
This commit is contained in:
commit
d4298f69e6
5 changed files with 103 additions and 2 deletions
5
.changeset/funny-numbers-prove.md
Normal file
5
.changeset/funny-numbers-prove.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@lion/ui': patch
|
||||
---
|
||||
|
||||
feat: allow SlotRerenderObject to first render on connectedCallback via `firstRenderOnConnected`
|
||||
|
|
@ -104,6 +104,13 @@ A `SlotRerenderObject` looks like this:
|
|||
```ts
|
||||
{
|
||||
template: TemplateResult;
|
||||
/**
|
||||
* For backward compat with traditional light render methods,
|
||||
* it might be needed to have slot contents available in `connectedCallback`.
|
||||
* Only enable this for existing components that rely on light content availability in connectedCallback.
|
||||
* For new components, please align with ReactiveElement/LitElement reactive cycle callbacks.
|
||||
*/
|
||||
firstRenderOnConnected?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -195,6 +195,11 @@ const SlotMixinImplementation = superclass =>
|
|||
} else if (isRerenderConfig(slotFunctionResult)) {
|
||||
// Rerenderable slots are scheduled in the "updated loop"
|
||||
this.__slotsThatNeedRerender.add(slotName);
|
||||
|
||||
// For backw. compat, we allow a first render on connectedCallback
|
||||
if (slotFunctionResult.firstRenderOnConnected) {
|
||||
this.__rerenderSlot(slotName);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Slot "${slotName}" configured inside "get slots()" (in prototype) of ${this.constructor.name} may return these types: TemplateResult | Node | {template:TemplateResult, afterRender?:function} | undefined.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import sinon from 'sinon';
|
||||
import { defineCE, expect, fixture, unsafeStatic, html } from '@open-wc/testing';
|
||||
import { defineCE, expect, fixture, fixtureSync, unsafeStatic, html } from '@open-wc/testing';
|
||||
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
|
||||
import { SlotMixin } from '@lion/ui/core.js';
|
||||
import { LitElement } from 'lit';
|
||||
|
|
@ -300,6 +300,83 @@ describe('SlotMixin', () => {
|
|||
expect(document.activeElement).to.equal(el._focusableNode);
|
||||
expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode);
|
||||
});
|
||||
|
||||
describe('firstRenderOnConnected (for backwards compatibility)', () => {
|
||||
it('does render on connected when firstRenderOnConnected:true', async () => {
|
||||
// Start with elem that does not render on connectedCallback
|
||||
const tag = defineCE(
|
||||
// @ts-expect-error
|
||||
class extends SlotMixin(LitElement) {
|
||||
static properties = { currentValue: Number };
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.currentValue = 0;
|
||||
}
|
||||
|
||||
get slots() {
|
||||
return {
|
||||
...super.slots,
|
||||
template: () => ({
|
||||
firstRenderOnConnected: true,
|
||||
template: html`<span>${this.currentValue}</span> `,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<slot name="template"></slot>`;
|
||||
}
|
||||
|
||||
get _templateNode() {
|
||||
return /** @type HTMLSpanElement */ (
|
||||
Array.from(this.children).find(elm => elm.slot === 'template')
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
const el = /** @type {* & SlotHost} */ (fixtureSync(`<${tag}></${tag}>`));
|
||||
expect(el._templateNode.slot).to.equal('template');
|
||||
expect(el._templateNode.textContent?.trim()).to.equal('0');
|
||||
});
|
||||
|
||||
it('does not render on connected when firstRenderOnConnected:false', async () => {
|
||||
// Start with elem that does not render on connectedCallback
|
||||
const tag = defineCE(
|
||||
// @ts-expect-error
|
||||
class extends SlotMixin(LitElement) {
|
||||
static properties = { currentValue: Number };
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.currentValue = 0;
|
||||
}
|
||||
|
||||
get slots() {
|
||||
return {
|
||||
...super.slots,
|
||||
template: () => ({ template: html`<span>${this.currentValue}</span> ` }),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<slot name="template"></slot>`;
|
||||
}
|
||||
|
||||
get _templateNode() {
|
||||
return /** @type HTMLSpanElement */ (
|
||||
Array.from(this.children).find(elm => elm.slot === 'template')
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
const el = /** @type {* & SlotHost} */ (fixtureSync(`<${tag}></${tag}>`));
|
||||
expect(el._templateNode).to.be.undefined;
|
||||
await el.updateComplete;
|
||||
expect(el._templateNode.slot).to.equal('template');
|
||||
expect(el._templateNode.textContent?.trim()).to.equal('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SlotFunctionResult types', () => {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,16 @@ export type SlotRerenderObject = {
|
|||
template: TemplateResult;
|
||||
/**
|
||||
* Add logic that will be performed after the render
|
||||
* @deprecated
|
||||
* @deprecated use regular ReactiveElement/LitElement reactive cycle callbacks instead
|
||||
*/
|
||||
afterRender?: Function;
|
||||
/**
|
||||
* For backward compat with traditional light render methods,
|
||||
* it might be needed to have slot contents available in `connectedCallback`.
|
||||
* Only enable this for existing components that rely on light content availability in connectedCallback.
|
||||
* For new components, please align with ReactiveElement/LitElement reactive cycle callbacks.
|
||||
*/
|
||||
firstRenderOnConnected?: boolean;
|
||||
};
|
||||
|
||||
export type SlotFunctionResult = TemplateResult | Element | SlotRerenderObject | undefined;
|
||||
|
|
|
|||
Loading…
Reference in a new issue