refactor: better shortcut hook
This commit is contained in:
30
packages/ui/lib/shortcuts/Debug.tsx
Normal file
30
packages/ui/lib/shortcuts/Debug.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { keysStore } from "./store";
|
||||
|
||||
export function ShortcutDebug() {
|
||||
const entries = useSyncExternalStore(
|
||||
keysStore.subscribe,
|
||||
keysStore.getSnapshot,
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 100,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
backgroundColor: "black",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "red", fontFamily: "mono" }}>
|
||||
{entries
|
||||
.values()
|
||||
.map(([key, _]) => key)
|
||||
.toArray()
|
||||
.join(",")}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
12
packages/ui/lib/shortcuts/Provider.tsx
Normal file
12
packages/ui/lib/shortcuts/Provider.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useKeyboard } from "@opentui/react";
|
||||
import { keysStore } from "./store";
|
||||
|
||||
export function ShortcutProvider({ children }: { children: ReactNode }) {
|
||||
useKeyboard((e) => {
|
||||
const fn = keysStore.getHandler(e.name);
|
||||
fn?.();
|
||||
});
|
||||
|
||||
return children;
|
||||
}
|
||||
13
packages/ui/lib/shortcuts/Provider.web.tsx
Normal file
13
packages/ui/lib/shortcuts/Provider.web.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { keysStore } from "./store";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("keydown", (e) => {
|
||||
const fn = keysStore.getHandler(e.key);
|
||||
fn?.();
|
||||
});
|
||||
}
|
||||
|
||||
export function ShortcutProvider({ children }: { children: ReactNode }) {
|
||||
return children;
|
||||
}
|
||||
14
packages/ui/lib/shortcuts/hooks.ts
Normal file
14
packages/ui/lib/shortcuts/hooks.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { keysStore } from "./store";
|
||||
|
||||
export const useShortcut = (key: string, handler: () => void) => {
|
||||
const ref = useRef(handler);
|
||||
ref.current = handler;
|
||||
|
||||
useEffect(() => {
|
||||
keysStore.register(key, ref);
|
||||
return () => {
|
||||
keysStore.deregister(key);
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
3
packages/ui/lib/shortcuts/index.ts
Normal file
3
packages/ui/lib/shortcuts/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./Debug";
|
||||
export * from "./Provider";
|
||||
export * from "./store";
|
||||
40
packages/ui/lib/shortcuts/store.ts
Normal file
40
packages/ui/lib/shortcuts/store.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { type RefObject } from "react";
|
||||
|
||||
// internal map
|
||||
const keys = new Map<string, RefObject<() => void>>();
|
||||
|
||||
// cached snapshot (stable reference)
|
||||
let snapshot: [string, RefObject<() => void>][] = [];
|
||||
|
||||
let listeners = new Set<() => void>();
|
||||
|
||||
function emit() {
|
||||
// refresh snapshot ONLY when keys actually change
|
||||
snapshot = Array.from(keys.entries());
|
||||
for (const fn of listeners) fn();
|
||||
}
|
||||
|
||||
export const keysStore = {
|
||||
subscribe(fn: () => void) {
|
||||
listeners.add(fn);
|
||||
return () => listeners.delete(fn);
|
||||
},
|
||||
|
||||
getSnapshot() {
|
||||
return snapshot; // stable unless emit() ran
|
||||
},
|
||||
|
||||
register(key: string, ref: RefObject<() => void>) {
|
||||
keys.set(key, ref);
|
||||
emit();
|
||||
},
|
||||
|
||||
deregister(key: string) {
|
||||
keys.delete(key);
|
||||
emit();
|
||||
},
|
||||
|
||||
getHandler(key: string) {
|
||||
return keys.get(key)?.current;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user