← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


commit fa4040496b251aad12f0225f636f00c929540597
parent 47e27704ad6399b4a21778ae7860d7433cd55ff8
Author: Michael Camilleri <[email protected]>
Date:   Wed, 25 Feb 2026 13:25:21 +0900

Fix cursor during drag reordering in macOS

Co-Authored-By: Codex GPT 5.3 <[email protected]>

Diffstat

MListless/Extensions/TaskListView+Logic.swift | 5++---
MListlessMac/Views/TaskListView.swift | 88+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------

2 files changed, 59 insertions(+), 34 deletions(-)

diff --git a/Listless/Extensions/TaskListView+Logic.swift b/Listless/Extensions/TaskListView+Logic.swift

@@ -414,9 +414,8 @@ extension TaskListView {
         }
     }
 
-    func handleDrop(items: [String]) -> Bool {
-        guard let droppedUUIDString = items.first,
-            let droppedUUID = UUID(uuidString: droppedUUIDString),
+    func commitCurrentDrag() -> Bool {
+        guard let droppedUUID = draggedTaskID,
             let order = visualOrder,
             let finalIndex = order.firstIndex(of: droppedUUID)
         else {

diff --git a/ListlessMac/Views/TaskListView.swift b/ListlessMac/Views/TaskListView.swift

@@ -1,4 +1,5 @@
 import SwiftUI
+import UniformTypeIdentifiers
 
 struct TaskListView: View {
     enum FocusField: Hashable {
@@ -181,37 +182,37 @@ struct TaskListView: View {
                                 Color.clear
                                     .frame(maxHeight: .infinity)
                                     .layoutPriority(1)
-                                    .dropDestination(
-                                        for: String.self, action: { _, _ in false },
-                                        isTargeted: { isTargeted in
-                                            if isTargeted {
-                                                updateVisualOrder(insertBefore: task.id)
-                                            }
-                                        })
+                                    .onDrop(
+                                        of: [UTType.text],
+                                        delegate: TaskReorderDropDelegate(
+                                            onTargeted: { updateVisualOrder(insertBefore: task.id) },
+                                            onPerform: { commitCurrentDrag() }
+                                        )
+                                    )
 
                                 // Middle 2/3 - insert based on direction
                                 Color.clear
                                     .frame(maxHeight: .infinity)
                                     .layoutPriority(4)
-                                    .dropDestination(
-                                        for: String.self, action: { _, _ in false },
-                                        isTargeted: { isTargeted in
-                                            if isTargeted {
-                                                updateVisualOrderSmart(relativeTo: task.id)
-                                            }
-                                        })
+                                    .onDrop(
+                                        of: [UTType.text],
+                                        delegate: TaskReorderDropDelegate(
+                                            onTargeted: { updateVisualOrderSmart(relativeTo: task.id) },
+                                            onPerform: { commitCurrentDrag() }
+                                        )
+                                    )
 
                                 // Bottom 1/6 - insert AFTER
                                 Color.clear
                                     .frame(maxHeight: .infinity)
                                     .layoutPriority(1)
-                                    .dropDestination(
-                                        for: String.self, action: { _, _ in false },
-                                        isTargeted: { isTargeted in
-                                            if isTargeted {
-                                                updateVisualOrder(insertAfter: task.id)
-                                            }
-                                        })
+                                    .onDrop(
+                                        of: [UTType.text],
+                                        delegate: TaskReorderDropDelegate(
+                                            onTargeted: { updateVisualOrder(insertAfter: task.id) },
+                                            onPerform: { commitCurrentDrag() }
+                                        )
+                                    )
                             }
                         }
                     }
@@ -221,13 +222,13 @@ struct TaskListView: View {
                 if !activeTasks.isEmpty && draggedTaskID != nil {
                     Color.clear
                         .frame(height: 44)
-                        .dropDestination(
-                            for: String.self, action: { _, _ in false },
-                            isTargeted: { isTargeted in
-                                if isTargeted {
-                                    updateVisualOrder(insertAtEnd: true)
-                                }
-                            })
+                        .onDrop(
+                            of: [UTType.text],
+                            delegate: TaskReorderDropDelegate(
+                                onTargeted: { updateVisualOrder(insertAtEnd: true) },
+                                onPerform: { commitCurrentDrag() }
+                            )
+                        )
                 }
 
                 ForEach(completedTasks) { task in
@@ -245,9 +246,13 @@ struct TaskListView: View {
                 }
             }
             .frame(maxWidth: .infinity, alignment: .topLeading)
-            .dropDestination(for: String.self) { items, location in
-                handleDrop(items: items)
-            }
+            .onDrop(
+                of: [UTType.text],
+                delegate: TaskReorderDropDelegate(
+                    onTargeted: {},
+                    onPerform: { commitCurrentDrag() }
+                )
+            )
         }
         .contentShape(Rectangle())
         .onTapGesture {
@@ -333,3 +338,24 @@ struct TaskListView: View {
         }
     }
 }
+
+private struct TaskReorderDropDelegate: DropDelegate {
+    let onTargeted: () -> Void
+    let onPerform: () -> Bool
+
+    func validateDrop(info: DropInfo) -> Bool {
+        true
+    }
+
+    func dropEntered(info: DropInfo) {
+        onTargeted()
+    }
+
+    func dropUpdated(info: DropInfo) -> DropProposal? {
+        return DropProposal(operation: .move)
+    }
+
+    func performDrop(info: DropInfo) -> Bool {
+        onPerform()
+    }
+}