-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathslot-all.html
159 lines (154 loc) · 7.75 KB
/
slot-all.html
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<!-- slot-all @version: 0.1.0 @license: MIT -->
<script>
(function(){
/**
* A custom element to distribute all elements
* regardless of their slot name
*
* ```html
* <div>
* #shadow-root
* <slot-all></slot-all>
* <div slot="foo">Named slot</div>
* </div>
* ```
*/
customElements.define('slot-all', class SlotAll extends HTMLElement{
/**
* Initialize host observer and slots map.
*/
constructor(){
super();
const slotAll = this;
// host observer instance
function mutationCallback(mutationsList){
for(var mutation of mutationsList) {
mutation.addedNodes.length && slotAll._createSlots(mutation.addedNodes);
mutation.removedNodes.length && slotAll._removeSlots(mutation.removedNodes);
}
}
// ShadyDOM breaks mutation observer, so we need to use its non-standard API
// workaround https://github.com/webcomponents/shadydom/issues/260
if(window.ShadyDOM && ShadyDOM.observeChildren){
this.__shadyMutationCallback = mutationCallback;
} else {
this._hostObserver = new MutationObserver(mutationCallback);
}
// map of created named slots
this.__createdSlots = new Map();
}
/**
* Observe adding and removing nodes to the shadow host,
* to create or remove named slots.
*/
connectedCallback(){
// force it to be non-displayed element
this.style.display = 'none';
// Do nothing if elements is already disconnected
// https://github.com/whatwg/html/pull/4127/files#diff-36cd38f49b9afa08222c0dc9ebfe35ebR67065
if(!this.isConnected){
return ;
}
const host = this.getRootNode().host;
// Start observing the shadow host for child list changes
// workaround https://github.com/webcomponents/shadydom/issues/260
if(window.ShadyDOM && this.__shadyMutationCallback){
this.__shadyObserver = ShadyDOM.observeChildren(host, this.__shadyMutationCallback);
} else {
this._hostObserver.observe(host, { childList: true });
}
// Create slots for initial list of children
this._createSlots(Array.from(host.children));
}
/**
* Disconnect host observer, remove created slots, clear the slots map.
*/
disconnectedCallback(){
// unobserve host changes
this._hostObserver && this._hostObserver.disconnect();
// unobserve shady dom host changes
// workaround https://github.com/webcomponents/shadydom/issues/260
this.__shadyObserver && ShadyDOM.unobserveChildren(this.__shadyObserver);
for(let [name, slot] of this.__createdSlots){
// workaround https://github.com/webcomponents/shadydom/issues/262
// WC polyfill does not support .remove()
// slot.remove();
slot.parentNode && slot.parentNode.removeChild(slot);
}
this.__createdSlots.clear();
}
/**
* Create slots for given elements, if needed:
* - element has a slot name,
* - names slot was not yet created by this `slot-all``
* @param {NodeList|HTMLCollection} nodeList list of host's children or document fragments with children
*/
_createSlots(nodeList){
// Ensure shadow dom distribution is completed in polyfilled browsers
// window.ShadyDOM && window.ShadyDOM.flush();
// Trick Polymer to render this shadow tree to make `assignedSlot` of element accessible.
// it's cheaper than the code above, as flushes only this tree
// https://github.com/webcomponents/shadydom/issues/295
window.ShadyDOM && window.ShadyDOM.inUse && this.assignedSlot;
const slotAll = this;
const slotsMap = slotAll.__createdSlots;
nodeList.forEach(function createSlot(element){
// Polyfilled mutation observer sometimes gives DocumentFragments as addedNode
if(element.nodeType === Node.DOCUMENT_FRAGMENT_NODE){
Array.from(element.children).forEach(createSlot);
return;
}
const name = element.slot;
if(
element.nodeType === Node.ELEMENT_NODE &&
element.hasAttribute('slot') &&
!slotsMap.has(name) &&
// do not distribute already distributed elements,
// once the other slot is removed this element will not add it
!element.assignedSlot
){
const namedSlot = document.createElement('slot');
namedSlot.setAttribute('name', name);
namedSlot.setAttribute('all-slotted', '');
// add to the local map, to be able to clean it later
slotsMap.set(name, namedSlot);
// following is not supported by polyfill
// slotAll.insertAdjacentElement('beforebegin', namedSlot);
// workaround https://github.com/webcomponents/shadydom/issues/263
slotAll.parentNode.insertBefore(namedSlot, slotAll);
}
});
}
/**
* Remove slots for given elements, if needed:
* - there are no more host's children with a slot name for the given slot name
* @param {NodeList|HTMLCollection} nodeList list of removed host's children
*/
_removeSlots(nodeList){
const slotAll = this;
let __createdSlots = this.__createdSlots;
nodeList.forEach((element)=>{
if(element.nodeType === Node.ELEMENT_NODE){
// element.assignedSlot no longer works,
// we need to find the formerly assigned slot element manually
const slotName = element.getAttribute('slot');
// if host still has the elements with the same slot name,
// we should keep the slot
const slot = __createdSlots.get(slotName);
// Polyfilled browsers do not have assignedElements
// this time assignedNodes is somewhat safe to use
// as we assume to support only names slots => elements
// if(slot && slot.assignedElements().length == 0){
if(slot && slot.assignedNodes().length == 0){
// workaround https://github.com/webcomponents/shadydom/issues/262
// WC polyfill does not support .remove()
// slot.remove();
slot.parentNode.removeChild(slot);
__createdSlots.delete(slot);
}
}
});
}
});
})();
</script>