refactor: better shortcut hook

This commit is contained in:
Max Koon
2025-12-05 17:05:23 -05:00
parent 2df7f2d924
commit 76f2a43bd0
21 changed files with 481 additions and 143 deletions

View 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>
);
}

View 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;
}

View 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;
}

View 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);
};
}, []);
};

View File

@@ -0,0 +1,3 @@
export * from "./Debug";
export * from "./Provider";
export * from "./store";

View 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;
},
};