electron-notify-manager 1.0.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 (95) hide show
  1. package/README.md +323 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +6 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/src/NotificationManager.d.ts +45 -0
  7. package/dist/src/NotificationManager.d.ts.map +1 -0
  8. package/dist/src/NotificationManager.js +337 -0
  9. package/dist/src/NotificationManager.js.map +1 -0
  10. package/dist/src/NotificationWindow.d.ts +19 -0
  11. package/dist/src/NotificationWindow.d.ts.map +1 -0
  12. package/dist/src/NotificationWindow.js +165 -0
  13. package/dist/src/NotificationWindow.js.map +1 -0
  14. package/dist/src/constants.d.ts +22 -0
  15. package/dist/src/constants.d.ts.map +1 -0
  16. package/dist/src/constants.js +39 -0
  17. package/dist/src/constants.js.map +1 -0
  18. package/dist/src/core/NotificationQueue.d.ts +14 -0
  19. package/dist/src/core/NotificationQueue.d.ts.map +1 -0
  20. package/dist/src/core/NotificationQueue.js +35 -0
  21. package/dist/src/core/NotificationQueue.js.map +1 -0
  22. package/dist/src/errors.d.ts +23 -0
  23. package/dist/src/errors.d.ts.map +1 -0
  24. package/dist/src/errors.js +43 -0
  25. package/dist/src/errors.js.map +1 -0
  26. package/dist/src/ipc/IpcChannels.d.ts +9 -0
  27. package/dist/src/ipc/IpcChannels.d.ts.map +1 -0
  28. package/dist/src/ipc/IpcChannels.js +11 -0
  29. package/dist/src/ipc/IpcChannels.js.map +1 -0
  30. package/dist/src/position/PositionCalculator.d.ts +22 -0
  31. package/dist/src/position/PositionCalculator.d.ts.map +1 -0
  32. package/dist/src/position/PositionCalculator.js +77 -0
  33. package/dist/src/position/PositionCalculator.js.map +1 -0
  34. package/dist/src/positionCalculator.d.ts +19 -0
  35. package/dist/src/positionCalculator.d.ts.map +1 -0
  36. package/dist/src/positionCalculator.js +50 -0
  37. package/dist/src/positionCalculator.js.map +1 -0
  38. package/dist/src/types/config.types.d.ts +11 -0
  39. package/dist/src/types/config.types.d.ts.map +1 -0
  40. package/dist/src/types/config.types.js +3 -0
  41. package/dist/src/types/config.types.js.map +1 -0
  42. package/dist/src/types/index.d.ts +8 -0
  43. package/dist/src/types/index.d.ts.map +1 -0
  44. package/dist/src/types/index.js +3 -0
  45. package/dist/src/types/index.js.map +1 -0
  46. package/dist/src/types/ipc.types.d.ts +12 -0
  47. package/dist/src/types/ipc.types.d.ts.map +1 -0
  48. package/dist/src/types/ipc.types.js +3 -0
  49. package/dist/src/types/ipc.types.js.map +1 -0
  50. package/dist/src/types/notification.types.d.ts +28 -0
  51. package/dist/src/types/notification.types.d.ts.map +1 -0
  52. package/dist/src/types/notification.types.js +3 -0
  53. package/dist/src/types/notification.types.js.map +1 -0
  54. package/dist/src/types/position.types.d.ts +12 -0
  55. package/dist/src/types/position.types.d.ts.map +1 -0
  56. package/dist/src/types/position.types.js +3 -0
  57. package/dist/src/types/position.types.js.map +1 -0
  58. package/dist/src/types/theme.types.d.ts +8 -0
  59. package/dist/src/types/theme.types.d.ts.map +1 -0
  60. package/dist/src/types/theme.types.js +3 -0
  61. package/dist/src/types/theme.types.js.map +1 -0
  62. package/dist/src/utils/idGenerator.d.ts +7 -0
  63. package/dist/src/utils/idGenerator.d.ts.map +1 -0
  64. package/dist/src/utils/idGenerator.js +16 -0
  65. package/dist/src/utils/idGenerator.js.map +1 -0
  66. package/dist/src/utils/logger.d.ts +12 -0
  67. package/dist/src/utils/logger.d.ts.map +1 -0
  68. package/dist/src/utils/logger.js +38 -0
  69. package/dist/src/utils/logger.js.map +1 -0
  70. package/dist/src/utils/themeDetector.d.ts +3 -0
  71. package/dist/src/utils/themeDetector.d.ts.map +1 -0
  72. package/dist/src/utils/themeDetector.js +11 -0
  73. package/dist/src/utils/themeDetector.js.map +1 -0
  74. package/dist/src/utils/validators.d.ts +10 -0
  75. package/dist/src/utils/validators.d.ts.map +1 -0
  76. package/dist/src/utils/validators.js +95 -0
  77. package/dist/src/utils/validators.js.map +1 -0
  78. package/dist/types.d.ts +4 -0
  79. package/dist/types.d.ts.map +1 -0
  80. package/dist/types.js +3 -0
  81. package/dist/types.js.map +1 -0
  82. package/package.json +50 -0
  83. package/renderer/notification.css +172 -0
  84. package/renderer/notification.html +52 -0
  85. package/renderer/notification.js +137 -0
  86. package/renderer/notification.ts +150 -0
  87. package/renderer/preload.js +38 -0
  88. package/renderer/preload.ts +56 -0
  89. package/renderer/scripts/animationController.ts +12 -0
  90. package/renderer/scripts/icons.ts +36 -0
  91. package/renderer/scripts/notification.js +188 -0
  92. package/renderer/scripts/progressBar.ts +10 -0
  93. package/renderer/styles/animations.css +28 -0
  94. package/renderer/styles/notification.css +186 -0
  95. package/renderer/styles/themes.css +94 -0
package/README.md ADDED
@@ -0,0 +1,323 @@
1
+ # electron-notify-manager
2
+
3
+ In-app toast notifications for Electron using `BrowserWindow` (not OS native notifications).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i electron-notify-manager
9
+ ```
10
+
11
+ - **Peer dependency**: `electron >= 13`
12
+ - **Runtime**: main process only (creates `BrowserWindow` instances)
13
+
14
+ ## Quick Start
15
+
16
+ ```ts
17
+ import { app, BrowserWindow } from 'electron';
18
+ import { NotificationManager } from 'electron-notify-manager';
19
+
20
+ let notifier: NotificationManager | null = null;
21
+
22
+ function createMainWindow(): BrowserWindow {
23
+ const win = new BrowserWindow({ width: 900, height: 650 });
24
+ void win.loadURL('data:text/html,<h1>Electron</h1>');
25
+ return win;
26
+ }
27
+
28
+ app
29
+ .whenReady()
30
+ .then(() => {
31
+ createMainWindow();
32
+
33
+ notifier = new NotificationManager({ position: 'topRight' });
34
+
35
+ notifier.show({
36
+ title: 'Update available',
37
+ description: 'Version 2.1.0 is ready to install',
38
+ variant: 'default',
39
+ duration: 4000,
40
+ });
41
+ })
42
+ .catch((err: unknown) => {
43
+ // eslint-disable-next-line no-console
44
+ console.error(err);
45
+ app.quit();
46
+ });
47
+
48
+ app.on('before-quit', () => {
49
+ notifier?.destroy();
50
+ notifier = null;
51
+ });
52
+ ```
53
+
54
+ ## 9. Stacking & Queue
55
+
56
+ `NotificationManager` keeps a per-display stack ordered **newest → oldest**. When `maxVisible` is reached, new notifications are **queued** (IDs are returned immediately, but no `BrowserWindow` is created until a slot is available). When a visible notification closes, the manager **promotes** the next queued notification and then **reflows** all visible windows to close the gap.
57
+
58
+ Reflow means recalculating each visible notification’s \(x, y\) based on the target display’s `workArea`, `position`, `margin`, `gap`, and the item’s index in the visible stack. Existing windows move with an animation; the newly promoted window animates in from the left or right edge depending on the configured position.
59
+
60
+ ```ts
61
+ import { app, BrowserWindow } from 'electron';
62
+ import { NotificationManager } from 'electron-notify-manager';
63
+
64
+ let notifier: NotificationManager | null = null;
65
+
66
+ app.whenReady().then(() => {
67
+ const win = new BrowserWindow({ width: 900, height: 650 });
68
+ void win.loadURL('data:text/html,<h1>Stacking demo</h1>');
69
+
70
+ notifier = new NotificationManager({
71
+ position: 'bottomRight',
72
+ maxVisible: 3, // show max 3 at once, rest queue
73
+ gap: 10, // px between notifications
74
+ margin: 16, // px from screen edge
75
+ });
76
+
77
+ for (let i = 1; i <= 8; i += 1) {
78
+ notifier.show({
79
+ title: `Queued toast #${i}`,
80
+ description: 'Close one and the next will be promoted.',
81
+ variant: i % 2 === 0 ? 'success' : 'default',
82
+ duration: 2500,
83
+ });
84
+ }
85
+ });
86
+
87
+ app.on('before-quit', () => {
88
+ notifier?.destroy();
89
+ notifier = null;
90
+ });
91
+ ```
92
+
93
+ ## 10. Configuration Reference
94
+
95
+ `NotificationManagerOptions` passed to `new NotificationManager(options)`.
96
+
97
+ | Option | Type | Default | Description |
98
+ |-------------|------------------------|-----------------|--------------------------------------------------|
99
+ | position | `NotificationPosition` | `'bottomRight'` | Screen position |
100
+ | width | `number` | `360` | Window width in px |
101
+ | height | `number` | `100` | Window height in px |
102
+ | margin | `number` | `16` | Distance from screen edge in px |
103
+ | gap | `number` | `10` | Gap between notifications in px |
104
+ | maxVisible | `number` | `5` | Max visible at once (rest queue) |
105
+ | debug | `boolean` | `false` | Enable debug logging (reserved for future use) |
106
+
107
+ **Position values** (`NotificationPosition`):
108
+
109
+ - `topLeft` | `topCenter` | `topRight`
110
+ - `bottomLeft` | `bottomCenter` | `bottomRight`
111
+
112
+ ## 11. NotificationOptions Reference
113
+
114
+ Options passed to `notifier.show(options)`.
115
+
116
+ | Option | Type | Default | Description |
117
+ |----------------|---------------------------------------------------|---------------|-----------------------------------------------------------------------------|
118
+ | title | `string` | — | Required. Title text |
119
+ | description | `string` | — | Required. Body text |
120
+ | image | `string` | — | Absolute path to an image file (PNG/JPG/SVG). If omitted, uses built-in icon |
121
+ | duration | `number` | `4000` | Duration in ms. `0` disables auto-close |
122
+ | variant | `'default' \| 'success' \| 'error' \| 'warning' \| 'loading' \| 'progress'` | `'default'` | Visual style and behavior |
123
+ | theme | `'dark' \| 'light' \| 'auto'` | `'auto'` | Theme mode (`auto` follows system theme) |
124
+ | progress | `number` | — | `progress` variant only. 0–100 |
125
+ | progressLabel | `string` | — | `progress` variant only. Label shown next to progress |
126
+ | loadingText | `string` | — | `loading` variant only. Secondary text |
127
+ | onClick | `() => void` | — | Callback invoked when user clicks the notification |
128
+ | onClose | `() => void` | — | Callback invoked when the notification is dismissed |
129
+
130
+ ## 12. Events
131
+
132
+ `NotificationManager` is an `EventEmitter`.
133
+
134
+ ```ts
135
+ import { app, BrowserWindow } from 'electron';
136
+ import { NotificationManager } from 'electron-notify-manager';
137
+ import type { CloseReason } from 'electron-notify-manager/dist/types';
138
+
139
+ let notifier: NotificationManager | null = null;
140
+
141
+ app.whenReady().then(() => {
142
+ const win = new BrowserWindow({ width: 900, height: 650 });
143
+ void win.loadURL('data:text/html,<h1>Events demo</h1>');
144
+
145
+ notifier = new NotificationManager({ position: 'bottomRight' });
146
+
147
+ notifier.on('show', (id: string) => {
148
+ // Fired when the notification is actually shown (not when queued).
149
+ // eslint-disable-next-line no-console
150
+ console.log('Notification shown:', id);
151
+ });
152
+
153
+ notifier.on('close', (id: string, reason: CloseReason) => {
154
+ // reason: 'duration' | 'user' | 'programmatic' | 'app-quit'
155
+ // eslint-disable-next-line no-console
156
+ console.log('Closed:', id, 'reason:', reason);
157
+ });
158
+
159
+ notifier.on('click', (id: string) => {
160
+ // eslint-disable-next-line no-console
161
+ console.log('Clicked:', id);
162
+ });
163
+
164
+ notifier.show({
165
+ title: 'Events wired',
166
+ description: 'Click or wait for auto-close.',
167
+ variant: 'default',
168
+ duration: 4000,
169
+ });
170
+ });
171
+
172
+ app.on('before-quit', () => {
173
+ notifier?.destroy();
174
+ notifier = null;
175
+ });
176
+ ```
177
+
178
+ ## 13. Callbacks
179
+
180
+ Callbacks are configured per-notification via `onClick` and `onClose`.
181
+
182
+ ```ts
183
+ import { app, BrowserWindow, shell } from 'electron';
184
+ import path from 'path';
185
+ import { NotificationManager } from 'electron-notify-manager';
186
+
187
+ let notifier: NotificationManager | null = null;
188
+
189
+ app.whenReady().then(() => {
190
+ const win = new BrowserWindow({ width: 900, height: 650 });
191
+ void win.loadURL('data:text/html,<h1>Callbacks demo</h1>');
192
+
193
+ notifier = new NotificationManager({ position: 'bottomRight' });
194
+
195
+ const filePath = path.join(process.cwd(), 'document.pdf');
196
+
197
+ notifier.show({
198
+ title: 'File saved',
199
+ description: 'document.pdf',
200
+ variant: 'success',
201
+ duration: 5000,
202
+ onClick: () => {
203
+ void shell.showItemInFolder(filePath);
204
+ },
205
+ onClose: () => {
206
+ // eslint-disable-next-line no-console
207
+ console.log('notification dismissed');
208
+ },
209
+ });
210
+ });
211
+
212
+ app.on('before-quit', () => {
213
+ notifier?.destroy();
214
+ notifier = null;
215
+ });
216
+ ```
217
+
218
+ ## 14. Custom Image
219
+
220
+ ```ts
221
+ import { app, BrowserWindow } from 'electron';
222
+ import path from 'path';
223
+ import { NotificationManager } from 'electron-notify-manager';
224
+
225
+ let notifier: NotificationManager | null = null;
226
+
227
+ app.whenReady().then(() => {
228
+ const win = new BrowserWindow({ width: 900, height: 650 });
229
+ void win.loadURL('data:text/html,<h1>Image demo</h1>');
230
+
231
+ notifier = new NotificationManager({ position: 'bottomRight' });
232
+
233
+ notifier.show({
234
+ title: 'TaskEra',
235
+ description: 'New message received',
236
+ image: path.join(__dirname, 'assets', 'icon.png'),
237
+ variant: 'default',
238
+ duration: 5000,
239
+ });
240
+ });
241
+
242
+ app.on('before-quit', () => {
243
+ notifier?.destroy();
244
+ notifier = null;
245
+ });
246
+ ```
247
+
248
+ Note: If no `image` is provided, the renderer uses a built-in SVG icon for the chosen `variant`.
249
+
250
+ ## 15. Sticky Notifications
251
+
252
+ ```ts
253
+ import { app, BrowserWindow } from 'electron';
254
+ import { NotificationManager } from 'electron-notify-manager';
255
+
256
+ let notifier: NotificationManager | null = null;
257
+
258
+ app.whenReady().then(() => {
259
+ const win = new BrowserWindow({ width: 900, height: 650 });
260
+ void win.loadURL('data:text/html,<h1>Sticky demo</h1>');
261
+
262
+ notifier = new NotificationManager({ position: 'bottomRight' });
263
+
264
+ // duration: 0 = never auto-closes
265
+ const id = notifier.show({
266
+ title: 'Action required',
267
+ description: 'Please review the pending changes.',
268
+ variant: 'warning',
269
+ duration: 0,
270
+ });
271
+
272
+ // Close programmatically when ready
273
+ setTimeout(() => {
274
+ notifier?.close(id);
275
+ }, 4000);
276
+ });
277
+
278
+ app.on('before-quit', () => {
279
+ notifier?.destroy();
280
+ notifier = null;
281
+ });
282
+ ```
283
+
284
+ ## 16. Cleanup
285
+
286
+ ```ts
287
+ import { app, BrowserWindow } from 'electron';
288
+ import { NotificationManager } from 'electron-notify-manager';
289
+
290
+ let notifier: NotificationManager | null = null;
291
+
292
+ app.whenReady().then(() => {
293
+ const win = new BrowserWindow({ width: 900, height: 650 });
294
+ void win.loadURL('data:text/html,<h1>Cleanup demo</h1>');
295
+
296
+ notifier = new NotificationManager({ position: 'bottomRight' });
297
+ notifier.show({ title: 'Ready', description: 'App is running', variant: 'default', duration: 2000 });
298
+ });
299
+
300
+ app.on('before-quit', () => {
301
+ notifier?.destroy(); // closes all windows, removes IPC listeners
302
+ notifier = null;
303
+ });
304
+ ```
305
+
306
+ ## 17. How It Works
307
+
308
+ Each notification is its own `BrowserWindow`, configured as transparent, frameless, and always-on-top so it behaves like an in-app toast rather than a normal window. The main process computes the window coordinates from the target display’s `workArea` and the configured `position`, `margin`, and `gap`. The renderer controls visuals (icons, theme, and CSS animations), while the main process owns window lifecycle and positioning. The two sides communicate using Electron IPC to handle click/close events, theme updates in `auto` mode, and reposition requests during stacking changes.
309
+
310
+ ## Example app (in this repo)
311
+
312
+ This package includes a demo under `example/`.
313
+
314
+ ```bash
315
+ cd example
316
+ npm i
317
+ npm start
318
+ ```
319
+
320
+ ## 18. License
321
+
322
+ MIT
323
+
@@ -0,0 +1,2 @@
1
+ export { NotificationManager } from './src/NotificationManager';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NotificationManager = void 0;
4
+ var NotificationManager_1 = require("./src/NotificationManager");
5
+ Object.defineProperty(exports, "NotificationManager", { enumerable: true, get: function () { return NotificationManager_1.NotificationManager; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;AAAA,iEAAgE;AAAvD,0HAAA,mBAAmB,OAAA"}
@@ -0,0 +1,45 @@
1
+ import { EventEmitter } from 'events';
2
+ import type { CloseReason, NotificationManagerOptions, NotificationOptions } from './types';
3
+ import type { NotificationUpdatePayload } from './types/notification.types';
4
+ export declare class NotificationManager extends EventEmitter {
5
+ private readonly options;
6
+ private readonly notifications;
7
+ private readonly windows;
8
+ private readonly order;
9
+ private readonly pending;
10
+ private readonly onIpcCloseBound;
11
+ private readonly onIpcClickBound;
12
+ constructor(options?: NotificationManagerOptions);
13
+ /**
14
+ * Show a notification window and return its unique ID.
15
+ */
16
+ show(options: NotificationOptions): string;
17
+ /**
18
+ * Manually close a notification by ID.
19
+ */
20
+ close(id: string): void;
21
+ /**
22
+ * Close all active notifications.
23
+ */
24
+ closeAll(): void;
25
+ /**
26
+ * Destroy manager and clean up all resources.
27
+ */
28
+ destroy(): void;
29
+ dispose(reason?: CloseReason): void;
30
+ /**
31
+ * Update an existing notification (loading/progress/description).
32
+ */
33
+ update(id: string, payload: NotificationUpdatePayload): void;
34
+ private onRendererClose;
35
+ private onRendererClick;
36
+ private closeById;
37
+ private idsOnDisplay;
38
+ private reflowForDisplay;
39
+ private pump;
40
+ private getTargetDisplay;
41
+ private getDisplayIdForWindow;
42
+ private newId;
43
+ private broadcastThemeIfAuto;
44
+ }
45
+ //# sourceMappingURL=NotificationManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationManager.d.ts","sourceRoot":"","sources":["../../src/NotificationManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAOtC,OAAO,KAAK,EACV,WAAW,EAEX,0BAA0B,EAC1B,mBAAmB,EAGpB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAc5E,qBAAa,mBAAoB,SAAQ,YAAY;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgC;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAE3D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6C;IAC7E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6C;gBAE1D,OAAO,GAAE,0BAA+B;IA4B3D;;OAEG;IACI,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM;IAuBjD;;OAEG;IACI,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAqB9B;;OAEG;IACI,QAAQ,IAAI,IAAI;IASvB;;OAEG;IACI,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,MAAM,GAAE,WAA4B,GAAG,IAAI;IAuB1D;;OAEG;IACI,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,yBAAyB,GAAG,IAAI;IAyBnE,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,SAAS;IA4BjB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,gBAAgB;IA0CxB,OAAO,CAAC,IAAI;IAmDZ,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,oBAAoB;CAY7B"}