@@ -208,16 +208,28 @@ export function SettingsWindow() {
208208 } , [ ] ) ;
209209
210210 /**
211- * Native window drag from any non-interactive surface — mirrors the
212- * chat overlay's `handleDragStart` in App.tsx. Walks up the DOM from
213- * the click target and bails if it hits a form control or button so
214- * those keep working; otherwise calls `startDragging()`. We do this
215- * via JS instead of `data-tauri-drag-region` because the attribute
216- * only initiates drag from the element it's set on (and form
217- * children inside the body block the attribute from working there).
211+ * Native window drag from non-interactive, non-text surfaces. Walks
212+ * up the DOM and bails on:
213+ * 1. Interactive tags (form controls, buttons, links, SVGs) so
214+ * clicks on them still register as clicks, not drags.
215+ * 2. Text-bearing leaves — any element that directly contains a
216+ * non-empty text node. This lets users click-drag to highlight
217+ * labels, values, and descriptions inside the body, then Cmd+C
218+ * to copy. Without this check the whole window would slide
219+ * under the cursor and the selection would never start.
220+ *
221+ * We do this via JS instead of `data-tauri-drag-region` because the
222+ * attribute only initiates drag from the element it's set on, and
223+ * form children inside the body block it from working at the root.
224+ *
225+ * Only the primary mouse button initiates a drag; secondary/middle
226+ * clicks pass through so context menus and middle-click behaviors
227+ * are unaffected.
218228 */
219229 const handleDragStart = useCallback ( ( e : React . MouseEvent ) => {
220- const el = e . target as HTMLElement | null ;
230+ if ( e . button !== 0 ) return ;
231+ const el = e . target as HTMLElement ;
232+
221233 const INTERACTIVE_TAGS = new Set ( [
222234 'TEXTAREA' ,
223235 'INPUT' ,
@@ -228,11 +240,24 @@ export function SettingsWindow() {
228240 'SVG' ,
229241 'LABEL' ,
230242 ] ) ;
231- let current = el ;
243+ let current : HTMLElement | null = el ;
232244 while ( current ) {
233245 if ( INTERACTIVE_TAGS . has ( current . tagName . toUpperCase ( ) ) ) return ;
234246 current = current . parentElement ;
235247 }
248+
249+ // Bail if the click landed directly on a text node. Layout
250+ // wrappers (DIV/SECTION) without their own text still drag.
251+ for ( const node of Array . from ( el . childNodes ) ) {
252+ if (
253+ node . nodeType === Node . TEXT_NODE &&
254+ node . textContent &&
255+ node . textContent . trim ( ) . length > 0
256+ ) {
257+ return ;
258+ }
259+ }
260+
236261 e . preventDefault ( ) ;
237262 void getCurrentWindow ( ) . startDragging ( ) ;
238263 } , [ ] ) ;
0 commit comments