Initial commit
This commit is contained in:
96
src/composables/useDraggableModal.js
Normal file
96
src/composables/useDraggableModal.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import { onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
||||
import { clamp } from "../utils/format.js";
|
||||
|
||||
export function useDraggableModal(initialPosition = { x: 190, y: 140 }) {
|
||||
const modalRef = ref(null);
|
||||
const headerRef = ref(null);
|
||||
|
||||
const state = reactive({
|
||||
dragging: false,
|
||||
pointerId: null,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
x: initialPosition.x,
|
||||
y: initialPosition.y,
|
||||
});
|
||||
|
||||
const style = reactive({
|
||||
left: `${state.x}px`,
|
||||
top: `${state.y}px`,
|
||||
});
|
||||
|
||||
function clampToViewport() {
|
||||
const modal = modalRef.value;
|
||||
if (!modal) return;
|
||||
const rect = modal.getBoundingClientRect();
|
||||
const pad = 10;
|
||||
const maxLeft = Math.max(pad, window.innerWidth - rect.width - pad);
|
||||
const maxTop = Math.max(pad, window.innerHeight - rect.height - pad);
|
||||
state.x = clamp(parseFloat(style.left), pad, maxLeft);
|
||||
state.y = clamp(parseFloat(style.top), pad, maxTop);
|
||||
style.left = `${state.x}px`;
|
||||
style.top = `${state.y}px`;
|
||||
}
|
||||
|
||||
function onHeaderPointerDown(e, closeSelector = ".close-btn") {
|
||||
if (e.target.closest(closeSelector) || e.button !== 0) return;
|
||||
const modal = modalRef.value;
|
||||
if (!modal) return;
|
||||
state.dragging = true;
|
||||
state.pointerId = e.pointerId;
|
||||
headerRef.value?.setPointerCapture?.(e.pointerId);
|
||||
const rect = modal.getBoundingClientRect();
|
||||
state.offsetX = e.clientX - rect.left;
|
||||
state.offsetY = e.clientY - rect.top;
|
||||
}
|
||||
|
||||
function onWindowPointerMove(e) {
|
||||
if (!state.dragging) return;
|
||||
if (state.pointerId != null && e.pointerId !== state.pointerId) return;
|
||||
const modal = modalRef.value;
|
||||
if (!modal) return;
|
||||
const rect = modal.getBoundingClientRect();
|
||||
const pad = 10;
|
||||
const x = clamp(e.clientX - state.offsetX, pad, Math.max(pad, window.innerWidth - rect.width - pad));
|
||||
const y = clamp(e.clientY - state.offsetY, pad, Math.max(pad, window.innerHeight - rect.height - pad));
|
||||
style.left = `${x}px`;
|
||||
style.top = `${y}px`;
|
||||
}
|
||||
|
||||
function onWindowPointerUp(e) {
|
||||
if (!state.dragging) return;
|
||||
if (state.pointerId != null && e.pointerId !== state.pointerId) return;
|
||||
state.dragging = false;
|
||||
state.pointerId = null;
|
||||
clampToViewport();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("pointermove", onWindowPointerMove);
|
||||
window.addEventListener("pointerup", onWindowPointerUp);
|
||||
window.addEventListener("pointercancel", onWindowPointerUp);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("pointermove", onWindowPointerMove);
|
||||
window.removeEventListener("pointerup", onWindowPointerUp);
|
||||
window.removeEventListener("pointercancel", onWindowPointerUp);
|
||||
});
|
||||
|
||||
function resetPosition(x = initialPosition.x, y = initialPosition.y) {
|
||||
state.x = x;
|
||||
state.y = y;
|
||||
style.left = `${x}px`;
|
||||
style.top = `${y}px`;
|
||||
}
|
||||
|
||||
return {
|
||||
modalRef,
|
||||
headerRef,
|
||||
state,
|
||||
style,
|
||||
onHeaderPointerDown,
|
||||
resetPosition,
|
||||
clampToViewport,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user