let observing = false; const OBSERVER = new MutationObserver( checkPendingSelectors ); export function elementReady( selector: string, callback: Callback ): void { try { const el = document.querySelector< HTMLElement >( selector ); if ( el ) { callback( el, setCss( selector ) ); return; } enqueueSelectorCallback( selector, callback ); } catch ( _ ) { // eslint-disable-next-line no-console console.log( `Element “${ selector }” not found.` ); } } // ======= // HELPERS // ======= type Callback = ( el: HTMLElement, setCss: ( style: string ) => void ) => unknown; const setCss = ( selector: string ) => ( css: string ): void => { const style = document.createElement( 'style' ); style.textContent = `${ selector } {\n${ trim( css ) }\n}`; document.head?.appendChild( style ); }; const trim = ( s = '' ) => s.replace( /^\s*/, '' ).replace( /\s*$/, '' ); const PENDING_SELECTORS = new Map< string, ReadonlyArray< Callback > >(); function enqueueSelectorCallback( selector: string, callback: Callback ) { const selectors = PENDING_SELECTORS.get( selector ) ?? []; PENDING_SELECTORS.set( selector, [ ...selectors, callback ] ); if ( ! observing ) { observing = true; OBSERVER.observe( document.body, { childList: true, subtree: true, } ); } } function checkPendingSelectors() { for ( const [ selector, callbacks ] of PENDING_SELECTORS ) { const el = document.querySelector< HTMLElement >( selector ); if ( el ) { callbacks.forEach( ( cb ) => cb( el, setCss( selector ) ) ); PENDING_SELECTORS.delete( selector ); } } if ( PENDING_SELECTORS.size === 0 ) { OBSERVER.disconnect(); observing = false; } }