diff --git a/biome.json b/biome.json
index c652017..53b2fa4 100644
--- a/biome.json
+++ b/biome.json
@@ -25,7 +25,8 @@
"includes": [
"src/**/*.{ts,tsx,js,jsx}",
"**/*.{json,jsonc,md,mdx,cjs,mjs}",
- "!dist/**/*"
+ "!dist/**/*",
+ "!addon/**/*"
]
},
"formatter": {
diff --git a/docs/sidebar.md b/docs/sidebar.md
index 6712134..397d7c2 100644
--- a/docs/sidebar.md
+++ b/docs/sidebar.md
@@ -6,16 +6,26 @@ Documentation:
- [Opera Sidebar Action API](https://help.opera.com/en/extensions/sidebar-action-api/)
- [Firefox Sidebar Action API](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/sidebarAction)
-A promise-based wrapper around Chrome's side panel (`chrome.sidePanel`, MV3) and Firefox/Opera `sidebarAction` APIs. Methods provide unified behavior to get/set options and behavior, open the panel, and, where supported, manage title and icon (Firefox & Opera) and badge (Opera).
+A promise-based wrapper around Chromium's side panel (`chrome.sidePanel`, MV3) and Firefox/Opera `sidebarAction` APIs. Methods provide unified behavior to get/set options and behavior, open the panel, and, where supported, manage title and icon (Firefox & Opera) and badge (Opera). Support includes Chrome, Edge, Firefox, and Opera.
+
+## Classes
+
+### SidebarError
+
+Custom error class thrown when an API method is not supported or fails.
## Methods
-- [getSidebarOptions(tabId?)](#getSidebarOptions)
-- [getSidebarBehavior()](#getSidebarBehavior)
+- [getSidebarOptions(tabId?)](#getSidebarOptions) [Chromium]
+- [getSidebarBehavior()](#getSidebarBehavior) [Chromium]
- [canOpenSidebar()](#canOpenSidebar)
+- [canCloseSidebar()](#canCloseSidebar)
- [openSidebar(options)](#openSidebar)
-- [setSidebarOptions(options?)](#setSidebarOptions)
-- [setSidebarBehavior(behavior?)](#setSidebarBehavior)
+- [closeSidebar(options)](#closeSidebar)
+- [setSidebarOptions(options?)](#setSidebarOptions) [Chromium]
+- [setSidebarBehavior(behavior?)](#setSidebarBehavior) [Chromium]
+- [isOpenSidebar(windowId?)](#isOpenSidebar)
+- [toggleSidebar()](#toggleSidebar) [Firefox]
- [setSidebarPath(path, tabId?)](#setSidebarPath)
- [getSidebarPath(tabId?)](#getSidebarPath)
- [setSidebarTitle(title, tabId?)](#setSidebarTitle) [Firefox, Opera]
@@ -33,23 +43,23 @@ A promise-based wrapper around Chrome's side panel (`chrome.sidePanel`, MV3) and
-### getSidebarOptions
+### getSidebarOptions [Chromium]
```
getSidebarOptions(tabId?: number): Promise
```
-Retrieves the side panel options (e.g., `path`) for the specified tab. Throws if the Side Panel API isn't supported. [MV3]
+Retrieves the side panel options (e.g., `path`) for the specified tab. Throws if the Side Panel API isn't supported (requires Chromium-based browsers like Chrome, Edge, or Opera MV3). [MV3]
-### getSidebarBehavior
+### getSidebarBehavior [Chromium]
```
getSidebarBehavior(): Promise
```
-Gets the current side panel behavior settings. Throws if unsupported. [MV3]
+Gets the current side panel behavior settings. Throws if unsupported (requires Chromium-based browsers like Chrome, Edge, or Opera MV3). [MV3]
@@ -59,7 +69,17 @@ Gets the current side panel behavior settings. Throws if unsupported. [MV3]
canOpenSidebar(): boolean
```
-Returns `true` if `chrome.sidePanel` (MV3) is available, or if `browser.sidebarAction.open` is available (Firefox).
+Returns `true` if `chrome.sidePanel` (Chromium MV3) is available, or if `sidebarAction.open` is available (Firefox/Opera).
+
+
+
+### canCloseSidebar
+
+```
+canCloseSidebar(): boolean
+```
+
+Returns `true` if `chrome.sidePanel` (Chromium MV3) is available, or if `sidebarAction.close` is available (Firefox/Opera).
@@ -69,27 +89,37 @@ Returns `true` if `chrome.sidePanel` (MV3) is available, or if `browser.sidebarA
openSidebar(options: chrome.sidePanel.OpenOptions): Promise
```
-Opens the side panel with the given options in Chrome (MV3). Falls back to `browser.sidebarAction.open()` in Firefox. Logs a warning and resolves as a no-op if unsupported.
+Opens the side panel with the given options in Chromium-based browsers (MV3). Falls back to `sidebarAction.open()` in Firefox/Opera. Throws if unsupported.
+
+
+
+### closeSidebar
+
+```
+closeSidebar(options: chrome.sidePanel.CloseOptions): Promise
+```
+
+Closes the side panel with the given options in Chromium-based browsers (MV3). Falls back to `sidebarAction.close()` in Firefox/Opera. Throws if unsupported.
-### setSidebarOptions
+### setSidebarOptions [Chromium]
```
setSidebarOptions(options?: chrome.sidePanel.PanelOptions): Promise
```
-Sets side panel options (e.g., `path`) in Chrome (MV3). Logs a warning and resolves as a no-op if unsupported. [MV3]
+Sets side panel options (e.g., `path`) in Chromium-based browsers (MV3). Throws if unsupported. [MV3]
-### setSidebarBehavior
+### setSidebarBehavior [Chromium]
```
setSidebarBehavior(behavior?: chrome.sidePanel.PanelBehavior): Promise
```
-Updates default panel behavior in Chrome (MV3). Logs a warning and resolves as a no-op if unsupported. [MV3]
+Updates default panel behavior in Chromium-based browsers (MV3). Throws if unsupported. [MV3]
@@ -99,7 +129,7 @@ Updates default panel behavior in Chrome (MV3). Logs a warning and resolves as a
setSidebarPath(path: string, tabId?: number): Promise
```
-Sets the sidebar path in Chrome via `setOptions` (MV3) or via `sidebarAction.setPanel()` in Firefox/Opera. Throws if unsupported.
+Sets the sidebar path in Chromium-based browsers via `setOptions` (MV3) or via `sidebarAction.setPanel()` in Firefox/Opera. Throws if unsupported.
@@ -109,7 +139,27 @@ Sets the sidebar path in Chrome via `setOptions` (MV3) or via `sidebarAction.set
getSidebarPath(tabId?: number): Promise
```
-Retrieves the sidebar path from Chrome (MV3) or parses from `sidebarAction.getPanel()` in Firefox/Opera. Throws if unsupported.
+Retrieves the sidebar path from Chromium-based browsers (MV3) or parses from `sidebarAction.getPanel()` in Firefox/Opera. Throws if unsupported.
+
+
+
+### isOpenSidebar
+
+```
+isOpenSidebar(windowId?: number): Promise
+```
+
+Checks if the sidebar is open for the given window in Chromium-based browsers (MV3) using `getContexts` and in Firefox/Opera using `sidebarAction.isOpen()`. Throws if unsupported.
+
+
+
+### toggleSidebar [Firefox]
+
+```
+toggleSidebar(): Promise
+```
+
+Toggles the sidebar in Firefox. Throws if unsupported.
@@ -119,7 +169,7 @@ Retrieves the sidebar path from Chrome (MV3) or parses from `sidebarAction.getPa
setSidebarTitle(title: string | number, tabId?: number): Promise
```
-Sets the sidebar title via `sidebarAction.setTitle()` (Firefox/Opera). Logs a warning if unsupported.
+Sets the sidebar title via `sidebarAction.setTitle()` (Firefox/Opera). Throws if unsupported.
@@ -129,7 +179,7 @@ Sets the sidebar title via `sidebarAction.setTitle()` (Firefox/Opera). Logs a wa
setSidebarIcon(details: opr.sidebarAction.IconDetails): Promise
```
-Sets the sidebar icon via `sidebarAction.setIcon()` (Firefox/Opera). Logs a warning if unsupported.
+Sets the sidebar icon via `sidebarAction.setIcon()` (Firefox/Opera). Throws if unsupported.
> Known issue (Opera): The `opr.sidebarAction.setIcon` API is currently broken and may fail with "Access to extension API denied".
> See: https://forums.opera.com/topic/75680/opr-sidebaraction-seticon-api-is-broken-access-to-extension-api-denied
@@ -142,7 +192,7 @@ Sets the sidebar icon via `sidebarAction.setIcon()` (Firefox/Opera). Logs a warn
setSidebarBadgeText(text: string | number, tabId?: number): Promise
```
-Sets the sidebar badge text via `opr.sidebarAction.setBadgeText()` (Opera only). Logs a warning if unsupported.
+Sets the sidebar badge text via `opr.sidebarAction.setBadgeText()` (Opera only). Throws if unsupported.
@@ -162,7 +212,7 @@ Clears the sidebar badge text (equivalent to setting an empty string) via `opr.s
setSidebarBadgeTextColor(color: string | [number, number, number, number], tabId?: number): Promise
```
-Sets the sidebar badge text color via `opr.sidebarAction.setBadgeTextColor()` (Opera only). Logs a warning if unsupported.
+Sets the sidebar badge text color via `opr.sidebarAction.setBadgeTextColor()` (Opera only). Throws if unsupported.
@@ -172,7 +222,7 @@ Sets the sidebar badge text color via `opr.sidebarAction.setBadgeTextColor()` (O
setSidebarBadgeBgColor(color: string | [number, number, number, number], tabId?: number): Promise
```
-Sets the sidebar badge background color via `opr.sidebarAction.setBadgeBackgroundColor()` (Opera only). Logs a warning if unsupported.
+Sets the sidebar badge background color via `opr.sidebarAction.setBadgeBackgroundColor()` (Opera only). Throws if unsupported.
diff --git a/package-lock.json b/package-lock.json
index efc9d48..5bb1226 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,7 @@
"@commitlint/cli": "^20.0.0",
"@commitlint/config-conventional": "^20.0.0",
"@release-it/conventional-changelog": "^10.0.1",
- "@types/chrome": "^0.1.12",
+ "@types/chrome": "^0.1.36",
"@types/jest": "^30.0.0",
"husky": "^9.1.7",
"jest": "^30.1.3",
diff --git a/package.json b/package.json
index 8317bac..32dc0d6 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,7 @@
"@commitlint/cli": "^20.0.0",
"@commitlint/config-conventional": "^20.0.0",
"@release-it/conventional-changelog": "^10.0.1",
- "@types/chrome": "^0.1.12",
+ "@types/chrome": "^0.1.36",
"@types/jest": "^30.0.0",
"husky": "^9.1.7",
"jest": "^30.1.3",
diff --git a/src/sidebar.ts b/src/sidebar.ts
index b5dbfaa..da08cc0 100644
--- a/src/sidebar.ts
+++ b/src/sidebar.ts
@@ -1,4 +1,5 @@
import {browser} from "./browser";
+import {getContexts} from "./runtime";
import {callWithPromise} from "./utils";
import type {FirefoxSidebarAction, OperaSidebarAction, SidebarAction} from "./types";
@@ -6,8 +7,10 @@ type Color = string | ColorArray;
type ColorArray = chrome.extensionTypes.ColorArray;
type OpenOptions = chrome.sidePanel.OpenOptions;
+type CloseOptions = chrome.sidePanel.CloseOptions;
type PanelOptions = chrome.sidePanel.PanelOptions;
type PanelBehavior = chrome.sidePanel.PanelBehavior;
+type ContextFilter = chrome.runtime.ContextFilter;
type IconDetails = opr.sidebarAction.IconDetails;
// Available in Firefox and Opera
@@ -19,6 +22,8 @@ const isAvailableOperaSidebar = (): boolean => globalThis?.opr?.sidebarAction !=
// Chromium standard
const sidePanel = (): typeof chrome.sidePanel | undefined => browser().sidePanel;
+export class SidebarError extends Error {}
+
// Methods
export const getSidebarOptions = (tabId?: number): Promise =>
callWithPromise(cb => {
@@ -56,8 +61,22 @@ export const canOpenSidebar = (): boolean => {
return false;
};
+export const canCloseSidebar = (): boolean => {
+ if (sidePanel()) {
+ return true;
+ }
+
+ const sb = (sidebarAction() as FirefoxSidebarAction) || undefined;
+
+ if (sb) {
+ return !!sb.close;
+ }
+
+ return false;
+};
+
export const openSidebar = (options: OpenOptions): Promise =>
- callWithPromise(cb => {
+ callWithPromise(async cb => {
const sp = sidePanel();
if (sp) {
@@ -67,12 +86,39 @@ export const openSidebar = (options: OpenOptions): Promise =>
const sb = sidebarAction() as FirefoxSidebarAction | undefined;
if (sb?.open) {
- return sb.open();
+ const result = sb.open();
+
+ if (result instanceof Promise) {
+ await result;
+ }
+
+ return cb();
+ }
+
+ throw new SidebarError("The sidebarAction.open API is not supported in this browser");
+ });
+
+export const closeSidebar = (options: CloseOptions): Promise =>
+ callWithPromise(async cb => {
+ const sp = sidePanel();
+
+ if (sp) {
+ return sp.close(options, cb);
}
- console.warn("The sidebar open API is not supported in this browser");
+ const sb = sidebarAction() as FirefoxSidebarAction | undefined;
- cb();
+ if (sb?.close) {
+ const result = sb.close();
+
+ if (result instanceof Promise) {
+ await result;
+ }
+
+ return cb();
+ }
+
+ throw new SidebarError("The sidebarAction.close API is not supported in this browser");
});
export const setSidebarOptions = (options?: PanelOptions): Promise =>
@@ -80,9 +126,7 @@ export const setSidebarOptions = (options?: PanelOptions): Promise =>
const sp = sidePanel();
if (!sp) {
- console.warn("The chrome.sidePanel.setOptions API is not supported for this browser");
-
- return cb();
+ throw new SidebarError("The chrome.sidePanel.setOptions API is not supported for this browser");
}
sp.setOptions(options || {}, cb);
@@ -93,12 +137,53 @@ export const setSidebarBehavior = (behavior?: PanelBehavior): Promise =>
const sp = sidePanel();
if (!sp) {
- console.warn("The chrome.sidePanel.setPanelBehavior API is not supported in this browser");
+ throw new SidebarError("The chrome.sidePanel.setPanelBehavior API is not supported in this browser");
+ }
+
+ sp.setPanelBehavior(behavior || {}, cb);
+ });
+
+export const isOpenSidebar = async (windowId?: number): Promise => {
+ if (sidePanel()) {
+ const filter: ContextFilter = {contextTypes: ["SIDE_PANEL"]};
+
+ if (windowId) filter.windowIds = [windowId];
+
+ return (await getContexts(filter)).length !== 0;
+ }
+
+ return callWithPromise(async cb => {
+ const sb = sidebarAction() as FirefoxSidebarAction | undefined;
+
+ if (sb?.isOpen) {
+ const result = sb.isOpen({windowId});
+
+ if (result instanceof Promise) {
+ return cb(await result);
+ } else {
+ return cb(result);
+ }
+ }
+
+ throw new SidebarError("The sidebarAction.isOpen API is not supported in this browser");
+ });
+};
+
+export const toggleSidebar = (): Promise =>
+ callWithPromise(async cb => {
+ const sb = sidebarAction() as FirefoxSidebarAction | undefined;
+
+ if (sb?.toggle) {
+ const result = sb.toggle();
+
+ if (result instanceof Promise) {
+ await result;
+ }
return cb();
}
- sp.setPanelBehavior(behavior || {}, cb);
+ throw new SidebarError("The sidebarAction.toggle API is not supported in this browser");
});
// Customs methods
@@ -153,19 +238,16 @@ export const setSidebarTitle = (title: string | number, tabId?: number): Promise
callWithPromise(async cb => {
const sb = sidebarAction();
- if (!sb) {
- console.warn("The sidebarAction.setTitle API is supported only in Opera or Firefox");
+ if (sb?.setTitle) {
+ const result = sb.setTitle({tabId, title: title.toString()});
+ if (result instanceof Promise) {
+ await result;
+ }
return cb();
}
- const result = sb.setTitle({tabId, title: title.toString()});
-
- if (result instanceof Promise) {
- await result;
- }
-
- cb();
+ throw new SidebarError("The sidebarAction.setTitle API is supported only in Opera or Firefox");
});
export const setSidebarBadgeText = (text: string | number, tabId?: number): Promise =>
@@ -178,9 +260,7 @@ export const setSidebarBadgeText = (text: string | number, tabId?: number): Prom
return cb();
}
- console.warn("The opr.sidebarAction.setBadgeText API is supported only in Opera");
-
- cb();
+ throw new SidebarError("The sidebarAction.setBadgeText API is supported only in Opera");
});
export const clearSidebarBadgeText = (tabId?: number): Promise => setSidebarBadgeText("", tabId);
@@ -211,9 +291,7 @@ export const setSidebarIcon = (details: IconDetails): Promise =>
return cb();
}
- console.warn("The sidebarAction.setIcon API is supported only in Opera or Firefox");
-
- cb();
+ throw new SidebarError("The sidebarAction.setIcon API is supported only in Opera or Firefox");
});
export const setSidebarBadgeTextColor = (color: Color, tabId?: number): Promise =>
@@ -226,9 +304,7 @@ export const setSidebarBadgeTextColor = (color: Color, tabId?: number): Promise<
return cb();
}
- console.warn("The opr.sidebarAction.setBadgeTextColor API is supported only in Opera");
-
- cb();
+ throw new SidebarError("The sidebarAction.setBadgeTextColor API is supported only in Opera");
});
export const setSidebarBadgeBgColor = (color: Color, tabId?: number): Promise =>
@@ -241,9 +317,7 @@ export const setSidebarBadgeBgColor = (color: Color, tabId?: number): Promise =>
@@ -258,7 +332,7 @@ export const getSidebarTitle = (tabId?: number): Promise =>
return (sb as any).getTitle({tabId});
}
- throw new Error("The sidebarAction.getTitle API not available");
+ throw new SidebarError("The sidebarAction.getTitle API not available");
});
export const getSidebarBadgeText = (tabId?: number): Promise =>
@@ -269,7 +343,7 @@ export const getSidebarBadgeText = (tabId?: number): Promise =>
return sb.getBadgeText({tabId}, cb);
}
- throw new Error("The opr.sidebarAction.getBadgeText API is supported only in Opera");
+ throw new SidebarError("The sidebarAction.getBadgeText API is supported only in Opera");
});
export const getSidebarBadgeTextColor = (tabId?: number): Promise =>
@@ -280,7 +354,7 @@ export const getSidebarBadgeTextColor = (tabId?: number): Promise =>
return sb.getBadgeTextColor({tabId}, cb);
}
- throw new Error("The opr.sidebarAction.getBadgeTextColor API is supported only in Opera");
+ throw new SidebarError("The sidebarAction.getBadgeTextColor API is supported only in Opera");
});
export const getSidebarBadgeBgColor = (tabId?: number): Promise =>
@@ -291,5 +365,5 @@ export const getSidebarBadgeBgColor = (tabId?: number): Promise =>
return sb.getBadgeBackgroundColor({tabId}, cb);
}
- throw new Error("The opr.sidebarAction.getBadgeBackgroundColor API is supported only in Opera");
+ throw new SidebarError("The sidebarAction.getBadgeBackgroundColor API is supported only in Opera");
});