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

svg-renderer

v0.2.0

Published

Fast, correct SVG rendering for React Native on top of react-native-svg, plus a pan/zoom viewer (import from /viewer) with taps resolved in SVG space, selection highlights and built-in controls.

Readme

svg-renderer

Render real-world SVG in React Native through react-native-svg, with the parsing, CSS cascade, normalization, style batching and geometry of svg-core in front of it, and a pan/zoom viewer with taps resolved in SVG space, selection highlights, badges and built-in controls.

Pre-alpha. See the design document for scope and roadmap.

Install

npm install svg-renderer react-native-svg

For the viewer, add the gesture libraries (already present in most Expo apps):

npm install react-native-gesture-handler react-native-reanimated

They are optional peers: apps that only render SVG never load them.

svg-renderer re-exports the whole svg-core API, so a React Native app needs only this package. Install svg-core on its own for Node, servers or the CLI.

Rendering

import { SvgRenderer } from 'svg-renderer';

<SvgRenderer source={{ xml }} width="100%" height={240} />
<SvgRenderer source={{ uri: 'https://example.com/plan.svg' }} onDocument={(doc) => console.log(doc.warnings)} />

Parse once, render many times:

import { parseSvg } from 'svg-renderer';

const doc = useMemo(() => parseSvg(xml), [xml]);
<SvgRenderer source={{ document: doc }} />

| Prop | Type | Description | |---|---|---| | source | { xml } \| { uri, fetchText? } \| { document } | What to render. | | width, height | number \| string | Size of the drawing surface. Default 100%. | | style | ViewStyle | Passed to the root <Svg>. | | planOptions | PlanOptions | Forwarded to document.plan(); interactive marks nodes that stay individual units. | | viewBox | Rect | Render only this document region, stretched onto width × height. | | overrides | Map<SvgNode, StyleOverride> | Per-node style overrides (selection highlights). | | children | react-native-svg elements | Appended inside the root <Svg>, in document coordinates. | | onDocument | (doc) => void | Called when a document is parsed or received. | | onError | (error) => void | Called when parsing or fetching fails; nothing is rendered. | | fallback | ReactNode | Rendered while a uri source loads or after an error. |

Viewer

import { useMemo, useRef, useState } from 'react';
import { parseSvg } from 'svg-renderer';
import { SvgViewer, type SvgViewerRef } from 'svg-renderer/viewer';

export function FloorPlan({ xml, regions }: { xml: string; regions: Record<string, { name: string }> }) {
  const document = useMemo(() => parseSvg(xml), [xml]);
  const viewer = useRef<SvgViewerRef>(null);
  const [selection, setSelection] = useState<string[]>([]);

  return (
    <SvgViewer
      ref={viewer}
      document={document}
      style={{ flex: 1 }}
      interactive={regions}                        // id -> data; becomes node.data
      selection={selection}                        // built-in selection: tap selects, tap again deselects
      onSelectionChange={setSelection}
      onElementPress={({ node, data }) => console.log('tapped', node.id, data)}
      decorators={[{ match: '.room', anchor: 'center', layer: 'overlay', render: (node) => <Badge label={String(node.data?.name)} /> }]}
      controls={{ position: 'bottom-right' }}      // built-in zoom in / out / fit; `false` hides them
    />
  );
}

The app root must be wrapped in GestureHandlerRootView (see the gesture-handler docs).

Pan, pinch and double-tap zoom run on the UI thread; a released pan glides on with the finger's velocity and stops inside the content bounds, and a camera pushed past the bounds eases back. Taps are converted to document coordinates and resolved through the core's hit testing to the nearest interactive ancestor, so they work identically on every backend. The drawing is rasterized for the resting camera (whole content while it fits a pixel budget, an overscanned viewport region beyond that) and re-anchored when a gesture ends and whenever the fingers rest for a moment mid-gesture, so a long pinch stays crisp. Each layer positions itself against the live camera and the previous layer stays until the new one has laid out, so re-anchoring never flashes or jumps.

| Prop | Type | Description | |---|---|---| | document | SvgDocument | Parsed with parseSvg. | | interactive | string \| (node) => boolean \| Record<id, data> | Which nodes respond to taps. A record also attaches its values as node.data. | | onElementPress | (hit: ElementHit) => void | Nearest interactive ancestor of the topmost shape under the tap. | | onElementLongPress | (hit: ElementHit) => void | Long press (400 ms) on an interactive element; does not change the selection. | | pressedStyle, pressedDuration | StyleOverride | (node) => StyleOverride, number | Brief highlight of the tapped element for touch feedback. Off unless given; 180 ms. | | inertia | boolean | Pan glides after release. Default true. | | accessibility | (node, data) => { label, hint? } | null | Screen-reader targets for interactive elements; activating one acts like a tap. | | onBackgroundPress | (point, screenPoint) => void | Tap that hit nothing interactive. | | selectionMode | 'single' \| 'multiple' \| 'none' | Built-in selection by id. single (default): tap selects, tapping the selected element again deselects it. multiple: taps toggle membership. | | selection, defaultSelection, onSelectionChange | string[] | Controlled or uncontrolled selection; the callback also receives the ElementHit (null for background taps and ref calls). | | selectedStyle | StyleOverride \| (node) => StyleOverride | Highlight for selected elements. Default green stroke, width 3. | | clearSelectionOnBackgroundPress | boolean | Default true. | | elementStyles | Record<idOrSelector, StyleOverride> | Restyle any nodes; wins over selectedStyle. | | decorators | Decorator[] | Badges/labels anchored to matching nodes, in the SVG (layer: 'svg') or as fixed-size overlay views (layer: 'overlay'). minTargetSize hides a decoration while its element is drawn smaller than that many pixels (labels of small rooms appear as the user zooms in, with a short fade); minZoom / maxZoom limit it to a zoom range relative to the initial fit. avoidOverlap hides the lower-priority label of two that would collide on screen (default priority: bounding-box area), re-evaluated whenever the camera settles. | | controls | boolean \| ViewerControlsOptions | Built-in buttons. Default true, top-right. | | renderControls | (api) => ReactNode | Replace the built-in buttons with your own. | | initialFit | 'content' \| 'viewBox' \| Rect | What to show first. Default content. | | minZoom, maxZoom | number | Relative to the initial fit. Default 0.5 and 8. | | padding | number | Pixels around the fitted content. Default 16. | | hitSlop | number | Extra pixels around thin shapes that still count. Default 8. | | hitMode | 'painted' \| 'geometry' | geometry makes unfilled outlines tappable inside. Default geometry. | | doubleTapZoom | number | Factor per double tap, 0 disables. Default 2. | | backend | ViewerBackend | Rendering component; defaults to the react-native-svg backend. | | onCameraChange | (camera) => void | Called when the resting camera changes: after a gesture or animation settles and after a mid-gesture re-anchor. | | children | ReactNode | App views above the drawing that do not move with the camera. |

Ref: fitToElement(id, opts), fitToElements(ids, opts), fitToBounds(rect, opts), fitToContent(opts), zoomBy(factor, focal?, opts), zoomTo(scale, focal?, opts), getCamera(), screenToSvg(point), svgToScreen(point), getSelection(), setSelection(ids), select(id), deselect(id), toggleSelection(id), clearSelection(). Fit and zoom calls made before the viewer has measured itself (for example right after mounting it for a new floor) are applied as soon as it has.

Custom backends receive camera (the camera the layer is laid out for), live (the live camera as shared values) and onReady; they display the layer with the transform that maps camera onto live and call onReady once laid out, so the viewer can drop the layer being replaced.