Adds comprehensive README for calendar package
Introduces detailed documentation for calendar library, covering: - Core features and architecture - Installation and setup guide - Event system reference - Types and configuration options - Extensibility and utilities Prepares for initial library release with thorough documentation
This commit is contained in:
parent
ceb44446f0
commit
7db22245e2
2 changed files with 621 additions and 1 deletions
620
packages/calendar/README.md
Normal file
620
packages/calendar/README.md
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
# Calendar
|
||||
|
||||
Professional TypeScript calendar component with offline-first architecture, drag-and-drop functionality, and real-time synchronization capabilities.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple View Modes**: Date-based (day/week/month) and resource-based (people, rooms) views
|
||||
- **Drag & Drop**: Smooth event dragging with snap-to-grid, cross-column movement, and timed/all-day conversion
|
||||
- **Event Resizing**: Intuitive resize handles for adjusting event duration
|
||||
- **Offline-First**: IndexedDB storage with automatic background sync
|
||||
- **Event-Driven Architecture**: Decoupled components via centralized EventBus
|
||||
- **Dependency Injection**: Built on NovaDI for clean, testable architecture
|
||||
- **Extensions**: Modular extensions for teams, departments, bookings, customers, schedules, and audit logging
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install calendar
|
||||
```
|
||||
|
||||
## Quick Start (AI-Friendly Setup Guide)
|
||||
|
||||
### Step 1: Create HTML Structure
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="node_modules/calendar/dist/css/calendar.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="calendar-wrapper">
|
||||
<swp-calendar-container>
|
||||
<swp-time-axis>
|
||||
<swp-header-spacer></swp-header-spacer>
|
||||
<swp-time-axis-content id="time-axis"></swp-time-axis-content>
|
||||
</swp-time-axis>
|
||||
<swp-grid-container>
|
||||
<swp-header-viewport>
|
||||
<swp-header-track>
|
||||
<swp-calendar-header></swp-calendar-header>
|
||||
</swp-header-track>
|
||||
<swp-header-drawer></swp-header-drawer>
|
||||
</swp-header-viewport>
|
||||
<swp-content-viewport>
|
||||
<swp-content-track>
|
||||
<swp-scrollable-content>
|
||||
<swp-time-grid>
|
||||
<swp-grid-lines></swp-grid-lines>
|
||||
<swp-day-columns></swp-day-columns>
|
||||
</swp-time-grid>
|
||||
</swp-scrollable-content>
|
||||
</swp-content-track>
|
||||
</swp-content-viewport>
|
||||
</swp-grid-container>
|
||||
</swp-calendar-container>
|
||||
</div>
|
||||
|
||||
<script type="module" src="dist/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Step 2: Initialize Calendar
|
||||
|
||||
```typescript
|
||||
import { Container } from '@novadi/core';
|
||||
import {
|
||||
registerCoreServices,
|
||||
CalendarApp,
|
||||
IndexedDBContext,
|
||||
SettingsService,
|
||||
ViewConfigService,
|
||||
EventService,
|
||||
EventBus,
|
||||
CalendarEvents
|
||||
} from 'calendar';
|
||||
|
||||
async function init() {
|
||||
// 1. Create DI container and register services
|
||||
const container = new Container();
|
||||
const builder = container.builder();
|
||||
registerCoreServices(builder, {
|
||||
dbConfig: { dbName: 'MyCalendarDB', dbVersion: 1 }
|
||||
});
|
||||
const app = builder.build();
|
||||
|
||||
// 2. Initialize IndexedDB
|
||||
const dbContext = app.resolveType<IndexedDBContext>();
|
||||
await dbContext.initialize();
|
||||
|
||||
// 3. Seed required settings (first time only)
|
||||
const settingsService = app.resolveType<SettingsService>();
|
||||
const viewConfigService = app.resolveType<ViewConfigService>();
|
||||
|
||||
await settingsService.save({
|
||||
id: 'grid',
|
||||
dayStartHour: 8,
|
||||
dayEndHour: 17,
|
||||
workStartHour: 9,
|
||||
workEndHour: 16,
|
||||
hourHeight: 64,
|
||||
snapInterval: 15,
|
||||
syncStatus: 'synced'
|
||||
});
|
||||
|
||||
await settingsService.save({
|
||||
id: 'workweek',
|
||||
presets: {
|
||||
standard: { id: 'standard', label: 'Standard', workDays: [1, 2, 3, 4, 5], periodDays: 7 }
|
||||
},
|
||||
defaultPreset: 'standard',
|
||||
firstDayOfWeek: 1,
|
||||
syncStatus: 'synced'
|
||||
});
|
||||
|
||||
await viewConfigService.save({
|
||||
id: 'simple',
|
||||
groupings: [{ type: 'date', values: [], idProperty: 'date', derivedFrom: 'start' }],
|
||||
syncStatus: 'synced'
|
||||
});
|
||||
|
||||
// 4. Initialize CalendarApp
|
||||
const calendarApp = app.resolveType<CalendarApp>();
|
||||
const containerEl = document.querySelector('swp-calendar-container') as HTMLElement;
|
||||
await calendarApp.init(containerEl);
|
||||
|
||||
// 5. Render a view
|
||||
const eventBus = app.resolveType<EventBus>();
|
||||
eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: 'simple' });
|
||||
}
|
||||
|
||||
init().catch(console.error);
|
||||
```
|
||||
|
||||
### Step 3: Add Events
|
||||
|
||||
```typescript
|
||||
const eventService = app.resolveType<EventService>();
|
||||
|
||||
await eventService.save({
|
||||
id: crypto.randomUUID(),
|
||||
title: 'Meeting',
|
||||
start: new Date('2024-01-15T09:00:00'),
|
||||
end: new Date('2024-01-15T10:00:00'),
|
||||
type: 'meeting',
|
||||
allDay: false,
|
||||
syncStatus: 'synced'
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
| Component | Description |
|
||||
|-----------|-------------|
|
||||
| `CalendarApp` | Main application entry point |
|
||||
| `CalendarOrchestrator` | Coordinates rendering pipeline |
|
||||
| `EventBus` | Central event dispatcher for all inter-component communication |
|
||||
| `DateService` | Date calculations and formatting |
|
||||
| `IndexedDBContext` | Offline storage infrastructure |
|
||||
|
||||
### Managers
|
||||
|
||||
| Manager | Description |
|
||||
|---------|-------------|
|
||||
| `DragDropManager` | Event drag-drop with smooth animations and snap-to-grid |
|
||||
| `EdgeScrollManager` | Automatic scrolling at viewport edges during drag |
|
||||
| `ResizeManager` | Event resizing with visual feedback |
|
||||
| `ScrollManager` | Scroll behavior and position management |
|
||||
| `HeaderDrawerManager` | All-day events drawer toggle |
|
||||
| `EventPersistenceManager` | Saves drag/resize changes to storage |
|
||||
|
||||
### Renderers
|
||||
|
||||
| Renderer | Description |
|
||||
|----------|-------------|
|
||||
| `DateRenderer` | Renders date-based column groupings |
|
||||
| `ResourceRenderer` | Renders resource-based column groupings |
|
||||
| `EventRenderer` | Renders timed events in columns |
|
||||
| `ScheduleRenderer` | Renders working hours backgrounds |
|
||||
| `HeaderDrawerRenderer` | Renders all-day events in header |
|
||||
| `TimeAxisRenderer` | Renders time labels on the left axis |
|
||||
|
||||
### Storage
|
||||
|
||||
| Service | Description |
|
||||
|---------|-------------|
|
||||
| `EventService` / `EventStore` | Calendar event CRUD |
|
||||
| `ResourceService` / `ResourceStore` | Resource management |
|
||||
| `SettingsService` / `SettingsStore` | Tenant settings |
|
||||
| `ViewConfigService` / `ViewConfigStore` | View configurations |
|
||||
|
||||
---
|
||||
|
||||
## Events Reference
|
||||
|
||||
### Lifecycle Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `core:initialized` | `CoreEvents.INITIALIZED` | - | Calendar core initialized |
|
||||
| `core:ready` | `CoreEvents.READY` | - | Calendar ready for interaction |
|
||||
| `core:destroyed` | `CoreEvents.DESTROYED` | - | Calendar destroyed |
|
||||
|
||||
### View Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `view:changed` | `CoreEvents.VIEW_CHANGED` | `{ viewId: string }` | View type changed |
|
||||
| `view:rendered` | `CoreEvents.VIEW_RENDERED` | - | View finished rendering |
|
||||
|
||||
### Navigation Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `nav:date-changed` | `CoreEvents.DATE_CHANGED` | `{ date: Date }` | Current date changed |
|
||||
| `nav:navigation-completed` | `CoreEvents.NAVIGATION_COMPLETED` | - | Navigation animation completed |
|
||||
|
||||
### Data Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `data:loading` | `CoreEvents.DATA_LOADING` | - | Data loading started |
|
||||
| `data:loaded` | `CoreEvents.DATA_LOADED` | - | Data loading completed |
|
||||
| `data:error` | `CoreEvents.DATA_ERROR` | `{ error: Error }` | Data loading error |
|
||||
|
||||
### Grid Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `grid:rendered` | `CoreEvents.GRID_RENDERED` | - | Grid finished rendering |
|
||||
| `grid:clicked` | `CoreEvents.GRID_CLICKED` | `{ time: Date, columnKey: string }` | Grid area clicked |
|
||||
|
||||
### Event Management
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `event:created` | `CoreEvents.EVENT_CREATED` | `ICalendarEvent` | Event created |
|
||||
| `event:updated` | `CoreEvents.EVENT_UPDATED` | `IEventUpdatedPayload` | Event updated |
|
||||
| `event:deleted` | `CoreEvents.EVENT_DELETED` | `{ eventId: string }` | Event deleted |
|
||||
| `event:selected` | `CoreEvents.EVENT_SELECTED` | `{ eventId: string }` | Event selected |
|
||||
|
||||
### Drag-Drop Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `event:drag-start` | `CoreEvents.EVENT_DRAG_START` | `IDragStartPayload` | Drag started |
|
||||
| `event:drag-move` | `CoreEvents.EVENT_DRAG_MOVE` | `IDragMovePayload` | Dragging (throttled) |
|
||||
| `event:drag-end` | `CoreEvents.EVENT_DRAG_END` | `IDragEndPayload` | Drag completed |
|
||||
| `event:drag-cancel` | `CoreEvents.EVENT_DRAG_CANCEL` | `IDragCancelPayload` | Drag cancelled |
|
||||
| `event:drag-column-change` | `CoreEvents.EVENT_DRAG_COLUMN_CHANGE` | `IDragColumnChangePayload` | Moved to different column |
|
||||
|
||||
### Header Drag Events (Timed to All-Day Conversion)
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `event:drag-enter-header` | `CoreEvents.EVENT_DRAG_ENTER_HEADER` | `IDragEnterHeaderPayload` | Entered header area |
|
||||
| `event:drag-move-header` | `CoreEvents.EVENT_DRAG_MOVE_HEADER` | `IDragMoveHeaderPayload` | Moving in header area |
|
||||
| `event:drag-leave-header` | `CoreEvents.EVENT_DRAG_LEAVE_HEADER` | `IDragLeaveHeaderPayload` | Left header area |
|
||||
|
||||
### Resize Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `event:resize-start` | `CoreEvents.EVENT_RESIZE_START` | `IResizeStartPayload` | Resize started |
|
||||
| `event:resize-end` | `CoreEvents.EVENT_RESIZE_END` | `IResizeEndPayload` | Resize completed |
|
||||
|
||||
### Edge Scroll Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `edge-scroll:tick` | `CoreEvents.EDGE_SCROLL_TICK` | `{ deltaY: number }` | Scroll tick during edge scroll |
|
||||
| `edge-scroll:started` | `CoreEvents.EDGE_SCROLL_STARTED` | - | Edge scrolling started |
|
||||
| `edge-scroll:stopped` | `CoreEvents.EDGE_SCROLL_STOPPED` | - | Edge scrolling stopped |
|
||||
|
||||
### Sync Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `sync:started` | `CoreEvents.SYNC_STARTED` | - | Background sync started |
|
||||
| `sync:completed` | `CoreEvents.SYNC_COMPLETED` | - | Background sync completed |
|
||||
| `sync:failed` | `CoreEvents.SYNC_FAILED` | `{ error: Error }` | Background sync failed |
|
||||
|
||||
### Entity Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `entity:saved` | `CoreEvents.ENTITY_SAVED` | `IEntitySavedPayload` | Entity saved to storage |
|
||||
| `entity:deleted` | `CoreEvents.ENTITY_DELETED` | `IEntityDeletedPayload` | Entity deleted from storage |
|
||||
|
||||
### Audit Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `audit:logged` | `CoreEvents.AUDIT_LOGGED` | `IAuditLoggedPayload` | Audit entry logged |
|
||||
|
||||
### Rendering Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `events:rendered` | `CoreEvents.EVENTS_RENDERED` | - | Events finished rendering |
|
||||
|
||||
### System Events
|
||||
|
||||
| Event | Constant | Payload | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `system:error` | `CoreEvents.ERROR` | `{ error: Error, context?: string }` | System error occurred |
|
||||
|
||||
---
|
||||
|
||||
## Command Events (Host to Calendar)
|
||||
|
||||
Use these to control the calendar from your application:
|
||||
|
||||
```typescript
|
||||
import { EventBus, CalendarEvents } from 'calendar';
|
||||
|
||||
const eventBus = app.resolveType<EventBus>();
|
||||
|
||||
// Navigate
|
||||
eventBus.emit(CalendarEvents.CMD_NAVIGATE_PREV);
|
||||
eventBus.emit(CalendarEvents.CMD_NAVIGATE_NEXT);
|
||||
|
||||
// Render a view
|
||||
eventBus.emit(CalendarEvents.CMD_RENDER, { viewId: 'simple' });
|
||||
|
||||
// Toggle header drawer
|
||||
eventBus.emit(CalendarEvents.CMD_DRAWER_TOGGLE);
|
||||
|
||||
// Change workweek preset
|
||||
eventBus.emit(CalendarEvents.CMD_WORKWEEK_CHANGE, { presetId: 'standard' });
|
||||
|
||||
// Update view grouping
|
||||
eventBus.emit(CalendarEvents.CMD_VIEW_UPDATE, { type: 'resource', values: ['r1', 'r2'] });
|
||||
```
|
||||
|
||||
| Command | Constant | Payload | Description |
|
||||
|---------|----------|---------|-------------|
|
||||
| Navigate Previous | `CalendarEvents.CMD_NAVIGATE_PREV` | - | Go to previous period |
|
||||
| Navigate Next | `CalendarEvents.CMD_NAVIGATE_NEXT` | - | Go to next period |
|
||||
| Render View | `CalendarEvents.CMD_RENDER` | `{ viewId: string }` | Render specified view |
|
||||
| Toggle Drawer | `CalendarEvents.CMD_DRAWER_TOGGLE` | - | Toggle all-day drawer |
|
||||
| Change Workweek | `CalendarEvents.CMD_WORKWEEK_CHANGE` | `{ presetId: string }` | Change workweek preset |
|
||||
| Update View | `CalendarEvents.CMD_VIEW_UPDATE` | `{ type: string, values: string[] }` | Update grouping values |
|
||||
|
||||
---
|
||||
|
||||
## Types
|
||||
|
||||
### Core Types
|
||||
|
||||
```typescript
|
||||
// Event types
|
||||
type CalendarEventType = 'customer' | 'vacation' | 'break' | 'meeting' | 'blocked';
|
||||
|
||||
interface ICalendarEvent {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
start: Date;
|
||||
end: Date;
|
||||
type: CalendarEventType;
|
||||
allDay: boolean;
|
||||
bookingId?: string;
|
||||
resourceId?: string;
|
||||
customerId?: string;
|
||||
recurringId?: string;
|
||||
syncStatus: SyncStatus;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// Resource types
|
||||
type ResourceType = 'person' | 'room' | 'equipment' | 'vehicle' | 'custom';
|
||||
|
||||
interface IResource {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
type: ResourceType;
|
||||
avatarUrl?: string;
|
||||
color?: string;
|
||||
isActive?: boolean;
|
||||
defaultSchedule?: IWeekSchedule;
|
||||
syncStatus: SyncStatus;
|
||||
}
|
||||
|
||||
// Sync status
|
||||
type SyncStatus = 'synced' | 'pending' | 'error';
|
||||
```
|
||||
|
||||
### Settings Types
|
||||
|
||||
```typescript
|
||||
interface IGridSettings {
|
||||
id: 'grid';
|
||||
dayStartHour: number;
|
||||
dayEndHour: number;
|
||||
workStartHour: number;
|
||||
workEndHour: number;
|
||||
hourHeight: number;
|
||||
snapInterval: number;
|
||||
}
|
||||
|
||||
interface IWorkweekPreset {
|
||||
id: string;
|
||||
workDays: number[]; // ISO weekdays: 1=Monday, 7=Sunday
|
||||
label: string;
|
||||
periodDays: number; // Navigation step (1=day, 7=week)
|
||||
}
|
||||
|
||||
interface IWeekSchedule {
|
||||
[day: number]: ITimeSlot | null; // null = off that day
|
||||
}
|
||||
|
||||
interface ITimeSlot {
|
||||
start: string; // "HH:mm"
|
||||
end: string; // "HH:mm"
|
||||
}
|
||||
```
|
||||
|
||||
### Drag-Drop Payloads
|
||||
|
||||
```typescript
|
||||
interface IDragStartPayload {
|
||||
eventId: string;
|
||||
element: HTMLElement;
|
||||
ghostElement: HTMLElement;
|
||||
startY: number;
|
||||
mouseOffset: { x: number; y: number };
|
||||
columnElement: HTMLElement;
|
||||
}
|
||||
|
||||
interface IDragEndPayload {
|
||||
swpEvent: SwpEvent;
|
||||
sourceColumnKey: string;
|
||||
target: 'grid' | 'header';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Extensions
|
||||
|
||||
Import extensions separately to keep bundle size minimal:
|
||||
|
||||
```typescript
|
||||
// Teams extension
|
||||
import { registerTeams, TeamService, TeamStore, TeamRenderer } from 'calendar/teams';
|
||||
|
||||
// Departments extension
|
||||
import { registerDepartments, DepartmentService, DepartmentStore } from 'calendar/departments';
|
||||
|
||||
// Bookings extension
|
||||
import { registerBookings, BookingService, BookingStore } from 'calendar/bookings';
|
||||
|
||||
// Customers extension
|
||||
import { registerCustomers, CustomerService, CustomerStore } from 'calendar/customers';
|
||||
|
||||
// Schedules extension (working hours)
|
||||
import { registerSchedules, ResourceScheduleService, ScheduleOverrideService } from 'calendar/schedules';
|
||||
|
||||
// Audit extension
|
||||
import { registerAudit, AuditService, AuditStore } from 'calendar/audit';
|
||||
|
||||
// Register with container builder
|
||||
const builder = container.builder();
|
||||
registerCoreServices(builder);
|
||||
registerTeams(builder);
|
||||
registerSchedules(builder);
|
||||
// ... etc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Calendar Options
|
||||
|
||||
```typescript
|
||||
interface ICalendarOptions {
|
||||
timeConfig?: ITimeFormatConfig;
|
||||
gridConfig?: IGridConfig;
|
||||
dbConfig?: IDBConfig;
|
||||
}
|
||||
|
||||
// Time format configuration
|
||||
interface ITimeFormatConfig {
|
||||
timezone: string; // e.g., 'Europe/Copenhagen'
|
||||
use24HourFormat: boolean;
|
||||
locale: string; // e.g., 'da-DK'
|
||||
dateFormat: string;
|
||||
showSeconds: boolean;
|
||||
}
|
||||
|
||||
// Grid configuration
|
||||
interface IGridConfig {
|
||||
hourHeight: number; // Pixels per hour (default: 64)
|
||||
dayStartHour: number; // Grid start hour (default: 6)
|
||||
dayEndHour: number; // Grid end hour (default: 18)
|
||||
snapInterval: number; // Minutes to snap to (default: 15)
|
||||
gridStartThresholdMinutes: number;
|
||||
}
|
||||
|
||||
// Database configuration
|
||||
interface IDBConfig {
|
||||
dbName: string; // IndexedDB database name
|
||||
dbVersion: number; // Schema version
|
||||
}
|
||||
```
|
||||
|
||||
### Default Configuration
|
||||
|
||||
```typescript
|
||||
import {
|
||||
defaultTimeFormatConfig,
|
||||
defaultGridConfig,
|
||||
defaultDBConfig
|
||||
} from 'calendar';
|
||||
|
||||
// Defaults:
|
||||
// timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
// use24HourFormat: true
|
||||
// locale: 'da-DK'
|
||||
// hourHeight: 64
|
||||
// dayStartHour: 6
|
||||
// dayEndHour: 18
|
||||
// snapInterval: 15
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Utilities
|
||||
|
||||
### Position Utilities
|
||||
|
||||
```typescript
|
||||
import {
|
||||
calculateEventPosition,
|
||||
minutesToPixels,
|
||||
pixelsToMinutes,
|
||||
snapToGrid
|
||||
} from 'calendar';
|
||||
|
||||
// Convert time to pixels
|
||||
const pixels = minutesToPixels(120, 64); // 120 mins at 64px/hour = 128px
|
||||
|
||||
// Snap to 15-minute grid
|
||||
const snapped = snapToGrid(new Date(), 15);
|
||||
```
|
||||
|
||||
### Event Layout Engine
|
||||
|
||||
```typescript
|
||||
import { eventsOverlap, calculateColumnLayout } from 'calendar';
|
||||
|
||||
// Check if two events overlap
|
||||
const overlap = eventsOverlap(event1, event2);
|
||||
|
||||
// Calculate layout for overlapping events
|
||||
const layout = calculateColumnLayout(events);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Listening to Events
|
||||
|
||||
```typescript
|
||||
import { EventBus, CoreEvents } from 'calendar';
|
||||
|
||||
const eventBus = app.resolveType<EventBus>();
|
||||
|
||||
// Subscribe to event updates
|
||||
eventBus.on(CoreEvents.EVENT_UPDATED, (e: Event) => {
|
||||
const { eventId, sourceColumnKey, targetColumnKey } = (e as CustomEvent).detail;
|
||||
console.log(`Event ${eventId} moved from ${sourceColumnKey} to ${targetColumnKey}`);
|
||||
});
|
||||
|
||||
// Subscribe to drag events
|
||||
eventBus.on(CoreEvents.EVENT_DRAG_END, (e: Event) => {
|
||||
const { swpEvent, target } = (e as CustomEvent).detail;
|
||||
console.log(`Dropped on ${target}:`, swpEvent);
|
||||
});
|
||||
|
||||
// One-time listener
|
||||
eventBus.once(CoreEvents.READY, () => {
|
||||
console.log('Calendar is ready!');
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CSS Customization
|
||||
|
||||
The calendar uses CSS custom properties for theming. Override these in your CSS:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--calendar-hour-height: 64px;
|
||||
--calendar-header-height: 48px;
|
||||
--calendar-column-min-width: 120px;
|
||||
--calendar-event-border-radius: 4px;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `@novadi/core` - Dependency injection framework (peer dependency)
|
||||
- `dayjs` - Date manipulation and formatting
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Proprietary - SWP
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "calendar",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.7",
|
||||
"description": "Calendar library",
|
||||
"author": "SWP",
|
||||
"type": "module",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue