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

mini-timeline

v0.1.9

Published

A lightweight, dependency-free React timeline editor with tracks, sub-tracks, playback, trimming, and JSON persistence.

Readme

DECADE.TW-mini-timeline

A lightweight React timeline editor with parent tracks, collapsible sub-tracks, smooth playback, clip editing, and optional JSON persistence. It has no runtime dependency beyond React and does not use a timeline UI library.

Screenshot

screen1.png

Features

  • Controlled and uncontrolled state
  • Parent tracks and one-level sub-tracks
  • Add, rename, expand, collapse, and delete lanes
  • Create, move, cross-lane move, trim, duplicate, and delete clips
  • Custom clip types, labels, colors, and default data
  • Editable clip name, start, end, and length
  • Editable total duration constrained by content
  • Playback rate, seeking, auto-scroll, and smooth playhead
  • Command/Meta-wheel zoom, Ctrl-wheel horizontal scroll, contained normal wheel, and right-drag pan
  • Generic enter, update, leave, time, and active-clip callbacks
  • Optional localStorage persistence
  • Versioned JSON import/export with legacy-array import support
  • ESM, CommonJS, CSS, source maps, and TypeScript declarations

Install

npm install mini-timeline
import { MiniTimeline } from 'mini-timeline';
import 'mini-timeline/style.css';

The package name is provisional. Verify npm name availability and update package.json before publishing if necessary.

Minimal uncontrolled example

const initialTracks = [
  {
    id: 'track-1',
    name: 'Main',
    collapsed: false,
    actions: [
      {
        id: 'clip-1',
        start: 0,
        end: 2,
        type: 'cue',
        data: { name: 'Opening' }
      }
    ],
    subTracks: []
  }
];

export function Editor() {
  return (
    <MiniTimeline
      defaultValue={initialTracks}
      defaultDuration={60}
      storageKey="my-project-timeline"
      clipTypes={[
        { id: 'cue', label: 'Cue', color: '#16747d', defaultData: { name: 'Cue' } },
        { id: 'audio', label: 'Audio', color: '#9a681e', defaultData: { name: 'Audio' } },
        { id: 'animation', label: 'Animation', color: '#7349a4', defaultData: { name: 'Animation' } }
      ]}
    />
  );
}

Controlled example

import { useState } from 'react';
import { MiniTimeline } from 'mini-timeline';
import 'mini-timeline/style.css';

export function ControlledEditor() {
  const [tracks, setTracks] = useState([]);
  const [duration, setDuration] = useState(120);

  return (
    <MiniTimeline
      value={tracks}
      onChange={(nextTracks, detail) => {
        console.log(detail.type);
        setTracks(nextTracks);
      }}
      duration={duration}
      onDurationChange={setDuration}
      clipTypes={[{ id: 'event', label: 'Event', color: '#2563eb' }]}
    />
  );
}

Lifecycle integration

The package does not play audio or send network messages itself. Consumers connect effects through callbacks:

<MiniTimeline
  value={tracks}
  onChange={setTracks}
  clipTypes={clipTypes}
  onClipEnter={(clip, context) => {
    if (clip.type === 'audio') audioEngine.play(clip.data.src, context.time - clip.start);
    if (clip.type === 'cue') udp.send(clip.data.startMessage);
  }}
  onClipUpdate={(clip, context) => {
    animationEngine.seek(clip.id, context.time - clip.start);
  }}
  onClipLeave={(clip) => {
    if (clip.type === 'audio') audioEngine.stop(clip.id);
    if (clip.type === 'cue') udp.send(clip.data.stopMessage);
  }}
  onActiveClipChange={(clip) => console.log('Active clip:', clip)}
/>

Lifecycle context contains time, previousTime, playing, rate, track, and subTrack.

Props

| Prop | Type | Default | Description | | --- | --- | --- | --- | | value | TimelineTrack[] | — | Controlled track data | | defaultValue | TimelineTrack[] | [] | Initial uncontrolled data | | onChange | (tracks, detail) => void | — | Called for every data mutation | | clipTypes | ClipTypeDefinition[] | Default Clip | Available clip types | | duration | number | — | Controlled total duration | | defaultDuration | number | 60 | Initial/minimum uncontrolled duration | | onDurationChange | (duration) => void | — | Called after duration edits/import | | storageKey | string | — | Enables localStorage mirroring | | snapInterval | number | 0.1 | Movement/property snap in seconds | | minDuration | number | 0.1 | Minimum clip duration | | playbackRate | number | — | Controlled playback rate | | defaultPlaybackRate | number | 1 | Initial uncontrolled rate | | onPlaybackRateChange | (rate) => void | — | Playback rate callback | | onTimeChange | (time) => void | — | Throttled playback and immediate seek callback | | onClipEnter | (clip, context) => void | — | Called when playhead enters a clip | | onClipUpdate | (clip, context) => void | — | Called during active playback | | onClipLeave | (clip, context) => void | — | Called on leave/pause/seek/unmount | | onActiveClipChange | (clipOrNull) => void | — | First active clip callback | | className | string | '' | Additional root class | | ariaLabel | string | Timeline editor | Accessible editor label |

Data schema

interface TimelineClip {
  id: string;
  start: number;
  end: number;
  type: string;
  data: { name?: string; [key: string]: unknown };
}

interface TimelineSubTrack {
  id: string;
  name: string;
  actions: TimelineClip[];
}

interface TimelineTrack {
  id: string;
  name: string;
  collapsed?: boolean;
  actions: TimelineClip[];
  subTracks?: TimelineSubTrack[];
}

Normalization accepts legacy clips containing effectId; output uses type.

Persistence and JSON files

When storageKey is set, every change is mirrored as:

{
  "version": 1,
  "duration": 120,
  "tracks": []
}

Export downloads this schema. Import accepts both the versioned document and legacy root track arrays. Malformed imports leave current state unchanged and display an error.

Total duration cannot be less than the latest clip end.

Mouse and keyboard controls

| Input | Action | | --- | --- | | Space | Play/pause outside editable fields | | Delete or Backspace | Delete selected clip outside editable fields | | Double-click empty lane | Create clip | | Drag clip | Move in time or across visible lanes | | Drag clip edge | Trim | | Right-click clip | Duplicate/Delete menu | | Click/drag ruler | Seek | | Command/Meta + wheel | Zoom around pointer | | Ctrl + wheel | Horizontal scroll | | Normal wheel | Scroll inside timeline | | Right-drag empty timeline | Pan |

Styling

Override CSS custom properties on .mini-timeline:

.my-editor {
  --mt-bg: #090b10;
  --mt-panel: #131722;
  --mt-border: #344258;
  --mt-text: #f5f7fb;
  --mt-muted: #93a3b6;
  --mt-focus: #60a5fa;
  --mt-playhead: #fb7185;
  --mt-header-width: 240px;
}

Clip colors come from clipTypes.

Browser support and accessibility

The package targets modern browsers supporting Pointer Events, requestAnimationFrame, CSS custom properties, and color-mix(). It provides accessible names, visible focus states, live status regions, keyboard guards for editable fields, and text labels in addition to clip colors.

Build and package check

npm install
npm run lint
npm run build
npm run pack-check

Publishing is intentionally not automatic. After selecting an available package name and authenticating with npm:

npm publish