From 948d644e053551cf809c9e5b9820ae9b4bfe0299 Mon Sep 17 00:00:00 2001 From: Luccas Correa Date: Fri, 13 Mar 2026 10:59:02 -0300 Subject: [PATCH 1/9] fix(comments): align floating sidebar comments to active text Replace viewport-based bubble scrolling with sidebar layer translation so the active floating comment aligns to the clicked document text. Also constrain the floating comments container to act as a clipped viewport for the translated sidebar content. --- packages/superdoc/src/SuperDoc.vue | 4 +++ .../CommentsLayer/FloatingComments.vue | 26 +++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/superdoc/src/SuperDoc.vue b/packages/superdoc/src/SuperDoc.vue index d7d74d4f21..8a218d3d2d 100644 --- a/packages/superdoc/src/SuperDoc.vue +++ b/packages/superdoc/src/SuperDoc.vue @@ -1169,11 +1169,15 @@ const getPDFViewer = () => { .right-sidebar { min-width: 320px; + height: 100%; } .floating-comments { min-width: 300px; width: 300px; + height: 100%; + overflow-y: hidden; + overflow-x: hidden; } .superdoc__layers { diff --git a/packages/superdoc/src/components/CommentsLayer/FloatingComments.vue b/packages/superdoc/src/components/CommentsLayer/FloatingComments.vue index 8fb9344545..052f91c18c 100644 --- a/packages/superdoc/src/components/CommentsLayer/FloatingComments.vue +++ b/packages/superdoc/src/components/CommentsLayer/FloatingComments.vue @@ -73,6 +73,7 @@ const { activeZoom } = storeToRefs(superdocStore); const floatingCommentsContainer = ref(null); const commentsRenderKey = ref(0); +const sidebarOffsetY = ref(0); // Resolve activeComment (which stores commentId) to the position key used by allPositions // (which prefers importedId). Without this, imported Word comments where importedId !== commentId @@ -316,8 +317,8 @@ watch(activeCommentKey, (newKey, oldKey) => { }); }); -// Scroll to the active comment ONLY when its anchor is off-screen. -// getBoundingClientRect() is viewport-relative (accounts for scroll + zoom). +// Align the active comment bubble with the same on-screen Y position as its +// document anchor by translating the inner sidebar layer. watch(activeComment, () => { if (scrollTimer) clearTimeout(scrollTimer); @@ -332,18 +333,15 @@ watch(activeComment, () => { scrollTimer = setTimeout(() => { const el = placeholderRefs.value[key]; if (!el) return; + const parentRect = props.parent?.getBoundingClientRect?.(); + if (!parentRect) return; - const rect = el.getBoundingClientRect(); - const margin = 80; - const availableHeight = window.innerHeight - 2 * margin; - const isVisible = - rect.height > availableHeight - ? rect.top >= margin - : rect.top >= margin && rect.bottom <= window.innerHeight - margin; + const anchorTop = getAnchorTop(comment); + if (typeof anchorTop !== 'number' || isNaN(anchorTop)) return; - if (!isVisible) { - el.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - } + const desiredTop = parentRect.top + anchorTop; + const currentTop = el.getBoundingClientRect().top; + sidebarOffsetY.value += desiredTop - currentTop; }, 400); }); }); @@ -458,7 +456,7 @@ onBeforeUnmount(() => {
-