Mirror: React hooks for accessible, common web interactions. UI super powers without the UI.
1const mutationObservers: Map<HTMLElement, MutationObserver> = new Map(); 2const resizeListeners: Map<HTMLElement, Array<() => void>> = new Map(); 3 4const resizeObserver = new ResizeObserver(entries => { 5 const parents = new Set<Element>(); 6 for (let i = 0; i < entries.length; i++) { 7 const parent = entries[i].target.parentElement; 8 if (parent && !parents.has(parent)) { 9 parents.add(parent); 10 const listeners = resizeListeners.get(parent) || []; 11 for (let i = 0; i < listeners.length; i++) listeners[i](); 12 } 13 } 14}); 15 16export function observeScrollHeight( 17 element: HTMLElement, 18 onScrollHeightChange: (scrollHeight: number) => void 19): () => void { 20 const listeners = resizeListeners.get(element) || []; 21 const isFirstListener = !listeners.length; 22 resizeListeners.set(element, listeners); 23 24 let previousScrollHeight: null | number = null; 25 let hasUnmounted = false; 26 const onResize = () => { 27 const scrollHeight = element.scrollHeight || 0; 28 if (!hasUnmounted && scrollHeight !== previousScrollHeight) { 29 onScrollHeightChange(element.scrollHeight); 30 previousScrollHeight = scrollHeight; 31 } 32 }; 33 34 listeners.push(onResize); 35 36 if (isFirstListener) { 37 const mutationObserver = new MutationObserver(entries => { 38 for (let i = 0; i < entries.length; i++) { 39 const entry = entries[i]; 40 for (let j = 0; j < entry.addedNodes.length; j++) { 41 const node = entry.addedNodes[j]; 42 if (node.nodeType === Node.ELEMENT_NODE) { 43 resizeObserver.observe(node as Element); 44 } 45 } 46 47 for (let j = 0; j < entry.removedNodes.length; j++) { 48 const node = entry.removedNodes[j]; 49 if (node.nodeType === Node.ELEMENT_NODE) { 50 resizeObserver.unobserve(node as Element); 51 } 52 } 53 } 54 }); 55 56 const childNodes = element.childNodes; 57 for (let i = 0; i < childNodes.length; i++) 58 if (childNodes[i].nodeType === Node.ELEMENT_NODE) 59 resizeObserver.observe(childNodes[i] as Element); 60 61 mutationObserver.observe(element, { childList: true }); 62 mutationObservers.set(element, mutationObserver); 63 } 64 65 requestAnimationFrame(onResize); 66 67 return () => { 68 const listeners = resizeListeners.get(element) || []; 69 listeners.splice(listeners.indexOf(onResize), 1); 70 hasUnmounted = true; 71 72 if (!listeners.length) { 73 const mutationObserver = mutationObservers.get(element); 74 if (mutationObserver) mutationObserver.disconnect(); 75 76 const childNodes = element.childNodes; 77 for (let i = 0; i < childNodes.length; i++) 78 if (childNodes[i].nodeType === Node.ELEMENT_NODE) 79 resizeObserver.unobserve(childNodes[i] as Element); 80 81 resizeListeners.delete(element); 82 mutationObservers.delete(element); 83 } 84 }; 85}