Skip to content
This repository has been archived by the owner on Jan 25, 2023. It is now read-only.

Feat/hotkey filter #5

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions src/hotkey/README.md
Original file line number Diff line number Diff line change
@@ -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, isOutsideFormElement } 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: isOutsideFormElement,
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 <Button onClick={savePressed}>{isCtrlDown ? 'Ctrl+S' : 'Save'}</Button>;
};

const savePressed = createEvent<MouseEvent>();

sample({
clock: [savePressed, hotkey('Ctrl+S')],
target: saveFx,
});
```
8 changes: 8 additions & 0 deletions src/hotkey/filters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const FORM_TAGS = ['input', 'textarea', 'select'];

export const isOutsideFormElement = () => {
const activeTag = document.activeElement?.tagName.toLowerCase() || '';
return !FORM_TAGS.includes(activeTag);
};

export const isFormElementFocused = () => isOutsideFormElement();
5 changes: 3 additions & 2 deletions src/hotkey/hotkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ interface hotkeyT {
(params: {
key: KeyboardEvent['key'];
type?: keyof typeof keyEvents;
filter?: Store<boolean>;
filter?: Store<boolean> | ((evt: KeyboardEvent) => boolean);

target?: Target;
}): Event<KeyboardEvent>;
}
Expand All @@ -37,7 +38,7 @@ export const hotkey: hotkeyT = (...args) => {
if (normalizedParams.filter) {
keyTriggered = guard({
clock: keyTriggered,
filter: normalizedParams.filter as Store<boolean>,
filter: normalizedParams.filter,
});
}
if (normalizedParams.target) {
Expand Down
2 changes: 2 additions & 0 deletions src/hotkey/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { hotkey } from './hotkey';
import { keySequence } from './key-sequence';

export { isOutsideFormElement, isFormElementFocused } from './filters';

export { hotkey, keySequence as keyboardSequence };
36 changes: 33 additions & 3 deletions src/keys/README.md
Original file line number Diff line number Diff line change
@@ -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 <Button onClick={savePressed}>{isCtrlDown ? 'Ctrl+S' : 'Save'}</Button>;
};

const savePressed = createEvent<MouseEvent>();

sample({
clock: [savePressed, hotkey('Ctrl+S')],
target: saveFx,
});
```
18 changes: 12 additions & 6 deletions src/mouse/README.md
Original file line number Diff line number Diff line change
@@ -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';
Expand Down