minimizable.ts RawHistoryFolder
1import type { Action } from 'svelte/action';
2
3interface MinimizableParameters {
4 enabled?: boolean;
5 onMinimize?: (isMinimized: boolean) => void;
6}
7
8interface MinimizableAttributes {
9 onminimize?: (e: CustomEvent<{ isMinimized: boolean }>) => void;
10}
11
12interface StoredState {
13 width: number;
14 height: number;
15 x: number;
16 y: number;
17}
18
19export const minimizable: Action<HTMLDivElement, MinimizableParameters, MinimizableAttributes> = (node, params = {}) => {
20 const {
21 enabled = true,
22 onMinimize
23 } = params;
24
25 let isMinimized = false;
26 let storedState: StoredState | null = null;
27
28 function calculateMinimizedWidth() {
29 const titleBar = node.querySelector('.title-bar-text');
30 if (!titleBar) return 200;
31
32 // Create a temporary element to measure text width
33 const temp = document.createElement('div');
34 temp.style.position = 'absolute';
35 temp.style.visibility = 'hidden';
36 temp.style.whiteSpace = 'nowrap';
37 temp.style.font = window.getComputedStyle(titleBar).font;
38 temp.textContent = titleBar.textContent;
39 document.body.appendChild(temp);
40
41 // Get the width and add some padding
42 const width = temp.offsetWidth + 100; // Add 100px for buttons and padding
43 document.body.removeChild(temp);
44
45 return Math.max(200, Math.min(400, width)); // Keep width between 200 and 400px
46 }
47
48 function minimize() {
49 if (!enabled || isMinimized) return;
50
51 // Store current state
52 storedState = {
53 width: node.offsetWidth,
54 height: node.offsetHeight,
55 x: parseInt(node.style.left) || 0,
56 y: parseInt(node.style.top) || 0
57 };
58
59 // Check if we're in mobile mode
60 const isMobile = window.innerWidth <= 768;
61
62 if (!isMobile) {
63 // Desktop minimization
64 const minimizedWidth = calculateMinimizedWidth();
65 const minimizedHeight = 32; // Height of title bar
66 const minimizedX = 10;
67 const minimizedY = window.innerHeight - minimizedHeight - 10;
68
69 node.style.width = `${minimizedWidth}px`;
70 node.style.height = `${minimizedHeight}px`;
71 node.style.left = `${minimizedX}px`;
72 node.style.top = `${minimizedY}px`;
73 } else {
74 // Mobile minimization - position at bottom of screen
75 node.style.width = '100%';
76 node.style.height = '100%';
77 node.style.left = '0';
78 node.style.top = `${window.innerHeight - 32}px`; // 32px is title bar height
79 }
80
81 isMinimized = true;
82 node.classList.add('minimized');
83
84 // Hide resize handle
85 const resizeHandle = node.querySelector('.resize-handle');
86 if (resizeHandle) {
87 (resizeHandle as HTMLElement).style.display = 'none';
88 }
89
90 onMinimize?.(true);
91 node.dispatchEvent(new CustomEvent('minimize', {
92 detail: { isMinimized: true }
93 }));
94 }
95
96 function restore() {
97 if (!enabled || !isMinimized || !storedState) return;
98
99 const isMobile = window.innerWidth <= 768;
100
101 if (!isMobile) {
102 // Desktop restoration
103 node.style.width = `${storedState.width}px`;
104 node.style.height = `${storedState.height}px`;
105 node.style.left = `${storedState.x}px`;
106 node.style.top = `${storedState.y}px`;
107 } else {
108 // Mobile restoration
109 node.style.width = '100%';
110 node.style.height = '100%';
111 node.style.left = '0';
112 node.style.top = '0';
113 }
114
115 isMinimized = false;
116 node.classList.remove('minimized');
117
118 // Show resize handle if not in mobile mode
119 const resizeHandle = node.querySelector('.resize-handle');
120 if (resizeHandle) {
121 (resizeHandle as HTMLElement).style.display = !isMobile ? 'block' : 'none';
122 }
123
124 // Ensure window body is visible
125 const windowBody = node.querySelector('.window-body');
126 if (windowBody) {
127 (windowBody as HTMLElement).style.display = 'flex';
128 }
129
130 onMinimize?.(false);
131 node.dispatchEvent(new CustomEvent('minimize', {
132 detail: { isMinimized: false }
133 }));
134 }
135
136 function toggle() {
137 if (isMinimized) {
138 restore();
139 } else {
140 minimize();
141 }
142 }
143
144 // Handle double click on title bar
145 const titleBar = node.querySelector('.title-bar');
146 if (titleBar) {
147 titleBar.addEventListener('dblclick', toggle);
148 }
149
150 // Expose methods to the node
151 Object.assign(node, {
152 minimize,
153 restore,
154 toggleMinimize: toggle,
155 isMinimized: () => isMinimized
156 });
157
158 return {
159 update(newParams: MinimizableParameters) {
160 Object.assign(params, newParams);
161 },
162 destroy() {
163 if (titleBar) {
164 titleBar.removeEventListener('dblclick', toggle);
165 }
166 }
167 };
168};