Skip to content

Headless Core

The trud-calendar-core package provides all calendar logic with zero dependencies and zero React dependency. Use it to build calendar UIs in any framework — Vue, Svelte, Angular, vanilla JS, or server-side.

Terminal window
npm install trud-calendar-core

All TypeScript interfaces and type definitions:

import type {
CalendarEvent,
CalendarView,
CalendarState,
CalendarAction,
CalendarConfig,
CalendarSlots,
CalendarLocale,
CalendarLabels,
DateString,
DateTimeString,
PositionedEvent,
EventSegment,
RecurrenceRule,
RecurrenceDay,
TimedEventSegment,
UndoStack,
VirtualRange,
} from "trud-calendar-core";

Pure functions for date math — no side effects, fully tree-shakeable:

import {
parseDate,
toDateString,
toDateTimeString,
addDays,
addMonths,
addWeeks,
startOfWeek,
startOfMonth,
endOfMonth,
isSameDay,
isSameMonth,
isToday,
isBefore,
isAfter,
rangesOverlap,
dateInRange,
daysBetween,
eachDayOfRange,
getWeekDays,
getMonthViewRange,
getWeekViewRange,
getVisibleRange,
getTimeOfDay,
getDurationHours,
getHourLabels,
} from "trud-calendar-core";

Locale-aware formatting via the Intl API (memoized formatters):

import {
formatToolbarTitle,
formatWeekdayShort,
formatWeekdayNarrow,
formatDayNumber,
formatTime,
formatTimeRange,
formatAgendaDate,
formatMonthDay,
} from "trud-calendar-core";
formatTime("2026-03-13T14:30:00", "en-US"); // "2:30 PM"
formatTime("2026-03-13T14:30:00", "de-DE"); // "14:30"
formatAgendaDate("2026-03-13", "es-ES"); // "viernes, 13 de marzo"
formatToolbarTitle("2026-03-13", "month", "ja-JP"); // "2026年3月"

Sorting, filtering, layout algorithms:

import {
sortEvents,
filterEventsInRange,
getEventsForDay,
isMultiDayEvent,
partitionEvents,
segmentMultiDayEvent,
segmentTimedMultiDayEvent,
getEventSegments,
buildOverlapGroups,
assignColumns,
computeTimePositions,
groupEventsByDate,
} from "trud-calendar-core";

RFC 5545 RRULE expansion engine:

import {
expandRecurringEvents,
generateOccurrences,
} from "trud-calendar-core";
// Expand all recurring events in a date range
const expanded = expandRecurringEvents(events, "2026-03-01", "2026-03-31");
// Generate occurrence dates for a single rule
const dates = generateOccurrences(
{ freq: "weekly", byDay: ["MO", "WE", "FR"] },
"2026-03-01", // start date
"2026-03-01", // range start
"2026-03-31", // range end
);

See Recurrence for detailed documentation.

A generic, framework-agnostic undo stack:

import {
createUndoStack,
pushState,
undo,
redo,
canUndo,
canRedo,
} from "trud-calendar-core";
import type { UndoStack } from "trud-calendar-core";
let stack = createUndoStack(initialEvents); // UndoStack<CalendarEvent[]>
stack = pushState(stack, newEvents); // snapshot current state
stack = undo(stack); // go back
stack = redo(stack); // go forward
canUndo(stack); // boolean
canRedo(stack); // boolean

The stack is immutable — each operation returns a new UndoStack. Default max history is 30 states.

Viewport-based event filtering for large datasets:

import {
filterVisibleEvents,
scrollToViewportRange,
} from "trud-calendar-core";
import type { VirtualRange } from "trud-calendar-core";
// Filter PositionedEvent[] to only those visible in the viewport
const visible = filterVisibleEvents(positioned, viewportTop, viewportBottom, overscan);
// Convert scroll position to hour range
const range: VirtualRange = scrollToViewportRange(
scrollTop, // current scroll offset
containerHeight, // visible container height
totalHeight, // total scrollable height
dayStartHour, // e.g., 0
dayEndHour, // e.g., 24
);
// range.startHour, range.endHour

A reducer for calendar navigation state:

import {
calendarReducer,
createInitialState,
} from "trud-calendar-core";
const state = createInitialState("2026-03-13", "month");
// { currentDate: "2026-03-13", view: "month" }
const next = calendarReducer(state, { type: "NAVIGATE_NEXT" });
// { currentDate: "2026-04-13", view: "month" }

Actions: NAVIGATE_PREV, NAVIGATE_NEXT, NAVIGATE_TODAY, SET_DATE, SET_VIEW.

import {
DEFAULT_LABELS,
DEFAULT_LOCALE,
DEFAULT_VIEW,
VIEWS, // ["month", "week", "day", "agenda"]
DEFAULT_DAY_START_HOUR, // 0
DEFAULT_DAY_END_HOUR, // 24
HOURS_IN_DAY, // 24
MINUTES_IN_HOUR, // 60
MINUTES_IN_DAY, // 1440
} from "trud-calendar-core";
import {
getVisibleRange,
filterEventsInRange,
computeTimePositions,
eachDayOfRange,
formatTime,
formatWeekdayShort,
} from "trud-calendar-core";
// Get the visible date range for a week view starting Monday
const range = getVisibleRange("2026-03-13", "week", 1);
const days = eachDayOfRange(range.start, range.end);
// Filter events
const visible = filterEventsInRange(myEvents, range.start, range.end);
// Compute time-grid positions for a day
const dayEvents = visible.filter(e => e.start.startsWith("2026-03-13"));
const positioned = computeTimePositions(dayEvents, 8, 20);
// Render
for (const { event, top, height, column, totalColumns } of positioned) {
const el = document.createElement("div");
el.textContent = `${formatTime(event.start, "en-US")} ${event.title}`;
el.style.position = "absolute";
el.style.top = `${top}%`;
el.style.height = `${height}%`;
el.style.left = `${(column / totalColumns) * 100}%`;
el.style.width = `${100 / totalColumns}%`;
container.appendChild(el);
}