Skip to content

Instantly share code, notes, and snippets.

@asleepace
Last active May 8, 2025 08:22
Show Gist options
  • Save asleepace/278d261e765f6fb127d673c1b13db535 to your computer and use it in GitHub Desktop.
Save asleepace/278d261e765f6fb127d673c1b13db535 to your computer and use it in GitHub Desktop.
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