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 });
 | 
			
		||||
	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());
 | 
			
		||||
	on(full_name, console.log);
 | 
			
		||||
	
 | 
			
		||||
	return el("div", { className }).append(
 | 
			
		||||
	return el("div", { className }, on.connected(console.log)).append(
 | 
			
		||||
		el("p").append(
 | 
			
		||||
			el("#text", { textContent: "Hello " }),
 | 
			
		||||
			el("strong", { textContent: full_name }),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user