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 🙏

© 2025 – Pkg Stats / Ryan Hefner

attach-this

v0.0.4

Published

A collection of ergonomic everyday helpers for Svelte 5. ✨

Readme

Attach This

A collection of ergonomic everyday helpers for Svelte 5. ✨

Installation

npm install attach-this

Philosophy

These lightweight, easy to use helpers combine spreadable event handlers and attachments with reactive stores. Each one follows the same pattern and can be:

  • Spread onto elements: <div {...helper}>
  • Subscribed to as Svelte stores: $helper

The store properties are added using Object.defineProperty, which means that spread will not enumerate over them so they don't get added to elements; only the event listeners and the attachments do.

Movable

Easily makes elements movable and tracks z-indexes to keep recent items on top. You can spread the event listeners and attachment onto the element with {...movable} and then access the current state with $movable.

Usage

<script>
   import { movable } from 'attach-this'
</script>

<div {...movable}>
   <!-- $movable is either null or { activeElement, x, y } -->
</div>

Hoverable

Tracks which element is currently being hovered and automatically applies a hovering CSS class. Also prevents parents of hovered children from also being in a hover state.

Usage

<script>
   import { hoverable } from 'attach-this'
</script>

<div {...hoverable}>
   <!-- $hoverable contains the currently hovered element or null -->
</div>

<style>
   .hovering {
      background-color: #f0f0f0;
      transform: scale(1.02);
   }
</style>

Filter

Filter must be created by passing the array you wish to filter to createFilter. The array should contain objects with string keys.

Filters a given array using inputs whose name match the array elements' keys. First, create the store with createFilter and then spread it onto inputs, and access the filtered array with $ syntax.

Usage

<script>
   import { createFilter } from 'attach-this'

   const records = [
      { name: "Alice", age: 32 },
      { name: "Bob", age: 40 },
      { name: "Charlie", age: 18 },
      { name: "Dana", age: 21 },
      { name: "Eddie", age: 27 }
   ];

   const filter = createFilter(records)
</script>

<input {...filter} name="name" type="text" placeholder="Filter by name" />
<input {...filter} name="age" type="number" placeholder="Filter by age" />

<ul>
   {#each $filter as record}
      <li>{record.name} is {record.age}</li>
   {/each}
</ul>

Validate

Validates any StandardSchemaV1 schema.

Usage

<script>
   import { validate } from "attach-this"
   import { z } from 'zod' // any StandardSchemaV1 will do!

   const personSchema = z.object({
      name: z.string().min(1, "Name cannot be empty").trim(),
      age: z.number()
         .int()
         .min(0, "Age cannot be negative")
         .max(150, "Age cannot exceed 150")
   })

   const data = $state({
      name: '',
      age: 0
   })

   const validator = validate(personSchema)
</script>

<form {@attach validator(data)} {onsubmit}>
   <!-- inputs must be wrapped in divs because
        <input> can't have pseudo-elements -->
   <div>
      <label for="name">Name</label>
      <input name="name" type="text" bind:value={data.name} />
   </div>
   <div>
      <label for="age">Age</label>
      <input name="age" type="number" bind:value={data.age} />
   </div>
   <button type="submit">Add Person</button>
</form>

<style>
   /* access error messages on pseudo-elements */
   div[data-error]::before, div[data-error]::after {
      content: attr(data-error);
   }

   /*
   you may need to wrap those selectors
   with :global() since they won't be
   present at compile time.

   Alternatively, put the css in an external file.
   */
</style>

Replace

Dynamic text replacement system that can swap out content based on lookup tables and locales. Can replace by letter, word or text node.

Usage

<script>
   import { createReplaceStore } from 'attach-this'

   const lookupTable = {
      en: {
         welcome_text: "Welcome",
         greeting_message: "How are you today?",
      },
      fr: {
         welcome_text: "Bienvenue",
         greeting_message: "Comment allez-vous aujourd'hui?",
      }
   }

   const replace = createReplaceStore(lookupTable)

   $replace = 'en' // Set current locale
</script>

<replace.text>
   <h2>welcome_text</h2>
   <p>greeting_message</p>
</replace.text>

<!-- Force specific locale -->
<replace.text fr>
   <h2>welcome_text</h2>
   <p>greeting_message</p>
</replace.text>