import type { ComputedRef, Ref } from 'vue';
|
|
import { onBeforeUnmount, ref, watch } from 'vue';
|
|
export function useModalResizable(
|
targetRef: Ref<HTMLElement | undefined>,
|
resizable: ComputedRef<boolean>,
|
containerSelector?: ComputedRef<string | undefined>,
|
) {
|
const resizing = ref(false);
|
const handleRef = ref<HTMLElement>();
|
|
function onMousedown(e: MouseEvent) {
|
e.preventDefault();
|
e.stopPropagation();
|
|
const target = targetRef.value;
|
if (!target) return;
|
|
const startX = e.clientX;
|
const startY = e.clientY;
|
const startWidth = target.offsetWidth;
|
const startHeight = target.offsetHeight;
|
const startLeft = target.getBoundingClientRect().left;
|
const startTop = target.getBoundingClientRect().top;
|
|
resizing.value = true;
|
document.body.style.cursor = 'nwse-resize';
|
document.body.style.userSelect = 'none';
|
|
const onMousemove = (e: MouseEvent) => {
|
const deltaX = e.clientX - startX;
|
const deltaY = e.clientY - startY;
|
|
let newWidth = Math.max(startWidth, startWidth + deltaX);
|
let newHeight = Math.max(startHeight, startHeight + deltaY);
|
|
if (containerSelector?.value) {
|
const container = document.querySelector(containerSelector.value);
|
if (container) {
|
const containerRect = container.getBoundingClientRect();
|
newWidth = Math.min(newWidth, containerRect.right - startLeft);
|
newHeight = Math.min(newHeight, containerRect.bottom - startTop);
|
}
|
} else {
|
const docElement = document.documentElement;
|
newWidth = Math.min(newWidth, docElement.clientWidth - startLeft);
|
newHeight = Math.min(newHeight, docElement.clientHeight - startTop);
|
}
|
|
target.style.width = `${newWidth}px`;
|
target.style.height = `${newHeight}px`;
|
|
const currentLeft = target.getBoundingClientRect().left;
|
const currentTop = target.getBoundingClientRect().top;
|
if (currentLeft !== startLeft || currentTop !== startTop) {
|
const offsetX = currentLeft - startLeft;
|
const offsetY = currentTop - startTop;
|
const transform = target.style.transform || '';
|
const matchX = transform.match(/translate\(([-\d.]+)px/);
|
const matchY = transform.match(/translate\([-\d.]+px,\s*([-\d.]+)px/);
|
const currentTranslateX = matchX && matchX[1] ? Number.parseFloat(matchX[1]) : 0;
|
const currentTranslateY = matchY && matchY[1] ? Number.parseFloat(matchY[1]) : 0;
|
const newTranslateX = currentTranslateX - offsetX;
|
const newTranslateY = currentTranslateY - offsetY;
|
target.style.transform = `translate(${newTranslateX}px, ${newTranslateY}px)`;
|
}
|
};
|
|
const onMouseup = () => {
|
resizing.value = false;
|
document.body.style.cursor = '';
|
document.body.style.userSelect = '';
|
document.removeEventListener('mousemove', onMousemove);
|
document.removeEventListener('mouseup', onMouseup);
|
};
|
|
document.addEventListener('mousemove', onMousemove);
|
document.addEventListener('mouseup', onMouseup);
|
}
|
|
function addHandle() {
|
const target = targetRef.value;
|
if (!target || handleRef.value) return;
|
|
const handle = document.createElement('div');
|
handle.className = 'modal-resize-handle';
|
Object.assign(handle.style, {
|
position: 'absolute',
|
right: '2px',
|
bottom: '2px',
|
width: '18px',
|
height: '18px',
|
cursor: 'nwse-resize',
|
zIndex: '10',
|
});
|
|
handle.addEventListener('mousedown', onMousedown);
|
target.append(handle);
|
handleRef.value = handle;
|
}
|
|
function removeHandle() {
|
if (handleRef.value) {
|
handleRef.value.removeEventListener('mousedown', onMousedown);
|
handleRef.value.remove();
|
handleRef.value = undefined;
|
}
|
}
|
|
function resetSize() {
|
const target = targetRef.value;
|
if (target) {
|
target.style.width = '';
|
target.style.height = '';
|
target.style.transform = '';
|
}
|
}
|
|
watch(
|
() => targetRef.value,
|
(target) => {
|
if (target && resizable.value) {
|
addHandle();
|
} else {
|
removeHandle();
|
}
|
},
|
{ immediate: true },
|
);
|
|
watch(resizable, (val) => {
|
if (val && targetRef.value) {
|
addHandle();
|
} else {
|
removeHandle();
|
}
|
});
|
|
onBeforeUnmount(() => {
|
removeHandle();
|
});
|
|
return {
|
resizing,
|
resetSize,
|
};
|
}
|