diff --git a/packages/slate-react/src/utils/scroll-to-selection.js b/packages/slate-react/src/utils/scroll-to-selection.js index 618e4795c..a0001a176 100644 --- a/packages/slate-react/src/utils/scroll-to-selection.js +++ b/packages/slate-react/src/utils/scroll-to-selection.js @@ -2,6 +2,45 @@ import getWindow from 'get-window' import isBackward from 'selection-is-backward' +/** + * CSS overflow values that would cause scrolling. + * + * @type {Array} + */ + +const OVERFLOWS = [ + 'auto', + 'overlay', + 'scroll', +] + +/** + * Find the nearest parent with scrolling, or window. + * + * @param {el} Element + */ + +function findScrollContainer(el) { + const window = getWindow(el) + let parent = el.parentNode + let scroller = window + + while (!scroller) { + if (!parent.parentNode) break + const style = window.getComputedStyle(parent) + const { overflowY } = style + + if (OVERFLOWS.includes(overflowY)) { + scroller = parent + break + } + + parent = parent.parentNode + } + + return scroller +} + /** * Scroll the current selection's focus point into view if needed. * @@ -12,22 +51,47 @@ function scrollToSelection(selection) { if (!selection.anchorNode) return const window = getWindow(selection.anchorNode) + const scroller = findScrollContainer(selection.anchorNode) + const isWindow = scroller == window const backward = isBackward(selection) const range = selection.getRangeAt(0) const rect = range.getBoundingClientRect() - const { innerWidth, innerHeight, pageYOffset, pageXOffset } = window - const top = (backward ? rect.top : rect.bottom) + pageYOffset - const left = (backward ? rect.left : rect.right) + pageXOffset + let width + let height + let yOffset + let xOffset + + if (isWindow) { + const { innerWidth, innerHeight, pageYOffset, pageXOffset } = scroller + width = innerWidth + height = innerHeight + yOffset = pageYOffset + xOffset = pageXOffset + } else { + const { offsetWidth, offsetHeight, scrollTop, scrollLeft } = scroller + width = offsetWidth + height = offsetHeight + yOffset = scrollTop + xOffset = scrollLeft + } + + const top = (backward ? rect.top : rect.bottom) + yOffset + const left = (backward ? rect.left : rect.right) + xOffset + + const x = left < yOffset || innerWidth + xOffset < left + ? left - width / 2 + : xOffset - const x = left < pageXOffset || innerWidth + pageXOffset < left - ? left - innerWidth / 2 - : pageXOffset + const y = top < yOffset || height + yOffset < top + ? top - height / 2 + : yOffset - const y = top < pageYOffset || innerHeight + pageYOffset < top - ? top - innerHeight / 2 - : pageYOffset - - window.scrollTo(x, y) + if (isWindow) { + window.scrollTo(x, y) + } else { + scroller.scrollTop = scroller.scrollTop + y + scroller.scrollLeft = scroller.scrollLeft + x + } } /**