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

@get-set/gs-tree

v1.0.2

Published

Get-Set Tree — a dual-target (vanilla / jQuery / HTMLElement.prototype / React) treeview with expand-collapse, tri-state checkboxes, selection, keyboard navigation, lazy loading, drag-reorder, filtering and theming.

Readme

GSTree

A dependency-free, feature-rich treeview available in two flavours from one codebase:

  • Native / vanilla JS — a window.GSTree(selector, params) factory plus a window.GSTreeConfigue instance registry. jQuery and HTMLElement.prototype adapters are wired automatically.
  • React — a <GSTree /> component with an imperative ref handle.

Both share the exact same engine (actions/, constants/, helpers/, types/), so behaviour is identical across the two.

Features

  • Four ways to use it — vanilla window.GSTree, jQuery $(...).GSTree(), element.GSTree(), and the React <GSTree> component.
  • Full editing — add, remove, rename (inline, on double-click / F2), duplicate, and reorder nodes — via hover action buttons (nodeActions), the keyboard, or a complete imperative CRUD API (addNode / removeNode / renameNode / updateNode / duplicateNode / moveNode / getNodes). Every edit fires onChange with the new tree.
  • 8 visual variantsdefault, bordered (panel), filled (soft row fills), pills (fully-rounded rows, gradient accent selection), cards (each root branch is an elevated card that lifts on hover), glass (blurred glassmorphism), minimal (accent-bar selection, zero chrome), and rail (app sidebar / nav with accent-tinted selection).
  • 4 twisty styles — stroked chevron (default), solid caret, plusminus (morphs + → −), and arrow.
  • Colored icon tilesiconTiles wraps every icon in a rounded, tinted tile (the Notion / macOS / Linear look), tinted by a per-node color. Plus built-in defaultIcons: 'folder' (open/closed folders + file sheets) or 'dot' bullets. A node's own icon always wins.
  • Badges, subtitles, counts & meta — per-node badge (+ badgeColor), a muted subtitle second line, a right-aligned meta (size / time / count), and showCount child-count badges on parents.
  • Data or markup — build the tree from a nodes array, or (native) let it enhance existing nested <ul>/<li> markup with data-* attributes.
  • Smooth expand/collapse — height animation via the grid-template-rows: 0fr → 1fr technique (no px→auto snap, no transform that would break sticky), with tunable duration/easing and prefers-reduced-motion support. Optional accordion mode keeps one branch open at a time.
  • Staggered child entrance — when a branch opens (or lazy children arrive), its children play a fade / slide / scale entrance with a per-row stagger — only on the branch that just opened, never on unrelated re-renders.
  • Tri-state checkboxes — parent/child cascade with an indeterminate (half-checked) state that recomputes as you go; disabled nodes never block a parent from being "fully checked". checkStrictly turns cascading off (independent checkboxes).
  • Selectionsingle (default), multiple (Ctrl/⌘-toggle and Shift-range), or false.
  • Lazy loading — a node's children may be an async loader (node, ctx) => Promise<GSTreeNode[]>; a spinner shows while it resolves and results are cached. A checked parent cascades onto freshly-loaded children.
  • Full keyboard navigation — WAI-ARIA tree pattern: Up/Down, Left/Right to collapse/expand or step to parent/first child (RTL-aware), Home/End, Enter to activate, Space to toggle a checkbox, * to expand all siblings, and type-ahead. Roving tabindex keeps exactly one node tabbable.
  • Sortable-grade drag-reorder — the same tactile engine feel as GSSortable: a floating ghost clone that follows the cursor with a lift (shadow + tilt), a dimmed source row, a polished drop indicator (line / pill / glow) for before / after / inside, a drop-zone highlight, edge auto-scroll, a FLIP drop-settle, and optional drag handles (bar / dots / grip / lines). Per-node draggable override + an onMove veto hook.
  • FilteringsetFilter(q) hides non-matching nodes, keeps + auto-expands matching ancestors, highlights the match with <mark>, and can show an emptyText message when nothing matches.
  • Theminglight / dark / auto (follows OS), a custom accentColor token, sm/md/lg size presets, configurable indent, connector guides (showLines draws ancestor rails + elbow stubs), and per-node icon.
  • RTL — right-to-left layout with mirrored twisties and inverted arrow-key semantics.
  • Accessibilityrole="tree" / role="treeitem" / role="group", aria-expanded / aria-selected / aria-checked (mixed for indeterminate) / aria-level / aria-setsize / aria-posinset / aria-disabled, aria-multiselectable, and roving focus.
  • React-only gsx prop — scoped, per-instance inline styles injected into <head> and cleaned up on unmount.

Installation

npm i @get-set/gs-tree

Compatibility

| Target | Requirement | |---|---| | React (the <GSTree> component) | React 16.8+ (Hooks are required), and 17 / 18 / 19. React is an optional peer dependency — you only need it for the component. | | Native / vanilla (window.GSTree) | No framework. Any modern evergreen browser. | | jQuery | Optional — the $.fn.GSTree adapter is registered only when window.jQuery is present when the bundle loads. | | TypeScript | First-class — type declarations (.d.ts) ship in the package. | | SSR / Next.js | Safe to import server-side (no DOM access at module load). Render the component inside a Client Component ('use client'). |

The peer range is ^16.8.0 || ^17.0 || ^18.0 || ^19.0, with react / react-dom marked optional so the native build has zero peer dependencies.

Package entry points

From package.json:

| Field | Value | |---|---| | name | @get-set/gs-tree | | main | dist/components/GSTree.js | | module | dist/components/GSTree.js | | types | dist/components/GSTree.d.ts | | exports['.'].import | ./dist/components/GSTree.js (React/ESM) | | exports['.'].require | ./dist-js/bundle.js (native window-global bundle) | | files | dist, dist-js, styles, example.html |

Project layout

GSTree.ts             # native entry (webpack -> dist-js/bundle.js, window global + adapters)
components/GSTree.tsx # React component (tsc -> dist/, npm entry)
actions/              # native lifecycle (init/refresh/destroy/getCurrentParams/animate)
constants/            # defaultParams
helpers/              # model, check, keyboard, layout, parse, render, tree-ops, uihelpers
types/                # Params / Ref / Window augmentation
components/styles/     # SCSS + compiled CSS + CSS-as-TS (runtime injection for React)
styles/               # SCSS + compiled CSS (for <link> use by the native build)

Build

npm install
npm run build          # builds both targets
npm run build:js       # native bundle -> dist-js/bundle.js
npm run build:react    # React + types  -> dist/

Tests

Unit tests use Vitest + jsdom:

npm test         # run once
npm run test:watch

Coverage spans the pure logic (param resolution, the model walk/flatten/filter helpers, the tri-state check conform/toggleCheck cascade, keyboard action resolution, immutable moveNode reorder) and the native DOM target (factory, registry, markup enhance, expand/collapse, selection, checkbox cascade, keyboard nav). Note: jsdom has no layout engine, so drag geometry is validated via the pure dropPositionFromOffset / moveNode helpers rather than rendered pixels.


Usage — 1. Native / vanilla JS

Load the bundle and call the factory with a selector or an element. The native bundle injects its own stylesheet, so the <link> is optional (include it if you prefer to control the CSS yourself):

<link rel="stylesheet" href="node_modules/@get-set/gs-tree/styles/GSTree.css" />
<script src="node_modules/@get-set/gs-tree/dist-js/bundle.js"></script>

<div id="tree"></div>

<script>
  const tree = window.GSTree('#tree', {
    reference: 'demo',
    theme: 'dark',
    checkable: true,
    selectable: 'single',
    defaultExpanded: ['src'],
    onSelect: (keys, node) => console.log('selected', keys, node),
    onCheck: (checked, info) => console.log('checked', checked, info.halfCheckedKeys),
    nodes: [
      {
        id: 'src', label: 'src', icon: '📁',
        children: [
          { id: 'index.ts', label: 'index.ts', icon: '📄' },
          { id: 'app.ts', label: 'app.ts', icon: '📄' }
        ]
      },
      { id: 'readme', label: 'README.md', icon: '📄' }
    ]
  });

  tree.expandAll();
  tree.setFilter('app');
</script>

The factory accepts either a CSS selector string or a raw HTMLElement (the jQuery + prototype adapters both pass a raw element here). It returns the instance Ref, or undefined if the element was not found or the reference is already taken.

Enhancing existing markup

With no nodes param, the native target reads nested <ul>/<li> markup. Each <li> becomes a node; a nested <ul> becomes its children. data-* attributes map to node fields (data-key/data-id, data-icon (HTML), data-expanded, data-selected, data-checked, data-disabled, data-leaf, data-draggable):

<div id="tree">
  <ul>
    <li data-key="src" data-expanded="true">src
      <ul>
        <li data-key="index" data-leaf="true">index.ts</li>
      </ul>
    </li>
    <li data-key="readme" data-leaf="true">README.md</li>
  </ul>
</div>
<script>window.GSTree('#tree', { theme: 'dark' });</script>

The instance registry — window.GSTreeConfigue

Every instance registers itself under its reference (a supplied string or an auto-generated UUID). Look one up later without keeping the return value around:

const tree = window.GSTreeConfigue.instance('demo');
tree.expand('src');
tree.setChecked(['index.ts']);
console.log(tree.getChecked(), tree.getSelected());
tree.destroy();            // tears down + unregisters

Usage — 2. jQuery

When window.jQuery is present as the bundle loads, a $.fn.GSTree adapter is registered. It constructs one instance per matched element:

$('#tree').GSTree({ checkable: true, nodes: [/* … */] });

Usage — 3. HTMLElement.prototype

The bundle also augments HTMLElement.prototype, so any element can build itself:

document.getElementById('tree').GSTree({ selectable: 'multiple', nodes: [/* … */] });

Usage — 4. React

'use client';
import { useRef } from 'react';
import GSTree, { GSTreeHandle } from '@get-set/gs-tree';

export default function Demo() {
  const ref = useRef<GSTreeHandle>(null);

  return (
    <>
      <button onClick={() => ref.current?.expandAll()}>Expand all</button>
      <input onChange={(e) => ref.current?.setFilter(e.target.value)} placeholder="Filter…" />

      <GSTree
        ref={ref}
        theme="auto"
        checkable
        selectable="multiple"
        draggable
        defaultExpandAll
        gsx={{ '.gs-tree-row-selected': { fontWeight: 600 } }}
        onSelect={(keys, node) => console.log('selected', keys, node)}
        onCheck={(checked, info) => console.log('checked', checked, info.halfCheckedKeys)}
        onMove={(dragKey, dropKey, pos) => console.log('move', dragKey, pos, dropKey)}
        nodes={[
          { id: 'a', label: 'Folder A', children: [{ id: 'a1', label: 'File A1' }] },
          { id: 'b', label: 'Folder B', children: [{ id: 'b1', label: 'File B1' }] }
        ]}
      />
    </>
  );
}

The component is forwardRef; unknown DOM props (id, data-*, aria-*, event handlers…) are forwarded to the tree root, while plugin props are stripped so they never leak onto the DOM node.


Node shape — GSTreeNode

| Field | Type | Notes | |---|---|---| | id | string \| number | Stable key. key is an accepted alias. | | label | string (native) · ReactNode (React) | Visible text. Filtering / type-ahead use its plain-text form. | | icon | string (HTML) · ReactNode (React) | Leading icon. A string is rendered as HTML/emoji. | | children | GSTreeNode[] · (node, ctx) => Promise<GSTreeNode[]> | Static children or an async lazy loader. | | isLeaf | boolean | Force a leaf (no twisty, never loads). | | expanded / selected / checked | boolean | Uncontrolled initial per-node state. | | disabled | boolean | Non-interactive, dimmed; never toggled by a cascade. | | checkable / draggable | boolean | Per-node override of the tree-level flag. | | className | string | Extra class on the node row. | | badge | string \| number | Trailing badge chip (a count, a status, "new"…). | | badgeColor | string | Custom badge background (text goes white). | | subtitle | string | Muted second line under the label. | | meta | string | Right-aligned muted meta text (a size, a time…). | | color | string | Per-node accent — tints the icon tile / dot + --gstree-node-color. | | data | unknown | Arbitrary payload echoed back in callbacks. |

Params

Native params and React props share these names (React additionally accepts gsx and standard DOM attributes).

| Param | Type | Default | Description | |---|---|---|---| | nodes | GSTreeNode[] | [] | The tree data. | | reference | string | auto UUID | Registry key. | | expandedKeys | GSTreeKey[] | — | Controlled expanded set. | | defaultExpanded | GSTreeKey[] | — | Uncontrolled initial expanded set. | | defaultExpandAll | boolean | false | Expand every parent on mount. | | accordion | boolean | false | Only one branch open per level. | | checkable | boolean | false | Show checkboxes. | | checkedKeys | GSTreeKey[] | — | Controlled checked set. | | defaultChecked | GSTreeKey[] | — | Uncontrolled initial checked set. | | checkStrictly | boolean | false | Independent checkboxes (no cascade). | | selectable | false \| 'single' \| 'multiple' | 'single' | Selection mode. | | selectedKeys | GSTreeKey[] | — | Controlled selected set. | | defaultSelected | GSTreeKey[] | — | Uncontrolled initial selected set. | | draggable | boolean | false | Enable drag-reorder. | | dragHandle | false \| true \| 'bar' \| 'dots' \| 'grip' \| 'lines' | false | Restrict dragging to a rendered grip handle. | | cancel | string | undefined | CSS selector; a pointer-down on a matching element (or its descendants) in a node does not start a drag — for custom interactive content (buttons/links/inputs) inside node labels. The built-in twisty/checkbox/row-actions/rename-input are already excluded. Works for whole-row + dragHandle modes, mouse + touch. Example: 'button, input, a, [data-no-drag]'. | | dragGhost | boolean | true | Floating clone that follows the cursor. | | lift | boolean | true | Lift the ghost (shadow + slight scale/tilt). | | dropIndicator | 'line' \| 'pill' \| 'glow' | 'pill' | Drop-position indicator style. | | dropZoneHighlight | boolean | true | Highlight the tree while dragging. | | autoScroll | boolean | true | Auto-scroll near the tree's top/bottom edge. | | dragClass / ghostClass | string | — | Extra classes on the grabbed row / ghost + indicator. | | sortAnimation | number | 200 | Drop-settle (FLIP) reflow duration (ms). | | sortEasing | string | — | Easing for the drop-settle reflow. | | filter | string | '' | Filter query. | | animation | boolean | true | Expand/collapse height slide. | | animationDuration | number | 200 | Slide duration (ms). | | animationEasing | string | cubic-bezier(0.16,1,0.3,1) | Slide easing. | | reducedMotion | 'auto' \| boolean | 'auto' | Honor prefers-reduced-motion. | | variant | 'default' \| 'bordered' \| 'filled' \| 'pills' \| 'cards' \| 'glass' \| 'minimal' \| 'rail' | 'default' | Visual variant (row/container skin). | | twisty | 'chevron' \| 'caret' \| 'plusminus' \| 'arrow' | 'chevron' | Expander glyph style. | | defaultIcons | false \| 'folder' \| 'dot' | false | Built-in icons for nodes without an icon. | | iconTiles | boolean | false | Wrap icons in rounded tinted tiles (per-node color). | | childAnimation | 'fade' \| 'slide' \| 'scale' \| false | 'fade' | Staggered child entrance on expand. | | showCount | boolean | false | Child-count badge on parents. | | emptyText | string | — | Message when no nodes are visible. | | editable | boolean | false | Inline-rename on double-click / F2. | | nodeActions | boolean \| GSTreeAction[] | false | Hover action buttons (true = add + rename + delete). | | addNodeLabel | string | 'New node' | Label for a node created by the built-in "add" action. | | confirmDelete | boolean | false | window.confirm before the built-in delete. | | theme | 'light' \| 'dark' \| 'auto' | 'auto' | Visual theme. | | accentColor | string | — | --gstree-accent token. | | rtl | boolean | false | Right-to-left layout. | | size | 'sm' \| 'md' \| 'lg' | 'md' | Row size preset. | | indent | number | 20 | Indent per depth level (px). | | showLines | boolean | false | Draw connector lines. | | showIcons | boolean | true | Show per-node icons. | | className | string | — | Extra class on the tree root. |

Callbacks

| Callback | Signature | |---|---| | onExpand | (node, key) => void | | onCollapse | (node, key) => void | | onSelect | (keys, node) => void | | onCheck | (checkedKeys, { node, halfCheckedKeys }) => void | | onActivate | (node, key) => void — Enter / primary click | | onLoad | (node, children) => void — after a lazy loader resolves | | onMove | (dragKey, dropKey, position) => boolean \| void — return false to veto | | onChange | (nodes) => void — after ANY built-in edit, with the new tree | | onAdd | (node, parentKey) => void | | onRemove | (node) => void | | onRename | (node, label) => void |

Imperative API

Native: the object returned by the factory (also via window.GSTreeConfigue.instance(ref)). React: the ref handle (GSTreeHandle).

| Method | Description | |---|---| | expand(key) / collapse(key) / toggle(key) | Expand / collapse / toggle a branch. | | expandAll() / collapseAll() | Expand / collapse every parent. | | select(key) | Select a node. | | getSelected() | GSTreeKey[] of selected keys. | | check(key) | Toggle a node's checkbox (with cascade unless strict). | | getChecked() | GSTreeKey[] of fully-checked keys. | | setChecked(keys) | Replace the checked set (re-derives half-checked). | | scrollTo(key) | Scroll a node into view. | | setFilter(query) | Apply / clear the filter. | | addNode(parentKey, node, index?) | Add a child (or a root when parentKey is null); returns the new key. | | removeNode(key) | Remove a node. | | updateNode(key, patch) | Shallow-merge a patch into a node. | | renameNode(key, label) | Set a node's label (fires onRename). | | duplicateNode(key) | Clone a node with fresh keys next to it; returns the new key. | | moveNode(dragKey, dropKey, position) | Reorder programmatically ('before' \| 'after' \| 'inside'). | | beginEdit(key) | Start an inline rename. | | getNode(key) / getNodes() | Read the current model. | | refresh() | Rebuild from the current model + state. | | destroy() | Tear down (native: unregisters too). |

Keyboard

| Key | Action | |---|---| | / | Move to next / previous visible node. | | | Expand a collapsed parent, else move to its first child. (RTL: .) | | | Collapse an expanded parent, else move to its parent. (RTL: .) | | Home / End | First / last visible node. | | Enter | Activate (select + onActivate). | | Space | Toggle the checkbox (when checkable), else activate. | | * | Expand all sibling parents at the current level. | | type a letter | Type-ahead to the next node whose label starts with it. |

Theming

Every visual is a CSS custom property on .gs-tree, so you can theme without touching the JS:

.gs-tree {
  --gstree-accent: #7c3aed;
  --gstree-radius: 10px;
  --gstree-row-height: 32px;
  --gstree-indent: 24px;
  --gstree-folder-color: #f59e0b; /* defaultIcons folders */
  --gstree-badge-bg: #eef2f7;     /* badge chip */
  --gstree-glass-bg: rgba(255, 255, 255, 0.55); /* glass variant */
}

theme="dark" (or auto under prefers-color-scheme: dark) swaps the background/border/hover/selected tokens — including the badge, folder/file, row-fill, card and glass tokens, so every variant works in both themes out of the box. accentColor and indent params set --gstree-accent / --gstree-indent inline per instance.

Picking a variant

| Variant | Feels like | |---|---| | default | Clean list — hover + tinted selection. | | bordered | The tree sits in a bordered, softly-shadowed panel. | | filled | Every row is a soft chip; selection tints with the accent. | | pills | Fully-rounded rows; selection becomes a solid accent pill. | | cards | Each root branch is an elevated card — great for grouped settings. | | glass | Translucent, blurred panel for hero/dashboard surfaces. | | minimal | Zero chrome; selection is an accent bar + colored label. | | rail | App sidebar / nav: tall rounded rows, accent-tinted selection + bar. |

Editing

const ref = useRef<GSTreeHandle>(null);

<GSTree
  ref={ref}
  editable                                   // double-click / F2 to rename
  draggable                                  // drag to reorder
  nodeActions={['add', 'rename', 'duplicate', 'delete']}   // hover buttons
  iconTiles
  onChange={(nodes) => save(nodes)}          // persist the new tree
  nodes={data}
/>;

// …or drive it imperatively:
const key = ref.current!.addNode('parent-id', { label: 'New' });
ref.current!.beginEdit(key);                 // inline-rename the fresh node
ref.current!.duplicateNode('some-id');
ref.current!.moveNode('a', 'b', 'inside');

Sortable-grade drag

draggable turns on a drag engine tuned to feel like GSSortable:

<GSTree
  draggable
  dragHandle="grip"        // grab only via a rendered grip (or false = whole row)
  dropIndicator="glow"     // 'line' | 'pill' | 'glow'
  lift                     // ghost lifts with a shadow + slight tilt
  autoScroll               // scrolls when you drag near an edge
  sortAnimation={200}      // FLIP drop-settle
  onMove={(dragKey, dropKey, pos) => pos !== 'inside'}  // return false to veto
  onChange={(nodes) => save(nodes)}
  nodes={data}
/>

While dragging you get a floating ghost clone, a dimmed source row, a live drop indicator (before / after / inside), a drop-zone highlight, edge auto-scroll, and a FLIP settle when the node lands — all prefers-reduced-motion aware. onMove can veto a drop; onChange fires with the reordered tree.

nodeActions also accepts custom buttons: { key, title, icon, danger?, hidden?, onClick(node, api) } — where api is the same op set (addNode / removeNode / renameNode / duplicateNode / beginEdit / …), so a custom button can mutate the tree too. In native, icon is an HTML/SVG string; in React it's a ReactNode.

License

ISC © Get-Set