forked from argoproj/argocd-example-apps
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Insource react-keyhooks package (argoproj#120)
Signed-off-by: Remington Breeze <[email protected]>
- Loading branch information
Showing
5 changed files
with
252 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './context/theme'; | ||
export * from './keypress'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
import * as React from 'react'; | ||
|
||
export enum Key { | ||
TAB = 9, | ||
ENTER = 13, | ||
SHIFT = 16, | ||
ESCAPE = 27, | ||
LEFT = 37, | ||
UP = 38, | ||
RIGHT = 39, | ||
DOWN = 40, | ||
A = 65, | ||
B = 66, | ||
C = 67, | ||
D = 68, | ||
E = 69, | ||
F = 70, | ||
G = 71, | ||
H = 72, | ||
I = 73, | ||
J = 74, | ||
K = 75, | ||
L = 76, | ||
M = 77, | ||
N = 78, | ||
O = 79, | ||
P = 80, | ||
Q = 81, | ||
R = 82, | ||
S = 83, | ||
T = 84, | ||
U = 85, | ||
V = 86, | ||
W = 87, | ||
X = 88, | ||
Y = 89, | ||
Z = 90, | ||
SLASH = 191, | ||
QUESTION = 191, | ||
} | ||
|
||
export enum NumKey { | ||
ZERO = 48, | ||
ONE = 49, | ||
TWO = 50, | ||
THREE = 51, | ||
FOUR = 52, | ||
FIVE = 53, | ||
SIX = 54, | ||
SEVEN = 55, | ||
EIGHT = 56, | ||
NINE = 57, | ||
} | ||
|
||
export enum NumPadKey { | ||
ZERO = 96, | ||
ONE = 97, | ||
TWO = 98, | ||
THREE = 99, | ||
FOUR = 100, | ||
FIVE = 101, | ||
SIX = 102, | ||
SEVEN = 103, | ||
EIGHT = 104, | ||
NINE = 105, | ||
} | ||
|
||
export type AnyNumKey = NumKey | NumPadKey; | ||
export type AnyKeys = AnyNumKey | Key | (AnyNumKey | Key)[]; | ||
|
||
// useNav adds simple stateful navigation to your component | ||
// Returns: | ||
// - pos: indicates current position | ||
// - nav: fxn that accepts an integer that represents number to increment/decrement pos | ||
// - reset: fxn that sets current position to -1 | ||
// Accepts: | ||
// - upperBound: maximum value that pos can grow to | ||
// - init: optional initial value for pos | ||
|
||
export const useNav = (upperBound: number, init?: number): [number, (n: number) => boolean, () => void] => { | ||
const [pos, setPos] = React.useState(init || -1); | ||
const isInBounds = (p: number): boolean => p < upperBound && p > -1; | ||
|
||
const nav = (val: number): boolean => { | ||
const newPos = pos + val; | ||
return isInBounds(newPos) ? setPos(newPos) === null : false; | ||
}; | ||
|
||
const reset = () => { | ||
setPos(-1); | ||
}; | ||
|
||
return [pos, nav, reset]; | ||
}; | ||
|
||
export type KeyState = {action: KeyAction; pressed: boolean; group: number}; | ||
export type KeyAction = (keyCode?: number) => boolean; | ||
export type KeyMap = {[key: number]: KeyState}; | ||
export type KeyHandler = (e: KeyboardEvent) => null; | ||
|
||
export type KeyFxn = (keys: AnyKeys, action: KeyAction, combo?: boolean) => void; | ||
|
||
export interface GroupMap { | ||
groupForKey: {[key: number]: number}; | ||
groups: {[group: number]: KeyMap}; | ||
index: number; | ||
} | ||
|
||
const handlePress = (e: KeyboardEvent, state: GroupMap) => { | ||
const {groups, groupForKey} = state; | ||
const g = groupForKey[e.keyCode]; | ||
if (groups[g]) { | ||
let allPressed = true; | ||
groups[g][e.keyCode].pressed = true; | ||
|
||
for (const i of Object.keys(groups[g])) { | ||
const k = parseInt(i, 10); | ||
const key = groups[g][k]; | ||
|
||
if (!key.pressed) { | ||
allPressed = false; | ||
} | ||
} | ||
|
||
if (allPressed) { | ||
const prevent = groups[g][e.keyCode].action(e.keyCode); | ||
if (prevent) { | ||
e.preventDefault(); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
const handleKeyUp = (e: KeyboardEvent, state: GroupMap) => { | ||
const {groups, groupForKey} = state; | ||
const g = groupForKey[e.keyCode]; | ||
if (groups[g]) { | ||
groups[g][e.keyCode].pressed = false; | ||
} | ||
}; | ||
|
||
const useKeyListen = (state: GroupMap) => { | ||
const localKeyPress = (e: KeyboardEvent) => handlePress(e, state); | ||
const localKeyUp = (e: KeyboardEvent) => handleKeyUp(e, state); | ||
|
||
React.useEffect(() => { | ||
document.addEventListener('keydown', localKeyPress); | ||
document.addEventListener('keyup', localKeyUp); | ||
return () => { | ||
document.removeEventListener('keydown', localKeyPress); | ||
document.removeEventListener('keyup', localKeyUp); | ||
}; | ||
}, [state]); | ||
}; | ||
|
||
export const useKeyListener = () => { | ||
let state = NewGroupMap(); | ||
useKeyListen(state); | ||
return (keys: AnyKeys, action: KeyAction, combo?: boolean) => { | ||
state = addKeybinding(state, keys, action, combo); | ||
}; | ||
}; | ||
|
||
export const useSharedKeyListener = (): GroupMap => { | ||
const state = NewGroupMap(); | ||
useKeyListen(state); | ||
return state; | ||
}; | ||
|
||
const NewGroupMap = () => { | ||
const groupForKey = {} as {[key: number]: number}; | ||
const groups = {} as {[group: number]: KeyMap}; | ||
return { | ||
groups, | ||
groupForKey, | ||
index: 0, | ||
}; | ||
}; | ||
|
||
export const addKeybinding = (state: GroupMap, keys: AnyKeys, action: KeyAction, combo?: boolean): GroupMap => { | ||
const {groups, groupForKey} = state; | ||
let index = state.index; | ||
if (Array.isArray(keys)) { | ||
let g = index; | ||
for (const key of keys) { | ||
// create association between this key and its group | ||
groupForKey[key] = index; | ||
|
||
if (!groups[g]) { | ||
groups[index] = {} as KeyMap; | ||
} | ||
|
||
groups[index][key] = { | ||
group: g, | ||
action, | ||
pressed: false, | ||
}; | ||
|
||
if (!combo) { | ||
g = g + 1; | ||
} | ||
} | ||
index = g + 1; | ||
} else { | ||
groupForKey[keys] = index; | ||
|
||
if (!groups[index]) { | ||
groups[index] = {} as KeyMap; | ||
} | ||
|
||
groups[index][keys] = { | ||
group: index, | ||
action, | ||
pressed: false, | ||
}; | ||
|
||
index = index + 1; | ||
} | ||
|
||
return {groups, groupForKey, index}; | ||
}; | ||
|
||
export const NumKeyToNumber = (key: AnyNumKey): number => { | ||
if (key > 47 && key < 58) { | ||
return key - 48; | ||
} else if (key > 95 && key < 106) { | ||
return key - 96; | ||
} | ||
return -1; | ||
}; | ||
|
||
export const KeybindingContext = React.createContext<{ | ||
keybindingState: GroupMap; | ||
useKeybinding: KeyFxn; | ||
}>({ | ||
keybindingState: NewGroupMap(), | ||
useKeybinding: (keys: AnyKeys, action: KeyAction, combo?: boolean) => null, | ||
}); | ||
|
||
export const KeybindingProvider = (props: {children: React.ReactNode}) => { | ||
let keybindingState: GroupMap = useSharedKeyListener(); | ||
|
||
const useKeybinding = (keys: AnyKeys, action: KeyAction, combo?: boolean) => { | ||
keybindingState = addKeybinding(keybindingState, keys, action, combo); | ||
}; | ||
|
||
return <KeybindingContext.Provider value={{keybindingState, useKeybinding}}>{props.children}</KeybindingContext.Provider>; | ||
}; |