Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 67 additions & 29 deletions packages/vuetify/src/directives/intersect/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Utilities
import { nextTick } from 'vue'
import { SUPPORTS_INTERSECTION } from '@/util'

// Types
Expand All @@ -20,6 +21,32 @@ export interface ObserveDirectiveBinding extends Omit<DirectiveBinding, 'modifie
}
}

// Elements with display:contents have no layout box; IntersectionObserver cannot observe them.
// Walk down to the first child that has a real box.
function getObservableEl (el: HTMLElement): HTMLElement {
if (getComputedStyle(el).display === 'contents') {
for (let i = 0; i < el.children.length; i++) {
const child = el.children[i]
if (child instanceof HTMLElement) return getObservableEl(child)
}
}
return el
}

const scrollableRE = /auto|scroll/

function getScrollableAncestor (el: HTMLElement): HTMLElement | null {
let parent = el.parentElement
while (parent) {
const { overflow, overflowY, overflowX } = getComputedStyle(parent)
if (scrollableRE.test(overflow + overflowY + overflowX)) {
return parent
}
parent = parent.parentElement
}
return null
}

function mounted (el: HTMLElement, binding: ObserveDirectiveBinding) {
if (!SUPPORTS_INTERSECTION) return

Expand All @@ -29,45 +56,56 @@ function mounted (el: HTMLElement, binding: ObserveDirectiveBinding) {
? value
: { handler: value, options: {} }

const observer = new IntersectionObserver((
entries: IntersectionObserverEntry[] = [],
observer: IntersectionObserver
) => {
const _observe = el._observe?.[binding.instance!.$.uid]
if (!_observe) return // Just in case, should never fire

const isIntersecting = entries.some(entry => entry.isIntersecting)

// If is not quiet or has already been
// initted, invoke the user callback
if (
handler && (
!modifiers.quiet ||
_observe.init
) && (
!modifiers.once ||
isIntersecting ||
_observe.init
)
) {
handler(isIntersecting, entries, observer)
nextTick(() => {
const resolvedOptions: IntersectionObserverInit = {
...options,
root: options?.root !== undefined
? options.root
: getScrollableAncestor(el),
}

if (isIntersecting && modifiers.once) unmounted(el, binding)
else _observe.init = true
}, options)
const observer = new IntersectionObserver((
entries: IntersectionObserverEntry[] = [],
observer: IntersectionObserver
) => {
const _observe = el._observe?.[binding.instance!.$.uid]
if (!_observe) return // Just in case, should never fire

const isIntersecting = entries.some(entry => entry.isIntersecting)

// If is not quiet or has already been
// initted, invoke the user callback
if (
handler && (
!modifiers.quiet ||
_observe.init
) && (
!modifiers.once ||
isIntersecting ||
_observe.init
)
) {
handler(isIntersecting, entries, observer)
}

if (isIntersecting && modifiers.once) unmounted(el, binding)
else _observe.init = true
}, resolvedOptions)

el._observe = Object(el._observe)

el._observe = Object(el._observe)
el._observe![binding.instance!.$.uid] = { init: false, observer }
const target = getObservableEl(el)
el._observe![binding.instance!.$.uid] = { init: false, observer, target }

observer.observe(el)
observer.observe(target)
})
}

function unmounted (el: HTMLElement, binding: ObserveDirectiveBinding) {
const observe = el._observe?.[binding.instance!.$.uid]
if (!observe) return

observe.observer.unobserve(el)
observe.observer.unobserve(observe.target ?? el)
delete el._observe![binding.instance!.$.uid]
}

Expand Down
1 change: 1 addition & 0 deletions packages/vuetify/src/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ declare global {
_observe?: Record<number, {
init: boolean
observer: IntersectionObserver
target?: HTMLElement
} | undefined>
_mutate?: Record<number, {
observer: MutationObserver
Expand Down
Loading