fix(ui): selectstart blocker handles Text node event targets (#718)

* fix(ui): handle Text node targets in global selectstart blocker

selectstart can target a Text node without closest(); resolve to the
parent Element before checking inputs and data-selectable regions.

* docs(changelog): note PR #718 selectstart Text node fix

* fix(ui): narrow selectstart target to Node before parentNode

Satisfies tsc: EventTarget has no parentElement/parentNode until
narrowed with instanceof Node.
This commit is contained in:
cucadmuh
2026-05-15 16:21:38 +03:00
committed by GitHub
parent 45e0e1206f
commit e1283f4068
2 changed files with 30 additions and 5 deletions
+24 -5
View File
@@ -1,5 +1,20 @@
import { useEffect } from 'react';
/** `Event.target` may be a Text node — only Elements expose `closest` / `tagName`. */
function eventTargetElement(e: Event): Element | null {
const t = e.target;
if (!t) return null;
if (t instanceof Element) return t;
if (!(t instanceof Node)) return null;
const parent = t.parentNode;
return parent instanceof Element ? parent : null;
}
function isTextInputElement(el: Element): boolean {
const tag = el.tagName;
return tag === 'INPUT' || tag === 'TEXTAREA' || (el as HTMLElement).isContentEditable;
}
/**
* Globally tame Linux/WebKitGTK + Wayland behaviour that the page-level
* CSS / Tauri config can't reach:
@@ -29,16 +44,20 @@ export function useGlobalDndAndSelectionBlockers(): void {
const blockSelectAll = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) return;
const el = eventTargetElement(e);
if (el && isTextInputElement(el)) return;
e.preventDefault();
}
};
const blockSelectStart = (e: Event) => {
const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) return;
if ((target as HTMLElement).closest('[data-selectable]')) return;
const el = eventTargetElement(e);
if (!el) {
e.preventDefault();
return;
}
if (isTextInputElement(el)) return;
if (el.closest('[data-selectable]')) return;
e.preventDefault();
};