File size: 2,663 Bytes
3e05655 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | import { observeElementOffset, type Virtualizer } from "@tanstack/solid-virtual"
export function observeElementOffsetReconnectAware<TScrollElement extends Element, TItemElement extends Element>(
instance: Virtualizer<TScrollElement, TItemElement>,
callback: (offset: number, isScrolling: boolean) => void,
) {
let active = true
const deliver = (offset: number, isScrolling: boolean) => {
if (!active) return
callback(offset, isScrolling)
}
const cleanupOffset = observeElementOffset(instance, deliver)
const element = instance.scrollElement
const targetWindow = instance.targetWindow
const root = element?.closest("main") ?? element?.ownerDocument.body
if (!element || !targetWindow || !root)
return () => {
active = false
cleanupOffset?.()
}
let removed = false
let frame: number | undefined
const clearCheck = () => {
if (frame === undefined) return
targetWindow.cancelAnimationFrame(frame)
frame = undefined
}
const startCheck = () => {
clearCheck()
const deadline = targetWindow.performance.now() + instance.options.isScrollingResetDelay
let framesAfterDeadline = 0
const check = (time: number) => {
frame = undefined
if (element.isConnected) {
const offset = instance.options.horizontal
? element.scrollLeft * (instance.options.isRtl ? -1 : 1)
: element.scrollTop
if (instance.scrollOffset === null || Math.abs(offset - instance.scrollOffset) > 1) deliver(offset, false)
}
if (time >= deadline) framesAfterDeadline += 1
if (framesAfterDeadline >= 2) return
frame = targetWindow.requestAnimationFrame(check)
}
frame = targetWindow.requestAnimationFrame(check)
}
const observer = new targetWindow.MutationObserver((records) => {
if (!active) return
records.forEach((record) => {
if (record.target === element || element.contains(record.target)) return
if (mutationNodesContainElement(record.removedNodes, element)) {
removed = true
clearCheck()
}
if (!removed || !element.isConnected || !mutationNodesContainElement(record.addedNodes, element)) return
removed = false
startCheck()
})
})
// Session routes are replaced below persistent main; body is the fallback for isolated hosts.
observer.observe(root, { childList: true, subtree: true })
return () => {
active = false
observer.disconnect()
clearCheck()
cleanupOffset?.()
}
}
export function mutationNodesContainElement(nodes: Iterable<Node>, element: Element) {
return [...nodes].some((node) => node === element || node.contains(element))
}
|