11import { useEffect , useRef , useState } from 'react' ;
22
3- /**
4- * Used to detect when a Clerk component has been added to the DOM.
5- */
6- function waitForElementChildren ( options : { selector ?: string ; root ?: HTMLElement | null ; timeout ?: number } ) {
7- const { root = document ?. body , selector, timeout = 0 } = options ;
3+ const createAwaitableMutationObserver = (
4+ globalOptions : MutationObserverInit & {
5+ isReady : ( el : HTMLElement | null , selector : string ) => boolean ;
6+ } ,
7+ ) => {
8+ const isReady = globalOptions ?. isReady ;
89
9- return new Promise < void > ( ( resolve , reject ) => {
10- if ( ! root ) {
11- reject ( new Error ( 'No root element provided' ) ) ;
12- return ;
13- }
10+ return ( options : { selector : string ; root ?: HTMLElement | null ; timeout ?: number } ) =>
11+ new Promise < void > ( ( resolve , reject ) => {
12+ const { root = document ?. body , selector, timeout = 0 } = options ;
1413
15- let elementToWatch : HTMLElement | null = root ;
16- if ( selector ) {
17- elementToWatch = root ?. querySelector ( selector ) ;
18- }
14+ if ( ! root ) {
15+ reject ( new Error ( 'No root element provided' ) ) ;
16+ return ;
17+ }
1918
20- // Check if the element already has child nodes
21- const isElementAlreadyPresent = elementToWatch ?. childElementCount && elementToWatch . childElementCount > 0 ;
22- if ( isElementAlreadyPresent ) {
23- resolve ( ) ;
24- return ;
25- }
19+ let elementToWatch : HTMLElement | null = root ;
20+ if ( selector ) {
21+ elementToWatch = root ?. querySelector ( selector ) ;
22+ }
2623
27- // Set up a MutationObserver to detect when the element has children
28- const observer = new MutationObserver ( mutationsList => {
29- for ( const mutation of mutationsList ) {
30- if ( mutation . type === 'childList' ) {
24+ // Initial readiness check
25+ if ( isReady ( elementToWatch , selector ) ) {
26+ resolve ( ) ;
27+ return ;
28+ }
29+
30+ // Set up a MutationObserver to detect when the element has children
31+ const observer = new MutationObserver ( mutationsList => {
32+ for ( const mutation of mutationsList ) {
3133 if ( ! elementToWatch && selector ) {
3234 elementToWatch = root ?. querySelector ( selector ) ;
3335 }
3436
35- if ( elementToWatch ?. childElementCount && elementToWatch . childElementCount > 0 ) {
36- observer . disconnect ( ) ;
37- resolve ( ) ;
38- return ;
37+ if (
38+ ( globalOptions . childList && mutation . type === 'childList' ) ||
39+ ( globalOptions . attributes && mutation . type === 'attributes' )
40+ ) {
41+ if ( isReady ( elementToWatch , selector ) ) {
42+ observer . disconnect ( ) ;
43+ resolve ( ) ;
44+ return ;
45+ }
3946 }
4047 }
48+ } ) ;
49+
50+ observer . observe ( root , globalOptions ) ;
51+
52+ // Set up an optional timeout to reject the promise if the element never gets child nodes
53+ if ( timeout > 0 ) {
54+ setTimeout ( ( ) => {
55+ observer . disconnect ( ) ;
56+ reject ( new Error ( `Timeout waiting for ${ selector } ` ) ) ;
57+ } , timeout ) ;
4158 }
4259 } ) ;
60+ } ;
4361
44- observer . observe ( root , { childList : true , subtree : true } ) ;
45-
46- // Set up an optional timeout to reject the promise if the element never gets child nodes
47- if ( timeout > 0 ) {
48- setTimeout ( ( ) => {
49- observer . disconnect ( ) ;
50- reject ( new Error ( `Timeout waiting for element children` ) ) ;
51- } , timeout ) ;
52- }
53- } ) ;
54- }
62+ const waitForElementChildren = createAwaitableMutationObserver ( {
63+ childList : true ,
64+ subtree : true ,
65+ isReady : ( el , selector ) => ! ! el ?. childElementCount && el ?. matches ?.( selector ) && el . childElementCount > 0 ,
66+ } ) ;
5567
5668/**
5769 * Detect when a Clerk component has mounted by watching DOM updates to an element with a `data-clerk-component="${component}"` property.
5870 */
59- export function useWaitForComponentMount ( component ?: string ) {
71+ export function useWaitForComponentMount (
72+ component ?: string ,
73+ options ?: { selector : string } ,
74+ ) : 'rendering' | 'rendered' | 'error' {
6075 const watcherRef = useRef < Promise < void > > ( ) ;
6176 const [ status , setStatus ] = useState < 'rendering' | 'rendered' | 'error' > ( 'rendering' ) ;
6277
@@ -66,15 +81,22 @@ export function useWaitForComponentMount(component?: string) {
6681 }
6782
6883 if ( typeof window !== 'undefined' && ! watcherRef . current ) {
69- watcherRef . current = waitForElementChildren ( { selector : `[data-clerk-component="${ component } "]` } )
84+ const defaultSelector = `[data-clerk-component="${ component } "]` ;
85+ const selector = options ?. selector ;
86+ watcherRef . current = waitForElementChildren ( {
87+ selector : selector
88+ ? // Allows for `[data-clerk-component="xxxx"][data-some-attribute="123"] .my-class`
89+ defaultSelector + selector
90+ : defaultSelector ,
91+ } )
7092 . then ( ( ) => {
7193 setStatus ( 'rendered' ) ;
7294 } )
7395 . catch ( ( ) => {
7496 setStatus ( 'error' ) ;
7597 } ) ;
7698 }
77- } , [ component ] ) ;
99+ } , [ component , options ?. selector ] ) ;
78100
79101 return status ;
80102}
0 commit comments