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

@binaryoperations/json-tree-editor

v1.0.6

Published

Interactive JSON tree editor — Solid components + framework-agnostic web component

Readme

@binaryoperations/json-tree-editor

Interactive JSON tree editor you can drop into any app: edit keys, types, and primitives in a collapsible tree while treating a JSON string as the single source of truth.

Use it as a SolidJS component (TypeScript source, peer solid-js) or as a framework-agnostic web component (<json-tree-editor> with Solid bundled).

Install

npm install @binaryoperations/json-tree-editor
# or: pnpm add @binaryoperations/json-tree-editor
# or: yarn add @binaryoperations/json-tree-editor

Solid apps also need solid-js and a Solid toolchain (for example vite-plugin-solid):

npm install solid-js

Package entry points

| Import | What you get | | --- | --- | | @binaryoperations/json-tree-editor | JsonTreeView + JsonTreeViewProps only (peer solid-js) | | @binaryoperations/json-tree-editor/utils | Parse helpers, path utilities, type utils, lower-level primitives | | @binaryoperations/json-tree-editor/web-component | Prebuilt <json-tree-editor> custom element (Solid bundled) | | @binaryoperations/json-tree-editor/styles.css | Styles for the Solid path (WC embeds styles in shadow DOM) |


Web component usage

Import the web component once. Solid is bundled, so React, Vue, Svelte, and vanilla hosts do not need solid-js.

<script type="module">
  import '@binaryoperations/json-tree-editor/web-component';

  const el = document.querySelector('json-tree-editor');
  el.value = JSON.stringify({ hello: 'world', count: 1 }, null, 2);

  el.addEventListener('change', (e) => {
    console.log(e.detail.value); // pretty JSON string
  });
  // Alias with the same payload:
  // el.addEventListener('json-change', (e) => { ... });
</script>

<json-tree-editor></json-tree-editor>

Small documents can use the attribute instead of the property:

<json-tree-editor value='{"a":1}'></json-tree-editor>
import '@binaryoperations/json-tree-editor/web-component';
import type { JsonTreeEditorElement } from '@binaryoperations/json-tree-editor/web-component';

const el = document.querySelector('json-tree-editor') as JsonTreeEditorElement;
el.value = '{"name":"Ada"}';

el.addEventListener('change', (event) => {
  const { value } = (event as CustomEvent<{ value: string }>).detail;
  // Sync value back into your store / form state
  console.log(value);
});

Web component API

| Surface | Type | Notes | | --- | --- | --- | | Property value | string | Preferred source of truth, especially for large JSON | | Attribute value | string | Optional; reflected only when length ≤ ~8KB | | Property defaultExpandedDepth | number | Nesting levels open on mount (0 = root only). Default 0 | | Attribute default-expanded-depth | number string | Optional; e.g. default-expanded-depth="1" | | Property / attribute disabled | boolean | Disables pointer edits; expandAll/collapseAll still work | | Method expandAll() | void | Expand every object/array (chunked rAF) | | Method collapseAll() | void | Collapse to root only | | Method getRoot() | HTMLDivElement \| null | The .json-tree element in shadow DOM | | Getter isExpanding | boolean | True while a chunked expandAll is running | | Event change | CustomEvent<{ value: string }> | Fired after a tree edit with pretty-printed JSON | | Event json-change | same as change | Extra alias for hosts that prefer a namespaced event | | Event expand-progress | CustomEvent<{ done, total } \| null> | During expandAll; null when idle/done/cancelled | | Event expand | CustomEvent<{ expanded: string[] }> | Once when expandAll finishes (full key list) | | Event collapse | CustomEvent<{ expanded: string[] }> | Once when collapseAll finishes |

const el = document.querySelector('json-tree-editor');
el.addEventListener('expand', (e) => console.log(e.detail.expanded));
el.addEventListener('expand-progress', (e) => console.log(e.detail));
el.expandAll();
el.collapseAll();

Styles live in an open shadow DOM. Theme tokens are defined on :host, so you can override them with a style attribute, CSS on the host element, or ::part selectors (see Theming).


SolidJS usage

Solid consumers import TypeScript source from the package root. Your bundler compiles the JSX with your app’s solid-js instance—no separate library JS build is required for this path.

Import styles once in your app entry or layout:

import '@binaryoperations/json-tree-editor/styles.css';
import { createSignal } from 'solid-js';
import { JsonTreeView } from '@binaryoperations/json-tree-editor';
import '@binaryoperations/json-tree-editor/styles.css';

export function JsonPanel() {
  const [source, setSource] = createSignal('{"hello":"world"}');

  return (
    <JsonTreeView
      value={source()}
      onChange={(prettyJson) => setSource(prettyJson)}
    />
  );
}
import { createSignal } from 'solid-js';
import {
  JsonTreeView,
  type JsonTreeViewHandle,
} from '@binaryoperations/json-tree-editor';

const [source, setSource] = createSignal(myJson);
let tree: JsonTreeViewHandle | undefined;

<button type="button" onClick={() => tree?.expandAll()}>Expand all</button>
<button type="button" onClick={() => tree?.collapseAll()}>Collapse all</button>

<JsonTreeView
  ref={(h) => { tree = h; }}
  value={source()}
  onChange={setSource}
  onExpand={(keys) => console.log('expanded', keys.size)}
  onExpandProgress={(p) => console.log(p)}
  onCollapse={(keys) => console.log('collapsed', keys.size)}
/>

expandAll is chunked (rAF) for large documents. Initial open depth is set with defaultExpandedDepth (no path keys required).

JsonTreeView props

| Prop | Type | Required | Description | | --- | --- | --- | --- | | value | string | yes | JSON document source (parsed internally) | | onChange | (prettyJson: string) => void | yes | Called after an edit with pretty JSON (2-space indent, no trailing whitespace) | | defaultExpandedDepth | number | no | Nesting levels open on mount (0 = root only, default). 1 opens root + direct child containers, etc. | | onExpand | (keys: Set<string>) => void | no | Once when expandAll finishes (full key set) | | onExpandProgress | (p: { done, total } \| null) => void | no | During expandAll; null when idle/done/cancelled | | onCollapse | (keys: Set<string>) => void | no | Once when collapseAll finishes |

JsonTreeView ref handle

| Method | Description | | --- | --- | | expandAll() | Expand every object/array (chunked) | | collapseAll() | Collapse to root only (cancels in-flight expand) | | isExpanding() | Whether a chunked expandAll is running | | getRoot() | The root .json-tree DOM element (or null before mount) |

Keep the source string as document truth: pass it as value, push tree edits back via onChange.

Root rules (applied inside the view): blank source is treated as a valid empty object {} (no error). The root must be an object or array (never string / number / boolean / null).

JsonTreeView always keeps a tree visible:

  • Blank source → empty object {} (no error banner)
  • Primitive root → error banner + normalized empty object {}
  • Syntax errors → error banner + previous valid tree (or {} if none yet)
  • Tree edits still call onChange with pretty JSON so the user can recover from the tree pane

Utils entry (@binaryoperations/json-tree-editor/utils)

Path helpers (getAtPath, setAtPath, …), type utilities, parse helpers (parseJsonSource, JsonValidity), and lower-level primitives (JsonTreeNode, editors, badges) are exported from /utils. Most apps only need the package root (JsonTreeView); use utils when the host needs validity for its own UI or expand-all helpers.


Theming

Defaults match a dark editor chrome. Override CSS variables on the web component host or on .json-tree (Solid light DOM):

json-tree-editor,
.json-tree {
  --jte-bg: #0c0e12;
  --jte-fg: #e6e8ec;
  --jte-border: #232833;
  --jte-key: #93c5fd;
  --jte-string: #86efac;
  --jte-number: #fcd34d;
  --jte-boolean: #c4b5fd;
  --jte-null: #9ca3af;
  --jte-row-hover: #151922;
  --jte-focus-ring: #60a5fa;
  --jte-font-mono: ui-monospace, Menlo, Consolas, monospace;
  --jte-font-size: 12.5px;
}

| Variable group | Role | | --- | --- | | --jte-bg / --jte-fg | Tree surface and default text | | --jte-border / --jte-border-strong | Nesting and control borders | | --jte-row-hover / --jte-row-focus-bg | Row chrome | | --jte-key / --jte-key-root / --jte-key-index | Property keys | | --jte-string / --jte-number / --jte-boolean / --jte-null | Primitive value colors | | --jte-type-* | Type badge colors | | --jte-focus-ring / --jte-focus-border | Focus outlines | | --jte-font / --jte-font-mono / --jte-font-size | Typography |

Major pieces expose part for styling from outside the shadow tree:

json-tree-editor::part(tree) { /* .json-tree root */ }
json-tree-editor::part(row) { /* one tree row */ }
json-tree-editor::part(key) { }
json-tree-editor::part(value) { }
json-tree-editor::part(type) { /* badge-styled type <select> */ }
json-tree-editor::part(chevron) { }
json-tree-editor::part(actions) { }
json-tree-editor::part(input) { }
json-tree-editor::part(disabled) { /* invalid-JSON state panel */ }

Also available: scroll, summary, action, null.

On the Solid path, the same CSS variables apply. You can also target BEM-style classes (.json-tree-row, and so on) after importing styles.css.


Keyboard navigation

Focus a tree row (click the row chrome, or Tab to the active row). Arrow keys move among visible rows (depth-first, respecting expand state). Navigation is disabled while focus is inside an input, select, or textarea so caret and type controls keep normal Left/Right (and select Up/Down) behavior.

| Key | Action | | --- | --- | | ArrowDown | Next visible row | | ArrowUp | Previous visible row | | ArrowRight | Expand a collapsed container; if already expanded, move to first child | | ArrowLeft | Collapse an expanded container; if collapsed or a leaf, move to parent | | Home | First visible row | | End | Last visible row |

Roving tabindex marks one visible role="treeitem" as tabbable; others use -1.


Changelog

See CHANGELOG.md for release history.

Demos

Interactive demos are coming soon. Until then, clone the monorepo and run the local demo package (see the repository README).

License

MIT © 2026 Shashank