Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/modifier-key-support-for-dnd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
name: Modifier Key Support For Dnd
about: new issue
title: "[Modifier Key Support For Dnd] <TITLE HERE>"
labels: ModifierKeySupportForDnd
assignees: tanu18

---
356 changes: 356 additions & 0 deletions ModifierKeySupportForDnd/explainer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
# Drag and Drop Modifier Keys

## Honoring Keyboard Modifiers for dropEffect During Drag and Drop

**Author:** [Tanu Jain](https://github.com/tanu18)

**Co-authors:** [Rakesh Goulikar](https://github.com/ragoulik), [Rohan Raja](https://github.com/roraja)

## Participate
- [Issue tracker](https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/ModifierKeySupportForDnd)
- [Open a new issue](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?assignees=tanu18&labels=ModifierKeySupportForDnd&template=modifier-key-support-for-dnd.md&title=%Modifier+Key+Support+For+Dnd%5D+%3CTITLE+HERE%3E)

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of Contents

- [Introduction](#introduction)
- [User Problem](#user-problem)
- [Goals](#goals)
- [Non-Goals](#non-goals)
- [Proposal](#proposal)
- [Modifier Key to dropEffect Mapping](#modifier-key-to-dropeffect-mapping)
- [Priority Ordering in effectAllowed](#priority-ordering-in-effectallowed)
- [Handling Disallowed Operations](#handling-disallowed-operations)
- [Decision Flow](#decision-flow)
- [Examples](#examples)
- [Boundary Scenarios](#boundary-scenarios)
- [Pros](#pros)
- [Cons](#cons)
- [Considered Alternative: Ignore Modifier Keys Entirely](#considered-alternative-ignore-modifier-keys-entirely)
- [Pros of Alternate Approach](#pros-of-alternate-approach)
- [Cons of Alternate Approach](#cons-of-alternate-approach)
- [Accessibility, Privacy, and Security Considerations](#accessibility-privacy-and-security-considerations)
- [Appendix](#appendix)
- [Appendix 1: Platform Modifier Key Conventions](#appendix-1-platform-modifier-key-conventions)
- [Appendix 2: effectAllowed Values and Priority](#appendix-2-effectallowed-values-and-priority)
- [References and Acknowledgements](#references-and-acknowledgements)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Introduction

This proposal introduces support for honoring keyboard modifier keys during drag and drop operations, an enhancement to the [HTML Drag and Drop API](https://html.spec.whatwg.org/multipage/dnd.html) that allows the browser to compute the `dropEffect` value based on both the modifier keys pressed by the user and the `effectAllowed` value set by the drag source.

```js
// Example: User holds Ctrl while dragging on Windows
// effectAllowed is set to "copyMove"
element.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'copyMove';
});

element.addEventListener('dragover', (e) => {
e.preventDefault();
// With this proposal, dropEffect will be "copy" because:
// 1. Ctrl is pressed (Windows convention for copy)
// 2. "copy" is allowed by effectAllowed
console.log(e.dataTransfer.dropEffect); // "copy"
});
```

Currently, keyboard modifier keys (Ctrl, Shift, Alt/Option) are ignored during drag and drop operations in Chromium. Native file managers and desktop applications (e.g., Windows File Explorer, macOS Finder, Nautilus on Linux) use these modifiers to let users choose between copy, move, and link operations. This proposal brings web drag and drop behavior in line with these platform conventions, making the `dropEffect` value predictable from the user's key state.

## User Problem

Users expect consistent behavior across applications when performing drag and drop operations. On most operating systems, holding specific modifier keys during a drag operation changes the intended action:

- **Windows/Linux**: Ctrl = Copy, Shift = Move, Ctrl+Shift = Link
- **macOS**: Option = Copy, Command = Move, Option+Command = Link

However, current browser implementations ignore these modifier keys when computing the `dropEffect` value. This creates a disconnect between:

1. **Native applications** (file managers, desktop apps) where modifier keys directly control whether a drag results in a copy, move, or link
2. **Web applications** where modifier keys have no effect on the `dropEffect` value

This inconsistency frustrates users who rely on modifier keys to control drag behavior, especially those who frequently switch between native and web applications. Web developers who want drag-and-drop to match native platform behavior must manually listen for `keydown`/`keyup` events, maintain modifier key state, and map that state to the correct `dropEffect` value—duplicating logic that the browser could handle natively.

## Goals

- Honor platform-specific modifier key conventions during drag and drop operations.
- Compute `dropEffect` based on both modifier keys pressed and `effectAllowed` constraints.
- Define a single algorithm for computing `dropEffect` from modifier keys that all browsers implement identically across Windows, macOS, and Linux.
- Preserve backward compatibility: applications that explicitly set `dropEffect` in event handlers continue to override the browser-computed value.

## Non-Goals

- Modifying the `effectAllowed` property behavior or its possible values.
- Changing how `dropEffect` is set programmatically by event handlers.
- Altering drag and drop behavior for touch or pointer-based interactions without keyboard.

## Proposal

We propose that User Agents compute the initial `dropEffect` value during `dragenter` and `dragover` events based on:

1. The current modifier key state (mapped to platform conventions)
2. The `effectAllowed` value set by the drag source

This computation occurs before event handlers run, allowing developers to inspect and optionally override the browser-computed value.

### Modifier Key to dropEffect Mapping

When `effectAllowed = "all"`, the browser maps the current modifier key state to `dropEffect` using the following platform-specific conventions:

| Platform | Copy | Move | Link |
|----------|------|------|------|
| Windows/Linux | Ctrl | Shift | Ctrl+Shift |
| macOS | Option | Command | Option+Command |

**Example: Platform-native modifier behavior**
```js
// On Windows, user drags with Ctrl held
// effectAllowed = "all"
element.addEventListener('dragover', (e) => {
e.preventDefault();
console.log(e.dataTransfer.dropEffect); // "copy" (Ctrl on Windows)
});
```

### Priority Ordering in effectAllowed

For compound `effectAllowed` values (`"copyMove"`, `"linkMove"`, `"copyLink"`), the left-to-right order of operations in the value name defines priority:

- **P0 (Primary)**: The first effect in the name (e.g., "copy" in "copyMove")
- **P1 (Fallback)**: The second effect in the name (e.g., "move" in "copyMove")

**Rules:**
- If no modifier key is pressed → use P0
- If modifier requests P1 and it's allowed → use P1
- If modifier requests an operation not allowed → fall back to P0

**Example: Priority-based effect selection**
```js
element.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'copyMove'; // P0 = copy, P1 = move
});

element.addEventListener('dragover', (e) => {
e.preventDefault();

// No modifier key pressed → dropEffect = "copy" (P0)
// Shift pressed (move) → dropEffect = "move" (P1, allowed)
// Ctrl+Shift pressed (link) → dropEffect = "copy" (link not allowed, fall back to P0)
});
```

### Handling Disallowed Operations

When the user's modifier key indicates a specific operation that is not permitted by `effectAllowed`, the browser follows these rules:

**(A) If only one operation is allowed** (e.g., `effectAllowed = "copy"`)
→ Always use that operation, ignoring modifier keys.

```js
element.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'copy'; // Only copy allowed
});

element.addEventListener('dragover', (e) => {
e.preventDefault();
// Even if Shift (move) is pressed, dropEffect = "copy"
console.log(e.dataTransfer.dropEffect); // "copy"
});
```

**(B) If multiple operations are allowed** (e.g., `effectAllowed = "linkMove"`)
1. Check if the modifier-requested operation is allowed
- If yes → use it
- If no → ignore it
2. Fall back to the primary allowed operation (P0) based on the ordering rule

```js
element.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'linkMove'; // P0 = link, P1 = move
});

element.addEventListener('dragover', (e) => {
e.preventDefault();

// On Windows:
// Ctrl pressed (copy request) → "link" (copy not allowed, fall back to P0)
// Shift pressed (move request) → "move" (allowed, P1)
// Ctrl+Shift pressed (link request) → "link" (allowed, P0)
// No modifier → "link" (P0)
});
```

## Decision Flow

The following decision flow describes how `dropEffect` is computed:

1. **Is `effectAllowed = "all"`?**
- **Yes** → Follow file manager app behavior (use platform modifier conventions directly)

2. **Is `effectAllowed` a single operation (e.g., "move", "copy", "link")?**
- **Yes** → Always use that single operation, regardless of modifier keys

3. **Is `effectAllowed` a compound value (e.g., "copyMove", "linkMove")?**
- **Yes** → Define priority: first operation = P0 (higher priority), second = P1

4. **Is any modifier key pressed?**
- **No** → Use P0 (e.g., "copy" for "copyMove")

5. **Does the modifier key result in P1 operation (per file manager conventions)?**
- **Yes** → Use P1
- **No** → Use P0

## Examples

**Example 1: effectAllowed = "all" with Ctrl on Windows**
```js
// User drags with Ctrl held on Windows
element.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'all';
});

element.addEventListener('dragover', (e) => {
e.preventDefault();
console.log(e.dataTransfer.dropEffect); // "copy"
});
```

**Example 2: effectAllowed = "copyMove" with no modifier**
```js
element.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'copyMove';
});

element.addEventListener('dragover', (e) => {
e.preventDefault();
// No modifier → P0 = "copy"
console.log(e.dataTransfer.dropEffect); // "copy"
});
```

**Example 3: effectAllowed = "linkMove" with Ctrl (copy request) on Windows**
```js
element.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'linkMove';
});

element.addEventListener('dragover', (e) => {
e.preventDefault();
// Ctrl requests "copy", but copy is not allowed
// Fall back to P0 = "link"
console.log(e.dataTransfer.dropEffect); // "link"
});
```

**Example 4: Developer override**
```js
element.addEventListener('dragover', (e) => {
e.preventDefault();
// Browser computed dropEffect based on modifiers
console.log(e.dataTransfer.dropEffect); // e.g., "copy"

// Developer can still override
e.dataTransfer.dropEffect = 'move';
});
```

## Boundary Scenarios

- **No modifier key pressed**: Use P0 for compound `effectAllowed` values, or the single allowed operation.
- **Multiple modifier keys pressed**: Use the combined operation if it matches platform conventions (e.g., Ctrl+Shift = link on Windows).
- **Modifier key held but released mid-drag**: The `dropEffect` updates on each subsequent `dragover` event to reflect the current modifier key state at the time the event fires.
- **effectAllowed = "none"**: `dropEffect` should be "none" regardless of modifiers.
- **effectAllowed = "uninitialized"**: Treated as "all" per the existing specification.

```js
// Boundary: effectAllowed = "none"
element.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'none';
});

element.addEventListener('dragover', (e) => {
e.preventDefault();
// Always "none", modifier keys ignored
console.log(e.dataTransfer.dropEffect); // "none"
});
```

## Pros

- Aligns web drag and drop behavior with the modifier key conventions used by Windows File Explorer, macOS Finder, and Linux file managers (Nautilus, Dolphin, Thunar).
- Improves user experience for users accustomed to modifier key shortcuts in native file managers and desktop applications.
- Preserves backward compatibility: applications that explicitly set `dropEffect` in `dragover`/`dragenter` handlers continue to override the browser-computed value.
- Provides a deterministic algorithm for computing `dropEffect` from modifier key state and `effectAllowed`.

## Cons

- Developers unfamiliar with native OS drag-and-drop conventions must learn platform-specific modifier key mappings (e.g., Ctrl = copy on Windows vs. Option = copy on macOS).
- The P0/P1 priority ordering is a new concept not present in the current HTML specification and must be clearly documented for web developers.
- Cross-platform web applications must account for the fact that the same user intent (e.g., "copy") requires different modifier keys on different platforms (Ctrl on Windows/Linux, Option on macOS).

## Considered Alternative: Ignore Modifier Keys Entirely

An alternative approach is to maintain the current behavior where modifier keys do not influence `dropEffect`. Under this model, `dropEffect` would only be determined by:
1. The `effectAllowed` value
2. Explicit developer assignment in event handlers

### Pros of Alternate Approach

- Simpler mental model: `dropEffect` is determined solely by `effectAllowed` and explicit developer assignment, with no platform-specific modifier key behavior.
- No changes required to existing browser implementations.
- Developers retain full, explicit control over `dropEffect` in every drag event handler.

### Cons of Alternate Approach

- The `dropEffect` value does not reflect the user's modifier key state, making web drag-and-drop behavior inconsistent with native applications.
- Developers must manually listen for `keydown`/`keyup` events, track modifier key state, and map that state to platform-specific `dropEffect` values—error-prone boilerplate that the browser could handle.
- Accurately reproducing native file manager modifier key mappings across Windows, macOS, and Linux requires per-platform conditional logic that is difficult to test.
- When users drag content between a native application and a web application, the modifier key they hold has no effect on the web side, breaking the expected copy/move/link behavior.

## Accessibility, Privacy, and Security Considerations

**Accessibility**: This change improves accessibility by honoring standard platform modifier key conventions that users with motor impairments rely on to control drag operations without additional UI interactions. Modifier keys provide a keyboard-driven alternative to visual drag cues (such as cursor icon changes) for selecting copy, move, or link.

**Privacy**: No additional user data is collected or exposed. The modifier key state (`ctrlKey`, `shiftKey`, `altKey`, `metaKey`) is already accessible to web applications through existing `DragEvent` properties.

**Security**: This proposal does not alter the security model of the Drag and Drop API. All existing security restrictions (e.g., cross-origin data access limitations during drag) remain in effect. The modifier key state used for `dropEffect` computation is the same state already exposed to JavaScript.

## Appendix

### Appendix 1: Platform Modifier Key Conventions

| Platform | Copy | Move | Link |
|----------|------|------|------|
| Windows | Ctrl | Shift | Ctrl+Shift |
| Linux | Ctrl | Shift | Ctrl+Shift |
| macOS | Option (⌥) | Command (⌘) | Option+Command (⌥⌘) |

These conventions are used by native file managers:
- **Windows**: File Explorer
- **Linux**: Nautilus, Dolphin, Thunar
- **macOS**: Finder

### Appendix 2: effectAllowed Values and Priority

| effectAllowed Value | P0 (Primary) | P1 (Secondary) | P2 (Tertiary) |
|---------------------|--------------|----------------|---------------|
| `"none"` | none | — | — |
| `"copy"` | copy | — | — |
| `"move"` | move | — | — |
| `"link"` | link | — | — |
| `"copyMove"` | copy | move | — |
| `"copyLink"` | copy | link | — |
| `"linkMove"` | link | move | — |
| `"all"` | (follows platform convention) | | |
| `"uninitialized"` | (treated as "all") | | |

## References and Acknowledgements

References:
- [HTML Drag and Drop API Specification](https://html.spec.whatwg.org/multipage/dnd.html)
- [DataTransfer.dropEffect MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/dropEffect)
- [DataTransfer.effectAllowed MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed)