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

timeline-scheduler

v0.1.0

Published

Framework-agnostic Web Component timeline scheduler with drag & drop

Readme

timeline-scheduler

A framework-agnostic Web Component for scheduling and visualising events across multiple resources on a daily timeline.

timeline-scheduler preview

  • Zero dependencies — built with Lit, works in any framework or plain HTML
  • Drag & drop — move events across resources and time with snapping
  • Resize — drag event edges to adjust duration
  • Hold to create — press and hold on empty space to draw a new event
  • Navigation — built-in prev/next day buttons with date picker and zoom controls
  • Overlap handling — overlapping events are automatically stacked in sub-rows
  • Multi-day events — events spanning midnight are clipped and shown on each day
  • Keyboard accessible — Tab between items, arrow keys to move them
  • Customisable — CSS custom properties for theming, custom item renderer, and full event hooks
  • TypeScript — fully typed

Install

npm install timeline-scheduler

Quick start

<script type="module">
  import "timeline-scheduler";
</script>

<timeline-scheduler id="tl"></timeline-scheduler>
const tl = document.getElementById("tl");

tl.resources = [
  { id: "r1", name: "Alice Johnson", avatar: "https://example.com/alice.jpg" },
  { id: "r2", name: "Bob Smith" },
];

tl.items = [
  {
    id: "i1",
    resourceId: "r1",
    name: "Team sync",
    color: "#3b82f6",
    start: new Date("2024-04-13T09:00:00"),
    end: new Date("2024-04-13T10:00:00"),
    description: "Weekly check-in",
  },
];

tl.date = new Date("2024-04-13");
tl.showNav = true;
tl.draggable = true;
tl.resizable = true;
tl.creatable = true;

// Keep data in sync after drag / resize
tl.addEventListener("change", (e) => {
  tl.items = e.detail.items;
});

// Navigate to the new date when the user clicks prev/next
tl.addEventListener("date-change", (e) => {
  tl.date = e.detail.date;
});

React

import "timeline-scheduler";
import type { TimelineScheduler } from "timeline-scheduler";

declare global {
  namespace JSX {
    interface IntrinsicElements {
      "timeline-scheduler": React.DetailedHTMLProps<
        React.HTMLAttributes<TimelineScheduler>,
        TimelineScheduler
      >;
    }
  }
}

function App() {
  const ref = useRef<TimelineScheduler>(null);

  useEffect(() => {
    const tl = ref.current!;
    tl.resources = [...];
    tl.items = [...];
    tl.showNav = true;
    tl.draggable = true;

    tl.addEventListener("change", (e: Event) => {
      tl.items = (e as CustomEvent).detail.items;
    });
  }, []);

  return <timeline-scheduler ref={ref} style={{ maxHeight: "600px" }} />;
}

Features

Navigation bar

tl.showNav = true;

Shows previous/next day buttons, a clickable date label that opens the native date picker, and zoom controls. Control individual parts:

tl.showDateNav = false; // hide prev/next + date picker
tl.showZoomControls = false; // hide zoom buttons
// nav bar auto-hides when both are false

Zoom

tl.zoom = 2; // 1 (default), 2, or 4

At zoom > 1 the timeline scrolls horizontally while resource labels and the time header stay sticky.

Hold to create

tl.creatable = true;

tl.addEventListener("item-create", (e) => {
  const { resourceId, start, end } = e.detail;
  tl.items = [
    ...tl.items,
    {
      id: crypto.randomUUID(),
      resourceId,
      name: "New event",
      color: "#64748b",
      start,
      end,
    },
  ];
});

Press and hold on empty space for ~400 ms, then drag to set the duration.

Custom item renderer

Replace the default name + time display with your own content:

tl.renderItem = (item) => {
  const el = document.createElement("span");
  el.textContent = `★ ${item.name}`;
  el.style.fontWeight = "600";
  return el;
};

tl.renderItem = null; // reset to default

Resize constraints

tl.minDurationMinutes = 30; // can't resize shorter than 30 min
tl.maxDurationMinutes = 240; // can't resize longer than 4 hours

Multi-day items

Items whose start and end span multiple days are automatically clipped to the visible day. An event from April 12 at 15:00 to April 13 at 09:00 will show as 15:00–24:00 on the 12th and 00:00–09:00 on the 13th.

Keyboard navigation

| Key | Action | | -------------------------- | ----------------------------------- | | ArrowLeft / ArrowRight | Move focused item one snap interval | | Tab / Shift+Tab | Move focus between items |

Built-in context menu

Right-clicking an item opens a context menu with Edit, Delete, and Close. Edit opens a modal to change the name, resource, and times. Disable with editable = false to handle it yourself:

tl.editable = false;

tl.addEventListener("item-contextmenu", (e) => {
  const { item, x, y } = e.detail;
  // show your own menu at (x, y)
});

Theming

timeline-scheduler {
  max-height: 600px;
  --tl-resource-col-width: 200px;
  --tl-font-family: "Inter", sans-serif;
}

Dark mode:

timeline-scheduler {
  --tl-bg: #0f172a;
  --tl-header-bg: #1e293b;
  --tl-resource-border-color: #334155;
  --tl-grid-line-color: #334155;
  --tl-text-color: #f1f5f9;
  --tl-time-label-color: #64748b;
}

Reference

Properties

Data

| Property | Type | Default | Description | | ----------- | ---------------- | ------- | ----------------------------------------------- | | resources | Resource[] | [] | Rows to display | | items | TimelineItem[] | [] | Events to render | | date | Date | today | The day shown. Items are filtered to this date. |

Time range

| Property | Type | Default | Description | | ------------- | -------- | ------- | ------------------------------------------- | | startHour | number | 0 | First visible hour (0–23) | | endHour | number | 24 | Last visible hour (1–24) | | snapMinutes | number | 15 | Snap interval in minutes during drag/resize |

Interaction

| Property | Type | Default | Description | | -------------------- | --------- | ------- | ------------------------------------------------------- | | draggable | boolean | true | Allow drag & drop | | resizable | boolean | false | Allow resizing by dragging edges | | readonly | boolean | false | Disable all interaction | | creatable | boolean | false | Allow hold-to-create on empty space | | editable | boolean | true | Allow built-in edit modal on double-click / right-click | | minDurationMinutes | number | 0 | Minimum duration during resize (0 = no limit) | | maxDurationMinutes | number | 0 | Maximum duration during resize (0 = no limit) |

Display

| Property | Type | Default | Description | | ------------------ | ------------------------------------------- | ------- | ----------------------------------------- | | showNav | boolean | false | Show the navigation bar | | showDateNav | boolean | true | Show prev/next + date picker in nav bar | | showZoomControls | boolean | true | Show zoom buttons in nav bar | | zoom | number | 1 | Horizontal zoom factor (1, 2, or 4) | | showTime | boolean | true | Show start–end time inside event blocks | | showAvatar | boolean | true | Show avatar / initials in resource column | | showEventCount | boolean | true | Show event count below resource name | | showTooltip | boolean | true | Show hover tooltip on events | | showNowLine | boolean | true | Show current-time indicator line | | renderItem | ((item: TimelineItem) => unknown) \| null | null | Custom render function for event content |


Events

| Event | Detail | Fires when | | ------------------ | ------------------------------------ | ------------------------------------------------------------- | | change | { items: TimelineItem[] } | An item is moved or resized. Always write back to tl.items. | | item-click | { item } | An item is clicked | | item-dblclick | { item } | An item is double-clicked | | item-hover | { item, type: 'enter' \| 'leave' } | Pointer enters or leaves an item | | item-contextmenu | { item, x, y } | An item is right-clicked | | item-dragstart | { item } | Drag begins | | item-dragend | { item, resourceId, start, end } | Drag released — final position | | item-resizestart | { item } | Resize begins | | item-resizeend | { item, start, end } | Resize released — final times | | item-create | { resourceId, start, end } | New item created via hold-to-create | | date-change | { date } | User navigated to a different day |


CSS custom properties

| Property | Default | Description | | ---------------------------- | ------------ | ----------------------------------------- | | --tl-font-family | sans-serif | Font used throughout the component | | --tl-bg | #ffffff | Background color | | --tl-header-bg | #f8fafc | Time header and nav bar background | | --tl-resource-col-width | 160px | Width of the resource label column | | --tl-resource-border-color | #e2e8f0 | Border between resource rows | | --tl-grid-line-color | #e2e8f0 | Hour grid lines | | --tl-text-color | #1e293b | Primary text color | | --tl-time-label-color | #94a3b8 | Hour labels in the header | | --tl-item-radius | 4px | Border radius of event blocks | | --tl-item-text-color | #ffffff | Text color inside event blocks | | --tl-item-opacity-dragging | 0.4 | Opacity of the source item while dragging | | --tl-focus-color | #3b82f6 | Focus outline color | | --tl-now-line-color | #ef4444 | Current-time indicator color | | --tl-create-ghost-color | #3b82f6 | Hold-to-create ghost block color | | --tl-tooltip-bg | #1e293b | Tooltip background | | --tl-tooltip-color | #ffffff | Tooltip text color |


TypeScript types

import type {
  Resource,
  TimelineItem,
  ChangeDetail,
  ItemClickDetail,
  ItemDblClickDetail,
  ItemHoverDetail,
  ItemContextMenuDetail,
  ItemDragStartDetail,
  ItemDragEndDetail,
  ItemResizeStartDetail,
  ItemResizeEndDetail,
  ItemCreateDetail,
  DateChangeDetail,
} from "timeline-scheduler";

interface Resource {
  id: string;
  name: string;
  avatar?: string; // URL — falls back to coloured initials
}

interface TimelineItem {
  id: string;
  resourceId: string;
  name: string;
  color: string; // any valid CSS color
  start: Date;
  end: Date;
  description?: string; // shown in the hover tooltip
}

Development

npm install
npm run dev    # playground at localhost:5173
npm test       # run tests
npm run build  # build dist/

License

MIT