electron-menubar 1.0.1 → 10.0.2

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.
package/lib/index.mjs ADDED
@@ -0,0 +1,525 @@
1
+ import { BrowserWindow, Tray, app, globalShortcut, screen } from "electron";
2
+ import { EventEmitter } from "node:events";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import url from "node:url";
6
+ //#region src/Positioner.ts
7
+ /**
8
+ * Computes `{x, y}` coordinates for placing a {@link BrowserWindow} at a named
9
+ * position, optionally relative to a tray icon's bounds. Ported from
10
+ * `electron-positioner@4.1.0` to drop the unmaintained runtime dependency.
11
+ */
12
+ var Positioner = class {
13
+ browserWindow;
14
+ constructor(browserWindow) {
15
+ this.browserWindow = browserWindow;
16
+ }
17
+ calculate(position, trayBounds) {
18
+ if (!position) throw new TypeError("Positioner.calculate: a `position` argument is required.");
19
+ const screenSize = trayBounds ? screen.getDisplayMatching(trayBounds).workArea : screen.getDisplayNearestPoint(screen.getCursorScreenPoint()).workArea;
20
+ const [windowWidth, windowHeight] = this.browserWindow.getSize();
21
+ const trayX = trayBounds?.x ?? NaN;
22
+ const trayWidth = trayBounds?.width ?? NaN;
23
+ const positions = {
24
+ trayLeft: {
25
+ x: Math.floor(trayX),
26
+ y: screenSize.y
27
+ },
28
+ trayBottomLeft: {
29
+ x: Math.floor(trayX),
30
+ y: Math.floor(screenSize.height - (windowHeight - screenSize.y))
31
+ },
32
+ trayRight: {
33
+ x: Math.floor(trayX - windowWidth + trayWidth),
34
+ y: screenSize.y
35
+ },
36
+ trayBottomRight: {
37
+ x: Math.floor(trayX - windowWidth + trayWidth),
38
+ y: Math.floor(screenSize.height - (windowHeight - screenSize.y))
39
+ },
40
+ trayCenter: {
41
+ x: Math.floor(trayX - windowWidth / 2 + trayWidth / 2),
42
+ y: screenSize.y
43
+ },
44
+ trayBottomCenter: {
45
+ x: Math.floor(trayX - windowWidth / 2 + trayWidth / 2),
46
+ y: Math.floor(screenSize.height - (windowHeight - screenSize.y))
47
+ },
48
+ topLeft: {
49
+ x: screenSize.x,
50
+ y: screenSize.y
51
+ },
52
+ topRight: {
53
+ x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),
54
+ y: screenSize.y
55
+ },
56
+ bottomLeft: {
57
+ x: screenSize.x,
58
+ y: Math.floor(screenSize.height - (windowHeight - screenSize.y))
59
+ },
60
+ bottomRight: {
61
+ x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),
62
+ y: Math.floor(screenSize.height - (windowHeight - screenSize.y))
63
+ },
64
+ topCenter: {
65
+ x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),
66
+ y: screenSize.y
67
+ },
68
+ bottomCenter: {
69
+ x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),
70
+ y: Math.floor(screenSize.height - (windowHeight - screenSize.y))
71
+ },
72
+ leftCenter: {
73
+ x: screenSize.x,
74
+ y: screenSize.y + Math.floor(screenSize.height / 2) - Math.floor(windowHeight / 2)
75
+ },
76
+ rightCenter: {
77
+ x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),
78
+ y: screenSize.y + Math.floor(screenSize.height / 2) - Math.floor(windowHeight / 2)
79
+ },
80
+ center: {
81
+ x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),
82
+ y: Math.floor((screenSize.height + screenSize.y) / 2 - windowHeight / 2)
83
+ }
84
+ };
85
+ const coords = positions[position];
86
+ if (position.startsWith("tray")) {
87
+ if (coords.x + windowWidth > screenSize.width + screenSize.x) return {
88
+ x: positions.topRight.x,
89
+ y: coords.y
90
+ };
91
+ }
92
+ return coords;
93
+ }
94
+ };
95
+ //#endregion
96
+ //#region src/util/cleanOptions.ts
97
+ /**
98
+ * @ignore
99
+ */
100
+ /** */
101
+ const DEFAULT_WINDOW_HEIGHT = 400;
102
+ const DEFAULT_WINDOW_WIDTH = 400;
103
+ /**
104
+ * Take as input some options, and return a sanitized version of it.
105
+ *
106
+ * @param opts - The options to clean.
107
+ * @ignore
108
+ */
109
+ function cleanOptions(opts) {
110
+ const options = { ...opts };
111
+ if (options.activateWithApp === void 0) options.activateWithApp = true;
112
+ if (options.ignoreDoubleClickEvents === void 0) options.ignoreDoubleClickEvents = true;
113
+ if (!options.dir) options.dir = app.getAppPath();
114
+ if (!path.isAbsolute(options.dir)) options.dir = path.resolve(options.dir);
115
+ if (options.index === void 0) options.index = url.format({
116
+ pathname: path.join(options.dir, "index.html"),
117
+ protocol: "file:",
118
+ slashes: true
119
+ });
120
+ options.loadUrlOptions = options.loadUrlOptions || {};
121
+ options.tooltip = options.tooltip || "";
122
+ if (!options.browserWindow) options.browserWindow = {};
123
+ options.browserWindow.width = options.browserWindow.width !== void 0 ? options.browserWindow.width : DEFAULT_WINDOW_WIDTH;
124
+ options.browserWindow.height = options.browserWindow.height !== void 0 ? options.browserWindow.height : DEFAULT_WINDOW_HEIGHT;
125
+ return options;
126
+ }
127
+ //#endregion
128
+ //#region src/util/getWindowPosition.ts
129
+ /**
130
+ * Utilities to get taskbar position and consequently menubar's position
131
+ */
132
+ /** */
133
+ const isLinux = process.platform === "linux";
134
+ const trayToScreenRects = (tray) => {
135
+ const { workArea, bounds: screenBounds } = screen.getDisplayMatching(tray.getBounds());
136
+ workArea.x -= screenBounds.x;
137
+ workArea.y -= screenBounds.y;
138
+ return [screenBounds, workArea];
139
+ };
140
+ /**
141
+ * Determine taskbard location: "top", "bottom", "left" or "right".
142
+ *
143
+ * Only tested on Windows for now, and only used in Windows.
144
+ *
145
+ * @param tray - The Electron Tray instance.
146
+ */
147
+ function taskbarLocation(tray) {
148
+ const [screenBounds, workArea] = trayToScreenRects(tray);
149
+ if (workArea.x > 0) {
150
+ if (isLinux && workArea.y > 0) return "top";
151
+ return "left";
152
+ }
153
+ if (workArea.y > 0) return "top";
154
+ if (workArea.width < screenBounds.width) return "right";
155
+ return "bottom";
156
+ }
157
+ /**
158
+ * Depending on where the taskbar is, determine where the window should be
159
+ * positioned.
160
+ *
161
+ * @param tray - The Electron Tray instance.
162
+ */
163
+ function getWindowPosition(tray) {
164
+ switch (process.platform) {
165
+ case "darwin": return "trayCenter";
166
+ case "linux":
167
+ case "win32": {
168
+ const traySide = taskbarLocation(tray);
169
+ if (traySide === "top") return isLinux ? "topRight" : "trayCenter";
170
+ if (traySide === "bottom") return "bottomRight";
171
+ if (traySide === "left") return "leftCenter";
172
+ if (traySide === "right") return "bottomRight";
173
+ }
174
+ }
175
+ return "topRight";
176
+ }
177
+ //#endregion
178
+ //#region src/Menubar.ts
179
+ /**
180
+ * The main Menubar class.
181
+ */
182
+ var Menubar = class extends EventEmitter {
183
+ _app;
184
+ _browserWindow;
185
+ _contextMenu;
186
+ _blurTimeout = null;
187
+ _isDestroyed;
188
+ _isQuitting;
189
+ _isVisible;
190
+ _cachedBounds;
191
+ _options;
192
+ _positioner;
193
+ _shortcut;
194
+ _rightClickContextMenuBound = false;
195
+ _tray;
196
+ constructor(app, options) {
197
+ super();
198
+ this._app = app;
199
+ this._options = cleanOptions(options);
200
+ this._isDestroyed = false;
201
+ this._isQuitting = false;
202
+ this._isVisible = false;
203
+ app.on("before-quit", this.onBeforeQuit);
204
+ if (app.isReady()) process.nextTick(this.onAppReady);
205
+ else app.on("ready", this.onAppReady);
206
+ }
207
+ /**
208
+ * The Electron [App](https://electronjs.org/docs/api/app)
209
+ * instance.
210
+ */
211
+ get app() {
212
+ return this._app;
213
+ }
214
+ /**
215
+ * The {@link Positioner} instance used to compute where the menubar window
216
+ * should appear on screen. Available after the `after-create-window` event.
217
+ */
218
+ get positioner() {
219
+ if (!this._positioner) throw new Error("Please access `this.positioner` after the `after-create-window` event has fired.");
220
+ return this._positioner;
221
+ }
222
+ /**
223
+ * The Electron [Tray](https://electronjs.org/docs/api/tray) instance.
224
+ */
225
+ get tray() {
226
+ if (!this._tray) throw new Error("Please access `this.tray` after the `ready` event has fired.");
227
+ return this._tray;
228
+ }
229
+ /**
230
+ * The Electron [BrowserWindow](https://electronjs.org/docs/api/browser-window)
231
+ * instance, if it's present.
232
+ */
233
+ get window() {
234
+ return this._browserWindow;
235
+ }
236
+ /**
237
+ * Tear down the menubar instance: destroy the window, remove the tray, and
238
+ * detach all listeners. Subsequent clicks on the tray will be no-ops until a
239
+ * new {@link Menubar} instance is created.
240
+ */
241
+ destroy() {
242
+ if (this.isDestroyed()) return;
243
+ this._isDestroyed = true;
244
+ if (this._shortcut) {
245
+ globalShortcut.unregister(this._shortcut);
246
+ this._shortcut = void 0;
247
+ }
248
+ if (this._browserWindow) {
249
+ this._browserWindow.destroy();
250
+ this._browserWindow = void 0;
251
+ }
252
+ if (this._tray) {
253
+ for (const event of [
254
+ "click",
255
+ "right-click",
256
+ "double-click"
257
+ ]) this._tray.removeListener(event, this.clicked);
258
+ this._tray.setToolTip("");
259
+ this._tray = void 0;
260
+ }
261
+ this._app.removeListener("ready", this.onAppReady);
262
+ this._app.removeListener("activate", this.onAppActivate);
263
+ this._app.removeListener("before-quit", this.onBeforeQuit);
264
+ }
265
+ /**
266
+ * Whether {@link destroy} has been called on this menubar instance.
267
+ */
268
+ isDestroyed() {
269
+ return this._isDestroyed;
270
+ }
271
+ /**
272
+ * Retrieve a menubar option.
273
+ *
274
+ * @param key - The option key to retrieve, see {@link Options}.
275
+ */
276
+ getOption(key) {
277
+ return this._options[key];
278
+ }
279
+ /**
280
+ * Hide the menubar window.
281
+ */
282
+ hideWindow() {
283
+ if (!this._browserWindow || !this._isVisible) return;
284
+ this.emit("hide");
285
+ this._browserWindow.hide();
286
+ this.emit("after-hide");
287
+ this._isVisible = false;
288
+ if (this._blurTimeout) {
289
+ clearTimeout(this._blurTimeout);
290
+ this._blurTimeout = null;
291
+ }
292
+ this.refreshLinuxContextMenu();
293
+ }
294
+ /**
295
+ * Register a global keyboard accelerator that toggles the menubar window.
296
+ * Replaces any previously registered shortcut owned by this Menubar.
297
+ * Pass `undefined` to clear the current shortcut without registering a new
298
+ * one. Returns whether the registration succeeded.
299
+ *
300
+ * @param accelerator - An Electron
301
+ * [Accelerator](https://electronjs.org/docs/api/accelerator) string, or
302
+ * `undefined` to clear.
303
+ */
304
+ setGlobalShortcut(accelerator) {
305
+ if (this._shortcut) {
306
+ globalShortcut.unregister(this._shortcut);
307
+ this._shortcut = void 0;
308
+ }
309
+ this._options.globalShortcut = accelerator;
310
+ if (!accelerator) return true;
311
+ const ok = globalShortcut.register(accelerator, () => this.toggleWindow());
312
+ if (ok) this._shortcut = accelerator;
313
+ return ok;
314
+ }
315
+ /**
316
+ * Toggle the menubar window: hide it if visible, show it otherwise.
317
+ * Resolves once the window finishes showing or hiding.
318
+ */
319
+ async toggleWindow() {
320
+ if (this._browserWindow && this._isVisible) {
321
+ this.hideWindow();
322
+ return;
323
+ }
324
+ await this.showWindow();
325
+ }
326
+ /**
327
+ * Re-center the menubar window over the tray icon. Convenience wrapper for
328
+ * `positioner.move('trayCenter', tray.getBounds())` that's safe to call
329
+ * after the `after-create-window` event. No-op if the window doesn't
330
+ * exist yet.
331
+ */
332
+ recenterOnTray() {
333
+ if (!this._browserWindow || !this._tray) return;
334
+ const bounds = this._tray.getBounds();
335
+ const { x, y } = this.positioner.calculate("trayCenter", bounds);
336
+ this._browserWindow.setPosition(Math.round(x), Math.round(y));
337
+ }
338
+ /**
339
+ * Replace the tray context menu after construction. On Linux this also
340
+ * re-publishes the menu to the SNI host, which is required after mutating
341
+ * items in-place since libappindicator caches the previous serialization.
342
+ * On macOS/Windows the right-click popup handler reads the current menu
343
+ * reference, so swapping or clearing here takes effect immediately.
344
+ *
345
+ * @param menu - The new menu, or `null` to clear it.
346
+ */
347
+ setContextMenu(menu) {
348
+ this._contextMenu = menu ?? void 0;
349
+ this._options.contextMenu = menu ?? void 0;
350
+ if (!this._tray) return;
351
+ if (process.platform === "linux") {
352
+ this._tray.setContextMenu(menu);
353
+ return;
354
+ }
355
+ if (menu && !this._rightClickContextMenuBound) this.bindRightClickContextMenu();
356
+ }
357
+ /**
358
+ * Change an option after menubar is created.
359
+ *
360
+ * @param key - The option key to modify, see {@link Options}.
361
+ * @param value - The value to set.
362
+ */
363
+ setOption(key, value) {
364
+ this._options[key] = value;
365
+ }
366
+ /**
367
+ * Show the menubar window.
368
+ *
369
+ * @param trayPos - The bounds to show the window in.
370
+ */
371
+ async showWindow(trayPos) {
372
+ if (!this.tray) throw new Error("Tray should have been instantiated by now");
373
+ if (!this._browserWindow) await this.createWindow();
374
+ if (!this._browserWindow) throw new Error("Window has been initialized just above. qed.");
375
+ this.emit("show");
376
+ if (trayPos && trayPos.x !== 0) this._cachedBounds = trayPos;
377
+ else if (!this._cachedBounds && this.tray.getBounds) this._cachedBounds = this.tray.getBounds();
378
+ this.positionWindow();
379
+ this._browserWindow.show();
380
+ this._isVisible = true;
381
+ this.emit("after-show");
382
+ this.refreshLinuxContextMenu();
383
+ }
384
+ /**
385
+ * Compute and apply the tray-anchored position of the browser window. Safe
386
+ * to call any time after `createWindow` has run — invoked from
387
+ * {@link showWindow} on every show, and from the window's `resize` event so
388
+ * `setSize` calls reposition the window correctly.
389
+ */
390
+ positionWindow = () => {
391
+ if (!this._browserWindow || !this._tray) return;
392
+ if (["win32", "linux"].includes(process.platform)) this._options.windowPosition = getWindowPosition(this._tray);
393
+ const trayPos = this._cachedBounds ?? this._tray.getBounds?.();
394
+ let noBoundsPosition;
395
+ if ((trayPos === void 0 || trayPos.x === 0) && this._options.windowPosition?.startsWith("tray")) noBoundsPosition = process.platform === "win32" ? "bottomRight" : "topRight";
396
+ const position = this.positioner.calculate(this._options.windowPosition || noBoundsPosition, trayPos);
397
+ const x = this._options.browserWindow.x !== void 0 ? this._options.browserWindow.x : position.x;
398
+ const y = this._options.browserWindow.y !== void 0 ? this._options.browserWindow.y : position.y;
399
+ this._browserWindow.setPosition(Math.round(x), Math.round(y));
400
+ };
401
+ async appReady() {
402
+ if (this.app.dock && !this._options.showDockIcon) this.app.dock.hide();
403
+ if (this._options.activateWithApp) this.app.on("activate", this.onAppActivate);
404
+ let trayImage = this._options.icon || path.join(this._options.dir, "IconTemplate.png");
405
+ if (typeof trayImage === "string" && !fs.existsSync(trayImage)) trayImage = path.join(__dirname, "..", "assets", "IconTemplate.png");
406
+ const trigger = this._options.trigger ?? (this._options.showOnRightClick ? "right-click" : "click");
407
+ this._tray = this._options.tray || new Tray(trayImage);
408
+ if (!this.tray) throw new Error("Tray has been initialized above");
409
+ if (trigger !== "none") {
410
+ this.tray.on(trigger, this.clicked);
411
+ this.tray.on("double-click", this.clicked);
412
+ }
413
+ if (process.platform === "darwin" && this._options.ignoreDoubleClickEvents) this.tray.setIgnoreDoubleClickEvents(true);
414
+ this.tray.setToolTip(this._options.tooltip);
415
+ if (this._options.contextMenu) this.bindContextMenu(this._options.contextMenu);
416
+ if (this._options.globalShortcut) this.setGlobalShortcut(this._options.globalShortcut);
417
+ if (!this._options.windowPosition) this._options.windowPosition = getWindowPosition(this.tray);
418
+ if (this._options.preloadWindow) await this.createWindow();
419
+ this.emit("ready");
420
+ }
421
+ /**
422
+ * Callback on tray icon click or double-click.
423
+ *
424
+ * @param e
425
+ * @param bounds
426
+ */
427
+ clicked = async (event, bounds) => {
428
+ if (event && (event.shiftKey || event.ctrlKey || event.metaKey)) return this.hideWindow();
429
+ if (this._blurTimeout) clearInterval(this._blurTimeout);
430
+ if (this._browserWindow && this._isVisible) return this.hideWindow();
431
+ this._cachedBounds = bounds || this._cachedBounds;
432
+ await this.showWindow(this._cachedBounds);
433
+ };
434
+ onAppActivate = (_event, hasVisibleWindows) => {
435
+ if (!hasVisibleWindows) this.showWindow().catch(console.error);
436
+ };
437
+ onBeforeQuit = () => {
438
+ this._isQuitting = true;
439
+ };
440
+ bindContextMenu(menu) {
441
+ this._contextMenu = menu;
442
+ if (process.platform === "linux") {
443
+ this.tray.setContextMenu(menu);
444
+ return;
445
+ }
446
+ this.bindRightClickContextMenu();
447
+ }
448
+ bindRightClickContextMenu() {
449
+ this.tray.on("right-click", (_event, bounds) => {
450
+ const current = this._contextMenu;
451
+ if (!current) return;
452
+ this.tray.popUpContextMenu(current, {
453
+ x: bounds.x,
454
+ y: bounds.y
455
+ });
456
+ });
457
+ this._rightClickContextMenuBound = true;
458
+ }
459
+ refreshLinuxContextMenu() {
460
+ if (process.platform === "linux" && this._contextMenu && this._tray && !this._tray.isDestroyed?.()) this._tray.setContextMenu(this._contextMenu);
461
+ }
462
+ onAppReady = () => {
463
+ if (this._isDestroyed) return;
464
+ this.appReady().catch((err) => console.error("menubar: ", err));
465
+ };
466
+ async createWindow() {
467
+ this.emit("create-window");
468
+ const defaults = {
469
+ show: false,
470
+ frame: false
471
+ };
472
+ this._browserWindow = new BrowserWindow({
473
+ ...defaults,
474
+ ...this._options.browserWindow
475
+ });
476
+ this._positioner = new Positioner(this._browserWindow);
477
+ this._browserWindow.on("blur", () => {
478
+ if (!this._browserWindow) return;
479
+ this._browserWindow.isAlwaysOnTop() ? this.emit("focus-lost") : this._blurTimeout = setTimeout(() => {
480
+ this.hideWindow();
481
+ }, 100);
482
+ });
483
+ if (this._options.showOnAllWorkspaces !== false) this._browserWindow.setVisibleOnAllWorkspaces(true, { skipTransformProcessType: true });
484
+ if (this._options.hideOnClose) this._browserWindow.on("close", (event) => {
485
+ if (this._isDestroyed || this._isQuitting) return;
486
+ event.preventDefault();
487
+ setImmediate(() => this.hideWindow());
488
+ });
489
+ if (this._options.escapeToHide) this._browserWindow.webContents.on("before-input-event", (_event, input) => {
490
+ if (input.type === "keyDown" && input.key === "Escape") this.hideWindow();
491
+ });
492
+ this._browserWindow.on("closed", this.windowClear.bind(this));
493
+ this._browserWindow.on("resize", this.positionWindow);
494
+ this.emit("before-load");
495
+ if (this._options.index !== false) await this._browserWindow.loadURL(this._options.index, this._options.loadUrlOptions);
496
+ this.emit("after-create-window");
497
+ }
498
+ windowClear() {
499
+ this._browserWindow = void 0;
500
+ this.emit("after-close");
501
+ }
502
+ };
503
+ //#endregion
504
+ //#region src/index.ts
505
+ /**
506
+ * Entry point of menubar
507
+ * @example
508
+ * ```typescript
509
+ * import { menubar } from 'menubar';
510
+ * ```
511
+ */
512
+ /** */
513
+ /**
514
+ * Factory function to create a menubar application
515
+ *
516
+ * @param options - Options for creating a menubar application, see
517
+ * {@link Options}
518
+ */
519
+ function menubar(options) {
520
+ return new Menubar(app, options);
521
+ }
522
+ //#endregion
523
+ export { Menubar, getWindowPosition, menubar, taskbarLocation };
524
+
525
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["electronScreen"],"sources":["../src/Positioner.ts","../src/util/cleanOptions.ts","../src/util/getWindowPosition.ts","../src/Menubar.ts","../src/index.ts"],"sourcesContent":["import { type BrowserWindow, type Rectangle, screen } from 'electron';\n\n/**\n * Named anchor points for placing the menubar window. The `tray*` values are\n * relative to the tray icon's bounds; the rest are relative to the work area\n * of the display containing the cursor (or the tray, when bounds are given).\n */\nexport type WindowPosition =\n | 'trayLeft'\n | 'trayBottomLeft'\n | 'trayRight'\n | 'trayBottomRight'\n | 'trayCenter'\n | 'trayBottomCenter'\n | 'topLeft'\n | 'topRight'\n | 'bottomLeft'\n | 'bottomRight'\n | 'topCenter'\n | 'bottomCenter'\n | 'leftCenter'\n | 'rightCenter'\n | 'center';\n\n/**\n * Computes `{x, y}` coordinates for placing a {@link BrowserWindow} at a named\n * position, optionally relative to a tray icon's bounds. Ported from\n * `electron-positioner@4.1.0` to drop the unmaintained runtime dependency.\n */\nexport class Positioner {\n private readonly browserWindow: BrowserWindow;\n\n constructor(browserWindow: BrowserWindow) {\n this.browserWindow = browserWindow;\n }\n\n calculate(\n position?: WindowPosition,\n trayBounds?: Rectangle,\n ): { x: number; y: number } {\n if (!position) {\n throw new TypeError(\n 'Positioner.calculate: a `position` argument is required.',\n );\n }\n\n const screenSize = trayBounds\n ? screen.getDisplayMatching(trayBounds).workArea\n : screen.getDisplayNearestPoint(screen.getCursorScreenPoint()).workArea;\n const [windowWidth, windowHeight] = this.browserWindow.getSize();\n const trayX = trayBounds?.x ?? Number.NaN;\n const trayWidth = trayBounds?.width ?? Number.NaN;\n\n const positions: Record<WindowPosition, { x: number; y: number }> = {\n trayLeft: {\n x: Math.floor(trayX),\n y: screenSize.y,\n },\n trayBottomLeft: {\n x: Math.floor(trayX),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n trayRight: {\n x: Math.floor(trayX - windowWidth + trayWidth),\n y: screenSize.y,\n },\n trayBottomRight: {\n x: Math.floor(trayX - windowWidth + trayWidth),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n trayCenter: {\n x: Math.floor(trayX - windowWidth / 2 + trayWidth / 2),\n y: screenSize.y,\n },\n trayBottomCenter: {\n x: Math.floor(trayX - windowWidth / 2 + trayWidth / 2),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n topLeft: {\n x: screenSize.x,\n y: screenSize.y,\n },\n topRight: {\n x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),\n y: screenSize.y,\n },\n bottomLeft: {\n x: screenSize.x,\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n bottomRight: {\n x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n topCenter: {\n x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),\n y: screenSize.y,\n },\n bottomCenter: {\n x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),\n y: Math.floor(screenSize.height - (windowHeight - screenSize.y)),\n },\n leftCenter: {\n x: screenSize.x,\n y:\n screenSize.y +\n Math.floor(screenSize.height / 2) -\n Math.floor(windowHeight / 2),\n },\n rightCenter: {\n x: Math.floor(screenSize.x + (screenSize.width - windowWidth)),\n y:\n screenSize.y +\n Math.floor(screenSize.height / 2) -\n Math.floor(windowHeight / 2),\n },\n center: {\n x: Math.floor(screenSize.x + (screenSize.width / 2 - windowWidth / 2)),\n y: Math.floor(\n (screenSize.height + screenSize.y) / 2 - windowHeight / 2,\n ),\n },\n };\n\n const coords = positions[position];\n\n // On Windows, a tray-relative position can push the window past the right\n // edge of the work area. Snap back to `topRight` x in that case so it stays\n // visible. See https://github.com/jenslind/electron-positioner.\n if (position.startsWith('tray')) {\n if (coords.x + windowWidth > screenSize.width + screenSize.x) {\n return { x: positions.topRight.x, y: coords.y };\n }\n }\n\n return coords;\n }\n}\n","/**\n * @ignore\n */\n\n/** */\n\nimport path from 'node:path';\nimport url from 'node:url';\n\nimport { app } from 'electron';\n\nimport type { Options } from '../types';\n\nconst DEFAULT_WINDOW_HEIGHT = 400;\nconst DEFAULT_WINDOW_WIDTH = 400;\n\n/**\n * Take as input some options, and return a sanitized version of it.\n *\n * @param opts - The options to clean.\n * @ignore\n */\nexport function cleanOptions(opts?: Partial<Options>): Options {\n const options: Partial<Options> = { ...opts };\n\n if (options.activateWithApp === undefined) {\n options.activateWithApp = true;\n }\n if (options.ignoreDoubleClickEvents === undefined) {\n options.ignoreDoubleClickEvents = true;\n }\n if (!options.dir) {\n options.dir = app.getAppPath();\n }\n if (!path.isAbsolute(options.dir)) {\n options.dir = path.resolve(options.dir);\n }\n // Note: options.index can be `false`\n if (options.index === undefined) {\n options.index = url.format({\n pathname: path.join(options.dir, 'index.html'),\n protocol: 'file:',\n slashes: true,\n });\n }\n options.loadUrlOptions = options.loadUrlOptions || {};\n\n options.tooltip = options.tooltip || '';\n\n // `icon`, `preloadWindow`, `showDockIcon`, `showOnAllWorkspaces`,\n // `showOnRightClick` don't need any special treatment\n\n // Now we take care of `browserWindow`\n if (!options.browserWindow) {\n options.browserWindow = {};\n }\n\n // Set width/height on options to be usable before the window is created\n options.browserWindow.width =\n // Note: not using `options.browserWindow.width || DEFAULT_WINDOW_WIDTH` so\n // that users can put a 0 width\n options.browserWindow.width !== undefined\n ? options.browserWindow.width\n : DEFAULT_WINDOW_WIDTH;\n options.browserWindow.height =\n options.browserWindow.height !== undefined\n ? options.browserWindow.height\n : DEFAULT_WINDOW_HEIGHT;\n\n return options as Options;\n}\n","/**\n * Utilities to get taskbar position and consequently menubar's position\n */\n\n/** */\n\nimport { screen as electronScreen, type Rectangle, type Tray } from 'electron';\n\nconst isLinux = process.platform === 'linux';\n\nconst trayToScreenRects = (tray: Tray): [Rectangle, Rectangle] => {\n // There may be more than one screen, so we need to figure out on which screen our tray icon lives.\n const { workArea, bounds: screenBounds } = electronScreen.getDisplayMatching(\n tray.getBounds(),\n );\n\n workArea.x -= screenBounds.x;\n workArea.y -= screenBounds.y;\n\n return [screenBounds, workArea];\n};\n\ntype TaskbarLocation = 'top' | 'bottom' | 'left' | 'right';\n\n/**\n * Determine taskbard location: \"top\", \"bottom\", \"left\" or \"right\".\n *\n * Only tested on Windows for now, and only used in Windows.\n *\n * @param tray - The Electron Tray instance.\n */\nexport function taskbarLocation(tray: Tray): TaskbarLocation {\n const [screenBounds, workArea] = trayToScreenRects(tray);\n\n // TASKBAR LEFT\n if (workArea.x > 0) {\n // Most likely Ubuntu hence assuming the window should be on top\n if (isLinux && workArea.y > 0) return 'top';\n // The workspace starts more on the right\n return 'left';\n }\n\n // TASKBAR TOP\n if (workArea.y > 0) {\n return 'top';\n }\n\n // TASKBAR RIGHT\n // Here both workArea.y and workArea.x are 0 so we can no longer leverage them.\n // We can use the workarea and display width though.\n // Determine taskbar location\n if (workArea.width < screenBounds.width) {\n // The taskbar is either on the left or right, but since the LEFT case was handled above,\n // we can be sure we're dealing with a right taskbar\n return 'right';\n }\n\n // TASKBAR BOTTOM\n // Since all the other cases were handled, we can be sure we're dealing with a bottom taskbar\n return 'bottom';\n}\n\ntype WindowPosition =\n | 'trayCenter'\n | 'topRight'\n | 'trayBottomCenter'\n | 'leftCenter'\n | 'bottomRight';\n\n/**\n * Depending on where the taskbar is, determine where the window should be\n * positioned.\n *\n * @param tray - The Electron Tray instance.\n */\nexport function getWindowPosition(tray: Tray): WindowPosition {\n switch (process.platform) {\n // macOS\n // Supports top taskbars\n case 'darwin':\n return 'trayCenter';\n // Linux\n // Windows\n // Supports top/bottom/left/right taskbar\n case 'linux':\n case 'win32': {\n const traySide = taskbarLocation(tray);\n\n // Assign position for menubar\n if (traySide === 'top') {\n return isLinux ? 'topRight' : 'trayCenter';\n }\n if (traySide === 'bottom') {\n return 'bottomRight';\n }\n if (traySide === 'left') {\n // Vertically centered against the left edge of the work area.\n // `bottomLeft` would put the window in the screen's bottom-left\n // corner — visually disconnected from the tray icon, which sits\n // somewhere on the left strip. Tray-anchored y isn't available\n // through `Positioner` for side taskbars, so center is the best\n // compromise that stays close to the tray.\n return 'leftCenter';\n }\n if (traySide === 'right') {\n return 'bottomRight';\n }\n }\n }\n\n // When we really don't know, we just show the menubar on the top-right\n return 'topRight';\n}\n","import { EventEmitter } from 'node:events';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { BrowserWindow, globalShortcut, type Menu, Tray } from 'electron';\n\nimport { Positioner } from './Positioner';\nimport type { Options } from './types';\nimport { cleanOptions } from './util/cleanOptions';\nimport { getWindowPosition } from './util/getWindowPosition';\n\n/**\n * The main Menubar class.\n */\nexport class Menubar extends EventEmitter {\n private _app: Electron.App;\n private _browserWindow?: BrowserWindow;\n private _contextMenu?: Menu;\n private _blurTimeout: NodeJS.Timeout | null = null; // track blur events with timeout\n private _isDestroyed: boolean;\n private _isQuitting: boolean; // set on app `before-quit`, used by hideOnClose\n private _isVisible: boolean; // track visibility\n private _cachedBounds?: Electron.Rectangle; // _cachedBounds are needed for double-clicked event\n private _options: Options;\n private _positioner: Positioner | undefined;\n private _shortcut?: Electron.Accelerator;\n private _rightClickContextMenuBound = false;\n private _tray?: Tray;\n\n constructor(app: Electron.App, options?: Partial<Options>) {\n super();\n this._app = app;\n this._options = cleanOptions(options);\n this._isDestroyed = false;\n this._isQuitting = false;\n this._isVisible = false;\n\n app.on('before-quit', this.onBeforeQuit);\n\n if (app.isReady()) {\n // See https://github.com/maxogden/menubar/pull/151\n process.nextTick(this.onAppReady);\n } else {\n app.on('ready', this.onAppReady);\n }\n }\n\n /**\n * The Electron [App](https://electronjs.org/docs/api/app)\n * instance.\n */\n get app(): Electron.App {\n return this._app;\n }\n\n /**\n * The {@link Positioner} instance used to compute where the menubar window\n * should appear on screen. Available after the `after-create-window` event.\n */\n get positioner(): Positioner {\n if (!this._positioner) {\n throw new Error(\n 'Please access `this.positioner` after the `after-create-window` event has fired.',\n );\n }\n\n return this._positioner;\n }\n\n /**\n * The Electron [Tray](https://electronjs.org/docs/api/tray) instance.\n */\n get tray(): Tray {\n if (!this._tray) {\n throw new Error(\n 'Please access `this.tray` after the `ready` event has fired.',\n );\n }\n\n return this._tray;\n }\n\n /**\n * The Electron [BrowserWindow](https://electronjs.org/docs/api/browser-window)\n * instance, if it's present.\n */\n get window(): BrowserWindow | undefined {\n return this._browserWindow;\n }\n\n /**\n * Tear down the menubar instance: destroy the window, remove the tray, and\n * detach all listeners. Subsequent clicks on the tray will be no-ops until a\n * new {@link Menubar} instance is created.\n */\n destroy(): void {\n if (this.isDestroyed()) {\n return;\n }\n // Set first so `hideOnClose` lets the close go through instead of\n // intercepting it.\n this._isDestroyed = true;\n\n if (this._shortcut) {\n globalShortcut.unregister(this._shortcut);\n this._shortcut = undefined;\n }\n\n if (this._browserWindow) {\n this._browserWindow.destroy();\n this._browserWindow = undefined;\n }\n\n if (this._tray) {\n // Ensure all potential listeners are removed.\n for (const event of ['click', 'right-click', 'double-click']) {\n this._tray.removeListener(\n event as Parameters<Tray['on']>[0],\n this.clicked,\n );\n }\n this._tray.setToolTip('');\n this._tray = undefined;\n }\n\n this._app.removeListener('ready', this.onAppReady);\n this._app.removeListener('activate', this.onAppActivate);\n this._app.removeListener('before-quit', this.onBeforeQuit);\n }\n\n /**\n * Whether {@link destroy} has been called on this menubar instance.\n */\n isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Retrieve a menubar option.\n *\n * @param key - The option key to retrieve, see {@link Options}.\n */\n getOption<K extends keyof Options>(key: K): Options[K] {\n return this._options[key];\n }\n\n /**\n * Hide the menubar window.\n */\n hideWindow(): void {\n if (!this._browserWindow || !this._isVisible) {\n return;\n }\n this.emit('hide');\n this._browserWindow.hide();\n this.emit('after-hide');\n this._isVisible = false;\n if (this._blurTimeout) {\n clearTimeout(this._blurTimeout);\n this._blurTimeout = null;\n }\n this.refreshLinuxContextMenu();\n }\n\n /**\n * Register a global keyboard accelerator that toggles the menubar window.\n * Replaces any previously registered shortcut owned by this Menubar.\n * Pass `undefined` to clear the current shortcut without registering a new\n * one. Returns whether the registration succeeded.\n *\n * @param accelerator - An Electron\n * [Accelerator](https://electronjs.org/docs/api/accelerator) string, or\n * `undefined` to clear.\n */\n setGlobalShortcut(accelerator: Electron.Accelerator | undefined): boolean {\n if (this._shortcut) {\n globalShortcut.unregister(this._shortcut);\n this._shortcut = undefined;\n }\n this._options.globalShortcut = accelerator;\n if (!accelerator) {\n return true;\n }\n const ok = globalShortcut.register(accelerator, () => this.toggleWindow());\n if (ok) {\n this._shortcut = accelerator;\n }\n return ok;\n }\n\n /**\n * Toggle the menubar window: hide it if visible, show it otherwise.\n * Resolves once the window finishes showing or hiding.\n */\n async toggleWindow(): Promise<void> {\n if (this._browserWindow && this._isVisible) {\n this.hideWindow();\n return;\n }\n await this.showWindow();\n }\n\n /**\n * Re-center the menubar window over the tray icon. Convenience wrapper for\n * `positioner.move('trayCenter', tray.getBounds())` that's safe to call\n * after the `after-create-window` event. No-op if the window doesn't\n * exist yet.\n */\n recenterOnTray(): void {\n if (!this._browserWindow || !this._tray) {\n return;\n }\n const bounds = this._tray.getBounds();\n const { x, y } = this.positioner.calculate('trayCenter', bounds);\n this._browserWindow.setPosition(Math.round(x), Math.round(y));\n }\n\n /**\n * Replace the tray context menu after construction. On Linux this also\n * re-publishes the menu to the SNI host, which is required after mutating\n * items in-place since libappindicator caches the previous serialization.\n * On macOS/Windows the right-click popup handler reads the current menu\n * reference, so swapping or clearing here takes effect immediately.\n *\n * @param menu - The new menu, or `null` to clear it.\n */\n setContextMenu(menu: Menu | null): void {\n this._contextMenu = menu ?? undefined;\n this._options.contextMenu = menu ?? undefined;\n if (!this._tray) {\n return;\n }\n if (process.platform === 'linux') {\n // `setContextMenu(null)` clears the menu on Linux.\n this._tray.setContextMenu(menu);\n return;\n }\n // macOS / Windows: bind the right-click popup once on first non-empty\n // assignment. The handler reads `this._contextMenu` at invoke time, so\n // later swaps and clears take effect without rebinding (and never leak\n // a stale closure reference).\n if (menu && !this._rightClickContextMenuBound) {\n this.bindRightClickContextMenu();\n }\n }\n\n /**\n * Change an option after menubar is created.\n *\n * @param key - The option key to modify, see {@link Options}.\n * @param value - The value to set.\n */\n setOption<K extends keyof Options>(key: K, value: Options[K]): void {\n this._options[key] = value;\n }\n\n /**\n * Show the menubar window.\n *\n * @param trayPos - The bounds to show the window in.\n */\n async showWindow(trayPos?: Electron.Rectangle): Promise<void> {\n if (!this.tray) {\n throw new Error('Tray should have been instantiated by now');\n }\n\n if (!this._browserWindow) {\n await this.createWindow();\n }\n\n // Use guard for TypeScript, to avoid ! everywhere\n if (!this._browserWindow) {\n throw new Error('Window has been initialized just above. qed.');\n }\n\n this.emit('show');\n\n // Cache fresh tray bounds (or fall back to existing cache / tray bounds)\n // so `positionWindow` can reposition without an event payload — for\n // example when the window resizes via `setSize`.\n if (trayPos && trayPos.x !== 0) {\n this._cachedBounds = trayPos;\n } else if (!this._cachedBounds && this.tray.getBounds) {\n this._cachedBounds = this.tray.getBounds();\n }\n\n this.positionWindow();\n this._browserWindow.show();\n this._isVisible = true;\n this.emit('after-show');\n this.refreshLinuxContextMenu();\n }\n\n /**\n * Compute and apply the tray-anchored position of the browser window. Safe\n * to call any time after `createWindow` has run — invoked from\n * {@link showWindow} on every show, and from the window's `resize` event so\n * `setSize` calls reposition the window correctly.\n */\n private positionWindow = (): void => {\n if (!this._browserWindow || !this._tray) {\n return;\n }\n\n // 'Windows' taskbar: sync window position each time before positioning.\n // https://github.com/maxogden/menubar/issues/232\n if (['win32', 'linux'].includes(process.platform)) {\n this._options.windowPosition = getWindowPosition(this._tray);\n }\n\n const trayPos = this._cachedBounds ?? this._tray.getBounds?.();\n\n // Default the window to the right if `trayPos` bounds are undefined or null.\n let noBoundsPosition: Options['windowPosition'];\n if (\n (trayPos === undefined || trayPos.x === 0) &&\n this._options.windowPosition?.startsWith('tray')\n ) {\n noBoundsPosition =\n process.platform === 'win32' ? 'bottomRight' : 'topRight';\n }\n\n const position = this.positioner.calculate(\n this._options.windowPosition || noBoundsPosition,\n trayPos,\n ) as { x: number; y: number };\n\n // Not using `||` because x and y can be zero.\n const x =\n this._options.browserWindow.x !== undefined\n ? this._options.browserWindow.x\n : position.x;\n const y =\n this._options.browserWindow.y !== undefined\n ? this._options.browserWindow.y\n : position.y;\n\n // `.setPosition` crashed on non-integers\n // https://github.com/maxogden/menubar/issues/233\n this._browserWindow.setPosition(Math.round(x), Math.round(y));\n };\n\n private async appReady(): Promise<void> {\n if (this.app.dock && !this._options.showDockIcon) {\n this.app.dock.hide();\n }\n\n if (this._options.activateWithApp) {\n this.app.on('activate', this.onAppActivate);\n }\n\n let trayImage =\n this._options.icon || path.join(this._options.dir, 'IconTemplate.png');\n if (typeof trayImage === 'string' && !fs.existsSync(trayImage)) {\n trayImage = path.join(__dirname, '..', 'assets', 'IconTemplate.png'); // Default cat icon\n }\n\n const trigger =\n this._options.trigger ??\n (this._options.showOnRightClick ? 'right-click' : 'click');\n\n this._tray = this._options.tray || new Tray(trayImage);\n // Type guards for TS not to complain\n if (!this.tray) {\n throw new Error('Tray has been initialized above');\n }\n if (trigger !== 'none') {\n this.tray.on(trigger as Parameters<Tray['on']>[0], this.clicked);\n this.tray.on('double-click', this.clicked);\n }\n // macOS-only: ignore double-click so an accidental second click doesn't\n // race the blur handler and cause a tray-icon flicker.\n if (\n process.platform === 'darwin' &&\n this._options.ignoreDoubleClickEvents\n ) {\n this.tray.setIgnoreDoubleClickEvents(true);\n }\n this.tray.setToolTip(this._options.tooltip);\n\n if (this._options.contextMenu) {\n this.bindContextMenu(this._options.contextMenu);\n }\n\n if (this._options.globalShortcut) {\n this.setGlobalShortcut(this._options.globalShortcut);\n }\n\n if (!this._options.windowPosition) {\n this._options.windowPosition = getWindowPosition(this.tray);\n }\n\n if (this._options.preloadWindow) {\n await this.createWindow();\n }\n\n this.emit('ready');\n }\n\n /**\n * Callback on tray icon click or double-click.\n *\n * @param e\n * @param bounds\n */\n private clicked = async (\n event?: Electron.KeyboardEvent,\n bounds?: Electron.Rectangle,\n ): Promise<void> => {\n if (event && (event.shiftKey || event.ctrlKey || event.metaKey)) {\n return this.hideWindow();\n }\n\n // if blur was invoked clear timeout\n if (this._blurTimeout) {\n clearInterval(this._blurTimeout);\n }\n\n if (this._browserWindow && this._isVisible) {\n return this.hideWindow();\n }\n\n this._cachedBounds = bounds || this._cachedBounds;\n await this.showWindow(this._cachedBounds);\n };\n\n private onAppActivate = (\n _event: Electron.Event,\n hasVisibleWindows: boolean,\n ): void => {\n if (!hasVisibleWindows) {\n this.showWindow().catch(console.error);\n }\n };\n\n private onBeforeQuit = (): void => {\n this._isQuitting = true;\n };\n\n private bindContextMenu(menu: Menu): void {\n this._contextMenu = menu;\n if (process.platform === 'linux') {\n // libappindicator / StatusNotifierItem requires the menu to live on the\n // tray itself; right-click is handled by the desktop environment.\n this.tray.setContextMenu(menu);\n return;\n }\n this.bindRightClickContextMenu();\n }\n\n private bindRightClickContextMenu(): void {\n // macOS / Windows: pop up the current menu on right-click so left-click\n // stays bound to toggling the menubar window. Read `this._contextMenu`\n // at invoke time so `setContextMenu()` swaps and clears take effect\n // without rebinding (and without leaking a stale closure reference).\n this.tray.on('right-click', (_event, bounds) => {\n const current = this._contextMenu;\n if (!current) {\n return;\n }\n this.tray.popUpContextMenu(current, { x: bounds.x, y: bounds.y });\n });\n this._rightClickContextMenuBound = true;\n }\n\n private refreshLinuxContextMenu(): void {\n // libappindicator caches the menu's serialized state, so consumers that\n // mutate items in-place need the menu re-published. Cheap to do; safe to\n // call unconditionally on every show/hide.\n if (\n process.platform === 'linux' &&\n this._contextMenu &&\n this._tray &&\n !this._tray.isDestroyed?.()\n ) {\n this._tray.setContextMenu(this._contextMenu);\n }\n }\n\n private onAppReady = (): void => {\n // Guard against `destroy()` being called between construction and the\n // scheduled `process.nextTick`/`'ready'` firing.\n if (this._isDestroyed) {\n return;\n }\n this.appReady().catch((err) => console.error('menubar: ', err));\n };\n\n private async createWindow(): Promise<void> {\n this.emit('create-window');\n\n // We add some default behavior for menubar's browserWindow, to make it\n // look like a menubar\n const defaults = {\n show: false, // Don't show it at first\n frame: false, // Remove window frame\n };\n\n this._browserWindow = new BrowserWindow({\n ...defaults,\n ...this._options.browserWindow,\n });\n\n this._positioner = new Positioner(this._browserWindow);\n\n this._browserWindow.on('blur', () => {\n if (!this._browserWindow) {\n return;\n }\n\n // hack to close if icon clicked when open\n this._browserWindow.isAlwaysOnTop()\n ? this.emit('focus-lost')\n : (this._blurTimeout = setTimeout(() => {\n this.hideWindow();\n }, 100));\n });\n\n if (this._options.showOnAllWorkspaces !== false) {\n // https://github.com/electron/electron/issues/37832#issuecomment-1497882944\n this._browserWindow.setVisibleOnAllWorkspaces(true, {\n skipTransformProcessType: true, // Avoid damaging the original visible state of app.dock\n });\n }\n\n if (this._options.hideOnClose) {\n this._browserWindow.on('close', (event) => {\n if (this._isDestroyed || this._isQuitting) {\n return;\n }\n event.preventDefault();\n // Defer the hide for Wayland: hiding synchronously from the `close`\n // handler can leave frameless surfaces in a half-closed state.\n setImmediate(() => this.hideWindow());\n });\n }\n\n if (this._options.escapeToHide) {\n this._browserWindow.webContents.on(\n 'before-input-event',\n (_event, input) => {\n if (input.type === 'keyDown' && input.key === 'Escape') {\n this.hideWindow();\n }\n },\n );\n }\n\n // Use `closed` (not `close`) so consumer `close` listeners can still read\n // `mb.window` and call `event.preventDefault()` without racing our cleanup.\n this._browserWindow.on('closed', this.windowClear.bind(this));\n\n // Re-anchor the window to the tray when its size changes (e.g. via\n // `mb.window.setSize(...)`), so the window doesn't end up clipped under\n // the taskbar. https://github.com/maxogden/menubar/issues/349\n this._browserWindow.on('resize', this.positionWindow);\n\n this.emit('before-load');\n\n // If the user explicity set options.index to false, we don't loadURL\n // https://github.com/maxogden/menubar/issues/255\n if (this._options.index !== false) {\n await this._browserWindow.loadURL(\n this._options.index,\n this._options.loadUrlOptions,\n );\n }\n this.emit('after-create-window');\n }\n\n private windowClear(): void {\n this._browserWindow = undefined;\n this.emit('after-close');\n }\n}\n","/**\n * Entry point of menubar\n * @example\n * ```typescript\n * import { menubar } from 'menubar';\n * ```\n */\n\n/** */\n\nimport { app } from 'electron';\n\nimport { Menubar } from './Menubar';\nimport type { Options } from './types';\n\nexport * from './util/getWindowPosition';\nexport { Menubar };\n\n/**\n * Factory function to create a menubar application\n *\n * @param options - Options for creating a menubar application, see\n * {@link Options}\n */\nexport function menubar(options?: Partial<Options>): Menubar {\n return new Menubar(app, options);\n}\n"],"mappings":"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"}