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