JSPM

evnty

2.1.97
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 16088
  • Score
    100M100P100Q133094F
  • License MIT

0-Deps, simple, fast, for browser and node js reactive anonymous event library

Package Exports

  • evnty

Readme

Evnty

0-Deps, simple, fast, for browser and node js reactive anonymous event library.

Coverage Status Github Build Status NPM version Downloads Snyk

Table of Contents

Motivation

In traditional event handling in TypeScript, events are often represented as strings, and there's no easy way to apply functional transformations like filtering or mapping directly on the event data. This approach lacks type safety, and chaining operations require additional boilerplate, making the code verbose and less maintainable.

The proposed library introduces a robust Event abstraction that encapsulates event data and provides a suite of functional methods like map, filter, reduce, debounce, etc., allowing for a more declarative and type-safe approach to event handling. This design facilitates method chaining and composition, making the code more readable and maintainable. For instance, it allows developers to create new events by transforming or filtering existing ones, thus promoting code reusability and modularity.

Benefits:

  • Type Safety: The Event abstraction enforces type safety, helping catch potential errors at compile-time.
  • Method Chaining: Functional methods can be chained together to form complex event handling logic with less code.
  • Event Composition: Events can be merged, filtered, or transformed to create new events, promoting code modularity and reusability.
  • Reduced Boilerplate: The library reduces the boilerplate code required to set up complex event handling logic, making the codebase cleaner and easier to manage.

Features

  • Supports ESM and CommonJS
  • Promises support
  • Full-featured TypeScript support
  • Browser & Workers environment compatibility
  • Performance eventemitter3/eventemitter2/event-emitter/events/native node/native browser

Platform Support

NodeJS Chrome Firefox Safari Opera Edge
Latest ✔ Latest ✔ Latest ✔ Latest ✔ Latest ✔ Latest ✔

Installing

Using pnpm:

pnpm add evnty

Using yarn:

yarn add evnty

Using npm:

npm install evnty

Usage

createEvent

Creates a new event instance.

import { createEvent, Event } from 'evnty';

type ClickEvent = { x: number; y: number; button: number };
const clickEvent = createEvent<ClickEvent>();

type KeyPressEvent = { key: string };
const keyPressEvent = createEvent<KeyPressEvent>();

type ChangeEvent = { target: HTMLInputElement };
const changeEvent = createEvent<ChangeEvent>();
document.querySelector('input').addEventListener('change', changeEvent);

filter

Returns a new event that will only be triggered once the provided filter function returns true. Supports predicate type guard function and filter function as a more concise variant.

import { Predicate } from 'evnty';

type SpacePressEvent = KeyPressEvent & { key: 'Space' };
type LeftClickEvent = ClickEvent & { button: 1 };

const spacePressPredicate: Predicate<KeyPressEvent, SpacePressEvent> = (keyPressEvent): keyPressEvent is SpacePressEvent => keyPressEvent.key === 'Space';
// event type is inferred from the predicate function
const spacePressPredicatedEvent = keyPressEvent.filter(spacePredicate);
// event type is inferred from the explicitly specified type
const spacePressFilteredEvent = keyPressEvent.filter<SpacePressEvent>(({ key }) => key === 'Space');

const leftClickPredicate: Predicate<ClickEvent, LeftClickEvent> = (mouseClickEvent): mouseClickEvent is LeftClickEvent => mouseClickEvent.button === 1;
// event type is inferred from the predicate function
const leftClickPredicatedEvent = clickEvent.filter(leftClickPredicate);
// event type is inferred from the explicitly specified type
const leftClickFilteredEvent = keyPressEvent.filter<LeftClickEvent>(({ button }) => button === 1);

map

Returns a new event that maps the values of this event using the provided mapper function.

const point = { x: 100, y: 100 };
const distanceClickEvent = clickEvent.map((click) => (click.x - point.x) ** 2 + (click.y - point.y) ** 2);
const lowerCaseKeyPressEvent = keyPressEvent.map(({ key }) => key.toLowerCase());
const trimmedChangeEvent = changeEvent.map((event) => event.target.value.trim());

reduce

Returns a new event that reduces the emitted values using the provided reducer function.

const rightClickCountEvent = clickEvent.reduce((result, click) => (click.button === 2 ? result + 1 : result), 0);

debounce

Returns a new debounced event that will not fire until a certain amount of time has passed since the last time it was triggered.

const searchEvent = changeEvent.debounce(500);

merge

Merges multiple events into a single event.

import { merge } from 'evnty';

const inputEvent = merge(clickEvent, keyPressEvent);

createInterval

Creates an event that triggers at a specified interval.

import { createInterval } from 'evnty';

const everySecondEvent = createInterval(1000);

Examples

import createEvent, { Event } from 'evnty';

// Creates a click event
type Click = { button: string };
const clickEvent = createEvent<Click>();
const handleClick = ({ button }: Click) => console.log('Clicked button is', button);
const unsubscribeClick = clickEvent.on(handleClick);

// Creates a key press event
type KeyPress = { key: string };
const keyPressEvent = createEvent<KeyPress>();
const handleKeyPress = ({ key }: KeyPress) => console.log('Key pressed', key);
const unsubscribeKeyPress = keyPressEvent.on(handleKeyPress);

// Merges click and key press events into input event
type Input = Click | KeyPress;
const handleInput = (input: Input) => console.log('Input', input);;
const inputEvent = Event.merge(clickEvent, keyPressEvent);
inputEvent.on(handleInput);

// Filters a click event to only include left-click events.
const handleLeftClick = () => console.log('Left button is clicked');
const leftClickEvent = clickEvent.filter(({ button }) => button === 'left');
leftClickEvent.on(handleLeftClick);

// Will press Enter after one second
setTimeout(keyPressEvent, 1000, { key: 'Enter' });
// Waits once the first Enter key press event occurs
await keyPressEvent.first(({ key }) => key === 'Enter').onceAsync();

keyPressEvent({ key: 'W' });
keyPressEvent({ key: 'A' });
keyPressEvent({ key: 'S' });
keyPressEvent({ key: 'D' });

clickEvent({ button: 'right' });
clickEvent({ button: 'left' });
clickEvent({ button: 'middle' });

// Unsubscribe click listener
unsubscribeClick();
// It does not log anything because of click listener is unsubscribed
leftClickEvent.off(handleLeftClick);

// Unsubscribe key press listener once first Esc key press occur
unsubscribeKeyPress.after(() => keyPressEvent
  .first(({ key }) => key === 'Esc')
  .onceAsync()
);
// Press Esc to unsubscribe key press listener
keyPressEvent({ key: 'Esc' });

Migration

From v1 to v2

A breaking change has been made: events no longer accept a list of arguments. Now, each event accepts a single argument, so simply wrap your arguments in an object. This decision was taken to leverage the benefits of predicate type guards.

License

License Apache-2.0 Copyright (c) 2021-present Ivan Zakharchanka