TypeScript / JavaScript Guide
TypeScript / JavaScript Guide
Section titled “TypeScript / JavaScript Guide”The winpane npm package is a native Node.js addon built with napi-rs. It works with Node.js, Bun, and Electron. TypeScript type definitions are generated automatically by napi-rs.
For Deno or environments where native addons are not available, use the JSON-RPC host as a subprocess instead (see the alternative approach section).
Install
Section titled “Install”npm install winpanePrebuilt binaries are included for Windows x64 and ARM64. No build tools needed.
Hello world
Section titled “Hello world”import { WinPane } from "winpane";
const wp = new WinPane();const hud = wp.createHud({ width: 300, height: 100, monitor: 0, anchor: 'top_left', margin: 40 });
wp.setRect(hud, "bg", { x: 0, y: 0, width: 300, height: 100, fill: "#14141ec8", cornerRadius: 8,});wp.setText(hud, "msg", { text: "Hello from TypeScript", x: 16, y: 16, fontSize: 18,});wp.show(hud);
// Keep the process alivesetInterval(() => {}, 1000);API types
Section titled “API types”Surface creation methods return a numeric ID. All subsequent calls take this ID as the first argument.
// Surface creationwp.createHud(options: HudOptions): numberwp.createPanel(options: PanelOptions): numberwp.createPip(options: PipOptions): numberwp.createTray(options: TrayOptions): number
// Elements (Hud and Panel only)wp.setText(surfaceId: number, key: string, options: TextOptions): voidwp.setRect(surfaceId: number, key: string, options: RectOptions): voidwp.setImage(surfaceId: number, key: string, options: ImageOptions): voidwp.removeElement(surfaceId: number, key: string): void
// Surface controlwp.show(surfaceId: number): voidwp.hide(surfaceId: number): voidwp.setPosition(surfaceId: number, x: number, y: number): voidwp.setSize(surfaceId: number, width: number, height: number): voidwp.setOpacity(surfaceId: number, opacity: number): voidwp.fadeIn(surfaceId: number, durationMs: number): voidwp.fadeOut(surfaceId: number, durationMs: number): voidwp.setCaptureExcluded(surfaceId: number, excluded: boolean): voidwp.setBackdrop(surfaceId: number, backdrop: "none" | "mica" | "acrylic"): voidwp.backdropSupported(): boolean
// Anchoringwp.anchorTo(surfaceId: number, targetHwnd: number, anchor: string, offsetX: number, offsetY: number): voidwp.unanchor(surfaceId: number): void
// Position & monitorswp.getPosition(surfaceId: number): number[]wp.monitors(): MonitorInfo[]
// PiP-specificwp.setSourceRegion(surfaceId: number, options: SourceRegionOptions): voidwp.clearSourceRegion(surfaceId: number): void
// Traywp.setTooltip(trayId: number, tooltip: string): voidwp.setTrayIcon(trayId: number, iconPath: string): voidwp.setPopup(trayId: number, panelSurfaceId: number): voidwp.setMenu(trayId: number, items: MenuItemOptions[]): void
// Eventswp.pollEvent(): WinPaneEvent | null
// Lifecyclewp.destroy(id: number): voidwp.close(): voidOption types
Section titled “Option types”interface HudOptions { width: number; height: number; x?: number; // default: 0 y?: number; // default: 0 monitor?: number; anchor?: 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'; margin?: number; positionKey?: string;}
interface PanelOptions { width: number; height: number; x?: number; y?: number; monitor?: number; anchor?: 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'; margin?: number; draggable?: boolean; // default: false dragHeight?: number; // default: 0 positionKey?: string;}
interface PipOptions { sourceHwnd: number; // HWND as a 64-bit integer width: number; height: number; x?: number; y?: number; monitor?: number; anchor?: 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'; margin?: number; positionKey?: string;}
interface TrayOptions { iconPath?: string; // path to PNG/JPEG/BMP, default: white 16x16 tooltip?: string;}
interface TextOptions { text: string; x: number; y: number; fontSize: number; color?: string; // hex, default: "#ffffff" fontFamily?: string; bold?: boolean; italic?: boolean; interactive?: boolean; // Panel only}
interface RectOptions { x: number; y: number; width: number; height: number; fill?: string; // hex, default: "#ffffff" cornerRadius?: number; borderColor?: string; // hex borderWidth?: number; interactive?: boolean;}
interface ImageOptions { path: string; // local file path x: number; y: number; width: number; height: number; interactive?: boolean;}
interface MenuItemOptions { id: number; label: string; enabled?: boolean; // default: true}
interface SourceRegionOptions { x: number; y: number; width: number; height: number;}
interface WinPaneEvent { eventType: string; // "element_clicked", "element_hovered", etc. surfaceId?: number; key?: string; button?: string; // "left", "right", "middle" itemId?: number; x?: number; // present in "surface_moved" events y?: number;}Colors
Section titled “Colors”Hex strings with optional # prefix:
"#f00" // shorthand, alpha 255"#ff0000" // full hex, alpha 255"#ff000080" // with alpha (00 = transparent, ff = opaque)"ff0000" // # prefix is optionalInteractive panels with events
Section titled “Interactive panels with events”const panel = wp.createPanel({ width: 260, height: 120, monitor: 0, anchor: 'top_left', margin: 40, draggable: true, dragHeight: 30, positionKey: 'my_panel',});
wp.setRect(panel, "bg", { x: 0, y: 0, width: 260, height: 120, fill: "#191923e6", cornerRadius: 8,});
wp.setRect(panel, "btn", { x: 20, y: 50, width: 220, height: 40, fill: "#32508cc8", cornerRadius: 6, interactive: true,});
wp.setText(panel, "btn_label", { text: "Click Me", x: 90, y: 60, fontSize: 14,});
wp.show(panel);
// Event loopsetInterval(() => { let event: WinPaneEvent | null; while ((event = wp.pollEvent()) !== null) { switch (event.eventType) { case "element_clicked": if (event.key === "btn") { console.log("Button clicked"); } break; case "element_hovered": if (event.key === "btn") { wp.setRect(panel, "btn", { x: 20, y: 50, width: 220, height: 40, fill: "#4664aadc", cornerRadius: 6, interactive: true, }); } break; case "element_left": if (event.key === "btn") { wp.setRect(panel, "btn", { x: 20, y: 50, width: 220, height: 40, fill: "#32508cc8", cornerRadius: 6, interactive: true, }); } break; } }}, 16);Tray with popup
Section titled “Tray with popup”const tray = wp.createTray({ tooltip: "My App", iconPath: "icon.png" });
const popup = wp.createPanel({ width: 200, height: 100 });wp.setRect(popup, "bg", { x: 0, y: 0, width: 200, height: 100, fill: "#1e1e28f0", cornerRadius: 8,});wp.setText(popup, "msg", { text: "Hello from tray", x: 16, y: 16, fontSize: 14,});
wp.setPopup(tray, popup);wp.setMenu(tray, [ { id: 1, label: "Settings" }, { id: 99, label: "Quit" },]);
setInterval(() => { let event; while ((event = wp.pollEvent()) !== null) { if (event.eventType === "tray_menu_item_clicked" && event.itemId === 99) { wp.close(); process.exit(0); } }}, 16);Backdrop effects
Section titled “Backdrop effects”if (wp.backdropSupported()) { wp.setBackdrop(hud, "mica");}Use semi-transparent fills (low alpha) on background rects so the Mica/Acrylic effect shows through.
Electron
Section titled “Electron”The native addon works in Electron’s main process. Create overlays for your app from the main process, not the renderer.
// main.ts (Electron main process)import { WinPane } from "winpane";import { app, BrowserWindow } from "electron";
app.whenReady().then(() => { const win = new BrowserWindow({ width: 800, height: 600 });
const wp = new WinPane(); const companion = wp.createPanel({ width: 200, height: 80, positionKey: 'electron_companion' }); wp.setRect(companion, "bg", { x: 0, y: 0, width: 200, height: 80, fill: "#14141ec8", cornerRadius: 8, }); wp.setText(companion, "label", { text: "Companion panel", x: 12, y: 12, fontSize: 13, });
// Anchor to the Electron window const hwnd = win.getNativeWindowHandle().readInt32LE(0); wp.anchorTo(companion, hwnd, "top_right", 8, 0); wp.show(companion);});Alternative: JSON-RPC host
Section titled “Alternative: JSON-RPC host”For Deno, Bun without native addon support, or any JS runtime that cannot load napi modules, spawn winpane-host as a subprocess:
import { spawn } from "child_process";import { createInterface } from "readline";
const proc = spawn("winpane-host", [], { stdio: ["pipe", "pipe", "inherit"],});
const rl = createInterface({ input: proc.stdout! });
let nextId = 1;const pending = new Map<number, (result: any) => void>();
rl.on("line", (line) => { const msg = JSON.parse(line); if ("id" in msg && pending.has(msg.id)) { pending.get(msg.id)!(msg); pending.delete(msg.id); } else if (msg.method === "event") { console.log("Event:", msg.params); }});
function rpc(method: string, params: Record<string, any>): Promise<any> { const id = nextId++; return new Promise((resolve) => { pending.set(id, resolve); proc.stdin!.write(JSON.stringify({ jsonrpc: "2.0", method, params, id }) + "\n"); });}
// Usageconst { result } = await rpc("create_hud", { x: 100, y: 100, width: 300, height: 100 });await rpc("set_text", { surface_id: result.surface_id, key: "msg", text: "Hello", x: 16, y: 16, font_size: 18,});await rpc("show", { surface_id: result.surface_id });See the protocol reference for the full method list.
Next steps
Section titled “Next steps”- Node.js guide - Same API, more detail on each method
- Cookbook - 10 recipes with Node.js equivalents
- Design overview - Architecture and internals
- Limitations - Known constraints