@flighthq/tray 0.3.0-next.1032.e2b99fc → 0.3.0-next.1162.e90e518

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/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { createTrayIcon, destroyTrayIcon, displayTrayBalloon, getTrayCapabilities, getTrayIconBounds, getTrayIconTitle, getTrayIconTooltip, getTrayIcons, isTrayDestroyed, onTrayEvent, popupTrayContextMenu, removeTrayBalloon, setTrayIcon, setTrayIconContextMenu, setTrayIconTemplate, setTrayIconTitle, setTrayIconTooltip, setTrayIgnoreDoubleClickEvents, setTrayPressedIcon, startTrayIconAnimation, } from './contract';
1
+ export { createTrayIcon, destroyTrayIcon, displayTrayBalloon, getTrayCapabilities, getTrayIconBounds, getTrayIconTitle, getTrayIconTooltip, getTrayIcons, isTrayDestroyed, isTrayIconAnimating, onTrayEvent, popupTrayContextMenu, removeTrayBalloon, setTrayIcon, setTrayIconContextMenu, setTrayIconTemplate, setTrayIconTitle, setTrayIconTooltip, setTrayIgnoreDoubleClickEvents, setTrayPressedIcon, startTrayIconAnimation, stopTrayIconAnimation, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,8BAA8B,EAC9B,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,8BAA8B,EAC9B,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { createTrayIcon, destroyTrayIcon, displayTrayBalloon, getTrayCapabilities, getTrayIconBounds, getTrayIconTitle, getTrayIconTooltip, getTrayIcons, isTrayDestroyed, onTrayEvent, popupTrayContextMenu, removeTrayBalloon, setTrayIcon, setTrayIconContextMenu, setTrayIconTemplate, setTrayIconTitle, setTrayIconTooltip, setTrayIgnoreDoubleClickEvents, setTrayPressedIcon, startTrayIconAnimation, } from './contract';
1
+ export { createTrayIcon, destroyTrayIcon, displayTrayBalloon, getTrayCapabilities, getTrayIconBounds, getTrayIconTitle, getTrayIconTooltip, getTrayIcons, isTrayDestroyed, isTrayIconAnimating, onTrayEvent, popupTrayContextMenu, removeTrayBalloon, setTrayIcon, setTrayIconContextMenu, setTrayIconTemplate, setTrayIconTitle, setTrayIconTooltip, setTrayIgnoreDoubleClickEvents, setTrayPressedIcon, startTrayIconAnimation, stopTrayIconAnimation, } from './contract';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,8BAA8B,EAC9B,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,8BAA8B,EAC9B,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
package/dist/tray.d.ts CHANGED
@@ -11,6 +11,7 @@ export declare function getTrayIcons(): readonly TrayIcon[];
11
11
  export declare function getTrayIconTitle(tray: TrayIcon): string;
12
12
  export declare function getTrayIconTooltip(tray: TrayIcon): string;
13
13
  export declare function isTrayDestroyed(tray: TrayIcon): boolean;
14
+ export declare function isTrayIconAnimating(tray: TrayIcon): boolean;
14
15
  export declare function onTrayEvent(listener: (event: Readonly<TrayEventData>) => void): () => void;
15
16
  export declare function popupTrayContextMenu(tray: TrayIcon, position?: Readonly<Vector2Like>): void;
16
17
  export declare function removeTrayBalloon(tray: TrayIcon): void;
@@ -23,4 +24,5 @@ export declare function setTrayIconTooltip(tray: TrayIcon, tooltip: string): voi
23
24
  export declare function setTrayIgnoreDoubleClickEvents(tray: TrayIcon, ignore: boolean): void;
24
25
  export declare function setTrayPressedIcon(tray: TrayIcon, icon: string): void;
25
26
  export declare function startTrayIconAnimation(tray: TrayIcon, frames: readonly string[], intervalMs: number): () => void;
27
+ export declare function stopTrayIconAnimation(tray: TrayIcon): void;
26
28
  //# sourceMappingURL=tray.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tray.d.ts","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,QAAQ,EACR,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAc5D,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,GAAG,QAAQ,GAAG,IAAI,CAGnF;AAKD,wBAAgB,oBAAoB,IAAI,WAAW,CAmElD;AAGD,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAEpD;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAE9F;AAGD,wBAAgB,cAAc,IAAI,WAAW,CAG5C;AAKD,wBAAgB,mBAAmB,IAAI,QAAQ,CAAC,gBAAgB,CAAC,CAEhE;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,IAAI,CAEhF;AAGD,wBAAgB,YAAY,IAAI,SAAS,QAAQ,EAAE,CAIlD;AAGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAEvD;AAGD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAEzD;AAID,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAEvD;AAKD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAE1F;AAKD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,IAAI,CAE3F;AAGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAEtD;AAGD,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAEhE;AAID,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9D;AAID,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,gBAAgB,EAAE,GAAG,IAAI,CAE/F;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,GAAG,IAAI,CAE7E;AAGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEpE;AAGD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAExE;AAID,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAEpF;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAErE;AAOD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,IAAI,CAShH"}
1
+ {"version":3,"file":"tray.d.ts","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,QAAQ,EACR,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAgB5D,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,GAAG,QAAQ,GAAG,IAAI,CAGnF;AAKD,wBAAgB,oBAAoB,IAAI,WAAW,CAmElD;AAOD,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAGpD;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAE9F;AAGD,wBAAgB,cAAc,IAAI,WAAW,CAG5C;AAKD,wBAAgB,mBAAmB,IAAI,QAAQ,CAAC,gBAAgB,CAAC,CAEhE;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,IAAI,CAEhF;AAGD,wBAAgB,YAAY,IAAI,SAAS,QAAQ,EAAE,CAIlD;AAGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAEvD;AAGD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAEzD;AAID,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAEvD;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAE3D;AAKD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAE1F;AAKD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,IAAI,CAE3F;AAGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAEtD;AAGD,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAEhE;AAID,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9D;AAID,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,gBAAgB,EAAE,GAAG,IAAI,CAE/F;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,GAAG,IAAI,CAE7E;AAGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEpE;AAGD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAExE;AAID,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAEpF;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAErE;AAcD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,IAAI,CAgBhH;AAMD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAK1D"}
package/dist/tray.js CHANGED
@@ -1,4 +1,6 @@
1
- // Web tray capability constants. Web has no system tray — all capabilities are false.
1
+ // Web tray capability constants. Web has no system tray — all capabilities are false. Readonly and
2
+ // shared: getCapabilities hands this same object to every caller, so a mutable one would let any of
3
+ // them rewrite what the backend reports to all the others.
2
4
  const WEB_CAPABILITIES = {
3
5
  balloon: false,
4
6
  bounds: false,
@@ -85,7 +87,12 @@ export function createWebTrayBackend() {
85
87
  };
86
88
  }
87
89
  // Destroys a tray icon and frees its host resource. No-op when the host has no tray.
90
+ // Any icon animation running on this tray is stopped first: the interval writes through setTrayIcon,
91
+ // and left running it would keep calling into a resource the host has freed — forever, since the
92
+ // timer holds its own closure alive. Destroying the thing being animated is the clearest possible
93
+ // signal that the animation is over, so the caller does not have to have kept the stop function.
88
94
  export function destroyTrayIcon(tray) {
95
+ stopTrayIconAnimation(tray);
89
96
  getTrayBackend().destroy(tray.id);
90
97
  }
91
98
  // Displays a Windows balloon notification from the tray icon. No-op on macOS/Linux and on web.
@@ -129,6 +136,11 @@ export function getTrayIconTooltip(tray) {
129
136
  export function isTrayDestroyed(tray) {
130
137
  return getTrayBackend().isDestroyed(tray.id);
131
138
  }
139
+ // True while an icon animation started by startTrayIconAnimation is running on this tray. False once
140
+ // it is stopped, replaced by a later start, or the tray is destroyed.
141
+ export function isTrayIconAnimating(tray) {
142
+ return _animations.has(tray.id);
143
+ }
132
144
  // Subscribes to tray icon events, delivering a rich TrayEventData payload (id, type, bounds,
133
145
  // position, modifier keys, and drop payloads). Returns an unsubscribe function. On web this
134
146
  // never fires (no tray); a native host is required.
@@ -183,21 +195,51 @@ export function setTrayIgnoreDoubleClickEvents(tray, ignore) {
183
195
  export function setTrayPressedIcon(tray, icon) {
184
196
  getTrayBackend().setPressedIcon(tray.id, icon);
185
197
  }
186
- // Starts an animated icon sequence by cycling through the given frames at the specified interval.
187
- // The caller owns the timer — this function is a thin helper over setTrayIcon that starts an
188
- // interval and returns a stop function. Call the returned function (or stopTrayIconAnimation) to
189
- // cancel. The tray icon is not destroyed when the animation stops.
198
+ // Starts an animated icon sequence by cycling through the given frames at the specified interval,
199
+ // returning a stop function. An empty frame list is a no-op. The tray icon is not destroyed when the
200
+ // animation stops, and the last frame shown stays.
201
+ //
202
+ // A tray icon has one image, so it has one animation: starting a second on the same tray replaces the
203
+ // first rather than running both. Without that, a perfectly ordinary sequence — swap a "syncing"
204
+ // animation for an "error" one — left two intervals writing to the same icon on the same tick, and
205
+ // the only handle to the orphaned one was a stop function the caller had no reason to still be
206
+ // holding. The returned function stops only the animation it started, so calling a stale one cannot
207
+ // cancel a newer animation.
208
+ //
190
209
  // Note: interval timing is best-effort; the actual frame rate depends on the host event loop.
191
210
  export function startTrayIconAnimation(tray, frames, intervalMs) {
192
211
  if (frames.length === 0)
193
- return () => { };
212
+ return _noopStop;
213
+ stopTrayIconAnimation(tray);
194
214
  let index = 0;
195
215
  setTrayIcon(tray, frames[index]);
196
216
  const handle = setInterval(() => {
197
217
  index = (index + 1) % frames.length;
198
218
  setTrayIcon(tray, frames[index]);
199
219
  }, intervalMs);
200
- return () => clearInterval(handle);
220
+ _animations.set(tray.id, handle);
221
+ return () => {
222
+ // Only clear if this animation is still the current one; a later start already replaced it.
223
+ if (_animations.get(tray.id) !== handle)
224
+ return;
225
+ clearInterval(handle);
226
+ _animations.delete(tray.id);
227
+ };
228
+ }
229
+ // Stops the icon animation running on this tray, if any. Idempotent, and safe on a tray that never
230
+ // animated. The named counterpart to startTrayIconAnimation for callers that hold the TrayIcon but
231
+ // not the stop function it returned — which is most of them, since the handle is what gets passed
232
+ // around and the closure is what gets dropped.
233
+ export function stopTrayIconAnimation(tray) {
234
+ const handle = _animations.get(tray.id);
235
+ if (handle === undefined)
236
+ return;
237
+ clearInterval(handle);
238
+ _animations.delete(tray.id);
201
239
  }
202
240
  let _backend = null;
241
+ // The interval per animating tray id. Module-scoped like the backend, and correct at that scope: an
242
+ // id is the host's, so one id is one icon is one animation.
243
+ const _animations = new Map();
244
+ function _noopStop() { }
203
245
  //# sourceMappingURL=tray.js.map
package/dist/tray.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tray.js","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tray.js","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/tray",
3
- "version": "0.3.0-next.1032.e2b99fc",
3
+ "version": "0.3.0-next.1162.e90e518",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/flighthq/flight.git",
@@ -36,7 +36,7 @@
36
36
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
37
37
  },
38
38
  "dependencies": {
39
- "@flighthq/types": "0.3.0-next.1032.e2b99fc"
39
+ "@flighthq/types": "0.3.0-next.1162.e90e518"
40
40
  },
41
41
  "devDependencies": {
42
42
  "typescript": "^5.3.0"
package/src/tray.test.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  getTrayIconTitle,
21
21
  getTrayIconTooltip,
22
22
  isTrayDestroyed,
23
+ isTrayIconAnimating,
23
24
  onTrayEvent,
24
25
  popupTrayContextMenu,
25
26
  removeTrayBalloon,
@@ -32,6 +33,7 @@ import {
32
33
  setTrayIgnoreDoubleClickEvents,
33
34
  setTrayPressedIcon,
34
35
  startTrayIconAnimation,
36
+ stopTrayIconAnimation,
35
37
  } from './tray';
36
38
 
37
39
  // Fake backend state per icon.
@@ -54,6 +56,9 @@ function fakeBackend(caps: Partial<TrayCapabilities> = {}): TrayBackend & {
54
56
  capabilities: TrayCapabilities;
55
57
  fireEvent(event: TrayEventData): void;
56
58
  lastPopupPosition: { x: number; y: number } | null;
59
+ // Every icon written, in order — the animation tests assert on the sequence of writes rather than
60
+ // the final image, because two competing animations can leave a correct-looking final frame.
61
+ setIconCalls: string[];
57
62
  trays: Map<number, FakeTray>;
58
63
  } {
59
64
  const capabilities: TrayCapabilities = {
@@ -68,9 +73,12 @@ function fakeBackend(caps: Partial<TrayCapabilities> = {}): TrayBackend & {
68
73
  let eventListener: ((event: Readonly<TrayEventData>) => void) | null = null;
69
74
  const trays = new Map<number, FakeTray>();
70
75
 
76
+ const setIconCalls: string[] = [];
77
+
71
78
  return {
72
79
  capabilities,
73
80
  trays,
81
+ setIconCalls,
74
82
  lastPopupPosition: null,
75
83
  create(options) {
76
84
  const id = nextId++;
@@ -129,6 +137,7 @@ function fakeBackend(caps: Partial<TrayCapabilities> = {}): TrayBackend & {
129
137
  if (tray) tray.contextMenu = items;
130
138
  },
131
139
  setIcon(id, icon) {
140
+ setIconCalls.push(icon);
132
141
  const tray = trays.get(id);
133
142
  if (tray) tray.icon = icon;
134
143
  },
@@ -396,6 +405,43 @@ describe('isTrayDestroyed', () => {
396
405
  });
397
406
  });
398
407
 
408
+ describe('isTrayIconAnimating', () => {
409
+ beforeEach(() => {
410
+ vi.useFakeTimers();
411
+ });
412
+
413
+ afterEach(() => {
414
+ vi.useRealTimers();
415
+ });
416
+
417
+ it('is false before, true during, and false after an animation', () => {
418
+ setTrayBackend(fakeBackend());
419
+ const tray = createTrayIcon({ icon: 'a.png' })!;
420
+ expect(isTrayIconAnimating(tray)).toBe(false);
421
+ const stop = startTrayIconAnimation(tray, ['a.png', 'b.png'], 100);
422
+ expect(isTrayIconAnimating(tray)).toBe(true);
423
+ stop();
424
+ expect(isTrayIconAnimating(tray)).toBe(false);
425
+ });
426
+
427
+ it('is false for an empty frame list, which starts nothing', () => {
428
+ setTrayBackend(fakeBackend());
429
+ const tray = createTrayIcon({ icon: 'a.png' })!;
430
+ startTrayIconAnimation(tray, [], 100);
431
+ expect(isTrayIconAnimating(tray)).toBe(false);
432
+ });
433
+
434
+ it('tracks each tray independently', () => {
435
+ setTrayBackend(fakeBackend());
436
+ const first = createTrayIcon({ icon: 'a.png' })!;
437
+ const second = createTrayIcon({ icon: 'b.png' })!;
438
+ startTrayIconAnimation(first, ['a.png', 'b.png'], 100);
439
+ expect(isTrayIconAnimating(first)).toBe(true);
440
+ expect(isTrayIconAnimating(second)).toBe(false);
441
+ stopTrayIconAnimation(first);
442
+ });
443
+ });
444
+
399
445
  describe('onTrayEvent', () => {
400
446
  it('delivers rich TrayEventData via the active backend', () => {
401
447
  const backend = fakeBackend();
@@ -645,3 +691,85 @@ describe('startTrayIconAnimation', () => {
645
691
  expect(() => stop()).not.toThrow();
646
692
  });
647
693
  });
694
+
695
+ describe('stopTrayIconAnimation', () => {
696
+ beforeEach(() => {
697
+ vi.useFakeTimers();
698
+ });
699
+
700
+ afterEach(() => {
701
+ vi.useRealTimers();
702
+ });
703
+
704
+ it('stops the interval so no further frames are written', () => {
705
+ const backend = fakeBackend();
706
+ setTrayBackend(backend);
707
+ const tray = createTrayIcon({ icon: 'frame0.png' })!;
708
+ startTrayIconAnimation(tray, ['frame0.png', 'frame1.png'], 100);
709
+ stopTrayIconAnimation(tray);
710
+ const iconAtStop = backend.trays.get(tray.id)!.icon;
711
+ vi.advanceTimersByTime(500);
712
+ expect(backend.trays.get(tray.id)!.icon).toBe(iconAtStop);
713
+ });
714
+
715
+ it('is idempotent and safe on a tray that never animated', () => {
716
+ setTrayBackend(fakeBackend());
717
+ const tray = createTrayIcon({ icon: 'a.png' })!;
718
+ expect(() => {
719
+ stopTrayIconAnimation(tray);
720
+ stopTrayIconAnimation(tray);
721
+ }).not.toThrow();
722
+ });
723
+ });
724
+
725
+ describe('tray icon animation lifecycle', () => {
726
+ beforeEach(() => {
727
+ vi.useFakeTimers();
728
+ });
729
+
730
+ afterEach(() => {
731
+ vi.useRealTimers();
732
+ });
733
+
734
+ it('replaces a running animation instead of running both', () => {
735
+ // The regression this pins: two starts on one tray left two intervals writing to the same icon on
736
+ // the same tick — the icon flickered between the two sequences, and the only handle to the first
737
+ // was a stop function the caller had no reason to still hold.
738
+ const backend = fakeBackend();
739
+ setTrayBackend(backend);
740
+ const tray = createTrayIcon({ icon: 'a0.png' })!;
741
+ startTrayIconAnimation(tray, ['a0.png', 'a1.png'], 100);
742
+ startTrayIconAnimation(tray, ['b0.png', 'b1.png'], 100);
743
+ backend.setIconCalls.length = 0;
744
+ vi.advanceTimersByTime(100);
745
+ expect(backend.setIconCalls).toEqual(['b1.png']);
746
+ });
747
+
748
+ it('stops animating a tray that has been destroyed', () => {
749
+ // The regression this pins: the interval outlived the icon, calling setIcon on a destroyed tray
750
+ // forever — and the timer kept its own closure alive, so nothing could collect it.
751
+ const backend = fakeBackend();
752
+ setTrayBackend(backend);
753
+ const tray = createTrayIcon({ icon: 'a0.png' })!;
754
+ startTrayIconAnimation(tray, ['a0.png', 'a1.png'], 100);
755
+ destroyTrayIcon(tray);
756
+ backend.setIconCalls.length = 0;
757
+ vi.advanceTimersByTime(500);
758
+ expect(backend.setIconCalls).toEqual([]);
759
+ expect(isTrayIconAnimating(tray)).toBe(false);
760
+ });
761
+
762
+ it('a stale stop function cannot cancel the animation that replaced it', () => {
763
+ const backend = fakeBackend();
764
+ setTrayBackend(backend);
765
+ const tray = createTrayIcon({ icon: 'a0.png' })!;
766
+ const staleStop = startTrayIconAnimation(tray, ['a0.png', 'a1.png'], 100);
767
+ startTrayIconAnimation(tray, ['b0.png', 'b1.png'], 100);
768
+ staleStop();
769
+ expect(isTrayIconAnimating(tray)).toBe(true);
770
+ backend.setIconCalls.length = 0;
771
+ vi.advanceTimersByTime(100);
772
+ expect(backend.setIconCalls).toEqual(['b1.png']);
773
+ stopTrayIconAnimation(tray);
774
+ });
775
+ });