From 406c5898d103427193e589b4bbe615d0c739b2c5 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Tue, 02 Jun 2026 19:36:31 +0800
Subject: [PATCH] Prepare 7.8.9 tag navigation drop test package

---
 Apptag/ContentView.swift |   75 ++++++++++++++++++++++++++++++++++++-
 1 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index 9b6ce7c..f29d986 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -318,6 +318,7 @@
     var appDragResetToken = 0
     var bubbleDraftNote = ""
     var tagNavigationHoveredGroupName: String? = nil
+    var tagNavigationAppDropTargetName: String? = nil
     var tagNavigationLastHoverScrollID: String? = nil
     var tagNavigationLastHoverScrollAt: Date? = nil
 }
@@ -948,6 +949,7 @@
 
     private func appKitTagNavigation(orientation: TagNavigationView.Orientation) -> some View {
         let isHorizontal = orientation == .horizontal
+        let appDropTargetID = appGridInteraction.tagNavigationAppDropTargetName
         return TagNavigationView(
             items: tagLabels,
             orientation: orientation,
@@ -956,6 +958,8 @@
                 : NSEdgeInsets(top: 12, left: 12, bottom: 12, right: 12),
             dragModeActive: tagNavDragModeActive,
             draggingItemID: tagNavDragItem,
+            appDragModeActive: appGridInteraction.appDragModeActive,
+            appDropTargetID: appDropTargetID,
             onActivate: { tagID in
                 activateTagNavigation(tagID)
             },
@@ -965,6 +969,9 @@
             canReorder: { tagID in
                 canReorderTag(tagID)
             },
+            canAcceptAppDrop: { tagID in
+                canAssignDroppedAppToTag(tagID)
+            },
             onReorderBegan: { tagID in
                 beginTagNavReorder(tagID)
             },
@@ -973,6 +980,12 @@
             },
             onReorderEnded: {
                 endTagNavReorder()
+            },
+            onAppDropHoverChange: { tagID, active in
+                handleTagNavigationAppDropHover(tagID, active: active)
+            },
+            onAppDrop: { path, source, targetTag, copy in
+                dropAppOnTagNavigation(path: path, sourceTag: source, targetTag: targetTag, copy: copy)
             }
         )
     }
@@ -1903,6 +1916,13 @@
     }
 
     private func handleTagNavigationHover(_ id: String, active: Bool) {
+        if appGridInteraction.appDragModeActive {
+            if !active, appGridInteraction.tagNavigationHoveredGroupName == id {
+                appGridInteraction.tagNavigationHoveredGroupName = nil
+            }
+            return
+        }
+
         guard active else {
             if appGridInteraction.tagNavigationHoveredGroupName == id {
                 appGridInteraction.tagNavigationHoveredGroupName = nil
@@ -1913,6 +1933,24 @@
         appGridInteraction.tagNavigationHoveredGroupName = id
         fillColorlessContainer(id)
         scrollToTagFromHover(id)
+    }
+
+    private func handleTagNavigationAppDropHover(_ id: String, active: Bool) {
+        guard appGridInteraction.appDragModeActive else {
+            if appGridInteraction.tagNavigationAppDropTargetName == id {
+                appGridInteraction.tagNavigationAppDropTargetName = nil
+            }
+            return
+        }
+
+        guard active, canAssignDroppedAppToTag(id) else {
+            if appGridInteraction.tagNavigationAppDropTargetName == id {
+                appGridInteraction.tagNavigationAppDropTargetName = nil
+            }
+            return
+        }
+
+        appGridInteraction.tagNavigationAppDropTargetName = id
     }
 
     private func scrollToTagFromHover(_ id: String) {
@@ -2050,6 +2088,22 @@
         )
         showDropRefresh()
         refreshApps(forceLayoutRefresh: true)
+    }
+
+    private func dropAppOnTagNavigation(path: String, sourceTag: String, targetTag: String, copy: Bool) {
+        resetTransientDragState()
+        guard canAssignDroppedAppToTag(targetTag) else { return }
+        guard let app = allApps.first(where: { $0.path.path == path }) else { return }
+        guard !appHasTag(app, tagName: targetTag) else { return }
+        TagEditor.appendTags([targetTag], to: [path])
+        showDropRefresh()
+        refreshApps(forceLayoutRefresh: true)
+    }
+
+    private func canAssignDroppedAppToTag(_ tagName: String) -> Bool {
+        tagColors[tagName] != nil
+            && !isAppleBuiltInDropTarget(tagName)
+            && !isUncategorizedDropTarget(tagName)
     }
 
     private func dropAppOutsideGroup(path: String, sourceTag: String, copy: Bool) {
@@ -2263,12 +2317,17 @@
         if active {
             endTagNavReorder()
             clearAppBubbleState()
+            appGridInteraction.tagNavigationAppDropTargetName = nil
         }
         appGridInteraction.appDragModeActive = active
+        if !active {
+            appGridInteraction.tagNavigationAppDropTargetName = nil
+        }
         if active {
             DispatchQueue.main.asyncAfter(deadline: .now() + 8) {
                 if appGridInteraction.appDragModeActive && !AppDragCoordinator.shared.hasActiveDrag {
                     appGridInteraction.appDragModeActive = false
+                    appGridInteraction.tagNavigationAppDropTargetName = nil
                 }
             }
         }
@@ -2282,6 +2341,9 @@
         AppDragCoordinator.shared.cancelDrag()
         if appGridInteraction.appDragModeActive {
             appGridInteraction.appDragModeActive = false
+        }
+        if appGridInteraction.tagNavigationAppDropTargetName != nil {
+            appGridInteraction.tagNavigationAppDropTargetName = nil
         }
         if hadAppDragState {
             appGridInteraction.appDragResetToken &+= 1
@@ -2355,6 +2417,13 @@
     ) {
         appGridInteraction.appDragModeActive = false
         endTagNavReorder()
+        let closeQuickSearchAndOverlayBeforeOpening = closeQuickSearchOnSuccess
+            && closeOverlayOnSuccess
+            && (quickSearchCloseHidesOverlay || quickSearchOnlySession)
+        if closeQuickSearchAndOverlayBeforeOpening {
+            closeQuickSearch(hideOverlayIfNeeded: true)
+            hideOverlay()
+        }
         let configuration = NSWorkspace.OpenConfiguration()
         NSWorkspace.shared.openApplication(at: app.path, configuration: configuration) { _, error in
             DispatchQueue.main.async {
@@ -2366,10 +2435,10 @@
                 DispatchQueue.global(qos: .utility).async {
                     TagEditor.recordLauncherOpen(for: app.path.path)
                 }
-                if closeQuickSearchOnSuccess {
-                    closeQuickSearch(hideOverlayIfNeeded: false)
+                if closeQuickSearchOnSuccess && !closeQuickSearchAndOverlayBeforeOpening {
+                    closeQuickSearch(hideOverlayIfNeeded: closeOverlayOnSuccess)
                 }
-                if closeOverlayOnSuccess {
+                if closeOverlayOnSuccess && !closeQuickSearchAndOverlayBeforeOpening {
                     hideOverlay()
                 }
             }

--
Gitblit v1.9.3