Skip to content

Commit 097694d

Browse files
authored
refactor: split slots logic to controller (#93)
1 parent aac5335 commit 097694d

File tree

4 files changed

+118
-82
lines changed

4 files changed

+118
-82
lines changed

src/api-demo-knobs-mixin.ts

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { LitElement, PropertyValues } from 'lit';
22
import { property } from 'lit/decorators/property.js';
3-
import { getSlotDefault, Knob } from './lib/knobs.js';
3+
import { Knob } from './lib/knobs.js';
44
import {
55
ComponentWithProps,
66
CSSPropertyInfo,
7-
PropertyInfo,
8-
SlotInfo,
9-
SlotValue
7+
PropertyInfo
108
} from './lib/types.js';
119
import {
1210
getTemplates,
@@ -135,16 +133,13 @@ export interface ApiDemoKnobsInterface extends HasKnobs {
135133
tag: string;
136134
props: PropertyInfo[];
137135
propKnobs: Knob<PropertyInfo>[];
138-
slots: SlotInfo[];
139136
cssProps: CSSPropertyInfo[];
140137
exclude: string;
141138
vid?: number;
142-
processedSlots: SlotValue[];
143139
processedCss: CSSPropertyInfo[];
144140
customKnobs: Knob<PropertyInfo>[];
145141
knobs: Record<string, Knob>;
146142
setKnobs(target: HTMLInputElement): void;
147-
setSlots(target: HTMLInputElement): void;
148143
setCss(target: HTMLInputElement): void;
149144
initKnobs(component: HTMLElement): void;
150145
}
@@ -158,19 +153,13 @@ export const ApiDemoKnobsMixin = <T extends Constructor<LitElement>>(
158153
@property({ attribute: false })
159154
props: PropertyInfo[] = [];
160155

161-
@property({ attribute: false })
162-
slots: SlotInfo[] = [];
163-
164156
@property({ attribute: false })
165157
cssProps: CSSPropertyInfo[] = [];
166158

167159
@property() exclude = '';
168160

169161
@property({ type: Number }) vid?: number;
170162

171-
@property({ attribute: false })
172-
processedSlots!: SlotValue[];
173-
174163
@property({ attribute: false })
175164
processedCss!: CSSPropertyInfo[];
176165

@@ -188,29 +177,9 @@ export const ApiDemoKnobsMixin = <T extends Constructor<LitElement>>(
188177
if (props.has('tag')) {
189178
this.knobs = {};
190179
this.processedCss = [];
191-
this.processedSlots = [];
192180
this.propKnobs = getKnobs(this.tag, this.props, this.exclude);
193181
this.customKnobs = getCustomKnobs(this.tag, this.vid);
194182
}
195-
196-
if (props.has('slots') && this.slots) {
197-
this.processedSlots = this.slots
198-
.sort((a: SlotInfo, b: SlotInfo) => {
199-
if (a.name === '') {
200-
return 1;
201-
}
202-
if (b.name === '') {
203-
return -1;
204-
}
205-
return a.name.localeCompare(b.name);
206-
})
207-
.map((slot: SlotInfo) => {
208-
return {
209-
...slot,
210-
content: getSlotDefault(slot.name, 'content')
211-
};
212-
});
213-
}
214183
}
215184

216185
getKnob(name: string): {
@@ -269,20 +238,6 @@ export const ApiDemoKnobsMixin = <T extends Constructor<LitElement>>(
269238
}
270239
}
271240

272-
setSlots(target: HTMLInputElement): void {
273-
const name = target.dataset.slot;
274-
const content = target.value;
275-
276-
this.processedSlots = this.processedSlots.map((slot) => {
277-
return slot.name === name
278-
? {
279-
...slot,
280-
content
281-
}
282-
: slot;
283-
});
284-
}
285-
286241
initKnobs(component: HTMLElement) {
287242
if (hasTemplate(this.vid as number, this.tag, HOST)) {
288243
// Apply property values from template

src/api-viewer-demo.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { LitElement, html, TemplateResult } from 'lit';
22
import { property } from 'lit/decorators/property.js';
33
import { cache } from 'lit/directives/cache.js';
44
import { EventsController } from './controllers/events-controller.js';
5+
import { SlotsController } from './controllers/slots-controller.js';
56
import { renderEvents } from './lib/demo-events.js';
67
import { renderSnippet } from './lib/demo-snippet.js';
78
import { renderer } from './lib/renderer.js';
@@ -11,7 +12,7 @@ import {
1112
renderKnobs,
1213
slotRenderer
1314
} from './lib/knobs.js';
14-
import { EventInfo } from './lib/types.js';
15+
import { EventInfo, SlotInfo } from './lib/types.js';
1516
import { hasTemplate, TemplateTypes } from './lib/utils.js';
1617
import { ApiDemoKnobsMixin } from './api-demo-knobs-mixin.js';
1718
import './api-viewer-panel.js';
@@ -24,10 +25,15 @@ class ApiViewerDemo extends ApiDemoKnobsMixin(LitElement) {
2425
@property({ attribute: false })
2526
events: EventInfo[] = [];
2627

28+
@property({ attribute: false })
29+
slots: SlotInfo[] = [];
30+
2731
private _whenDefined: Record<string, Promise<unknown>> = {};
2832

2933
private eventsController!: EventsController;
3034

35+
private slotsController!: SlotsController;
36+
3137
protected createRenderRoot(): this {
3238
return this;
3339
}
@@ -47,7 +53,7 @@ class ApiViewerDemo extends ApiDemoKnobsMixin(LitElement) {
4753

4854
return html`
4955
<div part="warning">
50-
Element ${this.tag} is not defined. Have you imported it?
56+
Element ${tag} is not defined. Have you imported it?
5157
</div>
5258
`;
5359
}
@@ -62,16 +68,15 @@ class ApiViewerDemo extends ApiDemoKnobsMixin(LitElement) {
6268

6369
const id = this.vid as number;
6470
const log = this.eventsController?.log || [];
65-
const slots = this.processedSlots;
66-
const hideSlots = noSlots || hasTemplate(id, this.tag, TemplateTypes.SLOT);
71+
const slots = this.slotsController?.slots || [];
72+
const hideSlots = noSlots || hasTemplate(id, tag, TemplateTypes.SLOT);
6773

6874
return html`
6975
<div part="demo-output" @rendered=${this.onRendered}>
7076
${renderer({
7177
id,
72-
tag: this.tag,
78+
tag,
7379
knobs: this.knobs,
74-
slots,
7580
cssProps: this.processedCss
7681
})}
7782
</div>
@@ -82,13 +87,7 @@ class ApiViewerDemo extends ApiDemoKnobsMixin(LitElement) {
8287
${this.copyBtnText}
8388
</button>
8489
<div part="demo-snippet">
85-
${renderSnippet(
86-
this.vid as number,
87-
this.tag,
88-
this.knobs,
89-
slots,
90-
this.processedCss
91-
)}
90+
${renderSnippet(id, tag, this.knobs, slots, this.processedCss)}
9291
</div>
9392
</api-viewer-panel>
9493
<api-viewer-tab
@@ -206,6 +205,8 @@ class ApiViewerDemo extends ApiDemoKnobsMixin(LitElement) {
206205
this.initKnobs(component);
207206

208207
this.initEvents(component);
208+
209+
this.initSlots(component);
209210
}
210211

211212
private initEvents(component: HTMLElement) {
@@ -218,6 +219,20 @@ class ApiViewerDemo extends ApiDemoKnobsMixin(LitElement) {
218219
this.eventsController = new EventsController(this, component, this.events);
219220
}
220221

222+
private initSlots(component: HTMLElement) {
223+
const controller = this.slotsController;
224+
if (controller) {
225+
this.removeController(controller);
226+
}
227+
228+
this.slotsController = new SlotsController(
229+
this,
230+
this.vid as number,
231+
component,
232+
this.slots
233+
);
234+
}
235+
221236
private _onCssChanged(e: CustomEvent): void {
222237
this.setCss(e.composedPath()[0] as HTMLInputElement);
223238
}
@@ -227,7 +242,8 @@ class ApiViewerDemo extends ApiDemoKnobsMixin(LitElement) {
227242
}
228243

229244
private _onSlotChanged(e: Event): void {
230-
this.setSlots(e.composedPath()[0] as HTMLInputElement);
245+
const target = e.composedPath()[0] as HTMLInputElement;
246+
this.slotsController.setValue(target.dataset.slot as string, target.value);
231247
}
232248
}
233249

src/controllers/slots-controller.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { ReactiveController, ReactiveControllerHost } from 'lit';
2+
import { getSlotDefault } from '../lib/knobs.js';
3+
import { SlotInfo, SlotValue } from '../lib/types.js';
4+
import { hasTemplate, TemplateTypes } from '../lib/utils.js';
5+
6+
export class SlotsController implements ReactiveController {
7+
host: ReactiveControllerHost;
8+
9+
enabled: boolean;
10+
11+
el: HTMLElement;
12+
13+
private _slots: SlotValue[] = [];
14+
15+
get slots(): SlotValue[] {
16+
return this._slots;
17+
}
18+
19+
set slots(slots: SlotValue[]) {
20+
this._slots = slots;
21+
22+
// Apply slots content by re-creating nodes
23+
if (this.enabled && this.el.isConnected && slots && slots.length) {
24+
this.el.innerHTML = '';
25+
slots.forEach((slot) => {
26+
let node: Element | Text;
27+
const { name, content } = slot;
28+
if (name) {
29+
node = document.createElement('div');
30+
node.setAttribute('slot', name);
31+
node.textContent = content;
32+
} else {
33+
node = document.createTextNode(content);
34+
}
35+
this.el.appendChild(node);
36+
});
37+
}
38+
39+
// Update the demo snippet
40+
this.host.requestUpdate();
41+
}
42+
43+
constructor(
44+
host: ReactiveControllerHost,
45+
id: number,
46+
component: HTMLElement,
47+
slots: SlotInfo[]
48+
) {
49+
(this.host = host).addController(this as ReactiveController);
50+
this.el = component;
51+
this.enabled = !hasTemplate(id, component.localName, TemplateTypes.SLOT);
52+
this.slots = slots
53+
.sort((a: SlotInfo, b: SlotInfo) => {
54+
if (a.name === '') {
55+
return 1;
56+
}
57+
if (b.name === '') {
58+
return -1;
59+
}
60+
return a.name.localeCompare(b.name);
61+
})
62+
.map((slot: SlotInfo) => {
63+
return {
64+
...slot,
65+
content: getSlotDefault(slot.name, 'content')
66+
};
67+
}) as SlotValue[];
68+
}
69+
70+
hostDisconnected() {
71+
this.slots = [];
72+
}
73+
74+
setValue(name: string, content: string) {
75+
this.slots = this.slots.map((slot) => {
76+
return slot.name === name
77+
? {
78+
...slot,
79+
content
80+
}
81+
: slot;
82+
});
83+
}
84+
}

src/lib/renderer.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import { directive, Directive, PartInfo, PartType } from 'lit/directive.js';
33
import { templateContent } from 'lit/directives/template-content.js';
44
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
55
import { Knob } from './knobs.js';
6-
import { CSSPropertyInfo, SlotValue } from './types.js';
6+
import { CSSPropertyInfo } from './types.js';
77
import {
88
getTemplate,
99
getTemplateNode,
10-
hasTemplate,
1110
isTemplate,
1211
normalizeType,
1312
TemplateTypes
@@ -17,7 +16,6 @@ export type ComponentRendererOptions = {
1716
id: number;
1817
tag: string;
1918
knobs: Record<string, Knob>;
20-
slots: SlotValue[];
2119
cssProps: CSSPropertyInfo[];
2220
};
2321

@@ -27,7 +25,7 @@ const updateComponent = (
2725
component: HTMLElement,
2826
options: ComponentRendererOptions
2927
): void => {
30-
const { id, tag, knobs, slots, cssProps } = options;
28+
const { knobs, cssProps } = options;
3129

3230
// Apply knobs using properties or attributes
3331
Object.keys(knobs).forEach((key: string) => {
@@ -45,23 +43,6 @@ const updateComponent = (
4543
}
4644
});
4745

48-
// Apply slots content by re-creating nodes
49-
if (!hasTemplate(id, tag, SLOT) && slots.length) {
50-
component.innerHTML = '';
51-
slots.forEach((slot) => {
52-
let node: Element | Text;
53-
const { name, content } = slot;
54-
if (name) {
55-
node = document.createElement('div');
56-
node.setAttribute('slot', name);
57-
node.textContent = content;
58-
} else {
59-
node = document.createTextNode(content);
60-
}
61-
component.appendChild(node);
62-
});
63-
}
64-
6546
// Apply CSS props
6647
if (cssProps.length) {
6748
cssProps.forEach((prop) => {

0 commit comments

Comments
 (0)