Skip to content

Commit

Permalink
Added option to enable hotkeys on contentEditable tags
Browse files Browse the repository at this point in the history
  • Loading branch information
JohannesKlauss committed Apr 22, 2021
1 parent 2d3f0c0 commit 26ddd12
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 9 deletions.
20 changes: 20 additions & 0 deletions docs/useHotkeys.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,24 @@ edits the field content.
</>
);
}}
</Playground>

If you want to have hotkeys active while a user edits a tags contents you can pass `true` to the `enableOnContentEditable` option:

<Playground>
{() => {
const [amount, setAmount] = useState(0);
useHotkeys('cmd+s', (e) => {
e.preventDefault();
setAmount(prevAmount => prevAmount + 100);
}, {enableOnContentEditable: true});
return (
<>
<div>
{amount >= 0 ? 'Add' : 'Remove'} {Math.abs(amount)} dollars {amount >= 0 ? 'from' : 'to'} my bank account.
</div>
<div contentEditable={true}>Edit this text.</div>
</>
);
}}
</Playground>
26 changes: 17 additions & 9 deletions src/useHotkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ const isKeyboardEventTriggeredByInput = (ev: KeyboardEvent) => {
};

export type Options = {
enabled?: boolean;
filter?: typeof hotkeys.filter;
filterPreventDefault?: boolean;
enableOnTags?: AvailableTags[];
splitKey?: string;
scope?: string;
keyup?: boolean;
keydown?: boolean;
enabled?: boolean; // Main setting that determines if the hotkey is enabled or not. (Default: true)
filter?: typeof hotkeys.filter; // A filter function returning whether the callback should get triggered or not. (Default: undefined)
filterPreventDefault?: boolean; // Prevent default browser behavior if the filter function returns false. (Default: true)
enableOnTags?: AvailableTags[]; // Enable hotkeys on a list of tags. (Default: [])
enableOnContentEditable?: boolean; // Enable hotkeys on tags with contentEditable props. (Default: false)
splitKey?: string; // Character to split keys in hotkeys combinations. (Default +)
scope?: string; // Scope. Currently not doing anything.
keyup?: boolean; // Trigger on keyup event? (Default: undefined)
keydown?: boolean; // Trigger on keydown event? (Default: true)
};

export function useHotkeys<T extends Element>(keys: string, callback: KeyHandler, options?: Options): React.MutableRefObject<T | null>;
Expand All @@ -43,15 +44,21 @@ export function useHotkeys<T extends Element>(keys: string, callback: KeyHandler
keydown,
filterPreventDefault = true,
enabled = true,
enableOnContentEditable = false,
} = options as Options || {};
const ref = useRef<T | null>(null);

// The return value of this callback determines if the browsers default behavior is prevented.
const memoisedCallback = useCallback((keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => {
if (filter && !filter(keyboardEvent)) {
return !filterPreventDefault;
}

if (isKeyboardEventTriggeredByInput(keyboardEvent) && !tagFilter(keyboardEvent, enableOnTags) || (keyboardEvent.target as HTMLElement)?.isContentEditable) {
// Check whether the hotkeys was triggered inside an input and that input is enabled or if it was triggered by a content editable tag and it is enabled.
if (
(isKeyboardEventTriggeredByInput(keyboardEvent) && !tagFilter(keyboardEvent, enableOnTags))
|| ((keyboardEvent.target as HTMLElement)?.isContentEditable && !enableOnContentEditable)
) {
return true;
}

Expand All @@ -68,6 +75,7 @@ export function useHotkeys<T extends Element>(keys: string, callback: KeyHandler
return;
}

// In this case keydown is likely undefined, so we set it to false, since hotkeys needs the `keydown` key to have a value.
if (keyup && keydown !== true) {
(options as Options).keydown = false;
}
Expand Down

0 comments on commit 26ddd12

Please sign in to comment.