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