1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import * as React from "react";
|
18 |
|
19 | import { IConstructor } from "../../common/constructor";
|
20 | import { HOTKEYS_WARN_DECORATOR_NEEDS_REACT_ELEMENT, HOTKEYS_WARN_DECORATOR_NO_METHOD } from "../../common/errors";
|
21 | import { getDisplayName, isFunction } from "../../common/utils";
|
22 | import { HotkeyScope, HotkeysEvents } from "./hotkeysEvents";
|
23 | import { IHotkeysProps } from "./hotkeysTypes";
|
24 |
|
25 | export interface IHotkeysTargetComponent extends React.Component {
|
26 |
|
27 | render(): React.ReactElement<any> | null | undefined;
|
28 |
|
29 | |
30 |
|
31 |
|
32 |
|
33 | renderHotkeys: () => React.ReactElement<IHotkeysProps>;
|
34 | }
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | export function HotkeysTarget<T extends IConstructor<IHotkeysTargetComponent>>(WrappedComponent: T) {
|
40 | if (!isFunction(WrappedComponent.prototype.renderHotkeys)) {
|
41 | console.warn(HOTKEYS_WARN_DECORATOR_NO_METHOD);
|
42 | }
|
43 |
|
44 | return class HotkeysTargetClass extends WrappedComponent {
|
45 | public static displayName = `HotkeysTarget(${getDisplayName(WrappedComponent)})`;
|
46 |
|
47 |
|
48 | public globalHotkeysEvents: HotkeysEvents = new HotkeysEvents(HotkeyScope.GLOBAL);
|
49 |
|
50 |
|
51 | public localHotkeysEvents: HotkeysEvents = new HotkeysEvents(HotkeyScope.LOCAL);
|
52 |
|
53 | public componentDidMount() {
|
54 | if (super.componentDidMount != null) {
|
55 | super.componentDidMount();
|
56 | }
|
57 |
|
58 |
|
59 | document.addEventListener("keydown", this.globalHotkeysEvents.handleKeyDown);
|
60 | document.addEventListener("keyup", this.globalHotkeysEvents.handleKeyUp);
|
61 | }
|
62 |
|
63 | public componentWillUnmount() {
|
64 | super.componentWillUnmount?.();
|
65 | document.removeEventListener("keydown", this.globalHotkeysEvents.handleKeyDown);
|
66 | document.removeEventListener("keyup", this.globalHotkeysEvents.handleKeyUp);
|
67 |
|
68 | this.globalHotkeysEvents.clear();
|
69 | this.localHotkeysEvents.clear();
|
70 | }
|
71 |
|
72 | public render() {
|
73 | const element = super.render() as JSX.Element;
|
74 |
|
75 | if (element == null) {
|
76 |
|
77 | return element;
|
78 | }
|
79 |
|
80 | if (!React.isValidElement<any>(element)) {
|
81 | console.warn(HOTKEYS_WARN_DECORATOR_NEEDS_REACT_ELEMENT);
|
82 | return element;
|
83 | }
|
84 |
|
85 | if (isFunction(this.renderHotkeys)) {
|
86 | const hotkeys = this.renderHotkeys();
|
87 | if (this.localHotkeysEvents) {
|
88 | this.localHotkeysEvents.setHotkeys(hotkeys.props);
|
89 | }
|
90 | if (this.globalHotkeysEvents) {
|
91 | this.globalHotkeysEvents.setHotkeys(hotkeys.props);
|
92 | }
|
93 |
|
94 | if (this.localHotkeysEvents.count() > 0) {
|
95 | const tabIndex = hotkeys.props.tabIndex === undefined ? 0 : hotkeys.props.tabIndex;
|
96 |
|
97 | const { onKeyDown: existingKeyDown, onKeyUp: existingKeyUp } = element.props;
|
98 |
|
99 | const handleKeyDownWrapper = (e: React.KeyboardEvent<HTMLElement>) => {
|
100 | this.localHotkeysEvents.handleKeyDown(e.nativeEvent as KeyboardEvent);
|
101 | existingKeyDown?.(e);
|
102 | };
|
103 |
|
104 | const handleKeyUpWrapper = (e: React.KeyboardEvent<HTMLElement>) => {
|
105 | this.localHotkeysEvents.handleKeyUp(e.nativeEvent as KeyboardEvent);
|
106 | existingKeyUp?.(e);
|
107 | };
|
108 | return React.cloneElement(element, {
|
109 | onKeyDown: handleKeyDownWrapper,
|
110 | onKeyUp: handleKeyUpWrapper,
|
111 | tabIndex,
|
112 | });
|
113 | }
|
114 | }
|
115 | return element;
|
116 | }
|
117 | };
|
118 | }
|