mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +00:00
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:
@@ -498,6 +498,12 @@ Foundational work: faster reviews, narrower diffs, and a safety net under the pa
|
||||
* Opening **Now Playing** (sidebar, mobile route, or queue info panel) switches to the queue server before Subsonic metadata loads, with per-server fetch caches so artist/album cards do not show the wrong library.
|
||||
* **Scrobble**, **now-playing report**, and **savePlayQueue** sync use the queue server as well; removing that server profile clears `queueServerId`; the mini-player bridge receives `queueServerId`; enqueue/play-next from another browsed server is blocked with a toast.
|
||||
|
||||
### UI — selectstart blocker no longer throws on Text node targets
|
||||
|
||||
**By [@cucadmuh](https://github.com/cucadmuh), PR [#718](https://github.com/Psychotoxical/psysonic/pull/718)**
|
||||
|
||||
* The global **`selectstart`** handler assumed `event.target` was always an **`Element`**. Starting selection inside text (e.g. Now Playing **`[data-selectable]`** copy) could pass a **Text** node and crash with **`target.closest is not a function`**. The handler now resolves Text nodes to their parent element before applying allow/deny rules.
|
||||
|
||||
## [1.45.0] - 2026-05-04
|
||||
|
||||
## Added
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user