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

i-input-svelte

v0.1.0

Published

A Blender-style universal number input for Svelte 5 with drag-to-scrub, inline math expressions, and pluggable unit systems. A Svelte port of i-input.

Readme

i-input-svelte

A Blender-style universal number input for Svelte 5.

Drag-to-scrub, inline math expressions (3 * 2 + 1), pluggable unit systems (5km, 3ft 2in, 1m + 2cm), soft/hard limits, value rollover, and fully custom rendering.

Ships a styled <IInput /> component and a headless createIInput controller.

This is a Svelte port of i-input by Faraz Shaikh. All credit for the original design and logic goes to the upstream project.

Contents

Install

npm install i-input-svelte
# or
pnpm add i-input-svelte

svelte >= 5 is a peer dependency.

Quick start

<script lang="ts">
  import { IInput } from "i-input-svelte";
  let value = $state(0);
</script>

<IInput bind:value />

You can also use a callback instead of (or alongside) bind:value:

<IInput {value} onChange={(v) => (value = v)} />

Options

Accepted by both the IInput component and the createIInput controller.

| Name | Type | Default | Description | | ------------------ | ---------------------------------------- | --------- | --------------------------------------------------------------------------------- | | value | number | 0 | The current value (controlled). Bindable on the component. | | onChange | (value: number) => void | — | Called with the next value. | | step | number | 1 | Increment for arrow keys, wheel, and scrubbing. | | precision | number | 3 | Max decimal places shown in the default display. | | disabled | boolean | false | Disables all interaction. | | scrub | boolean | true | Enable drag-to-scrub. When false, click/tap edits instead. | | scrubSensitivity | number | 1 | Multiplier for scrub speed (< 1 is slower). | | scrubDirection | "x" \| "y" \| "free" | "free" | Scrub axis. "free" responds to both (drag right/up to increase). | | hardMin | number | -∞ | Absolute lower bound; enforced even when typing. | | hardMax | number | +∞ | Absolute upper bound; enforced even when typing. | | softMin | number | hardMin | Lower bound for scrub/step; typing can exceed it. | | softMax | number | hardMax | Upper bound for scrub/step; typing can exceed it. | | wrapMode | "none" \| "hard-limit" \| "soft-limit" | "none" | Wrap past the bounds instead of clamping (e.g. an angle 360° → 0°). | | unit | string | — | Display/parse unit suffix (e.g. "%", "m"). | | unitSystem | UnitSystem | — | Recognized units for parsing & display (see Units). | | customUnits | UnitDefinition[] | — | Extra units appended to unitSystem (or used standalone). | | formatDisplay | (info) => string | — | Custom display formatter. info = { value, unit, unitSystem, defaultDisplay }. |

IInput component

Renders a styled, interactive input. Accepts every option above plus the following.

| Name | Type | Default | Description | | -------------- | --------------------------------------------- | ------- | ---------------------------------------------------------------------- | | children | Snippet<[state, actions]> | — | Render overlay content. See below. | | styles | IInputStyles | — | Inline CSS-text overrides per part (see table below). | | classNames | IInputClassNames | — | Class name overrides per part (same keys as styles). | | style | string | — | Shortcut for styles.root. | | class | string | — | Shortcut for classNames.root. | | placeholder | string | — | Input placeholder while editing. | | name / id | string | — | Forwarded to the <input>. | | readOnly | boolean | false | Forwarded to the <input>. | | required | boolean | false | Forwarded to the <input>. | | autoComplete | string | "off" | Forwarded to the <input>. | | inputMode | string | — | Forwarded to the <input>. | | tabIndex | number | — | Forwarded to the root element (needed for keyboard focus). | | aria-* | string \| boolean | — | aria-label, aria-labelledby, aria-describedby, aria-invalid. |

Style / className parts

styles and classNames share these keys. Note that styles values are CSS text strings (Svelte inline styles), not objects:

| Key | Applies to | | -------------- | ----------------------------------------- | | root | The outer wrapper element. | | input | The <input> shown while editing. | | display | The text shown while not editing. | | rootInvalid | The root when the typed text is invalid. | | inputInvalid | The input when the typed text is invalid. |

<IInput
  bind:value
  styles={{
    root: "border-radius:999px; background:#10243a",
    input: "color:#dbeafe",
  }}
/>

Custom rendering (children snippet)

Pass a children snippet to render overlay content (fill bars, icons, buttons…) on top of the input. It receives the current state and actions.

<IInput bind:value hardMin={0} hardMax={100}>
  {#snippet children(state, actions)}
    <!-- a fill bar driven by the normalized value -->
    <div
      style="position:absolute; inset:0; width:{state.normalized *
        100}%; background:#4a7fb8; opacity:0.5; pointer-events:none;"
    ></div>
    {#if state.hovering}
      <button data-role="step" onclick={() => actions.stepBy(1)}>+</button>
    {/if}
  {/snippet}
</IInput>

Give interactive overlay elements data-role="step" so a click on them does not start a scrub/edit on the root.

state and actions are the same objects the controller exposes — see below.

createIInput controller (headless)

For full control over markup, use the headless controller. Pass a getter that returns the current options so it stays reactive, then wire the returned use: actions and reactive getters into your own template.

<script lang="ts">
  import { createIInput } from "i-input-svelte";

  let value = $state(0);
  const i = createIInput(() => ({ value, onChange: (v) => (value = v) }));
</script>

<div use:i.root tabindex="0">
  {#if i.editing}
    <input use:i.input bind:value={i.text} />
  {:else}
    <span>{i.display}{i.displayUnit ? ` ${i.displayUnit}` : ""}</span>
  {/if}
</div>

Controller members

| Member | Type | Description | | ----------------- | ----------------------------- | ---------------------------------------------------- | | root | Svelte action | use:i.root on your wrapper element. | | input | Svelte action | use:i.input on the <input> (rendered while editing). | | text | string (get/set) | Bind with bind:value={i.text}. | | state | IInputState | Snapshot of all reactive state. | | actions | IInputActions | Imperative helpers (see below). |

State is also exposed directly as reactive getters: i.editing, i.hovering, i.dragging, i.isTextValid, i.display, i.displayUnit, i.normalized.

state

| Field | Type | Description | | ------------- | --------------------- | -------------------------------------------- | | editing | boolean | The text input is active. | | hovering | boolean | Pointer is over the control. | | dragging | boolean | A scrub drag is in progress. | | text | string | Current raw text in the input. | | isTextValid | boolean | Whether text parses to a finite number. | | display | string | Formatted value shown when not editing. | | displayUnit | string \| undefined | Unit suffix appended to the display, if any. | | normalized | number | Value mapped to 0–1 across the soft range. |

actions

| Method | Description | | ------------- | -------------------------------------------- | | stepBy(dir) | Step by ±step (dir is -1 or 1). | | negate() | Flip the sign of the current value. | | set(value) | Set the value (clamped/wrapped accordingly). |

Units

A UnitSystem defines a base unit and the units recognized when parsing and displaying. A built-in distanceUnits system (base = meter) is provided.

<script lang="ts">
  import { IInput, distanceUnits } from "i-input-svelte";
  let meters = $state(1.8);
</script>

<!-- accepts "5km", "3ft 2in", "1m + 2cm" → stored in meters -->
<IInput bind:value={meters} unit="m" unitSystem={distanceUnits} />

Define your own:

const unitSystem = {
  baseNames: ["px"],
  units: [
    { names: ["px"], toBase: 1 },
    { names: ["rem"], toBase: 16 },
  ],
};

customUnits

Use customUnits to add extra UnitDefinitions without redefining a whole system. They are appended to unitSystem (later definitions win on name clashes); if no unitSystem is given, they form a standalone unitless system.

<IInput
  bind:value={meters}
  unit="m"
  unitSystem={distanceUnits}
  customUnits={[{ names: ["px", "pixel", "pixels"], toBase: 0.0002645833 }]}
/>
<!-- now "100px" parses alongside "5km", "3ft 2in", ... -->

Each UnitDefinition is { names: string[]; toBase: number }names are the case-insensitive aliases (canonical name first) and toBase converts that unit into the system's base.

Helpers

Exported alongside the component and controller:

| Export | Kind | Description | | -------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------- | | distanceUnits | UnitSystem | Built-in distance system (base = meter): km, m, cm, mm, mi, yd, ft, in, … | | extendUnitSystem(system, custom) | function | Returns a new UnitSystem with custom units appended (later definitions win on clashes). | | findUnit(system, name) | function | Look up a UnitDefinition by any of its names (case-insensitive); null if not found. | | formatComposite(value, system, valueUnit, parts) | function | Format a value across multiple units, e.g. 5' 10.87" or 1h 23m. | | evaluateExpression(text) | function | Safely evaluate a math expression; returns number \| null. |

import { findUnit, formatComposite, distanceUnits } from "i-input-svelte";

findUnit(distanceUnits, "ft"); // → { names: ["'", "ft", ...], toBase: 0.3048 }

formatComposite(1.8, distanceUnits, "m", [{ unit: "'" }, { unit: '"' }]);
// → "5' 10.866\""

formatComposite parts are { unit, suffix?, separator?, precision? }: earlier parts are floored to whole counts and the final part absorbs the remainder.

Types

Exported types: IInputProps, UseIInputOptions, IInputState, IInputActions, IInputStyles, IInputClassNames, UnitSystem, UnitDefinition, CompositePart.

Interactions

  • Drag the control to scrub (hold Shift for fine steps, Ctrl/Cmd for precision).
  • Click / tap to type. Inline math and units are evaluated on commit.
  • Arrow Up/Down or mouse wheel to step by step.
  • - while not editing negates the value.
  • Enter commits, Escape cancels.

Keyboard focus on the root requires a tabIndex/tabindex (the styled component forwards tabIndex; the headless example sets tabindex="0").

Development

npm install
npm run dev    # run the playground in src/demo
npm run check  # type-check with svelte-check
npm run build  # build the library to dist/ with svelte-package

License

MIT © Faraz Shaikh (original) and the Svelte port authors. See LICENSE.