diff --git a/src/components/utilities/Modal.vue b/src/components/utilities/Modal.vue index cc0cdfd..870546a 100644 --- a/src/components/utilities/Modal.vue +++ b/src/components/utilities/Modal.vue @@ -7,10 +7,12 @@ position: 'absolute', left: `${position.x}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" > - +

{{ title }}

@@ -35,6 +44,8 @@ const props = defineProps<{ isOpen: boolean; title: string; + initialWidth?: number; + initialHeight?: number; }>(); const emit = defineEmits<{ @@ -43,119 +54,128 @@ const modalRef = ref(null); const position = ref({ x: 0, y: 0 }); - const isDragging = ref(false); - const dragOffset = ref({ x: 0, y: 0 }); + const size = ref({ + 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 = () => { if (!modalRef.value) return; - - const modalRect = modalRef.value.getBoundingClientRect(); - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - position.value = { - x: (viewportWidth - modalRect.width) / 2, - y: (viewportHeight - modalRect.height) / 2, + x: (window.innerWidth - size.value.width) / 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 = () => { emit('close'); position.value = { x: 0, y: 0 }; }; - // Handle ESC key to close modal + // Event handlers const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape' && props.isOpen) { - close(); - } + if (event.key === 'Escape' && props.isOpen) close(); }; - // Handle window resize const handleResize = () => { - if (!isDragging.value) { - centerModal(); + if (!isDragging.value && !isResizing.value) { + position.value = constrainPosition(position.value.x, position.value.y); + size.value = constrainSize(size.value.width, size.value.height); } }; - // Watch for modal opening + // Lifecycle watch( () => props.isOpen, newValue => { - if (newValue) { - // Use nextTick to ensure the modal is mounted - nextTick(() => { - centerModal(); - }); - } + if (newValue) nextTick(centerModal); } ); onMounted(() => { document.addEventListener('keydown', handleKeyDown); window.addEventListener('resize', handleResize); - if (props.isOpen) { - centerModal(); - } + if (props.isOpen) centerModal(); }); onUnmounted(() => { document.removeEventListener('keydown', handleKeyDown); window.removeEventListener('resize', handleResize); - document.removeEventListener('mousemove', drag); - document.removeEventListener('touchmove', drag); - document.removeEventListener('mouseup', stopDrag); - document.removeEventListener('touchend', stopDrag); + stopAction(); });