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.
- package/README.md +377 -662
- package/dist/core/index.js +3 -3
- package/dist/index.d.mts +61 -602
- package/dist/index.d.ts +61 -602
- package/dist/index.js +963 -2375
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +937 -2364
- package/dist/index.mjs.map +1 -1
- package/dist/{vanilla/index.umd.js → index.umd.js} +438 -244
- package/dist/index.umd.js.map +1 -0
- package/dist/styles/calendar.css +96 -0
- package/package.json +54 -118
- package/dist/react/index.d.mts +0 -251
- package/dist/react/index.d.ts +0 -251
- package/dist/react/index.js +0 -969
- package/dist/react/index.js.map +0 -1
- package/dist/react/index.mjs +0 -924
- package/dist/react/index.mjs.map +0 -1
- package/dist/react-native/index.d.mts +0 -642
- package/dist/react-native/index.d.ts +0 -642
- package/dist/react-native/index.js +0 -1649
- package/dist/react-native/index.js.map +0 -1
- package/dist/react-native/index.mjs +0 -1615
- package/dist/react-native/index.mjs.map +0 -1
- package/dist/vanilla/index.d.mts +0 -271
- package/dist/vanilla/index.d.ts +0 -271
- package/dist/vanilla/index.js +0 -1064
- package/dist/vanilla/index.js.map +0 -1
- package/dist/vanilla/index.mjs +0 -1013
- package/dist/vanilla/index.mjs.map +0 -1
- package/dist/vanilla/index.umd.js.map +0 -1
- package/dist/vue/components/Calendar.vue.d.ts +0 -43
- package/dist/vue/components/Calendar.vue.d.ts.map +0 -1
- package/dist/vue/index.d.ts +0 -134
- package/dist/vue/index.d.ts.map +0 -1
- package/dist/vue/index.js +0 -1
- package/dist/vue/index.mjs +0 -717
- package/dist/vue/types.d.ts +0 -21
- package/dist/vue/types.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
# kalendly
|
|
1
|
+
# kalendly
|
|
2
2
|
|
|
3
|
-
A universal calendar component
|
|
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
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
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
|
-
##
|
|
17
|
+
## Live Demo
|
|
17
18
|
|
|
18
19
|
<div align="center">
|
|
19
20
|
|
|
20
21
|
[](https://kalendly-example.netlify.app/)
|
|
21
22
|
|
|
22
|
-
**[
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
107
|
-
import
|
|
89
|
+
```js
|
|
90
|
+
import 'kalendly';
|
|
108
91
|
import 'kalendly/styles';
|
|
109
92
|
|
|
110
|
-
|
|
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
|
|
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
|
|
133
|
-
import
|
|
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
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
```
|
|
173
|
-
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
160
|
+
<script setup>
|
|
161
|
+
import 'kalendly';
|
|
162
|
+
import 'kalendly/styles';
|
|
222
163
|
|
|
223
|
-
|
|
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
|
-
|
|
166
|
+
function onDateSelect(e) {
|
|
167
|
+
console.log('Selected:', e.detail.date);
|
|
168
|
+
}
|
|
249
169
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
```ts
|
|
179
|
+
// app.config.ts
|
|
180
|
+
import { ApplicationConfig } from '@angular/core';
|
|
181
|
+
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
305
182
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
198
|
+
### Svelte 5
|
|
400
199
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
<
|
|
207
|
+
<kal-calendar {events} oncal-date-select={e => console.log(e.detail.date)} />
|
|
414
208
|
```
|
|
415
209
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
439
|
-
|
|
440
|
-
**React:**
|
|
226
|
+
### Solid.js
|
|
441
227
|
|
|
442
|
-
```
|
|
443
|
-
import
|
|
228
|
+
```jsx
|
|
229
|
+
import 'kalendly';
|
|
444
230
|
import 'kalendly/styles';
|
|
445
231
|
|
|
446
232
|
function App() {
|
|
447
233
|
return (
|
|
448
|
-
<
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
-
|
|
243
|
+
## Styling
|
|
464
244
|
|
|
465
|
-
|
|
466
|
-
<template>
|
|
467
|
-
<Calendar :events="events" :theme="calendarTheme" />
|
|
468
|
-
</template>
|
|
245
|
+
### Loading styles
|
|
469
246
|
|
|
470
|
-
|
|
471
|
-
|
|
247
|
+
```js
|
|
248
|
+
// Bundler (Vite, webpack) — add once in your app entry (e.g. main.tsx)
|
|
472
249
|
import 'kalendly/styles';
|
|
473
250
|
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
492
|
-
|
|
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
|
-
|
|
258
|
+
### Overriding styles
|
|
506
259
|
|
|
507
|
-
|
|
508
|
-
import { Calendar } from 'kalendly/react-native';
|
|
260
|
+
kalendly uses Light DOM — all standard CSS techniques work:
|
|
509
261
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
270
|
+
/* 2. Direct class overrides */
|
|
271
|
+
.kalendly-calendar .calendar--card {
|
|
272
|
+
border-radius: 12px;
|
|
273
|
+
}
|
|
274
|
+
```
|
|
528
275
|
|
|
529
|
-
```
|
|
530
|
-
|
|
276
|
+
```js
|
|
277
|
+
// 3. JS theme property
|
|
278
|
+
document.querySelector('kal-calendar').theme = {
|
|
531
279
|
primary: '#6366f1',
|
|
532
|
-
|
|
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
|
-
|
|
284
|
+
## Attributes
|
|
548
285
|
|
|
549
|
-
|
|
286
|
+
Primitives are set as HTML attributes:
|
|
550
287
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
586
|
-
</div>
|
|
587
|
-
</template>
|
|
315
|
+
## Custom Events
|
|
588
316
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
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
|
-
|
|
600
|
-
</script>
|
|
601
|
-
```
|
|
325
|
+
`cal-availability-select` detail shape depends on mode:
|
|
602
326
|
|
|
603
|
-
**
|
|
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
|
-
|
|
606
|
-
import { createCalendar } from 'kalendly/vanilla';
|
|
330
|
+
## Availability Mode
|
|
607
331
|
|
|
608
|
-
|
|
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
|
-
|
|
615
|
-
container: '#calendar',
|
|
616
|
-
events: events,
|
|
617
|
-
theme: themes.blue,
|
|
618
|
-
});
|
|
334
|
+
### Day view
|
|
619
335
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
calendar.updateTheme(themes.purple);
|
|
623
|
-
});
|
|
336
|
+
```html
|
|
337
|
+
<kal-calendar availability-mode="day"></kal-calendar>
|
|
624
338
|
```
|
|
625
339
|
|
|
626
|
-
|
|
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
|
-
|
|
342
|
+
Pass the minimal event shape — no names, no descriptions:
|
|
631
343
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
-
|
|
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
|
-
|
|
354
|
+
```html
|
|
355
|
+
<kal-calendar availability-mode="time"></kal-calendar>
|
|
356
|
+
```
|
|
648
357
|
|
|
649
|
-
|
|
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
|
-
###
|
|
360
|
+
### Selectable range
|
|
657
361
|
|
|
658
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
668
|
-
//
|
|
669
|
-
|
|
670
|
-
|
|
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
|
-
|
|
674
|
-
|
|
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
|
-
|
|
383
|
+
Booked days/slots cannot be selected. The 3-click state machine: first click selects, second extends, third resets.
|
|
679
384
|
|
|
680
|
-
|
|
681
|
-
const calendar = createCalendar(options);
|
|
385
|
+
## Lazy Event Fetching
|
|
682
386
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
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
|
-
|
|
405
|
+
## Core API
|
|
694
406
|
|
|
695
|
-
|
|
407
|
+
`querySelector('kal-calendar')` returns `CalendarElement | null` automatically — no cast needed:
|
|
696
408
|
|
|
697
|
-
|
|
409
|
+
```ts
|
|
410
|
+
import type { CalendarElement } from 'kalendly';
|
|
698
411
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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
|
-
|
|
420
|
+
**JavaScript** works the same way without the import:
|
|
707
421
|
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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
|
-
|
|
432
|
+
## CalendarEvent Interface
|
|
717
433
|
|
|
718
|
-
|
|
434
|
+
```typescript
|
|
435
|
+
interface CalendarEvent {
|
|
436
|
+
id: string | number;
|
|
437
|
+
name: string;
|
|
438
|
+
date: string | Date;
|
|
719
439
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
440
|
+
startTime?: string; // e.g. "09:00"
|
|
441
|
+
endTime?: string; // e.g. "10:00"
|
|
442
|
+
allDay?: boolean;
|
|
723
443
|
|
|
724
|
-
|
|
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
|
-
|
|
456
|
+
status?: 'scheduled' | 'completed' | 'cancelled' | 'tentative';
|
|
457
|
+
priority?: 'low' | 'medium' | 'high';
|
|
727
458
|
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
##
|
|
475
|
+
## Theming
|
|
733
476
|
|
|
734
|
-
### CSS
|
|
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
|
-
|
|
495
|
+
### JS theme property (full reference)
|
|
752
496
|
|
|
753
|
-
|
|
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
|
-
|
|
756
|
-
import { calendarStyles } from 'kalendly/react-native';
|
|
513
|
+
### Dark theme example
|
|
757
514
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
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
|
|
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
|
-
|
|
538
|
+
const viewModel = engine.getViewModel();
|
|
539
|
+
// re-render
|
|
783
540
|
});
|
|
784
541
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
565
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
849
566
|
|
|
850
567
|
### Recent Updates
|
|
851
568
|
|
|
852
|
-
- **v0.1
|
|
853
|
-
- **v0.
|
|
854
|
-
- **v0.1.
|
|
855
|
-
- **v0.1.
|
|
856
|
-
- **v0.1.
|
|
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
|