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

@taskgenius/calendar

v0.16.2

Published

A lightweight, configurable TypeScript calendar component with drag-and-drop support

Downloads

77

Readme

@taskgenius/calendar

npm version CI License: MIT npm downloads

Used in taskgenius/taskgenius-plugin

A lightweight, configurable TypeScript calendar component library with drag-and-drop support.

Features

  • Three view modes - month, week, and day views with dedicated all-day lane
  • Extensible view system - register custom views or extend built-ins
  • Cross-midnight & multi-day support - timed/all-day events render and drag correctly across days
  • Drag-and-drop/resize - move or resize events with optional date-only mode
  • Flexible layout controls - first day of week, hide weekends, custom day/time filters
  • Overflow handling - event count badges plus configurable "+N more" popover renderer
  • Custom rendering hooks - date cells, event styling, event content, and popovers
  • Lightweight - <12KB gzipped with zero runtime deps
  • Pluggable adapters - Day.js by default, custom adapters supported
  • TypeScript first - complete type definitions and SOLID architecture

Installation

npm install @taskgenius/calendar dayjs

Quick Start

import { Calendar } from '@taskgenius/calendar';
import '@taskgenius/calendar/styles.css'; // Required - component uses CSS classes

// Method 1: Pass CSS selector string
const calendar = new Calendar('#app', {
  view: { type: 'week' },
  events: [
    {
      id: '1',
      title: 'Team Meeting',
      start: '2025-11-20 10:00',
      end: '2025-11-20 11:30',
      color: '#3b82f6'
    }
  ],
  onEventClick: (event) => {
    console.log('Clicked:', event.title);
  },
  onEventDrop: (event, newStart, newEnd) => {
    console.log('Moved:', event.title, newStart.toISOString(), newEnd.toISOString());
  }
});

// Method 2: Pass HTMLElement directly
const container = document.getElementById('app');
const calendar2 = new Calendar(container, {
  view: { type: 'month' }
});

Tree Shaking & Optimization

For users who want to minimize bundle size, you can use CalendarCore instead of Calendar. The CalendarCore class does not import any built-in views by default, allowing your bundler to exclude unused view code.

Using Calendar (Recommended)

The default Calendar class provides a "batteries-included" experience with automatic view registration:

import { Calendar } from '@taskgenius/calendar';
import '@taskgenius/calendar/styles.css';

// All built-in views (Month, Week, Day) are automatically available
const calendar = new Calendar('#app', {
  view: { type: 'month' }
});

Using CalendarCore (Advanced - Smaller Bundle)

For optimal bundle size, use CalendarCore with explicit view registration:

import { CalendarCore, MonthView, ViewRegistry } from '@taskgenius/calendar';
import '@taskgenius/calendar/styles.css';

// Only imports MonthView code - Week and Day views are tree-shaken
const calendar = new CalendarCore('#app', {
  view: { type: 'month' },
  viewRegistry: new ViewRegistry().register(MonthView)
});

Multiple views example:

import { CalendarCore, MonthView, WeekView, ViewRegistry } from '@taskgenius/calendar';

const registry = new ViewRegistry()
  .register(MonthView)
  .register(WeekView);

const calendar = new CalendarCore('#app', {
  view: { type: 'week' },
  viewRegistry: registry
});

When to Use Each

  • Use Calendar: Quick start, prototyping, or when bundle size is not a concern
  • Use CalendarCore: Production builds where bundle optimization is important

Styles

Important: The component requires @taskgenius/calendar/styles.css to render correctly. Since v0.4.0, all layout and styling use external CSS classes instead of inline styles.

Import options:

  • ES modules: import '@taskgenius/calendar/styles.css'; (recommended)
  • HTML link: <link rel="stylesheet" href="/node_modules/@taskgenius/calendar/dist/styles.css">

Customization:

  • Theme settings are delivered via CSS variables: --tg-primary-color, --tg-primary-rgb, --tg-cell-height, --tg-font-header, --tg-font-event. The library sets these on the .tg-calendar root; you can override them in your own styles.
  • To fully customize the look, you can override the default CSS or provide your own styles using the exposed tg-* class names.

API Reference

Calendar Class

The main entry point for the calendar component. This class extends CalendarCore and automatically registers all built-in views (Month, Week, Day) for a "batteries-included" experience. Perfect for quick starts and when bundle size is not a primary concern.

For advanced users: Use CalendarCore directly for a leaner, tree-shakeable version that requires manual view registration. See the Tree Shaking & Optimization section for details.

Constructor

new Calendar(container: string | HTMLElement, config?: CalendarConfig)

Parameters:

  • container - CSS selector string (e.g., '#app') or HTMLElement reference
  • config - Optional configuration object

Example:

// Using CSS selector
const cal1 = new Calendar('#calendar', { view: { type: 'week' } });

// Using HTMLElement
const element = document.getElementById('calendar');
const cal2 = new Calendar(element, { view: { type: 'week' } });

Methods

| Method | Description | |--------|-------------| | registerView(ViewClass, options?) | Register a custom view (class must expose static meta) | | unregisterView(type) | Remove a registered view type | | getRegisteredViews() | Get metadata for all registered views | | getViewRegistry() | Access the view registry instance | | hasView(type) | Check whether a view type is registered | | setView(type: ViewType \| string) | Switch between registered views (built-in or custom) | | getView() | Get current view type | | getActiveView() | Get the active view instance | | addEvent(event: CalendarEvent) | Add a new event | | removeEvent(id: string) | Remove event by ID | | updateEvent(id: string, updates: Partial<CalendarEvent>) | Update event properties | | setEvents(events: CalendarEvent[]) | Replace all events | | getEvents() | Get all events | | next() | Navigate to next period | | prev() | Navigate to previous period | | today() | Navigate to today | | goToDate(date: string \| Date) | Navigate to specific date | | getCurrentDate() | Get current displayed date (ISO string) | | setDraggable(enabled: boolean) | Enable or disable drag-and-drop at runtime | | isDraggable() | Check whether drag-and-drop is enabled | | refresh() | Force re-render | | destroy() | Cleanup and remove calendar |

Configuration

interface CalendarConfig {
  view?: ViewConfig;
  events?: CalendarEvent[];
  draggable?: DraggableConfig;
  theme?: ThemeConfig;
  dateAdapter?: DateAdapter<unknown>;  // Custom date adapter (default: Day.js)
  dateFormats?: Partial<DateFormatConfig>;  // Custom date display formats (unicode tokens recommended)
  headerFormat?: {                    // Deprecated: mapped to dateFormats
    month?: string;
    day?: string;
  };
  showEventCounts?: boolean;          // Default: false - Show event count badges on date cells
  
  // Event interactions
  onEventClick?: (event: CalendarEvent) => void;
  onEventDoubleClick?: (event: CalendarEvent) => void;
  onEventContextMenu?: (event: CalendarEvent, x: number, y: number) => void;
  onEventDrop?: (event: CalendarEvent, newStart: Date, newEnd: Date) => void;    // v0.8.0+: Date objects
  onEventResize?: (event: CalendarEvent, newStart: Date, newEnd: Date) => void;  // v0.9.0+: Resize callback
  
  // View and navigation
  onViewChange?: (viewType: ViewType) => void;
  onDateChange?: (date: Date) => void;
  
  // Date cell interactions (month view)
  onDateClick?: (date: Date) => void;
  onDateDoubleClick?: (date: Date) => void;
  onDateContextMenu?: (date: Date, x: number, y: number) => void;
  
  // Time slot interactions (week/day view)
  onTimeSlotClick?: (dateTime: Date) => void;
  onTimeSlotDoubleClick?: (dateTime: Date) => void;
  onTimeSlotContextMenu?: (dateTime: Date, x: number, y: number) => void;
  
  // Range selection (drag to select multiple cells)
  onDateRangeSelect?: (startDate: Date, endDate: Date) => void;
  onTimeRangeSelect?: (startDateTime: Date, endDateTime: Date) => void;
  
  // Rendering hooks
  onRenderDateCell?: (ctx: DateCellContext) => void;      // Custom date cell rendering
  onStyleEvent?: (event: CalendarEvent) => EventStyle;    // Custom event styling
  onRenderEvent?: (ctx: EventRenderContext) => void;      // v0.12.0+: Custom event content
  onRenderMoreEventsPopover?: (                          // v0.10.0+: Custom "+N more" popover renderer
    events: CalendarEvent[],
    date: Date,
    anchorEl: HTMLElement,
    defaultRender: () => void
  ) => void;
}

For custom view registries, the constructor also accepts:

interface ExtendedCalendarConfig extends CalendarConfig {
  viewRegistry?: ViewRegistry;       // Provide a custom registry
  registerBuiltInViews?: boolean;    // Default: true - auto-register Month/Week/Day views
}

ViewConfig

interface ViewConfig {
  type: 'month' | 'week' | 'day';   // Default: 'week'
  showDateHeader?: boolean;          // Default: true (time views)
  showWeekNumbers?: boolean;         // Default: false (month view)
  firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6;  // Default: 0 (supports full 0-6 range)
  showWeekends?: boolean;            // Default: true (false is converted to a dayFilter)
  maxEventsPerRow?: number;          // Month view: cap events per row before showing "+N more"
  dayFilter?: (date: unknown, ctx: DayFilterContext) => DayFilterResult; // Hide/customize days
  timeFilter?: (hour: number) => TimeFilterResult;   // Hide/customize time slots
  timeFormatter?: TimeFormatter;     // Custom time axis labels
}

dayFilter/timeFilter can return boolean or config objects (DayRenderConfig / TimeSlotConfig). Setting DayRenderConfig.disabled keeps the cell visible but hides events for that date.

DraggableConfig

interface DraggableConfig {
  enabled: boolean;      // Default: true
  snapMinutes?: number;  // Default: 15
  ghostOpacity?: number; // Default: 0.5
  dateOnly?: boolean;    // Default: false - Only adjust dates, keep time unchanged
}

ThemeConfig

interface ThemeConfig {
  primaryColor?: string;  // Default: '#3b82f6'
  cellHeight?: number;    // Default: 60 (pixels per hour)
  fontSize?: {
    header?: string;      // Default: '14px'
    event?: string;       // Default: '12px'
  };
}

CalendarEvent

interface CalendarEvent {
  id: string;                        // Unique identifier
  title: string;                     // Display title
  start: string;                     // ISO format: 'yyyy-MM-dd HH:mm' (supports cross-midnight spans)
  end: string;                       // ISO format: 'yyyy-MM-dd HH:mm' (use 00:00/23:59 for all-day)
  color?: string;                    // CSS color value
  metadata?: Record<string, unknown>; // Custom data
}

Examples

Basic Usage

import { Calendar } from '@taskgenius/calendar';
import '@taskgenius/calendar/styles.css'; // Required

// Initialize with CSS selector
const calendar = new Calendar('#calendar-container');

// Or initialize with DOM element
const element = document.querySelector('.my-calendar');
const calendar2 = new Calendar(element);

// Add event
calendar.addEvent({
  id: '1',
  title: 'Meeting',
  start: '2025-11-20 10:00',
  end: '2025-11-20 11:30',
  color: '#3b82f6'
});

// Switch view
calendar.setView('month');

// Navigate
calendar.next();
calendar.prev();
calendar.today();

// Clean up when done (important to prevent memory leaks)
calendar.destroy();

With Callbacks

const calendar = new Calendar('#app', {
  events: myEvents,
  onEventClick: (event) => {
    showEventDetails(event);
  },
  onEventDrop: (event, newStart, newEnd) => {
    // Event was moved to a new position
    console.log('Event moved:', event.title);
    saveEventToServer(event.id, { 
      start: newStart.toISOString(), 
      end: newEnd.toISOString() 
    });
  },
  onEventResize: (event, newStart, newEnd) => {
    // Event duration was changed
    console.log('Event resized:', event.title);
    saveEventToServer(event.id, { 
      start: newStart.toISOString(), 
      end: newEnd.toISOString() 
    });
  },
  onViewChange: (view) => {
    analytics.track('view_changed', { view });
  }
});

Custom Theme

const calendar = new Calendar('#app', {
  theme: {
    primaryColor: '#8b5cf6',
    cellHeight: 80,
    fontSize: {
      header: '16px',
      event: '14px'
    }
  }
});

Disable Drag-and-Drop

const calendar = new Calendar('#app', {
  draggable: {
    enabled: false
  }
});

Date-Only Drag Mode

const calendar = new Calendar('#app', {
  draggable: {
    enabled: true,
    dateOnly: true  // Only adjust dates, preserve original time
  }
});

Week Configuration

const calendar = new Calendar('#app', {
  view: {
    type: 'week',
    firstDayOfWeek: 1,  // Start week on Monday
    showWeekends: false  // Hide Saturday and Sunday
  }
});

Day/Time Filtering

const calendar = new Calendar('#app', {
  view: {
    type: 'week',
    firstDayOfWeek: 1,
    dayFilter: (_date, ctx) => ctx.isWeekend ? { visible: false } : true, // Hide weekends with config
    timeFilter: (hour) => hour >= 8 && hour < 18,  // Working hours only
    timeFormatter: (hour) => `${hour}:00`          // Custom time axis labels
  }
});

Event Count Badges

const calendar = new Calendar('#app', {
  view: { type: 'month' },
  showEventCounts: true  // Display event count on each date cell
});

Month Overflow & "+N more" Popover (v0.10.0+)

const calendar = new Calendar('#app', {
  view: {
    type: 'month',
    maxEventsPerRow: 3
  },
  onRenderMoreEventsPopover: (events, date, anchorEl, defaultRender) => {
    console.log('Hidden events on', date.toISOString(), events.length);
    defaultRender(); // Or render a custom popover
  }
});

User Interactions (v0.7.0+)

const calendar = new Calendar('#app', {
  view: { type: 'month' },
  
  // Date cell interactions (month view)
  onDateClick: (date) => {
    console.log('Clicked date:', date); // Date object
    showCreateEventDialog(date);
  },
  
  onDateDoubleClick: (date) => {
    console.log('Double-clicked date:', date);
    createQuickEvent(date);
  },
  
  onDateContextMenu: (date, x, y) => {
    console.log('Right-clicked date:', date, 'at position:', x, y);
    showContextMenu(date, x, y);
  },
  
  // Range selection (drag to select)
  onDateRangeSelect: (startDate, endDate) => {
    console.log('Selected range:', startDate, 'to', endDate);
    createMultiDayEvent(startDate, endDate);
  }
});

// For week/day views with time slots
const weekCalendar = new Calendar('#app', {
  view: { type: 'week' },
  
  // Time slot interactions
  onTimeSlotClick: (dateTime) => {
    console.log('Clicked time slot:', dateTime); // Date object with time
    createEventAt(dateTime);
  },
  
  onTimeSlotDoubleClick: (dateTime) => {
    console.log('Double-clicked time slot:', dateTime);
    quickCreateEvent(dateTime);
  },
  
  onTimeSlotContextMenu: (dateTime, x, y) => {
    console.log('Right-clicked time slot:', dateTime);
    showTimeSlotMenu(dateTime, x, y);
  },
  
  // Time range selection (drag to select multiple slots)
  onTimeRangeSelect: (startDateTime, endDateTime) => {
    console.log('Selected time range:', startDateTime, 'to', endDateTime);
    createTimedEvent(startDateTime, endDateTime);
  }
});

Custom Date Formats (v0.8.0+)

const calendar = new Calendar('#app', {
  // Customize date display formats (uses Unicode tokens)
  dateFormats: {
    date: 'yyyy/MM/dd',           // Default: 'yyyy-MM-dd'
    dateTime: 'yyyy/MM/dd HH:mm', // Default: 'yyyy-MM-dd HH:mm'
    time: 'HH:mm',                // Default: 'HH:mm'
    monthHeader: 'MMMM yyyy',     // Default: 'yyyy\u5e74M\u6708'
    dayHeader: 'MMMM d, yyyy'     // Default: 'yyyy\u5e74M\u6708d\u65e5'
  }
});

Note: Date format tokens use Unicode standard (compatible with date-fns, Day.js, and native adapter):

  • Year: yyyy (2025), yy (25)
  • Month: MM (01-12), M (1-12), MMMM (January), MMM (Jan)
  • Day: dd (01-31), d (1-31)
  • Hour: HH (00-23), H (0-23)
  • Minute: mm (00-59), m (0-59)
  • Legacy headerFormat is deprecated; it maps to dateFormats.monthHeader/dayHeader for backward compatibility.

Custom Date Cell Rendering

const calendar = new Calendar('#app', {
  onRenderDateCell: (ctx) => {
    // Add custom badge for past due dates with events
    if (ctx.isPastDue && ctx.events.length > 0) {
      const badge = document.createElement('div');
      badge.className = 'overdue-badge';
      badge.textContent = '!';
      ctx.cellEl.appendChild(badge);
    }
    
    // Add custom class for weekends
    if (ctx.date.getDay() === 0 || ctx.date.getDay() === 6) {
      ctx.cellEl.classList.add('weekend');
    }
  }
});

Add corresponding CSS for custom elements:

/* Style custom overdue badge */
.overdue-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  background: #ef4444;
  color: white;
  border-radius: 50%;
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: bold;
}

/* Style weekend cells */
.weekend {
  background-color: #fef3c7;
}

Custom Event Styling

const calendar = new Calendar('#app', {
  onStyleEvent: (event) => {
    // Style based on metadata
    const priority = event.metadata?.priority as number;
    const isCompleted = event.metadata?.completed as boolean;
    
    return {
      color: priority >= 2 ? '#ef4444' : '#3b82f6',
      opacity: isCompleted ? 0.5 : 1,
      className: isCompleted ? 'completed-event' : ''
    };
  }
});

Add CSS for custom event classes (optional):

/* Additional styling for completed events */
.completed-event {
  text-decoration: line-through;
}

Custom Event Rendering (v0.12.0+)

const calendar = new Calendar('#app', {
  onRenderEvent: (ctx) => {
    ctx.defaultRender(); // Keep default title/time rendering

    if (ctx.event.metadata?.priority === 'high') {
      const badge = document.createElement('span');
      badge.className = 'priority-badge';
      badge.textContent = '!';
      ctx.el.appendChild(badge);
    }
  }
});

Architecture

The library follows SOLID principles with a modular architecture:

src/
├── core/           # Main Calendar class, EventManager, DragController
├── adapters/       # Date library adapters (DayJs, etc.)
├── engines/        # Layout calculation (MonthEngine, TimeEngine)
├── renderers/      # DOM rendering (MonthRenderer, TimeRenderer)
├── styles/         # Static CSS + theme variable helpers (no auto-injection)
├── types/          # TypeScript type definitions
└── utils/          # DOM utilities

Key Components

  • Calendar - Main API and orchestration
  • EventManager - CRUD operations for events
  • DragController - Drag-and-drop interactions
  • MonthEngine/TimeEngine - Layout calculations
  • MonthRenderer/TimeRenderer - DOM generation
  • DateAdapter - Pluggable date library interface

Testing

# Run tests
npm test

# Run tests with UI
npm run test:ui

# Run tests with coverage
npm run test:coverage

Best Practices

Memory Management

Always call destroy() when you no longer need the calendar instance to prevent memory leaks:

// In React
useEffect(() => {
  const calendar = new Calendar(containerRef.current, config);
  
  return () => {
    calendar.destroy(); // Cleanup on unmount
  };
}, []);

// In Vue
onMounted(() => {
  calendar = new Calendar(el.value, config);
});

onUnmounted(() => {
  calendar.destroy(); // Cleanup on unmount
});

// In vanilla JS
function createCalendar() {
  const calendar = new Calendar('#app', config);
  
  // When removing the calendar
  function cleanup() {
    calendar.destroy();
    document.getElementById('app').innerHTML = '';
  }
  
  return { calendar, cleanup };
}

Initialization Options

You can initialize the calendar using either a CSS selector or a direct DOM element reference:

// Option 1: CSS Selector (simple and convenient)
const calendar = new Calendar('#calendar', config);

// Option 2: DOM Element (useful in frameworks)
const container = document.getElementById('calendar');
const calendar = new Calendar(container, config);

// Option 3: Dynamic element (e.g., in React with refs)
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
  if (containerRef.current) {
    const calendar = new Calendar(containerRef.current, config);
    return () => calendar.destroy();
  }
}, []);

Development

# Install dependencies
npm install

# Run demo
npm run demo

# Build library
npm run build

# Type check
npx tsc --noEmit

Project Structure

@taskgenius/calendar/
├── src/                 # Source code
├── tests/               # Test files
│   ├── unit/           # Unit tests
│   ├── integration/    # Integration tests
│   └── fixtures/       # Test data
├── examples/
│   └── demo/           # Vite demo project
├── dist/               # Built output
└── docs/               # Documentation

Date Adapters

The library uses a pluggable date adapter system. By default, it uses Day.js.

Using Day.js (Default)

import { Calendar } from '@taskgenius/calendar';
// Automatically uses DayJsAdapter

Custom Adapter

Implement the DateAdapter interface to use a different date library:

interface DateAdapter<T> {
  create(date?: string | Date | T): T;
  parse(dateStr: string, format?: string): T;
  format(date: T, format: string): string;
  // ... other methods
}

License

MIT ©TaskGenius

Contributing

Contributions are welcome! Please read the contributing guidelines before submitting a PR.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request