diff --git a/src/components/Main/NavigationBar/DesktopNavigationBar.vue b/src/components/Main/NavigationBar/DesktopNavigationBar.vue index 9152a673fa..151dac6627 100644 --- a/src/components/Main/NavigationBar/DesktopNavigationBar.vue +++ b/src/components/Main/NavigationBar/DesktopNavigationBar.vue @@ -42,6 +42,7 @@ @pointerdown="onDragStart" @pointermove="onDragging" @pointerup="onDragEnd" + @pointercancel="onDragEnd" @dblclick="initializeNavigationWidth" /> diff --git a/src/components/Main/NavigationBar/composables/useNavigationResizer.ts b/src/components/Main/NavigationBar/composables/useNavigationResizer.ts index 3e5c7747c8..bc333c5229 100644 --- a/src/components/Main/NavigationBar/composables/useNavigationResizer.ts +++ b/src/components/Main/NavigationBar/composables/useNavigationResizer.ts @@ -1,7 +1,8 @@ -import { computed, onUnmounted, ref } from 'vue' +import { computed, onUnmounted } from 'vue' import { useWindowSize } from '@vueuse/core' +import useDragging from '/@/composables/dom/useDragging' import { createAnimationFrameController } from '/@/lib/dom/animationFrame' import { MAX_NAVIGATION_WIDTH_RATIO, @@ -40,62 +41,37 @@ const useNavigationResizer = () => { return clampWidth(navigationWidth.value) }) - const isResizing = ref(false) - let pointerId: null | number = null + const { isDragging, onDragStart, onDragging, onDragEnd } = useDragging({ + targetRef: resizerRef, + onDragStart: () => { + document.body.style.cursor = 'e-resize' + document.body.style.userSelect = 'none' + if (!isNavigationClosed.value) updateNavigationLeft() + }, + onDragging: (e: PointerEvent) => { + animationFrame.request(() => { + const width = e.clientX - navigationLeft.value + + if (width <= NAVIGATION_CLOSING_THRESHOLD) { + closeNavigation() + } else { + setNavigationWidth(width) + } + }) + }, + onDragEnd: () => { + document.body.style.cursor = '' + document.body.style.userSelect = '' + if (navigationWidth.value > 0) saveNavigationWidth() + } + }) - const cleanup = () => { + onUnmounted(() => { animationFrame.cancel() - - document.body.style.cursor = '' - document.body.style.userSelect = '' - - if (!pointerId || !resizerRef.value?.hasPointerCapture(pointerId)) return - resizerRef.value?.releasePointerCapture(pointerId) - } - - const onDragStart = (e: PointerEvent) => { - isResizing.value = true - - if (!isNavigationClosed.value) updateNavigationLeft() - - document.body.style.cursor = 'e-resize' - document.body.style.userSelect = 'none' - - pointerId = e.pointerId - resizerRef.value?.setPointerCapture(pointerId) - - e.preventDefault() - } - - const onDragging = (e: PointerEvent) => { - if (!isResizing.value) return - - animationFrame.request(() => { - const width = e.clientX - navigationLeft.value - - if (width <= NAVIGATION_CLOSING_THRESHOLD) { - closeNavigation() - } else { - setNavigationWidth(width) - } - }) - - e.preventDefault() - } - - const onDragEnd = () => { - if (!isResizing.value) return - - if (navigationWidth.value > 0) saveNavigationWidth() - - isResizing.value = false - cleanup() - } - - onUnmounted(cleanup) + }) return { - isNavigationResizing: isResizing, + isNavigationResizing: isDragging, navigationWidth: clampedNavigationWidth, onDragStart, onDragging, diff --git a/src/composables/dom/useDragging.ts b/src/composables/dom/useDragging.ts new file mode 100644 index 0000000000..33bc5da1c4 --- /dev/null +++ b/src/composables/dom/useDragging.ts @@ -0,0 +1,81 @@ +import { type MaybeRefOrGetter, onBeforeUnmount, ref, toValue } from 'vue' + +import { useEventListener } from '@vueuse/core' + +interface UseDraggingOptions { + targetRef: MaybeRefOrGetter + onDragStart?: (e: PointerEvent) => void + onDragging?: (e: PointerEvent) => void + onDragEnd?: () => void +} + +const useDragging = ({ + targetRef, + onDragStart, + onDragging, + onDragEnd +}: UseDraggingOptions) => { + const isDragging = ref(false) + let pointerId: null | number = null + + const handleDragStart = (e: PointerEvent) => { + const target = toValue(targetRef) + + isDragging.value = true + + pointerId = e.pointerId + target?.setPointerCapture(pointerId) + + onDragStart?.(e) + e.preventDefault() + } + + const handleDragging = (e: PointerEvent) => { + if (!isDragging.value) return + + onDragging?.(e) + e.preventDefault() + } + + const handleDragEnd = () => { + if (!isDragging.value) return + isDragging.value = false + + const target = toValue(targetRef) + + if (pointerId && target?.hasPointerCapture(pointerId)) { + target?.releasePointerCapture(pointerId) + } + + pointerId = null + + onDragEnd?.() + } + + const handleVisibilityChange = () => { + if (document.hidden) { + handleDragEnd() + } + } + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + handleDragEnd() + } + } + + useEventListener(['blur', 'pagehide'], handleDragEnd) + useEventListener(document, 'contextmenu', handleDragEnd) + useEventListener(document, 'visibilitychange', handleVisibilityChange) + useEventListener(document, 'keydown', handleKeyDown) + onBeforeUnmount(handleDragEnd) + + return { + isDragging, + onDragStart: handleDragStart, + onDragging: handleDragging, + onDragEnd: handleDragEnd + } +} + +export default useDragging