-
Notifications
You must be signed in to change notification settings - Fork 272
Added explainer for Modifier Key Support For Dnd #1258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tanu18
wants to merge
1
commit into
MicrosoftEdge:main
Choose a base branch
from
tanu18:modifier_key_support_for_dnd
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
||
| --- |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,358 @@ | ||
| # Drag and Drop Modifier Keys | ||
|
|
||
| ## Honoring Keyboard Modifiers for dropEffect During Drag and Drop | ||
|
|
||
| **Author:** [Tanu Jain](https://github.com/tanu18) | ||
|
|
||
| ## 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 (such as Ctrl, Shift, Alt/Option) are not consistently honored during drag and drop operations across browsers. Native file managers and desktop applications use these modifiers to let users choose between copy, move, and link operations. This proposal brings web drag and drop behavior in line with platform conventions, improving user experience and predictability. | ||
|
|
||
| ## 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 do not consistently honor these modifier keys when computing the `dropEffect` value. This creates a disconnect between: | ||
|
|
||
| 1. **Native applications** (file managers, desktop apps) where modifier keys work as expected | ||
| 2. **Web applications** where modifier keys are often ignored during drag operations | ||
|
|
||
| This inconsistency frustrates users who rely on modifier keys to control drag behavior, particularly power users who frequently switch between native and web applications. Web developers also face challenges when trying to implement drag-and-drop experiences that match native platform behavior, often requiring complex workarounds to detect and respond to modifier key states. | ||
|
|
||
| ## Goals | ||
|
|
||
| - Honor platform-specific modifier key conventions during drag and drop operations. | ||
| - Compute `dropEffect` based on both modifier keys pressed and `effectAllowed` constraints. | ||
| - Ensure consistent behavior across different browsers and platforms. | ||
| - Maintain backward compatibility with existing web applications. | ||
|
|
||
| ## 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 should map the current modifier key state to `dropEffect` using native OS/file-manager 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 multi-effect values such as `"copyMove"`, `"linkMove"`, or `"copyLink"`, the left-to-right order defines which effect has 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` should update dynamically during subsequent `dragover` events. | ||
| - **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 native platform conventions. | ||
| - Improves user experience for power users familiar with modifier key shortcuts. | ||
| - Maintains backward compatibility—existing applications that set `dropEffect` programmatically continue to work. | ||
| - Provides a consistent, predictable model for computing `dropEffect`. | ||
|
|
||
| ## Cons | ||
|
|
||
| - Platform-specific behavior may be surprising for developers unfamiliar with native conventions. | ||
| - The priority ordering interpretation (P0/P1) is new and requires documentation clarity. | ||
| - Cross-platform web applications may need to account for different modifier key mappings. | ||
|
|
||
| ## 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—no platform-specific behavior to learn. | ||
| - No changes required to existing browser implementations. | ||
| - Full control remains with the developer. | ||
|
|
||
| ### Cons of Alternate Approach | ||
|
|
||
| - Inconsistent with native platform behavior, frustrating users. | ||
| - Places burden on developers to implement modifier key detection manually. | ||
| - Difficult for developers to match native file manager behavior accurately. | ||
| - Results in poor user experience when dragging between web and native applications. | ||
|
|
||
| ## Accessibility, Privacy, and Security Considerations | ||
|
|
||
| This proposal has no known negative impact on accessibility, privacy, or security. | ||
|
|
||
| **Accessibility**: This change improves accessibility by honoring standard platform conventions that users with motor impairments may rely on. Modifier keys provide an alternative to visual cues for indicating drag operation type. | ||
|
|
||
| **Privacy**: No additional data is collected or exposed. Modifier key state is already accessible to web applications through keyboard events. | ||
|
|
||
| **Security**: The proposal does not alter the security model of the Drag and Drop API. All existing security restrictions (e.g., cross-origin data access limitations) remain in effect. | ||
|
|
||
| ## 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) | ||
|
|
||
| Many thanks for valuable feedback and advice from: | ||
| - [Rohan Raja](https://github.com/roraja) | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please be specific where are we talking about the computing