дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
On macOS, with an editable element focused and an input method active, WebKit can deliver a tap as pointerup then pointerdown, so drag trackers saw every later pointermove as a held button: one tap plus a pointer move started a phantom drag (file drag label, tab detach, tool window move) and the next tap dropped it. A drag now starts only from a move with the primary button down, a move without it cancels the press, and late button-less moves after a drag started are ignored so the drop is kept. Resizers, dialog drag/resize, pan and the review gutter end instead; the Project view also listens for pointercancel. Refs #760 Co-authored-by: ntbang0901 <[email protected]>
63 рядки
2.4 KiB
TypeScript
63 рядки
2.4 KiB
TypeScript
/**
|
|
* Pan/zoom for imperatively injected content (Mermaid output in the Markdown
|
|
* preview). The preview scrolls, so only ⌘/Ctrl+wheel (and pinch) zooms; drag
|
|
* pans once zoomed in, double-click resets. Listeners live as long as `viewport`.
|
|
*/
|
|
import { clampZoom, zoomAt, type View } from "./imageZoom";
|
|
|
|
const IDENTITY: View = { z: 1, x: 0, y: 0 };
|
|
|
|
export function attachPanZoom(viewport: HTMLElement): void {
|
|
const pan = document.createElement("div");
|
|
pan.className = "pan-zoom-content";
|
|
pan.style.transformOrigin = "0 0";
|
|
pan.append(...viewport.childNodes);
|
|
viewport.append(pan);
|
|
|
|
let view = IDENTITY;
|
|
let drag: { id: number; x: number; y: number } | null = null;
|
|
|
|
const apply = (next: View) => {
|
|
if (next.z === 1 && view.z === 1) return;
|
|
// Lock the height on first zoom so the page doesn't reflow while the content scales.
|
|
if (view.z === 1 && !viewport.style.height) viewport.style.height = `${viewport.offsetHeight}px`;
|
|
view = next;
|
|
pan.style.transform =
|
|
next.z === 1 && !next.x && !next.y ? "" : `translate(${next.x}px, ${next.y}px) scale(${next.z})`;
|
|
viewport.dataset.zoomed = next.z === 1 ? "" : "true";
|
|
if (next.z === 1 && !next.x && !next.y) viewport.style.height = "";
|
|
};
|
|
|
|
viewport.addEventListener(
|
|
"wheel",
|
|
(e) => {
|
|
if (!(e.metaKey || e.ctrlKey) || !e.deltaY) return;
|
|
e.preventDefault();
|
|
const rect = viewport.getBoundingClientRect();
|
|
const factor = Math.exp(-e.deltaY * (e.deltaMode === 0 ? 0.01 : 0.33));
|
|
apply(zoomAt(view, clampZoom(view.z * factor), e.clientX - rect.left, e.clientY - rect.top));
|
|
},
|
|
{ passive: false },
|
|
);
|
|
viewport.addEventListener("pointerdown", (e) => {
|
|
if (e.button !== 0 || view.z <= 1) return;
|
|
viewport.setPointerCapture(e.pointerId);
|
|
drag = { id: e.pointerId, x: e.clientX, y: e.clientY };
|
|
viewport.dataset.dragging = "true";
|
|
});
|
|
viewport.addEventListener("pointermove", (e) => {
|
|
if (!drag || drag.id !== e.pointerId) return;
|
|
if (!(e.buttons & 1)) return end(e);
|
|
apply({ ...view, x: view.x + e.clientX - drag.x, y: view.y + e.clientY - drag.y });
|
|
drag = { ...drag, x: e.clientX, y: e.clientY };
|
|
});
|
|
const end = (e: PointerEvent) => {
|
|
if (drag?.id !== e.pointerId) return;
|
|
drag = null;
|
|
delete viewport.dataset.dragging;
|
|
};
|
|
viewport.addEventListener("pointerup", end);
|
|
viewport.addEventListener("pointercancel", end);
|
|
viewport.addEventListener("dblclick", () => apply(IDENTITY));
|
|
}
|