Merge nucleic/dapper-velvet-badger-dtr3 into dev

This commit is contained in:
2026-08-01 04:21:00 -07:00
parent d2fec53cec
commit d38fb7fac2
+28
View File
@@ -266,6 +266,15 @@ struct ChatInputField: NSViewRepresentable {
/// so the fitted height can be recomputed for the new text wrapping. /// so the fitted height can be recomputed for the new text wrapping.
final class HeightTrackingScrollView: NSScrollView { final class HeightTrackingScrollView: NSScrollView {
var onLayout: (() -> Void)? var onLayout: (() -> Void)?
/// `NSTextView` installs the I-beam directly when it handles a click. In a normal AppKit
/// hierarchy the next cursor rect under the pointer replaces it as soon as the pointer leaves,
/// but the SwiftUI views surrounding this representable do not consistently install an arrow
/// rect. That is especially visible in Home's tall, otherwise-empty hero field: the I-beam can
/// remain current across the whole window until AppKit's next cursor-rect rebuild.
///
/// Track the scroll view rather than its document view so this covers the field's exact visible
/// bounds even when an empty `NSTextView` is shorter than the Home composer's minimum height.
private var cursorExitTrackingArea: NSTrackingArea?
/// Force overlay-style scrollers, rejecting the system's legacy style ("Show scroll /// Force overlay-style scrollers, rejecting the system's legacy style ("Show scroll
/// bars: Always", common with a mouse attached). A legacy scroller consumes layout /// bars: Always", common with a mouse attached). A legacy scroller consumes layout
@@ -283,6 +292,25 @@ final class HeightTrackingScrollView: NSScrollView {
set { _ = newValue; super.scrollerStyle = .overlay } set { _ = newValue; super.scrollerStyle = .overlay }
} }
override func updateTrackingAreas() {
super.updateTrackingAreas()
if let cursorExitTrackingArea { removeTrackingArea(cursorExitTrackingArea) }
let area = NSTrackingArea(
rect: .zero,
options: [.mouseEnteredAndExited, .activeInKeyWindow, .inVisibleRect],
owner: self,
userInfo: nil)
addTrackingArea(area)
cursorExitTrackingArea = area
}
override func mouseExited(with event: NSEvent) {
// `set()` is intentional: the text view also sets (rather than pushes) its cursor, so
// there is no balanced cursor-stack entry to pop here.
NSCursor.arrow.set()
super.mouseExited(with: event)
}
override func layout() { override func layout() {
super.layout() super.layout()
onLayout?() onLayout?()