implemented new fitView logic
This commit is contained in:
@@ -6,25 +6,58 @@ type PressedKeys = Set<string>;
|
||||
type KeyOrCode = 'key' | 'code';
|
||||
|
||||
export type UseKeyPressOptions = {
|
||||
/**
|
||||
* Listen to key presses on a specific element.
|
||||
* @default document
|
||||
*/
|
||||
target?: Window | Document | HTMLElement | ShadowRoot | null;
|
||||
/**
|
||||
* You can use this flag to prevent triggering the key press hook when an input field is focused.
|
||||
* @default true
|
||||
*/
|
||||
actInsideInputWithModifier?: boolean;
|
||||
preventDefault?: boolean;
|
||||
};
|
||||
|
||||
const defaultDoc = typeof document !== 'undefined' ? document : null;
|
||||
|
||||
/**
|
||||
* Hook for handling key events.
|
||||
* This hook lets you listen for specific key codes and tells you whether they are
|
||||
* currently pressed or not.
|
||||
*
|
||||
* @public
|
||||
* @param param.keyCode - The key code (string or array of strings) to use
|
||||
* @param param.options - Options
|
||||
* @returns boolean
|
||||
* @param options - Options
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useKeyPress } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const spacePressed = useKeyPress('Space');
|
||||
* const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s']);
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* {spacePressed && <p>Space pressed!</p>}
|
||||
* {cmdAndSPressed && <p>Cmd + S pressed!</p>}
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useKeyPress(
|
||||
// the keycode can be a string 'a' or an array of strings ['a', 'a+d']
|
||||
// a string means a single key 'a' or a combination when '+' is used 'a+d'
|
||||
// an array means different possibilites. Explainer: ['a', 'd+s'] here the
|
||||
// user can use the single key 'a' or the combination 'd' + 's'
|
||||
/**
|
||||
* The key code (string or array of strings) specifies which key(s) should trigger
|
||||
* an action.
|
||||
*
|
||||
* A **string** can represent:
|
||||
* - A **single key**, e.g. `'a'`
|
||||
* - A **key combination**, using `'+'` to separate keys, e.g. `'a+d'`
|
||||
*
|
||||
* An **array of strings** represents **multiple possible key inputs**. For example, `['a', 'd+s']`
|
||||
* means the user can press either the single key `'a'` or the combination of `'d'` and `'s'`.
|
||||
* @default null
|
||||
*/
|
||||
keyCode: KeyCode | null = null,
|
||||
options: UseKeyPressOptions = { target: defaultDoc, actInsideInputWithModifier: true }
|
||||
): boolean {
|
||||
@@ -36,20 +69,24 @@ export function useKeyPress(
|
||||
// we need to remember the pressed keys in order to support combinations
|
||||
const pressedKeys = useRef<PressedKeys>(new Set([]));
|
||||
|
||||
// keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
|
||||
// keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
|
||||
// used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
||||
// we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
||||
// and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when
|
||||
// we can't find it in the list of keysToWatch.
|
||||
/*
|
||||
* keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
|
||||
* keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
|
||||
* used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
||||
* we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
||||
* and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when
|
||||
* we can't find it in the list of keysToWatch.
|
||||
*/
|
||||
const [keyCodes, keysToWatch] = useMemo<[Array<Keys>, Keys]>(() => {
|
||||
if (keyCode !== null) {
|
||||
const keyCodeArr = Array.isArray(keyCode) ? keyCode : [keyCode];
|
||||
const keys = keyCodeArr
|
||||
.filter((kc) => typeof kc === 'string')
|
||||
// we first replace all '+' with '\n' which we will use to split the keys on
|
||||
// then we replace '\n\n' with '\n+', this way we can also support the combination 'key++'
|
||||
// in the end we simply split on '\n' to get the key array
|
||||
/*
|
||||
* we first replace all '+' with '\n' which we will use to split the keys on
|
||||
* then we replace '\n\n' with '\n+', this way we can also support the combination 'key++'
|
||||
* in the end we simply split on '\n' to get the key array
|
||||
*/
|
||||
.map((kc) => kc.replace('+', '\n').replace('\n\n', '\n+').split('\n'));
|
||||
const keysFlat = keys.reduce((res: Keys, item) => res.concat(...item), []);
|
||||
|
||||
@@ -64,7 +101,7 @@ export function useKeyPress(
|
||||
|
||||
if (keyCode !== null) {
|
||||
const downHandler = (event: KeyboardEvent) => {
|
||||
modifierPressed.current = event.ctrlKey || event.metaKey || event.shiftKey;
|
||||
modifierPressed.current = event.ctrlKey || event.metaKey || event.shiftKey || event.altKey;
|
||||
const preventAction =
|
||||
(!modifierPressed.current || (modifierPressed.current && !options.actInsideInputWithModifier)) &&
|
||||
isInputDOMNode(event);
|
||||
@@ -76,19 +113,18 @@ export function useKeyPress(
|
||||
pressedKeys.current.add(event[keyOrCode]);
|
||||
|
||||
if (isMatchingKey(keyCodes, pressedKeys.current, false)) {
|
||||
event.preventDefault();
|
||||
const target = (event.composedPath?.()?.[0] || event.target) as Element | null;
|
||||
const isInteractiveElement = target?.nodeName === 'BUTTON' || target?.nodeName === 'A';
|
||||
|
||||
if (options.preventDefault !== false && (modifierPressed.current || !isInteractiveElement)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
setKeyPressed(true);
|
||||
}
|
||||
};
|
||||
|
||||
const upHandler = (event: KeyboardEvent) => {
|
||||
const preventAction =
|
||||
(!modifierPressed.current || (modifierPressed.current && !options.actInsideInputWithModifier)) &&
|
||||
isInputDOMNode(event);
|
||||
|
||||
if (preventAction) {
|
||||
return false;
|
||||
}
|
||||
const keyOrCode = useKeyOrCode(event.code, keysToWatch);
|
||||
|
||||
if (isMatchingKey(keyCodes, pressedKeys.current, true)) {
|
||||
@@ -133,12 +169,16 @@ export function useKeyPress(
|
||||
function isMatchingKey(keyCodes: Array<Keys>, pressedKeys: PressedKeys, isUp: boolean): boolean {
|
||||
return (
|
||||
keyCodes
|
||||
// we only want to compare same sizes of keyCode definitions
|
||||
// and pressed keys. When the user specified 'Meta' as a key somewhere
|
||||
// this would also be truthy without this filter when user presses 'Meta' + 'r'
|
||||
/*
|
||||
* we only want to compare same sizes of keyCode definitions
|
||||
* and pressed keys. When the user specified 'Meta' as a key somewhere
|
||||
* this would also be truthy without this filter when user presses 'Meta' + 'r'
|
||||
*/
|
||||
.filter((keys) => isUp || keys.length === pressedKeys.size)
|
||||
// since we want to support multiple possibilities only one of the
|
||||
// combinations need to be part of the pressed keys
|
||||
/*
|
||||
* since we want to support multiple possibilities only one of the
|
||||
* combinations need to be part of the pressed keys
|
||||
*/
|
||||
.some((keys) => keys.every((k) => pressedKeys.has(k)))
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user