-
Notifications
You must be signed in to change notification settings - Fork 0
Plugins
Emilio Romero edited this page Jun 3, 2026
·
13 revisions
Plugins allow you to extend Umbra with custom UI components or behavior.
export interface Plugin<T extends HTMLElement = HTMLElement> { // Optional: Element rendered by the plugin el?: T; // Required: Render the plugin UI render(): void | T; // Optional: Called when theme changes onThemeChange?: (theme: string) => void; // Optional: Called when Umbra is destroyed onDestroy?: () => void; }
-
Constructor - Plugin is instantiated with the Umbra instance as
host - render() - Called during initialization
- onThemeChange() - Called whenever the theme changes (including sync from other tabs)
-
onDestroy() - Called when
umbra.destroy()is invoked
The host parameter is your Umbra instance. You can call:
class MyPlugin implements Plugin { public static readonly pluginId = 'u-<something>'; // Required private host: any; constructor(host: any) { this.host = host; // Access: host.theme, host.toggleTheme(), host.getCurrentTheme() } render(): void | HTMLElement { throw new Error("Method not implemented."); } onThemeChange(theme: string): void { console.log('Theme changed to:', theme); } }
import type { Plugin } from '@emrocode/umbra'; interface KeyboardShortcutOptions { key?: string; ctrl?: boolean; shift?: boolean; target?: 'body' | 'input' | 'all'; cooldown?: number; } export class KeyboardShortcut implements Plugin { public static readonly pluginId = 'u-keyboard-shortcut'; private _host: any; private options: Required<KeyboardShortcutOptions>; private _lastTriggered: number = 0; constructor(host: any, options?: KeyboardShortcutOptions) { this._host = host; this.options = { key: options?.key ?? 'd', ctrl: options?.ctrl ?? false, shift: options?.shift ?? false, target: options?.target ?? 'body', cooldown: options?.cooldown ?? 300, }; } private handleKeyDown = (e: KeyboardEvent) => { if (this.options.target === 'body' && this.isTyping(e)) return; if (this.options.target === 'input' && !this.isTyping(e)) return; if (this.matches(e)) { e.preventDefault(); const now = Date.now(); if (now - this._lastTriggered < this.options.cooldown) return; this._lastTriggered = now; this._host.toggleTheme(); } }; render(): void { document.addEventListener('keydown', this.handleKeyDown); } onDestroy(): void { document.removeEventListener('keydown', this.handleKeyDown); } private matches(e: KeyboardEvent): boolean { return ( e.key.toLowerCase() === this.options.key.toLowerCase() && (!this.options.ctrl || e.ctrlKey || e.metaKey) && (!this.options.shift || e.shiftKey) && (this.options.ctrl || (!e.ctrlKey && !e.metaKey && !e.altKey)) ); } private isTyping(e: KeyboardEvent): boolean { const target = e.target as HTMLElement; const tagName = target.tagName.toLowerCase(); const isEditable = target.isContentEditable || tagName === 'input' || tagName === 'textarea'; return isEditable; } }