Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions Sources/MarkdownEngine/TextView/ClampedScrollView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,24 @@ final class ClampedScrollView: NSScrollView {
// Use the real content height (not the inflated frame) so small
// documents can't scroll past their actual content. The document view is
// always a `NativeTextViewContainer` (header band + text column).
let realHeight = (doc as? NativeTextViewContainer)?.scrollableContentHeight
?? doc.bounds.height
let maxY = max(minY, realHeight - contentView.bounds.height)
let container = doc as? NativeTextViewContainer
var realHeight = container?.scrollableContentHeight ?? doc.bounds.height
let b = contentView.bounds

// `scrollableContentHeight` comes from a cached TextKit-2 measurement that can
// under-measure. A continuous trackpad refreshes it mid-gesture, but a discrete
// device (mouse wheel, Ploopy trackball) sends one event with no relayout — so a
// stale-small height clamps the tick straight back = "scroll doesn't work". When
// a clamp-back is imminent, force one fresh full-layout re-measure first. This is
// self-limiting (only at the bottom) and still clamps to the real content height.
if let textView = container?.textView,
b.origin.y > realHeight - b.height {
textView.pendingFullLayoutMeasure = true
textView.recalcOverscroll(for: self)
realHeight = container?.scrollableContentHeight ?? doc.bounds.height
}

let maxY = max(minY, realHeight - contentView.bounds.height)
let clampedY = min(max(b.origin.y, minY), maxY)
if clampedY != b.origin.y {
contentView.scroll(to: NSPoint(x: b.origin.x, y: clampedY))
Expand Down
Loading