Skip to content

Commit

Permalink
Fixed contentEditable prop handling
Browse files Browse the repository at this point in the history
  • Loading branch information
JohannesKlauss committed Mar 29, 2021
1 parent 6c9d11d commit aff3345
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 18 deletions.
4 changes: 3 additions & 1 deletion babel.config.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"plugins": ["@babel/plugin-proposal-class-properties"]
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}
31 changes: 22 additions & 9 deletions docs/useHotkeys.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ useHotkeys(keys: string, callback: (event: KeyboardEvent, handler: HotkeysEvent)
modifier combination, etc. See [this](https://github.com/jaywcjlove/hotkeys/#defining-shortcuts)
section on the hotkeys documentation for more info.
* `callback: (event: KeyboardEvent, handler: HotkeysEvent) => void`: Gets executed when the defined keystroke
gets hit by the user. **Important:** Since version 1.5.0 this callback gets memoised inside the hook. So you don't have
to do this anymore by yourself.
gets hit by the user.
* `options: Options = {}` Pass options directly to the hotkeys package. See [Options](#options) section for more details.
* `deps: any[] = []`: The dependency array that gets appended to the memoisation of the callback. Here you define the inner
dependencies of your callback. If for example your callback actions depend on a referentially unstable value or a value
Expand All @@ -39,12 +38,6 @@ learn more and see an example where you have to set this array.
This will listen to the `ctrl+k` keystroke. If you press it, the counter increments by one. If `ctrl+l` gets pressed,
the counter will decrement by one.

### Important changes since 1.5.0!
Due to the nature of how `useEffect` works and to prevent resetting the hotkeys handler during every render, before 1.5.0
you had to memoise your callback yourself. Since this is tedious work and dependency arrays are a common pattern with
React hooks, I decided to bring the memoisation inside the hook, so you don't have to deal with it. Please read the
[**Memoisation**](#memoisation) section for more info on this.

```js
import { useHotkeys } from 'react-hotkeys-hook';
```
Expand All @@ -71,7 +64,7 @@ for an overview.
* `filter: (event: KeyboardEvent): boolean` is used to filter if a callback gets triggered depending on the keyboard event.
**Breaking Change in `3.0.0`!** Prior to version `3.0.0` the filter settings was one global setting that applied to every
hook. Since `3.0.0` this behavior changed. The `filter` option is now locally scoped to each call of `useHotkeys`.
* `filterPreventDefault: boolean` is used to prevent/allow the default browser behavior for the keystroke when the filter
* `filterPreventDefault: boolean` is used to prevent/allow the default browser behavior for the keystroke when the filter
return false (default value: `true`)
* `enableOnTags: string[]` is used to enable listening to hotkeys in form fields. Available values are `INPUT`, `TEXTAREA`
and `SELECT`.
Expand Down Expand Up @@ -139,3 +132,23 @@ approach for `setAmount` like in the very first example:
);
}}
</Playground>

## `contentEditable` prop

As of version 3.3 the `contentEditable` prop does work as intended. The hook will not trigger any hotkeys while the user
edits the field content.

<Playground>
{() => {
const [amount, setAmount] = useState(0);
useHotkeys('f', () => setAmount(prevAmount => prevAmount + 100));
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>
3 changes: 2 additions & 1 deletion doczrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import pkg from './package.json';
export default {
title: 'React Hotkeys Hook',
description: pkg.description,
base: `/${pkg.name}/`,
base: `/${pkg.name}`,
version: pkg.version,
propsParser: false,
hashRouter: true,
typescript: true,
themeConfig: {
initialColorMode: 'dark',
},
ignore: ['README.md', 'CHANGELOG.md']
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
]
},
"jest": {
"setupFilesAfterEnv": ["./setupTests.js"],
"testPathIgnorePatterns": [
"pkg",
".docz",
Expand Down
1 change: 1 addition & 0 deletions setupTests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'regenerator-runtime/runtime';
20 changes: 13 additions & 7 deletions src/useHotkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ type AvailableTags = 'INPUT' | 'TEXTAREA' | 'SELECT';
// We implement our own custom filter system.
hotkeys.filter = () => true;

const tagFilter = ({ target, srcElement }: KeyboardEvent, enableOnTags?: AvailableTags[]) => {
// @ts-ignore
const targetTagName = (target && target.tagName) || (srcElement && srcElement.tagName);
const tagFilter = ({ target }: KeyboardEvent, enableOnTags?: AvailableTags[]) => {
const targetTagName = target && (target as HTMLElement).tagName;

return Boolean(targetTagName && enableOnTags && enableOnTags.includes(targetTagName as AvailableTags));
return Boolean((targetTagName && enableOnTags && enableOnTags.includes(targetTagName as AvailableTags)));
};

const isKeyboardEventTriggeredByInput = (ev: KeyboardEvent) => {
Expand All @@ -37,15 +36,22 @@ export function useHotkeys<T extends Element>(keys: string, callback: KeyHandler
options = undefined;
}

const { enableOnTags, filter, keyup, keydown, filterPreventDefault = true, enabled = true } = options as Options || {};
const {
enableOnTags,
filter,
keyup,
keydown,
filterPreventDefault = true,
enabled = true,
} = options as Options || {};
const ref = useRef<T | null>(null);

const memoisedCallback = useCallback((keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => {
if (filter && !filter(keyboardEvent)) {
return !filterPreventDefault;
}

if (isKeyboardEventTriggeredByInput(keyboardEvent) && !tagFilter(keyboardEvent, enableOnTags)) {
if (isKeyboardEventTriggeredByInput(keyboardEvent) && !tagFilter(keyboardEvent, enableOnTags) || (keyboardEvent.target as HTMLElement)?.isContentEditable) {
return true;
}

Expand All @@ -59,7 +65,7 @@ export function useHotkeys<T extends Element>(keys: string, callback: KeyHandler

useEffect(() => {
if (!enabled) {
return
return;
}

if (keyup && keydown !== true) {
Expand Down

0 comments on commit aff3345

Please sign in to comment.