Tree-View Drag to Copy
Drag a file or folder in the tree onto another folder to move it; hold ⌥ Option or ⌘ Command while dropping to copy it instead. Copies are undoable (
⌘Z).
Implemented in the owned tranquil-drag-drop package — the core tree-view is third-party and
isn’t modified.
Behavior
- No modifier → move (unchanged core behavior).
- ⌥ / ⌘ held on drop → copy into the target folder. Name collisions get a numeric suffix, copying a folder into itself is blocked, and the copy is recorded for undo.
- The cursor shows the + copy badge while a modifier is held.
How it works (and the gotcha)
Core’s tree-view already copies on ⌘/Ctrl in onDrop (via copyEntry), but two things in its onDragStart conspire to break an Option/Command copy from an outside package:
- It sets
event.dataTransfer.effectAllowed = 'move'. - It calls
event.stopPropagation()before doing so.
Because effectAllowed is 'move', the browser treats an Option-held drag (a copy request) as an
invalid operation and cancels the drop before the drop event ever fires — so a drop-time handler
never runs. And because onDragStart stops propagation, the fix can’t live on a document listener
(bubble is blocked; a capture listener would just be overwritten by core’s later 'move').
The fix widens effectAllowed to 'copyMove' from a dragstart listener attached directly to the
tree-view element, registered after core’s (in observeTreeViewOperations). Same-node listeners
still run despite stopPropagation(), and registering after core means it runs last — after effectAllowed='move' is set — so it can override it. With both operations now permitted, the drop
fires, and a capture-phase drop handler copies via the tree-view’s own copyEntry (collision-safe;
emits entry-copied, which the package’s existing undo hook records).
Takeaway: to change a browser drag’s allowed effects when another handler sets them and calls stopPropagation(), attach on the same element after the original listener — not on a document
capture/bubble listener.