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

@expansion-kit/svelte-engine

v0.1.0

Published

Reactive expansion state for Svelte 5 — typed Map<key, mode> registry (open/collapsed/closed) with optional injectable-storage persistence

Readme

@expansion-kit/core

Reactive expansion state for Svelte 5. A thin Map<key, mode> registry for anything that opens, closes, or collapses — sidebar accordions, collapsible cards, and drawers — with optional persistence to localStorage (or any storage you inject).

  • Three modes, not just a boolean — 'open' | 'collapsed' | 'closed', so a drawer can be a full panel, an icon-only rail, or fully hidden.
  • A simple boolean API on top — isOpen/open/close/toggle for the common two-state case, so you don't have to think about modes at all if you don't need to.
  • SSR-safe — the class can be constructed before window exists; storage is only touched once you call initialize().
  • Zero required dependencies beyond Svelte — storage and encryption are both injectable interfaces, not hard dependencies.

Install

pnpm add @expansion-kit/core

svelte ^5.0.0 is a peer dependency.

Quick start (in-memory, no persistence)

import { ExpansionController } from '@expansion-kit/core';

const nav = new ExpansionController();

nav.isOpen('workspace'); // true (default fallback)
nav.close('workspace');
nav.isOpen('workspace'); // false

nav.toggle('workspace'); // → open

Used directly in a .svelte file, nav.state is a SvelteMap, so reads of isOpen() / getMode() inside a template or $derived are reactive.

<script lang="ts">
  import { ExpansionController } from '@expansion-kit/core';

  const nav = new ExpansionController({ defaultKeys: ['workspace'] });
</script>

<button onclick={() => nav.toggle('workspace')}>
  {nav.isOpen('workspace') ? 'Collapse' : 'Expand'}
</button>

Persisting across reloads

Persistence only activates once you call initialize() — this is what makes the controller safe to construct during SSR, before localStorage exists. Call it once, client-side, e.g. in onMount:

import { onMount } from 'svelte';
import { ExpansionController } from '@expansion-kit/core';

const nav = new ExpansionController({
  cacheKey: 'nav-state',
  defaultKeys: ['workspace'], // open on a genuine first visit, before any cache exists
});

onMount(() => {
  nav.initialize(); // attaches localStorage and hydrates any saved state
});

Before initialize() runs, the controller works entirely in-memory: defaultKeys are seeded synchronously in the constructor (safe during SSR), and any mutations you make are simply not persisted yet. Once initialize() runs, a valid saved cache fully replaces the constructor's defaultKeys — the user's last-known layout wins.

Injecting a custom storage backend

initialize() accepts any object matching the tiny ExpansionStorage interface (getItem/setItem/removeItem) — useful for sessionStorage, a cookie-backed adapter, or an in-memory store in tests.

nav.initialize({ storage: sessionStorage });

The three-state mode API (drawers, rails, etc.)

For anything beyond simple open/closed — e.g. a drawer that can also be an icon-only rail — use the mode API directly:

const drawer = new ExpansionController({ cacheKey: 'drawer' });

drawer.collapse('sidebar'); // icon-only rail
drawer.getMode('sidebar'); // 'collapsed'
drawer.isOpen('sidebar'); // false — only 'open' reads as true

drawer.setMode('sidebar', 'open'); // full panel

isOpen() treats 'collapsed' the same as 'closed' — it's true only for 'open'. If your UI needs to branch on all three states, read getMode() instead of isOpen().

API reference

new ExpansionController<TKey>(config?)

| Option | Type | Default | Description | | ------------- | ------------------ | ------- | ------------------------------------------------------------------- | | cacheKey | string | — | Storage key. Omit to keep state in-memory only. | | defaultKeys | TKey[] | — | Keys marked 'open' on first visit, before any valid cache exists. | | encrypt | ExpansionEncrypt | — | Optional encode/decode hook applied to the persisted JSON string. |

.initialize(config?)

| Option | Type | Default | | --------- | ------------------ | --------------------------------------- | | storage | ExpansionStorage | localStorage if available, else no-op |

Attaches storage and hydrates any persisted state. Call once, client-side. Safe to call again if you need to swap storage backends at runtime.

Query

| Method | Returns | | ----------------------------------- | --------------------------------------------------------------- | | isOpen(key, fallback = true) | boolean — true only if the mode is 'open' | | getMode(key, fallback = 'closed') | ExpansionMode — the explicit mode, or fallback if untracked | | has(key) | boolean — whether key has any explicit mode | | count | number — count of keys explicitly set to 'open' (reactive) |

Mutation

| Method | Effect | | ------------------------------ | ------------------------------------------------------------- | | open(key) | Set mode to 'open' | | close(key) | Set mode to 'closed' | | collapse(key) | Set mode to 'collapsed' | | setMode(key, mode) | Set an explicit ExpansionMode | | toggle(key, fallback = true) | Flip between 'open' and 'closed' (ignores 'collapsed') | | openMany(keys) | Set multiple keys to 'open' in one persist call | | closeAll() | Set every currently tracked key to 'closed' | | reset(key) | Remove the explicit mode for one key — fallback governs again | | clear() | Remove all explicit modes |

Cache format and migration

Persisted as a JSON array of [key, mode] tuples:

[
  ["workspace", "open"],
  ["sidebar", "collapsed"],
  ["growth", "closed"]
]

If you're upgrading from an older boolean-based version of this package, the legacy [key, boolean][] format is migrated transparently on read — true becomes 'open', false becomes 'closed'. No manual cache clearing is needed. A corrupted or unparseable cache entry is discarded (and the stored key removed) instead of throwing.

Encryption

ExpansionEncrypt (encode/decode) is an optional pass-through hook applied to the persisted JSON string before it's written to storage, and after it's read back. This package ships no cryptography — it's a thin, pluggable primitive, and encoding a client-side cache doesn't protect against anything a user with access to their own browser can't already see. It exists for cases like obscuring the raw JSON shape from casual inspection, or reusing an encode/decode pair you already have.

If you need real encryption, plug in your own implementation (e.g. Web Crypto) or build a small adapter package on top of this one:

import { ExpansionController, type ExpansionEncrypt } from '@expansion-kit/core';

const encrypt: ExpansionEncrypt = {
  encode: (json) => myEncode(json),
  decode: (value) => myDecode(value),
};

const nav = new ExpansionController({ cacheKey: 'nav', encrypt });

License

MIT