1import type { Action } from 'svelte/action';
2
3interface ResizableParameters {
4 enabled?: boolean;
5 minWidth?: number;
6 minHeight?: number;
7 maxWidth?: number;
8 maxHeight?: number;
9}
10
11interface ResizableAttributes {
12 onresizestart?: (e: CustomEvent<{ width: number; height: number }>) => void;
13 onresizemove?: (e: CustomEvent<{ width: number; height: number }>) => void;
14 onresizeend?: (e: CustomEvent<{ width: number; height: number }>) => void;
15}
16
17export const resizable: Action<HTMLElement, ResizableParameters, ResizableAttributes> = (
18 node,
19 params = {}
20) => {
21 let isResizing = false;
22 let startWidth = 0;
23 let startHeight = 0;
24 let startX = 0;
25 let startY = 0;
26
27 const {
28 enabled = true,
29 minWidth = 200,
30 minHeight = 200,
31 maxWidth = window.innerWidth,
32 maxHeight = window.innerHeight
33 } = params;
34
35 // Create and append resize handle
36 const handle = document.createElement('div');
37 handle.className = 'resize-handle';
38 Object.assign(handle.style, {
39 position: 'absolute',
40 bottom: '0',
41 right: '0',
42 width: '15px',
43 height: '15px',
44 cursor: 'se-resize',
45 zIndex: '1000'
46 });
47 node.appendChild(handle);
48
49 // Ensure parent element has position
50 if (getComputedStyle(node).position === 'static') {
51 node.style.position = 'relative';
52 }
53
54 function onMouseDown(event: MouseEvent) {
55 if (!enabled) return;
56
57 isResizing = true;
58 startX = event.clientX;
59 startY = event.clientY;
60 startWidth = node.offsetWidth;
61 startHeight = node.offsetHeight;
62
63 node.dispatchEvent(
64 new CustomEvent('resizestart', {
65 detail: { width: startWidth, height: startHeight }
66 })
67 );
68
69 event.preventDefault();
70 }
71
72 function onMouseMove(event: MouseEvent) {
73 if (!isResizing || !enabled) return;
74
75 const deltaX = event.clientX - startX;
76 const deltaY = event.clientY - startY;
77
78 const newWidth = Math.min(maxWidth, Math.max(minWidth, startWidth + deltaX));
79 const newHeight = Math.min(maxHeight, Math.max(minHeight, startHeight + deltaY));
80
81 node.style.width = `${newWidth}px`;
82 node.style.height = `${newHeight}px`;
83
84 node.dispatchEvent(
85 new CustomEvent('resizemove', {
86 detail: { width: newWidth, height: newHeight }
87 })
88 );
89 }
90
91 function onMouseUp() {
92 if (!isResizing) return;
93
94 isResizing = false;
95 node.dispatchEvent(
96 new CustomEvent('resizeend', {
97 detail: {
98 width: node.offsetWidth,
99 height: node.offsetHeight
100 }
101 })
102 );
103 }
104
105 handle.addEventListener('mousedown', onMouseDown);
106 window.addEventListener('mousemove', onMouseMove);
107 window.addEventListener('mouseup', onMouseUp);
108
109 return {
110 update(newParams: ResizableParameters) {
111 Object.assign(params, newParams);
112 },
113 destroy() {
114 handle.removeEventListener('mousedown', onMouseDown);
115 window.removeEventListener('mousemove', onMouseMove);
116 window.removeEventListener('mouseup', onMouseUp);
117 node.removeChild(handle);
118 }
119 };
120};