Skip to content
Closed
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
47 changes: 40 additions & 7 deletions packages/vuetify/src/directives/intersect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,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 && parent !== document.documentElement && parent !== document.body) {
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,17 +55,22 @@ function mounted (el: HTMLElement, binding: ObserveDirectiveBinding) {
? value
: { handler: value, options: {} }

const resolvedOptions: IntersectionObserverInit = {
...options,
root: options?.root !== undefined
? options.root
: getScrollableAncestor(el),
}

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

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

// If is not quiet or has already been
// initted, invoke the user callback
if (
handler && (
!modifiers.quiet ||
Expand All @@ -55,19 +86,21 @@ function mounted (el: HTMLElement, binding: ObserveDirectiveBinding) {

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

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

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

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