Files
openidea/src/platform/doubleClick.ts
T
l0ng-ai 329ad2c644 fix(ui): treat every second click of a burst as a double-click
WebKit fires `dblclick` only for the second click of a burst. A quick
double-click right after another arrives as `click` with `detail` 3 and 4 and
no `dblclick`, so the second double-click on a folder in the Project view (and
on any tree row, tab or list item handled by `onDoubleClick`) did nothing.

A window-level shim now dispatches a `dblclick` on the click's target for every
later even click of a burst (detail 4, 6, ...), after the page's own click
handlers. Existing `onDoubleClick` handlers and their `stopPropagation` apply
unchanged; an engine that fires its own `dblclick` for that click gets it
dropped as a duplicate.

The Project view test now delivers bursts the way WebKit does (`dblclick` only
at detail 2) and checks both expand modes over 2–6 click bursts.

Refs #724
2026-11-03 22:34:29 +08:00

56 lines
2.0 KiB
TypeScript

/**
* WebKit (the macOS webview) fires `dblclick` only for the second click of a burst: a
* quick double-click right after another arrives as `click` with `detail` 3 and 4 and no
* `dblclick`, so the second double-click on a folder, tab or row did nothing. This turns
* every later even click of a burst into a `dblclick` on the same target, so React
* `onDoubleClick` handlers (and their `stopPropagation`) see each double-click. An engine
* that does fire its own `dblclick` for that click gets it suppressed as a duplicate.
*/
const synthetic = new WeakSet<Event>();
export function installRepeatedDoubleClick(win: Window = window): () => void {
let last: { target: EventTarget; detail: number } | null = null;
// Bubble phase on the window: after the page's own click handlers, like the engine's dblclick.
const onClick = (e: MouseEvent) => {
last = null;
if (e.button !== 0 || e.detail < 4 || e.detail % 2 !== 0 || !e.target) return;
const dbl = new MouseEvent("dblclick", {
bubbles: true,
cancelable: true,
composed: true,
view: e.view,
detail: e.detail,
screenX: e.screenX,
screenY: e.screenY,
clientX: e.clientX,
clientY: e.clientY,
button: e.button,
buttons: e.buttons,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
altKey: e.altKey,
metaKey: e.metaKey,
});
synthetic.add(dbl);
last = { target: e.target, detail: e.detail };
e.target.dispatchEvent(dbl);
};
const onDoubleClick = (e: MouseEvent) => {
if (synthetic.has(e) || !last) return;
const duplicate = e.detail === last.detail && e.target === last.target;
last = null;
if (!duplicate) return;
e.stopImmediatePropagation();
e.preventDefault();
};
win.addEventListener("click", onClick);
win.addEventListener("dblclick", onDoubleClick, true);
return () => {
win.removeEventListener("click", onClick);
win.removeEventListener("dblclick", onDoubleClick, true);
};
}