Docs

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:

  1. It sets event.dataTransfer.effectAllowed = 'move'.
  2. 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.