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

@sld-kit/react

v0.7.0

Published

React components for single-line (electrical) diagrams: a headless pan/zoom canvas, element views, and event-dispatching editor chrome built on @sld-kit/core.

Readme

@sld-kit/react

React components for single-line (electrical) diagrams, built on @sld-kit/core. A headless pan/zoom canvas, the element views that paint the core's computed geometry, and the event-dispatching editor chrome — the React sibling of @sld-kit/svelte.

The components are deliberately headless and unopinionated:

  • No dialogs, no persistence, no routing. Components emit semantic callbacks (onSelect, onEditLabel, onCanvasDown, …); the host app decides what to do — open its own dialogs, persist to a store, etc.
  • No bundled UI kit. Zero runtime dependencies; only lucide-react icons.
  • Injectable strings. Every visible string ships an English default and is overridable through a component's labels prop, so apps localize without forking.
  • Injectable theming. Colors are supplied by the consumer's CSS. Components emit a class per position type (the tokens map, default sld-pos-*) and reference CSS custom properties (--sld-pos, --primary, --muted-foreground, …). The consumer's stylesheet provides the actual values, so dark mode and re-theming are entirely app-side.

Port status: at full parity with @sld-kit/svelte: canvas and element views, editor chrome (toolbar, flyouts, popovers, lane chip), composite ("diagram of diagrams") primitives and flow overlays. It also ships the grid-level box view (boxMode, line kinds, SymbolGlyph, orthogonal routing, port-direction override), which the Svelte adapter doesn't have yet. See the component parity table.

Install

npm install @sld-kit/react @sld-kit/core lucide-react react react-dom

@sld-kit/core, lucide-react, react and react-dom are peer dependencies. React 18 is the floor (the document binding uses useSyncExternalStore). The bundle carries a "use client" directive, so it drops into a Next.js App Router tree without per-import wrapping.

Quickstart

import { useMemo, useState } from 'react';
import { LayoutEngine } from '@sld-kit/core';
import { SldCanvas, useSldDocument } from '@sld-kit/react';

export function Diagram({ doc }) {
  // The document mutates in place, so a version counter — not its identity —
  // is what tells React to re-derive.
  const version = useSldDocument(doc);
  const engine = useMemo(() => new LayoutEngine(), []);
  const layout = useMemo(() => engine.layout(doc), [engine, doc, version]);
  const [selectedIds, setSelectedIds] = useState(new Set<string>());

  return (
    <SldCanvas
      doc={doc}
      layout={layout}
      version={version}
      selectedIds={selectedIds}
      onSelect={({ id }) => setSelectedIds(new Set([id]))}
      onClearSelection={() => setSelectedIds(new Set())}
      onEditLabel={({ id }) => openMyEditDialog(id)}
    />
  );
}

Wiring the full editing loop (command stack, drag-to-move, ghost preview, toolbar, dialogs) is the host app's job — see examples/react for a runnable editor.

Reacting to document changes

useSldDocument(doc) returns a monotonic version number. This is the one place the React adapter cannot mirror the Svelte one: SldDocument mutates in place, so Object.is on the document never reports a change. Always pass version into the dependency arrays that read the document — including SldCanvas's version prop.

useCommandStack(stack) returns { canUndo, canRedo } for wiring undo/redo buttons.

Imperative canvas API

SldCanvas forwards a ref exposing clientToSvg, svgToClient, zoomToFit, zoomIn, zoomOut, getViewBox and getSvgElement:

const canvasRef = useRef<SldCanvasHandle>(null);
<SldCanvas ref={canvasRef} … />
<button onClick={() => canvasRef.current?.zoomToFit()}>Fit</button>

Overlays

SldCanvas takes two overlay slots: background renders under the elements (grid slots, lane chrome) and children above them (drag ghost, rubber band).

<SldCanvas
  doc={doc}
  layout={layout}
  version={version}
  background={<LaneOverlay layout={layout} onSelectLane={setLane} />}
>
  <GridOverlay layout={layout} highlight={ghost} />
  {ghost && <GhostPreview rect={layout.cellRect(ghost.cell)} valid={ghost.valid} />}
</SldCanvas>

Theming (tokens)

The library emits a class per position type; your stylesheet gives it a color by setting --sld-pos.

:root {
  --sld-pos-line: hsl(217 91% 60%);
  --sld-pos-transformer: hsl(38 92% 50%);
}
.sld-pos-line {
  --sld-pos: var(--sld-pos-line);
}
.sld-pos-transformer {
  --sld-pos: var(--sld-pos-transformer);
}

Override the map with the tokens prop, or force one color for a whole diagram (e.g. a voltage bucket) with colorClass.

--sld-pos holds a complete CSS color here, not the HSL triplet @sld-kit/svelte expects. That adapter targets Tailwind v3 / shadcn-svelte, this one targets Tailwind v4 / shadcn, whose tokens are whole colors. Alpha is applied with the SVG fill-opacity / stroke-opacity attributes, which keeps it color-space agnostic. See the divergence note.

Tailwind purge

The package emits Tailwind utility classes but ships no Tailwind config. Your app must scan the built package or the diagram loses its colors:

/* Tailwind v4 */
@source "../node_modules/@sld-kit/react/dist";
// Tailwind v3
content: ['./src/**/*.{ts,tsx}', './node_modules/@sld-kit/react/dist/**/*.js'];

Styling the chrome

The interaction chrome is headless: the adapter owns the geometry, state and events, and your stylesheet owns the look. With no extra props or CSS it renders exactly the default look; every hook below is opt-in.

Composite frame (CompositeCanvas → SelectionFrame)

One frame serves both selection and hover, so the two never stack.

| Prop | Effect | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | hoverFrame | Also frame the child under the pointer (data-state="hover", faded, no rotate handle). Default false. | | hoveredChild | Controlled hover { id, section? } \| null: draw this instead of the pointer (e.g. highlight a box from a list). | | selectionCorners | 'dots' (default), 'brackets' or 'none'. | | onChildHover | The child (and section box, in a split box view) under the pointer, or null. Cleared while a button is held, while panning and in draw mode. |

| Part (class) | What it is | | ----------------------- | ---------------------------------------------------------------------------------- | | sld-selection | Root <g>. Carries the child's childColorClass, so CSS can tint by --sld-pos. | | sld-selection-body | The child's own corners, unpainted — a hook for a wash or a ping. | | sld-selection-outline | The dashed outline (padding outside the child, rounded by radius). | | sld-selection-corner | Corner dots or brackets. | | sld-selection-rotate | Rotation handle + stem (selected frames only). |

| Attribute (on the root) | Values | | ----------------------- | ----------------------------------------------------------------------------- | | data-state | selected | hover (frames are keyed by state, so entry animations replay) | | data-hover | "true" when the selected child is also under the pointer | | data-section | the hovered section box's key (split box view) | | data-corners | brackets | none (absent for the default dots) |

CSS variables: --sld-selection-stroke (colour, default the text-primary currentColor), --sld-selection-width, --sld-selection-dash. Numbers: style.composite.selection — strokeWidth, dashArray, padding, radius, bodyRadius, cornerRadius (dots), cornerArm (selected brackets), hoverCornerArm, rotateHandleOffset, rotateHandleRadius.

Editor elements (SldCanvas → element views)

| Prop | Effect | | ---------------- | ----------------------------------------------------------------------------------------- | | hoverHalo | Also draw the halo around the bus bar / position under the pointer. Default false. | | haloCorners | 'brackets' adds corner brackets to the halos; 'none' (default). | | busBarHitSlop | Widen each bar's hit area by N px (bars are thin). Default 0. | | onElementHover | { id, kind } \| null for the element under the pointer; cleared while a button is held. |

Roots: sld-busbar, sld-position, sld-connection, each with data-hover="true" / data-selected="true" when set. Halo parts: sld-halo (group, data-state), sld-halo-body (the element's rect, unpainted), sld-halo-outline, sld-halo-corner. CSS variables --sld-halo-stroke, --sld-halo-width, --sld-halo-dash; numbers in style.selection (padding, strokeWidth, dashArray, cornerArm, hoverCornerArm). Halo rects take rx from CSS (.sld-halo-outline { rx: 6px }). Connections have no halo — their selection is the heavier stroke.

Recipe

/* Tinted hover frame with marching ants and a faint wash; hide the rotate handle. */
.sld-selection {
  --sld-selection-stroke: var(--sld-pos);
}
.sld-selection[data-state='hover'] {
  opacity: 1;
}
.sld-selection[data-state='hover'] .sld-selection-outline {
  animation: march 0.9s linear infinite;
}
.sld-selection-body {
  fill: var(--sld-pos);
  fill-opacity: 0.08;
}
.sld-selection-rotate {
  display: none;
}
@keyframes march {
  to {
    stroke-dashoffset: -24;
  }
}

To hide the frame entirely: .sld-selection { display: none }. Paint set inline (the outline's stroke, width and dash) is reachable only through the CSS variables; everything else is a presentation attribute that any CSS rule overrides. The box-view example's app.css builds a full game-style "target lock" on these hooks.

Numbers vs. colors

SldViewStyle owns every stroke width, opacity, dash pattern and handle radius; CSS owns every color. Pass a partial through resolveViewStyle({...}) to a canvas's style prop.

FormatResolver is the per-element styling seam — pass the same function to the exporter's theme.resolveElementFormat so the live canvas and the SVG export agree.

License

MIT © Adrian Sanchez R