From faa9c7bf469aa32ecd94e4ffa6fce80a299214d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 20 Jan 2026 18:43:53 +0100 Subject: [PATCH] Scroll element in to view using center and for all the scrollable ancestors Closes #14527 --- sao/src/common.js | 57 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/sao/src/common.js b/sao/src/common.js index 00af62576b..34412ebf55 100644 --- a/sao/src/common.js +++ b/sao/src/common.js @@ -79,16 +79,59 @@ }; Sao.common.scrollIntoViewIfNeeded = function(element) { - element = element[0]; - if (element) { - var rect = element.getBoundingClientRect(); - if (rect.bottom > window.innerHeight) { - element.scrollIntoView(false); + function isScrollable(el) { + let style = getComputedStyle(el); + return /(auto|scroll|overlay)/.test( + style.overflow + style.overflowY + style.overflowX + ); + } + + function getScrollableAncestors(el) { + let ancestors = []; + let parent = el.parentElement; + while (parent && parent !== document.body) { + if (isScrollable(parent)) { + ancestors.push(parent); + } + parent = parent.parentElement; } - if (rect.top < 0) { - element.scrollIntoView(); + ancestors.push(window); + return ancestors; + } + + function scrollIntoViewWithin(el, container) { + let rect = el.getBoundingClientRect(); + + if (container === window) { + if (rect.top < 0 || rect.bottom > window.innerHeight) { + el.scrollIntoView({ + block: 'center', + }); + } + } else { + let container_rect = container.getBoundingClientRect(); + let top_ = rect.top - container_rect.top; + let bottom = top_ + rect.height; + + let target_top = container.scrollTop; + + if (top_ < 0) { + target_top += top_ - container.clientHeight / 2; + } else if (bottom > container.clientHeight) { + target_top += bottom - container.clientHeight / 2 + } + container.scrollTo({ + top: Math.max(0, target_top), + }); } } + + element.each(function() { + let ancestors = getScrollableAncestors(this); + for (let ancestor of ancestors) { + scrollIntoViewWithin(this, ancestor); + } + }); }; // Handle click and Return press event