From d622a61fd147447984fc9bd767fb2a698cecffdf Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 9 Sep 2026 18:57:15 +0300 Subject: [PATCH] more scrolling shenanigans --- src/components/conversation/conversation.js | 24 ++++++++++----------- src/composables/useScrollPosition.js | 9 +++++++- src/composables/useVirtualScrolling.js | 13 ++++++++--- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index e8e52c7d1..4aacd6c96 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -26,6 +26,7 @@ import { useStreamingStore } from 'src/stores/streaming.js' import { useConversation } from 'src/composables/useConversation.js' import { useTreeConversationTopology } from 'src/composables/useTreeConversationTopology.js' import { useVirtualScrolling } from 'src/composables/useVirtualScrolling.js' +import { useScrollPosition } from 'src/composables/useScrollPosition.js' import { WSConnectionStatus } from 'src/api/websocket.js' @@ -90,12 +91,13 @@ export default { setFocused(id) const target = document.querySelector(`.Status[data-status-id=${id}]`) await nextTick() - target.scrollIntoView({ behavior: 'smooth', block: 'center' }) + return await target.scrollIntoView({ behavior: 'smooth', block: 'center' }) } const { statusId } = toRefs(props) const router = useRouter() + const scroller = useScrollPosition() // # Main Configuration / global state const { mergedConfig } = storeToRefs(useMergedConfigStore()) @@ -146,10 +148,10 @@ export default { loadError, } = useConversation(focusedId, isExpanded) - watch(expanded, (value) => { - tryScrollTo(currentStatus.value.id) + watch(expanded, async (value) => { if (value) { - fetchConversation() + await fetchConversation() + await tryScrollTo(currentStatus.value.id) } else { resetDisplayState() } @@ -196,7 +198,7 @@ export default { heightChart: heightChartLinear, changeSuspendState: changeSuspendStateLinear, updateVirtualHeight: updateVirtualHeightLinear, - } = useVirtualScrolling(conversation, linearElement, currentStatus) + } = useVirtualScrolling(conversation, linearElement, scroller, true, currentStatus) // # Tree style stuff const isTreeView = computed(() => displayStyle.value === 'tree') @@ -214,7 +216,7 @@ export default { heightChart: heightChartAncestors, changeSuspendState: changeSuspendStateAncestors, updateVirtualHeight: updateVirtualHeightAncestors, - } = useVirtualScrolling(currentAncestors, ancestorsElement) + } = useVirtualScrolling(currentAncestors, ancestorsElement, scroller, true) const currentLevel = computed(() => [currentStatus.value].filter(Boolean)) const currentLevelElement = useTemplateRef('currentLevel') @@ -223,7 +225,7 @@ export default { totalHeight: totalHeightCurrentLevel, changeSuspendState: changeSuspendStateCurrentLevel, updateVirtualHeight: updateVirtualHeightCurrentLevel, - } = useVirtualScrolling(currentLevel, currentLevelElement, currentStatus) + } = useVirtualScrolling(currentLevel, currentLevelElement, scroller, false) const treeViewIsSimple = computed( () => !mergedConfig.value.conversationTreeAdvanced, @@ -239,12 +241,8 @@ export default { ) // # Scrolling - const diveIntoStatus = (id) => { - tryScrollTo(id) - } - const diveToTopLevel = () => { - tryScrollTo(currentAncestors.value[0].id) - } + const diveIntoStatus = (id) => tryScrollTo(id) + const diveToTopLevel = () => tryScrollTo(currentAncestors.value[0].id) return { // # Misc diff --git a/src/composables/useScrollPosition.js b/src/composables/useScrollPosition.js index 7772a35a4..cf3ee6751 100644 --- a/src/composables/useScrollPosition.js +++ b/src/composables/useScrollPosition.js @@ -3,6 +3,7 @@ import { onMounted, onUnmounted, ref } from 'vue' export function useScrollPosition() { const x = ref(0) const y = ref(0) + const inProgress = ref(false) const update = (e) => { x.value = window.scrollX @@ -17,5 +18,11 @@ export function useScrollPosition() { window.removeEventListener('scroll', update) }) - return { x, y } + const scrollBy = async (x1, y1, options) => { + inProgress.value = true + await window.scrollBy(x1, y1, options) + inProgress.value = false + } + + return { x, y, scrollBy, inProgress } } diff --git a/src/composables/useVirtualScrolling.js b/src/composables/useVirtualScrolling.js index 22ec6a24b..d66d0d91d 100644 --- a/src/composables/useVirtualScrolling.js +++ b/src/composables/useVirtualScrolling.js @@ -4,10 +4,15 @@ import { computed, ref, watch, nextTick } from 'vue' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useStatusesStore } from 'src/stores/statuses.js' -import { useScrollPosition } from 'src/composables/useScrollPosition.js' import { useWindowSize } from 'src/composables/useWindowSize.js' -export function useVirtualScrolling(conversation, body, anchorStatus) { +export function useVirtualScrolling( + conversation, + body, + scrollPosition, + scrollCompensation, + anchorStatus, +) { const getStatusObject = (id) => useStatusesStore().allStatuses.get(id) const { mergedConfig } = storeToRefs(useMergedConfigStore()) @@ -64,7 +69,7 @@ export function useVirtualScrolling(conversation, body, anchorStatus) { } // Scrolling - const { y: scrollY } = useScrollPosition() + const { y: scrollY, inProgress: scrollInProgress } = scrollPosition const { height: windowHeight } = useWindowSize() const topScrollBoundary = ref(0) @@ -125,6 +130,8 @@ export function useVirtualScrolling(conversation, body, anchorStatus) { }) watch(heightChart, async (newVal, oldVal) => { + if (scrollInProgress) return + if (!scrollCompensation) return pauseWatchers() const getAnchoredEl = (list) => anchor.value ? list.find(({ id }) => id === anchor.value)