electrobun 0.0.19-beta.99 → 0.1.0

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 (77) hide show
  1. package/README.md +1 -1
  2. package/dist/api/browser/webviewtag.ts +54 -2
  3. package/dist/api/bun/ElectrobunConfig.ts +171 -0
  4. package/dist/api/bun/core/BrowserWindow.ts +4 -0
  5. package/dist/api/bun/core/Tray.ts +14 -0
  6. package/dist/api/bun/core/Updater.ts +4 -3
  7. package/dist/api/bun/index.ts +2 -0
  8. package/dist/api/bun/proc/native.ts +107 -5
  9. package/dist/main.js +5 -4
  10. package/package.json +4 -2
  11. package/src/cli/index.ts +565 -148
  12. package/templates/hello-world/bun.lock +164 -2
  13. package/templates/hello-world/electrobun.config.ts +28 -0
  14. package/templates/hello-world/src/bun/index.ts +2 -2
  15. package/templates/hello-world/src/mainview/index.html +5 -6
  16. package/templates/hello-world/src/mainview/index.ts +1 -5
  17. package/templates/interactive-playground/README.md +26 -0
  18. package/templates/interactive-playground/assets/tray-icon.png +0 -0
  19. package/templates/interactive-playground/electrobun.config.ts +36 -0
  20. package/templates/interactive-playground/package-lock.json +36 -0
  21. package/templates/interactive-playground/package.json +15 -0
  22. package/templates/interactive-playground/src/bun/demos/files.ts +70 -0
  23. package/templates/interactive-playground/src/bun/demos/menus.ts +139 -0
  24. package/templates/interactive-playground/src/bun/demos/rpc.ts +83 -0
  25. package/templates/interactive-playground/src/bun/demos/system.ts +72 -0
  26. package/templates/interactive-playground/src/bun/demos/updates.ts +105 -0
  27. package/templates/interactive-playground/src/bun/demos/windows.ts +90 -0
  28. package/templates/interactive-playground/src/bun/index.ts +124 -0
  29. package/templates/interactive-playground/src/bun/types/rpc.ts +109 -0
  30. package/templates/interactive-playground/src/mainview/components/EventLog.ts +107 -0
  31. package/templates/interactive-playground/src/mainview/components/Sidebar.ts +65 -0
  32. package/templates/interactive-playground/src/mainview/components/Toast.ts +57 -0
  33. package/templates/interactive-playground/src/mainview/demos/FileDemo.ts +211 -0
  34. package/templates/interactive-playground/src/mainview/demos/MenuDemo.ts +102 -0
  35. package/templates/interactive-playground/src/mainview/demos/RPCDemo.ts +229 -0
  36. package/templates/interactive-playground/src/mainview/demos/TrayDemo.ts +132 -0
  37. package/templates/interactive-playground/src/mainview/demos/WebViewDemo.ts +411 -0
  38. package/templates/interactive-playground/src/mainview/demos/WindowDemo.ts +207 -0
  39. package/templates/interactive-playground/src/mainview/index.css +538 -0
  40. package/templates/interactive-playground/src/mainview/index.html +103 -0
  41. package/templates/interactive-playground/src/mainview/index.ts +238 -0
  42. package/templates/multitab-browser/README.md +34 -0
  43. package/templates/multitab-browser/bun.lock +224 -0
  44. package/templates/multitab-browser/electrobun.config.ts +32 -0
  45. package/templates/multitab-browser/package-lock.json +20 -0
  46. package/templates/multitab-browser/package.json +12 -0
  47. package/templates/multitab-browser/src/bun/index.ts +144 -0
  48. package/templates/multitab-browser/src/bun/tabManager.ts +200 -0
  49. package/templates/multitab-browser/src/bun/types/rpc.ts +78 -0
  50. package/templates/multitab-browser/src/mainview/index.css +487 -0
  51. package/templates/multitab-browser/src/mainview/index.html +94 -0
  52. package/templates/multitab-browser/src/mainview/index.ts +634 -0
  53. package/templates/photo-booth/README.md +108 -0
  54. package/templates/photo-booth/bun.lock +239 -0
  55. package/templates/photo-booth/electrobun.config.ts +28 -0
  56. package/templates/photo-booth/package.json +16 -0
  57. package/templates/photo-booth/src/bun/index.ts +92 -0
  58. package/templates/photo-booth/src/mainview/index.css +465 -0
  59. package/templates/photo-booth/src/mainview/index.html +124 -0
  60. package/templates/photo-booth/src/mainview/index.ts +499 -0
  61. package/tests/bun.lock +14 -0
  62. package/tests/electrobun.config.ts +45 -0
  63. package/tests/package-lock.json +36 -0
  64. package/tests/package.json +13 -0
  65. package/tests/src/bun/index.ts +100 -0
  66. package/tests/src/bun/test-runner.ts +508 -0
  67. package/tests/src/mainview/index.html +110 -0
  68. package/tests/src/mainview/index.ts +458 -0
  69. package/tests/src/mainview/styles/main.css +451 -0
  70. package/tests/src/testviews/tray-test.html +57 -0
  71. package/tests/src/testviews/webview-mask.html +114 -0
  72. package/tests/src/testviews/webview-navigation.html +36 -0
  73. package/tests/src/testviews/window-create.html +17 -0
  74. package/tests/src/testviews/window-events.html +29 -0
  75. package/tests/src/testviews/window-focus.html +37 -0
  76. package/tests/src/webviewtag/index.ts +11 -0
  77. package/templates/hello-world/electrobun.config +0 -18
@@ -0,0 +1,132 @@
1
+ export class TrayDemo {
2
+ private trays: Array<{ id: number; title: string }> = [];
3
+ private rpc: any;
4
+
5
+ render() {
6
+ return `
7
+ <div class="demo-section">
8
+ <div class="demo-header">
9
+ <span class="demo-icon">🔔</span>
10
+ <div>
11
+ <h2 class="demo-title">System Tray</h2>
12
+ <p class="demo-description">Create and manage system tray icons with menus</p>
13
+ </div>
14
+ </div>
15
+
16
+ <div class="demo-controls">
17
+ <h3>Create System Tray</h3>
18
+ <div class="control-group">
19
+ <label class="control-label">Title:</label>
20
+ <input type="text" id="tray-title" class="control-input" value="My Tray Item" style="width: 200px;">
21
+
22
+ <button class="btn btn-primary" id="create-tray">Create Tray</button>
23
+ <button class="btn btn-danger" id="remove-all-trays">Remove All Trays</button>
24
+ </div>
25
+
26
+ <div style="margin-top: 1rem; padding: 1rem; background: #f7fafc; border-radius: 0.5rem;">
27
+ <p style="color: #4a5568; font-size: 0.875rem;">
28
+ <strong>Note:</strong> System tray icons appear in your system's menu bar or notification area.
29
+ Click on the tray icon to see its menu. On macOS, look in the top-right menu bar.
30
+ </p>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="demo-results">
35
+ <div class="results-header">Active Trays (<span id="tray-count">0</span>):</div>
36
+ <div id="tray-list" class="tray-list">
37
+ <div class="no-trays" style="text-align: center; color: #718096; padding: 2rem;">
38
+ No system tray items created yet.
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ `;
44
+ }
45
+
46
+ initialize(rpc: any) {
47
+ this.rpc = rpc;
48
+
49
+ const createTrayBtn = document.getElementById('create-tray');
50
+ const removeAllTraysBtn = document.getElementById('remove-all-trays');
51
+
52
+ createTrayBtn?.addEventListener('click', async () => {
53
+ const title = (document.getElementById('tray-title') as HTMLInputElement).value;
54
+
55
+ try {
56
+ const result = await rpc.request.createTray({ title });
57
+ this.trays.push({ id: result.id, title });
58
+ this.updateTrayList();
59
+ console.log('Created tray:', result);
60
+ } catch (error) {
61
+ console.error('Error creating tray:', error);
62
+ }
63
+ });
64
+
65
+ removeAllTraysBtn?.addEventListener('click', async () => {
66
+ for (const tray of this.trays) {
67
+ try {
68
+ await rpc.request.removeTray(tray.id);
69
+ console.log('Removed tray:', tray.id);
70
+ } catch (error) {
71
+ console.error(`Error removing tray ${tray.id}:`, error);
72
+ }
73
+ }
74
+ this.trays = [];
75
+ this.updateTrayList();
76
+ });
77
+ }
78
+
79
+ private updateTrayList() {
80
+ const container = document.getElementById('tray-list');
81
+ const count = document.getElementById('tray-count');
82
+
83
+ if (!container || !count) return;
84
+
85
+ count.textContent = this.trays.length.toString();
86
+
87
+ if (this.trays.length === 0) {
88
+ container.innerHTML = `
89
+ <div class="no-trays" style="text-align: center; color: #718096; padding: 2rem;">
90
+ No system tray items created yet.
91
+ </div>
92
+ `;
93
+ return;
94
+ }
95
+
96
+ container.innerHTML = this.trays.map(tray => `
97
+ <div class="tray-item" style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 0.5rem; padding: 1rem; margin-bottom: 0.5rem;">
98
+ <div style="display: flex; justify-content: space-between; align-items: center;">
99
+ <div>
100
+ <strong>Tray ${tray.id}</strong>
101
+ <div style="color: #718096; font-size: 0.875rem;">${tray.title}</div>
102
+ <div style="color: #4299e1; font-size: 0.75rem; margin-top: 0.25rem;">
103
+ Click tray icon in menu bar to see menu
104
+ </div>
105
+ </div>
106
+ <button class="btn btn-small btn-danger remove-tray" data-tray-id="${tray.id}">Remove</button>
107
+ </div>
108
+ </div>
109
+ `).join('');
110
+
111
+ // Add remove button listeners
112
+ const removeButtons = document.querySelectorAll('.remove-tray');
113
+ removeButtons.forEach(btn => {
114
+ btn.addEventListener('click', async (e) => {
115
+ const id = parseInt((e.target as HTMLElement).getAttribute('data-tray-id') || '0');
116
+ try {
117
+ await this.rpc.request.removeTray(id);
118
+ console.log('Removed tray:', id);
119
+ this.trays = this.trays.filter(t => t.id !== id);
120
+ this.updateTrayList();
121
+ } catch (error) {
122
+ console.error(`Error removing tray ${id}:`, error);
123
+ }
124
+ });
125
+ });
126
+ }
127
+
128
+ // Handle events from the backend
129
+ onTrayClicked(data: { id: number; action: string }) {
130
+ console.log('Tray clicked:', data);
131
+ }
132
+ }
@@ -0,0 +1,411 @@
1
+ export class WebViewDemo {
2
+ private rpc: any;
3
+
4
+ render() {
5
+ return `
6
+ <div class="demo-section">
7
+ <div class="demo-header">
8
+ <span class="demo-icon">🌐</span>
9
+ <div>
10
+ <h2 class="demo-title">WebView Browser</h2>
11
+ <p class="demo-description">Embedded browser with navigation, transparency, masking, and advanced features</p>
12
+ </div>
13
+ </div>
14
+
15
+ <div class="demo-controls" style="padding: 1rem;">
16
+ <!-- Browser-like navigation bar -->
17
+ <div style="display: flex; gap: 0.5rem; align-items: center; padding: 0.5rem; background: #f7fafc; border: 1px solid #e2e8f0; border-radius: 0.5rem; margin-bottom: 1rem;">
18
+ <button class="btn btn-sm" id="webview-back" style="padding: 0.25rem 0.5rem;">←</button>
19
+ <button class="btn btn-sm" id="webview-forward" style="padding: 0.25rem 0.5rem;">→</button>
20
+ <button class="btn btn-sm" id="webview-reload" style="padding: 0.25rem 0.5rem;">🔄</button>
21
+ <input type="text" id="url-bar" class="control-input" style="flex: 1; padding: 0.25rem 0.5rem; font-size: 0.875rem;" placeholder="Enter URL or click quick nav buttons" />
22
+ <button class="btn btn-primary btn-sm" id="url-go" style="padding: 0.25rem 0.75rem;">Go</button>
23
+ </div>
24
+
25
+ <!-- Quick navigation buttons -->
26
+ <div style="display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap;">
27
+ <button class="btn btn-sm" id="nav-eggbun">eggbun.sh</button>
28
+ <button class="btn btn-sm" id="nav-electrobun">electrobun.dev</button>
29
+ <button class="btn btn-sm" id="nav-github">GitHub</button>
30
+ <button class="btn btn-sm" id="nav-wikipedia">Wikipedia Random</button>
31
+ </div>
32
+
33
+ <!-- WebView control buttons -->
34
+ <div style="display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap;">
35
+ <button class="btn btn-secondary btn-sm" id="toggle-transparent">Toggle Transparent</button>
36
+ <button class="btn btn-secondary btn-sm" id="toggle-passthrough">Toggle Passthrough</button>
37
+ <button class="btn btn-secondary btn-sm" id="toggle-hidden">Toggle Hidden</button>
38
+ <button class="btn btn-secondary btn-sm" id="add-mask">Add Mask</button>
39
+ <button class="btn btn-secondary btn-sm" id="remove-mask">Remove Mask</button>
40
+ </div>
41
+
42
+ <!-- Draggable element for testing -->
43
+ <div style="display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap;">
44
+ <div id="draggable-test" draggable="true" style="padding: 10px; width: 120px; height: 80px; border: 3px dashed #4a5568; cursor: move; background: white; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.875rem; text-align: center;">
45
+ <div>Drag me over the webview</div>
46
+ <div id="drag-coords" style="font-size: 0.75rem; color: #4a5568; margin-top: 0.25rem;">x: 0, y: 0</div>
47
+ </div>
48
+ </div>
49
+
50
+ </div>
51
+
52
+ <div class="demo-results" style="position: relative;">
53
+ <!-- Main webview container with mask overlay elements -->
54
+ <div style="position: relative; width: 100%; height: 500px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 0.5rem;">
55
+
56
+ <!-- Overlay squares for mask testing -->
57
+ <div class="element-to-mask" style="position: absolute; z-index: 10; top: -30px; right: 50px; width: 120px; height: 110px; background: black; color: white; padding: 10px; display: flex; align-items: center; justify-content: center; font-weight: bold; border: 10px solid firebrick">
58
+ Mask Layer 1
59
+ </div>
60
+
61
+ <div class="element-to-mask" style="position: absolute; z-index: 10; top: -20px; right: 160px; width: 120px; height: 130px; background: green; color: white; padding: 10px; display: flex; align-items: center; justify-content: center; font-weight: bold; border: 5px solid cadetblue;">
62
+ Mask Layer 2
63
+ </div>
64
+
65
+ <!-- Main webview -->
66
+ <electrobun-webview
67
+ id="main-webview"
68
+ style="width: 100%; height: 100%;"
69
+ src="https://electrobun.dev"
70
+ preload=""
71
+ renderer="cef">
72
+ </electrobun-webview>
73
+ </div>
74
+
75
+
76
+
77
+ <!-- Compact event log -->
78
+ <div style="margin-top: 1rem;">
79
+ <div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 0.5rem; padding: 0.5rem;">
80
+ <div style="font-weight: 500; padding: 0.25rem;">WebView Events Log</div>
81
+ <div id="webview-events" style="max-height: 150px; overflow-y: auto; margin-top: 0.5rem;">
82
+ <div class="no-events" style="text-align: center; color: #718096; font-size: 0.875rem;">
83
+ Events will appear here
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ <!-- Additional webviews for testing partitions -->
90
+ <div style="margin-top: 1rem;">
91
+ <div style="font-weight: 500; padding: 0.5rem; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 0.5rem;">Session Partition Testing (Wikipedia)</div>
92
+
93
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-top: 0.5rem;">
94
+ <div style="border: 1px solid #e2e8f0; border-radius: 0.25rem; overflow: hidden;">
95
+ <div style="background: #f7fafc; padding: 0.25rem 0.5rem; font-size: 0.75rem;">
96
+ Shared (Default)
97
+ </div>
98
+ <electrobun-webview
99
+ id="partition-default"
100
+ style="width: 100%; height: 200px;"
101
+ src="https://en.wikipedia.org/wiki/Main_Page"
102
+ renderer="cef"
103
+ partition="">
104
+ </electrobun-webview>
105
+ </div>
106
+
107
+ <div style="border: 1px solid #e2e8f0; border-radius: 0.25rem; overflow: hidden;">
108
+ <div style="background: #f7fafc; padding: 0.25rem 0.5rem; font-size: 0.75rem;">
109
+ Shared (Also Default)
110
+ </div>
111
+ <electrobun-webview
112
+ id="partition-default2"
113
+ style="width: 100%; height: 200px;"
114
+ src="https://en.wikipedia.org/wiki/Main_Page"
115
+ renderer="cef"
116
+ partition="">
117
+ </electrobun-webview>
118
+ </div>
119
+
120
+ <div style="border: 1px solid #e2e8f0; border-radius: 0.25rem; overflow: hidden;">
121
+ <div style="background: #eff6ff; padding: 0.25rem 0.5rem; font-size: 0.75rem;">
122
+ Isolated (persist:user1)
123
+ </div>
124
+ <electrobun-webview
125
+ id="partition-user1"
126
+ style="width: 100%; height: 200px;"
127
+ src="https://en.wikipedia.org/wiki/Main_Page"
128
+ renderer="cef"
129
+ partition="persist:user1">
130
+ </electrobun-webview>
131
+ </div>
132
+
133
+ <div style="border: 1px solid #e2e8f0; border-radius: 0.25rem; overflow: hidden;">
134
+ <div style="background: #f0fdf4; padding: 0.25rem 0.5rem; font-size: 0.75rem;">
135
+ Isolated (persist:user2)
136
+ </div>
137
+ <electrobun-webview
138
+ id="partition-user2"
139
+ style="width: 100%; height: 200px;"
140
+ src="https://en.wikipedia.org/wiki/Main_Page"
141
+ renderer="cef"
142
+ partition="persist:user2">
143
+ </electrobun-webview>
144
+ </div>
145
+ </div>
146
+
147
+ <div style="margin-top: 0.5rem; padding: 0.5rem; background: #ebf8ff; border: 1px solid #90cdf4; border-radius: 0.25rem; font-size: 0.75rem; color: #2b6cb0;">
148
+ <strong>How to test partitions:</strong> Click on any Wikipedia link in one webview. The two "Shared" webviews will both show visited links in purple, while the "Isolated" webviews maintain separate browsing history. Try logging in or changing settings - shared partitions share everything, isolated ones are independent.
149
+ </div>
150
+ </div>
151
+
152
+ <!-- HTML content test -->
153
+ <div style="margin-top: 1rem;">
154
+ <div style="font-weight: 500; padding: 0.5rem; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 0.5rem;">Inline HTML WebView</div>
155
+ <div style="margin-top: 0.5rem; border: 1px solid #e2e8f0; border-radius: 0.25rem; overflow: hidden;">
156
+ <electrobun-webview
157
+ style="width: 100%; height: 150px;"
158
+ preload="window.onload = () => {document.body.innerHTML += '<br>Hello from preload script!';}"
159
+ html="<html><body style='padding: 20px; font-family: system-ui;'><h2>WebView with inline HTML</h2><p>This webview is rendered from HTML string instead of URL.</p></body></html>">
160
+ </electrobun-webview>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ `;
166
+ }
167
+
168
+ initialize(rpc: any) {
169
+ this.rpc = rpc;
170
+ this.setupEventListeners();
171
+ this.setupWebViewEvents();
172
+ }
173
+
174
+ private setupEventListeners() {
175
+ const webview = document.getElementById('main-webview') as any;
176
+ const urlBar = document.getElementById('url-bar') as HTMLInputElement;
177
+
178
+ // Safety check: If webview doesn't exist, don't set up listeners
179
+ if (!webview) {
180
+ console.warn('WebView element not found during event listener setup');
181
+ return;
182
+ }
183
+
184
+ // Navigation controls
185
+ document.getElementById('webview-back')?.addEventListener('click', () => {
186
+ if (webview && typeof webview.goBack === 'function') {
187
+ webview.goBack();
188
+ this.addWebViewEvent('← Back');
189
+ }
190
+ });
191
+
192
+ document.getElementById('webview-forward')?.addEventListener('click', () => {
193
+ if (webview && typeof webview.goForward === 'function') {
194
+ webview.goForward();
195
+ this.addWebViewEvent('→ Forward');
196
+ }
197
+ });
198
+
199
+ document.getElementById('webview-reload')?.addEventListener('click', () => {
200
+ if (webview && typeof webview.reload === 'function') {
201
+ webview.reload();
202
+ this.addWebViewEvent('🔄 Reload');
203
+ }
204
+ });
205
+
206
+ // URL bar navigation
207
+ const navigateToUrl = () => {
208
+ const url = urlBar?.value?.trim();
209
+ if (url && webview && typeof webview.setAttribute === 'function') {
210
+ // Add protocol if missing
211
+ const finalUrl = url.startsWith('http://') || url.startsWith('https://')
212
+ ? url
213
+ : `https://${url}`;
214
+ webview.setAttribute('src', finalUrl);
215
+ this.addWebViewEvent(`Navigate: ${finalUrl}`);
216
+ }
217
+ };
218
+
219
+ document.getElementById('url-go')?.addEventListener('click', navigateToUrl);
220
+ urlBar?.addEventListener('keypress', (e) => {
221
+ if (e.key === 'Enter') {
222
+ navigateToUrl();
223
+ }
224
+ });
225
+
226
+ // Quick navigation
227
+ document.getElementById('nav-eggbun')?.addEventListener('click', () => {
228
+ if (webview && typeof webview.setAttribute === 'function' && urlBar) {
229
+ webview.setAttribute('src', 'https://eggbun.sh');
230
+ urlBar.value = 'https://eggbun.sh';
231
+ this.addWebViewEvent('Nav: eggbun.sh');
232
+ }
233
+ });
234
+
235
+ document.getElementById('nav-electrobun')?.addEventListener('click', () => {
236
+ if (webview && typeof webview.setAttribute === 'function' && urlBar) {
237
+ webview.setAttribute('src', 'https://electrobun.dev');
238
+ urlBar.value = 'https://electrobun.dev';
239
+ this.addWebViewEvent('Nav: electrobun.dev');
240
+ }
241
+ });
242
+
243
+ document.getElementById('nav-github')?.addEventListener('click', () => {
244
+ if (webview && typeof webview.setAttribute === 'function' && urlBar) {
245
+ webview.setAttribute('src', 'https://github.com/blackboardsh/electrobun');
246
+ urlBar.value = 'https://github.com/blackboardsh/electrobun';
247
+ this.addWebViewEvent('Nav: GitHub');
248
+ }
249
+ });
250
+
251
+ document.getElementById('nav-wikipedia')?.addEventListener('click', () => {
252
+ if (webview && typeof webview.setAttribute === 'function' && urlBar) {
253
+ webview.setAttribute('src', 'https://en.wikipedia.org/wiki/Special:Random');
254
+ urlBar.value = 'https://en.wikipedia.org/wiki/Special:Random';
255
+ this.addWebViewEvent('Nav: Wikipedia Random');
256
+ }
257
+ });
258
+
259
+ // WebView effects
260
+ let isTransparent = false;
261
+ let isPassthrough = false;
262
+ let isHidden = false;
263
+
264
+ document.getElementById('toggle-transparent')?.addEventListener('click', () => {
265
+ if (webview && typeof webview.toggleTransparent === 'function') {
266
+ webview.toggleTransparent();
267
+ isTransparent = !isTransparent;
268
+ this.addWebViewEvent(`Transparent: ${isTransparent ? 'ON' : 'OFF'}`);
269
+ }
270
+ });
271
+
272
+ document.getElementById('toggle-passthrough')?.addEventListener('click', () => {
273
+ if (webview && typeof webview.togglePassthrough === 'function') {
274
+ webview.togglePassthrough();
275
+ isPassthrough = !isPassthrough;
276
+ this.addWebViewEvent(`Passthrough: ${isPassthrough ? 'ON' : 'OFF'}`);
277
+ }
278
+ });
279
+
280
+ document.getElementById('toggle-hidden')?.addEventListener('click', () => {
281
+ if (webview && typeof webview.toggleHidden === 'function') {
282
+ webview.toggleHidden();
283
+ isHidden = !isHidden;
284
+ this.addWebViewEvent(`Hidden: ${isHidden ? 'ON' : 'OFF'}`);
285
+ }
286
+ });
287
+
288
+ // Element masking
289
+ let maskActive = false;
290
+ document.getElementById('add-mask')?.addEventListener('click', () => {
291
+ if (webview && typeof webview.addMaskSelector === 'function') {
292
+ webview.addMaskSelector('.element-to-mask');
293
+ maskActive = true;
294
+ this.addWebViewEvent('Mask added: .element-to-mask');
295
+ }
296
+ });
297
+
298
+ document.getElementById('remove-mask')?.addEventListener('click', () => {
299
+ if (webview && typeof webview.removeMaskSelector === 'function') {
300
+ webview.removeMaskSelector('.element-to-mask');
301
+ maskActive = false;
302
+ this.addWebViewEvent('Mask removed: .element-to-mask');
303
+ }
304
+ });
305
+
306
+ // Draggable element position tracking
307
+ const draggable = document.getElementById('draggable-test');
308
+ const dragCoords = document.getElementById('drag-coords');
309
+
310
+ if (draggable && dragCoords) {
311
+ draggable.addEventListener('dragstart', (e: DragEvent) => {
312
+ // Store initial offset
313
+ const rect = draggable.getBoundingClientRect();
314
+ (e.dataTransfer as any).effectAllowed = 'move';
315
+ });
316
+
317
+ // Update coordinates during drag
318
+ document.addEventListener('dragover', (e: DragEvent) => {
319
+ e.preventDefault();
320
+ if (dragCoords) {
321
+ dragCoords.textContent = `x: ${e.clientX}, y: ${e.clientY}`;
322
+ }
323
+ });
324
+
325
+ draggable.addEventListener('dragend', (e: DragEvent) => {
326
+ if (dragCoords) {
327
+ dragCoords.textContent = `x: ${e.clientX}, y: ${e.clientY}`;
328
+ }
329
+ });
330
+ }
331
+
332
+ // Add mask selectors to all webviews (with safety checks)
333
+ document.querySelectorAll('electrobun-webview').forEach((w: any) => {
334
+ if (w && typeof w.addMaskSelector === 'function') {
335
+ w.addMaskSelector("header");
336
+ }
337
+ });
338
+
339
+ }
340
+
341
+ private setupWebViewEvents() {
342
+ const webview = document.getElementById('main-webview') as any;
343
+ const urlBar = document.getElementById('url-bar') as HTMLInputElement;
344
+
345
+ if (webview && typeof webview.on === 'function') {
346
+ // Use the on() method as shown in the old playground
347
+ webview.on('did-navigate', (e: any) => {
348
+ const url = e.detail?.url || 'unknown';
349
+ if (urlBar) {
350
+ urlBar.value = url;
351
+ }
352
+ this.addWebViewEvent(`did-navigate: ${url}`);
353
+ });
354
+
355
+ webview.on('did-navigate-in-page', (e: any) => {
356
+ this.addWebViewEvent(`in-page-nav: ${e.detail?.url || 'unknown'}`);
357
+ });
358
+
359
+ webview.on('did-commit-navigation', (e: any) => {
360
+ this.addWebViewEvent(`commit-nav: ${e.detail?.url || 'unknown'}`);
361
+ });
362
+
363
+ webview.on('dom-ready', () => {
364
+ this.addWebViewEvent(`DOM ready`);
365
+ });
366
+
367
+ webview.on('new-window-open', (e: any) => {
368
+ this.addWebViewEvent(`new-window: ${e.detail?.url || 'unknown'}`);
369
+ });
370
+
371
+ // Also try addEventListener for compatibility
372
+ if (typeof webview.addEventListener === 'function') {
373
+ webview.addEventListener('did-navigate', (e: any) => {
374
+ const url = e.detail?.url || 'unknown';
375
+ if (urlBar) {
376
+ urlBar.value = url;
377
+ }
378
+ this.addWebViewEvent(`did-navigate: ${url}`);
379
+ });
380
+ }
381
+ }
382
+ }
383
+
384
+ private addWebViewEvent(event: string) {
385
+ const container = document.getElementById('webview-events');
386
+ if (!container) return;
387
+
388
+ const time = new Date().toLocaleTimeString();
389
+ const eventHtml = `
390
+ <div style="border-left: 2px solid #0ea5e9; padding: 0.25rem 0.5rem; margin-bottom: 0.25rem; font-size: 0.75rem;">
391
+ <span style="color: #64748b;">${time}</span>
392
+ <span style="color: #0369a1; margin-left: 0.5rem;">${event}</span>
393
+ </div>
394
+ `;
395
+
396
+ // Remove "no events" message if it exists
397
+ const noEvents = container.querySelector('.no-events');
398
+ if (noEvents) {
399
+ container.innerHTML = '';
400
+ }
401
+
402
+ // Add new event at the top
403
+ container.insertAdjacentHTML('afterbegin', eventHtml);
404
+
405
+ // Keep only last 10 events
406
+ const events = container.children;
407
+ while (events.length > 10) {
408
+ container.removeChild(events[events.length - 1]);
409
+ }
410
+ }
411
+ }