@volt-package/calendar-core
v0.1.1
Published
A pure JavaScript calendar engine providing core logic without UI rendering - state management, event handling, and lifecycle
Maintainers
Readme
@volt-package/calendar-core
The core package of Volt Calendar. Provides all essential features including state management, event system, date utilities, and interaction handling.
📦 Installation
npm install @volt-package/calendar-core
# or
pnpm add @volt-package/calendar-core🏗️ Architecture
Main Components
@volt-package/calendar-core
├── VoltCalendar # Main calendar class
├── Store # State management (based on @volt-package/store)
├── EventEmitter # Pub/Sub event system
├── InteractionPlugin # Drag-and-drop, coordinate conversion
├── Types # TypeScript type definitions
├── Date Utilities # Date utility functions
└── RRULE Helpers # Recurring event helper functions🚀 Quick Start
Basic Usage
import { VoltCalendar } from '@volt-package/calendar-core';
import { MonthPlugin } from '@volt-package/calendar-month';
const calendar = new VoltCalendar({
plugins: [MonthPlugin],
initialView: 'month',
events: [
{
id: 'event-1',
title: 'Meeting',
start: '2025-12-15T10:00:00',
end: '2025-12-15T11:00:00',
},
],
});
// Subscribe to state changes
calendar.subscribe((state) => {
console.log('Calendar state updated:', state);
});
// Navigate to next month
calendar.next();
// Add event
calendar.addEvent({
id: 'event-2',
title: 'Lunch',
start: '2025-12-15T12:00:00',
end: '2025-12-15T13:00:00',
});📚 API Documentation
VoltCalendar
Main calendar class. Entry point for all calendar functionality.
Constructor
new VoltCalendar(config: VoltCalendarConfig)Config Options:
interface VoltCalendarConfig {
plugins: any[]; // View plugin array
initialView: ViewType; // 'month' | 'week' | 'day'
events: CalendarEventInput[]; // Initial event list
locale?: string; // Locale (default: 'ko-KR')
timeZone?: string; // Timezone (default: browser timezone)
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; // Week start day (0: Sunday)
}Methods
Navigation
// Navigate to next period
calendar.next(): void
// Navigate to previous period
calendar.prev(): void
// Navigate to specific date
calendar.gotoDate(date: Date): void
// Change view
calendar.changeView(viewName: 'month' | 'week' | 'day'): voidEvent Management
// Add event
calendar.addEvent(event: CalendarEventInput): void
// Update event
calendar.updateEvent(event: CalendarEventInput): void
// Remove event
calendar.removeEvent(eventId: string): voidSubscription
// Subscribe to state changes
calendar.subscribe(listener: (state: ViewState) => void): () => void
// Example usage
const unsubscribe = calendar.subscribe((state) => {
console.log('State changed:', state);
});
// Unsubscribe
unsubscribe();Low-level API
// Access Store instance
calendar.getStore(): Store
// Access EventEmitter instance
calendar.getEmitter(): EventEmitter
// Access specific plugin
calendar.getPlugin(name: string): anyStore
State management class. Built on @volt-package/store for automatic dependency tracking and immutability.
Methods
Date Management
// Get current date
store.getCurrentDate(): Date
// Set current date
store.setCurrentDate(date: Date): void
// Get current view type
store.getCurrentView(): ViewType
// Set view type
store.setCurrentView(view: ViewType): voidEvent Management
// Get all events
store.getEvents(): CalendarEvent[]
// Get events in range (including recurring event expansion)
store.getEventsInRange(rangeStart: Date, rangeEnd: Date): CalendarEvent[]
// Add event
store.addEvent(event: CalendarEventInput): void
// Update event
store.updateEvent(event: CalendarEventInput): void
// Remove event
store.removeEvent(eventId: string): void
// Set events list
store.setEvents(events: CalendarEventInput[]): voidState Management
// Get view state
store.getState(): ViewState | null
// Set view state
store.setState(state: ViewState): void
// Get configuration
store.getConfig(): VoltCalendarConfigRecurring Events
// Get count of recurrence instances
store.getRecurrenceInstanceCount(
eventId: string,
rangeStart: Date,
rangeEnd: Date
): numberEventEmitter
Pub/Sub pattern-based event system.
Event Types
type EventType =
| 'stateChange' // State change
| 'viewChange' // View change
| 'dateChange' // Date change
| 'custom'; // Custom eventMethods
// Subscribe to event
emitter.on<T>(type: EventType, listener: (data: T) => void): () => void
// Emit event
emitter.emit<T>(type: EventType, data: T): void
// Subscribe to state changes (special handler)
emitter.subscribeToState(listener: (state: ViewState) => void): () => void
// Emit state change
emitter.emitStateChange(state: ViewState): void
// Clear all subscriptions
emitter.clear(): voidUsage Example
const emitter = calendar.getEmitter();
// Subscribe to view change event
const unsubscribe = emitter.on('viewChange', (data) => {
console.log('View changed to:', data.view);
});
// Emit custom event
emitter.emit('custom', {
type: 'myCustomEvent',
payload: { foo: 'bar' },
});InteractionPlugin
Provides interaction features such as drag-and-drop and coordinate conversion.
Methods
Container Setup
// Set calendar container element
plugin.setContainerElement(element: HTMLElement): voidCoordinate Conversion
// Convert screen coordinates to date/time
plugin.getDateFromPoint(
pageX: number,
pageY: number,
viewType: string
): Date | null
// Convert screen coordinates to column index (for week view)
plugin.getColumnFromPoint(pageX: number): number | null
// Convert pixels to time (minutes)
plugin.calculateTimeFromPixels(
pixels: number,
totalHeight: number
): numberDrag and Drop
// Start drag
plugin.startDrag(event: MouseEvent, startDate: Date): void
// Move drag
plugin.moveDrag(event: MouseEvent, viewType: string): DragState
// End drag
plugin.endDrag(): DragState
// Get drag state
plugin.getDragState(): DragStateUsage Example
import { VoltCalendar, InteractionPlugin } from '@volt-package/calendar-core';
const calendar = new VoltCalendar({
plugins: [MonthPlugin, InteractionPlugin],
initialView: 'week',
events: [],
});
const interaction = calendar.getPlugin('interaction') as InteractionPlugin;
// Set container
const container = document.getElementById('calendar-container');
interaction.setContainerElement(container!);
// Implement drag and drop
container.addEventListener('mousedown', (e) => {
const date = interaction.getDateFromPoint(e.pageX, e.pageY, 'week');
if (date) {
interaction.startDrag(e, date);
}
});
container.addEventListener('mousemove', (e) => {
const dragState = interaction.moveDrag(e, 'week');
if (dragState.isDragging) {
console.log('Dragging to:', dragState.currentDate);
}
});
container.addEventListener('mouseup', () => {
const finalState = interaction.endDrag();
if (finalState.startDate && finalState.currentDate) {
console.log('Dropped:', finalState);
}
});📐 Types
CalendarEventInput
User-provided event data:
interface CalendarEventInput {
id: string;
title: string;
start: string; // ISO 8601 format
end: string; // ISO 8601 format
isAllDay?: boolean;
resourceId?: string;
rrule?: {
freq: 'daily' | 'weekly' | 'monthly' | 'yearly';
interval?: number;
until?: string;
count?: number;
};
extendedProps?: Record<string, any>;
}CalendarEvent
Extended event data processed internally:
interface CalendarEvent extends CalendarEventInput {
_id: string; // Unique identifier (for recurring instances)
_instanceDate?: Date; // Occurrence date for recurring instances
}ViewState
Final rendering state of the view:
interface ViewState {
viewType: ViewType;
currentDate: Date;
dateRange: {
start: Date;
end: Date;
};
grid: GridCell[];
events: RenderedEvent[];
}RenderedEvent
Event data for rendering:
interface RenderedEvent {
def: CalendarEventInput;
style: StyleAttributes;
isStart: boolean;
isEnd: boolean;
}
interface StyleAttributes {
top: string; // "25.5%"
height: string; // "12.5%"
left: string; // "0%"
width: string; // "50%"
zIndex: number;
}🗓️ Date Utilities
Convenient utility functions wrapping @volt-package/date.
Date Comparison
import { isSameDay, isSameWeek, isSameMonth, isToday } from '@volt-package/calendar-core';
const date1 = new Date('2025-12-15');
const date2 = new Date('2025-12-15');
isSameDay(date1, date2); // true
isSameMonth(date1, date2); // true
isToday(date1); // false (if not today)Date Range
import { startOfMonth, endOfMonth, startOfWeek, endOfWeek } from '@volt-package/calendar-core';
const date = new Date('2025-12-15');
startOfMonth(date); // 2025-12-01 00:00:00
endOfMonth(date); // 2025-12-31 23:59:59
startOfWeek(date); // First day of week
endOfWeek(date); // Last day of weekDate Arithmetic
import { addDays, addWeeks, addMonths, daysBetween } from '@volt-package/calendar-core';
const date = new Date('2025-12-15');
addDays(date, 7); // 7 days later
addWeeks(date, 2); // 2 weeks later
addMonths(date, 1); // 1 month later
const days = daysBetween(new Date('2025-12-01'), new Date('2025-12-31')); // 30Time Handling
import { getTimeInMinutes, timeToPercentage, getDateOnly } from '@volt-package/calendar-core';
const datetime = new Date('2025-12-15T14:30:00');
getTimeInMinutes(datetime); // 870 (14 * 60 + 30)
timeToPercentage(datetime); // 60.42 (14:30 is 60.42% of the day)
getDateOnly(datetime); // 2025-12-15 00:00:00Time Overlap
import { timesOverlap } from '@volt-package/calendar-core';
const overlap = timesOverlap(
new Date('2025-12-15T10:00:00'), // Event1 start
new Date('2025-12-15T12:00:00'), // Event1 end
new Date('2025-12-15T11:00:00'), // Event2 start
new Date('2025-12-15T13:00:00') // Event2 end
); // true (overlaps)🔁 RRULE Support
Supports RFC 5545 recurring events using @volt-package/rrule.
Creating Recurring Events
const event = {
id: 'recurring-1',
title: 'Weekly Meeting',
start: '2025-12-01T10:00:00',
end: '2025-12-01T11:00:00',
rrule: {
freq: 'weekly',
interval: 1,
until: '2025-12-31',
},
};
calendar.addEvent(event);Recurring Event Expansion
Store's getEventsInRange method automatically expands recurring events:
const store = calendar.getStore();
const events = store.getEventsInRange(new Date('2025-12-01'), new Date('2025-12-31'));
// events includes all recurring instancesRRULE Options
interface RRuleOptions {
freq: 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY';
dtstart?: Date; // Start date
until?: Date; // End date
count?: number; // Number of occurrences
interval?: number; // Interval
byMonth?: number[]; // Specific months
byMonthDay?: number[]; // Specific days
byDay?: Weekday[]; // Specific weekdays
bySetPos?: number[]; // Position specification
}Helper Functions
import { isRecurringEvent, generateInstanceId } from '@volt-package/calendar-core';
// Check if event is recurring
if (isRecurringEvent(event.rrule)) {
console.log('This is a recurring event');
}
// Generate instance ID
const instanceId = generateInstanceId('event-1', new Date('2025-12-15')); // "event-1__2025-12-15"🔌 External Dependencies
The core package uses these libraries:
- @volt-package/date: Lightweight date library (Day.js alternative)
- @volt-package/rrule: RFC 5545 RRULE parser
- @volt-package/store: State management with automatic dependency tracking
All libraries are zero-dependency and focused on being lightweight.
🎯 Design Philosophy
1. Plugin Architecture
Core provides minimal functionality. Views are extended through plugins.
// Core alone cannot render
const calendar = new VoltCalendar({
plugins: [], // No plugins
initialView: 'month',
events: [],
});
// Activate views by adding plugins
import { MonthPlugin } from '@volt-package/calendar-month';
const calendar = new VoltCalendar({
plugins: [MonthPlugin], // Add plugin
initialView: 'month',
events: [],
});2. Framework Independence
Core is framework-independent. Works with React, Vue, Vanilla JS, etc.
// Vanilla JS
const calendar = new VoltCalendar({
/* ... */
});
calendar.subscribe((state) => {
// Update DOM
updateCalendarDOM(state);
});
// React
function Calendar() {
const [state, setState] = useState<ViewState | null>(null);
useEffect(() => {
const calendar = new VoltCalendar({
/* ... */
});
return calendar.subscribe(setState);
}, []);
return <CalendarView state={state} />;
}3. Immutability and Reactivity
Store is based on @volt-package/store to ensure automatic dependency tracking and immutability.
// Internally immutable update
store.setCurrentDate(new Date('2025-12-15'));
// → Store automatically detects changes and notifies subscribers📄 License
MIT
🤝 Contributing
Issues and PRs are always welcome!
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
