Last active
May 8, 2025 08:22
-
-
Save asleepace/278d261e765f6fb127d673c1b13db535 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type TimeoutID = ReturnType<typeof setTimeout> | |
export type HTMLSelector<T> = ( | |
mutations: MutationRecord[] | |
) => T | undefined | null | |
export interface HTMLSelectorConfig extends MutationObserverInit { | |
timeout?: number | |
} | |
const DEFAULT_OPTIONS: HTMLSelectorConfig = { | |
timeout: 5_000, | |
attributes: false, | |
childList: true, | |
subtree: true, | |
} | |
/** | |
* ## watchForSelector(parent, selector, config?) | |
* | |
* Observe parent element for mutations and run onMutations selector | |
* each time a mutation is observed. If the selector returns a non-nullish | |
* value, then this function will resolve with the result. | |
* | |
* ```ts | |
* const item = await watch(this.container, (mutations) => { | |
* | |
* return mutations.some((elem) => elem.id === 'item') | |
* | |
* }, { timeout: 1000, childList: true }) | |
* ``` | |
* | |
*/ | |
export async function watch<T>( | |
parent: HTMLElement, | |
select: HTMLSelector<T>, | |
config: HTMLSelectorConfig = DEFAULT_OPTIONS | |
): Promise<T | null> { | |
let { resolve, promise } = Promise.withResolvers<T | null>() | |
let timeoutId: TimeoutID | undefined = undefined | |
// run once before any setup | |
let element = select([]) | |
if (element) return element | |
let unsubscribe: ((element?: T | null) => void) | undefined | |
let observer = new MutationObserver((mutations, self) => { | |
element = select(mutations) | |
if (!element) return | |
unsubscribe?.(element) | |
self.disconnect() | |
}) | |
// register observer with options | |
const { timeout, ...options } = { ...DEFAULT_OPTIONS, ...config } | |
observer.observe(parent, options) | |
// initialize unsubscribe callback | |
unsubscribe = () => { | |
unsubscribe = undefined | |
observer.disconnect() | |
clearTimeout(timeoutId) | |
resolve(element || select([]) || null) | |
} | |
// register a max timeout if present | |
if (timeoutId) { | |
timeoutId = setTimeout(unsubscribe, timeout) | |
} | |
// return promise for element | |
return promise | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment