Skip to content

Commit 9ae4865

Browse files
committed
[WIP] useHotKeys hook
1 parent 922cccf commit 9ae4865

6 files changed

Lines changed: 77 additions & 22 deletions

File tree

apps/react-tools-demo/src/markdown/useMergedRef.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@ Hook to merge multiple refs into one.
77
export const UseMergedRef = () => {
88
const [state, setState] = useState(false);
99
const [refCb] = useResizeObserver<HTMLDivElement>(
10-
(entries: ResizeObserverEntry[]) => {
10+
useCallback((entries: ResizeObserverEntry[]) => {
1111
const result = entries[0].contentRect.width < 100;
1212
result !== state && setState(result);
13-
}
13+
}, [state])
1414
);
1515
const ref = useRef<HTMLDivElement>(null);
1616
const mergedRef = useMergedRef<HTMLDivElement>(ref, refCb);
1717

1818
const changeBorderColor = () => {
19-
mergedRef.current;
2019
ref.current && (ref.current.style.border = ref.current.style.border.indexOf("lightgray") !== -1
2120
? '1px solid darkcyan'
2221
: '1px solid lightgray'
@@ -38,16 +37,16 @@ export const UseMergedRef = () => {
3837
## API
3938

4039
```tsx
41-
useMergedRef <T>(...refs: React.Ref<T>[])
40+
useMergedRef <T>(...refs: Ref<T>[])
4241
```
4342

4443
> ### Params
4544
>
46-
> - __refs__: _React.Ref<T>[]_
45+
> - __refs__: _Ref<T>[]_
4746
>
4847
4948
> ### Returns
5049
>
5150
> __mergedRef__
52-
> - _React.RefObject<T>_
51+
> - _RefObject<T>_
5352
>

packages/react-tools/README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@
4141
- [x] useMemoCompare
4242
- [x] useMemoDeepCompare
4343
- [x] useMergedRef
44-
- [ ] useObjectRef
45-
- [ ] useArrayRef
4644
- [x] useLazyRef
4745
- [x] useId
4846

@@ -68,7 +66,6 @@
6866
- [x] useDoubleClick
6967
- [x] useBeforeUnload
7068
- [x] useScreen (orientation and ecc)
71-
- [ ] useKeysEvents
7269
- [ ] useHotKeys (https://mantine.dev/hooks/use-hotkeys/)
7370
- [ ] useImageOnLoad
7471
- [ ] usePinchZoom

packages/react-tools/src/hooks/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,4 @@ export { useDialogBox } from './useDialogBox';
6868
export { useBeforeUnload } from './useBeforeUnload';
6969
export { useDoubleClick } from './useDoubleClick';
7070
export { useScreen } from './useScreen';
71-
export { useMergedRef } from './useMergedRef';
71+
export { useMergedRef } from './useMergedRef';

packages/react-tools/src/hooks/useEventListener.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { useMemoizedFunction } from ".";
1111
* @param {"normal"|"layout"} [options.effectType="normal"] - option to set which hook is used to attach event listener.
1212
* @returns {()=>void} remove - used to manually remove the eventListener
1313
*/
14-
export const useEventListener = ({ type, listener, element = window, listenerOpts, effectType="normal" }: { type: string, listener: (evt: Event | CustomEvent) => void, element?: RefObject<HTMLElement> | Window, listenerOpts?: boolean | AddEventListenerOptions, effectType?: "normal" | "layout" }) => {
14+
export const useEventListener = <T extends Event | CustomEvent>({ type, listener, element = window, listenerOpts, effectType="normal" }: { type: string, listener: ((evt: T) => unknown | Promise<unknown>), element?: RefObject<HTMLElement> | Window, listenerOpts?: boolean | AddEventListenerOptions, effectType?: "normal" | "layout" }) => {
1515
const optsMemoized = useRef<typeof listenerOpts>(listenerOpts);
1616
const elementReference = useRef<HTMLElement | Window | null>();
1717
const effect = effectType === "layout" ? useLayoutEffect : useEffect;
@@ -24,14 +24,14 @@ export const useEventListener = ({ type, listener, element = window, listenerOpt
2424
: null
2525
: element as Window
2626

27-
elementReference.current && (elementReference.current as HTMLElement | Window).addEventListener(type, listener, opts);
27+
elementReference.current && (elementReference.current as HTMLElement | Window).addEventListener(type, listener as EventListenerOrEventListenerObject, opts);
2828
return () => {
29-
elementReference.current && (elementReference.current as HTMLElement | Window).removeEventListener(type, listener, opts);
29+
elementReference.current && (elementReference.current as HTMLElement | Window).removeEventListener(type, listener as EventListenerOrEventListenerObject, opts);
3030
}
3131
}, [element, type, listener]);
3232

3333
const remove = useMemoizedFunction(() => {
34-
elementReference.current && (elementReference.current as HTMLElement | Window).removeEventListener(type, listener, optsMemoized.current);
34+
elementReference.current && (elementReference.current as HTMLElement | Window).removeEventListener(type, listener as EventListenerOrEventListenerObject, optsMemoized.current);
3535
});
3636

3737
return remove;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { KeyboardEvent as KeyEvt, RefObject, useCallback } from "react";
2+
import { useEventListener } from ".";
3+
4+
5+
export const useHotKeys = ({ hotKey, type = "keydown", target = window, listener, listenerOpts }: { hotKey: string | `${'alt' | 'ctrl' | 'meta' | 'shift' | 'ctrlCommand'}+${string}` | `${'alt' | 'ctrl' | 'meta' | 'shift' | 'ctrlCommand'}+${'alt' | 'ctrl' | 'meta' | 'shift' | 'ctrlCommand'}+${string}`, type?: "keydown" | "keyup", target?: RefObject<HTMLElement> | Window, listener: (evt: KeyboardEvent | KeyEvt<HTMLElement>) => void | Promise<void>, listenerOpts?: boolean | AddEventListenerOptions }) => {
6+
const handler = useCallback((evt: KeyboardEvent | KeyEvt<HTMLElement>) => {
7+
let keys = hotKey.toLowerCase().split("+").map(el => el.trim());
8+
const modifiers = {
9+
alt: keys.includes('alt'),
10+
ctrl: keys.includes('ctrl'),
11+
meta: keys.includes('meta'),
12+
ctrlCommand: keys.includes('ctrlcommand'),
13+
shift: keys.includes('shift'),
14+
};
15+
keys = keys.filter(el => !['alt', 'ctrl', 'meta', 'shift', 'ctrlCommand'].includes(el));
16+
for (let i = 0, size = keys.length; i < size; i++) {
17+
if (keys[i] === "" && (i + 1) < size && keys[i + 1] === "") {
18+
keys[i + 1] = "+";
19+
}
20+
}
21+
keys = keys.filter(el => el !== "")
22+
const { altKey, ctrlKey, metaKey, shiftKey, key: pressedKey } = evt;
23+
if (modifiers.ctrlCommand) {
24+
if (!ctrlKey && !metaKey) {
25+
return false;
26+
}
27+
} else {
28+
if (ctrlKey !== modifiers.ctrl) {
29+
return false;
30+
}
31+
if (altKey !== modifiers.alt) {
32+
return false;
33+
}
34+
if (metaKey !== modifiers.meta) {
35+
return false;
36+
}
37+
if (shiftKey !== modifiers.shift) {
38+
return false;
39+
}
40+
}
41+
}, []);
42+
43+
const remove = useEventListener<KeyboardEvent>({
44+
type,
45+
listener: handler,
46+
listenerOpts,
47+
element: target,
48+
effectType: "normal"
49+
});
50+
51+
return remove;
52+
}
Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1-
import React, { MutableRefObject, useEffect, useRef } from "react";
1+
import { Ref, MutableRefObject, useMemo, useRef } from "react";
22

33
/**
44
* **`useMergedRef`**: Hook to merge multiple refs into one.
5-
* @param {React.Ref<T>[]} refs
6-
* @returns {React.RefObject<T>} mergedRef
5+
* @param {Ref<T>[]} refs
6+
* @returns {RefObject<T>} mergedRef
77
*/
8-
export const useMergedRef = <T>(...refs: React.Ref<T>[]) => {
8+
export const useMergedRef = <T>(...refs: Ref<T>[]) => {
99
const mergedRef = useRef<T>(null);
10-
useEffect(() => {
11-
refs.forEach(ref => typeof ref === "function" ? ref(mergedRef.current) : (ref as MutableRefObject<T | null>).current = mergedRef.current);
12-
}, [refs]);
13-
return mergedRef;
10+
return useMemo(() => (Object.defineProperty({}, "current", {
11+
get() {
12+
return (mergedRef as MutableRefObject<T>).current;
13+
},
14+
set(value: T) {
15+
(mergedRef as MutableRefObject<T | null>).current = value;
16+
refs.forEach(ref => typeof ref === "function" ? ref(mergedRef.current) : (ref as MutableRefObject<T | null>).current = mergedRef.current);
17+
},
18+
enumerable: false,
19+
configurable: false,
20+
})), [refs]) as Ref<T>;
1421
}

0 commit comments

Comments
 (0)