Files
odysseus/static/js/editor/draggable-dialog.js
T
pewdiepie-archdaemonandClaude Opus 5.5 5fac693c1c feat(photo-editor): Ajax Image Editor client (Photoshop-parity tools)
Photoshop-parity editor client: layers, selection/masking, brushes,
smart filters, LUT import, Quick Edit, mobile gestures, workspaces,
batch operations, and the service-worker versioning (v978->v1006).

Source: vendor/photocraft (ad1cff44) photo-editor client files, merged
onto upstream/dev (a8c147b2). Conflict resolution documented in
pr_work/A_conflicts.md; editor files taken from vendor/photocraft
(upstream's editor is an earlier snapshot of the same feature).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:57:35 +00:00

73 lines
2.5 KiB
JavaScript

// Makes a modal dialog draggable by its header, Photoshop-style.
//
// On the first grab the dialog is switched to `position:fixed` anchored at
// its current on-screen box, so it follows the pointer independently of the
// host modal's centering/transform layout. Dragging is clamped to the
// viewport and ignores presses that start on interactive controls (buttons,
// inputs, selects) so clicking Cancel/Create never becomes a drag.
export function makeDraggable(dialog, handle) {
if (!dialog || !handle || dialog.__geDraggable) return;
dialog.__geDraggable = true;
handle.classList.add('ge-draggable-handle');
let startX = 0;
let startY = 0;
let originLeft = 0;
let originTop = 0;
let dragging = false;
const onDown = (event) => {
if (event.button != null && event.button !== 0) return;
if (event.target?.closest?.('button, input, select, textarea, a, [role="button"]')) return;
const rect = dialog.getBoundingClientRect();
dialog.style.position = 'fixed';
dialog.style.margin = '0';
dialog.style.left = `${rect.left}px`;
dialog.style.top = `${rect.top}px`;
dialog.style.right = 'auto';
dialog.style.bottom = 'auto';
dialog.style.transform = 'none';
startX = event.clientX;
startY = event.clientY;
originLeft = rect.left;
originTop = rect.top;
dragging = true;
handle.setPointerCapture?.(event.pointerId);
event.preventDefault();
};
const onMove = (event) => {
if (!dragging) return;
const width = dialog.offsetWidth;
const height = dialog.offsetHeight;
const left = Math.max(0, Math.min(window.innerWidth - width, originLeft + (event.clientX - startX)));
const top = Math.max(0, Math.min(window.innerHeight - height, originTop + (event.clientY - startY)));
dialog.style.left = `${left}px`;
dialog.style.top = `${top}px`;
};
const onUp = (event) => {
if (!dragging) return;
dragging = false;
handle.releasePointerCapture?.(event.pointerId);
};
handle.addEventListener('pointerdown', onDown);
handle.addEventListener('pointermove', onMove);
handle.addEventListener('pointerup', onUp);
handle.addEventListener('pointercancel', onUp);
}
// Clears the inline positioning set by makeDraggable so the next open
// re-centers the dialog inside its host modal.
export function resetDraggable(dialog) {
if (!dialog) return;
dialog.style.position = '';
dialog.style.margin = '';
dialog.style.left = '';
dialog.style.top = '';
dialog.style.right = '';
dialog.style.bottom = '';
dialog.style.transform = '';
}