From 9d87dbed3f81d07a81b1484a1317d7f7c0258610 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 28 May 2026 19:58:24 +0800
Subject: [PATCH] Stabilize tag navigation hover highlighting

---
 Apptag/TagNavigationView.swift |   32 +++++++++++++++++++++-----------
 1 files changed, 21 insertions(+), 11 deletions(-)

diff --git a/Apptag/TagNavigationView.swift b/Apptag/TagNavigationView.swift
index 95f7915..2736f53 100644
--- a/Apptag/TagNavigationView.swift
+++ b/Apptag/TagNavigationView.swift
@@ -17,7 +17,7 @@
     let orientation: Orientation
     let contentInsets: NSEdgeInsets
     let onActivate: (String) -> Void
-    let onHover: (String) -> Void
+    let onHoverChange: (String, Bool) -> Void
 
     func makeNSView(context: Context) -> TagNavigationHostView {
         let view = TagNavigationHostView()
@@ -26,7 +26,7 @@
             orientation: orientation,
             contentInsets: contentInsets,
             onActivate: onActivate,
-            onHover: onHover
+            onHoverChange: onHoverChange
         )
         return view
     }
@@ -37,7 +37,7 @@
             orientation: orientation,
             contentInsets: contentInsets,
             onActivate: onActivate,
-            onHover: onHover
+            onHoverChange: onHoverChange
         )
     }
 }
@@ -63,14 +63,14 @@
         orientation: TagNavigationView.Orientation,
         contentInsets: NSEdgeInsets,
         onActivate: @escaping (String) -> Void,
-        onHover: @escaping (String) -> Void
+        onHoverChange: @escaping (String, Bool) -> Void
     ) {
         documentView.update(
             items: items,
             orientation: orientation,
             contentInsets: contentInsets,
             onActivate: onActivate,
-            onHover: onHover
+            onHoverChange: onHoverChange
         )
         scrollView.hasHorizontalScroller = orientation == .horizontal
         scrollView.hasVerticalScroller = orientation == .vertical
@@ -109,7 +109,7 @@
     private var contentInsets = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
     private var buttons: [TagNavigationButton] = []
     private var onActivate: (String) -> Void = { _ in }
-    private var onHover: (String) -> Void = { _ in }
+    private var onHoverChange: (String, Bool) -> Void = { _, _ in }
 
     override var isFlipped: Bool { true }
 
@@ -118,14 +118,14 @@
         orientation: TagNavigationView.Orientation,
         contentInsets: NSEdgeInsets,
         onActivate: @escaping (String) -> Void,
-        onHover: @escaping (String) -> Void
+        onHoverChange: @escaping (String, Bool) -> Void
     ) {
         let needsRebuild = self.items != items || self.orientation != orientation
         self.items = items
         self.orientation = orientation
         self.contentInsets = contentInsets
         self.onActivate = onActivate
-        self.onHover = onHover
+        self.onHoverChange = onHoverChange
 
         if needsRebuild {
             rebuildButtons()
@@ -170,7 +170,7 @@
         buttons = items.map { item in
             let button = TagNavigationButton(item: item, orientation: orientation)
             button.onActivate = { [weak self] tagID in self?.onActivate(tagID) }
-            button.onHover = { [weak self] tagID in self?.onHover(tagID) }
+            button.onHoverChange = { [weak self] tagID, active in self?.onHoverChange(tagID, active) }
             addSubview(button)
             return button
         }
@@ -200,11 +200,12 @@
 
 final class TagNavigationButton: NSButton {
     var onActivate: (String) -> Void = { _ in }
-    var onHover: (String) -> Void = { _ in }
+    var onHoverChange: (String, Bool) -> Void = { _, _ in }
 
     private var item: TagNavigationItem
     private var orientation: TagNavigationView.Orientation
     private var trackingAreaRef: NSTrackingArea?
+    private var isMouseInside = false
 
     var preferredSize: NSSize {
         let font = NSFont.systemFont(ofSize: 13, weight: .medium)
@@ -262,7 +263,16 @@
 
     override func mouseEntered(with event: NSEvent) {
         super.mouseEntered(with: event)
-        onHover(item.id)
+        guard !isMouseInside else { return }
+        isMouseInside = true
+        onHoverChange(item.id, true)
+    }
+
+    override func mouseExited(with event: NSEvent) {
+        super.mouseExited(with: event)
+        guard isMouseInside else { return }
+        isMouseInside = false
+        onHoverChange(item.id, false)
     }
 
     @objc private func performActivation() {

--
Gitblit v1.9.3