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:
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()
+ }
+}