@pixpilot/chrome-lifecycle 0.8.0 → 0.9.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.
package/README.md CHANGED
@@ -127,10 +127,68 @@ unsubscribe();
127
127
 
128
128
  **Callback data:**
129
129
 
130
- | Property | Type | Description |
131
- | ---------- | ------------------------- | ------------------------- |
132
- | `windowId` | `number` | Chrome window ID |
133
- | `state` | `'visible'` \| `'hidden'` | Current side panel state |
134
- | `reason` | `string` | What triggered the change |
130
+ | Property | Type | Description |
131
+ | --------------- | ---------------------------------------- | ----------------------------------------------- |
132
+ | `windowId` | `number` | Chrome window ID |
133
+ | `state` | `'visible'` \| `'hidden'` | Current side panel state |
134
+ | `previousState` | `'visible'` \| `'hidden'` \| `undefined` | State known before this one, `undefined` if new |
135
+ | `reason` | `string` | What triggered the change |
135
136
 
136
137
  **Returns:** Unsubscribe function
138
+
139
+ This fires for **every** state report, including repeats of a state you already know
140
+ about — the tracker re-reports `visible` when it reconnects to a restarted service
141
+ worker, and can report `hidden` twice in a row (a visibility change followed by a
142
+ port disconnect). If you want transitions rather than reports, use
143
+ `onSidePanelShown` / `onSidePanelHidden` below instead of comparing states yourself.
144
+
145
+ ##### `onSidePanelShown(listener)`
146
+
147
+ Fires only when a side panel _becomes_ visible. This is the event to use for "the
148
+ panel is back on screen, resync it".
149
+
150
+ ```typescript
151
+ import { onSidePanelShown } from '@pixpilot/chrome-lifecycle';
152
+
153
+ const unsubscribe = onSidePanelShown(({ windowId, reason }) => {
154
+ refreshPanelContents(windowId);
155
+ });
156
+ ```
157
+
158
+ It covers three cases, distinguishable via `reason`:
159
+
160
+ | `reason` | When |
161
+ | ------------------- | ---------------------------------------------------------------------------------- |
162
+ | `document-load` | A freshly loaded panel document |
163
+ | `visibility-change` | A cached document Chrome is showing again — see the note on other extensions below |
164
+ | `reconnected` | First report after the service worker restarted, so your background state was lost |
165
+
166
+ **Callback data:** same as `onSidePanelStateChange`
167
+
168
+ **Returns:** Unsubscribe function
169
+
170
+ ##### `onSidePanelHidden(listener)`
171
+
172
+ Fires only when a side panel stops being visible. Nothing fires for a window that
173
+ was never seen visible, so a service worker restart followed by a port disconnect
174
+ stays quiet instead of reporting a close the listener never saw open.
175
+
176
+ **Callback data:** same as `onSidePanelStateChange`
177
+
178
+ **Returns:** Unsubscribe function
179
+
180
+ #### When another extension's side panel takes over
181
+
182
+ Chrome gives all extensions one side panel slot per window. When the user opens a
183
+ different extension's panel, yours does **not** get torn down — Chrome keeps the
184
+ document alive and hides it, so it comes back with all of its state intact,
185
+ including anything stale it was showing before the switch.
186
+
187
+ That surfaces here as `visibility-change`, never `document-load`. A listener that
188
+ only refreshes on `document-load` will look correct until a user has two side panel
189
+ extensions installed, and then silently serve stale content. `onSidePanelShown`
190
+ handles both.
191
+
192
+ There is no Chrome API to ask whether your panel is the one currently on screen, so
193
+ `document.hidden` in the panel document is the only available signal. Leave
194
+ `trackDocumentVisibility` enabled if you rely on this.
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- const e=new Map;let t=!1;function n(){t||(t=!0,chrome.windows.onRemoved.addListener(t=>{let n=e.get(t);n&&(n.forEach(e=>{e()}),e.delete(t))}))}function r(t,r){n();let i=e.get(t);return i||(i=new Set,e.set(t,i)),i.add(r),()=>{let n=e.get(t);n&&(n.delete(r),n.size===0&&e.delete(t))}}const i=250,a=5e3,o=2;let s=!1;function c(e={}){if(s)return console.info(`Side panel state tracker already initialized`),()=>{};s=!0;let{trackDocumentVisibility:t=!0}=e,n,r,i=0,a,o=!1;function c(){r&&=(clearTimeout(r),void 0)}return chrome.windows.getCurrent(e=>{if(o)return;if(e.id==null){console.error(`[side-panel-state-tracker] Could not get window ID`);return}let s=e.id;function l(){return t&&document.hidden?`hidden`:`visible`}function u(){if(o||r)return;n=void 0,console.info(`[side-panel-state-tracker] Connection to background lost, scheduling reconnect...`);let e=Math.min(250*2**i,5e3);i+=1,r=setTimeout(()=>{r=void 0,f(`reconnected`)},e)}function d(e){let t=n;if(!t){u();return}try{t.postMessage({...e,windowId:s,type:`side-panel-state-tracker`,timestamp:Date.now()})}catch(e){console.error(`[side-panel-state-tracker] Failed to send message:`,e),u()}}function f(e){if(!o){c();try{let t=chrome.runtime.connect({name:chrome.runtime.id});n=t,i=0,t.onDisconnect.addListener(()=>{n===t&&(console.info(`[side-panel-state-tracker] Background connection lost.`),u())}),t.onMessage.addListener(e=>{e.type===`close-side-panel`&&(o=!0,c(),window.close())}),d({state:e===`document-load`?`visible`:l(),reason:e})}catch(e){console.error(`[side-panel-state-tracker] Failed to connect:`,e),u()}}}t&&(a=()=>{d({state:l(),reason:`visibility-change`})},document.addEventListener(`visibilitychange`,a)),f(`document-load`)}),()=>{o=!0,c(),a&&document.removeEventListener(`visibilitychange`,a);let e=n;n=void 0,e?.disconnect(),s=!1}}const l=new Map,u=new Set;let d=!1;function f(){d||(d=!0,chrome.action.onClicked.addListener(e=>{let t=l.get(e.windowId);t&&t.state===`visible`?t.port&&(l.delete(e.windowId),t.port.postMessage({type:`close-side-panel`})):chrome.sidePanel.open({windowId:e.windowId}).catch(console.error)}),chrome.runtime.onConnect.addListener(e=>{e.name===chrome.runtime.id&&(e.onMessage.addListener(t=>{t.type===`side-panel-state-tracker`&&t.state&&h({port:e,state:t.state,reason:t.reason??`unknown`,windowId:t.windowId,type:t.type})}),e.onDisconnect.addListener(e=>{Array.from(l.entries()).forEach(([t,n])=>{n.port&&n.port===e&&h({port:void 0,state:`hidden`,reason:`port-disconnected`,windowId:t,type:`side-panel-state-tracker`})})}))}))}function p(){d||=(f(),!0)}function m(e){if(e.type!==`side-panel-state-tracker`)return;let t={state:e.state,reason:e.reason,windowId:e.windowId};u.forEach(e=>{try{e(t)}catch(e){console.error(`Error in side panel state listener:`,e)}})}function h(e){let{windowId:t,state:n}=e;if(n===`hidden`){l.delete(t),m(e);return}l.set(t,e),m(e)}function g(e){return p(),l.get(e)?.state}function _(e){return p(),g(e)===`visible`}function v(e){return p(),u.add(e),()=>{u.delete(e)}}exports.getSidePanelStateForWindow=g,exports.initSidePanelStateManager=f,exports.initializeSidePanelStateTracker=c,exports.isWindowSidePanelVisible=_,exports.onSidePanelStateChange=v,exports.onWindowClose=r;
1
+ const e=new Map;let t=!1;function n(){t||(t=!0,chrome.windows.onRemoved.addListener(t=>{let n=e.get(t);n&&(n.forEach(e=>{e()}),e.delete(t))}))}function r(t,r){n();let i=e.get(t);return i||(i=new Set,e.set(t,i)),i.add(r),()=>{let n=e.get(t);n&&(n.delete(r),n.size===0&&e.delete(t))}}const i=250,a=5e3,o=2;let s=!1;function c(e={}){if(s)return console.info(`Side panel state tracker already initialized`),()=>{};s=!0;let{trackDocumentVisibility:t=!0}=e,n,r,i=0,a,o=!1;function c(){r&&=(clearTimeout(r),void 0)}return chrome.windows.getCurrent(e=>{if(o)return;if(e.id==null){console.error(`[side-panel-state-tracker] Could not get window ID`);return}let s=e.id;function l(){return t&&document.hidden?`hidden`:`visible`}function u(){if(o||r)return;n=void 0,console.info(`[side-panel-state-tracker] Connection to background lost, scheduling reconnect...`);let e=Math.min(250*2**i,5e3);i+=1,r=setTimeout(()=>{r=void 0,f(`reconnected`)},e)}function d(e){let t=n;if(!t){u();return}try{t.postMessage({...e,windowId:s,type:`side-panel-state-tracker`,timestamp:Date.now()})}catch(e){console.error(`[side-panel-state-tracker] Failed to send message:`,e),u()}}function f(e){if(!o){c();try{let t=chrome.runtime.connect({name:chrome.runtime.id});n=t,i=0,t.onDisconnect.addListener(()=>{n===t&&(console.info(`[side-panel-state-tracker] Background connection lost.`),u())}),t.onMessage.addListener(e=>{e.type===`close-side-panel`&&(o=!0,c(),window.close())}),d({state:e===`document-load`?`visible`:l(),reason:e})}catch(e){console.error(`[side-panel-state-tracker] Failed to connect:`,e),u()}}}t&&(a=()=>{d({state:l(),reason:`visibility-change`})},document.addEventListener(`visibilitychange`,a)),f(`document-load`)}),()=>{o=!0,c(),a&&document.removeEventListener(`visibilitychange`,a);let e=n;n=void 0,e?.disconnect(),s=!1}}const l=new Map,u=new Map,d=new Set;let f=!1;function p(){f||(f=!0,chrome.action.onClicked.addListener(e=>{let t=l.get(e.windowId);t&&t.state===`visible`?t.port&&(l.delete(e.windowId),t.port.postMessage({type:`close-side-panel`})):chrome.sidePanel.open({windowId:e.windowId}).catch(console.error)}),chrome.runtime.onConnect.addListener(e=>{e.name===chrome.runtime.id&&(e.onMessage.addListener(t=>{t.type===`side-panel-state-tracker`&&t.state&&g({port:e,state:t.state,reason:t.reason??`unknown`,windowId:t.windowId,type:t.type})}),e.onDisconnect.addListener(e=>{Array.from(l.entries()).forEach(([t,n])=>{n.port&&n.port===e&&g({port:void 0,state:`hidden`,reason:`port-disconnected`,windowId:t,type:`side-panel-state-tracker`})})}))}),chrome.windows.onRemoved.addListener(e=>{l.delete(e),u.delete(e)}))}function m(){f||=(p(),!0)}function h(e,t){if(e.type!==`side-panel-state-tracker`)return;let n={state:e.state,reason:e.reason,windowId:e.windowId,previousState:t};d.forEach(e=>{try{e(n)}catch(e){console.error(`Error in side panel state listener:`,e)}})}function g(e){let{windowId:t,state:n}=e,r=u.get(t);if(e.type===`side-panel-state-tracker`&&u.set(t,n),n===`hidden`){l.delete(t),h(e,r);return}l.set(t,e),h(e,r)}function _(e){return m(),l.get(e)?.state}function v(e){return m(),_(e)===`visible`}function y(e){return m(),d.add(e),()=>{d.delete(e)}}function b(e){return y(t=>{t.state===`visible`&&t.previousState!==`visible`&&e(t)})}function x(e){return y(t=>{t.state===`hidden`&&t.previousState===`visible`&&e(t)})}exports.getSidePanelStateForWindow=_,exports.initSidePanelStateManager=p,exports.initializeSidePanelStateTracker=c,exports.isWindowSidePanelVisible=v,exports.onSidePanelHidden=x,exports.onSidePanelShown=b,exports.onSidePanelStateChange=y,exports.onWindowClose=r;
package/dist/index.d.cts CHANGED
@@ -41,7 +41,19 @@ interface SidePanelStateData extends BaseSidePanelMessage {
41
41
  state: SidePanelState;
42
42
  reason: string;
43
43
  }
44
- type SidePanelStateChangeData = Omit<SidePanelStateData, 'timestamp' | 'type'>;
44
+ interface SidePanelStateChangeData extends Omit<SidePanelStateData, 'timestamp' | 'type'> {
45
+ /**
46
+ * The state recorded for this window before this change.
47
+ *
48
+ * `undefined` means nothing was recorded yet: the first event for a window, or
49
+ * the first event after the service worker restarted and lost its state. Use it
50
+ * to tell a real transition apart from a repeat of the state you already knew —
51
+ * the tracker reports the same state more than once (a reconnect while still
52
+ * visible, consecutive visibility changes), so plain state events are not
53
+ * transitions.
54
+ */
55
+ previousState?: SidePanelState;
56
+ }
45
57
  interface SidePanelClientMessage {
46
58
  type: 'close-side-panel';
47
59
  }
@@ -66,5 +78,31 @@ declare function isWindowSidePanelVisible(windowId: number): boolean;
66
78
  * @returns Unsubscribe function to remove the listener
67
79
  */
68
80
  declare function onSidePanelStateChange(listener: SidePanelStateListener): () => void;
81
+ /**
82
+ * Adds a listener that fires only when a side panel *becomes* visible, skipping
83
+ * repeats of a state you already knew about.
84
+ *
85
+ * This is the event to use for "the panel is back on screen, resync it". It covers
86
+ * a freshly loaded document (`reason: 'document-load'`), a panel Chrome had cached
87
+ * while another extension's side panel took over the slot
88
+ * (`reason: 'visibility-change'`), and the first report after a service worker
89
+ * restart (`reason: 'reconnected'`).
90
+ *
91
+ * @param listener - Callback function that receives state change data
92
+ * @returns Unsubscribe function to remove the listener
93
+ */
94
+ declare function onSidePanelShown(listener: SidePanelStateListener): () => void;
95
+ /**
96
+ * Adds a listener that fires only when a side panel *stops* being visible, skipping
97
+ * repeats of a state you already knew about.
98
+ *
99
+ * Nothing fires for a window that was never seen visible, so a service worker
100
+ * restart followed by a port disconnect stays quiet instead of reporting a close
101
+ * that the listener never saw open.
102
+ *
103
+ * @param listener - Callback function that receives state change data
104
+ * @returns Unsubscribe function to remove the listener
105
+ */
106
+ declare function onSidePanelHidden(listener: SidePanelStateListener): () => void;
69
107
  //#endregion
70
- export { InitializeSidePanelStateTrackerOptions, SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelStateChange, onWindowClose };
108
+ export { InitializeSidePanelStateTrackerOptions, SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, SidePanelStateListener, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelHidden, onSidePanelShown, onSidePanelStateChange, onWindowClose };
package/dist/index.d.ts CHANGED
@@ -41,7 +41,19 @@ interface SidePanelStateData extends BaseSidePanelMessage {
41
41
  state: SidePanelState;
42
42
  reason: string;
43
43
  }
44
- type SidePanelStateChangeData = Omit<SidePanelStateData, 'timestamp' | 'type'>;
44
+ interface SidePanelStateChangeData extends Omit<SidePanelStateData, 'timestamp' | 'type'> {
45
+ /**
46
+ * The state recorded for this window before this change.
47
+ *
48
+ * `undefined` means nothing was recorded yet: the first event for a window, or
49
+ * the first event after the service worker restarted and lost its state. Use it
50
+ * to tell a real transition apart from a repeat of the state you already knew —
51
+ * the tracker reports the same state more than once (a reconnect while still
52
+ * visible, consecutive visibility changes), so plain state events are not
53
+ * transitions.
54
+ */
55
+ previousState?: SidePanelState;
56
+ }
45
57
  interface SidePanelClientMessage {
46
58
  type: 'close-side-panel';
47
59
  }
@@ -66,5 +78,31 @@ declare function isWindowSidePanelVisible(windowId: number): boolean;
66
78
  * @returns Unsubscribe function to remove the listener
67
79
  */
68
80
  declare function onSidePanelStateChange(listener: SidePanelStateListener): () => void;
81
+ /**
82
+ * Adds a listener that fires only when a side panel *becomes* visible, skipping
83
+ * repeats of a state you already knew about.
84
+ *
85
+ * This is the event to use for "the panel is back on screen, resync it". It covers
86
+ * a freshly loaded document (`reason: 'document-load'`), a panel Chrome had cached
87
+ * while another extension's side panel took over the slot
88
+ * (`reason: 'visibility-change'`), and the first report after a service worker
89
+ * restart (`reason: 'reconnected'`).
90
+ *
91
+ * @param listener - Callback function that receives state change data
92
+ * @returns Unsubscribe function to remove the listener
93
+ */
94
+ declare function onSidePanelShown(listener: SidePanelStateListener): () => void;
95
+ /**
96
+ * Adds a listener that fires only when a side panel *stops* being visible, skipping
97
+ * repeats of a state you already knew about.
98
+ *
99
+ * Nothing fires for a window that was never seen visible, so a service worker
100
+ * restart followed by a port disconnect stays quiet instead of reporting a close
101
+ * that the listener never saw open.
102
+ *
103
+ * @param listener - Callback function that receives state change data
104
+ * @returns Unsubscribe function to remove the listener
105
+ */
106
+ declare function onSidePanelHidden(listener: SidePanelStateListener): () => void;
69
107
  //#endregion
70
- export { InitializeSidePanelStateTrackerOptions, SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelStateChange, onWindowClose };
108
+ export { InitializeSidePanelStateTrackerOptions, SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, SidePanelStateListener, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelHidden, onSidePanelShown, onSidePanelStateChange, onWindowClose };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- const e=new Map;let t=!1;function n(){t||(t=!0,chrome.windows.onRemoved.addListener(t=>{let n=e.get(t);n&&(n.forEach(e=>{e()}),e.delete(t))}))}function r(t,r){n();let i=e.get(t);return i||(i=new Set,e.set(t,i)),i.add(r),()=>{let n=e.get(t);n&&(n.delete(r),n.size===0&&e.delete(t))}}let i=!1;function a(e={}){if(i)return console.info(`Side panel state tracker already initialized`),()=>{};i=!0;let{trackDocumentVisibility:t=!0}=e,n,r,a=0,o,s=!1;function c(){r&&=(clearTimeout(r),void 0)}return chrome.windows.getCurrent(e=>{if(s)return;if(e.id==null){console.error(`[side-panel-state-tracker] Could not get window ID`);return}let i=e.id;function l(){return t&&document.hidden?`hidden`:`visible`}function u(){if(s||r)return;n=void 0,console.info(`[side-panel-state-tracker] Connection to background lost, scheduling reconnect...`);let e=Math.min(250*2**a,5e3);a+=1,r=setTimeout(()=>{r=void 0,f(`reconnected`)},e)}function d(e){let t=n;if(!t){u();return}try{t.postMessage({...e,windowId:i,type:`side-panel-state-tracker`,timestamp:Date.now()})}catch(e){console.error(`[side-panel-state-tracker] Failed to send message:`,e),u()}}function f(e){if(!s){c();try{let t=chrome.runtime.connect({name:chrome.runtime.id});n=t,a=0,t.onDisconnect.addListener(()=>{n===t&&(console.info(`[side-panel-state-tracker] Background connection lost.`),u())}),t.onMessage.addListener(e=>{e.type===`close-side-panel`&&(s=!0,c(),window.close())}),d({state:e===`document-load`?`visible`:l(),reason:e})}catch(e){console.error(`[side-panel-state-tracker] Failed to connect:`,e),u()}}}t&&(o=()=>{d({state:l(),reason:`visibility-change`})},document.addEventListener(`visibilitychange`,o)),f(`document-load`)}),()=>{s=!0,c(),o&&document.removeEventListener(`visibilitychange`,o);let e=n;n=void 0,e?.disconnect(),i=!1}}const o=new Map,s=new Set;let c=!1;function l(){c||(c=!0,chrome.action.onClicked.addListener(e=>{let t=o.get(e.windowId);t&&t.state===`visible`?t.port&&(o.delete(e.windowId),t.port.postMessage({type:`close-side-panel`})):chrome.sidePanel.open({windowId:e.windowId}).catch(console.error)}),chrome.runtime.onConnect.addListener(e=>{e.name===chrome.runtime.id&&(e.onMessage.addListener(t=>{t.type===`side-panel-state-tracker`&&t.state&&f({port:e,state:t.state,reason:t.reason??`unknown`,windowId:t.windowId,type:t.type})}),e.onDisconnect.addListener(e=>{Array.from(o.entries()).forEach(([t,n])=>{n.port&&n.port===e&&f({port:void 0,state:`hidden`,reason:`port-disconnected`,windowId:t,type:`side-panel-state-tracker`})})}))}))}function u(){c||=(l(),!0)}function d(e){if(e.type!==`side-panel-state-tracker`)return;let t={state:e.state,reason:e.reason,windowId:e.windowId};s.forEach(e=>{try{e(t)}catch(e){console.error(`Error in side panel state listener:`,e)}})}function f(e){let{windowId:t,state:n}=e;if(n===`hidden`){o.delete(t),d(e);return}o.set(t,e),d(e)}function p(e){return u(),o.get(e)?.state}function m(e){return u(),p(e)===`visible`}function h(e){return u(),s.add(e),()=>{s.delete(e)}}export{p as getSidePanelStateForWindow,l as initSidePanelStateManager,a as initializeSidePanelStateTracker,m as isWindowSidePanelVisible,h as onSidePanelStateChange,r as onWindowClose};
1
+ const e=new Map;let t=!1;function n(){t||(t=!0,chrome.windows.onRemoved.addListener(t=>{let n=e.get(t);n&&(n.forEach(e=>{e()}),e.delete(t))}))}function r(t,r){n();let i=e.get(t);return i||(i=new Set,e.set(t,i)),i.add(r),()=>{let n=e.get(t);n&&(n.delete(r),n.size===0&&e.delete(t))}}let i=!1;function a(e={}){if(i)return console.info(`Side panel state tracker already initialized`),()=>{};i=!0;let{trackDocumentVisibility:t=!0}=e,n,r,a=0,o,s=!1;function c(){r&&=(clearTimeout(r),void 0)}return chrome.windows.getCurrent(e=>{if(s)return;if(e.id==null){console.error(`[side-panel-state-tracker] Could not get window ID`);return}let i=e.id;function l(){return t&&document.hidden?`hidden`:`visible`}function u(){if(s||r)return;n=void 0,console.info(`[side-panel-state-tracker] Connection to background lost, scheduling reconnect...`);let e=Math.min(250*2**a,5e3);a+=1,r=setTimeout(()=>{r=void 0,f(`reconnected`)},e)}function d(e){let t=n;if(!t){u();return}try{t.postMessage({...e,windowId:i,type:`side-panel-state-tracker`,timestamp:Date.now()})}catch(e){console.error(`[side-panel-state-tracker] Failed to send message:`,e),u()}}function f(e){if(!s){c();try{let t=chrome.runtime.connect({name:chrome.runtime.id});n=t,a=0,t.onDisconnect.addListener(()=>{n===t&&(console.info(`[side-panel-state-tracker] Background connection lost.`),u())}),t.onMessage.addListener(e=>{e.type===`close-side-panel`&&(s=!0,c(),window.close())}),d({state:e===`document-load`?`visible`:l(),reason:e})}catch(e){console.error(`[side-panel-state-tracker] Failed to connect:`,e),u()}}}t&&(o=()=>{d({state:l(),reason:`visibility-change`})},document.addEventListener(`visibilitychange`,o)),f(`document-load`)}),()=>{s=!0,c(),o&&document.removeEventListener(`visibilitychange`,o);let e=n;n=void 0,e?.disconnect(),i=!1}}const o=new Map,s=new Map,c=new Set;let l=!1;function u(){l||(l=!0,chrome.action.onClicked.addListener(e=>{let t=o.get(e.windowId);t&&t.state===`visible`?t.port&&(o.delete(e.windowId),t.port.postMessage({type:`close-side-panel`})):chrome.sidePanel.open({windowId:e.windowId}).catch(console.error)}),chrome.runtime.onConnect.addListener(e=>{e.name===chrome.runtime.id&&(e.onMessage.addListener(t=>{t.type===`side-panel-state-tracker`&&t.state&&p({port:e,state:t.state,reason:t.reason??`unknown`,windowId:t.windowId,type:t.type})}),e.onDisconnect.addListener(e=>{Array.from(o.entries()).forEach(([t,n])=>{n.port&&n.port===e&&p({port:void 0,state:`hidden`,reason:`port-disconnected`,windowId:t,type:`side-panel-state-tracker`})})}))}),chrome.windows.onRemoved.addListener(e=>{o.delete(e),s.delete(e)}))}function d(){l||=(u(),!0)}function f(e,t){if(e.type!==`side-panel-state-tracker`)return;let n={state:e.state,reason:e.reason,windowId:e.windowId,previousState:t};c.forEach(e=>{try{e(n)}catch(e){console.error(`Error in side panel state listener:`,e)}})}function p(e){let{windowId:t,state:n}=e,r=s.get(t);if(e.type===`side-panel-state-tracker`&&s.set(t,n),n===`hidden`){o.delete(t),f(e,r);return}o.set(t,e),f(e,r)}function m(e){return d(),o.get(e)?.state}function h(e){return d(),m(e)===`visible`}function g(e){return d(),c.add(e),()=>{c.delete(e)}}function _(e){return g(t=>{t.state===`visible`&&t.previousState!==`visible`&&e(t)})}function v(e){return g(t=>{t.state===`hidden`&&t.previousState===`visible`&&e(t)})}export{m as getSidePanelStateForWindow,u as initSidePanelStateManager,a as initializeSidePanelStateTracker,h as isWindowSidePanelVisible,v as onSidePanelHidden,_ as onSidePanelShown,g as onSidePanelStateChange,r as onWindowClose};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pixpilot/chrome-lifecycle",
3
3
  "type": "module",
4
- "version": "0.8.0",
4
+ "version": "0.9.0",
5
5
  "description": "Lifecycle management utilities for Chrome extensions.",
6
6
  "author": "m.doaie <m.doaie@hotmail.com>",
7
7
  "license": "MIT",
@@ -30,9 +30,9 @@
30
30
  "tsdown": "^0.15.9",
31
31
  "typescript": "^5.9.3",
32
32
  "@internal/eslint-config": "0.3.0",
33
+ "@internal/tsconfig": "0.1.0",
33
34
  "@internal/prettier-config": "0.1.0",
34
35
  "@internal/vitest-config": "0.1.0",
35
- "@internal/tsconfig": "0.1.0",
36
36
  "@internal/tsdown-config": "0.1.0"
37
37
  },
38
38
  "prettier": "@internal/prettier-config",