From e5b00795c226c4d44f7b47257eb8982bd73b1025 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Sat, 28 Mar 2026 02:03:30 +0200 Subject: [PATCH] feat: cover more cases for data-tauri-drag=region="deep", add example for QA (#15164) --- crates/tauri/Cargo.toml | 4 + crates/tauri/src/window/scripts/drag.js | 70 ++-- examples/drag/README.md | 3 + examples/drag/index.html | 418 ++++++++++++++++++++++++ examples/drag/main.rs | 24 ++ examples/drag/tauri.conf.json | 35 ++ 6 files changed, 523 insertions(+), 31 deletions(-) create mode 100644 examples/drag/README.md create mode 100644 examples/drag/index.html create mode 100644 examples/drag/main.rs create mode 100644 examples/drag/tauri.conf.json diff --git a/crates/tauri/Cargo.toml b/crates/tauri/Cargo.toml index 071076fa4..874b986aa 100644 --- a/crates/tauri/Cargo.toml +++ b/crates/tauri/Cargo.toml @@ -240,6 +240,10 @@ path = "../../examples/commands/main.rs" name = "helloworld" path = "../../examples/helloworld/main.rs" +[[example]] +name = "drag" +path = "../../examples/drag/main.rs" + [[example]] name = "multiwebview" path = "../../examples/multiwebview/main.rs" diff --git a/crates/tauri/src/window/scripts/drag.js b/crates/tauri/src/window/scripts/drag.js index 2b533549a..5a0b1244f 100644 --- a/crates/tauri/src/window/scripts/drag.js +++ b/crates/tauri/src/window/scripts/drag.js @@ -9,53 +9,61 @@ // moves after the double click, it should be cancelled (see https://github.com/tauri-apps/tauri/issues/8306) //-----------------------// const TAURI_DRAG_REGION_ATTR = 'data-tauri-drag-region' + const CLICKABLE_TAGS = new Set([ + 'A', + 'BUTTON', + 'INPUT', + 'SELECT', + 'TEXTAREA', + 'LABEL', + 'SUMMARY' + ]) + const INTERACTIVE_ROLES = new Set([ + 'button', + 'link', + 'menuitem', + 'tab', + 'checkbox', + 'radio', + 'switch', + 'option' + ]) function isClickableElement(el) { - const tag = el.tagName && el.tagName.toLowerCase() - return ( - tag === 'a' - || tag === 'button' - || tag === 'input' - || tag === 'select' - || tag === 'textarea' - || tag === 'label' - || tag === 'summary' + CLICKABLE_TAGS.has(el.tagName) || (el.hasAttribute('contenteditable') && el.getAttribute('contenteditable') !== 'false') || (el.hasAttribute('tabindex') && el.getAttribute('tabindex') !== '-1') + || INTERACTIVE_ROLES.has(el.getAttribute('role')) ) } - // Walk the composed path from target upward. If a clickable element or a - // data-tauri-drag-region="false" element is encountered, return false (don't drag). - // Otherwise return true. + // Walk the composed path from target upward. // // Supported values for data-tauri-drag-region: - // (bare / no value) -> self: only direct clicks on this element trigger drag - // "deep" -> deep: clicks anywhere in the subtree trigger drag - // "false" -> disabled: drag is blocked here (and for ancestors) + // (bare / no value / "true") -> self: only direct clicks on this element trigger drag + // "deep" -> deep: clicks anywhere in the subtree trigger drag + // "false" -> disabled: drag is blocked here (and for ancestors) + // + // Clickable elements (buttons, links, etc.) normally block dragging, + // but if they themselves carry data-tauri-drag-region they act as drag regions. function isDragRegion(composedPath) { for (const el of composedPath) { if (!(el instanceof HTMLElement)) continue - // if we hit a clickable element or a disabled drag region, don't drag - if ( - isClickableElement(el) - || el.getAttribute(TAURI_DRAG_REGION_ATTR) === 'false' - ) { - return false - } - const attr = el.getAttribute(TAURI_DRAG_REGION_ATTR) - if (attr !== null) { - // deep: the whole subtree is a drag region - if (attr === 'deep') return true - // bare (or any unrecognized value): self-only - if (el === composedPath[0]) return true - // click was on a child of a self-only region - stop walking, don't drag - return false - } + + // clickable without explicit drag region → blocks drag + if (isClickableElement(el) && attr === null) return false + // no attr → keep walking up + if (attr === null) continue + // explicitly disabled + if (attr === 'false') return false + // subtree drag — any descendant triggers + if (attr === 'deep') return true + // bare or "true" attr — only direct clicks on this element + if (attr === '' || attr === 'true') return el === composedPath[0] } return false diff --git a/examples/drag/README.md b/examples/drag/README.md new file mode 100644 index 000000000..593d5753c --- /dev/null +++ b/examples/drag/README.md @@ -0,0 +1,3 @@ +# Drag Window Example + +To execute run the following on the root directory of the repository: `cargo run --example drag`. diff --git a/examples/drag/index.html b/examples/drag/index.html new file mode 100644 index 000000000..49208b182 --- /dev/null +++ b/examples/drag/index.html @@ -0,0 +1,418 @@ + + + + + + + + + +

Drag.js Paths & Configuration - QA Test Suite

+ +
+

Configuration Reference

+
+ Attribute: data-tauri-drag-region +
+
+ +
+

Drag Region Values

+
bare / no value → self (direct clicks only)
+
"deep" → deep (subtree clicks)
+
"false" → disabled (drag blocked)
+
+ +
+

QA Test Cases - Try Dragging Each Section

+ +
+
+ 1. No Attribute (Default - Should NOT Drag) +
+
+ No data-tauri-drag-region attribute +
+
+ +
+
+ 2. Bare Attribute (Self Only - Should Drag Direct Click) +
+
+ data-tauri-drag-region (bare) +
+
+ +
+
+ 3. Deep Attribute (Should Drag from Subtree) +
+
+ data-tauri-drag-region="deep" +
+ Nested content - should also drag from here +
+
+
+ +
+
4. False Attribute (Drag Disabled)
+
+ data-tauri-drag-region="false" (drag disabled) +
+
+ +
+
+ 5. Drag Region with Clickable Element (Button Blocks Drag) +
+
+ data-tauri-drag-region with button inside + +
+
+ +
+
+ 6. Drag Region with Input (Input Blocks Drag) +
+
+ data-tauri-drag-region with input inside + +
+
+ +
+
+ 7. Deep Drag Region with Clickable Elements +
+
+ data-tauri-drag-region="deep" with clickables +
+ + Link (Blocks) +
+
+
+ +
+
+ 8. Deep Drag Region with Non-Clickable Text +
+
+ data-tauri-drag-region="deep" with text +
+ Just plain text - should drag from here +
+
+
+ +
+
9. Nested: False Blocks Parent Drag
+
+ data-tauri-drag-region="deep" parent +
+ data-tauri-drag-region="false" child (blocks parent drag) +
+
+
+ +
+
+ 10. Double Click to Maximize (Test Platform Behavior) +
+
+ Double-click to test maximize (platform-dependent) +
+
+ macOS: Maximize on mouseup if cursor unmoved | Windows/Linux: Maximize + on double-click +
+
+ +
+
+ 11. Deep Drag Region with ARIA Roles (Role Elements Should Block Drag) +
+
+ data-tauri-drag-region="deep" with role-based elements +
+
role="button" (Blocks)
+
role="link" (Blocks)
+ +
Plain text between role boxes (Drags)
+
+
+
+ +
+
+ 12. Clickable Element with Bare Drag Region (Button Should Drag on + Direct Click) +
+
+ + + Link with data-tauri-drag-region (Should Drag) + +
+
+ +
+
+ 13. Clickable Element with Deep Drag Region (Should Drag from + Children) +
+ +
+ +
+
+ 14. Clickable Element with False Drag Region (Should NOT Drag) +
+ +
+ +
+
+ 15. ARIA Role Element with Drag Region (Should Drag) +
+
+
+ role="button" with data-tauri-drag-region (Should Drag) +
+ +
+
+ +
+
+ 16. Bare Drag Region with ARIA Role Child (Role Child Should Block + Drag) +
+
+ data-tauri-drag-region (bare) parent +
+ +
role="switch" (Blocks)
+
role="option" (Blocks)
+
+
+
+
+ +
+

Clickable Tags (Prevent Drag)

+

+    
+ +
+

Interactive Roles (Prevent Drag)

+

+    
+ +
+

Window Commands

+
+ Command: plugin:window|start_dragging +
+
+ Command: plugin:window|internal_toggle_maximize +
+
+ + + + diff --git a/examples/drag/main.rs b/examples/drag/main.rs new file mode 100644 index 000000000..3f913e593 --- /dev/null +++ b/examples/drag/main.rs @@ -0,0 +1,24 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + tauri::Builder::default() + .run(generate_context()) + .expect("error while running tauri application"); +} + +fn generate_context() -> tauri::Context { + let mut context = tauri::generate_context!("../../examples/drag/tauri.conf.json"); + for cmd in [ + "plugin:window|start_dragging", + "plugin:window|internal_toggle_maximize", + ] { + context + .runtime_authority_mut() + .__allow_command(cmd.to_string(), tauri_utils::acl::ExecutionContext::Local); + } + context +} diff --git a/examples/drag/tauri.conf.json b/examples/drag/tauri.conf.json new file mode 100644 index 000000000..ef823497a --- /dev/null +++ b/examples/drag/tauri.conf.json @@ -0,0 +1,35 @@ +{ + "$schema": "../../crates/tauri-schema-generator/schemas/config.schema.json", + "productName": "Hello World", + "version": "0.1.0", + "identifier": "com.tauri.helloworld", + "build": { + "frontendDist": ["index.html"] + }, + "app": { + "withGlobalTauri": true, + "windows": [ + { + "title": "Welcome to Tauri!", + "width": 800, + "height": 600, + "resizable": true, + "fullscreen": false + } + ], + "security": { + "csp": "default-src 'self'; connect-src ipc: http://ipc.localhost" + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "../.icons/32x32.png", + "../.icons/128x128.png", + "../.icons/128x128@2x.png", + "../.icons/icon.icns", + "../.icons/icon.ico" + ] + } +}