mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-11-03 22:59:16 +01:00 
			
		
		
		
	✨ Add connected/disconnected observers
				
					
				
			This commit is contained in:
		
							
								
								
									
										118
									
								
								src/events.js
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								src/events.js
									
									
									
									
									
								
							@@ -8,3 +8,121 @@ export function dispatch(event, detail){
 | 
				
			|||||||
		event= typeof detail==="undefined" ? new Event(event) : new CustomEvent(event, { detail });
 | 
							event= typeof detail==="undefined" ? new Event(event) : new CustomEvent(event, { detail });
 | 
				
			||||||
	return element=> element.dispatchEvent(event);
 | 
						return element=> element.dispatchEvent(event);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const connections_changes= connectionsChangesObserverConstructor();
 | 
				
			||||||
 | 
					on.connected= function(listener){
 | 
				
			||||||
 | 
						return function registerElement(element){
 | 
				
			||||||
 | 
							connections_changes.onConnected(element, listener);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					on.disconnected= function(listener){
 | 
				
			||||||
 | 
						return function registerElement(element){
 | 
				
			||||||
 | 
							connections_changes.onDisconnected(element, listener);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function connectionsChangesObserverConstructor(){
 | 
				
			||||||
 | 
						const store= new Map();
 | 
				
			||||||
 | 
						let is_observing= false;
 | 
				
			||||||
 | 
						const observer= new MutationObserver(function(mutations){
 | 
				
			||||||
 | 
							for(const mutation of mutations){
 | 
				
			||||||
 | 
								if(mutation.type!=="childList") continue;
 | 
				
			||||||
 | 
								if(observerAdded(mutation.addedNodes)){
 | 
				
			||||||
 | 
									stop();
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if(observerRemoved(mutation.removedNodes))
 | 
				
			||||||
 | 
									stop();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						return {
 | 
				
			||||||
 | 
							onConnected(element, listener){
 | 
				
			||||||
 | 
								start();
 | 
				
			||||||
 | 
								const listeners= getElementStore(element);
 | 
				
			||||||
 | 
								listeners.connected.push(listener);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							offConnected(element, listener){
 | 
				
			||||||
 | 
								if(!store.has(element)) return;
 | 
				
			||||||
 | 
								const ls= store.get(element);
 | 
				
			||||||
 | 
								const l= ls.connected;
 | 
				
			||||||
 | 
								l.splice(l.indexOf(listener), 1);
 | 
				
			||||||
 | 
								cleanWhenOff(element, ls);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							onDisconnected(element, listener){
 | 
				
			||||||
 | 
								start();
 | 
				
			||||||
 | 
								const listeners= getElementStore(element);
 | 
				
			||||||
 | 
								listeners.disconnected.push(listener);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							offDisconnected(element, listener){
 | 
				
			||||||
 | 
								if(!store.has(element)) return;
 | 
				
			||||||
 | 
								const ls= store.get(element);
 | 
				
			||||||
 | 
								const l= ls.disconnected;
 | 
				
			||||||
 | 
								l.splice(l.indexOf(listener), 1);
 | 
				
			||||||
 | 
								cleanWhenOff(element, ls);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						function cleanWhenOff(element, ls){
 | 
				
			||||||
 | 
							if(ls.connected.length || ls.disconnect.length)
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							store.delete(element);
 | 
				
			||||||
 | 
							stop();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						function getElementStore(element){
 | 
				
			||||||
 | 
							if(store.has(element)) return store.get(element);
 | 
				
			||||||
 | 
							const out= { connected: [], disconnected: [] };
 | 
				
			||||||
 | 
							store.set(element, out);
 | 
				
			||||||
 | 
							return out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						function start(){
 | 
				
			||||||
 | 
							if(is_observing) return;
 | 
				
			||||||
 | 
							is_observing= true;
 | 
				
			||||||
 | 
							observer.observe(document.body, { childList: true, subtree: true });
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						function stop(){
 | 
				
			||||||
 | 
							if(!is_observing || store.size) return;
 | 
				
			||||||
 | 
							is_observing= false;
 | 
				
			||||||
 | 
							observer.disconnect();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						//TODO remount support?
 | 
				
			||||||
 | 
						function requestIdle(){ return new Promise(function(resolve){
 | 
				
			||||||
 | 
							(requestIdleCallback || requestAnimationFrame)(resolve);
 | 
				
			||||||
 | 
						}); }
 | 
				
			||||||
 | 
						async function collectChildren(element){
 | 
				
			||||||
 | 
							if(store.size > 30)//TODO limit?
 | 
				
			||||||
 | 
								await requestIdle();
 | 
				
			||||||
 | 
							const out= [];
 | 
				
			||||||
 | 
							if(!(element instanceof Node)) return out;
 | 
				
			||||||
 | 
							for(const el of store.keys()){
 | 
				
			||||||
 | 
								if(el===element || !(el instanceof Node)) continue;
 | 
				
			||||||
 | 
								if(element.contains(el))
 | 
				
			||||||
 | 
									out.push(el);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return out;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						function observerAdded(addedNodes){
 | 
				
			||||||
 | 
							for(const element of addedNodes){
 | 
				
			||||||
 | 
								collectChildren(element).then(observerAdded);
 | 
				
			||||||
 | 
								if(!store.has(element)) return false;
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								const ls= store.get(element);
 | 
				
			||||||
 | 
								ls.connected.forEach(listener=> listener(element));
 | 
				
			||||||
 | 
								ls.connected.length= 0;
 | 
				
			||||||
 | 
								if(!ls.disconnected.length) store.delete(element);
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						function observerRemoved(removedNodes){
 | 
				
			||||||
 | 
							for(const element of removedNodes){
 | 
				
			||||||
 | 
								collectChildren(element).then(observerRemoved);
 | 
				
			||||||
 | 
								if(!store.has(element)) return false;
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								const ls= store.get(element);
 | 
				
			||||||
 | 
								ls.disconnected.forEach(listener=> listener(element));
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								ls.connected.length= 0;
 | 
				
			||||||
 | 
								ls.disconnected.length= 0;
 | 
				
			||||||
 | 
								store.delete(element);
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@ function component({ name= "World", surname= "" }= {}){
 | 
				
			|||||||
	const full_name= S(()=> store.name()+" "+store.surname());
 | 
						const full_name= S(()=> store.name()+" "+store.surname());
 | 
				
			||||||
	on(full_name, console.log);
 | 
						on(full_name, console.log);
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	return el("div", { className }).append(
 | 
						return el("div", { className }, on.connected(console.log)).append(
 | 
				
			||||||
		el("p").append(
 | 
							el("p").append(
 | 
				
			||||||
			el("#text", { textContent: "Hello " }),
 | 
								el("#text", { textContent: "Hello " }),
 | 
				
			||||||
			el("strong", { textContent: full_name }),
 | 
								el("strong", { textContent: full_name }),
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user