UNPKG

4.88 kBTypeScriptView Raw
1/*
2 * Copyright 2016 Palantir Technologies, Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import * as React from "react";
18
19import { IConstructor } from "../../common/constructor";
20import { HOTKEYS_WARN_DECORATOR_NEEDS_REACT_ELEMENT, HOTKEYS_WARN_DECORATOR_NO_METHOD } from "../../common/errors";
21import { getDisplayName, isFunction } from "../../common/utils";
22import { HotkeyScope, HotkeysEvents } from "./hotkeysEvents";
23import { IHotkeysProps } from "./hotkeysTypes";
24
25export interface IHotkeysTargetComponent extends React.Component {
26 /** Components decorated with the `@HotkeysTarget` decorator must implement React's component `render` function. */
27 render(): React.ReactElement<any> | null | undefined;
28
29 /**
30 * Components decorated with the `@HotkeysTarget` decorator must implement
31 * this method, and it must return a `Hotkeys` React element.
32 */
33 renderHotkeys: () => React.ReactElement<IHotkeysProps>;
34}
35
36/* eslint-disable deprecation/deprecation */
37
38/** @deprecated use `useHotkeys` hook or `<HotkeysTarget2>` component */
39export 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 /** @internal */
48 public globalHotkeysEvents: HotkeysEvents = new HotkeysEvents(HotkeyScope.GLOBAL);
49
50 /** @internal */
51 public localHotkeysEvents: HotkeysEvents = new HotkeysEvents(HotkeyScope.LOCAL);
52
53 public componentDidMount() {
54 if (super.componentDidMount != null) {
55 super.componentDidMount();
56 }
57
58 // attach global key event listeners
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 // always return `element` in case caller is distinguishing between `null` and `undefined`
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}