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