@tanstack/hotkeys
Version:
Type-safe, framework-agnostic keyboard hotkey management for the browser
55 lines (53 loc) • 2.08 kB
JavaScript
import { MODIFIER_KEYS, normalizeKeyName } from "./constants.js";
//#region src/_recording-guard.ts
const sessions = /* @__PURE__ */ new Set();
const captured = /* @__PURE__ */ new WeakSet();
const pending = /* @__PURE__ */ new Map();
let listening = false;
const identity = (event) => event.code || `key:${event.key}`;
/** Consumes recorded key releases and removes guards for completed chords. */
function release(event) {
if (pending.delete(identity(event))) captured.add(event);
if (MODIFIER_KEYS.has(normalizeKeyName(event.key))) {
for (const [id, key] of pending) if (!MODIFIER_KEYS.has(key)) pending.delete(id);
}
cleanup();
}
/** Clears pending releases when focus loss prevents the browser from delivering them. */
function blur() {
pending.clear();
cleanup();
}
/** Detaches shared listeners once neither active sessions nor pending releases need them. */
function cleanup() {
if (listening && sessions.size === 0 && pending.size === 0) {
document.removeEventListener("keyup", release, true);
window.removeEventListener("blur", blur);
listening = false;
}
}
/** Pauses application shortcut dispatch and starts tracking releases for this recorder. */
function beginRecording(owner) {
sessions.add(owner);
if (!listening && typeof document !== "undefined") {
document.addEventListener("keyup", release, true);
window.addEventListener("blur", blur);
listening = true;
}
}
/** Ends a session while retaining guards for keys still held after commit or cancellation. */
function endRecording(owner) {
sessions.delete(owner);
cleanup();
}
/** Marks a consumed keydown and guards subsequent repeats and its release. */
function captureRecordingEvent(event) {
captured.add(event);
pending.set(identity(event), normalizeKeyName(event.key));
}
/** Whether managers should suppress callbacks for this recording session or captured chord. */
function isRecordingEvent(event) {
return sessions.size > 0 || captured.has(event) || pending.has(identity(event));
}
//#endregion
export { beginRecording, captureRecordingEvent, endRecording, isRecordingEvent };