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

@swisper.ai/web-core

v0.0.7

Published

Core TypeScript utilities and types

Readme

@swisper.ai/web-core

npm license

Core TypeScript utilities and types. It holds the small React hooks and helpers that the Echo DS packages share: no components, no CSS, no provider. It runs in the browser only, because its hooks use ResizeObserver, IntersectionObserver and matchMedia and its CSS-variable helpers read getComputedStyle. useReducedMotion and useControlledProps are the exceptions: they also render on the server.

Boundary

Code belongs here if it is a framework-level helper or React hook with no markup, no styling and no dependency on another @swisper.ai/* package. Components and hooks tied to Echo's look or theme belong in @swisper.ai/ui, design tokens in @swisper.ai/design-system.

Install

npm install @swisper.ai/web-core

| Peer | Range | Why | | ----------- | ---------- | --------- | | react | >=18.0.0 | Hooks | | react-dom | >=18.0.0 | Rendering |

clsx and tailwind-merge are bundled into dist/, which imports nothing at runtime but React.

Setup

  1. Nothing to configure: import what you need.
  2. Use it:
import { cn, formatElementMeasurement, useElementMeasure } from '@swisper.ai/web-core';

export function Panel({ isActive }: { isActive: boolean }) {
  const { ref, width } = useElementMeasure<HTMLDivElement>();

  return (
    <div ref={ref} className={cn('p-16', isActive && 'p-24')}>
      Width: {formatElementMeasurement(width, 'unknown')}
    </div>
  );
}

API

| Export | Why it exists | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | cn | Joins class names and resolves Tailwind conflicts (clsx, then tailwind-merge), so a class passed in by a caller replaces the default it conflicts with. Tuned for Tailwind v4: a leading-* class survives next to a text size, whatever their order. The one cn for every @swisper.ai/* package | | useElementMeasure | Tracks an element's border-box width and height with a ResizeObserver. Returns a callback ref and whole-pixel sizes, or 'unset' until there is a size | | formatElementMeasurement | Turns a size from useElementMeasure into a CSS length: '320px', or a fallback ('100%' by default) for 'unset' | | useIntersectionVisibility | Whether an element is visible inside a root element (or the viewport), via IntersectionObserver. Waits for an element that isn't in the DOM yet, so it works with late-mounted rows | | useReducedMotion | Whether the user asked for reduced motion (prefers-reduced-motion: reduce), for motion CSS can't switch off: a JS-driven animation, an autoplaying video, smooth scrolling. Updates when the setting changes. Returns false on the server and while hydrating, unless you pass a getServerSnapshot | | useControlledProps | State for a prop the parent may control or leave to the component: the value / defaultValue / onChange trio. Keeps the value itself while value is undefined, and returns the parent's once it is anything else, null included. Returns [value, setValue]; setValue keeps its identity and calls onChange either way | | getCssVariableValue | Reads a CSS variable on :root (a theme size or breakpoint) as a number in pixels. Takes the unit it must be in ('px', 'rem' or 'em') and converts rem/em with the root font size | | getVariableNumberValue | Reads a numeric CSS variable from any element (:root by default). Requires pixels unless isPx: false |

Troubleshooting

| Symptom | Cause | Fix | | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | | "Invalid hook call", or a context is missing | The app resolves two copies of React | Add resolve: { dedupe: ['react', 'react-dom'] } to the Vite config | | useElementMeasure keeps returning 'unset' | The returned ref isn't attached to an element, or the element has no size yet (a zero size is reported as 'unset') | Pass ref to the element's ref prop; render formatElementMeasurement(value) to fall back meanwhile | | useIntersectionVisibility always returns false | targetElement or rootElement was read from useRef().current during render, so the hook got null and never re-ran | Keep the elements in state with a callback ref (ref={setElement}) and pass the state | | ReferenceError: ResizeObserver is not defined in a unit test | jsdom implements neither ResizeObserver nor IntersectionObserver | Stub both in the test setup file | | TypeError: window.matchMedia is not a function in a unit test | jsdom doesn't implement matchMedia, which useReducedMotion calls | Assign a window.matchMedia stub in the test setup file | | getCssVariableValue or getVariableNumberValue throws | The variable isn't defined on the element, is in another unit, or the call ran outside a browser (SSR) | Load the stylesheet that defines it before the call, and call it in an effect or event handler rather than at import |

License

Apache-2.0