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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jonathanhefner/taproot

v0.0.1

Published

Low-budget Stimulus knock-off

Downloads

13

Readme

Taproot

A low-budget Stimulus knock-off, weighing in at 6 kB minified / 2.5 kB minified + gzipped.

The key differences are:

  • The core HTML attributes are data-controllers and data-actions, instead of data-controller and data-action. This lets you test drive Taproot and Stimulus at the same time, if desired.

  • The data-actions syntax is:

    <div data-actions="kebab-case-controller:kebab-case-method@event [...]" />

    Modifiers such as +once or +passive can be appended to the event. Likewise, the event target can be changed by appending +document or +window to the event.

  • Implicit events are not supported, but commas can be used as shorthand to bind multiple actions to the same event:

    <div data-actions="selection:all,clipboard:copy@click" />

    Likewise, commas can be used as shorthand to bind the same action to multiple events:

    <input type="text" data-actions="autocomplete:suggest@change,keyup" />
  • Namespaced data attributes on the controller element can be accessed via a controller's data property. Default values can be specified via a static defaults property on the controller. A default value that is neither a string nor undefined indicates that attribute should be deserialized with JSON.parse when reading, and serialized with JSON.stringify when writing. For example:

    class BottlesController extends Taproot.Controller {
      static defaults = { count: 99 }
    
      decrement() {
        this.data.count -= 1
      }
    }

    If the data-bottles-count attribute is not present on the controller element, this.data.count will return 99. Otherwise, it will return the value of data-bottles-count parsed by JSON.parse.

  • Controllers provide a dataFor method that returns a proxy object which is similar to the data object for any element. For example:

    class BottlesController extends Taproot.Controller {
      static defaults = { count: 99 }
    
      takeN({ currentTarget }) {
        this.data.count -= this.dataFor(currentTarget, { n: 1 }).n
      }
    }

    If the data-bottles-n attribute is not present on currentTarget, this.dataFor(currentTarget, { n: 1 }).n will return 1. Otherwise, it will return the value of data-bottles-n parsed by JSON.parse. If the defaults argument is not specified, dataFor will use the controller's static defaults.

  • The nodes and nodeSets controller properties can be used to query for nodes that have a particular namespaced attribute. For example:

    class BottlesController extends Taproot.Controller {
      static defaults = { count: 99 }
    
      countChanged({ count }) {
        const { status } = this.nodes
        if (status) status.textContent = `${count} bottles of beer on the wall.`
      }
    }

    this.nodes.status will return the first node in the controller element tree that has a data-bottles-status attribute.

  • A controller's connect and disconnect methods will be invoked only if they reflect the state of the controller element at the time of invocation. For example:

    class ItemController extends Taproot.Controller {
      connect() { /* ... */ }
      disconnect() { /* ... */ }
    
      moveToTop() {
        this.element.parentElement.prepend(this.element)
      }
    
      remove() {
        this.element.remove()
      }
    }

    moveToTop generates two DOM mutation events because prepend removes the controller element from the DOM before prepending it to parentElement. However, at the time both mutation events are processed, the controller element is no longer disconnected from the DOM, so neither disconnect nor connect will be invoked.

    On the other hand, remove permanently removes the controller element from the DOM. When its corresponding mutation event is processed, disconnect will be invoked.

  • The Taproot.register method accepts an object that maps controller descriptors to constructors. It also automatically kebab-cases the given descriptors and strips their -controller suffix. Thus, for example, the following are all equivalent:

    import * as Taproot from "@jonathanhefner/taproot"
    import { FooController, BarController } from "./foo-and-bar.js"
    
    Taproot.register({ foo: FooController, bar: BarController })
    import * as Taproot from "@jonathanhefner/taproot"
    import { FooController as foo, BarController as bar } from "./foo-and-bar.js"
    
    Taproot.register({ foo, bar })
    import * as Taproot from "@jonathanhefner/taproot"
    import { FooController, BarController } from "./foo-and-bar.js"
    
    Taproot.register({ FooController, BarController })
    import * as Taproot from "@jonathanhefner/taproot"
    import * as fooAndBar from "./foo-and-bar.js"
    
    Taproot.register(fooAndBar)

    Taproot.register can also accept a non-anonymous constructor, and use its name as the descriptor. For example:

    import { register, Controller } from "@jonathanhefner/taproot"
    
    register(class BottlesController extends Controller {
      /* ... */
    })

    will register the bottles descriptor to BottlesController.

  • The Taproot.register method also acts as a "start" method. Taproot will not start any observers or perform any initialization until after register is called. When register is called, Taproot schedules a (re-)evaluation of all data-controllers elements after the current JavaScript task. Therefore, register can be called at any time, and additional calls within the same JavaScript task do not incur additional overhead.