plusui-native-core 0.1.102 → 0.1.104

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.
@@ -1,189 +1,218 @@
1
- #include <iostream>
2
- #include <plusui/keyboard.hpp>
3
- #include <unordered_map>
4
-
5
- namespace plusui {
6
-
7
- struct Keyboard::Impl {
8
- std::unordered_map<int, bool> pressedKeys;
9
- std::vector<std::function<void(const KeyEvent &)>> keyDownCallbacks;
10
- std::vector<std::function<void(const KeyEvent &)>> keyUpCallbacks;
11
- bool autoRepeat = true;
12
-
13
- static const std::unordered_map<KeyCode, std::string> &keyNames() {
14
- static std::unordered_map<KeyCode, std::string> names = {
15
- {KeyCode::Space, "Space"},
16
- {KeyCode::Escape, "Escape"},
17
- {KeyCode::Enter, "Enter"},
18
- {KeyCode::Tab, "Tab"},
19
- {KeyCode::Backspace, "Backspace"},
20
- {KeyCode::Delete, "Delete"},
21
- {KeyCode::Right, "Right"},
22
- {KeyCode::Left, "Left"},
23
- {KeyCode::Down, "Down"},
24
- {KeyCode::Up, "Up"},
25
- {KeyCode::A, "A"},
26
- {KeyCode::B, "B"},
27
- {KeyCode::C, "C"},
28
- {KeyCode::D, "D"},
29
- {KeyCode::E, "E"},
30
- {KeyCode::F, "F"},
31
- {KeyCode::G, "G"},
32
- {KeyCode::H, "H"},
33
- {KeyCode::I, "I"},
34
- {KeyCode::J, "J"},
35
- {KeyCode::K, "K"},
36
- {KeyCode::L, "L"},
37
- {KeyCode::M, "M"},
38
- {KeyCode::N, "N"},
39
- {KeyCode::O, "O"},
40
- {KeyCode::P, "P"},
41
- {KeyCode::Q, "Q"},
42
- {KeyCode::R, "R"},
43
- {KeyCode::S, "S"},
44
- {KeyCode::T, "T"},
45
- {KeyCode::U, "U"},
46
- {KeyCode::V, "V"},
47
- {KeyCode::W, "W"},
48
- {KeyCode::X, "X"},
49
- {KeyCode::Y, "Y"},
50
- {KeyCode::Z, "Z"},
51
- {KeyCode::F1, "F1"},
52
- {KeyCode::F2, "F2"},
53
- {KeyCode::F3, "F3"},
54
- {KeyCode::F4, "F4"},
55
- {KeyCode::F5, "F5"},
56
- {KeyCode::F6, "F6"},
57
- {KeyCode::F7, "F7"},
58
- {KeyCode::F8, "F8"},
59
- {KeyCode::F9, "F9"},
60
- {KeyCode::F10, "F10"},
61
- {KeyCode::F11, "F11"},
62
- {KeyCode::F12, "F12"},
63
- };
64
- return names;
65
- }
66
- };
67
-
68
- Keyboard::Keyboard() : pImpl(std::make_unique<Impl>()) {}
69
- Keyboard::~Keyboard() = default;
70
-
71
- Keyboard &Keyboard::instance() {
72
- static Keyboard instance;
73
- return instance;
74
- }
75
-
76
- void Keyboard::onKeyDown(std::function<void(const KeyEvent &)> callback) {
77
- pImpl->keyDownCallbacks.push_back(callback);
78
- }
79
-
80
- void Keyboard::onKeyUp(std::function<void(const KeyEvent &)> callback) {
81
- pImpl->keyUpCallbacks.push_back(callback);
82
- }
83
-
84
- bool Keyboard::isKeyPressed(KeyCode key) const {
85
- return pImpl->pressedKeys.count(static_cast<int>(key)) &&
86
- pImpl->pressedKeys.at(static_cast<int>(key));
87
- }
88
-
89
- bool Keyboard::areModsActive(KeyMod mods) const {
90
- (void)mods; // Suppress unused warning - placeholder implementation
91
- return false;
92
- }
93
-
94
- std::string Keyboard::getKeyName(KeyCode key) const {
95
- auto it = Impl::keyNames().find(key);
96
- return it != Impl::keyNames().end() ? it->second : "Unknown";
97
- }
98
-
99
- KeyCode Keyboard::getKeyFromName(const std::string &name) const {
100
- for (const auto &[key, keyName] : Impl::keyNames()) {
101
- if (keyName == name)
102
- return key;
103
- }
104
- return KeyCode::Unknown;
105
- }
106
-
107
- void Keyboard::setAutoRepeat(bool enabled) { pImpl->autoRepeat = enabled; }
108
-
109
- bool Keyboard::getAutoRepeat() const { return pImpl->autoRepeat; }
110
-
111
- struct ShortcutManager::Impl {
112
- std::map<std::string, std::pair<Shortcut, std::function<void()>>> shortcuts;
113
- std::function<void(const std::string &id)> triggerCallback;
114
- };
115
-
116
- ShortcutManager::ShortcutManager() : pImpl(std::make_unique<Impl>()) {}
117
- ShortcutManager::~ShortcutManager() = default;
118
-
119
- ShortcutManager &ShortcutManager::instance() {
120
- static ShortcutManager instance;
121
- return instance;
122
- }
123
-
124
- bool ShortcutManager::registerShortcut(const std::string &id,
125
- const Shortcut &shortcut,
126
- std::function<void()> callback) {
127
- pImpl->shortcuts[id] = {shortcut, callback};
128
- return true;
129
- }
130
-
131
- bool ShortcutManager::unregisterShortcut(const std::string &id) {
132
- return pImpl->shortcuts.erase(id) > 0;
133
- }
134
-
135
- void ShortcutManager::clearShortcuts() { pImpl->shortcuts.clear(); }
136
-
137
- bool ShortcutManager::isRegistered(const std::string &id) const {
138
- return pImpl->shortcuts.count(id) > 0;
139
- }
140
-
141
- Shortcut ShortcutManager::getShortcut(const std::string &id) const {
142
- auto it = pImpl->shortcuts.find(id);
143
- return it != pImpl->shortcuts.end() ? it->second.first : Shortcut();
144
- }
145
-
146
- void ShortcutManager::onShortcutTriggered(
147
- std::function<void(const std::string &id)> callback) {
148
- pImpl->triggerCallback = callback;
149
- }
150
-
151
- std::string Shortcut::toString() const {
152
- std::string s;
153
- if (mods & KeyMod::Control)
154
- s += "Ctrl+";
155
- if (mods & KeyMod::Alt)
156
- s += "Alt+";
157
- if (mods & KeyMod::Shift)
158
- s += "Shift+";
159
- if (mods & KeyMod::Super)
160
- s += "Super+";
161
- return s + std::to_string(static_cast<int>(key));
162
- }
163
-
164
- Shortcut Shortcut::parse(const std::string &str) {
165
- Shortcut shortcut;
166
- KeyMod mods = KeyMod::None;
167
- std::string key;
168
-
169
- for (size_t i = 0; i < str.size(); ++i) {
170
- if (str[i] == '+') {
171
- if (key == "Ctrl" || key == "Control")
172
- mods = mods | KeyMod::Control;
173
- else if (key == "Alt")
174
- mods = mods | KeyMod::Alt;
175
- else if (key == "Shift")
176
- mods = mods | KeyMod::Shift;
177
- else if (key == "Super" || key == "Win")
178
- mods = mods | KeyMod::Super;
179
- key.clear();
180
- } else {
181
- key += str[i];
182
- }
183
- }
184
-
185
- shortcut.mods = mods;
186
- return shortcut;
187
- }
188
-
189
- } // namespace plusui
1
+ /**
2
+ * keyboard.cpp — Platform-unified keyboard implementation
3
+ *
4
+ * Platform-specific code lives in:
5
+ * keyboard_windows.cpp — Windows (Win32 / WebView2)
6
+ * keyboard_macos.cpp — macOS (Carbon / CGEvent)
7
+ * keyboard_linux.cpp — Linux (X11 / XGrabKey)
8
+ *
9
+ * Each platform file defines (inside namespace plusui):
10
+ * static bool areModsActiveImpl(int flags)
11
+ * static bool isKeyPressedImpl(int keyCode)
12
+ * static bool registerGlobalShortcutImpl(id, keyCode, mods, callback) [macOS/Linux]
13
+ * static bool unregisterGlobalShortcutImpl(id) [macOS/Linux]
14
+ * static void clearGlobalShortcutsImpl() [macOS/Linux]
15
+ *
16
+ * Windows global shortcuts are handled in window.cpp/webview.cpp via
17
+ * RegisterHotKey/WM_HOTKEY (requires the HWND which lives on Window::Impl).
18
+ */
19
+
20
+ #include <iostream>
21
+ #include <plusui/keyboard.hpp>
22
+ #include <unordered_map>
23
+
24
+ // Pull in platform-specific helpers
25
+ #ifdef _WIN32
26
+ # include "keyboard_windows.cpp"
27
+ #elif defined(__APPLE__)
28
+ # include "keyboard_macos.cpp"
29
+ #else
30
+ # include "keyboard_linux.cpp"
31
+ #endif
32
+
33
+ namespace plusui {
34
+
35
+ // ── Keyboard::Impl ────────────────────────────────────────────────────────────
36
+
37
+ struct Keyboard::Impl {
38
+ std::unordered_map<int, bool> pressedKeys;
39
+ std::vector<std::function<void(const KeyEvent&)>> keyDownCallbacks;
40
+ std::vector<std::function<void(const KeyEvent&)>> keyUpCallbacks;
41
+ bool autoRepeat = true;
42
+
43
+ static const std::unordered_map<KeyCode, std::string>& keyNames() {
44
+ static std::unordered_map<KeyCode, std::string> names = {
45
+ {KeyCode::Space, "Space"},
46
+ {KeyCode::Escape, "Escape"},
47
+ {KeyCode::Enter, "Enter"},
48
+ {KeyCode::Tab, "Tab"},
49
+ {KeyCode::Backspace, "Backspace"},
50
+ {KeyCode::Delete, "Delete"},
51
+ {KeyCode::Right, "Right"},
52
+ {KeyCode::Left, "Left"},
53
+ {KeyCode::Down, "Down"},
54
+ {KeyCode::Up, "Up"},
55
+ {KeyCode::A, "A"}, {KeyCode::B, "B"}, {KeyCode::C, "C"},
56
+ {KeyCode::D, "D"}, {KeyCode::E, "E"}, {KeyCode::F, "F"},
57
+ {KeyCode::G, "G"}, {KeyCode::H, "H"}, {KeyCode::I, "I"},
58
+ {KeyCode::J, "J"}, {KeyCode::K, "K"}, {KeyCode::L, "L"},
59
+ {KeyCode::M, "M"}, {KeyCode::N, "N"}, {KeyCode::O, "O"},
60
+ {KeyCode::P, "P"}, {KeyCode::Q, "Q"}, {KeyCode::R, "R"},
61
+ {KeyCode::S, "S"}, {KeyCode::T, "T"}, {KeyCode::U, "U"},
62
+ {KeyCode::V, "V"}, {KeyCode::W, "W"}, {KeyCode::X, "X"},
63
+ {KeyCode::Y, "Y"}, {KeyCode::Z, "Z"},
64
+ {KeyCode::F1, "F1"}, {KeyCode::F2, "F2"}, {KeyCode::F3, "F3"},
65
+ {KeyCode::F4, "F4"}, {KeyCode::F5, "F5"}, {KeyCode::F6, "F6"},
66
+ {KeyCode::F7, "F7"}, {KeyCode::F8, "F8"}, {KeyCode::F9, "F9"},
67
+ {KeyCode::F10, "F10"}, {KeyCode::F11, "F11"}, {KeyCode::F12, "F12"},
68
+ };
69
+ return names;
70
+ }
71
+ };
72
+
73
+ // ── Keyboard ──────────────────────────────────────────────────────────────────
74
+
75
+ Keyboard::Keyboard() : pImpl(std::make_unique<Impl>()) {}
76
+ Keyboard::~Keyboard() = default;
77
+
78
+ Keyboard& Keyboard::instance() {
79
+ static Keyboard instance;
80
+ return instance;
81
+ }
82
+
83
+ void Keyboard::onKeyDown(std::function<void(const KeyEvent&)> callback) {
84
+ pImpl->keyDownCallbacks.push_back(callback);
85
+ }
86
+
87
+ void Keyboard::onKeyUp(std::function<void(const KeyEvent&)> callback) {
88
+ pImpl->keyUpCallbacks.push_back(callback);
89
+ }
90
+
91
+ bool Keyboard::isKeyPressed(KeyCode key) const {
92
+ // Prefer live query via platform helper over the cached map
93
+ return isKeyPressedImpl(static_cast<int>(key));
94
+ }
95
+
96
+ bool Keyboard::areModsActive(KeyMod mods) const {
97
+ return areModsActiveImpl(static_cast<int>(mods));
98
+ }
99
+
100
+ std::string Keyboard::getKeyName(KeyCode key) const {
101
+ auto it = Impl::keyNames().find(key);
102
+ return it != Impl::keyNames().end() ? it->second : "Unknown";
103
+ }
104
+
105
+ KeyCode Keyboard::getKeyFromName(const std::string& name) const {
106
+ for (const auto& [key, keyName] : Impl::keyNames()) {
107
+ if (keyName == name) return key;
108
+ }
109
+ return KeyCode::Unknown;
110
+ }
111
+
112
+ void Keyboard::setAutoRepeat(bool enabled) { pImpl->autoRepeat = enabled; }
113
+ bool Keyboard::getAutoRepeat() const { return pImpl->autoRepeat; }
114
+
115
+ // ── ShortcutManager ───────────────────────────────────────────────────────────
116
+
117
+ struct ShortcutManager::Impl {
118
+ std::map<std::string, std::pair<Shortcut, std::function<void()>>> shortcuts;
119
+ std::function<void(const std::string&)> triggerCallback;
120
+ // callback to execute JS in the webview when a global shortcut fires
121
+ std::function<void(const std::string&)> shortcutFiredFn;
122
+ };
123
+
124
+ ShortcutManager::ShortcutManager() : pImpl(std::make_unique<Impl>()) {}
125
+ ShortcutManager::~ShortcutManager() = default;
126
+
127
+ ShortcutManager& ShortcutManager::instance() {
128
+ static ShortcutManager instance;
129
+ return instance;
130
+ }
131
+
132
+ bool ShortcutManager::registerShortcut(const std::string& id,
133
+ const Shortcut& shortcut,
134
+ std::function<void()> callback) {
135
+ pImpl->shortcuts[id] = {shortcut, callback};
136
+
137
+ #if defined(__APPLE__) || (!defined(_WIN32) && !defined(__APPLE__))
138
+ // On non-Windows platforms, register the hotkey natively here.
139
+ // The callback fires the JS shortcut event via shortcutFiredFn.
140
+ auto fired = pImpl->shortcutFiredFn;
141
+ return registerGlobalShortcutImpl(
142
+ id,
143
+ static_cast<int>(shortcut.key),
144
+ static_cast<int>(shortcut.mods),
145
+ [id, callback, fired]() {
146
+ if (callback) callback();
147
+ if (fired) fired(id);
148
+ });
149
+ #else
150
+ // Windows: hotkey registration is done in webview.cpp via RegisterHotKey
151
+ // (requires the HWND which is only available there). ShortcutManager
152
+ // just stores the callback so it can be invoked from the JS bridge.
153
+ return true;
154
+ #endif
155
+ }
156
+
157
+ bool ShortcutManager::unregisterShortcut(const std::string& id) {
158
+ bool removed = pImpl->shortcuts.erase(id) > 0;
159
+ #if defined(__APPLE__) || (!defined(_WIN32) && !defined(__APPLE__))
160
+ unregisterGlobalShortcutImpl(id);
161
+ #endif
162
+ return removed;
163
+ }
164
+
165
+ void ShortcutManager::clearShortcuts() {
166
+ pImpl->shortcuts.clear();
167
+ #if defined(__APPLE__) || (!defined(_WIN32) && !defined(__APPLE__))
168
+ clearGlobalShortcutsImpl();
169
+ #endif
170
+ }
171
+
172
+ bool ShortcutManager::isRegistered(const std::string& id) const {
173
+ return pImpl->shortcuts.count(id) > 0;
174
+ }
175
+
176
+ Shortcut ShortcutManager::getShortcut(const std::string& id) const {
177
+ auto it = pImpl->shortcuts.find(id);
178
+ return it != pImpl->shortcuts.end() ? it->second.first : Shortcut();
179
+ }
180
+
181
+ void ShortcutManager::onShortcutTriggered(
182
+ std::function<void(const std::string&)> callback) {
183
+ pImpl->triggerCallback = callback;
184
+ }
185
+
186
+ // ── Shortcut helpers ──────────────────────────────────────────────────────────
187
+
188
+ std::string Shortcut::toString() const {
189
+ std::string s;
190
+ if (mods & KeyMod::Control) s += "Ctrl+";
191
+ if (mods & KeyMod::Alt) s += "Alt+";
192
+ if (mods & KeyMod::Shift) s += "Shift+";
193
+ if (mods & KeyMod::Super) s += "Super+";
194
+ return s + std::to_string(static_cast<int>(key));
195
+ }
196
+
197
+ Shortcut Shortcut::parse(const std::string& str) {
198
+ Shortcut shortcut;
199
+ KeyMod mods = KeyMod::None;
200
+ std::string key;
201
+
202
+ for (size_t i = 0; i < str.size(); ++i) {
203
+ if (str[i] == '+') {
204
+ if (key == "Ctrl" || key == "Control") mods = mods | KeyMod::Control;
205
+ else if (key == "Alt") mods = mods | KeyMod::Alt;
206
+ else if (key == "Shift") mods = mods | KeyMod::Shift;
207
+ else if (key == "Super" || key == "Win") mods = mods | KeyMod::Super;
208
+ key.clear();
209
+ } else {
210
+ key += str[i];
211
+ }
212
+ }
213
+
214
+ shortcut.mods = mods;
215
+ return shortcut;
216
+ }
217
+
218
+ } // namespace plusui
@@ -14,6 +14,15 @@
14
14
  export enum KeyCode {
15
15
  Unknown = 0,
16
16
  Space = 32,
17
+ // Digits 0–9
18
+ D0 = 48, D1 = 49, D2 = 50, D3 = 51, D4 = 52,
19
+ D5 = 53, D6 = 54, D7 = 55, D8 = 56, D9 = 57,
20
+ // Letters A–Z (GLFW convention: same as ASCII upper-case)
21
+ A = 65, B = 66, C = 67, D = 68, E = 69, F = 70,
22
+ G = 71, H = 72, I = 73, J = 74, K = 75, L = 76,
23
+ M = 77, N = 78, O = 79, P = 80, Q = 81, R = 82,
24
+ S = 83, T = 84, U = 85, V = 86, W = 87, X = 88,
25
+ Y = 89, Z = 90,
17
26
  Escape = 256,
18
27
  Enter = 257,
19
28
  Tab = 258,
@@ -65,40 +74,79 @@ export interface Shortcut {
65
74
  export type KeyEventCallback = (event: KeyEvent) => void;
66
75
  export type ShortcutCallback = () => void;
67
76
 
68
- let _invoke: ((name: string, args?: unknown[]) => Promise<unknown>) | null = null;
69
- let _event: ((event: string, callback: (...args: unknown[]) => void) => () => void) | null = null;
70
- const shortcutHandlers: Map<string, ShortcutCallback> = new Map();
77
+ // ── Bridge (matches window.ts exactly) ───────────────────────────────────────
71
78
 
72
- export function setInvokeFn(fn: (name: string, args?: unknown[]) => Promise<unknown>) {
73
- _invoke = fn;
74
- }
79
+ let callId = 0;
80
+ const pending = new Map<string, { resolve: (v: any) => void; reject: (e: any) => void }>();
75
81
 
76
- export function setEventFn(fn: (event: string, callback: (...args: unknown[]) => void) => () => void) {
77
- _event = fn;
82
+ function getGlobal(): any {
83
+ if (typeof window !== 'undefined') return window;
84
+ if (typeof globalThis !== 'undefined') return globalThis as any;
85
+ return {} as any;
78
86
  }
79
87
 
80
- async function invoke<T = unknown>(name: string, args?: unknown[]): Promise<T> {
81
- if (!_invoke) {
82
- if (typeof window !== 'undefined' && (window as any).__invoke__) {
83
- _invoke = (window as any).__invoke__;
84
- } else {
85
- throw new Error('Keyboard API not initialized');
88
+ const g = getGlobal();
89
+
90
+ // Merge __response__ handler multiple modules may define it; guard so we
91
+ // don't overwrite a version already installed by window.ts.
92
+ if (!g.__response__) {
93
+ g.__response__ = function(id: string, result: any, error?: any) {
94
+ const p = pending.get(id);
95
+ if (p) {
96
+ pending.delete(id);
97
+ error ? p.reject(new Error(error)) : p.resolve(result);
98
+ }
99
+ };
100
+ } else {
101
+ // Wrap the existing handler so both pending maps are served.
102
+ const existing = g.__response__;
103
+ g.__response__ = function(id: string, result: any, error?: any) {
104
+ existing(id, result, error);
105
+ const p = pending.get(id);
106
+ if (p) {
107
+ pending.delete(id);
108
+ error ? p.reject(new Error(error)) : p.resolve(result);
86
109
  }
87
- }
88
- return _invoke!(name, args) as Promise<T>;
110
+ };
89
111
  }
90
112
 
91
- function eventHandler(event: string, callback: (...args: unknown[]) => void): () => void {
92
- if (!_event) {
93
- if (typeof window !== 'undefined' && (window as any).__on__) {
94
- _event = (window as any).__on__;
113
+ async function invoke<T>(method: string, params: any[] = []): Promise<T> {
114
+ return new Promise((resolve, reject) => {
115
+ const id = String(++callId) + '-kb';
116
+ pending.set(id, { resolve, reject });
117
+
118
+ const payload = JSON.stringify({ id, method, params });
119
+
120
+ if (g.__native_invoke__) {
121
+ g.__native_invoke__(payload);
122
+ } else if (g.chrome && g.chrome.webview && g.chrome.webview.postMessage) {
123
+ g.chrome.webview.postMessage(payload);
95
124
  } else {
96
- return () => {};
125
+ pending.delete(id);
126
+ console.warn('[PlusUI] ' + method + ' - native bridge not ready');
127
+ resolve(null as T);
97
128
  }
98
- }
99
- return _event!(event, callback);
129
+ });
100
130
  }
101
131
 
132
+ // ── onKeyDown / onKeyUp / onShortcut ─────────────────────────────────────────
133
+ // C++ fires these as CustomEvents on `window` via ExecuteScript:
134
+ // plusui:keyboard:keydown { detail: KeyEvent }
135
+ // plusui:keyboard:keyup { detail: KeyEvent }
136
+ // plusui:keyboard:shortcut { detail: { id: string } }
137
+
138
+ function onDomEvent<T>(
139
+ eventName: string,
140
+ callback: (data: T) => void
141
+ ): () => void {
142
+ if (typeof window === 'undefined') return () => {};
143
+ const handler = (e: Event) => callback((e as CustomEvent<T>).detail);
144
+ window.addEventListener(eventName, handler as EventListener);
145
+ return () => window.removeEventListener(eventName, handler as EventListener);
146
+ }
147
+
148
+ // ── Public API ────────────────────────────────────────────────────────────────
149
+
102
150
  export const keyboard = {
103
151
  async isKeyPressed(key: KeyCode): Promise<boolean> {
104
152
  return invoke<boolean>('keyboard.isKeyPressed', [key]);
@@ -113,30 +161,40 @@ export const keyboard = {
113
161
  },
114
162
 
115
163
  async registerShortcut(id: string, shortcut: Shortcut, callback: ShortcutCallback): Promise<boolean> {
116
- shortcutHandlers.set(id, callback);
164
+ // Store the callback under the global handler table that WM_HOTKEY fires.
165
+ if (typeof window !== 'undefined') {
166
+ const w = window as any;
167
+ if (!w.__plusui_shortcut_handlers__) w.__plusui_shortcut_handlers__ = {};
168
+ w.__plusui_shortcut_handlers__[id] = callback;
169
+ }
117
170
  return invoke<boolean>('keyboard.registerShortcut', [id, shortcut]);
118
171
  },
119
172
 
120
173
  async unregisterShortcut(id: string): Promise<boolean> {
121
- shortcutHandlers.delete(id);
174
+ if (typeof window !== 'undefined') {
175
+ const w = window as any;
176
+ if (w.__plusui_shortcut_handlers__) delete w.__plusui_shortcut_handlers__[id];
177
+ }
122
178
  return invoke<boolean>('keyboard.unregisterShortcut', [id]);
123
179
  },
124
180
 
125
181
  async clearShortcuts(): Promise<void> {
126
- shortcutHandlers.clear();
182
+ if (typeof window !== 'undefined') {
183
+ (window as any).__plusui_shortcut_handlers__ = {};
184
+ }
127
185
  await invoke('keyboard.clearShortcuts');
128
186
  },
129
187
 
130
188
  onKeyDown(callback: KeyEventCallback): () => void {
131
- return eventHandler('keyboard:keydown', callback as (...args: unknown[]) => void);
189
+ return onDomEvent<KeyEvent>('plusui:keyboard:keydown', callback);
132
190
  },
133
191
 
134
192
  onKeyUp(callback: KeyEventCallback): () => void {
135
- return eventHandler('keyboard:keyup', callback as (...args: unknown[]) => void);
193
+ return onDomEvent<KeyEvent>('plusui:keyboard:keyup', callback);
136
194
  },
137
195
 
138
196
  onShortcut(callback: (id: string) => void): () => void {
139
- return eventHandler('keyboard:shortcut', callback as (...args: unknown[]) => void);
197
+ return onDomEvent<{ id: string }>('plusui:keyboard:shortcut', (d) => callback(d.id));
140
198
  },
141
199
 
142
200
  parseShortcut(str: string): Shortcut {
@@ -155,7 +213,7 @@ export const keyboard = {
155
213
  } else if (trimmed === 'super' || trimmed === 'win' || trimmed === 'cmd') {
156
214
  mods |= KeyMod.Super;
157
215
  } else {
158
- key = this.keyNameToCode(trimmed);
216
+ key = keyboard.keyNameToCode(trimmed);
159
217
  }
160
218
  }
161
219
 
@@ -166,10 +224,13 @@ export const keyboard = {
166
224
  const map: Record<string, KeyCode> = {
167
225
  'space': KeyCode.Space,
168
226
  'escape': KeyCode.Escape,
227
+ 'esc': KeyCode.Escape,
169
228
  'enter': KeyCode.Enter,
229
+ 'return': KeyCode.Enter,
170
230
  'tab': KeyCode.Tab,
171
231
  'backspace': KeyCode.Backspace,
172
232
  'delete': KeyCode.Delete,
233
+ 'del': KeyCode.Delete,
173
234
  'right': KeyCode.Right,
174
235
  'left': KeyCode.Left,
175
236
  'down': KeyCode.Down,
@@ -186,8 +247,16 @@ export const keyboard = {
186
247
  'f10': KeyCode.F10,
187
248
  'f11': KeyCode.F11,
188
249
  'f12': KeyCode.F12,
250
+ // A–Z single letters (GLFW convention: A=65 … Z=90)
251
+ 'a': KeyCode.A, 'b': KeyCode.B, 'c': KeyCode.C, 'd': KeyCode.D,
252
+ 'e': KeyCode.E, 'f': KeyCode.F, 'g': KeyCode.G, 'h': KeyCode.H,
253
+ 'i': KeyCode.I, 'j': KeyCode.J, 'k': KeyCode.K, 'l': KeyCode.L,
254
+ 'm': KeyCode.M, 'n': KeyCode.N, 'o': KeyCode.O, 'p': KeyCode.P,
255
+ 'q': KeyCode.Q, 'r': KeyCode.R, 's': KeyCode.S, 't': KeyCode.T,
256
+ 'u': KeyCode.U, 'v': KeyCode.V, 'w': KeyCode.W, 'x': KeyCode.X,
257
+ 'y': KeyCode.Y, 'z': KeyCode.Z,
189
258
  };
190
- return map[name] || KeyCode.Unknown;
259
+ return map[name] ?? KeyCode.Unknown;
191
260
  },
192
261
  };
193
262