From b3243aa234a161847b5fde682eff6f08dc2b2dc3 Mon Sep 17 00:00:00 2001 From: Anton Kosykh Date: Fri, 29 Jul 2022 03:04:38 +0300 Subject: [PATCH 1/4] feat: Support function `filter` for hotkey --- src/hotkey/hotkey.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hotkey/hotkey.ts b/src/hotkey/hotkey.ts index 57d80c8..ea9a1b0 100644 --- a/src/hotkey/hotkey.ts +++ b/src/hotkey/hotkey.ts @@ -14,7 +14,8 @@ interface hotkeyT { (params: { key: KeyboardEvent['key']; type?: keyof typeof keyEvents; - filter?: Store; + filter?: Store | ((evt: KeyboardEvent) => boolean); + target?: Target; }): Event; } @@ -37,7 +38,7 @@ export const hotkey: hotkeyT = (...args) => { if (normalizedParams.filter) { keyTriggered = guard({ clock: keyTriggered, - filter: normalizedParams.filter as Store, + filter: normalizedParams.filter, }); } if (normalizedParams.target) { From 8adca3e509264512111bbe1b07744610613405e8 Mon Sep 17 00:00:00 2001 From: Anton Kosykh Date: Fri, 29 Jul 2022 03:04:53 +0300 Subject: [PATCH 2/4] feat: `isFormElementFocused` helper --- src/hotkey/filters.ts | 6 ++++++ src/hotkey/index.ts | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 src/hotkey/filters.ts diff --git a/src/hotkey/filters.ts b/src/hotkey/filters.ts new file mode 100644 index 0000000..fb5653d --- /dev/null +++ b/src/hotkey/filters.ts @@ -0,0 +1,6 @@ +const FORM_TAGS = ['input', 'textarea', 'select']; + +export const isFormElementFocused = () => { + const activeTag = document.activeElement?.tagName.toLowerCase() || ''; + return !FORM_TAGS.includes(activeTag); +}; diff --git a/src/hotkey/index.ts b/src/hotkey/index.ts index 43ef9b6..8330f86 100644 --- a/src/hotkey/index.ts +++ b/src/hotkey/index.ts @@ -1,4 +1,6 @@ import { hotkey } from './hotkey'; import { keySequence } from './key-sequence'; +export { isFormElementFocused } from './filters'; + export { hotkey, keySequence as keyboardSequence }; From 5412d0ce1cb88bccdcd05f549b1216a5f25f39ce Mon Sep 17 00:00:00 2001 From: Anton Kosykh Date: Fri, 29 Jul 2022 03:05:04 +0300 Subject: [PATCH 3/4] docs: More docs! --- src/hotkey/README.md | 126 +++++++++++++++++++++++++++++++++++++++++++ src/keys/README.md | 36 +++++++++++-- src/mouse/README.md | 18 ++++--- 3 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 src/hotkey/README.md diff --git a/src/hotkey/README.md b/src/hotkey/README.md new file mode 100644 index 0000000..8de4068 --- /dev/null +++ b/src/hotkey/README.md @@ -0,0 +1,126 @@ +# Hotkey + +## What is this? + +Hotkeys with Effector made easy + +- Supports both Windows/MacOS style hotkeys +- Doesn't break if using with SSR +- Key sequences (someting like Konami code?) + +## Usage + +```tsx +import { hotkey } from 'effector-receptor'; + +const copyPressed = hotkey({ key: 'Ctrl+C' }); + +sample({ + clock: copyPressed, + source: $formData, + target: saveFx, +}); +``` + +## Customization + +#### Specifying event type + +```tsx +import { hotkey } from 'effector-receptor'; + +const spaceDown = hotkey({ key: 'Space', type: 'keydown' }); +const spaceUp = hotkey({ key: 'Space', type: 'keyup' }); +const spacePress = hotkey({ key: 'Space', type: 'keypress' }); +``` + +#### Shortcut + +```tsx +import { hotkey } from 'effector-receptor'; + +const copyPressed = hotkey('Ctrl+C'); +const spaceDown = hotkey('Space', 'keydown'); +``` + +#### `filter` prop + +```tsx +import { createStore } from 'effector'; +import { hotkey, isFormElementFocused } from 'effector-receptor'; + +const $isConfirmModalOpened = createStore(true); + +hotkey({ + key: 'Y', + filter: $isConfirmModalOpened, + target: removeFx, +}); + +hotkey({ + key: 'N', + filter: $isConfirmModalOpened, + target: closeModal, +}); + +hotkey({ + key: 'Ctrl+ArrowUp', + filter: () => !isFormElementFocused(), + target: triggersOnlyOutsideTextareas, +}); +``` + +#### `target` prop + +If you want to just trigger something instead of listening to event, you can use `target` prop: + +```tsx +import { sample } from 'effector'; +import { hotkey } from 'effector-receptor'; + +hotkey({ + key: 'Ctrl+C', + target: copyTextFx, +}); +// <=> +sample({ + clock: hotkey('Ctrl+C'), + target: copyTextFx, +}); +``` + +## Extra + +#### `keyup`, `keydown`, `keypress` events + +You can use internal wrappers for native events as well + +```tsx +import { keyup, keydown, keypress } from 'effector-receptor'; + +keyup.watch(console.log); // KeyboardEvent +``` + +#### `$isShiftDown`, `$isCtrlDown`, `$isAltDown` + +Stores that track if `Shift`/`Ctrl`/`Alt` buttons are held + +Simple use-case: display hotkeys in UI while holding `Ctrl` + +```tsx +import { useStore } from 'effector-react'; +import { hotkey, $isCtrlDown } from 'effector-receptor'; + +const SubmitButton = () => { + const isCtrlDown = useStore($isCtrlDown); + + return ; +}; + +const savePressed = createEvent(); + +sample({ + clock: [savePressed, hotkey('Ctrl+S')], + target: saveFx, +}); +``` diff --git a/src/keys/README.md b/src/keys/README.md index de714d9..70a326c 100644 --- a/src/keys/README.md +++ b/src/keys/README.md @@ -1,13 +1,43 @@ # Keys +## What is this? + +Wrapped events for keyboard + ## Methods #### `keyup`, `keydown`, `keypress` +These events trigger on `document.keyup/keydown/keypress` + ```ts import { keydown, keypress, keyup } from 'effector-receptor'; -keyup.watch(console.log); -keydown.watch(console.log); -keypress.watch(console.log); +keyup.watch(console.log); // KeyboardEvent +keydown.watch(console.log); // KeyboardEvent +keypress.watch(console.log); // KeyboardEvent +``` + +### `$isShiftDown`, `$isCtrlDown`, `$isAltDown` + +These stores to track if `Shift`/`Ctrl`/`Alt` buttons are held + +Simple use-case: display hotkeys in UI while holding `Ctrl` + +```tsx +import { useStore } from 'effector-react'; +import { hotkey, $isCtrlDown } from 'effector-receptor'; + +const SubmitButton = () => { + const isCtrlDown = useStore($isCtrlDown); + + return ; +}; + +const savePressed = createEvent(); + +sample({ + clock: [savePressed, hotkey('Ctrl+S')], + target: saveFx, +}); ``` diff --git a/src/mouse/README.md b/src/mouse/README.md index 6388cbc..c50e62b 100644 --- a/src/mouse/README.md +++ b/src/mouse/README.md @@ -1,23 +1,29 @@ # Mouse +## What is this? + +Wrapped events for mouse + ## Methods #### `mouseup`, `mousedown`, `mousepress`, `click`, `mousewheel` +These events trigger on `document.mouseup/mousedown/mousepress/click/mousewheel` + ```ts import { click, mousedown, mousepress, mouseup, mousewheel } from 'effector-receptor'; -mouseup.watch(console.log); -mousedown.watch(console.log); -mousepress.watch(console.log); -click.watch(console.log); -mousewheel.watch(console.log); +mouseup.watch(console.log); // MouseEvent +mousedown.watch(console.log); // MouseEvent +mousepress.watch(console.log); // MouseEvent +click.watch(console.log); // MouseEvent +mousewheel.watch(console.log); // WheelEvent ``` #### `nonGhost` - Filters ghost clicks > **Ghost-Click** is when you perform `mousedown`, move mouse a little bit and then do `mouseup`. -> In cases such as drag-and-drop, it's important to prevent ghost clicks +> In cases such as drag-and-drop, it's important to prevent ghost clicks for a better UX ```ts import { click, createRefStore, nonGhost, onTarget } from 'effector-receptor'; From 7639be896df70f48b9e2aaf42ce74b5f50cb01eb Mon Sep 17 00:00:00 2001 From: Anton Kosykh Date: Fri, 29 Jul 2022 03:11:19 +0300 Subject: [PATCH 4/4] feat: Add `isOutsideFormElement` utility --- src/hotkey/README.md | 4 ++-- src/hotkey/filters.ts | 4 +++- src/hotkey/index.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/hotkey/README.md b/src/hotkey/README.md index 8de4068..48607c7 100644 --- a/src/hotkey/README.md +++ b/src/hotkey/README.md @@ -47,7 +47,7 @@ const spaceDown = hotkey('Space', 'keydown'); ```tsx import { createStore } from 'effector'; -import { hotkey, isFormElementFocused } from 'effector-receptor'; +import { hotkey, isOutsideFormElement } from 'effector-receptor'; const $isConfirmModalOpened = createStore(true); @@ -65,7 +65,7 @@ hotkey({ hotkey({ key: 'Ctrl+ArrowUp', - filter: () => !isFormElementFocused(), + filter: isOutsideFormElement, target: triggersOnlyOutsideTextareas, }); ``` diff --git a/src/hotkey/filters.ts b/src/hotkey/filters.ts index fb5653d..9d54fc1 100644 --- a/src/hotkey/filters.ts +++ b/src/hotkey/filters.ts @@ -1,6 +1,8 @@ const FORM_TAGS = ['input', 'textarea', 'select']; -export const isFormElementFocused = () => { +export const isOutsideFormElement = () => { const activeTag = document.activeElement?.tagName.toLowerCase() || ''; return !FORM_TAGS.includes(activeTag); }; + +export const isFormElementFocused = () => isOutsideFormElement(); diff --git a/src/hotkey/index.ts b/src/hotkey/index.ts index 8330f86..222e64f 100644 --- a/src/hotkey/index.ts +++ b/src/hotkey/index.ts @@ -1,6 +1,6 @@ import { hotkey } from './hotkey'; import { keySequence } from './key-sequence'; -export { isFormElementFocused } from './filters'; +export { isOutsideFormElement, isFormElementFocused } from './filters'; export { hotkey, keySequence as keyboardSequence };