Modal resize
This commit is contained in:
parent
961bbf9ccb
commit
a61c7db33f
@ -7,10 +7,12 @@
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: `${position.x}px`,
|
left: `${position.x}px`,
|
||||||
top: `${position.y}px`,
|
top: `${position.y}px`,
|
||||||
|
width: `${size.width}px`,
|
||||||
|
height: `${size.height}px`,
|
||||||
}"
|
}"
|
||||||
class="bg-white rounded-2xl border-2 border-gray-300 shadow-xl max-w-4xl w-full max-h-[90vh] flex flex-col"
|
class="bg-white rounded-2xl border-2 border-gray-300 shadow-xl flex flex-col"
|
||||||
>
|
>
|
||||||
<!-- Header - Make it the drag handle -->
|
<!-- Header with drag handle -->
|
||||||
<div class="flex justify-between items-center p-4 border-b border-gray-100 cursor-move" @mousedown="startDrag" @touchstart="startDrag">
|
<div class="flex justify-between items-center p-4 border-b border-gray-100 cursor-move" @mousedown="startDrag" @touchstart="startDrag">
|
||||||
<h3 class="text-2xl font-semibold text-gray-900">{{ title }}</h3>
|
<h3 class="text-2xl font-semibold text-gray-900">{{ title }}</h3>
|
||||||
<button @click="close" class="p-2 hover:bg-gray-100 rounded-lg transition-colors">
|
<button @click="close" class="p-2 hover:bg-gray-100 rounded-lg transition-colors">
|
||||||
@ -24,6 +26,13 @@
|
|||||||
<div class="p-6 flex-1 overflow-auto">
|
<div class="p-6 flex-1 overflow-auto">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Resize handle -->
|
||||||
|
<div class="absolute bottom-0 right-0 w-4 h-4 cursor-se-resize" @mousedown="startResize" @touchstart="startResize">
|
||||||
|
<svg viewBox="0 0 24 24" class="w-4 h-4 text-gray-400">
|
||||||
|
<path fill="currentColor" d="M22 22H20V20H22V22ZM22 20H20V18H22V20ZM20 22H18V20H20V22ZM18 22H16V20H18V22Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@ -35,6 +44,8 @@
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
|
initialWidth?: number;
|
||||||
|
initialHeight?: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -43,119 +54,128 @@
|
|||||||
|
|
||||||
const modalRef = ref<HTMLElement | null>(null);
|
const modalRef = ref<HTMLElement | null>(null);
|
||||||
const position = ref({ x: 0, y: 0 });
|
const position = ref({ x: 0, y: 0 });
|
||||||
const isDragging = ref(false);
|
const size = ref({
|
||||||
const dragOffset = ref({ x: 0, y: 0 });
|
width: props.initialWidth || 800,
|
||||||
|
height: props.initialHeight || 600,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDragging = ref(false);
|
||||||
|
const isResizing = ref(false);
|
||||||
|
const startPos = ref({ x: 0, y: 0 });
|
||||||
|
const startSize = ref({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
// Unified start function for both drag and resize
|
||||||
|
const startAction = (event: MouseEvent | TouchEvent, action: 'drag' | 'resize') => {
|
||||||
|
event.preventDefault();
|
||||||
|
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
|
||||||
|
const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
|
||||||
|
|
||||||
|
if (action === 'drag') {
|
||||||
|
isDragging.value = true;
|
||||||
|
startPos.value = {
|
||||||
|
x: clientX - position.value.x,
|
||||||
|
y: clientY - position.value.y,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
isResizing.value = true;
|
||||||
|
startPos.value = { x: clientX, y: clientY };
|
||||||
|
startSize.value = { ...size.value };
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', handleMove);
|
||||||
|
document.addEventListener('touchmove', handleMove, { passive: false });
|
||||||
|
document.addEventListener('mouseup', stopAction);
|
||||||
|
document.addEventListener('touchend', stopAction);
|
||||||
|
};
|
||||||
|
|
||||||
|
const startDrag = (event: MouseEvent | TouchEvent) => startAction(event, 'drag');
|
||||||
|
const startResize = (event: MouseEvent | TouchEvent) => startAction(event, 'resize');
|
||||||
|
|
||||||
|
const handleMove = (event: MouseEvent | TouchEvent) => {
|
||||||
|
if (!isDragging.value && !isResizing.value) return;
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
|
||||||
|
const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
|
||||||
|
|
||||||
|
if (isDragging.value) {
|
||||||
|
const newX = clientX - startPos.value.x;
|
||||||
|
const newY = clientY - startPos.value.y;
|
||||||
|
position.value = constrainPosition(newX, newY);
|
||||||
|
} else if (isResizing.value) {
|
||||||
|
const deltaX = clientX - startPos.value.x;
|
||||||
|
const deltaY = clientY - startPos.value.y;
|
||||||
|
size.value = constrainSize(startSize.value.width + deltaX, startSize.value.height + deltaY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopAction = () => {
|
||||||
|
isDragging.value = false;
|
||||||
|
isResizing.value = false;
|
||||||
|
document.removeEventListener('mousemove', handleMove);
|
||||||
|
document.removeEventListener('touchmove', handleMove);
|
||||||
|
document.removeEventListener('mouseup', stopAction);
|
||||||
|
document.removeEventListener('touchend', stopAction);
|
||||||
|
};
|
||||||
|
|
||||||
|
const constrainPosition = (x: number, y: number) => {
|
||||||
|
if (!modalRef.value) return { x, y };
|
||||||
|
const modalRect = modalRef.value.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
x: Math.max(0, Math.min(x, window.innerWidth - modalRect.width)),
|
||||||
|
y: Math.max(0, Math.min(y, window.innerHeight - modalRect.height)),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const constrainSize = (width: number, height: number) => {
|
||||||
|
return {
|
||||||
|
width: Math.max(400, Math.min(width, window.innerWidth - position.value.x)),
|
||||||
|
height: Math.max(300, Math.min(height, window.innerHeight - position.value.y)),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Center the modal when it opens
|
|
||||||
const centerModal = () => {
|
const centerModal = () => {
|
||||||
if (!modalRef.value) return;
|
if (!modalRef.value) return;
|
||||||
|
|
||||||
const modalRect = modalRef.value.getBoundingClientRect();
|
|
||||||
const viewportWidth = window.innerWidth;
|
|
||||||
const viewportHeight = window.innerHeight;
|
|
||||||
|
|
||||||
position.value = {
|
position.value = {
|
||||||
x: (viewportWidth - modalRect.width) / 2,
|
x: (window.innerWidth - size.value.width) / 2,
|
||||||
y: (viewportHeight - modalRect.height) / 2,
|
y: (window.innerHeight - size.value.height) / 2,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const startDrag = (event: MouseEvent | TouchEvent) => {
|
|
||||||
if (!modalRef.value) return;
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
isDragging.value = true;
|
|
||||||
|
|
||||||
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
|
|
||||||
const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
|
|
||||||
|
|
||||||
dragOffset.value = {
|
|
||||||
x: clientX - position.value.x,
|
|
||||||
y: clientY - position.value.y,
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('mousemove', drag);
|
|
||||||
document.addEventListener('touchmove', drag, { passive: false });
|
|
||||||
document.addEventListener('mouseup', stopDrag);
|
|
||||||
document.addEventListener('touchend', stopDrag);
|
|
||||||
};
|
|
||||||
|
|
||||||
const drag = (event: MouseEvent | TouchEvent) => {
|
|
||||||
if (!isDragging.value || !modalRef.value) return;
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
|
|
||||||
const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
|
|
||||||
|
|
||||||
const modalRect = modalRef.value.getBoundingClientRect();
|
|
||||||
const viewportWidth = window.innerWidth;
|
|
||||||
const viewportHeight = window.innerHeight;
|
|
||||||
|
|
||||||
// Calculate new position
|
|
||||||
let newX = clientX - dragOffset.value.x;
|
|
||||||
let newY = clientY - dragOffset.value.y;
|
|
||||||
|
|
||||||
// Constrain to viewport bounds
|
|
||||||
newX = Math.max(0, Math.min(newX, viewportWidth - modalRect.width));
|
|
||||||
newY = Math.max(0, Math.min(newY, viewportHeight - modalRect.height));
|
|
||||||
|
|
||||||
position.value = { x: newX, y: newY };
|
|
||||||
};
|
|
||||||
|
|
||||||
const stopDrag = () => {
|
|
||||||
isDragging.value = false;
|
|
||||||
document.removeEventListener('mousemove', drag);
|
|
||||||
document.removeEventListener('touchmove', drag);
|
|
||||||
document.removeEventListener('mouseup', stopDrag);
|
|
||||||
document.removeEventListener('touchend', stopDrag);
|
|
||||||
};
|
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
emit('close');
|
emit('close');
|
||||||
position.value = { x: 0, y: 0 };
|
position.value = { x: 0, y: 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle ESC key to close modal
|
// Event handlers
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Escape' && props.isOpen) {
|
if (event.key === 'Escape' && props.isOpen) close();
|
||||||
close();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle window resize
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (!isDragging.value) {
|
if (!isDragging.value && !isResizing.value) {
|
||||||
centerModal();
|
position.value = constrainPosition(position.value.x, position.value.y);
|
||||||
|
size.value = constrainSize(size.value.width, size.value.height);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Watch for modal opening
|
// Lifecycle
|
||||||
watch(
|
watch(
|
||||||
() => props.isOpen,
|
() => props.isOpen,
|
||||||
newValue => {
|
newValue => {
|
||||||
if (newValue) {
|
if (newValue) nextTick(centerModal);
|
||||||
// Use nextTick to ensure the modal is mounted
|
|
||||||
nextTick(() => {
|
|
||||||
centerModal();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('keydown', handleKeyDown);
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
if (props.isOpen) {
|
if (props.isOpen) centerModal();
|
||||||
centerModal();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('keydown', handleKeyDown);
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
document.removeEventListener('mousemove', drag);
|
stopAction();
|
||||||
document.removeEventListener('touchmove', drag);
|
|
||||||
document.removeEventListener('mouseup', stopDrag);
|
|
||||||
document.removeEventListener('touchend', stopDrag);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user