treesap 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/components/ChatInput.d.ts +7 -0
  2. package/dist/components/ChatInput.d.ts.map +1 -0
  3. package/dist/components/ChatInput.js +11 -0
  4. package/dist/components/ChatInput.js.map +1 -0
  5. package/dist/components/Sidebar.d.ts +8 -0
  6. package/dist/components/Sidebar.d.ts.map +1 -0
  7. package/dist/components/Sidebar.js +7 -0
  8. package/dist/components/Sidebar.js.map +1 -0
  9. package/dist/components/SimpleLivePreview.js +1 -1
  10. package/dist/components/SimpleLivePreview.js.map +1 -1
  11. package/dist/pages/Code.d.ts.map +1 -1
  12. package/dist/pages/Code.js +2 -2
  13. package/dist/pages/Code.js.map +1 -1
  14. package/dist/services/websocket.d.ts.map +1 -1
  15. package/dist/services/websocket.js +1 -2
  16. package/dist/services/websocket.js.map +1 -1
  17. package/dist/static/components/ChatInput.js +237 -0
  18. package/dist/static/components/Sidebar.js +225 -0
  19. package/dist/static/components/SimpleLivePreview.js +73 -53
  20. package/dist/static/components/Terminal.js +143 -61
  21. package/dist/static/signals/SidebarSignal.js +123 -0
  22. package/dist/static/signals/TerminalSignal.js +137 -2
  23. package/dist/static/styles/main.css +180 -0
  24. package/package.json +1 -1
  25. package/src/components/ChatInput.tsx +56 -0
  26. package/src/components/Sidebar.tsx +99 -0
  27. package/src/components/SimpleLivePreview.tsx +4 -4
  28. package/src/pages/Code.tsx +18 -55
  29. package/src/services/websocket.ts +1 -5
  30. package/src/static/components/ChatInput.js +237 -0
  31. package/src/static/components/Sidebar.js +225 -0
  32. package/src/static/components/SimpleLivePreview.js +73 -53
  33. package/src/static/components/Terminal.js +143 -61
  34. package/src/static/signals/SidebarSignal.js +123 -0
  35. package/src/static/signals/TerminalSignal.js +137 -2
  36. package/src/static/styles/main.css +180 -0
  37. package/tailwind.config.ts +10 -0
@@ -0,0 +1,237 @@
1
+ // ChatInput component JavaScript for chat-style terminal input
2
+ class ChatInputManager {
3
+ constructor(chatInputId) {
4
+ this.chatInputId = chatInputId;
5
+ this.container = document.getElementById(chatInputId);
6
+ this.textarea = document.getElementById(`${chatInputId}-textarea`);
7
+ this.sendBtn = document.getElementById(`${chatInputId}-send-btn`);
8
+ this.executeBtn = document.getElementById(`${chatInputId}-execute-btn`);
9
+
10
+ // Get chat input data from window
11
+ const chatInputData = window[`chatInputData_${chatInputId.replace(/-/g, '_')}`];
12
+ if (!chatInputData) {
13
+ console.error(`No chat input data found for ${chatInputId}`);
14
+ return;
15
+ }
16
+
17
+ this.terminalId = chatInputData.terminalId;
18
+
19
+ console.log(`ChatInput ${chatInputId} initialized for terminal:`, this.terminalId);
20
+
21
+ this.init();
22
+ }
23
+
24
+ init() {
25
+ if (!this.container || !this.textarea || !this.sendBtn || !this.executeBtn) {
26
+ console.error('ChatInput elements not found!', {
27
+ container: !!this.container,
28
+ textarea: !!this.textarea,
29
+ sendBtn: !!this.sendBtn,
30
+ executeBtn: !!this.executeBtn
31
+ });
32
+ return;
33
+ }
34
+
35
+ this.setupEventListeners();
36
+ this.setupAutoResize();
37
+ }
38
+
39
+ setupEventListeners() {
40
+ // Handle send to input button click
41
+ this.sendBtn.addEventListener('click', () => {
42
+ this.sendToInput();
43
+ });
44
+
45
+ // Handle execute button click
46
+ this.executeBtn.addEventListener('click', () => {
47
+ this.executeCommand();
48
+ });
49
+
50
+ // Handle textarea key events
51
+ this.textarea.addEventListener('keydown', (e) => {
52
+ // Enter without shift sends to input field
53
+ if (e.key === 'Enter' && !e.shiftKey) {
54
+ e.preventDefault();
55
+ this.sendToInput();
56
+ }
57
+ // Ctrl+Enter or Cmd+Enter executes
58
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
59
+ e.preventDefault();
60
+ this.executeCommand();
61
+ }
62
+ // Allow Shift+Enter for new lines (default behavior)
63
+ });
64
+
65
+ // Handle input to auto-resize textarea
66
+ this.textarea.addEventListener('input', () => {
67
+ this.adjustTextareaHeight();
68
+ });
69
+
70
+ // Disable buttons when textarea is empty
71
+ this.textarea.addEventListener('input', () => {
72
+ this.updateButtonState();
73
+ });
74
+
75
+ // Focus textarea when container is clicked
76
+ this.container.addEventListener('click', (e) => {
77
+ if (e.target === this.container) {
78
+ this.textarea.focus();
79
+ }
80
+ });
81
+ }
82
+
83
+ setupAutoResize() {
84
+ // Initial state
85
+ this.adjustTextareaHeight();
86
+ this.updateButtonState();
87
+ }
88
+
89
+ adjustTextareaHeight() {
90
+ if (!this.textarea) return;
91
+
92
+ // Reset height to auto to get the correct scrollHeight
93
+ this.textarea.style.height = 'auto';
94
+
95
+ // Calculate the new height based on content
96
+ const scrollHeight = this.textarea.scrollHeight;
97
+ const maxHeight = 120; // Max height from CSS
98
+ const minHeight = 40; // Min height from CSS
99
+
100
+ const newHeight = Math.min(Math.max(scrollHeight, minHeight), maxHeight);
101
+ this.textarea.style.height = `${newHeight}px`;
102
+ }
103
+
104
+ updateButtonState() {
105
+ if (!this.textarea || !this.sendBtn || !this.executeBtn) return;
106
+
107
+ const hasContent = this.textarea.value.trim().length > 0;
108
+ this.sendBtn.disabled = !hasContent;
109
+ this.executeBtn.disabled = false; // Execute button is always enabled
110
+ }
111
+
112
+ sendToInput() {
113
+ if (!this.textarea) return;
114
+
115
+ const input = this.textarea.value.trim();
116
+ if (!input) return;
117
+
118
+ // Get the terminal manager for the associated terminal
119
+ const terminalManager = this.getTerminalManager();
120
+ if (!terminalManager) {
121
+ console.error(`No terminal manager found for terminal: ${this.terminalId}`);
122
+ return;
123
+ }
124
+
125
+ // Send the input to the terminal input field (without executing)
126
+ console.log(`Sending text to terminal input field ${this.terminalId}:`, input);
127
+ console.log('ChatInput sending (programmatic):', JSON.stringify(input), 'char codes:', input.split('').map(c => c.charCodeAt(0)));
128
+
129
+ // Simplest approach: just send the text directly
130
+ // This should work for most cases in Claude Code
131
+ terminalManager.sendInput(input);
132
+
133
+ // Clear the textarea
134
+ this.textarea.value = '';
135
+ this.adjustTextareaHeight();
136
+ this.updateButtonState();
137
+
138
+ // Focus back to textarea for next input
139
+ this.textarea.focus();
140
+ }
141
+
142
+ executeCommand() {
143
+ // Get the terminal manager for the associated terminal
144
+ const terminalManager = this.getTerminalManager();
145
+ if (!terminalManager) {
146
+ console.error(`No terminal manager found for terminal: ${this.terminalId}`);
147
+ return;
148
+ }
149
+
150
+ // Execute whatever is currently in the terminal input field
151
+ console.log(`Executing command in terminal ${this.terminalId}`);
152
+ console.log('ChatInput executing (programmatic):', JSON.stringify('\r'), 'char code:', '\r'.charCodeAt(0));
153
+ terminalManager.sendInput('\r');
154
+ }
155
+
156
+ getTerminalManager() {
157
+ // Access global terminal managers
158
+ if (window.terminalManagers && window.terminalManagers.has(this.terminalId)) {
159
+ return window.terminalManagers.get(this.terminalId);
160
+ }
161
+
162
+ // Fallback: try to find by checking all managers
163
+ if (window.terminalManagers) {
164
+ for (const [terminalId, manager] of window.terminalManagers.entries()) {
165
+ if (terminalId === this.terminalId) {
166
+ return manager;
167
+ }
168
+ }
169
+ }
170
+
171
+ // If not found, log available terminals for debugging
172
+ if (window.terminalManagers) {
173
+ console.log('Available terminal managers:', Array.from(window.terminalManagers.keys()));
174
+ console.log('Looking for terminal ID:', this.terminalId);
175
+ } else {
176
+ console.log('No terminal managers found in window object');
177
+ }
178
+
179
+ return null;
180
+ }
181
+
182
+ focus() {
183
+ if (this.textarea) {
184
+ this.textarea.focus();
185
+ }
186
+ }
187
+
188
+ destroy() {
189
+ // Clean up event listeners if needed
190
+ // The component will be removed from DOM, so most cleanup is automatic
191
+ }
192
+ }
193
+
194
+ // Auto-initialize when script loads
195
+ console.log('ChatInput.js loaded, looking for chat input containers...');
196
+ let chatInputManagers = new Map();
197
+
198
+ function initializeChatInputs() {
199
+ // Look for all sapling-islands with chat input content
200
+ const saplingIslands = document.querySelectorAll('sapling-island');
201
+
202
+ for (const island of saplingIslands) {
203
+ // Look for chat input container
204
+ const chatInputDiv = island.querySelector('div[id*="chat-input"]');
205
+ if (chatInputDiv && chatInputDiv.id) {
206
+ const chatInputId = chatInputDiv.id;
207
+ console.log('Found chat input component with ID:', chatInputId);
208
+
209
+ // Check if we already have a manager for this chat input
210
+ if (!chatInputManagers.has(chatInputId)) {
211
+ const manager = new ChatInputManager(chatInputId);
212
+ chatInputManagers.set(chatInputId, manager);
213
+ }
214
+ }
215
+ }
216
+
217
+ console.log(`Initialized ${chatInputManagers.size} chat input(s)`);
218
+ }
219
+
220
+ // Try immediate initialization
221
+ if (document.readyState === 'loading') {
222
+ document.addEventListener('DOMContentLoaded', initializeChatInputs);
223
+ } else {
224
+ initializeChatInputs();
225
+ }
226
+
227
+ // Make available globally
228
+ window.chatInputManagers = chatInputManagers;
229
+ window.ChatInputManager = ChatInputManager;
230
+ window.initializeChatInputs = initializeChatInputs;
231
+
232
+ // Cleanup on page unload
233
+ window.addEventListener('beforeunload', () => {
234
+ for (const manager of chatInputManagers.values()) {
235
+ manager.destroy();
236
+ }
237
+ });
@@ -0,0 +1,225 @@
1
+ // Sidebar component JavaScript for responsive behavior
2
+ import { sidebarStore } from '/signals/SidebarSignal.js';
3
+
4
+ class SidebarManager {
5
+ constructor(id = 'sidebar') {
6
+ this.id = id;
7
+
8
+ // DOM elements
9
+ this.backdrop = document.getElementById(`${id}-backdrop`);
10
+ this.pane = document.getElementById(`${id}-pane`);
11
+ this.closeBtn = document.getElementById(`${id}-close-btn`);
12
+ this.refreshBtn = document.getElementById('live-preview-refresh-btn');
13
+ this.urlInput = document.getElementById('live-preview-url-input');
14
+ this.loadBtn = document.getElementById('live-preview-load-btn');
15
+
16
+ // Reference to the sidebar store
17
+ this.store = sidebarStore;
18
+
19
+ this.init();
20
+ }
21
+
22
+ init() {
23
+ console.log('Initializing Sidebar:', this.id);
24
+ console.log('Elements found:', {
25
+ backdrop: !!this.backdrop,
26
+ pane: !!this.pane,
27
+ closeBtn: !!this.closeBtn,
28
+ refreshBtn: !!this.refreshBtn,
29
+ urlInput: !!this.urlInput,
30
+ loadBtn: !!this.loadBtn
31
+ });
32
+
33
+ // Set up event listeners
34
+ this.setupEventListeners();
35
+
36
+ // Subscribe to store state changes
37
+ this.subscribeToStore();
38
+
39
+ // Initial state update
40
+ this.updateSidebarState();
41
+ this.updateMobileToggle();
42
+ }
43
+
44
+ setupEventListeners() {
45
+ // Mobile close button
46
+ this.closeBtn?.addEventListener('click', () => this.store.close());
47
+
48
+ // Backdrop click to close
49
+ this.backdrop?.addEventListener('click', () => this.store.close());
50
+
51
+ // Mobile toggle button (in main layout)
52
+ const mobileToggle = document.getElementById('mobile-sidebar-toggle');
53
+ mobileToggle?.addEventListener('click', () => this.store.toggle());
54
+
55
+ // Refresh button
56
+ this.refreshBtn?.addEventListener('click', () => this.refreshPreview());
57
+
58
+ // URL navigation
59
+ this.loadBtn?.addEventListener('click', (e) => {
60
+ e.preventDefault();
61
+ this.loadUrl();
62
+ });
63
+
64
+ this.urlInput?.addEventListener('keypress', (e) => {
65
+ if (e.key === 'Enter') {
66
+ e.preventDefault();
67
+ this.loadUrl();
68
+ }
69
+ });
70
+
71
+ // Keyboard shortcuts
72
+ document.addEventListener('keydown', (e) => {
73
+ // Escape key to close sidebar on mobile
74
+ if (e.key === 'Escape' && this.store.isMobile.value && this.store.isOpen.value) {
75
+ e.preventDefault();
76
+ this.store.close();
77
+ }
78
+
79
+ // Cmd/Ctrl + B to toggle sidebar
80
+ if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
81
+ e.preventDefault();
82
+ this.store.toggle();
83
+ }
84
+ });
85
+
86
+ // Listen for custom events to maintain backward compatibility
87
+ document.addEventListener('sidebar:toggle', () => this.store.toggle());
88
+ document.addEventListener('sidebar:open', () => this.store.open());
89
+ document.addEventListener('sidebar:close', () => this.store.close());
90
+ }
91
+
92
+ subscribeToStore() {
93
+ // Subscribe to store changes and update UI accordingly
94
+ this.store.isOpen.subscribe(() => this.updateSidebarState());
95
+ this.store.isMobile.subscribe(() => this.updateSidebarState());
96
+ this.store.shouldShowBackdrop.subscribe(() => this.updateSidebarState());
97
+ this.store.shouldShowMobileToggle.subscribe(() => this.updateMobileToggle());
98
+ }
99
+
100
+ updateSidebarState() {
101
+ if (!this.pane || !this.backdrop) return;
102
+
103
+ const isOpen = this.store.isOpen.value;
104
+ const isMobile = this.store.isMobile.value;
105
+ const shouldShowBackdrop = this.store.shouldShowBackdrop.value;
106
+
107
+ if (isMobile) {
108
+ // Mobile behavior: overlay
109
+ if (shouldShowBackdrop) {
110
+ // Show backdrop
111
+ this.backdrop.classList.remove('opacity-0', 'pointer-events-none');
112
+ this.backdrop.classList.add('opacity-100');
113
+
114
+ // Prevent body scroll
115
+ document.body.style.overflow = 'hidden';
116
+ } else {
117
+ // Hide backdrop
118
+ this.backdrop.classList.remove('opacity-100');
119
+ this.backdrop.classList.add('opacity-0', 'pointer-events-none');
120
+
121
+ // Restore body scroll
122
+ document.body.style.overflow = '';
123
+ }
124
+
125
+ if (isOpen) {
126
+ // Show sidebar
127
+ this.pane.classList.remove('-translate-x-full');
128
+ this.pane.classList.add('translate-x-0');
129
+ } else {
130
+ // Hide sidebar
131
+ this.pane.classList.remove('translate-x-0');
132
+ this.pane.classList.add('-translate-x-full');
133
+ }
134
+ } else {
135
+ // Desktop behavior: side panel
136
+ // Hide backdrop (not needed on desktop)
137
+ this.backdrop.classList.add('opacity-0', 'pointer-events-none');
138
+
139
+ // Restore body scroll
140
+ document.body.style.overflow = '';
141
+
142
+ if (isOpen) {
143
+ // Show sidebar
144
+ this.pane.classList.remove('-translate-x-full');
145
+ this.pane.classList.add('translate-x-0');
146
+ this.pane.style.display = '';
147
+ } else {
148
+ // Hide sidebar completely on desktop
149
+ this.pane.style.display = 'none';
150
+ }
151
+ }
152
+ }
153
+
154
+ updateMobileToggle() {
155
+ const mobileToggle = document.getElementById('mobile-sidebar-toggle');
156
+ const shouldShow = this.store.shouldShowMobileToggle.value;
157
+
158
+ if (mobileToggle) {
159
+ mobileToggle.style.display = shouldShow ? 'flex' : 'none';
160
+ }
161
+ }
162
+
163
+ refreshPreview() {
164
+ // Dispatch event for SimpleLivePreview to handle
165
+ document.dispatchEvent(new CustomEvent('preview:refresh'));
166
+ }
167
+
168
+ loadUrl() {
169
+ if (this.urlInput) {
170
+ const path = this.urlInput.value.trim();
171
+ // Dispatch event for SimpleLivePreview to handle
172
+ document.dispatchEvent(new CustomEvent('preview:loadUrl', {
173
+ detail: { path }
174
+ }));
175
+ }
176
+ }
177
+
178
+ // Public API methods
179
+ getState() {
180
+ return this.store.getState();
181
+ }
182
+
183
+ destroy() {
184
+ // Restore body scroll
185
+ document.body.style.overflow = '';
186
+
187
+ // Clean up is handled by the signal store
188
+ }
189
+ }
190
+
191
+ // Auto-initialize when script loads
192
+ console.log('Sidebar.js loaded, looking for sidebar containers...');
193
+
194
+ function initializeSidebar() {
195
+ // Look for sapling-islands containing sidebar content
196
+ const saplingIslands = document.querySelectorAll('sapling-island');
197
+
198
+ for (const island of saplingIslands) {
199
+ // Look for sidebar pane div
200
+ const sidebarPane = island.querySelector('div[id$="-pane"]');
201
+ if (sidebarPane && sidebarPane.id.includes('sidebar')) {
202
+ const sidebarId = sidebarPane.id.replace('-pane', '');
203
+ console.log('Found Sidebar component with ID:', sidebarId);
204
+
205
+ // Create and store manager
206
+ const manager = new SidebarManager(sidebarId);
207
+ window.sidebarManager = manager; // Make globally available
208
+
209
+ break; // Only one sidebar per page
210
+ }
211
+ }
212
+ }
213
+
214
+ // Initialize immediately since Sapling islands are ready
215
+ initializeSidebar();
216
+
217
+ // Make available globally
218
+ window.SidebarManager = SidebarManager;
219
+
220
+ // Cleanup on page unload
221
+ window.addEventListener('beforeunload', () => {
222
+ if (window.sidebarManager) {
223
+ window.sidebarManager.destroy();
224
+ }
225
+ });
@@ -1,4 +1,6 @@
1
1
  // SimpleLivePreview component JavaScript
2
+ import { sidebarStore } from '/signals/SidebarSignal.js';
3
+
2
4
  class SimpleLivePreviewManager {
3
5
  constructor(id = 'simple-preview') {
4
6
  this.id = id;
@@ -17,8 +19,8 @@ class SimpleLivePreviewManager {
17
19
  // Get preview port from iframe data attribute
18
20
  this.previewPort = this.iframe?.getAttribute('data-preview-port') || 5173;
19
21
 
20
- // State
21
- this.isSidebarHidden = false;
22
+ // Reference to the sidebar store
23
+ this.store = sidebarStore;
22
24
 
23
25
  this.init();
24
26
  }
@@ -34,12 +36,15 @@ class SimpleLivePreviewManager {
34
36
 
35
37
  // Set up event listeners
36
38
  this.setupEventListeners();
39
+
40
+ // Subscribe to sidebar store changes
41
+ this.subscribeToStore();
37
42
  }
38
43
 
39
44
  setupEventListeners() {
40
45
  // Hide sidebar toggle (both sidebar and floating button)
41
- this.hideSidebarBtn?.addEventListener('click', () => this.toggleSidebarVisibility());
42
- this.floatingHideSidebarBtn?.addEventListener('click', () => this.toggleSidebarVisibility());
46
+ this.hideSidebarBtn?.addEventListener('click', () => this.store.toggle());
47
+ this.floatingHideSidebarBtn?.addEventListener('click', () => this.store.toggle());
43
48
 
44
49
  // Refresh button
45
50
  this.refreshBtn?.addEventListener('click', () => this.refreshIframe());
@@ -62,6 +67,21 @@ class SimpleLivePreviewManager {
62
67
  this.loadUrl();
63
68
  });
64
69
 
70
+ // Listen for events from Sidebar component
71
+ document.addEventListener('preview:refresh', () => this.refreshIframe());
72
+ document.addEventListener('preview:loadUrl', (e) => {
73
+ if (e.detail && e.detail.path !== undefined) {
74
+ this.loadUrlFromPath(e.detail.path);
75
+ }
76
+ });
77
+
78
+ // Legacy: Listen for sidebar state changes (for backward compatibility)
79
+ document.addEventListener('sidebar:stateChanged', (e) => {
80
+ if (e.detail) {
81
+ this.handleSidebarStateChange(e.detail);
82
+ }
83
+ });
84
+
65
85
  // Keyboard shortcuts
66
86
  document.addEventListener('keydown', (e) => {
67
87
  // Cmd/Ctrl + R for refresh
@@ -69,11 +89,6 @@ class SimpleLivePreviewManager {
69
89
  e.preventDefault();
70
90
  this.refreshIframe();
71
91
  }
72
- // Cmd/Ctrl + B to toggle sidebar panel
73
- if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
74
- e.preventDefault();
75
- this.toggleSidebarVisibility();
76
- }
77
92
  });
78
93
 
79
94
  // Handle iframe load errors (for X-Frame-Options violations)
@@ -129,55 +144,41 @@ class SimpleLivePreviewManager {
129
144
  }
130
145
  }
131
146
 
132
- toggleSidebarVisibility() {
133
- this.isSidebarHidden = !this.isSidebarHidden;
147
+ subscribeToStore() {
148
+ // Subscribe to sidebar store changes
149
+ this.store.shouldShowFloatingButton.subscribe(() => this.updateFloatingButton());
150
+ this.store.isOpen.subscribe(() => this.updateFloatingButton());
134
151
 
135
- const sidebarPane = document.getElementById('sidebar-pane');
136
- const previewPane = document.getElementById(this.id);
152
+ // Initial update
153
+ this.updateFloatingButton();
154
+ }
155
+
156
+ updateFloatingButton() {
157
+ const floatingBtn = this.floatingHideSidebarBtn;
158
+ const floatingIcon = this.floatingHideSidebarIcon;
159
+ const shouldShow = this.store.shouldShowFloatingButton.value;
137
160
 
138
- if (sidebarPane && previewPane) {
139
- if (this.isSidebarHidden) {
140
- // Hide sidebar pane and make preview full width
141
- sidebarPane.style.display = 'none';
142
- previewPane.classList.remove('flex-1');
143
- previewPane.classList.add('w-full');
144
-
145
- // Update button icons and titles
146
- if (this.hideSidebarIcon) {
147
- this.hideSidebarIcon.setAttribute('icon', 'ph:sidebar-simple-fill');
148
- }
149
- if (this.floatingHideSidebarIcon) {
150
- this.floatingHideSidebarIcon.setAttribute('icon', 'ph:sidebar-simple-fill');
151
- }
152
- if (this.hideSidebarBtn) {
153
- this.hideSidebarBtn.setAttribute('title', 'Show Sidebar');
154
- }
155
- if (this.floatingHideSidebarBtn) {
156
- this.floatingHideSidebarBtn.setAttribute('title', 'Show Sidebar');
161
+ if (floatingBtn) {
162
+ if (shouldShow) {
163
+ // Show floating button when sidebar is closed
164
+ floatingBtn.style.display = 'flex';
165
+ // Update icon and title
166
+ if (floatingIcon) {
167
+ floatingIcon.setAttribute('icon', 'ph:sidebar-simple-fill');
157
168
  }
169
+ floatingBtn.setAttribute('title', 'Show Sidebar');
158
170
  } else {
159
- // Show sidebar pane and restore original widths
160
- sidebarPane.style.display = '';
161
- previewPane.classList.remove('w-full');
162
- previewPane.classList.add('flex-1');
163
-
164
- // Update button icons and titles
165
- if (this.hideSidebarIcon) {
166
- this.hideSidebarIcon.setAttribute('icon', 'ph:sidebar-simple');
167
- }
168
- if (this.floatingHideSidebarIcon) {
169
- this.floatingHideSidebarIcon.setAttribute('icon', 'ph:sidebar-simple');
170
- }
171
- if (this.hideSidebarBtn) {
172
- this.hideSidebarBtn.setAttribute('title', 'Hide Sidebar');
173
- }
174
- if (this.floatingHideSidebarBtn) {
175
- this.floatingHideSidebarBtn.setAttribute('title', 'Hide Sidebar');
176
- }
171
+ // Hide floating button when sidebar is open
172
+ floatingBtn.style.display = 'none';
177
173
  }
178
174
  }
179
175
  }
180
176
 
177
+ toggleSidebarVisibility() {
178
+ // Use the store to toggle
179
+ this.store.toggle();
180
+ }
181
+
181
182
  refreshIframe() {
182
183
  if (this.iframe) {
183
184
  console.log('Refreshing iframe...');
@@ -191,7 +192,13 @@ class SimpleLivePreviewManager {
191
192
  loadUrl() {
192
193
  if (this.urlInput && this.iframe) {
193
194
  const path = this.urlInput.value.trim();
194
- console.log('loadUrl called with path:', path);
195
+ this.loadUrlFromPath(path);
196
+ }
197
+ }
198
+
199
+ loadUrlFromPath(path) {
200
+ if (this.iframe) {
201
+ console.log('loadUrlFromPath called with path:', path);
195
202
 
196
203
  // Check if it's an external URL (starts with http:// or https://)
197
204
  if (path.startsWith('http://') || path.startsWith('https://')) {
@@ -201,8 +208,10 @@ class SimpleLivePreviewManager {
201
208
  // Open external URLs in a new tab
202
209
  console.log('Opening external URL in new tab:', path);
203
210
  window.open(path, '_blank');
204
- // Clear the input
205
- this.urlInput.value = '';
211
+ // Clear the input if it exists
212
+ if (this.urlInput) {
213
+ this.urlInput.value = '';
214
+ }
206
215
  return;
207
216
  }
208
217
  }
@@ -211,9 +220,20 @@ class SimpleLivePreviewManager {
211
220
  const newUrl = path ? baseUrl + '/' + path.replace(/^\//, '') : baseUrl;
212
221
  console.log('Loading URL in iframe:', newUrl);
213
222
  this.iframe.src = newUrl;
223
+
224
+ // Update input if it exists
225
+ if (this.urlInput) {
226
+ this.urlInput.value = path;
227
+ }
214
228
  }
215
229
  }
216
230
 
231
+ handleSidebarStateChange(state) {
232
+ // Legacy method for backward compatibility
233
+ console.log('Legacy sidebar state changed:', state);
234
+ // The floating button is now handled by updateFloatingButton()
235
+ }
236
+
217
237
  handleIframeError() {
218
238
  // Get the current iframe src and open it in a new tab if it's external
219
239
  if (this.iframe && this.iframe.src) {