Skip to content

Commit

Permalink
feat(useMutationObserver): allow multiple targets (#3741)
Browse files Browse the repository at this point in the history
  • Loading branch information
chirokas committed Feb 20, 2024
1 parent 15fc0fa commit 98fac39
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 9 deletions.
31 changes: 31 additions & 0 deletions packages/core/useMutationObserver/index.test.ts
@@ -1,5 +1,6 @@
import { promiseTimeout } from '@vueuse/shared'
import { describe, expect, it, vi } from 'vitest'
import { computed, ref } from 'vue-demi'
import { useMutationObserver } from '.'

describe('useMutationObserver', () => {
Expand Down Expand Up @@ -187,4 +188,34 @@ describe('useMutationObserver', () => {
expect(records![0].target).toBe(target)
expect(cb).toHaveBeenCalledTimes(1)
})

it('should work with multiple targets', async () => {
const headerElement = ref<HTMLDivElement | null>(
document.createElement('div'),
)
const footerElement = ref<HTMLDivElement | null>(
document.createElement('div'),
)
const targets = computed(() => [headerElement.value, footerElement.value])
const cb = vi.fn()

const { takeRecords } = useMutationObserver(targets, cb, {
attributes: true,
})

headerElement.value?.setAttribute('id', 'header')
footerElement.value?.setAttribute('id', 'footer')
let records = takeRecords()
await promiseTimeout(10)
expect(records).toHaveLength(2)
expect(records![0].target).toBe(headerElement.value)
expect(records![1].target).toBe(footerElement.value)

headerElement.value = null
footerElement.value?.removeAttribute('id')
records = takeRecords()
await promiseTimeout(10)
expect(records).toHaveLength(1)
expect(records![0].target).toBe(footerElement.value)
})
})
27 changes: 18 additions & 9 deletions packages/core/useMutationObserver/index.ts
@@ -1,6 +1,7 @@
import { tryOnScopeDispose } from '@vueuse/shared'
import { watch } from 'vue-demi'
import type { MaybeComputedElementRef } from '../unrefElement'
import type { MaybeRefOrGetter } from '@vueuse/shared'
import { notNullish, toValue, tryOnScopeDispose } from '@vueuse/shared'
import { computed, watch } from 'vue-demi'
import type { MaybeComputedElementRef, MaybeElement } from '../unrefElement'
import { unrefElement } from '../unrefElement'
import { useSupported } from '../useSupported'
import type { ConfigurableWindow } from '../_configurable'
Expand All @@ -18,7 +19,7 @@ export interface UseMutationObserverOptions extends MutationObserverInit, Config
* @param options
*/
export function useMutationObserver(
target: MaybeComputedElementRef,
target: MaybeComputedElementRef | MaybeComputedElementRef[] | MaybeRefOrGetter<MaybeElement[]>,
callback: MutationCallback,
options: UseMutationObserverOptions = {},
) {
Expand All @@ -33,17 +34,25 @@ export function useMutationObserver(
}
}

const targets = computed(() => {
const value = toValue(target)
const items = (Array.isArray(value) ? value : [value])
.map(unrefElement)
.filter(notNullish)
return new Set(items)
})

const stopWatch = watch(
() => unrefElement(target),
(el) => {
() => targets.value,
(targets) => {
cleanup()

if (isSupported.value && window && el) {
if (isSupported.value && window && targets.size) {
observer = new MutationObserver(callback)
observer!.observe(el, mutationOptions)
targets.forEach(el => observer!.observe(el, mutationOptions))
}
},
{ immediate: true },
{ immediate: true, flush: 'post' },
)

const takeRecords = () => {
Expand Down

0 comments on commit 98fac39

Please sign in to comment.