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

@dev-jelly/tinytipy-solid

v0.1.2

Published

Solid adapter for tinytipy — diff-driven text morphing with a typing cursor. Ships JSX source compiled by the consumer's Solid pipeline.

Readme

@dev-jelly/tinytipy-solid

Solid 1.8+ adapter for tinytipy — diff-driven text morphing with a typing cursor. Binds the framework-agnostic @dev-jelly/tinytipy to Solid's reactivity primitives and renders the canonical tm-root / tm-reserve / tm-layer / tm-sr-only structure described in ADAPTERS_SPEC.md.

Install

pnpm add @dev-jelly/tinytipy-solid @dev-jelly/tinytipy solid-js

One-time CSS import

Import the canonical stylesheet once in your app entry. The component does not import any CSS from JS — your bundler is in charge.

// app entry (e.g. src/index.tsx)
import "@dev-jelly/tinytipy/styles.css";

<TextMorph /> component

Declarative. Renders the full structure for you.

import { TextMorph } from "@dev-jelly/tinytipy-solid";

function LiveLabel() {
  return <TextMorph from="3s" to="300ms" />;
}

Props

| Prop | Type | Default | Notes | | --------------------- | ------------------------------------------------- | ----------- | --------------------------------------------------------------------- | | from | string | required | Source text. | | to | string | required | Target text. | | timing | Partial<MorphTiming> | defaultTiming | Per-phase timing overrides. | | autoPlay | boolean | true | Play on mount and after from/to changes. | | instant | boolean | false | Skip animation, jump straight to to. | | prefersReducedMotion| boolean | auto-detect | Force reduced-motion behaviour. | | reserveLayout | 'both' \| 'to' \| 'from' \| 'none' | 'both' | Hidden reserve text to prevent reflow. | | cursorLayout | 'overlay' \| 'inline' | 'overlay' | Zero-width overlay cursor or legacy inline cursor. | | class / className | string | — | Appended to the root element (tm-root is always kept). | | onDone | () => void | — | Fires once when the morph reaches to. | | controllerRef | (c: TextMorphController) => void | — | Receive the underlying controller for imperative control on mount. |

createTextMorph primitive

For full markup control. Returns an accessor over the live RenderState plus imperative controls. Accepts values or accessors for the reactive inputs.

import { createTextMorph } from "@dev-jelly/tinytipy-solid";
import { For } from "solid-js";

function CustomMorph(props: { from: string; to: string }) {
  const { state, play, pause, reset, finish } = createTextMorph({
    from: () => props.from,
    to: () => props.to,
    onDone: () => console.log("done"),
  });

  return (
    <span>
      <For each={state().runs}>
        {(run) => <span data-status={run.status}>{run.text}</span>}
      </For>
      <button onClick={() => reset()}>reset</button>
    </span>
  );
}

Return

| Field | Type | Description | | ------------ | ----------------------------- | ------------------------------------------------------------ | | state | Accessor<RenderState> | Live snapshot — runs, cursorRunId, cursorOffset, done. | | play | () => void | Start/restart the morph. | | pause | () => void | Hold the current visible state. | | reset | () => void | Return to the initial (from) snapshot. | | finish | () => void | Jump straight to to. | | controller | TextMorphController | The underlying controller (one per call). |

Lifecycle guarantees

  • Exactly one TextMorphController is constructed per createTextMorph call. It is reused for every from/to/timing change (setPair / internal plan recompute) — never recreated.
  • The controller is destroyed automatically via onCleanup when the owning reactive scope disposes (component unmount, root disposal). All timers are cancelled and listeners unsubscribed.
  • autoPlay is read once at creation; flipping it later does not restart an in-flight morph.

Rendering rules (custom markup)

If you use createTextMorph directly and build your own DOM, follow the canonical structure so the canonical stylesheet works:

<span class="tm-root" data-cursor-layout="overlay">
  <span class="tm-reserve" aria-hidden="true"><!-- reserve text(s) --></span>
  <span class="tm-layer" aria-hidden="true">
    <!-- one node per token from getRenderTokens(state) -->
    <span class="tm-run tm-run--{kind}" data-status="{status}">{text}</span>
    <span class="tm-cursor" aria-hidden="true"></span> <!-- active edit or resolved text end -->
  </span>
  <span class="tm-sr-only">{final `to` text}</span>
</span>

Use getRenderTokens(state) from @dev-jelly/tinytipy to get an ordered list (run and cursor tokens — the cursor follows the active run, then the full resolved text) and getReserveTexts(reserveLayout, from, to) for the reserve layer.

The default overlay mode uses a zero-width flow anchor and positioned caret, so it adds no horizontal advance. Set cursorLayout="inline" for the legacy space-consuming cursor. Completion, empty-text rendering, currentColor, and reduced-motion behavior are unchanged in both modes.

Package shape

This package ships source (src/index.tsx); the consumer's vite-plugin-solid pipeline compiles the JSX. The build script is tsc --emitDeclarationOnly — it only emits the .d.ts files.