feat(快捷键): 实现 useHotkeys Hook
- 提供 useHotkeys 单个快捷键注册 - 提供 useHotkeysGroup 批量注册 - 提供 useHotkeysHelper 帮助面板控制 - 提供 useRegisteredHotkeys 获取已注册快捷键 - 支持依赖数组自动更新
This commit is contained in:
parent
14e3185664
commit
827a033d41
208
src/hooks/useHotkeys.ts
Normal file
208
src/hooks/useHotkeys.ts
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, DependencyList, useMemo } from 'react';
|
||||||
|
import { useHotkeysContext } from '@/providers/HotkeysProvider';
|
||||||
|
import type { HotkeyConfig, ModifierKey, HotkeyCategory } from '@/types/hotkeys';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useHotkeys Hook 配置选项
|
||||||
|
*/
|
||||||
|
export interface UseHotkeysOptions {
|
||||||
|
/** 主键 */
|
||||||
|
key: string;
|
||||||
|
/** 修饰键 */
|
||||||
|
modifiers?: ModifierKey[];
|
||||||
|
/** 快捷键描述 */
|
||||||
|
description: string;
|
||||||
|
/** 分类 */
|
||||||
|
category?: HotkeyCategory;
|
||||||
|
/** 触发回调 */
|
||||||
|
action: () => void;
|
||||||
|
/** 是否启用 */
|
||||||
|
enabled?: boolean | (() => boolean);
|
||||||
|
/** 是否阻止默认行为 */
|
||||||
|
preventDefault?: boolean;
|
||||||
|
/** 是否允许在输入框中触发 */
|
||||||
|
allowInInput?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册快捷键的 Hook
|
||||||
|
*
|
||||||
|
* @param id 快捷键唯一标识符
|
||||||
|
* @param options 快捷键配置
|
||||||
|
* @param deps 依赖数组(当依赖变化时重新注册)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* // 单键快捷键(非输入框时触发)
|
||||||
|
* useHotkeys('new-chat', {
|
||||||
|
* key: 'n',
|
||||||
|
* description: '新建对话',
|
||||||
|
* category: '导航',
|
||||||
|
* action: () => setShowNewChatModal(true),
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // 组合键快捷键
|
||||||
|
* useHotkeys('open-search', {
|
||||||
|
* key: 'k',
|
||||||
|
* modifiers: ['ctrl', 'meta'], // Ctrl+K 或 Cmd+K
|
||||||
|
* description: '打开搜索',
|
||||||
|
* category: '导航',
|
||||||
|
* action: () => setShowSearchModal(true),
|
||||||
|
* allowInInput: true, // 在输入框中也生效
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useHotkeys(
|
||||||
|
id: string,
|
||||||
|
options: UseHotkeysOptions,
|
||||||
|
deps: DependencyList = []
|
||||||
|
): void {
|
||||||
|
const { register, unregister } = useHotkeysContext();
|
||||||
|
|
||||||
|
// 将 options 转换为 HotkeyConfig
|
||||||
|
const config: HotkeyConfig = useMemo(
|
||||||
|
() => ({
|
||||||
|
key: options.key,
|
||||||
|
modifiers: options.modifiers,
|
||||||
|
description: options.description,
|
||||||
|
category: options.category,
|
||||||
|
action: options.action,
|
||||||
|
enabled: options.enabled,
|
||||||
|
preventDefault: options.preventDefault,
|
||||||
|
allowInInput: options.allowInInput,
|
||||||
|
}),
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[
|
||||||
|
options.key,
|
||||||
|
options.description,
|
||||||
|
options.category,
|
||||||
|
options.enabled,
|
||||||
|
options.preventDefault,
|
||||||
|
options.allowInInput,
|
||||||
|
// modifiers 需要特殊处理(数组比较)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
JSON.stringify(options.modifiers),
|
||||||
|
// action 放在 deps 中由用户控制
|
||||||
|
...deps,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 注册快捷键
|
||||||
|
register(id, {
|
||||||
|
...config,
|
||||||
|
action: options.action, // 使用最新的 action
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清理:注销快捷键
|
||||||
|
return () => {
|
||||||
|
unregister(id);
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [id, config, register, unregister]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量注册快捷键的 Hook
|
||||||
|
*
|
||||||
|
* @param configs 快捷键配置数组
|
||||||
|
* @param deps 依赖数组
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* useHotkeysGroup([
|
||||||
|
* {
|
||||||
|
* id: 'new-chat',
|
||||||
|
* key: 'n',
|
||||||
|
* description: '新建对话',
|
||||||
|
* action: () => setShowNewChatModal(true),
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* id: 'open-search',
|
||||||
|
* key: 'k',
|
||||||
|
* modifiers: ['ctrl', 'meta'],
|
||||||
|
* description: '打开搜索',
|
||||||
|
* action: () => setShowSearchModal(true),
|
||||||
|
* },
|
||||||
|
* ]);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useHotkeysGroup(
|
||||||
|
configs: (UseHotkeysOptions & { id: string })[],
|
||||||
|
deps: DependencyList = []
|
||||||
|
): void {
|
||||||
|
const { register, unregister } = useHotkeysContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 批量注册
|
||||||
|
configs.forEach(({ id, ...options }) => {
|
||||||
|
register(id, {
|
||||||
|
key: options.key,
|
||||||
|
modifiers: options.modifiers,
|
||||||
|
description: options.description,
|
||||||
|
category: options.category,
|
||||||
|
action: options.action,
|
||||||
|
enabled: options.enabled,
|
||||||
|
preventDefault: options.preventDefault,
|
||||||
|
allowInInput: options.allowInInput,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清理:批量注销
|
||||||
|
return () => {
|
||||||
|
configs.forEach(({ id }) => {
|
||||||
|
unregister(id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [register, unregister, JSON.stringify(configs.map((c) => c.id)), ...deps]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取快捷键帮助面板控制的 Hook
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { isOpen, toggle, open, close } = useHotkeysHelper();
|
||||||
|
*
|
||||||
|
* return (
|
||||||
|
* <button onClick={toggle}>
|
||||||
|
* {isOpen ? '关闭帮助' : '快捷键帮助'}
|
||||||
|
* </button>
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useHotkeysHelper() {
|
||||||
|
const { isHelperOpen, toggleHelper, openHelper, closeHelper } =
|
||||||
|
useHotkeysContext();
|
||||||
|
|
||||||
|
return {
|
||||||
|
isOpen: isHelperOpen,
|
||||||
|
toggle: toggleHelper,
|
||||||
|
open: openHelper,
|
||||||
|
close: closeHelper,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有已注册快捷键的 Hook
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const hotkeys = useRegisteredHotkeys();
|
||||||
|
*
|
||||||
|
* return (
|
||||||
|
* <ul>
|
||||||
|
* {hotkeys.map(({ id, config }) => (
|
||||||
|
* <li key={id}>{config.description}</li>
|
||||||
|
* ))}
|
||||||
|
* </ul>
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useRegisteredHotkeys() {
|
||||||
|
const { getAll } = useHotkeysContext();
|
||||||
|
return getAll();
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user