import type { MaybeRefOrGetter, TemplateRef } from 'vue' import { useEventListener, useMutationObserver } from '@vueuse/core' import { computed, reactive, toValue, watch } from 'vue' interface ChatScrollOptions { startOffset?: number endOffset?: number } export function useChatScroll(target: TemplateRef, options?: MaybeRefOrGetter) { const el = computed(() => toValue(target)) const arrivedState = reactive({ start: true, end: false, }) let lastScrollHeight = 0 let lastScrollTop = 0 watch(el, (el) => { if (!el) return scrollToStart(true) getArrivedState() }, { flush: 'post' }) useMutationObserver(el, (mutations) => { if (arrivedState.start) { scrollToStart(true) } else { // const prepended = mutations.some(mutation => Array.from(mutation.addedNodes.values()).some(node => !!node.nextEle)) // console.log('prepended', prepended) // for (const mutation of mutations) { // for (const node of mutation.addedNodes) { // console.log('added node', node, !!node.nextSibling) // if (node.nextSibling) { // // добавлено сверху/в середину // // el.value!.scrollTop += (node as HTMLElement).offsetHeight // } // } // // // for (const node of mutation.removedNodes) { // // // previousSibling/nextSibling здесь — это соседи на момент удаления // // if (mutation.previousSibling || mutation.nextSibling) { // // // удалено сверху/в середину — уменьшаем scrollTop // // el.value!.scrollTop -= (node as HTMLElement).offsetHeight // // } // // } // } const heightDiff = el.value!.scrollHeight - lastScrollHeight // const scrollDiff = el.value!.scrollTop - lastScrollTop // // console.log('height', 'old', lastScrollHeight, 'new', el.value!.scrollHeight, 'diff', heightDiff) // console.log('scroll', 'old', lastScrollTop, 'new', el.value!.scrollTop, 'diff', scrollDiff) // el.value!.scrollTop += heightDiff } getArrivedState() }, { childList: true, subtree: true, }) useEventListener(el, 'scroll', () => { getArrivedState() lastScrollTop = el.value!.scrollTop }, { passive: true }) useEventListener(el, 'scrollend', () => { getArrivedState() }, { passive: true }) function getArrivedState() { if (!el.value) { arrivedState.start = true arrivedState.end = false return } const { startOffset = 0, endOffset = 0 } = toValue(options) ?? {} const offsetHeight = el.value.offsetHeight const scrollHeight = el.value.scrollHeight const scrollTop = Math.abs(el.value.scrollTop) arrivedState.start = scrollTop + offsetHeight >= scrollHeight - startOffset arrivedState.end = scrollTop <= endOffset lastScrollHeight = el.value!.scrollHeight } function scrollToStart(instant = false) { if (!el.value) return const offset = Math.ceil(el.value.scrollHeight - el.value.offsetHeight) el.value.scrollTo({ top: offset, behavior: instant ? 'instant' : 'smooth' }) } return { arrivedState, scrollToStart, } }