@nikala-ui/hooks 0.11.0 → 0.12.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.
@@ -0,0 +1,140 @@
1
+ import { onMount, onCleanup } from "solid-js";
2
+ import { isTauriEnvironment } from "./create-tauri-window";
3
+
4
+ export interface CreateGlobalShortcutOptions {
5
+ /** Shortcut key combination, e.g. 'CommandOrControl+Shift+P' or 'Alt+Space'. */
6
+ shortcut: string | string[];
7
+ /** Handler invoked when global shortcut is triggered. */
8
+ onTrigger: (shortcut: string) => void;
9
+ /** Automatically register shortcut on mount. Defaults to true. */
10
+ autoRegister?: boolean;
11
+ }
12
+
13
+ export interface CreateGlobalShortcutReturn {
14
+ /** Register the configured global shortcut with Tauri / Browser. */
15
+ register: () => Promise<boolean>;
16
+ /** Unregister the shortcut. */
17
+ unregister: () => Promise<void>;
18
+ /** Indicates whether the shortcut is currently registered. */
19
+ isRegistered: () => boolean;
20
+ }
21
+
22
+ /**
23
+ * SolidJS reactive primitive for registering native OS global hotkeys via Tauri v2 global shortcut plugin,
24
+ * with automatic fallback to browser DOM keyboard listeners.
25
+ */
26
+ export function createGlobalShortcut(
27
+ options: CreateGlobalShortcutOptions
28
+ ): CreateGlobalShortcutReturn {
29
+ let registered = false;
30
+ const shortcuts = Array.isArray(options.shortcut) ? options.shortcut : [options.shortcut];
31
+
32
+ const getTauriShortcutPlugin = async () => {
33
+ if (typeof window === "undefined") return null;
34
+ try {
35
+ if ((window as any).__TAURI__?.globalShortcut) {
36
+ return (window as any).__TAURI__.globalShortcut;
37
+ }
38
+ // Variable prevents Vite dev server from static module resolution
39
+ const moduleName = "@tauri-apps/plugin-global-shortcut";
40
+ // @ts-ignore - Optional runtime dependency in Tauri apps
41
+ const plugin = await import(/* @vite-ignore */ moduleName).catch(() => null);
42
+ return plugin;
43
+ } catch {
44
+ return null;
45
+ }
46
+ };
47
+
48
+ const normalizeKey = (k: string) => {
49
+ const key = k.toLowerCase().trim();
50
+ if (key === " " || key === "space" || key === "spacebar") return "space";
51
+ if (key === "esc" || key === "escape") return "escape";
52
+ if (key === "return" || key === "enter") return "enter";
53
+ return key;
54
+ };
55
+
56
+ const handleBrowserKeydown = (e: KeyboardEvent) => {
57
+ const eventKey = normalizeKey(e.key);
58
+ const eventCode = normalizeKey(e.code.replace(/^Key|^Digit/, ""));
59
+
60
+ for (const keyCombo of shortcuts) {
61
+ const parts = keyCombo.toLowerCase().split("+");
62
+ const hasCtrlOrCmd = parts.includes("commandorcontrol") || parts.includes("ctrl") || parts.includes("cmd");
63
+ const hasShift = parts.includes("shift");
64
+ const hasAlt = parts.includes("alt");
65
+ const mainKey = parts.find((p) => !["commandorcontrol", "ctrl", "cmd", "shift", "alt"].includes(p));
66
+
67
+ const ctrlMatch = hasCtrlOrCmd ? e.ctrlKey || e.metaKey : !e.ctrlKey && !e.metaKey;
68
+ const shiftMatch = hasShift ? e.shiftKey : !e.shiftKey;
69
+ const altMatch = hasAlt ? e.altKey : !e.altKey;
70
+ const keyMatch = mainKey
71
+ ? eventKey === normalizeKey(mainKey) || eventCode === normalizeKey(mainKey)
72
+ : false;
73
+
74
+ if (ctrlMatch && shiftMatch && altMatch && keyMatch) {
75
+ e.preventDefault();
76
+ options.onTrigger(keyCombo);
77
+ break;
78
+ }
79
+ }
80
+ };
81
+
82
+ const register = async (): Promise<boolean> => {
83
+ if (typeof window === "undefined") return false;
84
+
85
+ if (isTauriEnvironment()) {
86
+ const plugin = await getTauriShortcutPlugin();
87
+ if (plugin?.register) {
88
+ try {
89
+ for (const s of shortcuts) {
90
+ await plugin.register(s, () => options.onTrigger(s));
91
+ }
92
+ registered = true;
93
+ return true;
94
+ } catch {
95
+ // Fallback to web listener
96
+ }
97
+ }
98
+ }
99
+
100
+ window.addEventListener("keydown", handleBrowserKeydown);
101
+ registered = true;
102
+ return true;
103
+ };
104
+
105
+ const unregister = async (): Promise<void> => {
106
+ if (typeof window === "undefined") return;
107
+
108
+ if (isTauriEnvironment()) {
109
+ const plugin = await getTauriShortcutPlugin();
110
+ if (plugin?.unregister) {
111
+ try {
112
+ for (const s of shortcuts) {
113
+ await plugin.unregister(s);
114
+ }
115
+ } catch {
116
+ // Handled
117
+ }
118
+ }
119
+ }
120
+
121
+ window.removeEventListener("keydown", handleBrowserKeydown);
122
+ registered = false;
123
+ };
124
+
125
+ onMount(() => {
126
+ if (options.autoRegister !== false) {
127
+ register();
128
+ }
129
+ });
130
+
131
+ onCleanup(() => {
132
+ unregister();
133
+ });
134
+
135
+ return {
136
+ register,
137
+ unregister,
138
+ isRegistered: () => registered,
139
+ };
140
+ }