|
1 | 1 | <script setup lang="ts"> |
2 | | -import { onBeforeUnmount } from 'vue' |
3 | | -import { useResizable } from '@/composables' |
4 | 2 | import { useScrollbar } from '@/composables/scrollbar' |
5 | 3 | import { ui } from '@/ui/figma' |
6 | 4 | import { options } from '@/ui/state' |
7 | | -import { useDraggable, useWindowSize, watchDebounced } from '@vueuse/core' |
| 5 | +import { useDraggable, useEventListener, useWindowSize, watchDebounced } from '@vueuse/core' |
8 | 6 |
|
9 | 7 | const panel = useTemplateRef('panel') |
10 | 8 | const header = useTemplateRef('header') |
@@ -32,33 +30,89 @@ const { x, y, isDragging } = useDraggable(panel, { |
32 | 30 |
|
33 | 31 | const { width: windowWidth, height: windowHeight } = useWindowSize() |
34 | 32 |
|
35 | | -const { |
36 | | - width: panelWidth, |
37 | | - isResizing, |
38 | | - handleRightEdgeResize, |
39 | | - handleLeftEdgeResize, |
40 | | - resetWidth, |
41 | | - isAtMinWidth, |
42 | | - isAtMaxWidth, |
43 | | - cleanup |
44 | | -} = useResizable({ |
45 | | - min: ui.tempadPanelWidth, |
46 | | - max: ui.tempadPanelMaxWidth, |
47 | | - defaultWidth: ui.tempadPanelWidth, |
48 | | - initialWidth: position?.width, |
49 | | - onPositionChange: (positionDelta) => { |
| 33 | +const panelWidth = ref(position?.width ?? ui.tempadPanelWidth) |
| 34 | +const isResizing = ref(false) |
| 35 | +
|
| 36 | +// no need reactive state, directly using variables |
| 37 | +let resizeState: { |
| 38 | + direction: 'left' | 'right' |
| 39 | + startX: number |
| 40 | + startWidth: number |
| 41 | + target: HTMLElement |
| 42 | + pointerId: number |
| 43 | +} | null = null |
| 44 | +
|
| 45 | +function clampWidth(value: number): number { |
| 46 | + return Math.max(ui.tempadPanelWidth, Math.min(ui.tempadPanelMaxWidth, value)) |
| 47 | +} |
| 48 | +
|
| 49 | +function startResize(e: PointerEvent, direction: 'left' | 'right') { |
| 50 | + e.preventDefault() |
| 51 | + e.stopPropagation() |
| 52 | +
|
| 53 | + const target = e.currentTarget as HTMLElement |
| 54 | + if (!target) return |
| 55 | +
|
| 56 | + target.setPointerCapture(e.pointerId) |
| 57 | + isResizing.value = true |
| 58 | +
|
| 59 | + resizeState = { |
| 60 | + direction, |
| 61 | + startX: e.clientX, |
| 62 | + startWidth: panelWidth.value, |
| 63 | + target, |
| 64 | + pointerId: e.pointerId |
| 65 | + } |
| 66 | +} |
| 67 | +
|
| 68 | +function onPointerMove(e: PointerEvent) { |
| 69 | + if (!isResizing.value || !resizeState) return |
| 70 | +
|
| 71 | + if (e.buttons === 0) { |
| 72 | + endResize(e) |
| 73 | + return |
| 74 | + } |
| 75 | +
|
| 76 | + const deltaX = e.clientX - resizeState.startX |
| 77 | + const newWidth = |
| 78 | + resizeState.direction === 'right' |
| 79 | + ? clampWidth(resizeState.startWidth + deltaX) |
| 80 | + : clampWidth(resizeState.startWidth - deltaX) |
| 81 | +
|
| 82 | + if (resizeState.direction === 'left') { |
| 83 | + const positionDelta = panelWidth.value - newWidth |
50 | 84 | x.value += positionDelta |
51 | | - }, |
52 | | - onResizeEnd: (width) => { |
53 | | - if (position) { |
54 | | - position.width = width |
55 | | - } |
56 | 85 | } |
57 | | -}) |
58 | 86 |
|
59 | | -onBeforeUnmount(() => { |
60 | | - cleanup() |
61 | | -}) |
| 87 | + panelWidth.value = newWidth |
| 88 | +} |
| 89 | +
|
| 90 | +function endResize(e: PointerEvent) { |
| 91 | + if (!isResizing.value || !resizeState) return |
| 92 | +
|
| 93 | + isResizing.value = false |
| 94 | + resizeState.target.releasePointerCapture(resizeState.pointerId) |
| 95 | +
|
| 96 | + if (position) { |
| 97 | + position.width = panelWidth.value |
| 98 | + } |
| 99 | +
|
| 100 | + resizeState = null |
| 101 | +} |
| 102 | +
|
| 103 | +function resetWidth() { |
| 104 | + panelWidth.value = ui.tempadPanelWidth |
| 105 | + if (position) { |
| 106 | + delete position.width |
| 107 | + } |
| 108 | +} |
| 109 | +
|
| 110 | +useEventListener('pointermove', onPointerMove) |
| 111 | +useEventListener('pointerup', endResize) |
| 112 | +useEventListener('pointercancel', endResize) |
| 113 | +
|
| 114 | +const isAtMinWidth = computed(() => panelWidth.value <= ui.tempadPanelWidth) |
| 115 | +const isAtMaxWidth = computed(() => panelWidth.value >= ui.tempadPanelMaxWidth) |
62 | 116 |
|
63 | 117 | const restrictedPosition = computed(() => { |
64 | 118 | if (!panel.value || !header.value) { |
@@ -105,13 +159,6 @@ function toggleMinimized() { |
105 | 159 | options.value.minimized = !options.value.minimized |
106 | 160 | } |
107 | 161 |
|
108 | | -function onResizeHandleDoubleClick() { |
109 | | - if (position) { |
110 | | - delete position.width |
111 | | - } |
112 | | - resetWidth() |
113 | | -} |
114 | | -
|
115 | 162 | const leftHandleCursor = computed(() => { |
116 | 163 | if (isAtMaxWidth.value) return 'e-resize' |
117 | 164 | if (isAtMinWidth.value) return 'w-resize' |
@@ -140,13 +187,13 @@ const resizingCursor = 'ew-resize' |
140 | 187 | > |
141 | 188 | <div |
142 | 189 | class="tp-panel-resize-handle tp-panel-resize-handle-left" |
143 | | - @pointerdown="handleLeftEdgeResize" |
144 | | - @dblclick="onResizeHandleDoubleClick" |
| 190 | + @pointerdown="startResize($event, 'left')" |
| 191 | + @dblclick="resetWidth" |
145 | 192 | /> |
146 | 193 | <div |
147 | 194 | class="tp-panel-resize-handle tp-panel-resize-handle-right" |
148 | | - @pointerdown="handleRightEdgeResize" |
149 | | - @dblclick="onResizeHandleDoubleClick" |
| 195 | + @pointerdown="startResize($event, 'right')" |
| 196 | + @dblclick="resetWidth" |
150 | 197 | /> |
151 | 198 | <header ref="header" class="tp-row tp-row-justify tp-panel-header" @dblclick="toggleMinimized"> |
152 | 199 | <slot name="header" /> |
|
0 commit comments