UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

124 lines (123 loc) 4.22 kB
import { signal } from "@preact/signals-core"; import { GraphState } from "../../graph"; /** * KeyboardService * This is an internal service that manages keyboard state for the graph. * * It is used to detect keyboard state changes and to provide a reactive signal with the current keyboard state. * * @example * ```typescript * const keyboardService = new KeyboardService(graph); * keyboardService.$keyboardState.subscribe((state) => { * console.log("is shift key pressed", state.shiftKey); * console.log("is meta key pressed", state.metaKey); * console.log("is ctrl key pressed", state.ctrlKey); * console.log("is alt key pressed", state.altKey); * }); * ``` * @internal */ export class KeyboardService { constructor(graph) { this.graph = graph; this.$keyboardState = signal({ shiftKey: false, metaKey: false, ctrlKey: false, altKey: false, }); this.lastEvent = null; this.unsubscribes = []; this.keybordEvents = new EventTarget(); this.handleKeyDown = (event) => { if (this.hasChanged(event)) { this.lastEvent = event; this.$keyboardState.value = { shiftKey: event.shiftKey, metaKey: event.metaKey, ctrlKey: event.ctrlKey, altKey: event.altKey, }; } this.notifyKeyCode(event); }; this.graph.on("state-change", (event) => { if (event.detail.state === GraphState.READY) { this.startListening(); } else if (event.detail.state === GraphState.INIT) { this.stopListening(); } }); } isShiftPressed() { return this.$keyboardState.value.shiftKey; } isMetaPressed() { return this.$keyboardState.value.metaKey; } isCtrlPressed() { return this.$keyboardState.value.ctrlKey; } isAltPressed() { return this.$keyboardState.value.altKey; } startListening() { this.graph.layers.$root?.ownerDocument?.addEventListener("keydown", this.handleKeyDown, { capture: true, }); this.graph.layers.$root?.ownerDocument?.addEventListener("keyup", this.handleKeyDown, { capture: true, }); this.unsubscribes.push(() => { this.graph.layers.$root?.ownerDocument?.removeEventListener("keydown", this.handleKeyDown, { capture: true, }); this.graph.layers.$root?.ownerDocument?.removeEventListener("keyup", this.handleKeyDown, { capture: true, }); }); } onPress(key, cb, options) { this.keybordEvents.addEventListener(`press-${key}`, cb, options); return () => { this.keybordEvents.removeEventListener(`press-${key}`, cb); }; } onRelease(key, cb, options) { this.keybordEvents.addEventListener(`release-${key}`, cb, options); return () => { this.keybordEvents.removeEventListener(`release-${key}`, cb); }; } stopListening() { this.unsubscribes.forEach((unsubscribe) => unsubscribe()); this.unsubscribes = []; this.lastEvent = null; this.$keyboardState.value = { shiftKey: false, metaKey: false, ctrlKey: false, altKey: false, }; } hasChanged(event) { return (this.lastEvent?.key !== event.key || this.lastEvent?.shiftKey !== event.shiftKey || this.lastEvent?.metaKey !== event.metaKey || this.lastEvent?.ctrlKey !== event.ctrlKey || this.lastEvent?.altKey !== event.altKey); } notifyKeyCode(event) { if (event.type === "keydown") { this.keybordEvents.dispatchEvent(new CustomEvent(`press-${event.key}`, { detail: event })); } else if (event.type === "keyup") { this.keybordEvents.dispatchEvent(new CustomEvent(`release-${event.key}`, { detail: event })); } } cleanup() { this.stopListening(); } }