calkit 0.1.0
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 +734 -0
- package/dist/booking.es.js +1845 -0
- package/dist/booking.es.js.map +1 -0
- package/dist/booking.umd.js +888 -0
- package/dist/booking.umd.js.map +1 -0
- package/dist/calkit.es.js +5230 -0
- package/dist/calkit.es.js.map +1 -0
- package/dist/calkit.umd.js +2164 -0
- package/dist/calkit.umd.js.map +1 -0
- package/dist/datepicker.es.js +1619 -0
- package/dist/datepicker.es.js.map +1 -0
- package/dist/datepicker.umd.js +818 -0
- package/dist/datepicker.umd.js.map +1 -0
- package/dist/scheduler.es.js +3235 -0
- package/dist/scheduler.es.js.map +1 -0
- package/dist/scheduler.umd.js +1547 -0
- package/dist/scheduler.umd.js.map +1 -0
- package/dist/timepicker.es.js +962 -0
- package/dist/timepicker.es.js.map +1 -0
- package/dist/timepicker.umd.js +517 -0
- package/dist/timepicker.umd.js.map +1 -0
- package/llms.txt +594 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
# CalKit
|
|
2
|
+
|
|
3
|
+
Vanilla JS web component library for date pickers, time pickers, booking calendars, and resource schedulers. Zero dependencies. Shadow DOM encapsulated. Themeable.
|
|
4
|
+
|
|
5
|
+
<!--  -->
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
### CDN (`<script>` tag)
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<script src="https://cdn.jsdelivr.net/gh/SimonKefas/calkit/dist/calkit.umd.js"></script>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
All four components are registered automatically. No imports needed.
|
|
16
|
+
|
|
17
|
+
### Bundler (Vite, Webpack, etc.)
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import { CalDatepicker, CalBooking, CalTimepicker, CalScheduler } from 'calkit';
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Uses the ES module builds from `node_modules`.
|
|
24
|
+
|
|
25
|
+
### Individual Bundles
|
|
26
|
+
|
|
27
|
+
Load only the components you need:
|
|
28
|
+
|
|
29
|
+
| Bundle | CDN (`<script>`) | ES Module (bundler) | Gzipped |
|
|
30
|
+
|--------|------------------|---------------------|---------|
|
|
31
|
+
| **Full** (all 4) | `calkit.umd.js` (148 KB) | `calkit.es.js` (176 KB) | ~34 KB |
|
|
32
|
+
| **Datepicker** only | `datepicker.umd.js` (48 KB) | `datepicker.es.js` (56 KB) | ~12 KB |
|
|
33
|
+
| **Timepicker** only | `timepicker.umd.js` (32 KB) | `timepicker.es.js` (36 KB) | ~8 KB |
|
|
34
|
+
| **Booking** only | `booking.umd.js` (56 KB) | `booking.es.js` (64 KB) | ~14 KB |
|
|
35
|
+
| **Scheduler** only | `scheduler.umd.js` (88 KB) | `scheduler.es.js` (104 KB) | ~21 KB |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Datepicker
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<cal-datepicker mode="single" theme="light"></cal-datepicker>
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
const picker = document.querySelector('cal-datepicker');
|
|
48
|
+
picker.addEventListener('cal:change', (e) => {
|
|
49
|
+
console.log('Selected:', e.detail.value); // "2026-03-15"
|
|
50
|
+
});
|
|
51
|
+
</script>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Timepicker
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<cal-timepicker start-time="09:00" end-time="17:00" interval="30" format="12h"></cal-timepicker>
|
|
58
|
+
|
|
59
|
+
<script>
|
|
60
|
+
const tp = document.querySelector('cal-timepicker');
|
|
61
|
+
tp.addEventListener('cal:time-change', (e) => {
|
|
62
|
+
console.log('Selected:', e.detail.value); // "14:30"
|
|
63
|
+
});
|
|
64
|
+
</script>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Booking Calendar
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<cal-booking theme="light"></cal-booking>
|
|
71
|
+
|
|
72
|
+
<script>
|
|
73
|
+
const booking = document.querySelector('cal-booking');
|
|
74
|
+
booking.bookings = [
|
|
75
|
+
{ id: '1', start: '2026-03-10', end: '2026-03-14', label: 'Alice', color: 'blue' },
|
|
76
|
+
{ id: '2', start: '2026-03-14', end: '2026-03-18', label: 'Bob', color: 'green' },
|
|
77
|
+
];
|
|
78
|
+
booking.addEventListener('cal:change', (e) => {
|
|
79
|
+
console.log('Booked:', e.detail.value); // { start, end }
|
|
80
|
+
});
|
|
81
|
+
</script>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Scheduler
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<cal-scheduler view="week" start-time="08:00" end-time="18:00" theme="light"></cal-scheduler>
|
|
88
|
+
|
|
89
|
+
<script>
|
|
90
|
+
const sched = document.querySelector('cal-scheduler');
|
|
91
|
+
sched.resources = [
|
|
92
|
+
{ id: 'room-a', name: 'Room A' },
|
|
93
|
+
{ id: 'room-b', name: 'Room B' },
|
|
94
|
+
];
|
|
95
|
+
sched.events = [
|
|
96
|
+
{ id: '1', title: 'Meeting', start: '2026-03-02', startTime: '09:00', endTime: '10:30', resourceId: 'room-a', color: 'blue' },
|
|
97
|
+
];
|
|
98
|
+
sched.addEventListener('cal:slot-select', (e) => {
|
|
99
|
+
console.log('Slot:', e.detail); // { date, startTime, endTime, resourceId, resource }
|
|
100
|
+
});
|
|
101
|
+
</script>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Components Overview
|
|
107
|
+
|
|
108
|
+
| Feature | Datepicker | Timepicker | Booking | Scheduler |
|
|
109
|
+
|---------|:----------:|:----------:|:-------:|:---------:|
|
|
110
|
+
| Selection modes | single, multi, range | single, multi, range | range | slot-based |
|
|
111
|
+
| Display modes | inline, popover | inline, popover | inline, popover | inline |
|
|
112
|
+
| Views | — | — | — | day, week, month |
|
|
113
|
+
| Theming | light, dark, auto | light, dark, auto | light, dark, auto | light, dark, auto |
|
|
114
|
+
| Loading skeleton | yes | yes | yes | yes |
|
|
115
|
+
| Keyboard navigation | yes | — | yes | Escape to dismiss |
|
|
116
|
+
| Drag interaction | — | — | — | move, resize, create |
|
|
117
|
+
| Resources | — | — | — | tabs, columns |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## cal-datepicker
|
|
122
|
+
|
|
123
|
+
A date picker supporting single, multi, and range selection with inline or popover display.
|
|
124
|
+
|
|
125
|
+
### Attributes
|
|
126
|
+
|
|
127
|
+
| Attribute | Type | Default | Description |
|
|
128
|
+
|-----------|------|---------|-------------|
|
|
129
|
+
| `mode` | `"single"` \| `"multi"` \| `"range"` | `"single"` | Selection mode |
|
|
130
|
+
| `display` | `"inline"` \| `"popover"` | `"inline"` | Render mode |
|
|
131
|
+
| `theme` | `"light"` \| `"dark"` \| `"auto"` | `"light"` | Color theme |
|
|
132
|
+
| `value` | `string` | — | Initial value. Single: `"2026-03-15"`. Range: `"2026-03-10/2026-03-15"`. Multi: `"2026-03-10,2026-03-12"` |
|
|
133
|
+
| `min-date` | `string` | — | Earliest selectable date (`"YYYY-MM-DD"`) |
|
|
134
|
+
| `max-date` | `string` | — | Latest selectable date (`"YYYY-MM-DD"`) |
|
|
135
|
+
| `disabled-dates` | `string` | — | Comma-separated dates to disable (`"2026-03-25,2026-03-26"`) |
|
|
136
|
+
| `first-day` | `number` | `0` | First day of week (0 = Sunday, 1 = Monday) |
|
|
137
|
+
| `locale` | `string` | — | Locale for formatting |
|
|
138
|
+
| `presets` | `string` | — | Comma-separated preset keys for range mode: `"today,this-week,next-7,next-30"` |
|
|
139
|
+
| `placeholder` | `string` | `"Select date"` | Popover trigger placeholder text |
|
|
140
|
+
| `dual` | `boolean` | — | Show two months side-by-side (range mode) |
|
|
141
|
+
| `loading` | `boolean` | — | Show skeleton loading state |
|
|
142
|
+
|
|
143
|
+
### Properties
|
|
144
|
+
|
|
145
|
+
| Property | Type | Description |
|
|
146
|
+
|----------|------|-------------|
|
|
147
|
+
| `value` | `string \| string[] \| {start: string, end: string} \| null` | Read/write. Shape depends on `mode` |
|
|
148
|
+
| `loading` | `boolean` | Read/write loading state |
|
|
149
|
+
|
|
150
|
+
### Methods
|
|
151
|
+
|
|
152
|
+
| Method | Parameters | Description |
|
|
153
|
+
|--------|------------|-------------|
|
|
154
|
+
| `open()` | — | Open popover (popover mode only) |
|
|
155
|
+
| `close()` | — | Close popover |
|
|
156
|
+
| `goToMonth(month, year)` | `month: number (0-11), year: number` | Navigate view to specific month |
|
|
157
|
+
| `showStatus(type, message, opts?)` | `type: "error"\|"warning"\|"info"\|"success"` | Show status banner |
|
|
158
|
+
| `clearStatus()` | — | Clear status banner |
|
|
159
|
+
|
|
160
|
+
### Events
|
|
161
|
+
|
|
162
|
+
| Event | `detail` shape | Fires when |
|
|
163
|
+
|-------|---------------|------------|
|
|
164
|
+
| `cal:change` | `{value: string}` (single) / `{value: {start, end}}` (range) / `{value: string[]}` (multi) | Date selection changes |
|
|
165
|
+
| `cal:month-change` | `{year: number, month: number}` | View navigates to new month |
|
|
166
|
+
| `cal:open` | `{}` | Popover opens |
|
|
167
|
+
| `cal:close` | `{}` | Popover closes |
|
|
168
|
+
| `cal:status` | `{type: string\|null, message: string\|null}` | Status banner changes |
|
|
169
|
+
|
|
170
|
+
### Example: Inline Range with Presets
|
|
171
|
+
|
|
172
|
+
```html
|
|
173
|
+
<cal-datepicker
|
|
174
|
+
mode="range"
|
|
175
|
+
dual
|
|
176
|
+
presets="today,this-week,next-7,next-30"
|
|
177
|
+
min-date="2026-01-01"
|
|
178
|
+
theme="light"
|
|
179
|
+
></cal-datepicker>
|
|
180
|
+
|
|
181
|
+
<script>
|
|
182
|
+
document.querySelector('cal-datepicker').addEventListener('cal:change', (e) => {
|
|
183
|
+
const { start, end } = e.detail.value;
|
|
184
|
+
console.log(`Range: ${start} to ${end}`);
|
|
185
|
+
});
|
|
186
|
+
</script>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Example: Popover with Initial Value
|
|
190
|
+
|
|
191
|
+
```html
|
|
192
|
+
<cal-datepicker
|
|
193
|
+
mode="single"
|
|
194
|
+
display="popover"
|
|
195
|
+
value="2026-03-15"
|
|
196
|
+
placeholder="Pick a date"
|
|
197
|
+
></cal-datepicker>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
> Full reference: [docs/cal-datepicker.md](docs/cal-datepicker.md)
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## cal-timepicker
|
|
205
|
+
|
|
206
|
+
A time slot picker for selecting single times, multiple times, or time ranges.
|
|
207
|
+
|
|
208
|
+
### Attributes
|
|
209
|
+
|
|
210
|
+
| Attribute | Type | Default | Description |
|
|
211
|
+
|-----------|------|---------|-------------|
|
|
212
|
+
| `mode` | `"single"` \| `"multi"` \| `"range"` | `"single"` | Selection mode |
|
|
213
|
+
| `display` | `"inline"` \| `"popover"` | `"inline"` | Render mode |
|
|
214
|
+
| `theme` | `"light"` \| `"dark"` \| `"auto"` | `"light"` | Color theme |
|
|
215
|
+
| `start-time` | `string` | `"09:00"` | First slot time (`"HH:MM"`) |
|
|
216
|
+
| `end-time` | `string` | `"17:00"` | Last slot boundary (`"HH:MM"`) |
|
|
217
|
+
| `interval` | `number` | `30` | Minutes between slots |
|
|
218
|
+
| `format` | `"12h"` \| `"24h"` | `"24h"` | Time display format |
|
|
219
|
+
| `value` | `string` | — | Initial value. Single: `"14:30"`. Range: `"09:00/12:00"`. Multi: `"09:00,10:30"` |
|
|
220
|
+
| `placeholder` | `string` | `"Select time"` | Popover trigger placeholder |
|
|
221
|
+
| `duration-labels` | `boolean` | — | Show duration labels on each slot |
|
|
222
|
+
| `loading` | `boolean` | — | Show skeleton loading state |
|
|
223
|
+
|
|
224
|
+
### Properties
|
|
225
|
+
|
|
226
|
+
| Property | Type | Description |
|
|
227
|
+
|----------|------|-------------|
|
|
228
|
+
| `value` | `string \| string[] \| {start: string, end: string} \| null` | Read/write. Shape depends on `mode` |
|
|
229
|
+
| `slots` | `Array<{time: string, label?: string, available?: boolean}>` | Custom slot definitions (overrides auto-generation) |
|
|
230
|
+
| `unavailableTimes` | `string[]` | Array of `"HH:MM"` strings to mark unavailable |
|
|
231
|
+
| `loading` | `boolean` | Read/write loading state |
|
|
232
|
+
|
|
233
|
+
### Methods
|
|
234
|
+
|
|
235
|
+
| Method | Parameters | Description |
|
|
236
|
+
|--------|------------|-------------|
|
|
237
|
+
| `open()` | — | Open popover |
|
|
238
|
+
| `close()` | — | Close popover |
|
|
239
|
+
| `showStatus(type, message, opts?)` | `type: "error"\|"warning"\|"info"\|"success"` | Show status banner |
|
|
240
|
+
| `clearStatus()` | — | Clear status banner |
|
|
241
|
+
|
|
242
|
+
### Events
|
|
243
|
+
|
|
244
|
+
| Event | `detail` shape | Fires when |
|
|
245
|
+
|-------|---------------|------------|
|
|
246
|
+
| `cal:time-change` | `{value: string}` (single) / `{value: {start, end}}` (range) / `{value: string[]}` (multi) | Time selection changes |
|
|
247
|
+
| `cal:open` | `{}` | Popover opens |
|
|
248
|
+
| `cal:close` | `{}` | Popover closes |
|
|
249
|
+
| `cal:status` | `{type: string\|null, message: string\|null}` | Status banner changes |
|
|
250
|
+
|
|
251
|
+
### Example: Duration Labels
|
|
252
|
+
|
|
253
|
+
```html
|
|
254
|
+
<cal-timepicker
|
|
255
|
+
start-time="09:00"
|
|
256
|
+
end-time="17:00"
|
|
257
|
+
interval="60"
|
|
258
|
+
format="12h"
|
|
259
|
+
duration-labels
|
|
260
|
+
></cal-timepicker>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Example: Custom Slots with Unavailable Times
|
|
264
|
+
|
|
265
|
+
```html
|
|
266
|
+
<cal-timepicker mode="single" format="12h"></cal-timepicker>
|
|
267
|
+
|
|
268
|
+
<script>
|
|
269
|
+
const tp = document.querySelector('cal-timepicker');
|
|
270
|
+
tp.slots = [
|
|
271
|
+
{ time: '09:00', label: 'Morning', available: true },
|
|
272
|
+
{ time: '12:00', label: 'Lunch', available: false },
|
|
273
|
+
{ time: '14:00', label: 'Afternoon', available: true },
|
|
274
|
+
];
|
|
275
|
+
</script>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
> Full reference: [docs/cal-timepicker.md](docs/cal-timepicker.md)
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## cal-booking
|
|
283
|
+
|
|
284
|
+
A booking calendar that displays existing reservations as colored overlays and lets users select date ranges. Supports overlap validation and optional time slot selection.
|
|
285
|
+
|
|
286
|
+
### Attributes
|
|
287
|
+
|
|
288
|
+
| Attribute | Type | Default | Description |
|
|
289
|
+
|-----------|------|---------|-------------|
|
|
290
|
+
| `theme` | `"light"` \| `"dark"` \| `"auto"` | `"light"` | Color theme |
|
|
291
|
+
| `display` | `"inline"` \| `"popover"` | `"inline"` | Render mode |
|
|
292
|
+
| `min-date` | `string` | — | Earliest selectable date |
|
|
293
|
+
| `max-date` | `string` | — | Latest selectable date |
|
|
294
|
+
| `first-day` | `number` | `0` | First day of week |
|
|
295
|
+
| `placeholder` | `string` | `"Select dates"` | Popover trigger placeholder |
|
|
296
|
+
| `dual` | `boolean` | — | Show two months side-by-side |
|
|
297
|
+
| `show-labels-on-hover` | `boolean` | — | Show booking labels on day hover |
|
|
298
|
+
| `time-slots` | `boolean` | — | Enable time slot selection after date range |
|
|
299
|
+
| `time-start` | `string` | `"09:00"` | Time grid start (when `time-slots` enabled) |
|
|
300
|
+
| `time-end` | `string` | `"17:00"` | Time grid end |
|
|
301
|
+
| `time-interval` | `number` | `60` | Time grid interval in minutes |
|
|
302
|
+
| `time-format` | `"12h"` \| `"24h"` | `"24h"` | Time display format |
|
|
303
|
+
| `duration-labels` | `boolean` | — | Show duration labels on time slots |
|
|
304
|
+
| `loading` | `boolean` | — | Show skeleton loading state |
|
|
305
|
+
|
|
306
|
+
### Properties
|
|
307
|
+
|
|
308
|
+
| Property | Type | Description |
|
|
309
|
+
|----------|------|-------------|
|
|
310
|
+
| `value` | `{start: string, end: string, startTime?: string, endTime?: string} \| null` | Read/write selected range |
|
|
311
|
+
| `bookings` | `Booking[]` | Array of existing bookings to display |
|
|
312
|
+
| `dayData` | `Record<string, {label?: string, status?: string}>` | Per-date metadata |
|
|
313
|
+
| `labelFormula` | `(dateStr: string) => {label?: string, status?: string} \| null` | Dynamic label function (highest priority) |
|
|
314
|
+
| `timeSlots` | `Array<{time: string, label?: string, available?: boolean}>` | Custom time slot definitions |
|
|
315
|
+
| `loading` | `boolean` | Read/write loading state |
|
|
316
|
+
|
|
317
|
+
### Methods
|
|
318
|
+
|
|
319
|
+
| Method | Parameters | Description |
|
|
320
|
+
|--------|------------|-------------|
|
|
321
|
+
| `open()` | — | Open popover |
|
|
322
|
+
| `close()` | — | Close popover |
|
|
323
|
+
| `goToMonth(month, year)` | `month: number (0-11), year: number` | Navigate to month |
|
|
324
|
+
| `showStatus(type, message, opts?)` | `type: "error"\|"warning"\|"info"\|"success"` | Show status banner |
|
|
325
|
+
| `clearStatus()` | — | Clear status banner |
|
|
326
|
+
|
|
327
|
+
### Events
|
|
328
|
+
|
|
329
|
+
| Event | `detail` shape | Fires when |
|
|
330
|
+
|-------|---------------|------------|
|
|
331
|
+
| `cal:change` | `{value: {start, end, startTime?, endTime?}}` | Range selection completes |
|
|
332
|
+
| `cal:selection-invalid` | `{start: string, end: string}` | Selection overlaps existing booking |
|
|
333
|
+
| `cal:month-change` | `{year: number, month: number}` | View navigates to new month |
|
|
334
|
+
| `cal:open` | `{}` | Popover opens |
|
|
335
|
+
| `cal:close` | `{}` | Popover closes |
|
|
336
|
+
| `cal:status` | `{type: string\|null, message: string\|null}` | Status banner changes |
|
|
337
|
+
|
|
338
|
+
### Example: Booking Calendar with Bookings
|
|
339
|
+
|
|
340
|
+
```html
|
|
341
|
+
<cal-booking theme="light" dual></cal-booking>
|
|
342
|
+
|
|
343
|
+
<script>
|
|
344
|
+
const booking = document.querySelector('cal-booking');
|
|
345
|
+
booking.bookings = [
|
|
346
|
+
{ id: '1', start: '2026-03-05', end: '2026-03-10', label: 'Alice', color: 'blue' },
|
|
347
|
+
{ id: '2', start: '2026-03-10', end: '2026-03-15', label: 'Bob', color: 'green' },
|
|
348
|
+
{ id: '3', start: '2026-03-20', end: '2026-03-25', label: 'Carol', color: 'orange' },
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
// Price labels via formula
|
|
352
|
+
booking.labelFormula = (date) => {
|
|
353
|
+
const d = new Date(date);
|
|
354
|
+
const isWeekend = d.getDay() === 0 || d.getDay() === 6;
|
|
355
|
+
return { label: isWeekend ? '$150' : '$100' };
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
booking.addEventListener('cal:change', (e) => {
|
|
359
|
+
console.log('Selected range:', e.detail.value);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
booking.addEventListener('cal:selection-invalid', () => {
|
|
363
|
+
console.log('Overlaps existing booking!');
|
|
364
|
+
});
|
|
365
|
+
</script>
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Example: Date + Time Flow
|
|
369
|
+
|
|
370
|
+
```html
|
|
371
|
+
<cal-booking
|
|
372
|
+
time-slots
|
|
373
|
+
time-start="14:00"
|
|
374
|
+
time-end="22:00"
|
|
375
|
+
time-interval="30"
|
|
376
|
+
time-format="12h"
|
|
377
|
+
></cal-booking>
|
|
378
|
+
|
|
379
|
+
<script>
|
|
380
|
+
document.querySelector('cal-booking').addEventListener('cal:change', (e) => {
|
|
381
|
+
// { start: "2026-03-10", end: "2026-03-12", startTime: "14:00", endTime: "11:00" }
|
|
382
|
+
console.log(e.detail.value);
|
|
383
|
+
});
|
|
384
|
+
</script>
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
> Full reference: [docs/cal-booking.md](docs/cal-booking.md)
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## cal-scheduler
|
|
392
|
+
|
|
393
|
+
A full-featured resource scheduling calendar with day, week, and month views. Supports drag-to-move, drag-to-resize, drag-to-create, resource tabs/columns, all-day events, and a floating action button.
|
|
394
|
+
|
|
395
|
+
### Attributes
|
|
396
|
+
|
|
397
|
+
| Attribute | Type | Default | Description |
|
|
398
|
+
|-----------|------|---------|-------------|
|
|
399
|
+
| `theme` | `"light"` \| `"dark"` \| `"auto"` | `"light"` | Color theme |
|
|
400
|
+
| `view` | `"day"` \| `"week"` \| `"month"` | `"week"` | Current calendar view |
|
|
401
|
+
| `layout` | `"vertical"` | `"vertical"` | Grid layout |
|
|
402
|
+
| `date` | `string` | today | Anchor date (`"YYYY-MM-DD"`) |
|
|
403
|
+
| `start-time` | `string` | `"08:00"` | Day grid start time |
|
|
404
|
+
| `end-time` | `string` | `"18:00"` | Day grid end time |
|
|
405
|
+
| `interval` | `number` | `30` | Slot interval in minutes |
|
|
406
|
+
| `format` | `"12h"` \| `"24h"` | `"24h"` | Time display format |
|
|
407
|
+
| `first-day` | `number` | `0` | First day of week |
|
|
408
|
+
| `slot-height` | `number` | `48` | Pixel height per time slot |
|
|
409
|
+
| `resource-mode` | `"tabs"` \| `"columns"` | `"tabs"` | How resources are displayed |
|
|
410
|
+
| `show-event-time` | `"true"` \| `"false"` | `"true"` | Show time row on event blocks |
|
|
411
|
+
| `show-fab` | `boolean` | — | Show floating action button |
|
|
412
|
+
| `draggable-events` | `boolean` | — | Enable drag-to-move/resize/create |
|
|
413
|
+
| `snap-interval` | `number` | — | Drag snap interval in minutes (defaults to `interval`) |
|
|
414
|
+
| `min-duration` | `number` | — | Minimum event duration in minutes for drag operations |
|
|
415
|
+
| `max-duration` | `number` | — | Maximum event duration in minutes for drag operations |
|
|
416
|
+
| `loading` | `boolean` | — | Show skeleton loading state |
|
|
417
|
+
|
|
418
|
+
### Properties
|
|
419
|
+
|
|
420
|
+
| Property | Type | Description |
|
|
421
|
+
|----------|------|-------------|
|
|
422
|
+
| `resources` | `Resource[]` | Array of resource objects |
|
|
423
|
+
| `events` | `Event[]` | Array of event objects |
|
|
424
|
+
| `eventActions` | `EventAction[]` | Action buttons shown in event detail popover |
|
|
425
|
+
| `eventContent` | `(event: Event, resource: Resource) => HTMLElement \| string` | Custom event block renderer |
|
|
426
|
+
| `value` | `{date, startTime, endTime, resourceId, resource} \| null` | Read-only last selected slot |
|
|
427
|
+
| `loading` | `boolean` | Read/write loading state |
|
|
428
|
+
|
|
429
|
+
### Methods
|
|
430
|
+
|
|
431
|
+
| Method | Parameters | Description |
|
|
432
|
+
|--------|------------|-------------|
|
|
433
|
+
| `goToDate(dateStr)` | `dateStr: string` | Navigate to date |
|
|
434
|
+
| `setView(view)` | `view: "day"\|"week"\|"month"` | Switch view |
|
|
435
|
+
| `today()` | — | Navigate to today |
|
|
436
|
+
| `next()` | — | Navigate forward (day/week/month) |
|
|
437
|
+
| `prev()` | — | Navigate backward |
|
|
438
|
+
| `findAvailableSlot(opts)` | `{date?, duration, resourceId?, minCapacity?}` | Find first available slot across resources |
|
|
439
|
+
| `isSlotAvailable(date, startTime, endTime, resourceId)` | all `string` | Check if a specific slot is free |
|
|
440
|
+
| `showStatus(type, message, opts?)` | `type: "error"\|"warning"\|"info"\|"success"` | Show status banner |
|
|
441
|
+
| `clearStatus()` | — | Clear status banner |
|
|
442
|
+
|
|
443
|
+
### Events
|
|
444
|
+
|
|
445
|
+
| Event | `detail` shape | Fires when |
|
|
446
|
+
|-------|---------------|------------|
|
|
447
|
+
| `cal:slot-select` | `{date, startTime, endTime, resourceId, resource}` | Empty slot is clicked |
|
|
448
|
+
| `cal:slot-create` | `{date, startTime, endTime, resourceId, resource}` | Drag-to-create completes |
|
|
449
|
+
| `cal:event-click` | `{event, resourceId, resource}` | Event block is clicked |
|
|
450
|
+
| `cal:event-move` | `{event, from: {date, startTime, endTime, resourceId}, to: {date, startTime, endTime, resourceId}}` | Drag-to-move completes |
|
|
451
|
+
| `cal:event-resize` | `{event, from: {endTime}, to: {endTime}}` | Drag-to-resize completes |
|
|
452
|
+
| `cal:event-action` | `{action: string, event, resourceId, resource}` | Event detail action button clicked |
|
|
453
|
+
| `cal:fab-create` | `{date: string, view: string}` | FAB button clicked |
|
|
454
|
+
| `cal:date-change` | `{date: string, view: string}` | Navigation changes date |
|
|
455
|
+
| `cal:view-change` | `{view: string, date: string}` | View type changes |
|
|
456
|
+
| `cal:status` | `{type: string\|null, message: string\|null}` | Status banner changes |
|
|
457
|
+
|
|
458
|
+
### Example: Week View with Resources
|
|
459
|
+
|
|
460
|
+
```html
|
|
461
|
+
<cal-scheduler
|
|
462
|
+
view="week"
|
|
463
|
+
resource-mode="tabs"
|
|
464
|
+
start-time="08:00"
|
|
465
|
+
end-time="18:00"
|
|
466
|
+
format="12h"
|
|
467
|
+
theme="light"
|
|
468
|
+
></cal-scheduler>
|
|
469
|
+
|
|
470
|
+
<script>
|
|
471
|
+
const sched = document.querySelector('cal-scheduler');
|
|
472
|
+
|
|
473
|
+
sched.resources = [
|
|
474
|
+
{ id: 'room-a', name: 'Room A', capacity: 10 },
|
|
475
|
+
{ id: 'room-b', name: 'Room B', capacity: 20 },
|
|
476
|
+
];
|
|
477
|
+
|
|
478
|
+
sched.events = [
|
|
479
|
+
{
|
|
480
|
+
id: '1', title: 'Team Standup',
|
|
481
|
+
start: '2026-03-02', startTime: '09:00', endTime: '09:30',
|
|
482
|
+
resourceId: 'room-a', color: 'blue',
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
id: '2', title: 'Workshop',
|
|
486
|
+
start: '2026-03-03', startTime: '13:00', endTime: '16:00',
|
|
487
|
+
resourceId: 'room-b', color: 'green',
|
|
488
|
+
},
|
|
489
|
+
];
|
|
490
|
+
|
|
491
|
+
sched.eventActions = [
|
|
492
|
+
{ label: 'Edit' },
|
|
493
|
+
{ label: 'Delete', type: 'danger' },
|
|
494
|
+
];
|
|
495
|
+
|
|
496
|
+
sched.addEventListener('cal:event-action', (e) => {
|
|
497
|
+
console.log(e.detail.action, e.detail.event);
|
|
498
|
+
});
|
|
499
|
+
</script>
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Example: Drag-to-Move and Resize
|
|
503
|
+
|
|
504
|
+
```html
|
|
505
|
+
<cal-scheduler
|
|
506
|
+
view="week"
|
|
507
|
+
draggable-events
|
|
508
|
+
snap-interval="15"
|
|
509
|
+
min-duration="15"
|
|
510
|
+
max-duration="240"
|
|
511
|
+
></cal-scheduler>
|
|
512
|
+
|
|
513
|
+
<script>
|
|
514
|
+
const sched = document.querySelector('cal-scheduler');
|
|
515
|
+
// ... set resources and events ...
|
|
516
|
+
|
|
517
|
+
sched.addEventListener('cal:event-move', (e) => {
|
|
518
|
+
const { event, from, to } = e.detail;
|
|
519
|
+
console.log(`Moved "${event.title}" from ${from.date} ${from.startTime} to ${to.date} ${to.startTime}`);
|
|
520
|
+
// Update your data and re-assign sched.events
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
sched.addEventListener('cal:event-resize', (e) => {
|
|
524
|
+
const { event, from, to } = e.detail;
|
|
525
|
+
console.log(`Resized "${event.title}" end: ${from.endTime} → ${to.endTime}`);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
sched.addEventListener('cal:slot-create', (e) => {
|
|
529
|
+
console.log('New event via drag:', e.detail);
|
|
530
|
+
// { date, startTime, endTime, resourceId, resource }
|
|
531
|
+
});
|
|
532
|
+
</script>
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Example: Venue Booking with Availability Check
|
|
536
|
+
|
|
537
|
+
```html
|
|
538
|
+
<cal-scheduler view="day" show-fab></cal-scheduler>
|
|
539
|
+
|
|
540
|
+
<script>
|
|
541
|
+
const sched = document.querySelector('cal-scheduler');
|
|
542
|
+
|
|
543
|
+
sched.resources = [
|
|
544
|
+
{ id: 'court-1', name: 'Court 1', capacity: 4 },
|
|
545
|
+
{ id: 'court-2', name: 'Court 2', capacity: 4 },
|
|
546
|
+
];
|
|
547
|
+
|
|
548
|
+
// Find first available 60-minute slot
|
|
549
|
+
const slot = sched.findAvailableSlot({ duration: 60 });
|
|
550
|
+
if (slot) {
|
|
551
|
+
console.log(`Available: ${slot.date} ${slot.startTime}-${slot.endTime} on ${slot.resourceId}`);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Check specific slot
|
|
555
|
+
const free = sched.isSlotAvailable('2026-03-02', '10:00', '11:00', 'court-1');
|
|
556
|
+
|
|
557
|
+
sched.addEventListener('cal:fab-create', (e) => {
|
|
558
|
+
console.log('Create new event for', e.detail.date);
|
|
559
|
+
});
|
|
560
|
+
</script>
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
> Full reference: [docs/cal-scheduler.md](docs/cal-scheduler.md)
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## Theming
|
|
568
|
+
|
|
569
|
+
All components support three theme modes via the `theme` attribute:
|
|
570
|
+
|
|
571
|
+
| Value | Behavior |
|
|
572
|
+
|-------|----------|
|
|
573
|
+
| `"light"` (default) | Light color scheme |
|
|
574
|
+
| `"dark"` | Dark color scheme |
|
|
575
|
+
| `"auto"` | Follows `prefers-color-scheme` media query |
|
|
576
|
+
|
|
577
|
+
### Top CSS Custom Properties
|
|
578
|
+
|
|
579
|
+
Override from outside the Shadow DOM using the tag selector:
|
|
580
|
+
|
|
581
|
+
| Token | Light Default | Description |
|
|
582
|
+
|-------|--------------|-------------|
|
|
583
|
+
| `--cal-bg` | `0 0% 100%` | Background color (HSL channels) |
|
|
584
|
+
| `--cal-fg` | `240 6% 10%` | Foreground/text color |
|
|
585
|
+
| `--cal-accent` | `240 6% 10%` | Accent color for selected states |
|
|
586
|
+
| `--cal-border` | `240 6% 90%` | Border color |
|
|
587
|
+
| `--cal-radius` | `8px` | Border radius |
|
|
588
|
+
|
|
589
|
+
### Override Example
|
|
590
|
+
|
|
591
|
+
```css
|
|
592
|
+
cal-datepicker, cal-scheduler {
|
|
593
|
+
--cal-accent: 220 90% 56%;
|
|
594
|
+
--cal-accent-fg: 0 0% 100%;
|
|
595
|
+
--cal-radius: 12px;
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
All color tokens use raw HSL channels (e.g., `240 6% 10%`) and are consumed internally as `hsl(var(--cal-...))`. This allows alpha modifications with the `/` syntax: `hsl(var(--cal-accent) / 0.5)`.
|
|
600
|
+
|
|
601
|
+
> Full token table: [docs/theming.md](docs/theming.md)
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## Data Shapes
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
interface Event {
|
|
609
|
+
id: string;
|
|
610
|
+
title: string;
|
|
611
|
+
start: string; // "YYYY-MM-DD"
|
|
612
|
+
end?: string; // "YYYY-MM-DD" (multi-day or all-day)
|
|
613
|
+
startTime?: string; // "HH:MM" (omit for all-day events)
|
|
614
|
+
endTime?: string; // "HH:MM"
|
|
615
|
+
resourceId?: string; // links to Resource.id
|
|
616
|
+
color?: "blue" | "green" | "red" | "orange" | "gray";
|
|
617
|
+
locked?: boolean; // prevents drag operations
|
|
618
|
+
metadata?: Record<string, string | number>;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
interface Resource {
|
|
622
|
+
id: string;
|
|
623
|
+
name: string;
|
|
624
|
+
capacity?: number;
|
|
625
|
+
color?: "blue" | "green" | "red" | "orange" | "gray";
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
interface Booking {
|
|
629
|
+
id: string;
|
|
630
|
+
start: string; // "YYYY-MM-DD"
|
|
631
|
+
end: string; // "YYYY-MM-DD"
|
|
632
|
+
label?: string;
|
|
633
|
+
color?: "blue" | "green" | "red" | "orange" | "gray";
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
interface TimeSlot {
|
|
637
|
+
time: string; // "HH:MM"
|
|
638
|
+
label?: string; // display label
|
|
639
|
+
available?: boolean; // default: true
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
interface DayData {
|
|
643
|
+
[dateStr: string]: {
|
|
644
|
+
label?: string;
|
|
645
|
+
status?: string; // "available" | "booked" | custom
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
interface EventAction {
|
|
650
|
+
label: string;
|
|
651
|
+
type?: "danger"; // red styling
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## Framework Integration
|
|
658
|
+
|
|
659
|
+
CalKit components are standard web components. They work in any framework.
|
|
660
|
+
|
|
661
|
+
### React
|
|
662
|
+
|
|
663
|
+
```jsx
|
|
664
|
+
import 'calkit';
|
|
665
|
+
|
|
666
|
+
function App() {
|
|
667
|
+
const ref = useRef(null);
|
|
668
|
+
|
|
669
|
+
useEffect(() => {
|
|
670
|
+
const el = ref.current;
|
|
671
|
+
el.events = [/* ... */];
|
|
672
|
+
|
|
673
|
+
const handler = (e) => console.log(e.detail);
|
|
674
|
+
el.addEventListener('cal:change', handler);
|
|
675
|
+
return () => el.removeEventListener('cal:change', handler);
|
|
676
|
+
}, []);
|
|
677
|
+
|
|
678
|
+
return <cal-datepicker ref={ref} mode="single" theme="light" />;
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### Vue
|
|
683
|
+
|
|
684
|
+
```vue
|
|
685
|
+
<template>
|
|
686
|
+
<cal-datepicker
|
|
687
|
+
ref="picker"
|
|
688
|
+
mode="range"
|
|
689
|
+
theme="light"
|
|
690
|
+
@cal:change="onChange"
|
|
691
|
+
/>
|
|
692
|
+
</template>
|
|
693
|
+
|
|
694
|
+
<script setup>
|
|
695
|
+
import 'calkit';
|
|
696
|
+
|
|
697
|
+
function onChange(e) {
|
|
698
|
+
console.log(e.detail.value);
|
|
699
|
+
}
|
|
700
|
+
</script>
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### Svelte
|
|
704
|
+
|
|
705
|
+
```svelte
|
|
706
|
+
<script>
|
|
707
|
+
import 'calkit';
|
|
708
|
+
|
|
709
|
+
function handleChange(e) {
|
|
710
|
+
console.log(e.detail.value);
|
|
711
|
+
}
|
|
712
|
+
</script>
|
|
713
|
+
|
|
714
|
+
<cal-datepicker mode="single" theme="light" on:cal:change={handleChange} />
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## Browser Support
|
|
720
|
+
|
|
721
|
+
CalKit requires [Constructable Stylesheets](https://web.dev/constructable-stylesheets/) (`adoptedStyleSheets`). Fallback `<style>` injection is included for older browsers.
|
|
722
|
+
|
|
723
|
+
| Browser | Version |
|
|
724
|
+
|---------|---------|
|
|
725
|
+
| Chrome | 73+ |
|
|
726
|
+
| Edge | 79+ |
|
|
727
|
+
| Firefox | 101+ |
|
|
728
|
+
| Safari | 16.4+ |
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
## License
|
|
733
|
+
|
|
734
|
+
MIT
|