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
|
```ts
|
||||||
{
|
{
|
||||||
template: TemplateResult;
|
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)) {
|
} else if (isRerenderConfig(slotFunctionResult)) {
|
||||||
// Rerenderable slots are scheduled in the "updated loop"
|
// Rerenderable slots are scheduled in the "updated loop"
|
||||||
this.__slotsThatNeedRerender.add(slotName);
|
this.__slotsThatNeedRerender.add(slotName);
|
||||||
|
|
||||||
|
// For backw. compat, we allow a first render on connectedCallback
|
||||||
|
if (slotFunctionResult.firstRenderOnConnected) {
|
||||||
|
this.__rerenderSlot(slotName);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
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.
|
`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 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 { ScopedElementsMixin } from '@open-wc/scoped-elements';
|
||||||
import { SlotMixin } from '@lion/ui/core.js';
|
import { SlotMixin } from '@lion/ui/core.js';
|
||||||
import { LitElement } from 'lit';
|
import { LitElement } from 'lit';
|
||||||
|
|
@ -300,6 +300,83 @@ describe('SlotMixin', () => {
|
||||||
expect(document.activeElement).to.equal(el._focusableNode);
|
expect(document.activeElement).to.equal(el._focusableNode);
|
||||||
expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode);
|
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', () => {
|
describe('SlotFunctionResult types', () => {
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,16 @@ export type SlotRerenderObject = {
|
||||||
template: TemplateResult;
|
template: TemplateResult;
|
||||||
/**
|
/**
|
||||||
* Add logic that will be performed after the render
|
* Add logic that will be performed after the render
|
||||||
* @deprecated
|
* @deprecated use regular ReactiveElement/LitElement reactive cycle callbacks instead
|
||||||
*/
|
*/
|
||||||
afterRender?: Function;
|
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;
|
export type SlotFunctionResult = TemplateResult | Element | SlotRerenderObject | undefined;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue