kalendly 0.1.7 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +377 -662
  2. package/dist/core/index.js +3 -3
  3. package/dist/index.d.mts +61 -602
  4. package/dist/index.d.ts +61 -602
  5. package/dist/index.js +963 -2375
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +937 -2364
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/{vanilla/index.umd.js → index.umd.js} +438 -244
  10. package/dist/index.umd.js.map +1 -0
  11. package/dist/styles/calendar.css +96 -0
  12. package/package.json +54 -118
  13. package/dist/react/index.d.mts +0 -251
  14. package/dist/react/index.d.ts +0 -251
  15. package/dist/react/index.js +0 -969
  16. package/dist/react/index.js.map +0 -1
  17. package/dist/react/index.mjs +0 -924
  18. package/dist/react/index.mjs.map +0 -1
  19. package/dist/react-native/index.d.mts +0 -642
  20. package/dist/react-native/index.d.ts +0 -642
  21. package/dist/react-native/index.js +0 -1649
  22. package/dist/react-native/index.js.map +0 -1
  23. package/dist/react-native/index.mjs +0 -1615
  24. package/dist/react-native/index.mjs.map +0 -1
  25. package/dist/vanilla/index.d.mts +0 -271
  26. package/dist/vanilla/index.d.ts +0 -271
  27. package/dist/vanilla/index.js +0 -1064
  28. package/dist/vanilla/index.js.map +0 -1
  29. package/dist/vanilla/index.mjs +0 -1013
  30. package/dist/vanilla/index.mjs.map +0 -1
  31. package/dist/vanilla/index.umd.js.map +0 -1
  32. package/dist/vue/components/Calendar.vue.d.ts +0 -43
  33. package/dist/vue/components/Calendar.vue.d.ts.map +0 -1
  34. package/dist/vue/index.d.ts +0 -134
  35. package/dist/vue/index.d.ts.map +0 -1
  36. package/dist/vue/index.js +0 -1
  37. package/dist/vue/index.mjs +0 -717
  38. package/dist/vue/types.d.ts +0 -21
  39. package/dist/vue/types.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,25 +1,26 @@
1
- # kalendly Universal Calendar
1
+ # kalendly
2
2
 
3
- A universal calendar component that works seamlessly across React, Vue, and React Native with full TypeScript support.
3
+ A universal calendar web component works in React, Vue, Svelte, Angular, Solid.js, and plain HTML with no framework dependency.
4
4
 
5
5
  ## Features
6
6
 
7
- - 🚀 **Universal**: Works with React, Vue, React Native, and Vanilla JavaScript
8
- - 📱 **Responsive**: Mobile-friendly design that matches your existing UI implementation
9
- - 🎨 **Customizable**: Easy to theme and customize with CSS variables
10
- - 🔒 **Type Safe**: Full TypeScript support
11
- - 📅 **Event Management**: Add, display, and manage events with rich metadata
12
- - 🔔 **Advanced Features**: Recurring events, reminders, categories, priorities, and collaboration
13
- - 🌐 **Accessible**: Built with accessibility in mind
14
- - 📦 **Tree Shakeable**: Import only what you need
7
+ - **Framework-agnostic**: Single `<kal-calendar>` custom element, no framework required
8
+ - **Responsive**: Mobile-friendly, matches your existing UI
9
+ - **Themeable**: CSS variables + JS property API
10
+ - **Type Safe**: Full TypeScript support
11
+ - **Event-rich**: Categories, priorities, time ranges, attendees, and more
12
+ - **Availability mode**: Day and time views for booking flows — hides event details, shows booked/free cells
13
+ - **Lazy loading**: Per-month on-demand fetch with skeleton shimmer state
14
+ - **Accessible**: Built with accessibility in mind
15
+ - **Tree-shakeable**: Import only what you need
15
16
 
16
- ## 🎯 Live Demo
17
+ ## Live Demo
17
18
 
18
19
  <div align="center">
19
20
 
20
21
  [![Kalendly Calendar Demo](./docs/images/calendar-demo.gif)](https://kalendly-example.netlify.app/)
21
22
 
22
- **[🚀 Try the Interactive Demo →](https://kalendly-example.netlify.app/)**
23
+ **[Try the Interactive Demo →](https://kalendly-example.netlify.app/)**
23
24
 
24
25
  </div>
25
26
 
@@ -33,811 +34,527 @@ A universal calendar component that works seamlessly across React, Vue, and Reac
33
34
  <a href="https://kalendly-example.netlify.app/vue">
34
35
  <img src="https://img.shields.io/badge/Vue-4FC08D?style=for-the-badge&logo=vue.js&logoColor=white" alt="Vue Demo"/>
35
36
  </a>
37
+ <a href="https://kalendly-example.netlify.app/angular">
38
+ <img src="https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white" alt="Angular Demo"/>
39
+ </a>
40
+ <a href="https://kalendly-example.netlify.app/svelte">
41
+ <img src="https://img.shields.io/badge/Svelte-FF3E00?style=for-the-badge&logo=svelte&logoColor=white" alt="Svelte Demo"/>
42
+ </a>
43
+ <a href="https://kalendly-example.netlify.app/solid">
44
+ <img src="https://img.shields.io/badge/Solid.js-2C4F7C?style=for-the-badge&logo=solid&logoColor=white" alt="Solid.js Demo"/>
45
+ </a>
36
46
  </p>
37
47
 
38
48
  ## Installation
39
49
 
40
50
  ```bash
41
51
  npm install kalendly
42
- # or
43
- yarn add kalendly
44
- # or
45
- pnpm add kalendly
46
52
  ```
47
53
 
48
54
  ## Usage
49
55
 
50
- ### React
51
-
52
- ```jsx
53
- import React from 'react';
54
- import { Calendar } from 'kalendly/react';
55
- import 'kalendly/styles';
56
-
57
- const events = [
58
- {
59
- id: 1,
60
- name: 'Team Meeting',
61
- date: '2025-01-15',
62
- },
63
- {
64
- id: 2,
65
- name: 'Project Deadline',
66
- date: '2025-01-20',
67
- },
68
- ];
69
-
70
- function App() {
71
- const handleDateSelect = date => {
72
- console.log('Selected date:', date);
73
- };
74
-
75
- return (
76
- <div>
77
- <Calendar
78
- events={events}
79
- onDateSelect={handleDateSelect}
80
- title="My Calendar"
81
- />
82
- </div>
83
- );
84
- }
56
+ ### Vanilla HTML / CDN
85
57
 
86
- export default App;
58
+ ```html
59
+ <link
60
+ rel="stylesheet"
61
+ href="https://unpkg.com/kalendly/dist/styles/calendar.css"
62
+ />
63
+ <script src="https://unpkg.com/kalendly/dist/index.umd.js"></script>
64
+
65
+ <kal-calendar id="cal" title="My Calendar"></kal-calendar>
66
+
67
+ <script>
68
+ const cal = document.getElementById('cal');
69
+
70
+ // Set events (JS property — not an attribute)
71
+ cal.events = [
72
+ { id: 1, name: 'Team Meeting', date: new Date(2025, 0, 15) },
73
+ { id: 2, name: 'Project Deadline', date: new Date(2025, 0, 20) },
74
+ ];
75
+
76
+ // Listen to custom events
77
+ cal.addEventListener('cal-date-select', e => {
78
+ console.log('Selected:', e.detail.date, e.detail.events);
79
+ });
80
+
81
+ cal.addEventListener('cal-month-change', e => {
82
+ console.log('Month:', e.detail.year, e.detail.month);
83
+ });
84
+ </script>
87
85
  ```
88
86
 
89
- ### Vue
90
-
91
- ```vue
92
- <template>
93
- <div>
94
- <Calendar
95
- :events="events"
96
- @date-select="handleDateSelect"
97
- title="My Calendar"
98
- >
99
- <template #event="{ event }">
100
- <div class="custom-event">{{ event.name }}</div>
101
- </template>
102
- </Calendar>
103
- </div>
104
- </template>
87
+ ### ES Modules
105
88
 
106
- <script setup>
107
- import { Calendar } from 'kalendly/vue';
89
+ ```js
90
+ import 'kalendly';
108
91
  import 'kalendly/styles';
109
92
 
110
- const events = [
111
- {
112
- id: 1,
113
- name: 'Team Meeting',
114
- date: '2025-01-15',
115
- },
116
- {
117
- id: 2,
118
- name: 'Project Deadline',
119
- date: '2025-01-20',
120
- },
121
- ];
122
-
123
- const handleDateSelect = date => {
124
- console.log('Selected date:', date);
125
- };
126
- </script>
93
+ // <kal-calendar> is now registered and ready
127
94
  ```
128
95
 
129
- ### React Native
96
+ ### React 19
97
+
98
+ React 19 has full custom element support — pass objects/arrays as props and listen to custom events directly.
130
99
 
131
100
  ```jsx
132
- import React from 'react';
133
- import { View } from 'react-native';
134
- import { Calendar } from 'kalendly/react-native';
135
-
136
- const events = [
137
- {
138
- id: 1,
139
- name: 'Team Meeting',
140
- date: '2025-01-15',
141
- },
142
- {
143
- id: 2,
144
- name: 'Project Deadline',
145
- date: '2025-01-20',
146
- },
147
- ];
101
+ import 'kalendly';
102
+ import 'kalendly/styles';
148
103
 
149
104
  function App() {
150
- const handleDateSelect = date => {
151
- console.log('Selected date:', date);
152
- };
153
-
154
105
  return (
155
- <View style={{ flex: 1 }}>
156
- <Calendar
157
- events={events}
158
- onDateSelect={handleDateSelect}
159
- title="My Calendar"
160
- />
161
- </View>
106
+ <kal-calendar
107
+ title="My Calendar"
108
+ events={events}
109
+ oncal-date-select={e => console.log(e.detail.date)}
110
+ oncal-month-change={e => console.log(e.detail.year, e.detail.month)}
111
+ />
162
112
  );
163
113
  }
164
-
165
- export default App;
166
114
  ```
167
115
 
168
- ### Vanilla JavaScript
169
-
170
- #### Modern ES Modules (Recommended)
116
+ > **React 18 users:** React 18 does not forward object/array props or custom events to custom elements. You need to wire these up via a `ref`:
117
+ >
118
+ > ```jsx
119
+ > import { useRef, useEffect } from 'react';
120
+ > import 'kalendly';
121
+ >
122
+ > function Calendar({ events, onDateSelect, onMonthChange, ...attrs }) {
123
+ > const ref = useRef(null);
124
+ >
125
+ > useEffect(() => {
126
+ > if (ref.current) ref.current.events = events;
127
+ > }, [events]);
128
+ >
129
+ > useEffect(() => {
130
+ > const el = ref.current;
131
+ > if (!el) return;
132
+ > const onSelect = e => onDateSelect?.(e.detail.date, e.detail.events);
133
+ > const onChange = e => onMonthChange?.(e.detail.year, e.detail.month);
134
+ > el.addEventListener('cal-date-select', onSelect);
135
+ > el.addEventListener('cal-month-change', onChange);
136
+ > return () => {
137
+ > el.removeEventListener('cal-date-select', onSelect);
138
+ > el.removeEventListener('cal-month-change', onChange);
139
+ > };
140
+ > }, [onDateSelect, onMonthChange]);
141
+ >
142
+ > return <kal-calendar ref={ref} {...attrs} />;
143
+ > }
144
+ > ```
145
+
146
+ ### Vue 3
147
+
148
+ Vue 3 supports custom elements natively — bind props with `:` and listen to events with `@`:
171
149
 
172
- ```html
173
- <!DOCTYPE html>
174
- <html>
175
- <head>
176
- <link
177
- rel="stylesheet"
178
- href="node_modules/kalendly/dist/styles/calendar.css"
179
- />
180
- <title>Vanilla JS Calendar</title>
181
- </head>
182
- <body>
183
- <div id="calendar"></div>
184
-
185
- <script type="module">
186
- import { createCalendar } from 'kalendly/vanilla';
187
-
188
- const events = [
189
- { id: 1, name: 'Team Meeting', date: '2025-01-15' },
190
- { id: 2, name: 'Project Deadline', date: '2025-01-20' },
191
- ];
192
-
193
- const calendar = createCalendar({
194
- container: '#calendar',
195
- events: events,
196
- title: 'My Calendar',
197
- });
198
-
199
- // Listen to events (note: use addEventListener, not onclick)
200
- document.getElementById('calendar').addEventListener('dateSelect', e => {
201
- console.log('Selected date:', e.detail.date);
202
- });
203
-
204
- document.getElementById('calendar').addEventListener('monthChange', e => {
205
- console.log('Month changed:', e.detail.year, e.detail.month);
206
- });
207
-
208
- // Update events dynamically
209
- // calendar.updateEvents(newEvents);
210
-
211
- // Navigate to specific date
212
- // calendar.goToDate(new Date(2025, 5, 15));
213
-
214
- // Cleanup when done
215
- // calendar.destroy();
216
- </script>
217
- </body>
218
- </html>
219
- ```
150
+ ```vue
151
+ <template>
152
+ <kal-calendar
153
+ title="My Calendar"
154
+ :events="events"
155
+ @cal-date-select="onDateSelect"
156
+ @cal-month-change="onMonthChange"
157
+ />
158
+ </template>
220
159
 
221
- #### CDN Usage (Browser)
160
+ <script setup>
161
+ import 'kalendly';
162
+ import 'kalendly/styles';
222
163
 
223
- ```html
224
- <!DOCTYPE html>
225
- <html>
226
- <head>
227
- <link
228
- rel="stylesheet"
229
- href="https://unpkg.com/kalendly/dist/styles/calendar.css"
230
- />
231
- </head>
232
- <body>
233
- <div id="calendar"></div>
234
-
235
- <script type="module">
236
- import { createCalendar } from 'https://unpkg.com/kalendly/dist/vanilla/index.mjs';
237
-
238
- const calendar = createCalendar({
239
- container: '#calendar',
240
- events: [{ id: 1, name: 'Meeting', date: '2025-01-15' }],
241
- title: 'My Calendar',
242
- });
243
- </script>
244
- </body>
245
- </html>
246
- ```
164
+ const events = [{ id: 1, name: 'Team Meeting', date: new Date(2025, 0, 15) }];
247
165
 
248
- #### Legacy/Global Usage (Without Modules)
166
+ function onDateSelect(e) {
167
+ console.log('Selected:', e.detail.date);
168
+ }
249
169
 
250
- ```html
251
- <!DOCTYPE html>
252
- <html>
253
- <head>
254
- <link
255
- rel="stylesheet"
256
- href="https://unpkg.com/kalendly/dist/styles/calendar.css"
257
- />
258
- </head>
259
- <body>
260
- <div id="calendar"></div>
261
-
262
- <script src="https://unpkg.com/kalendly/dist/vanilla/index.umd.js"></script>
263
- <script>
264
- const { createCalendar } = Kalendly;
265
-
266
- const calendar = createCalendar({
267
- container: '#calendar',
268
- events: [{ id: 1, name: 'Meeting', date: '2025-01-15' }],
269
- title: 'My Calendar',
270
- });
271
-
272
- // Now you can use global functions with onclick
273
- function handleDateSelect() {
274
- console.log('Date selected!');
275
- }
276
-
277
- document
278
- .getElementById('calendar')
279
- .addEventListener('dateSelect', handleDateSelect);
280
- </script>
281
- </body>
282
- </html>
170
+ function onMonthChange(e) {
171
+ console.log('Month:', e.detail.year, e.detail.month);
172
+ }
173
+ </script>
283
174
  ```
284
175
 
285
- ## API Reference
286
-
287
- ### Props
288
-
289
- | Prop | Type | Default | Description |
290
- | -------------------- | --------------------------------------- | ------------------ | --------------------------------------- |
291
- | `events` | `CalendarEvent[]` | `[]` | Array of events to display |
292
- | `initialDate` | `Date` | `new Date()` | Initial date to display |
293
- | `minYear` | `number` | `currentYear - 30` | Minimum selectable year |
294
- | `maxYear` | `number` | `currentYear + 10` | Maximum selectable year |
295
- | `weekStartsOn` | `0 \| 1` | `0` | Week start day (0 = Sunday, 1 = Monday) |
296
- | `useShortMonthNames` | `boolean` | `false` | Use abbreviated month names (Jan, Feb) |
297
- | `categoryColors` | `CategoryColorMap` | `{}` | Custom colors for event categories |
298
- | `theme` | `CalendarTheme` | `undefined` | Custom theme colors for the calendar |
299
- | `title` | `string` | `undefined` | Optional calendar title displayed above |
300
- | `onDateSelect` | `(date: Date) => void` | - | Callback when date is selected |
301
- | `onEventClick` | `(event: CalendarEvent) => void` | - | Callback when event is clicked |
302
- | `onMonthChange` | `(year: number, month: number) => void` | - | Callback when month changes |
176
+ ### Angular
303
177
 
304
- ### CalendarEvent Interface
178
+ ```ts
179
+ // app.config.ts
180
+ import { ApplicationConfig } from '@angular/core';
181
+ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
305
182
 
306
- ```typescript
307
- interface CalendarEvent {
308
- // Required fields
309
- id: string | number;
310
- name: string;
311
- date: string | Date;
312
-
313
- // Time fields
314
- startTime?: string; // e.g., "09:00", "14:30"
315
- endTime?: string; // e.g., "10:00", "16:00"
316
- allDay?: boolean; // True for all-day events
317
-
318
- // Display & categorization
319
- description?: string;
320
- color?: string; // Custom event color
321
- category?:
322
- | 'work'
323
- | 'personal'
324
- | 'meeting'
325
- | 'deadline'
326
- | 'appointment'
327
- | 'other';
328
- location?: string; // Event location
329
- url?: string; // Related URL or meeting link
330
-
331
- // Status & priority
332
- status?: 'scheduled' | 'completed' | 'cancelled' | 'tentative';
333
- priority?: 'low' | 'medium' | 'high';
334
-
335
- // Collaboration
336
- attendees?: string[]; // List of attendee names/emails
337
- organizer?: string; // Event organizer
338
-
339
- // Reminders & recurrence
340
- reminders?: number[]; // Minutes before event to remind (e.g., [15, 60])
341
- recurring?: {
342
- frequency: 'daily' | 'weekly' | 'monthly' | 'yearly';
343
- interval?: number; // Every X days/weeks/months/years
344
- endDate?: string | Date; // When recurrence ends
345
- daysOfWeek?: number[]; // For weekly recurrence (0 = Sunday)
346
- };
347
-
348
- // Metadata
349
- notes?: string; // Additional notes
350
- tags?: string[]; // Event tags
351
- createdAt?: string | Date; // Creation timestamp
352
- updatedAt?: string | Date; // Last update timestamp
353
-
354
- // Flexibility for custom fields
355
- [key: string]: unknown;
356
- }
183
+ // app.component.ts
184
+ import 'kalendly';
185
+ import 'kalendly/styles';
357
186
  ```
358
187
 
359
- #### Example with Enhanced Fields
360
-
361
- ```typescript
362
- const events = [
363
- {
364
- id: 1,
365
- name: 'Team Standup',
366
- date: '2025-01-15',
367
- startTime: '09:00',
368
- endTime: '09:30',
369
- category: 'meeting',
370
- location: 'Conference Room A',
371
- url: 'https://meet.google.com/abc-defg-hij',
372
- attendees: ['john@example.com', 'jane@example.com'],
373
- organizer: 'team-lead@example.com',
374
- priority: 'high',
375
- status: 'scheduled',
376
- reminders: [15, 60],
377
- recurring: {
378
- frequency: 'daily',
379
- interval: 1,
380
- daysOfWeek: [1, 2, 3, 4, 5], // Mon-Fri
381
- endDate: '2025-12-31',
382
- },
383
- tags: ['team', 'sync'],
384
- },
385
- {
386
- id: 2,
387
- name: 'Project Deadline',
388
- date: '2025-01-20',
389
- allDay: true,
390
- category: 'deadline',
391
- priority: 'high',
392
- status: 'scheduled',
393
- description: 'Final submission for Q1 project',
394
- notes: 'Make sure all tests pass before submission',
395
- },
396
- ];
188
+ ```html
189
+ <!-- app.component.html -->
190
+ <kal-calendar
191
+ title="My Calendar"
192
+ [events]="events"
193
+ (cal-date-select)="onDateSelect($event)"
194
+ (cal-month-change)="onMonthChange($event)"
195
+ ></kal-calendar>
397
196
  ```
398
197
 
399
- #### Category Colors
198
+ ### Svelte 5
400
199
 
401
- You can customize colors for different event categories:
402
-
403
- ```typescript
404
- const categoryColors = {
405
- work: '#4CAF50',
406
- personal: '#2196F3',
407
- meeting: '#FF9800',
408
- deadline: '#F44336',
409
- appointment: '#9C27B0',
410
- other: '#607D8B'
411
- };
200
+ ```svelte
201
+ <script>
202
+ import 'kalendly';
203
+ import 'kalendly/styles';
204
+ let { events = [] } = $props();
205
+ </script>
412
206
 
413
- <Calendar events={events} categoryColors={categoryColors} />
207
+ <kal-calendar {events} oncal-date-select={e => console.log(e.detail.date)} />
414
208
  ```
415
209
 
416
- ## Theming
417
-
418
- Kalendly supports custom theming across all frameworks with a **consistent API**. Customize calendar colors to match your brand or create light/dark themes.
419
-
420
- ### CalendarTheme Interface
210
+ ### Svelte 4
211
+
212
+ ```svelte
213
+ <script>
214
+ import { onMount } from 'svelte';
215
+ import 'kalendly';
216
+ import 'kalendly/styles';
217
+ export let events = [];
218
+ let calEl;
219
+ onMount(() => { calEl.events = events; });
220
+ $: if (calEl) calEl.events = events;
221
+ </script>
421
222
 
422
- ```typescript
423
- interface CalendarTheme {
424
- primary?: string; // Primary brand color
425
- secondary?: string; // Secondary brand color
426
- tertiary?: string; // Tertiary/accent color
427
- textColor?: string; // Main text color
428
- textLight?: string; // Light/secondary text color
429
- background?: string; // Background color
430
- cellHover?: string; // Cell hover state color
431
- borderColor?: string; // Border color
432
- todayOutline?: string; // Today indicator color
433
- selectedBg?: string; // Selected date background
434
- eventIndicator?: string; // Event indicator dot color
435
- }
223
+ <kal-calendar bind:this={calEl} on:cal-date-select on:cal-month-change />
436
224
  ```
437
225
 
438
- ### Basic Theme Example
439
-
440
- **React:**
226
+ ### Solid.js
441
227
 
442
- ```tsx
443
- import { Calendar } from 'kalendly/react';
228
+ ```jsx
229
+ import 'kalendly';
444
230
  import 'kalendly/styles';
445
231
 
446
232
  function App() {
447
233
  return (
448
- <Calendar
449
- events={events}
450
- theme={{
451
- primary: '#3b82f6',
452
- secondary: '#60a5fa',
453
- tertiary: '#93c5fd',
454
- borderColor: '#e5e7eb',
455
- todayOutline: '#fbbf24',
456
- eventIndicator: '#10b981',
457
- }}
234
+ <kal-calendar
235
+ title="My Calendar"
236
+ prop:events={events}
237
+ on:cal-date-select={e => console.log(e.detail.date)}
458
238
  />
459
239
  );
460
240
  }
461
241
  ```
462
242
 
463
- **Vue:**
243
+ ## Styling
464
244
 
465
- ```vue
466
- <template>
467
- <Calendar :events="events" :theme="calendarTheme" />
468
- </template>
245
+ ### Loading styles
469
246
 
470
- <script setup lang="ts">
471
- import { Calendar } from 'kalendly/vue';
247
+ ```js
248
+ // Bundler (Vite, webpack) add once in your app entry (e.g. main.tsx)
472
249
  import 'kalendly/styles';
473
250
 
474
- const calendarTheme = {
475
- primary: '#3b82f6',
476
- secondary: '#60a5fa',
477
- tertiary: '#93c5fd',
478
- borderColor: '#e5e7eb',
479
- todayOutline: '#fbbf24',
480
- eventIndicator: '#10b981',
481
- };
482
- </script>
483
- ```
484
-
485
- **Vanilla JavaScript:**
486
-
487
- ```javascript
488
- import { createCalendar } from 'kalendly/vanilla';
489
- import 'kalendly/styles';
251
+ // Plain HTML
252
+ // <link rel="stylesheet" href="/node_modules/kalendly/dist/styles/calendar.css">
490
253
 
491
- const calendar = createCalendar({
492
- container: '#calendar',
493
- events: events,
494
- theme: {
495
- primary: '#3b82f6',
496
- secondary: '#60a5fa',
497
- tertiary: '#93c5fd',
498
- borderColor: '#e5e7eb',
499
- todayOutline: '#fbbf24',
500
- eventIndicator: '#10b981',
501
- },
502
- });
254
+ // Angular add to angular.json → projects → architect → build → styles
255
+ // "node_modules/kalendly/dist/styles/calendar.css"
503
256
  ```
504
257
 
505
- **React Native:**
258
+ ### Overriding styles
506
259
 
507
- ```tsx
508
- import { Calendar } from 'kalendly/react-native';
260
+ kalendly uses Light DOM — all standard CSS techniques work:
509
261
 
510
- function App() {
511
- return (
512
- <Calendar
513
- events={events}
514
- theme={{
515
- primary: '#3b82f6',
516
- secondary: '#60a5fa',
517
- tertiary: '#93c5fd',
518
- borderColor: '#e5e7eb',
519
- todayOutline: '#fbbf24',
520
- eventIndicator: '#10b981',
521
- }}
522
- />
523
- );
262
+ ```css
263
+ /* 1. CSS custom properties (recommended) */
264
+ :root {
265
+ --calendar-primary-color: #6366f1;
266
+ --calendar-background: #1e1e2e;
267
+ --calendar-border-color: #334155;
524
268
  }
525
- ```
526
269
 
527
- ### Dark Theme Example
270
+ /* 2. Direct class overrides */
271
+ .kalendly-calendar .calendar--card {
272
+ border-radius: 12px;
273
+ }
274
+ ```
528
275
 
529
- ```typescript
530
- const darkTheme = {
276
+ ```js
277
+ // 3. JS theme property
278
+ document.querySelector('kal-calendar').theme = {
531
279
  primary: '#6366f1',
532
- secondary: '#818cf8',
533
- tertiary: '#a5b4fc',
534
- textColor: '#f9fafb',
535
- textLight: '#d1d5db',
536
- background: '#1f2937',
537
- cellHover: '#374151',
538
- borderColor: '#4b5563',
539
- todayOutline: '#fbbf24',
540
- selectedBg: '#312e81',
541
- eventIndicator: '#34d399'
280
+ background: '#1e1e2e',
542
281
  };
543
-
544
- <Calendar events={events} theme={darkTheme} />
545
282
  ```
546
283
 
547
- ### Dynamic Theme Switching
284
+ ## Attributes
548
285
 
549
- **React:**
286
+ Primitives are set as HTML attributes:
550
287
 
551
- ```tsx
552
- import { useState } from 'react';
553
- import { Calendar } from 'kalendly/react';
288
+ | Attribute | Type | Default | Description |
289
+ | ----------------------- | ---------------- | ---------------- | ------------------------------------------------ |
290
+ | `title` | `string` | — | Calendar title |
291
+ | `initial-date` | `string` | today | ISO date string for initial view |
292
+ | `min-year` | `string` | currentYear - 30 | Minimum year in picker |
293
+ | `max-year` | `string` | currentYear + 10 | Maximum year in picker |
294
+ | `week-starts-on` | `"0"\|"1"` | `"0"` | Week start: 0 = Sunday, 1 = Monday |
295
+ | `use-short-month-names` | `string` | — | Present = use abbreviated month names |
296
+ | `availability-mode` | `"day"\|"time"` | — | Hides event details; shows booked/free cells |
297
+ | `selectable` | `"range"` | — | Enables day/slot selection (requires avail mode) |
298
+ | `loading` | `boolean` (flag) | — | Present = render skeleton shimmer cells |
554
299
 
555
- const themes = {
556
- blue: { primary: '#3b82f6', secondary: '#60a5fa' },
557
- purple: { primary: '#8b5cf6', secondary: '#a78bfa' },
558
- green: { primary: '#10b981', secondary: '#34d399' },
559
- };
560
-
561
- function App() {
562
- const [currentTheme, setCurrentTheme] = useState('blue');
300
+ ## Properties
563
301
 
564
- return (
565
- <>
566
- <button onClick={() => setCurrentTheme('blue')}>Blue</button>
567
- <button onClick={() => setCurrentTheme('purple')}>Purple</button>
568
- <button onClick={() => setCurrentTheme('green')}>Green</button>
569
-
570
- <Calendar events={events} theme={themes[currentTheme]} />
571
- </>
572
- );
573
- }
574
- ```
302
+ Rich objects are set as JS properties (not attributes):
575
303
 
576
- **Vue:**
304
+ | Property | Type | Description |
305
+ | ---------------- | ---------------------------------- | ------------------------------------------------- |
306
+ | `events` | `CalendarEvent[]` | Events to display |
307
+ | `loading` | `boolean` | `true` = render skeleton cells; `false` = restore |
308
+ | `theme` | `CalendarTheme` | Custom theme colors |
309
+ | `categoryColors` | `CategoryColorMap` | Per-category color overrides |
310
+ | `renderEvent` | `(event: CalendarEvent) => string` | Custom event HTML renderer |
311
+ | `renderNoEvents` | `() => string` | Custom empty-state HTML renderer |
577
312
 
578
- ```vue
579
- <template>
580
- <div>
581
- <button @click="currentTheme = 'blue'">Blue</button>
582
- <button @click="currentTheme = 'purple'">Purple</button>
583
- <button @click="currentTheme = 'green'">Green</button>
313
+ > `renderEvent` and `renderNoEvents` are ignored when `availability-mode` is set.
584
314
 
585
- <Calendar :events="events" :theme="themes[currentTheme]" />
586
- </div>
587
- </template>
315
+ ## Custom Events
588
316
 
589
- <script setup lang="ts">
590
- import { ref } from 'vue';
591
- import { Calendar } from 'kalendly/vue';
317
+ | Event | `detail` shape | Description |
318
+ | ------------------------- | ---------------------------------------------------------------------- | -------------------------------------- |
319
+ | `cal-date-select` | `{ date: Date, events: CalendarEvent[] }` | User clicked a date (normal mode) |
320
+ | `cal-month-change` | `{ year: number, month: number }` | Fires **before** the new month renders |
321
+ | `cal-availability-select` | `{ startDate: Date, endDate: Date }` or `{ date, startTime, endTime }` | Day/slot selected in availability mode |
592
322
 
593
- const themes = {
594
- blue: { primary: '#3b82f6', secondary: '#60a5fa' },
595
- purple: { primary: '#8b5cf6', secondary: '#a78bfa' },
596
- green: { primary: '#10b981', secondary: '#34d399' },
597
- };
323
+ All events bubble and are composed (cross Shadow DOM boundaries).
598
324
 
599
- const currentTheme = ref('blue');
600
- </script>
601
- ```
325
+ `cal-availability-select` detail shape depends on mode:
602
326
 
603
- **Vanilla JavaScript:**
327
+ - **Day mode** (`availability-mode="day"`): `{ startDate: Date, endDate: Date }` — first click gives `startDate === endDate`; second click extends the range; third click resets
328
+ - **Time mode** (`availability-mode="time"`): `{ date: Date, startTime: string, endTime: string }` — first click selects a single slot; second click extends; third click resets
604
329
 
605
- ```javascript
606
- import { createCalendar } from 'kalendly/vanilla';
330
+ ## Availability Mode
607
331
 
608
- const themes = {
609
- blue: { primary: '#3b82f6', secondary: '#60a5fa' },
610
- purple: { primary: '#8b5cf6', secondary: '#a78bfa' },
611
- green: { primary: '#10b981', secondary: '#34d399' },
612
- };
332
+ Hides all event details from the end user — only booked/free state is shown. Designed for scheduling and booking flows where the server's event data must not be exposed to the viewer.
613
333
 
614
- const calendar = createCalendar({
615
- container: '#calendar',
616
- events: events,
617
- theme: themes.blue,
618
- });
334
+ ### Day view
619
335
 
620
- // Update theme dynamically without recreation
621
- document.getElementById('purple-btn').addEventListener('click', () => {
622
- calendar.updateTheme(themes.purple);
623
- });
336
+ ```html
337
+ <kal-calendar availability-mode="day"></kal-calendar>
624
338
  ```
625
339
 
626
- > **Note:** The `updateTheme()` method is available in Vanilla JavaScript for efficient theme updates without recreating the calendar instance.
627
-
628
- ## Framework-Specific Features
340
+ Days with events get a red tint (booked); days without get a green tint (free). Clicking a day opens no popup.
629
341
 
630
- ### React Props
342
+ Pass the minimal event shape — no names, no descriptions:
631
343
 
632
- | Prop | Type | Description |
633
- | ---------------- | ------------------------------------- | ------------------------ |
634
- | `className` | `string` | CSS class name |
635
- | `style` | `React.CSSProperties` | Inline styles |
636
- | `renderEvent` | `(event: CalendarEvent) => ReactNode` | Custom event renderer |
637
- | `renderNoEvents` | `() => ReactNode` | Custom no events message |
638
-
639
- ### Vue Props & Slots
344
+ ```js
345
+ cal.events = [
346
+ { id: 1, date: new Date(2025, 4, 8) },
347
+ { id: 2, date: new Date(2025, 4, 8), startTime: '14:00', endTime: '16:00' },
348
+ { id: 3, date: new Date(2025, 4, 20), startTime: '10:00', endTime: '12:00' },
349
+ ];
350
+ ```
640
351
 
641
- | Slot | Props | Description |
642
- | ----------- | -------------------------- | ------------------------ |
643
- | `title` | - | Custom title content |
644
- | `event` | `{ event: CalendarEvent }` | Custom event display |
645
- | `no-events` | - | Custom no events message |
352
+ ### Time view
646
353
 
647
- ### React Native Props
354
+ ```html
355
+ <kal-calendar availability-mode="time"></kal-calendar>
356
+ ```
648
357
 
649
- | Prop | Type | Description |
650
- | ----------------- | ----------- | -------------------------- |
651
- | `style` | `ViewStyle` | Container style |
652
- | `headerStyle` | `ViewStyle` | Header style |
653
- | `cellStyle` | `ViewStyle` | Calendar cell style |
654
- | `showCloseButton` | `boolean` | Show close button in popup |
358
+ Clicking a day opens a time-grid popup showing which hours are booked (red) and free (green). No event name or organiser is ever rendered.
655
359
 
656
- ### Vanilla JavaScript Options
360
+ ### Selectable range
657
361
 
658
- | Option | Type | Description |
659
- | ---------------- | ---------------------------------- | ----------------------------- |
660
- | `container` | `HTMLElement \| string` | Container element or selector |
661
- | `className` | `string` | Additional CSS class |
662
- | `renderEvent` | `(event: CalendarEvent) => string` | Custom event HTML renderer |
663
- | `renderNoEvents` | `() => string` | Custom no events HTML |
362
+ Add `selectable="range"` to let the user pick a free day or time slot:
664
363
 
665
- ### Vanilla JavaScript Events
364
+ ```html
365
+ <kal-calendar availability-mode="day" selectable="range"></kal-calendar>
366
+ <kal-calendar availability-mode="time" selectable="range"></kal-calendar>
367
+ ```
666
368
 
667
- ```javascript
668
- // Listen to calendar events
669
- calendar.container.addEventListener('dateSelect', e => {
670
- console.log('Date selected:', e.detail.date);
369
+ ```js
370
+ // Day mode fires on every click
371
+ cal.addEventListener('cal-availability-select', e => {
372
+ const { startDate, endDate } = e.detail;
373
+ console.log('Selected:', startDate, '→', endDate);
671
374
  });
672
375
 
673
- calendar.container.addEventListener('monthChange', e => {
674
- console.log('Month changed:', e.detail.year, e.detail.month);
376
+ // Time mode — fires on every slot click
377
+ cal.addEventListener('cal-availability-select', e => {
378
+ const { date, startTime, endTime } = e.detail;
379
+ console.log('Slot:', date, startTime, '–', endTime);
675
380
  });
676
381
  ```
677
382
 
678
- ### Vanilla JavaScript API
383
+ Booked days/slots cannot be selected. The 3-click state machine: first click selects, second extends, third resets.
679
384
 
680
- ```javascript
681
- const calendar = createCalendar(options);
385
+ ## Lazy Event Fetching
682
386
 
683
- // Methods
684
- calendar.updateEvents(newEvents); // Update events
685
- calendar.getCurrentDate(); // Get selected date
686
- calendar.goToDate(new Date()); // Navigate to date
687
- calendar.getEngine(); // Access core engine
688
- calendar.destroy(); // Cleanup
387
+ `cal-month-change` fires **before** the new month renders, so you can set `loading = true` synchronously — the calendar shows skeleton shimmer cells from the first frame with no empty-calendar flash.
388
+
389
+ ```js
390
+ cal.addEventListener('cal-month-change', async ({ detail }) => {
391
+ const { year, month } = detail;
392
+ cal.loading = true;
393
+ cal.events = await fetchEvents(year, month); // your API call
394
+ cal.loading = false;
395
+ });
689
396
  ```
690
397
 
691
- ## Common Issues & Solutions
398
+ The "dump all events upfront" pattern still works unchanged — `cal-month-change` is optional:
399
+
400
+ ```js
401
+ // Load once, component handles all months
402
+ cal.events = allEvents;
403
+ ```
692
404
 
693
- ### Vanilla JavaScript
405
+ ## Core API
694
406
 
695
- **Issue: "Function is not defined" errors with onclick handlers**
407
+ `querySelector('kal-calendar')` returns `CalendarElement | null` automatically no cast needed:
696
408
 
697
- ❌ **Wrong:**
409
+ ```ts
410
+ import type { CalendarElement } from 'kalendly';
698
411
 
699
- ```html
700
- <button onclick="myFunction()">Click me</button>
701
- <script type="module">
702
- function myFunction() {} // Not accessible globally in modules
703
- </script>
412
+ const cal = document.querySelector('kal-calendar'); // CalendarElement | null
413
+ cal?.goToDate(new Date());
414
+ cal?.updateEvents(events);
415
+ cal?.updateTheme(theme);
416
+ cal?.getCurrentDate(); // Date | null
417
+ cal?.getEngine(); // CalendarEngine
704
418
  ```
705
419
 
706
- **Correct:**
420
+ **JavaScript** works the same way without the import:
707
421
 
708
- ```html
709
- <button id="myButton">Click me</button>
710
- <script type="module">
711
- function myFunction() {}
712
- document.getElementById('myButton').addEventListener('click', myFunction);
713
- </script>
422
+ ```js
423
+ const cal = document.querySelector('kal-calendar');
424
+
425
+ cal.updateEvents(newEvents);
426
+ cal.updateTheme(newTheme);
427
+ cal.goToDate(new Date());
428
+ cal.getCurrentDate();
429
+ cal.getEngine();
714
430
  ```
715
431
 
716
- **Issue: Module imports not working**
432
+ ## CalendarEvent Interface
717
433
 
718
- Use the correct import paths:
434
+ ```typescript
435
+ interface CalendarEvent {
436
+ id: string | number;
437
+ name: string;
438
+ date: string | Date;
719
439
 
720
- - Local: `'./dist/vanilla/index.mjs'`
721
- - NPM: `'kalendly/vanilla'`
722
- - CDN: `'https://unpkg.com/kalendly/dist/vanilla/index.mjs'`
440
+ startTime?: string; // e.g. "09:00"
441
+ endTime?: string; // e.g. "10:00"
442
+ allDay?: boolean;
723
443
 
724
- **Issue: Styles not loading**
444
+ description?: string;
445
+ color?: string;
446
+ category?:
447
+ | 'work'
448
+ | 'personal'
449
+ | 'meeting'
450
+ | 'deadline'
451
+ | 'appointment'
452
+ | 'other';
453
+ location?: string;
454
+ url?: string;
725
455
 
726
- Always include the CSS file:
456
+ status?: 'scheduled' | 'completed' | 'cancelled' | 'tentative';
457
+ priority?: 'low' | 'medium' | 'high';
727
458
 
728
- ```html
729
- <link rel="stylesheet" href="path/to/kalendly/dist/styles/calendar.css" />
459
+ attendees?: string[];
460
+ organizer?: string;
461
+ reminders?: number[]; // minutes before event
462
+ recurring?: {
463
+ frequency: 'daily' | 'weekly' | 'monthly' | 'yearly';
464
+ interval?: number;
465
+ endDate?: string | Date;
466
+ daysOfWeek?: number[];
467
+ };
468
+
469
+ notes?: string;
470
+ tags?: string[];
471
+ [key: string]: unknown;
472
+ }
730
473
  ```
731
474
 
732
- ## Customization
475
+ ## Theming
733
476
 
734
- ### CSS Variables (React/Vue)
477
+ ### All CSS variables
735
478
 
736
479
  ```css
737
480
  :root {
738
481
  --calendar-primary-color: #fc8917;
739
- --calendar-primary-color-rgb: 252, 137, 23;
740
482
  --calendar-secondary-color: #fca045;
741
- --calendar-secondary-color-rgb: 252, 160, 69;
742
483
  --calendar-tertiary-color: #fdb873;
743
484
  --calendar-text-color: #2c3e50;
485
+ --calendar-text-light: #6b7280;
744
486
  --calendar-border-color: #dee2e6;
745
487
  --calendar-today-outline: #f7db04;
746
488
  --calendar-event-indicator: #1890ff;
747
489
  --calendar-background: #fff;
490
+ --calendar-cell-hover: #f3f4f6;
491
+ --calendar-selected-bg: #eff6ff;
748
492
  }
749
493
  ```
750
494
 
751
- > **Note:** The `-rgb` variables are used for semi-transparent backgrounds (e.g., hover states) and provide compatibility with older browsers (Chrome < 111, Safari iOS < 16.2).
495
+ ### JS theme property (full reference)
752
496
 
753
- ### React Native Theming
497
+ ```js
498
+ cal.theme = {
499
+ primary: '#3b82f6',
500
+ secondary: '#60a5fa',
501
+ tertiary: '#93c5fd',
502
+ textColor: '#111827',
503
+ textLight: '#6b7280',
504
+ background: '#ffffff',
505
+ cellHover: '#f3f4f6',
506
+ borderColor: '#e5e7eb',
507
+ todayOutline: '#fbbf24',
508
+ selectedBg: '#eff6ff',
509
+ eventIndicator: '#10b981',
510
+ };
511
+ ```
754
512
 
755
- ```javascript
756
- import { calendarStyles } from 'kalendly/react-native';
513
+ ### Dark theme example
757
514
 
758
- // Customize styles
759
- const customStyles = {
760
- ...calendarStyles,
761
- container: {
762
- ...calendarStyles.container,
763
- backgroundColor: '#f5f5f5',
764
- },
515
+ ```js
516
+ cal.theme = {
517
+ primary: '#6366f1',
518
+ secondary: '#818cf8',
519
+ textColor: '#f9fafb',
520
+ textLight: '#d1d5db',
521
+ background: '#1f2937',
522
+ cellHover: '#374151',
523
+ borderColor: '#4b5563',
524
+ todayOutline: '#fbbf24',
525
+ selectedBg: '#312e81',
526
+ eventIndicator: '#34d399',
765
527
  };
766
528
  ```
767
529
 
768
- ## Core API
769
-
770
- You can also use the core calendar engine directly:
530
+ ## Core Engine (advanced)
771
531
 
772
532
  ```typescript
773
533
  import { CalendarEngine } from 'kalendly/core';
774
534
 
775
- const engine = new CalendarEngine({
776
- events: myEvents,
777
- initialDate: new Date(),
778
- });
535
+ const engine = new CalendarEngine({ events, initialDate: new Date() });
779
536
 
780
- // Subscribe to changes
781
537
  const unsubscribe = engine.subscribe(() => {
782
- console.log('Calendar state changed');
538
+ const viewModel = engine.getViewModel();
539
+ // re-render
783
540
  });
784
541
 
785
- // Get current state
786
- const viewModel = engine.getViewModel();
787
- const actions = engine.getActions();
542
+ engine.getActions().next();
543
+ engine.getActions().previous();
544
+ engine.getActions().jump(2025, 5);
545
+ engine.getActions().goToToday();
788
546
 
789
- // Navigate months
790
- actions.next();
791
- actions.previous();
792
- actions.jump(2025, 5); // June 2025
793
- actions.goToToday(); // Navigate to current month
794
-
795
- // Check current view
796
- const isCurrentMonth = actions.isCurrentMonth(); // true if viewing today's month
797
-
798
- // Clean up
799
547
  unsubscribe();
800
548
  engine.destroy();
801
549
  ```
802
550
 
803
- ## TypeScript Support
804
-
805
- The package is written in TypeScript and provides full type definitions:
806
-
807
- ```typescript
808
- import type { CalendarEvent, CalendarProps, CalendarState } from 'kalendly';
809
- ```
810
-
811
551
  ## Browser Support
812
552
 
813
- - **React/Vue**: Modern browsers (Chrome 90+, Firefox 88+, Safari 14+)
814
- - **React Native**: iOS 12+, Android API 21+
553
+ Custom Elements v1 Chrome 67+, Firefox 63+, Safari 12.1+, Edge 79+.
815
554
 
816
555
  ## Contributing
817
556
 
818
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for detailed information on:
819
-
820
- - Development setup
821
- - Project structure
822
- - Building and testing
823
- - Running examples
824
- - Submitting pull requests
825
- - Release process
826
-
827
- Quick start:
828
-
829
- ```bash
830
- # Clone and install
831
- git clone https://github.com/callezenwaka/kalendly.git
832
- cd kalendly
833
- npm install
834
-
835
- # Run tests
836
- npm test
837
-
838
- # Run examples locally
839
- npm run dev:examples
840
- ```
557
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
841
558
 
842
559
  ## License
843
560
 
@@ -845,15 +562,13 @@ MIT © Callis Ezenwaka
845
562
 
846
563
  ## Changelog
847
564
 
848
- See [CHANGELOG.md](CHANGELOG.md) for detailed release notes and version history.
565
+ See [CHANGELOG.md](CHANGELOG.md).
849
566
 
850
567
  ### Recent Updates
851
568
 
852
- - **v0.1.7**: Vanilla calendar performance optimization with event delegation, targeted DOM updates for picker navigation
853
- - **v0.1.6**: Navigation enhancements with Today button, month/year picker dropdown, optional `title` prop, calendar grid improvements showing previous/next month days, and browser compatibility fixes
854
- - **v0.1.5**: Universal theming system, TypeScript support improvements, integration test enhancements
855
- - **v0.1.4**: Netlify configuration updates
856
- - **v0.1.3**: Vue types generation improvements
857
- - **v0.1.2**: Enhanced event parameters with structured metadata, categories, recurrence, collaboration features
858
- - **v0.1.1**: Pre-commit hooks and trusted publishing with OIDC
569
+ - **v0.2.1**: Add availability mode (day/time views), selectable range, lazy event fetching with skeleton loading
570
+ - **v0.2.0**: Migrated to a single `<kal-calendar>` web component works natively in React, Vue, Angular, Svelte, Solid.js, and plain HTML with no framework dependency
571
+ - **v0.1.7**: Vanilla calendar performance optimization with event delegation
572
+ - **v0.1.6**: Navigation enhancements — Today button, month/year picker, optional `title` prop
573
+ - **v0.1.5**: Universal theming system, TypeScript improvements
859
574
  - **v0.1.0**: Initial release with React, Vue, React Native, and Vanilla JavaScript support