npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

receptor

v2.2.1

Published

A better way to manage DOM event delegation and handling

Downloads

128,357

Readme

receptor

Yes, it's another DOM event delegation toolkit! If you're new to event delegation, here's the gist:

Event delegation is a method for listening for events that bubble to a higher level in the DOM and applying a function only if the event's target meets some criteria (typically that it matches a given CSS selector). The primary advantage over adding event listeners to specific elements is that you don't need to know what's in the DOM to listen for events from certain elements. In other words, you can modify the DOM willy-nilly without having to juggle adding and removing "direct" event listeners whenever certain elements are added and removed.

There are lots of different tools that do delegation. The technique is even baked into jQuery, which behaves "correctly" in the sense that delegated functions match CSS selectors both for the target element and its ancestors.

💥 The big difference between receptor and other tools is that it offers functional, declarative methods for "routing" events via different selectors and keyboard keys, ignoring subtrees of the DOM, adding one-off listeners, and crafting reuseable behaviors.

Installation

Node

You can install receptor as a Node module with:

npm install receptor

In CommonJS environments, you may either require the module and access its API via the top-level export:

const receptor = require('receptor');
const click = receptor.delegate('click', 'button', function(e) {
  // ...
});

...or, if you're using ES2015 and/or tree-shaking, you can import the top-level API methods directly:

import {behavior, delegateAll} from 'receptor';
// etc.

Browser

To use receptor in the browser, you can either bundle it with your CommonJS tool of choice (webpack, rollup, browserify, etc.) or grab the browser-ready build from the dist directory.

API

# receptor.delegate(selector, fn)

Returns a delegated function that only calls the fn callback if the event target matches the given CSS selector, or if it is contained by an element that does. The callback is called with the matching element as this.

Why? Because many delegation tools only handle the case in which the event's target matches the given selector, which breaks down as soon as you want to delegate to elements with children.

<button>hi</button>
<button>hi <i>there</i></button>
<script>
document.body.addEventListener(
  'click',
  receptor.delegate('button', function(event) {
    console.log('clicked button:', this);
  })
);
</script>

# receptor.delegateAll(selectors)

Returns a delegated function that treats each key in the selectors map as a CSS selector to match a la receptor.delegate(), and can either delegate events to multiple callbacks with matching selectors or short-circuit delegation to later selectors by returning false:

<button>some button</button>
<button aria-controls="other">another button</button>
<script>
document.body.addEventListener(
  'click',
  receptor.delegateAll({
    'button[aria-controls]': function(event) {
      console.log('clicked controller:', this);
      return false; // no other delegates will be called
    },
    'button': function(event) {
      console.log('clicked other button:', this);
    }
  })
);

# receptor.ignore(element, callback)

Returns a delegated function that only calls the callback if the event's target isn't contained by the provided element. This is useful for creating event handlers that only fire if the user interacts with something outside of a given UI element.

# receptor.once(callback [, options])

Returns a wrapped function that removes itself as an event listener as soon as it's called, then calls the callback function with the same arugments. If you provide listener options, those will also be passed to removeEventListener().

# receptor.keymap(keys)

Returns a delegated function in which each key in the keys object is treated as a key name or combination with modifier keys:

document.body.addEventListener('keydown', receptor.keymap({
  'ArrowLeft':        moveLeft,
  'ArrowRight':       moveRight,
  'Shift+ArrowLeft':  moveLeftMore,
  'Shift+ArrowRight': moveRightMore
}));

In other words, this is a more declarative alternative to a single event handler with potentially messy key detection logic. Supported modifier keys are Alt, Control (or Ctrl), and Shift.

# receptor.behavior(listeners [, properties])

Returns a behavior object defined by one or more delegated listeners, which exposes add() and remove() methods for attaching and removing all delegates. Other properties will be merged if provided.

Each key in listeners should be one of either:

  1. One or more event types, such as click, keydown, or mousedown touchstart (multiple types are separated by spaces), in which case the value can either be a function or an object suitable for receptor.delegateAll(); or
  2. A string in the form types:delegate(selector), where types can be one or more event types separated with spaces, and selector is a CSS selector.

This is the primary building block for developing rich, declarative interactions:

<button role="menubutton" aria-controls="menu">toggle menu</button>
<menu id="menu" aria-hidden="true">
  <!-- stuff -->
</menu>
<script>
const MenuButton = receptor.behavior({
  'click': {
    '[role=menubutton]': function(event) {
      let pressed = MenuButton.toggle(this);
      if (pressed) {
        const close = MenuButton.getCloseListener(this);
        window.addEventListener('click', close);
      }
    }
  }
}, {
  toggle: function(button, pressed) {
    if (typeof pressed !== 'boolean') {
      pressed = button.getAttribute('aria-pressed') !== 'true';
    }
    button.setAttribute('aria-pressed', pressed);
    MenuButton.getMenu(button).setAttribute('aria-hidden', !pressed);
    return pressed;
  },

  getMenu: function(button) {
    const id = button.getAttribute('aria-controls');
    return document.getElementById(id);
  },

  getCloseListener: function(button) {
    return receptor.ignore(button, receptor.once(function() {
      MenuButton.toggle(button, false);
    }));
  }
});

// enable the interaction on any [role=menubutton]
MenuButton.add(document.body);
// later...
// MenuButton.remove(document.body);
</script>