-
-
Notifications
You must be signed in to change notification settings - Fork 830
/
slot.ts
103 lines (83 loc) · 2.93 KB
/
slot.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import type { ReactiveController, ReactiveControllerHost } from 'lit';
/** A reactive controller that determines when slots exist. */
export class HasSlotController implements ReactiveController {
host: ReactiveControllerHost & Element;
slotNames: string[] = [];
constructor(host: ReactiveControllerHost & Element, ...slotNames: string[]) {
(this.host = host).addController(this);
this.slotNames = slotNames;
this.handleSlotChange = this.handleSlotChange.bind(this);
}
private hasDefaultSlot() {
return [...this.host.childNodes].some(node => {
if (node.nodeType === node.TEXT_NODE && node.textContent!.trim() !== '') {
return true;
}
if (node.nodeType === node.ELEMENT_NODE) {
const el = node as HTMLElement;
const tagName = el.tagName.toLowerCase();
// Ignore visually hidden elements since they aren't rendered
if (tagName === 'sl-visually-hidden') {
return false;
}
// If it doesn't have a slot attribute, it's part of the default slot
if (!el.hasAttribute('slot')) {
return true;
}
}
return false;
});
}
private hasNamedSlot(name: string) {
return this.host.querySelector(`:scope > [slot="${name}"]`) !== null;
}
test(slotName: string) {
return slotName === '[default]' ? this.hasDefaultSlot() : this.hasNamedSlot(slotName);
}
hostConnected() {
this.host.shadowRoot!.addEventListener('slotchange', this.handleSlotChange);
}
hostDisconnected() {
this.host.shadowRoot!.removeEventListener('slotchange', this.handleSlotChange);
}
handleSlotChange(event: Event) {
const slot = event.target as HTMLSlotElement;
if ((this.slotNames.includes('[default]') && !slot.name) || (slot.name && this.slotNames.includes(slot.name))) {
this.host.requestUpdate();
}
}
}
/**
* Given a slot, this function iterates over all of its assigned element and text nodes and returns the concatenated
* HTML as a string. This is useful because we can't use slot.innerHTML as an alternative.
*/
export function getInnerHTML(slot: HTMLSlotElement): string {
const nodes = slot.assignedNodes({ flatten: true });
let html = '';
[...nodes].forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
html += (node as HTMLElement).outerHTML;
}
if (node.nodeType === Node.TEXT_NODE) {
html += node.textContent;
}
});
return html;
}
/**
* Given a slot, this function iterates over all of its assigned text nodes and returns the concatenated text as a
* string. This is useful because we can't use slot.textContent as an alternative.
*/
export function getTextContent(slot: HTMLSlotElement | undefined | null): string {
if (!slot) {
return '';
}
const nodes = slot.assignedNodes({ flatten: true });
let text = '';
[...nodes].forEach(node => {
if (node.nodeType === Node.TEXT_NODE) {
text += node.textContent;
}
});
return text;
}