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

windease

v0.6.0

Published

Browser window/zone/workspace model with pluggable layout strategies, opt-in drag-and-drop, snapshot persistence — headless core and React bindings in one package.

Readme

windease

Browser-based window manager. One package, two entry points: a framework-agnostic core (windease) and React bindings (windease/react).

npm install windease

React bindings peer-depend on react@^19 (declared optional — install only if you import from windease/react).

Playground: every strategy and DnD path lives in the Ladle playground at https://orochi235.github.io/windease/.

API reference: TypeDoc-generated reference at https://orochi235.github.io/windease/api/.

See docs/concepts.md for the canonical vocabulary (what's a window vs. zone vs. workspace, which of the four state buckets owns what, and how reserved keys like pinned / locked interact with layout and DnD).

  • Node + capabilities, not classes. Every node optionally carries container / slot / focus / lifecycle. The core enforces only structural invariants (no cycles, single focus, bidirectional links). Panel / Group / Zone are convention names with shipped presets, not built-in types.
  • Recursive containers — any node with a container capability hosts children, and a child may itself be a container. "Tray inside a window" is just a panel whose container is set.
  • Universal lifecycle. Every node carries an FSM (mounted → visible ↔ hidden → destroyed); panels additionally carry transit (atomic moves) and focus (single-focus invariant).
  • Record replacement. Every store mutation produces a fresh Node reference; React's useSyncExternalStore invalidates correctly by default.
  • JSON-safe snapshots via serialize(store) / deserialize(snap).
  • Layout strategies are pure functions. Built-ins: gridStrategy, stackStrategy, stripStrategy, splitStrategy (binary by default, recursive when recursive: true in config). Strategies work unchanged on recursive trees via the LayoutNode adapter.

Quick start

npm install windease
import { gridStrategy } from 'windease';
import {
  Provider,
  StrategyRegistryProvider,
  Zone,
  Panel,
} from 'windease/react';

export function App() {
  return (
    <Provider>
      <StrategyRegistryProvider strategies={{ grid: gridStrategy }}>
        <Zone
          id="root"
          strategyId="grid"
          config={{ cols: 2 }}
          viewport={{ w: 720, h: 480 }}
        >
          <Panel id="a" meta={{ title: 'A' }} />
          <Panel id="b" meta={{ title: 'B' }} order={10} />
        </Zone>
      </StrategyRegistryProvider>
    </Provider>
  );
}

<Panel> / <Group> / <Zone> register themselves with the underlying store on mount and unregister on unmount. JSX is the source of truth for the shape of the tree.

Import the baseline stylesheet once at the top of your app:

import 'windease/styles.css';

It supplies the structural rules .windease-zone, .windease-window, and the insertion-line affordance default. All visual styling is yours.

Imperative API (advanced / dynamic trees)

For server-loaded layouts, programmatically generated nodes, or anything that can't be expressed as static JSX, use the store directly:

import { Store, createPanel, asNodeId } from 'windease';

const store = new Store();
store.registerNode(createPanel({ id: asNodeId('p1'), parentId: asNodeId('root') }));

<Provider store={store}>{/* ... */}</Provider>

Imperative and declarative nodes coexist under the same parent. JSX-owned ids reconcile their props on every render; imperative ids retain whatever the caller set. See docs/concepts.md for the ownership model.

State machines

Every node carries up to three FSMs, all defined in src/machines/ and run through a tiny Machine<State, Event> runtime in src/fsm.ts. Snapshots serialize the current state name; deserialize rebuilds a fresh machine in that state.

Lifecycle (every node). Drives node.lifecycle.state. show / hide are idempotent on their target state; destroy is terminal.

stateDiagram-v2
    [*] --> mounted
    mounted --> visible: show
    mounted --> destroyed: destroy
    visible --> hidden: hide
    hidden --> visible: show
    visible --> destroyed: destroy
    hidden --> destroyed: destroy
    destroyed --> [*]

Transit (slotted nodes during moveNode). Provides an atomic release-then-claim envelope around reparenting so transition listeners can stage CSS/animation around the move. settle returns to idle.

stateDiagram-v2
    [*] --> idle
    idle --> claiming: beginClaim
    idle --> releasing: beginRelease
    claiming --> idle: settle
    releasing --> idle: settle

Focus (nodes that opt into the focus capability). Enforces the single-focus invariant per store: focusing one node automatically blurs the previous focus holder.

stateDiagram-v2
    [*] --> blurred
    blurred --> focused: focus
    focused --> blurred: blur

Drag and drop

DnD is opt-in. Wrap your panel chrome in <DragHandle>, register each container as a drop target with useDropTarget(zoneId, ref), and put the tree under <DragProvider>. The drag controller honors:

  • slot.placement.locked — per-child drag suppression.
  • container.allowsDragOut — zone-level drag suppression.
  • container.allowsDrop — zone-level drop refusal.
  • The destination strategy's canAccept(prospective-items, options) — e.g. splitStrategy with recursive: false refuses anything that wouldn't leave exactly two children.
  • An optional consumer-supplied canAccept(sourceId) on the drop target.

See the Parallel zones / Drag between story for the canonical setup.

Resize

Pass affordances to <Container> to render the strategy's interactive gutters. splitStrategy ships draggable gutters out of the box (binary by default; pass recursive: true for arbitrary trees). State persists on node.container.state and survives snapshot/hydrate. Per-child hints.minSize is honored as a pixel floor so manual gutter drags can't push a panel below its declared minimum, and hints.maxSize as a ceiling so a pane can't grow past it (on initial layout, explicit sizing, and gutter drags). The default 4px gutter ships with an 8px-wide hit area (affordanceHitPad).

To pin a pane to a fixed pixel extent along the split's axis, set placement.size via store.patchPlacement(id, { size: { w } }) (or { h }); the sibling auto-takes the remainder, and a gutter drag clears it, reverting that pane to ratio control. Combine with hints.maxSize for an "auto up to a cap" pane.

Develop

npm install
npm test
npm run build
npm run lint
npm run ladle    # opens the playground at http://localhost:61000/

Design / planning docs live under docs/superpowers/. Canonical reference: docs/concepts.md.