mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-30 02:19:52 +02:00
refactor scroll logic (#1428)
This commit is contained in:
committed by
Ian Storm Taylor
parent
a4028cac6b
commit
4f1c26961d
@@ -20,13 +20,13 @@ const OVERFLOWS = [
|
||||
* @param {el} Element
|
||||
*/
|
||||
|
||||
function findScrollContainer(el) {
|
||||
const window = getWindow(el)
|
||||
function findScrollContainer(el, window) {
|
||||
let parent = el.parentNode
|
||||
let scroller
|
||||
|
||||
while (!scroller) {
|
||||
if (!parent.parentNode) break
|
||||
|
||||
const style = window.getComputedStyle(parent)
|
||||
const { overflowY } = style
|
||||
|
||||
@@ -38,7 +38,14 @@ function findScrollContainer(el) {
|
||||
parent = parent.parentNode
|
||||
}
|
||||
|
||||
if (!scroller) return window
|
||||
// COMPAT: Because Chrome does not allow doucment.body.scrollTop, we're
|
||||
// assuming that window.scrollTo() should be used if the scrollable element
|
||||
// turns out to be document.body or document.documentElement. This will work
|
||||
// unless body is intentionally set to scrollable by restricting its height
|
||||
// (e.g. height: 100vh).
|
||||
if (!scroller) {
|
||||
return window.document.body
|
||||
}
|
||||
|
||||
return scroller
|
||||
}
|
||||
@@ -53,41 +60,88 @@ function scrollToSelection(selection) {
|
||||
if (!selection.anchorNode) return
|
||||
|
||||
const window = getWindow(selection.anchorNode)
|
||||
const scroller = findScrollContainer(selection.anchorNode)
|
||||
const isWindow = scroller == window
|
||||
const scroller = findScrollContainer(selection.anchorNode, window)
|
||||
const isWindow = scroller == window.document.body || scroller == window.document.documentElement
|
||||
const backward = isBackward(selection)
|
||||
|
||||
const range = selection.getRangeAt(0)
|
||||
const rect = range.getBoundingClientRect()
|
||||
let selectionRect = range.getBoundingClientRect()
|
||||
|
||||
// COMPAT: range.getBoundingClientRect() returns 0s in Safari when range is
|
||||
// collapsed. Expanding the range by 1 is a relatively effective workaround
|
||||
// for vertical scroll, although horizontal may be off by 1 character.
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=138949
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=435438
|
||||
if (range.collapsed && selectionRect.top == 0 && selectionRect.height == 0) {
|
||||
if (range.startOffset == 0) {
|
||||
range.setEnd(range.endContainer, 1)
|
||||
} else {
|
||||
range.setStart(range.startContainer, range.startOffset - 1)
|
||||
}
|
||||
|
||||
selectionRect = range.getBoundingClientRect()
|
||||
}
|
||||
|
||||
let width
|
||||
let height
|
||||
let yOffset
|
||||
let xOffset
|
||||
let scrollerTop = 0
|
||||
let scrollerLeft = 0
|
||||
let scrollerBordersY = 0
|
||||
let scrollerBordersX = 0
|
||||
|
||||
if (isWindow) {
|
||||
const { innerWidth, innerHeight, pageYOffset, pageXOffset } = scroller
|
||||
const { innerWidth, innerHeight, pageYOffset, pageXOffset } = window
|
||||
width = innerWidth
|
||||
height = innerHeight
|
||||
yOffset = pageYOffset
|
||||
xOffset = pageXOffset
|
||||
} else {
|
||||
const { offsetWidth, offsetHeight, scrollTop, scrollLeft } = scroller
|
||||
const {
|
||||
borderTopWidth,
|
||||
borderBottomWidth,
|
||||
borderLeftWidth,
|
||||
borderRightWidth,
|
||||
} = window.getComputedStyle(scroller)
|
||||
|
||||
const scrollerRect = scroller.getBoundingClientRect()
|
||||
width = offsetWidth
|
||||
height = offsetHeight
|
||||
yOffset = scrollTop - scrollerRect.top
|
||||
xOffset = scrollLeft - scrollerRect.left
|
||||
scrollerTop = scrollerRect.top + parseInt(borderTopWidth, 10)
|
||||
scrollerLeft = scrollerRect.left + parseInt(borderLeftWidth, 10)
|
||||
scrollerBordersY = parseInt(borderTopWidth, 10) + parseInt(borderBottomWidth, 10)
|
||||
scrollerBordersX = parseInt(borderLeftWidth, 10) + parseInt(borderRightWidth, 10)
|
||||
yOffset = scrollTop
|
||||
xOffset = scrollLeft
|
||||
}
|
||||
|
||||
const top = (backward ? rect.top : rect.bottom) + yOffset
|
||||
const left = (backward ? rect.left : rect.right) + xOffset
|
||||
const selectionFocusTop = backward ? selectionRect.top : selectionRect.bottom
|
||||
const selectionTop = selectionFocusTop + yOffset - scrollerTop
|
||||
|
||||
const x = left < yOffset || (width + xOffset) < left
|
||||
? left - width / 2
|
||||
: xOffset
|
||||
const selectionFocusLeft = backward ? selectionRect.left : selectionRect.right
|
||||
const selectionLeft = selectionFocusLeft + xOffset - scrollerLeft
|
||||
|
||||
let x = xOffset
|
||||
let y = yOffset
|
||||
|
||||
if (selectionLeft < xOffset) {
|
||||
// selection to the left of viewport
|
||||
x = selectionLeft
|
||||
} else if (selectionLeft + selectionRect.width + scrollerBordersX > xOffset + width) {
|
||||
// selection to the right of viewport
|
||||
x = selectionLeft + scrollerBordersX - width
|
||||
}
|
||||
|
||||
if (selectionTop < yOffset) {
|
||||
// selection above viewport
|
||||
y = selectionTop
|
||||
} else if (selectionTop + selectionRect.height + scrollerBordersY > yOffset + height) {
|
||||
// selection below viewport
|
||||
y = selectionTop + scrollerBordersY + selectionRect.height - height
|
||||
}
|
||||
|
||||
const y = top < yOffset || (height + yOffset) < top
|
||||
? top - height / 2
|
||||
: yOffset
|
||||
|
||||
if (isWindow) {
|
||||
window.scrollTo(x, y)
|
||||
|
Reference in New Issue
Block a user