Skip to content

Commit 5b16b3b

Browse files
authored
fix(core): slot controller when used in react (#2531)
1 parent 99e4081 commit 5b16b3b

File tree

2 files changed

+50
-52
lines changed

2 files changed

+50
-52
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
"@patternfly/pfe-core": patch
3+
---
4+
`SlotController`: ensure first render is correct when used in certain javascript frameworks

core/pfe-core/controllers/slot-controller.ts

Lines changed: 46 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { ReactiveController, ReactiveElement } from 'lit';
22

3-
import { bound } from '../decorators/bound.js';
43
import { Logger } from './logger.js';
54

65
interface AnonymousSlot {
@@ -50,52 +49,60 @@ const isSlot =
5049
export class SlotController implements ReactiveController {
5150
public static anonymous = Symbol('anonymous slot');
5251

53-
private nodes = new Map<string | typeof SlotController.anonymous, Slot>();
52+
#nodes = new Map<string | typeof SlotController.anonymous, Slot>();
5453

55-
private logger: Logger;
54+
#logger: Logger;
5655

57-
private firstUpdated = false;
56+
#firstUpdated = false;
5857

59-
private mo = new MutationObserver(this.onMutation);
58+
#mo = new MutationObserver(records => this.#onMutation(records));
6059

61-
private slotNames: (string | null)[];
60+
#slotNames: (string | null)[];
6261

63-
private deprecations: Record<string, string> = {};
62+
#deprecations: Record<string, string> = {};
6463

6564
constructor(public host: ReactiveElement, ...config: ([SlotsConfig] | (string | null)[])) {
66-
this.logger = new Logger(this.host);
65+
this.#logger = new Logger(this.host);
6766

6867
if (isObjectConfigSpread(config)) {
6968
const [{ slots, deprecations }] = config;
70-
this.slotNames = slots;
71-
this.deprecations = deprecations ?? {};
69+
this.#slotNames = slots;
70+
this.#deprecations = deprecations ?? {};
7271
} else if (config.length >= 1) {
73-
this.slotNames = config;
74-
this.deprecations = {};
72+
this.#slotNames = config;
73+
this.#deprecations = {};
7574
} else {
76-
this.slotNames = [null];
75+
this.#slotNames = [null];
7776
}
7877

7978

8079
host.addController(this);
8180
}
8281

83-
hostConnected() {
84-
this.host.addEventListener('slotchange', this.onSlotChange as EventListener);
85-
this.firstUpdated = false;
86-
this.mo.observe(this.host, { childList: true });
87-
this.init();
82+
async hostConnected() {
83+
this.host.addEventListener('slotchange', this.#onSlotChange as EventListener);
84+
this.#firstUpdated = false;
85+
this.#mo.observe(this.host, { childList: true });
86+
// Map the defined slots into an object that is easier to query
87+
this.#nodes.clear();
88+
// Loop over the properties provided by the schema
89+
this.#slotNames.forEach(this.#initSlot);
90+
Object.values(this.#deprecations).forEach(this.#initSlot);
91+
this.host.requestUpdate();
92+
// insurance for framework integrations
93+
await this.host.updateComplete;
94+
this.host.requestUpdate();
8895
}
8996

9097
hostUpdated() {
91-
if (!this.firstUpdated) {
92-
this.slotNames.forEach(this.initSlot);
93-
this.firstUpdated = true;
98+
if (!this.#firstUpdated) {
99+
this.#slotNames.forEach(this.#initSlot);
100+
this.#firstUpdated = true;
94101
}
95102
}
96103

97104
hostDisconnected() {
98-
this.mo.disconnect();
105+
this.#mo.disconnect();
99106
}
100107

101108
/**
@@ -106,11 +113,11 @@ export class SlotController implements ReactiveController {
106113
*/
107114
hasSlotted(...names: string[]): boolean {
108115
if (!names.length) {
109-
this.logger.warn(`Please provide at least one slot name for which to search.`);
116+
this.#logger.warn(`Please provide at least one slot name for which to search.`);
110117
return false;
111118
} else {
112119
return names.some(x =>
113-
this.nodes.get(x)?.hasContent ?? false);
120+
this.#nodes.get(x)?.hasContent ?? false);
114121
}
115122
}
116123

@@ -135,57 +142,44 @@ export class SlotController implements ReactiveController {
135142
*/
136143
getSlotted<T extends Element = Element>(...slotNames: string[]): T[] {
137144
if (!slotNames.length) {
138-
return (this.nodes.get(SlotController.anonymous)?.elements ?? []) as T[];
145+
return (this.#nodes.get(SlotController.anonymous)?.elements ?? []) as T[];
139146
} else {
140147
return slotNames.flatMap(slotName =>
141-
this.nodes.get(slotName)?.elements ?? []) as T[];
148+
this.#nodes.get(slotName)?.elements ?? []) as T[];
142149
}
143150
}
144151

145-
@bound private onSlotChange(event: Event & { target: HTMLSlotElement }) {
152+
#onSlotChange = (event: Event & { target: HTMLSlotElement }) => {
146153
const slotName = event.target.name;
147-
this.initSlot(slotName);
154+
this.#initSlot(slotName);
148155
this.host.requestUpdate();
149-
}
156+
};
150157

151-
@bound private async onMutation(records: MutationRecord[]) {
158+
#onMutation = async (records: MutationRecord[]) => {
152159
const changed = [];
153160
for (const { addedNodes, removedNodes } of records) {
154161
for (const node of [...addedNodes, ...removedNodes]) {
155162
if (node instanceof HTMLElement && node.slot) {
156-
this.initSlot(node.slot);
163+
this.#initSlot(node.slot);
157164
changed.push(node.slot);
158165
}
159166
}
160167
}
161-
if (changed.length) {
162-
this.host.requestUpdate();
163-
}
164-
}
168+
this.host.requestUpdate();
169+
};
165170

166-
private getChildrenForSlot<T extends Element = Element>(name: string | typeof SlotController.anonymous): T[] {
171+
#getChildrenForSlot<T extends Element = Element>(name: string | typeof SlotController.anonymous): T[] {
167172
const children = Array.from(this.host.children) as T[];
168173
return children.filter(isSlot(name));
169174
}
170175

171-
@bound private initSlot(slotName: string | null) {
176+
#initSlot = (slotName: string | null) => {
172177
const name = slotName || SlotController.anonymous;
173-
const elements = this.nodes.get(name)?.slot?.assignedElements?.() ?? this.getChildrenForSlot(name);
178+
const elements = this.#nodes.get(name)?.slot?.assignedElements?.() ?? this.#getChildrenForSlot(name);
174179
const selector = slotName ? `slot[name="${slotName}"]` : 'slot:not([name])';
175180
const slot = this.host.shadowRoot?.querySelector?.<HTMLSlotElement>(selector) ?? null;
176181
const hasContent = !!elements.length;
177-
this.nodes.set(name, { elements, name: slotName ?? '', hasContent, slot });
178-
this.logger.log(slotName, hasContent);
179-
}
180-
181-
/**
182-
* Maps the defined slots into an object that is easier to query
183-
*/
184-
@bound private init() {
185-
this.nodes.clear();
186-
// Loop over the properties provided by the schema
187-
this.slotNames.forEach(this.initSlot);
188-
Object.values(this.deprecations).forEach(this.initSlot);
189-
this.host.requestUpdate();
190-
}
182+
this.#nodes.set(name, { elements, name: slotName ?? '', hasContent, slot });
183+
this.#logger.log(slotName, hasContent);
184+
};
191185
}

0 commit comments

Comments
 (0)