213 lines
7.9 KiB
TypeScript
213 lines
7.9 KiB
TypeScript
import { onMounted, onBeforeUnmount, type Ref, watch } from 'vue'
|
|
|
|
interface ResizeHandle {
|
|
x: number
|
|
y: number
|
|
}
|
|
|
|
interface UseResizeAndDragOptions {
|
|
onResize?: (width: number, height: number) => void
|
|
onDrag?: (left: number, top: number) => void
|
|
left?: number
|
|
top?: number
|
|
width?: number
|
|
height?: number
|
|
}
|
|
|
|
export function useResizeAndDrag(
|
|
elementRef: Ref<HTMLElement | undefined>,
|
|
resizeHandleRef: Ref<HTMLElement | undefined>,
|
|
dragHandleRef: Ref<HTMLElement | undefined>,
|
|
options?: UseResizeAndDragOptions
|
|
) {
|
|
const resizeHandle: ResizeHandle = { x: 0, y: 0 }
|
|
let startX = 0
|
|
let startY = 0
|
|
let startWidth = typeof options?.width === 'number' ? options.width : 0
|
|
let startHeight = typeof options?.height === 'number' ? options.height : 0
|
|
let startLeft = typeof options?.left === 'number' ? options.left : 0
|
|
let startTop = typeof options?.top === 'number' ? options.top : 0
|
|
let isDragging = false
|
|
|
|
const handleResizeMouseDown = (e: MouseEvent | TouchEvent) => {
|
|
e.stopPropagation()
|
|
e.preventDefault()
|
|
if (!elementRef.value || !resizeHandleRef.value) return
|
|
|
|
startX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX
|
|
startY = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY
|
|
startWidth = elementRef.value.offsetWidth
|
|
startHeight = elementRef.value.offsetHeight
|
|
resizeHandle.x = resizeHandleRef.value.offsetLeft
|
|
resizeHandle.y = resizeHandleRef.value.offsetTop
|
|
|
|
document.documentElement.addEventListener('mousemove', handleResizeMouseMove)
|
|
document.documentElement.addEventListener('touchmove', handleResizeMouseMove)
|
|
document.documentElement.addEventListener('mouseup', handleResizeMouseUp)
|
|
document.documentElement.addEventListener('touchend', handleResizeMouseUp)
|
|
}
|
|
|
|
const handleResizeMouseMove = (e: MouseEvent | TouchEvent) => {
|
|
if (!elementRef.value || !resizeHandleRef.value) return
|
|
|
|
let width = startWidth + ((e instanceof MouseEvent ? e.clientX : e.touches[0].clientX) - startX)
|
|
let height =
|
|
startHeight + ((e instanceof MouseEvent ? e.clientY : e.touches[0].clientY) - startY)
|
|
|
|
// Check if element exceeds viewport width
|
|
if (elementRef.value.offsetLeft + width > window.innerWidth) {
|
|
width = window.innerWidth - elementRef.value.offsetLeft
|
|
}
|
|
|
|
// Check if element exceeds viewport height
|
|
if (elementRef.value.offsetTop + height > window.innerHeight) {
|
|
height = window.innerHeight - elementRef.value.offsetTop
|
|
}
|
|
|
|
elementRef.value.style.width = `${width}px`
|
|
elementRef.value.style.height = `${height}px`
|
|
|
|
if (options?.onResize) {
|
|
options.onResize(width, height)
|
|
}
|
|
}
|
|
|
|
const handleResizeMouseUp = () => {
|
|
document.documentElement.removeEventListener('mousemove', handleResizeMouseMove)
|
|
document.documentElement.removeEventListener('touchmove', handleResizeMouseMove)
|
|
document.documentElement.removeEventListener('mouseup', handleResizeMouseUp)
|
|
document.documentElement.removeEventListener('touchend', handleResizeMouseUp)
|
|
}
|
|
|
|
const handleDragMouseDown = (e: MouseEvent | TouchEvent) => {
|
|
e.stopPropagation()
|
|
e.preventDefault()
|
|
if (!elementRef.value || !dragHandleRef.value) return
|
|
isDragging = true
|
|
startLeft = elementRef.value.offsetLeft
|
|
startTop = elementRef.value.offsetTop
|
|
startX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX
|
|
startY = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY
|
|
document.documentElement.addEventListener('mousemove', handleDragMouseMove)
|
|
document.documentElement.addEventListener('touchmove', handleDragMouseMove)
|
|
document.documentElement.addEventListener('mouseup', handleDragMouseUp)
|
|
document.documentElement.addEventListener('touchend', handleDragMouseUp)
|
|
}
|
|
|
|
const handleDragMouseMove = (e: MouseEvent | TouchEvent) => {
|
|
if (!elementRef.value || !dragHandleRef.value || !isDragging) return
|
|
const left = startLeft + ((e instanceof MouseEvent ? e.clientX : e.touches[0].clientX) - startX)
|
|
const top = startTop + ((e instanceof MouseEvent ? e.clientY : e.touches[0].clientY) - startY)
|
|
|
|
// Check if element exceeds viewport width
|
|
if (left < 0) {
|
|
elementRef.value.style.left = '0px'
|
|
} else if (left + elementRef.value.offsetWidth > window.innerWidth) {
|
|
elementRef.value.style.left = `${window.innerWidth - elementRef.value.offsetWidth}px`
|
|
} else {
|
|
elementRef.value.style.left = `${left}px`
|
|
}
|
|
|
|
// Check if element exceeds viewport height
|
|
if (top < 0) {
|
|
elementRef.value.style.top = '0px'
|
|
} else if (top + elementRef.value.offsetHeight > window.innerHeight) {
|
|
elementRef.value.style.top = `${window.innerHeight - elementRef.value.offsetHeight}px`
|
|
} else {
|
|
elementRef.value.style.top = `${top}px`
|
|
}
|
|
|
|
if (options?.onDrag) {
|
|
options.onDrag(left, top)
|
|
}
|
|
}
|
|
|
|
const handleDragMouseUp = () => {
|
|
isDragging = false
|
|
document.documentElement.removeEventListener('mousemove', handleDragMouseMove)
|
|
document.documentElement.removeEventListener('touchmove', handleDragMouseMove)
|
|
document.documentElement.removeEventListener('mouseup', handleDragMouseUp)
|
|
document.documentElement.removeEventListener('touchend', handleDragMouseUp)
|
|
}
|
|
|
|
const handleWindowResize = () => {
|
|
if (!elementRef.value || !resizeHandleRef.value) return
|
|
let left = elementRef.value.offsetLeft
|
|
let top = elementRef.value.offsetTop
|
|
let width = elementRef.value.offsetWidth
|
|
let height = elementRef.value.offsetHeight
|
|
|
|
// Check if element exceeds viewport width
|
|
if (left + width > window.innerWidth) {
|
|
left = window.innerWidth - width
|
|
if (left < 0) {
|
|
left = 0
|
|
width = window.innerWidth
|
|
}
|
|
}
|
|
|
|
// Check if element exceeds viewport height
|
|
if (top + height > window.innerHeight) {
|
|
top = window.innerHeight - height
|
|
if (top < 0) {
|
|
top = 0
|
|
height = window.innerHeight
|
|
}
|
|
}
|
|
|
|
// Update element position and size
|
|
elementRef.value.style.left = `${left}px`
|
|
elementRef.value.style.top = `${top}px`
|
|
elementRef.value.style.width = `${width}px`
|
|
elementRef.value.style.height = `${height}px`
|
|
}
|
|
onMounted(() => {
|
|
if (!elementRef.value || !options) return
|
|
if (typeof options.width === 'number') {
|
|
elementRef.value.style.width = `${options.width}px`
|
|
}
|
|
if (typeof options.height === 'number') {
|
|
elementRef.value.style.height = `${options.height}px`
|
|
}
|
|
if (typeof options.left === 'number') {
|
|
elementRef.value.style.left = `${options.left}px`
|
|
}
|
|
if (typeof options.top === 'number') {
|
|
elementRef.value.style.top = `${options.top}px`
|
|
}
|
|
handleWindowResize()
|
|
window.addEventListener('resize', handleWindowResize)
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
document.documentElement.removeEventListener('mousemove', handleResizeMouseMove)
|
|
document.documentElement.removeEventListener('touchmove', handleResizeMouseMove)
|
|
document.documentElement.removeEventListener('mouseup', handleResizeMouseUp)
|
|
document.documentElement.removeEventListener('touchend', handleResizeMouseUp)
|
|
document.documentElement.removeEventListener('mousemove', handleDragMouseMove)
|
|
document.documentElement.removeEventListener('touchmove', handleDragMouseMove)
|
|
document.documentElement.removeEventListener('mouseup', handleDragMouseUp)
|
|
document.documentElement.removeEventListener('touchend', handleDragMouseUp)
|
|
window.removeEventListener('resize', handleWindowResize)
|
|
})
|
|
|
|
watch(
|
|
() => [elementRef.value, resizeHandleRef.value, dragHandleRef.value],
|
|
([element, resizeHandle, dragHandle]) => {
|
|
if (element && resizeHandle) {
|
|
resizeHandle.addEventListener('mousedown', handleResizeMouseDown)
|
|
resizeHandle.addEventListener('touchstart', handleResizeMouseDown)
|
|
}
|
|
if (element && dragHandle) {
|
|
dragHandle.addEventListener('mousedown', handleDragMouseDown)
|
|
dragHandle.addEventListener('touchstart', handleDragMouseDown)
|
|
}
|
|
}
|
|
)
|
|
|
|
return {
|
|
handleResizeMouseDown,
|
|
handleDragMouseDown
|
|
}
|
|
}
|