@keybindy/react
Version:
Keybindy for React: Simple, scoped keyboard shortcuts that require little setup. designed to smoothly blend in with your React applications, allowing for robust keybinding functionality without the overhead.
254 lines (251 loc) • 8.23 kB
JavaScript
import React from 'react';
import ShortcutManager from '@keybindy/core';
let sharedInstance = null;
/**
* React hook to manage keyboard shortcuts using a shared instance of `ShortcutManager`.
* Automatically cleans up shortcuts registered by the component on unmount.
*
* @param {Object} config - Configuration object.
* @param {boolean} [config.logs=false] - Whether to enable debug logs in the console.
*
* @returns {Object} Object containing shortcut management methods.
*
* @example
* const {
* register,
* setScope,
* getCheatSheet,
* } = useKeybindy({ logs: true });
*
* useEffect(() => {
* register(['ctrl', 's'], () => save(), {
* scope: 'editor',
* data: { description: 'Save document' }
* });
* setScope('editor');
* }, []);
*/
function useKeybindy({ logs = false, onShortcutFired, } = {}) {
const managerRef = React.useRef(null);
const registeredIds = React.useRef(new Set());
if (!sharedInstance) {
sharedInstance = new ShortcutManager(onShortcutFired);
}
if (!managerRef.current) {
managerRef.current = sharedInstance;
managerRef.current.start();
}
const log = (...args) => {
if (logs)
console.log('[Keybindy]', ...args);
};
const warn = (...args) => {
if (logs)
console.warn('[Keybindy]', ...args);
};
/**
* Registers a new keyboard shortcut.
*
* @param {Keys[] | Keys[][]} keys - Key combination(s) to listen for.
* @param {ShortcutHandler} handler - Callback function to invoke when shortcut is triggered.
* @param {ShortcutOptions} [options] - Optional configuration, including scope and metadata.
*/
const register = React.useCallback((keys, handler, options) => {
if (keys.length === 0) {
warn('No keys provided to register');
return;
}
const id = options?.data?.id;
if (id)
registeredIds.current.add(id);
log('Registered:', id ?? keys);
managerRef.current?.register(keys, handler, options);
}, []);
/**
* Unregisters a previously registered keyboard shortcut.
*
* @param {Keys[]} keys - Key combination to unregister.
* @param {string} [scope] - Optional scope for more targeted unregistration.
*/
const unregister = React.useCallback((keys, scope) => {
if (keys.length === 0) {
warn('No keys provided to unregister');
return;
}
managerRef.current?.unregister(keys, scope);
log('Unregistered:', keys);
}, []);
/**
* Enables a previously disabled shortcut.
*
* @param {Keys[]} keys - Key combination to enable.
* @param {string} [scope] - Optional scope to target a specific set.
*/
const enable = React.useCallback((keys, scope) => {
if (keys.length === 0) {
warn('No keys provided to enable');
return;
}
managerRef.current?.enable(keys, scope);
log('Enabled:', keys);
}, []);
/**
* Disables a shortcut so it no longer triggers its handler.
*
* @param {Keys[]} keys - Key combination to disable.
* @param {string} [scope] - Optional scope to target a specific set.
*/
const disable = React.useCallback((keys, scope) => {
if (keys.length === 0) {
warn('No keys provided to disable');
return;
}
managerRef.current?.disable(keys, scope);
log('Disabled:', keys);
}, []);
/**
* Toggles a shortcut between enabled and disabled.
*
* @param {Keys[]} keys - Key combination to toggle.
* @param {string} [scope] - Optional scope to target a specific set.
*/
const toggle = React.useCallback((keys, scope) => {
if (keys.length === 0) {
warn('No keys provided to toggle');
return;
}
managerRef.current?.toggle(keys, scope);
log('Toggled:', keys);
}, []);
/**
* Returns a list of shortcuts registered in a given scope.
*
* @param {string} [scope] - The scope to query. Defaults to the active scope.
* @returns {Array} List of shortcut definitions.
*/
const getCheatSheet = React.useCallback((scope) => {
return managerRef.current?.getCheatSheet(scope);
}, []);
/**
* Returns the currently active scope.
* @returns {string} The active scope.
*/
const getActiveScope = React.useCallback(() => {
return managerRef.current?.getActiveScope();
}, []);
/**
* Disables all shortcuts in the specified scope or all scopes if no scope is provided.
* @param scope - The scope to disable shortcuts in.
*/
const disableAll = React.useCallback((scope) => {
managerRef.current?.disableAll(scope);
log(`Disabled all shortcuts${scope ? ` in scope "${scope}"` : ''}`);
}, []);
/**
* Enables all shortcuts in the specified scope or all scopes if no scope is provided.
* @param scope - The scope to enable shortcuts in.
*/
const enableAll = React.useCallback((scope) => {
managerRef.current?.enableAll(scope);
log(`Enabled all shortcuts${scope ? ` in scope "${scope}"` : ''}`);
}, []);
/**
* Sets the currently active shortcut scope.
*
* @param {string} scope - The scope name to set as active.
*/
const setScope = React.useCallback((scope) => {
managerRef.current?.setActiveScope(scope);
log('Scope set to:', scope);
}, []);
/**
* Resets the scope stack to the default state.
*/
const resetScope = React.useCallback(() => {
managerRef.current?.resetScope();
log('Reset scope');
}, []);
/**
* Returns all scopes in the stack.
* @returns An array of scopes.
*/
const getScopes = React.useCallback(() => {
return managerRef.current?.getScopes();
}, []);
/**
* Checks if the given scope is active.
* @param scope - The scope to check.
* @returns `true` if the scope is active, `false` otherwise.
*/
const isScopeActive = React.useCallback((scope) => {
return managerRef.current?.isScopeActive(scope);
}, []);
/**
* Registers a callback to be called when a key is typed.
* @param callback - The callback function to be called.
*/
const onTyping = React.useCallback((callback) => {
managerRef.current?.onTyping(callback);
}, []);
/**
* Pops the last scope from the scope stack.
*/
const popScope = React.useCallback(() => {
managerRef.current?.popScope();
log('Popped scope, active scope is:', managerRef.current?.getActiveScope());
}, []);
/**
* Pushes a new scope onto the scope stack.
* @param scope - The scope to push.
*/
const pushScope = React.useCallback((scope) => {
managerRef.current?.pushScope(scope);
log('Pushed scope:', scope);
}, []);
/**
* Returns internal information about the registered scopes and shortcuts.
*
* @param {string} [scope] - Optional scope to filter results.
* @returns {Object} Scope information.
*/
const getScopeInfo = React.useCallback((scope) => {
return managerRef.current?.getScopesInfo(scope);
}, []);
/**
* Destroys the instance of `ShortcutManager`.
* This should be called explicitly when you no longer need the manager.
*/
const destroy = () => {
managerRef.current?.destroy();
};
/**
* Clears the internal state, removing all pressed keys and event listeners.
* This does not unregister shortcuts.
*/
const clear = () => {
managerRef.current?.clear();
};
return {
register,
unregister,
enable,
disable,
toggle,
setScope,
getCheatSheet,
destroy,
getScopeInfo,
getActiveScope,
popScope,
pushScope,
resetScope,
getScopes,
isScopeActive,
onTyping,
enableAll,
clear,
disableAll,
manager: managerRef.current,
};
}
export { useKeybindy };