summaryrefslogtreecommitdiff
path: root/apps/web/src/actions/use-keybindings.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/actions/use-keybindings.ts')
-rw-r--r--apps/web/src/actions/use-keybindings.ts87
1 files changed, 87 insertions, 0 deletions
diff --git a/apps/web/src/actions/use-keybindings.ts b/apps/web/src/actions/use-keybindings.ts
new file mode 100644
index 00000000..c129795c
--- /dev/null
+++ b/apps/web/src/actions/use-keybindings.ts
@@ -0,0 +1,87 @@
+import { useEffect } from "react";
+import { invokeAction } from "@/actions";
+import { useEditor } from "@/editor/use-editor";
+import { useKeybindingsStore } from "@/actions/keybindings-store";
+import { isTypableDOMElement } from "@/utils/browser";
+
+/**
+ * a composable that hooks to the caller component's
+ * lifecycle and hooks to the keyboard events to fire
+ * the appropriate actions based on keybindings
+ */
+export function useKeybindingsListener() {
+ const editor = useEditor();
+ const {
+ keybindings,
+ getKeybindingString,
+ overlayDepth,
+ isLoadingProject,
+ isRecording,
+ } = useKeybindingsStore();
+
+ useEffect(() => {
+ const eventOptions: AddEventListenerOptions = { capture: true };
+ const handleKeyDown = (ev: KeyboardEvent) => {
+ const normalizedKey = (ev.key ?? "").toLowerCase();
+
+ if (overlayDepth > 0 || isLoadingProject || isRecording) {
+ return;
+ }
+
+ const binding = getKeybindingString(ev);
+ const activeElement = document.activeElement;
+ const isTextInput =
+ activeElement instanceof HTMLElement &&
+ isTypableDOMElement({ element: activeElement });
+ const boundAction = binding ? keybindings[binding] : undefined;
+
+ if (normalizedKey === "escape" && isTextInput) {
+ activeElement.blur();
+ return;
+ }
+
+ if (!binding) return;
+ if (!boundAction) return;
+
+ if (isTextInput) return;
+ if (boundAction === "paste-copied") {
+ if (!editor.clipboard.hasEntry()) return;
+ ev.preventDefault();
+ invokeAction("paste-copied", undefined, "keypress");
+ return;
+ }
+
+ ev.preventDefault();
+
+ switch (boundAction) {
+ case "seek-forward":
+ invokeAction("seek-forward", { seconds: 1 }, "keypress");
+ break;
+ case "seek-backward":
+ invokeAction("seek-backward", { seconds: 1 }, "keypress");
+ break;
+ case "jump-forward":
+ invokeAction("jump-forward", { seconds: 5 }, "keypress");
+ break;
+ case "jump-backward":
+ invokeAction("jump-backward", { seconds: 5 }, "keypress");
+ break;
+ default:
+ invokeAction(boundAction, undefined, "keypress");
+ }
+ };
+
+ document.addEventListener("keydown", handleKeyDown, eventOptions);
+
+ return () => {
+ document.removeEventListener("keydown", handleKeyDown, eventOptions);
+ };
+ }, [
+ keybindings,
+ getKeybindingString,
+ overlayDepth,
+ isLoadingProject,
+ isRecording,
+ editor,
+ ]);
+}