@gravity-ui/graph
Version:
Modern graph editor component
124 lines (123 loc) • 4.22 kB
JavaScript
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();
}
}