mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
fix(linux): stop Wayland GTK drag proxy and PsyDnD ghost (#268)
Block capture-phase dragstart in the webview so stray native HTML5 drags (e.g. SVG reorder grips) do not create a stuck translucent GTK surface on Wayland compositors. Harden PsyDnD lifecycle when mouseup never reaches the webview: blur, visibilitychange, pointerup/cancel in capture, and Escape cancel the floating label without dispatching a drop. Add -webkit-user-drag:none on sidebar customizer grips.
This commit is contained in:
+11
-7
@@ -324,13 +324,11 @@ function AppShell() {
|
||||
};
|
||||
}, [isDraggingQueue, handleMouseMove, handleMouseUp]);
|
||||
|
||||
// ── Global DnD fix for Linux/WebKitGTK ──────────────────────────
|
||||
// WebKitGTK (used by Tauri on Linux) requires the document itself to
|
||||
// accept drags via preventDefault() on dragover/dragenter. Without
|
||||
// this, the webview shows a "forbidden" cursor for all in-app HTML5
|
||||
// drag-and-drop because it never sees a valid drop target at the
|
||||
// document level. This is harmless on Windows/macOS where DnD already
|
||||
// works correctly.
|
||||
// ── Global DnD fix for Linux/WebKitGTK / Wayland ─────────────────
|
||||
// dragover/dragenter: WebKitGTK needs preventDefault so external drops are not
|
||||
// a permanent "forbidden" cursor. dragstart (capture): cancel native drags from
|
||||
// the page (e.g. SVG grips); Wayland can otherwise leave a stuck GTK drag-proxy.
|
||||
// In-app moves use psy-drag (mouse events). Harmless on Windows/macOS.
|
||||
useEffect(() => {
|
||||
const allow = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -358,9 +356,14 @@ function AppShell() {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const blockDragStart = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
document.addEventListener('dragover', allow);
|
||||
document.addEventListener('dragenter', allow);
|
||||
document.addEventListener('drop', blockDrop);
|
||||
document.addEventListener('dragstart', blockDragStart, true);
|
||||
document.addEventListener('keydown', blockSelectAll, true);
|
||||
document.addEventListener('selectstart', blockSelectStart);
|
||||
|
||||
@@ -368,6 +371,7 @@ function AppShell() {
|
||||
document.removeEventListener('dragover', allow);
|
||||
document.removeEventListener('dragenter', allow);
|
||||
document.removeEventListener('drop', blockDrop);
|
||||
document.removeEventListener('dragstart', blockDragStart, true);
|
||||
document.removeEventListener('keydown', blockSelectAll, true);
|
||||
document.removeEventListener('selectstart', blockSelectStart);
|
||||
};
|
||||
|
||||
@@ -128,6 +128,8 @@ export function DragDropProvider({ children }: { children: React.ReactNode }) {
|
||||
useEffect(() => {
|
||||
if (!state.payload) return;
|
||||
|
||||
let ended = false;
|
||||
|
||||
const onMove = (e: MouseEvent) => {
|
||||
// preventDefault stops the browser from treating the mouse movement as
|
||||
// a text-selection drag, which causes element highlighting and
|
||||
@@ -136,38 +138,63 @@ export function DragDropProvider({ children }: { children: React.ReactNode }) {
|
||||
setState((prev) => ({ ...prev, position: { x: e.clientX, y: e.clientY } }));
|
||||
};
|
||||
|
||||
const onUp = () => {
|
||||
// Clear any residual selection (from the pre-threshold phase).
|
||||
/** End drag; optionally fire `psy-drop` at the last known cursor position. */
|
||||
const endDrag = (dispatchDrop: boolean) => {
|
||||
if (ended || !stateRef.current.payload) return;
|
||||
ended = true;
|
||||
|
||||
window.getSelection()?.removeAllRanges();
|
||||
|
||||
// Dispatch a custom event so drop targets can react.
|
||||
// The payload is in `detail`.
|
||||
const evt = new CustomEvent('psy-drop', {
|
||||
bubbles: true,
|
||||
detail: stateRef.current.payload,
|
||||
});
|
||||
// Find element under cursor
|
||||
const el = document.elementFromPoint(
|
||||
stateRef.current.position.x,
|
||||
stateRef.current.position.y,
|
||||
);
|
||||
if (el) el.dispatchEvent(evt);
|
||||
if (dispatchDrop) {
|
||||
const evt = new CustomEvent('psy-drop', {
|
||||
bubbles: true,
|
||||
detail: stateRef.current.payload,
|
||||
});
|
||||
const el = document.elementFromPoint(
|
||||
stateRef.current.position.x,
|
||||
stateRef.current.position.y,
|
||||
);
|
||||
if (el) el.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
setState({ payload: null, position: { x: 0, y: 0 } });
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', onMove, { passive: false });
|
||||
document.addEventListener('mouseup', onUp);
|
||||
const onUp = () => endDrag(true);
|
||||
/** Wayland: webview may not get `mouseup` when the pointer leaves the surface — clear the ghost without a drop. */
|
||||
const onBlur = () => endDrag(false);
|
||||
const onVisibility = () => {
|
||||
if (document.hidden) endDrag(false);
|
||||
};
|
||||
const onPointerCancel = () => endDrag(false);
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
endDrag(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', onMove, { passive: false });
|
||||
document.addEventListener('mouseup', onUp, true);
|
||||
document.addEventListener('pointerup', onUp, true);
|
||||
document.addEventListener('pointercancel', onPointerCancel, true);
|
||||
window.addEventListener('blur', onBlur);
|
||||
document.addEventListener('visibilitychange', onVisibility);
|
||||
document.addEventListener('keydown', onKeyDown, true);
|
||||
|
||||
// Add a class so CSS can show grab cursor and suppress selection
|
||||
document.body.classList.add('psy-dragging');
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
document.removeEventListener('mouseup', onUp, true);
|
||||
document.removeEventListener('pointerup', onUp, true);
|
||||
document.removeEventListener('pointercancel', onPointerCancel, true);
|
||||
window.removeEventListener('blur', onBlur);
|
||||
document.removeEventListener('visibilitychange', onVisibility);
|
||||
document.removeEventListener('keydown', onKeyDown, true);
|
||||
document.body.classList.remove('psy-dragging');
|
||||
};
|
||||
}, [state.payload !== null]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, [state.payload]);
|
||||
|
||||
const ctxValue: DragDropContextValue = {
|
||||
startDrag,
|
||||
|
||||
@@ -3232,6 +3232,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
.sidebar-customizer-grip:active {
|
||||
|
||||
Reference in New Issue
Block a user