useKeyPress.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // https://ahooks.js.org/zh-CN/hooks/dom/use-key-press
  2. import type { Ref } from 'vue';
  3. import { onBeforeUnmount, onMounted, unref } from 'vue';
  4. import { noop } from '/@/utils';
  5. import { isFunction, isString, isNumber, isArray } from '/@/utils/is';
  6. export type KeyPredicate = (event: KeyboardEvent) => boolean;
  7. export type keyType = KeyboardEvent['keyCode'] | KeyboardEvent['key'];
  8. export type KeyFilter = keyType | keyType[] | ((event: KeyboardEvent) => boolean);
  9. export type EventHandler = (event: KeyboardEvent) => void;
  10. export type keyEvent = 'keydown' | 'keyup';
  11. export type TargetElement = HTMLElement | Element | Document | Window;
  12. export type Target = Ref<TargetElement>;
  13. export type EventOption = {
  14. events?: keyEvent[];
  15. target?: Target;
  16. };
  17. const defaultEvents: keyEvent[] = ['keydown'];
  18. // 键盘事件 keyCode 别名
  19. const aliasKeyCodeMap: Recordable<number | number[]> = {
  20. esc: 27,
  21. tab: 9,
  22. enter: 13,
  23. space: 32,
  24. up: 38,
  25. left: 37,
  26. right: 39,
  27. down: 40,
  28. delete: [8, 46],
  29. };
  30. // 键盘事件 key 别名
  31. const aliasKeyMap: Recordable<string | string[]> = {
  32. esc: 'Escape',
  33. tab: 'Tab',
  34. enter: 'Enter',
  35. space: ' ',
  36. // IE11 uses key names without `Arrow` prefix for arrow keys.
  37. up: ['Up', 'ArrowUp'],
  38. left: ['Left', 'ArrowLeft'],
  39. right: ['Right', 'ArrowRight'],
  40. down: ['Down', 'ArrowDown'],
  41. delete: ['Backspace', 'Delete'],
  42. };
  43. // 修饰键
  44. const modifierKey: Recordable<(event: KeyboardEvent) => boolean> = {
  45. ctrl: (event: KeyboardEvent) => event.ctrlKey,
  46. shift: (event: KeyboardEvent) => event.shiftKey,
  47. alt: (event: KeyboardEvent) => event.altKey,
  48. meta: (event: KeyboardEvent) => event.metaKey,
  49. };
  50. /**
  51. * 判断按键是否激活
  52. * @param [event: KeyboardEvent]键盘事件
  53. * @param [keyFilter: any] 当前键
  54. * @returns Boolean
  55. */
  56. function genFilterKey(event: any, keyFilter: any) {
  57. // 浏览器自动补全 input 的时候,会触发 keyDown、keyUp 事件,但此时 event.key 等为空
  58. if (!event.key) {
  59. return false;
  60. }
  61. // 数字类型直接匹配事件的 keyCode
  62. if (isNumber(keyFilter)) {
  63. return event.keyCode === keyFilter;
  64. }
  65. // 字符串依次判断是否有组合键
  66. const genArr = keyFilter.split('.');
  67. let genLen = 0;
  68. for (const key of genArr) {
  69. // 组合键
  70. const genModifier = modifierKey[key];
  71. // key 别名
  72. const aliasKey = aliasKeyMap[key];
  73. // keyCode 别名
  74. const aliasKeyCode = aliasKeyCodeMap[key];
  75. /**
  76. * 满足以上规则
  77. * 1. 自定义组合键别名
  78. * 2. 自定义 key 别名
  79. * 3. 自定义 keyCode 别名
  80. * 4. 匹配 key 或 keyCode
  81. */
  82. if (
  83. (genModifier && genModifier(event)) ||
  84. (aliasKey && isArray(aliasKey) ? aliasKey.includes(event.key) : aliasKey === event.key) ||
  85. (aliasKeyCode && isArray(aliasKeyCode)
  86. ? aliasKeyCode.includes(event.keyCode)
  87. : aliasKeyCode === event.keyCode) ||
  88. event.key.toUpperCase() === key.toUpperCase()
  89. ) {
  90. genLen++;
  91. }
  92. }
  93. return genLen === genArr.length;
  94. }
  95. /**
  96. * 键盘输入预处理方法
  97. */
  98. function genKeyFormat(keyFilter: any): KeyPredicate {
  99. if (isFunction(keyFilter)) {
  100. return keyFilter;
  101. }
  102. if (isString(keyFilter) || isNumber(keyFilter)) {
  103. return (event: KeyboardEvent) => genFilterKey(event, keyFilter);
  104. }
  105. if (isArray(keyFilter)) {
  106. return (event: KeyboardEvent) => keyFilter.some((item: any) => genFilterKey(event, item));
  107. }
  108. return keyFilter ? () => true : () => false;
  109. }
  110. export function useKeyPress(
  111. keyFilter: KeyFilter,
  112. eventHandler: EventHandler = noop,
  113. option: EventOption = {}
  114. ) {
  115. const { events = defaultEvents, target } = option;
  116. let el: TargetElement | null | undefined;
  117. function handler(event: any) {
  118. const genGuard: KeyPredicate = genKeyFormat(keyFilter);
  119. if (genGuard(event)) {
  120. return eventHandler(event);
  121. }
  122. }
  123. onMounted(() => {
  124. el = getTargetElement(target, window);
  125. if (!el) return;
  126. for (const eventName of events) {
  127. el.addEventListener(eventName, handler);
  128. }
  129. });
  130. onBeforeUnmount(() => {
  131. if (!el) return;
  132. for (const eventName of events) {
  133. el.removeEventListener(eventName, handler);
  134. }
  135. });
  136. }
  137. export function getTargetElement(
  138. target?: Target,
  139. defaultElement?: TargetElement
  140. ): TargetElement | undefined | null {
  141. if (!target) {
  142. return defaultElement;
  143. }
  144. return isFunction(target) ? target() : unref(target);
  145. }