@nomideusz/svelte-calendar 0.3.1 → 0.4.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 +568 -544
- package/dist/calendar/Calendar.svelte +21 -1
- package/dist/calendar/Calendar.svelte.d.ts +1 -0
- package/dist/calendar/Toolbar.svelte +5 -2
- package/dist/calendar/Toolbar.svelte.d.ts +2 -0
- package/dist/engine/view-state.svelte.d.ts +1 -0
- package/dist/engine/view-state.svelte.js +3 -0
- package/dist/views/agenda/Agenda.svelte +7 -5
- package/dist/views/agenda/Agenda.svelte.d.ts +1 -0
- package/dist/views/day/DayGrid.svelte +6 -3
- package/dist/views/day/DayGrid.svelte.d.ts +2 -0
- package/dist/views/day/DayTimeline.svelte +4 -3
- package/dist/views/day/DayTimeline.svelte.d.ts +5 -1
- package/dist/views/schedule/WeekSchedule.svelte +4 -0
- package/dist/views/schedule/WeekSchedule.svelte.d.ts +2 -0
- package/dist/views/settings/Settings.svelte +451 -173
- package/dist/views/settings/Settings.svelte.d.ts +22 -6
- package/dist/views/week/WeekGrid.svelte +9 -8
- package/dist/views/week/WeekGrid.svelte.d.ts +1 -0
- package/dist/views/week/WeekHeatmap.svelte +8 -7
- package/dist/views/week/WeekHeatmap.svelte.d.ts +1 -0
- package/dist/widget/CalendarWidget.svelte +0 -2
- package/dist/widget/widget.d.ts +1 -7
- package/dist/widget/widget.js +44 -3
- package/package.json +65 -67
- package/widget/widget.js +260 -260
package/README.md
CHANGED
|
@@ -1,544 +1,568 @@
|
|
|
1
|
-
# @nomideusz/svelte-calendar
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
|
15
|
-
|
|
16
|
-
| **
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{ id: '
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
]
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
|
110
|
-
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
`
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
###
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
|
337
|
-
|
|
338
|
-
| `--dt-
|
|
339
|
-
| `--dt-
|
|
340
|
-
| `--dt-
|
|
341
|
-
| `--dt-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
-
|
|
352
|
-
-
|
|
353
|
-
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
##
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
<
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1
|
+
# @nomideusz/svelte-calendar
|
|
2
|
+
|
|
3
|
+
[](https://github.com/nomideusz/svelte-calendar)
|
|
4
|
+
|
|
5
|
+
A themeable, pluggable **Svelte 5** calendar component library with **Day** and **Week** views — ready for yoga studios, tour bookings, concerts, language schools, and more.
|
|
6
|
+
|
|
7
|
+
> **Active development** — this library is under active development. APIs may evolve between minor versions. Feedback, issues, and PRs are welcome on [GitHub](https://github.com/nomideusz/svelte-calendar).
|
|
8
|
+
|
|
9
|
+
## Views — Concept-Paired
|
|
10
|
+
|
|
11
|
+
Views are organised into **concepts** that span both Day and Week granularity.
|
|
12
|
+
Switching between Day and Week preserves the active concept.
|
|
13
|
+
|
|
14
|
+
| Concept | Day View | Week View | Description |
|
|
15
|
+
|---------|----------|-----------|-------------|
|
|
16
|
+
| **Grid** | DayGrid | WeekGrid | The primary planner — time blocks on a scrollable grid. |
|
|
17
|
+
| **Timeline** | DayTimeline | — | Horizontal day timeline. Day-only. |
|
|
18
|
+
| **Agenda** | Agenda `mode="day"` | Agenda `mode="week"` | List / feed — Done, Now, Next (day) or grouped-by-day scroll (week). |
|
|
19
|
+
| **Heatmap** | — | WeekHeatmap | Density view — 24 cells per day showing busy/free intensity. Week-only. |
|
|
20
|
+
| **Schedule** | — | WeekSchedule | Zero-config weekly schedule display. Single import convenience wrapper. |
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add @nomideusz/svelte-calendar
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> **Peer dependency:** Svelte 5 (`^5.0.0`)
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```svelte
|
|
33
|
+
<script lang="ts">
|
|
34
|
+
import {
|
|
35
|
+
Calendar,
|
|
36
|
+
DayGrid,
|
|
37
|
+
DayTimeline,
|
|
38
|
+
Agenda,
|
|
39
|
+
WeekGrid,
|
|
40
|
+
WeekHeatmap,
|
|
41
|
+
createMemoryAdapter,
|
|
42
|
+
midnight,
|
|
43
|
+
} from '@nomideusz/svelte-calendar';
|
|
44
|
+
import type { CalendarView, TimelineEvent } from '@nomideusz/svelte-calendar';
|
|
45
|
+
|
|
46
|
+
const events: TimelineEvent[] = [
|
|
47
|
+
{
|
|
48
|
+
id: '1', title: 'Yoga Flow',
|
|
49
|
+
start: new Date('2025-03-01T09:00'), end: new Date('2025-03-01T10:00'),
|
|
50
|
+
color: '#818cf8', subtitle: 'With Anna', tags: ['Beginner'],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: '2', title: 'Meditation',
|
|
54
|
+
start: new Date('2025-03-01T12:00'), end: new Date('2025-03-01T12:45'),
|
|
55
|
+
color: '#34d399',
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// Adapters provide the data layer (in-memory, REST, etc.)
|
|
60
|
+
const adapter = createMemoryAdapter(events);
|
|
61
|
+
|
|
62
|
+
// Concepts are paired by label — switching Day↔Week preserves the concept
|
|
63
|
+
const views: CalendarView[] = [
|
|
64
|
+
{ id: 'day-grid', label: 'Grid', granularity: 'day', component: DayGrid },
|
|
65
|
+
{ id: 'week-grid', label: 'Grid', granularity: 'week', component: WeekGrid },
|
|
66
|
+
{ id: 'day-agenda', label: 'Agenda', granularity: 'day', component: Agenda, props: { mode: 'day' } },
|
|
67
|
+
{ id: 'week-agenda', label: 'Agenda', granularity: 'week', component: Agenda, props: { mode: 'week' } },
|
|
68
|
+
{ id: 'week-heatmap', label: 'Heatmap', granularity: 'week', component: WeekHeatmap },
|
|
69
|
+
];
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<Calendar
|
|
73
|
+
{views}
|
|
74
|
+
{adapter}
|
|
75
|
+
defaultView="week-grid"
|
|
76
|
+
theme={midnight}
|
|
77
|
+
height={600}
|
|
78
|
+
oneventclick={(ev) => console.log('clicked', ev.id)}
|
|
79
|
+
oneventcreate={(range) => console.log('create', range.start, range.end)}
|
|
80
|
+
/>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Recurring Weekly Schedules
|
|
84
|
+
|
|
85
|
+
Define a weekly schedule once — the adapter auto-projects it onto whatever week the calendar is viewing. No manual date math needed.
|
|
86
|
+
|
|
87
|
+
```svelte
|
|
88
|
+
<script lang="ts">
|
|
89
|
+
import { Calendar, WeekGrid, createRecurringAdapter, neutral } from '@nomideusz/svelte-calendar';
|
|
90
|
+
import type { CalendarView, RecurringEvent } from '@nomideusz/svelte-calendar';
|
|
91
|
+
|
|
92
|
+
const schedule: RecurringEvent[] = [
|
|
93
|
+
{ id: '1', title: 'Morning Yoga', dayOfWeek: 1, startTime: '07:00', endTime: '08:30', color: '#818cf8' },
|
|
94
|
+
{ id: '2', title: 'Pilates', dayOfWeek: 3, startTime: '18:00', endTime: '19:00', color: '#f472b6' },
|
|
95
|
+
{ id: '3', title: 'Sound Bath', dayOfWeek: 5, startTime: '19:00', endTime: '20:00', color: '#2dd4bf', subtitle: 'Crystal bowls', tags: ['Relaxing'] },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const adapter = createRecurringAdapter(schedule);
|
|
99
|
+
const views: CalendarView[] = [
|
|
100
|
+
{ id: 'week-grid', label: 'Grid', granularity: 'week', component: WeekGrid },
|
|
101
|
+
];
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<Calendar {views} {adapter} defaultView="week-grid" theme={neutral} readOnly />
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### RecurringEvent
|
|
108
|
+
|
|
109
|
+
| Field | Type | Description |
|
|
110
|
+
|-------|------|-------------|
|
|
111
|
+
| `id` | `string` | Unique identifier |
|
|
112
|
+
| `title` | `string` | Event title |
|
|
113
|
+
| `dayOfWeek` | `1–7` | ISO weekday (1 = Monday … 7 = Sunday) |
|
|
114
|
+
| `startTime` | `string` | Start time in `"HH:MM"` format |
|
|
115
|
+
| `endTime` | `string` | End time in `"HH:MM"` format |
|
|
116
|
+
| `color` | `string?` | Accent color |
|
|
117
|
+
| `subtitle` | `string?` | Subtitle (rendered below title) |
|
|
118
|
+
| `tags` | `string[]?` | Tag pills |
|
|
119
|
+
| `category` | `string?` | Category for grouping / colorMap |
|
|
120
|
+
| `data` | `Record?` | Arbitrary payload |
|
|
121
|
+
|
|
122
|
+
## WeekSchedule — Zero-Config Convenience
|
|
123
|
+
|
|
124
|
+
One import, one component. Pre-wires adapter, views, and toolbar internally:
|
|
125
|
+
|
|
126
|
+
```svelte
|
|
127
|
+
<script>
|
|
128
|
+
import { WeekSchedule } from '@nomideusz/svelte-calendar';
|
|
129
|
+
import { neutral } from '@nomideusz/svelte-calendar';
|
|
130
|
+
|
|
131
|
+
const schedule = [
|
|
132
|
+
{ id: '1', title: 'Yoga', dayOfWeek: 1, startTime: '07:00', endTime: '08:30', color: '#818cf8' },
|
|
133
|
+
{ id: '2', title: 'Pilates', dayOfWeek: 3, startTime: '18:00', endTime: '19:00', color: '#f472b6' },
|
|
134
|
+
];
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<WeekSchedule {schedule} theme={neutral} locale="pl-PL" height={560} readOnly />
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Works with concrete events too:
|
|
141
|
+
|
|
142
|
+
```svelte
|
|
143
|
+
<WeekSchedule events={myEvents} theme={neutral} height={560} />
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Read-Only Mode
|
|
147
|
+
|
|
148
|
+
Pass `readOnly` to disable drag, resize, and click-to-create interactions:
|
|
149
|
+
|
|
150
|
+
```svelte
|
|
151
|
+
<Calendar {views} {adapter} readOnly />
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
In read-only mode:
|
|
155
|
+
- Drag handles and resize affordances are disabled
|
|
156
|
+
- Empty-slot creation clicks are suppressed
|
|
157
|
+
- `oneventcreate` and `oneventmove` callbacks are not fired
|
|
158
|
+
- `oneventclick` still works for navigation/display purposes
|
|
159
|
+
|
|
160
|
+
## Visible Hours
|
|
161
|
+
|
|
162
|
+
Crop the grid to relevant hours — no more scrolling past empty early morning / late night rows:
|
|
163
|
+
|
|
164
|
+
```svelte
|
|
165
|
+
<!-- Only show 6 AM to 9 PM -->
|
|
166
|
+
<Calendar {views} {adapter} visibleHours={[6, 21]} />
|
|
167
|
+
|
|
168
|
+
<!-- Works on WeekSchedule too -->
|
|
169
|
+
<WeekSchedule {schedule} visibleHours={[7, 20]} />
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The `visibleHours` prop is a `[startHour, endHour)` tuple. It applies to the WeekHeatmap grid cells and is passed through to all views.
|
|
173
|
+
|
|
174
|
+
## Subtitle & Tags on Events
|
|
175
|
+
|
|
176
|
+
`TimelineEvent` supports `subtitle` and `tags` fields — rendered automatically in **all views**:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const events: TimelineEvent[] = [
|
|
180
|
+
{
|
|
181
|
+
id: '1',
|
|
182
|
+
title: 'Power Vinyasa',
|
|
183
|
+
start: new Date('2025-03-01T10:00'),
|
|
184
|
+
end: new Date('2025-03-01T11:15'),
|
|
185
|
+
color: '#f472b6',
|
|
186
|
+
subtitle: 'With Marco', // shown below the title
|
|
187
|
+
tags: ['Advanced', 'Hot'], // rendered as small color pills
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- **subtitle** — secondary text below the title (all views: WeekGrid, DayGrid, DayTimeline, Agenda, EventBlock)
|
|
193
|
+
- **tags** — accent-colored pills after the title (all views)
|
|
194
|
+
- In space-constrained views (DayGrid, DayTimeline), subtitle/tags appear only when the event block is tall/wide enough
|
|
195
|
+
|
|
196
|
+
## Color Map & Auto-Coloring
|
|
197
|
+
|
|
198
|
+
Instead of setting `color` on every event, let the adapter assign colors by category or title:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
// Explicit mapping
|
|
202
|
+
const adapter = createMemoryAdapter(events, {
|
|
203
|
+
colorMap: {
|
|
204
|
+
yoga: '#818cf8',
|
|
205
|
+
wellness: '#34d399',
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Auto-assign from a built-in 15-color vivid palette
|
|
210
|
+
const adapter = createMemoryAdapter(events, { autoColor: true });
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Theme-Aware Auto-Coloring
|
|
214
|
+
|
|
215
|
+
Pass the theme's accent hex to `autoColor` and the palette is generated to harmonize with your theme — colors rotate via golden-angle hue spacing from the accent, with lightness adjusted for dark/light backgrounds:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
// Harmonious palette seeded from indigo accent
|
|
219
|
+
const adapter = createMemoryAdapter(events, { autoColor: '#6366f1' });
|
|
220
|
+
|
|
221
|
+
// Works with the recurring adapter too
|
|
222
|
+
const adapter = createRecurringAdapter(schedule, { autoColor: '#ef4444' });
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
| `autoColor` value | Behaviour |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `true` | Original 15-color vivid palette (fixed, theme-independent) |
|
|
228
|
+
| `'#ef4444'` | Golden-angle hue rotation from that accent; lightness adapted to dark/light |
|
|
229
|
+
|
|
230
|
+
You can also use the palette generator directly:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { generatePalette } from '@nomideusz/svelte-calendar';
|
|
234
|
+
|
|
235
|
+
generatePalette('#6366f1', 8); // 8 theme-harmonious hex colors
|
|
236
|
+
generatePalette(); // default vivid 15-color palette
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Both `createMemoryAdapter` and `createRecurringAdapter` accept `colorMap` and `autoColor` options. Events with an explicit `color` field always take priority.
|
|
240
|
+
|
|
241
|
+
## Settings Panel
|
|
242
|
+
|
|
243
|
+
The `Settings` component provides a theme picker and dynamic fields for controlling view parameters. It renders as a compact horizontal panel with auto-fill columns so the calendar stays visible while adjusting options.
|
|
244
|
+
|
|
245
|
+
```svelte
|
|
246
|
+
<script lang="ts">
|
|
247
|
+
import { Settings } from '@nomideusz/svelte-calendar';
|
|
248
|
+
import type { SettingsField, PresetName } from '@nomideusz/svelte-calendar';
|
|
249
|
+
|
|
250
|
+
let theme: PresetName = $state('midnight');
|
|
251
|
+
let values = $state({ hourHeight: 60, elastic: true, startHour: 6, endHour: 21, visibleHours: false });
|
|
252
|
+
|
|
253
|
+
const fields: SettingsField[] = [
|
|
254
|
+
{ key: 'hourHeight', label: 'Hour Height', group: 'Layout', type: 'range', min: 40, max: 120, step: 5 },
|
|
255
|
+
{ key: 'elastic', label: 'Elastic Compression', group: 'Behavior', type: 'toggle' },
|
|
256
|
+
{ key: 'visibleHours', label: 'Visible Hours', group: 'Time Range', type: 'toggle' },
|
|
257
|
+
{ key: 'startHour', label: 'Start Hour', group: 'Time Range', type: 'range', min: 0, max: 23, step: 1, enabledWhen: 'visibleHours' },
|
|
258
|
+
{ key: 'endHour', label: 'End Hour', group: 'Time Range', type: 'range', min: 1, max: 24, step: 1, enabledWhen: 'visibleHours' },
|
|
259
|
+
];
|
|
260
|
+
</script>
|
|
261
|
+
|
|
262
|
+
<Settings {fields} bind:values bind:theme />
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Field types
|
|
266
|
+
|
|
267
|
+
| Type | Properties | Description |
|
|
268
|
+
|------|-----------|-------------|
|
|
269
|
+
| `range` | `min`, `max`, `step`, `enabledWhen?` | Slider with label and value display. `enabledWhen` references a toggle key — disables the slider when the toggle is off. |
|
|
270
|
+
| `toggle` | — | Switch toggle (on/off) |
|
|
271
|
+
| `select` | `options: {value, label}[]` | Dropdown select |
|
|
272
|
+
| `segment` | `options: {value, label}[]` | Pill-button radio group |
|
|
273
|
+
|
|
274
|
+
All fields support `group` for column grouping and `label` for display text.
|
|
275
|
+
|
|
276
|
+
## Themes
|
|
277
|
+
|
|
278
|
+
Five built-in presets — each view reads from the same `--dt-*` CSS custom property contract:
|
|
279
|
+
|
|
280
|
+
| Preset | Tone |
|
|
281
|
+
|--------|------|
|
|
282
|
+
| `midnight` | Dark (deep navy/slate, red accent) |
|
|
283
|
+
| `parchment` | Warm light (cream, burnt sienna accents) |
|
|
284
|
+
| `indigo` | Cool light (white surface, indigo accents) |
|
|
285
|
+
| `neutral` | **Site-friendly** — white/gray, blue accent, `inherit` fonts so it matches your site |
|
|
286
|
+
| `bare` | **Unstyled skeleton** — all `transparent`/`inherit`/`currentColor`, absorbs host styles entirely |
|
|
287
|
+
|
|
288
|
+
```svelte
|
|
289
|
+
<script>
|
|
290
|
+
import { midnight, parchment, indigo, neutral, bare, presets } from '@nomideusz/svelte-calendar';
|
|
291
|
+
</script>
|
|
292
|
+
|
|
293
|
+
<!-- Blends into your site with no extra work -->
|
|
294
|
+
<Calendar {views} {adapter} theme={neutral} />
|
|
295
|
+
|
|
296
|
+
<!-- Or use the presets map -->
|
|
297
|
+
<WeekGrid style={presets['parchment']} events={events} />
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Matching Your Site
|
|
301
|
+
|
|
302
|
+
The `neutral` preset inherits font families from your page (`--dt-sans: inherit; --dt-serif: inherit`) and uses a standard blue accent. For most sites this is all you need.
|
|
303
|
+
|
|
304
|
+
If you need full control, start from `bare` — it sets everything to `transparent`/`inherit`/`currentColor` — then override only the tokens you care about:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { bare } from '@nomideusz/svelte-calendar';
|
|
308
|
+
|
|
309
|
+
const myTheme = `
|
|
310
|
+
${bare}
|
|
311
|
+
--dt-accent: #e11d48;
|
|
312
|
+
--dt-bg: var(--my-app-surface);
|
|
313
|
+
--dt-text: var(--my-app-text);
|
|
314
|
+
--dt-border: var(--my-app-border);
|
|
315
|
+
`;
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Custom Themes
|
|
319
|
+
|
|
320
|
+
Pass any string of `--dt-*` custom property declarations:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
const custom = `
|
|
324
|
+
--dt-bg: #0a0a0a;
|
|
325
|
+
--dt-surface: #111;
|
|
326
|
+
--dt-border: rgba(255,255,255,0.06);
|
|
327
|
+
--dt-text: #e0e0e0;
|
|
328
|
+
--dt-accent: #10b981;
|
|
329
|
+
/* ...see presets.ts for the full token list */
|
|
330
|
+
`;
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
<details>
|
|
334
|
+
<summary><strong>Full token reference</strong></summary>
|
|
335
|
+
|
|
336
|
+
| Token | Purpose |
|
|
337
|
+
|-------|----------|
|
|
338
|
+
| `--dt-bg` | Main background |
|
|
339
|
+
| `--dt-surface` | Elevated surface (cards, popovers) |
|
|
340
|
+
| `--dt-border` | Default border |
|
|
341
|
+
| `--dt-border-day` | Day-column dividers |
|
|
342
|
+
| `--dt-text` | Primary text |
|
|
343
|
+
| `--dt-text-2` | Secondary text |
|
|
344
|
+
| `--dt-text-3` | Tertiary / muted text |
|
|
345
|
+
| `--dt-accent` | Accent color (buttons, now-indicator, highlights) |
|
|
346
|
+
| `--dt-accent-dim` | Accent at ~12% opacity |
|
|
347
|
+
| `--dt-glow` | Accent glow / focus ring |
|
|
348
|
+
| `--dt-today-bg` | Today column highlight |
|
|
349
|
+
| `--dt-btn-text` | Button label color |
|
|
350
|
+
| `--dt-scrollbar` | Scrollbar thumb |
|
|
351
|
+
| `--dt-success` | Success / completed indicator |
|
|
352
|
+
| `--dt-serif` | Serif font stack |
|
|
353
|
+
| `--dt-sans` | Sans-serif font stack |
|
|
354
|
+
| `--dt-mono` | Monospace font stack |
|
|
355
|
+
| `--dt-hm-empty` | Heatmap: empty cell |
|
|
356
|
+
| `--dt-hm-low` | Heatmap: low density |
|
|
357
|
+
| `--dt-hm-mid` | Heatmap: medium density |
|
|
358
|
+
| `--dt-hm-high` | Heatmap: high density |
|
|
359
|
+
| `--dt-hm-max` | Heatmap: maximum density |
|
|
360
|
+
|
|
361
|
+
</details>
|
|
362
|
+
|
|
363
|
+
## Accessibility
|
|
364
|
+
|
|
365
|
+
All interactive elements include proper ARIA attributes and keyboard support:
|
|
366
|
+
|
|
367
|
+
- **EventBlock** — `role="button"`, `aria-label` (title + time + duration + status), `tabindex="0"`, Enter/Space activates
|
|
368
|
+
- **EmptySlot** — `role="button"`, `aria-label` (time range + duration), keyboard-accessible for event creation
|
|
369
|
+
- **NowIndicator** — `role="status"`, `aria-live="polite"` announces the current time
|
|
370
|
+
- **Calendar** — `role="region"`, `aria-label="Calendar"`
|
|
371
|
+
- **Toolbar** — `aria-label="Calendar navigation"`
|
|
372
|
+
- Focus-visible outlines on all interactive primitives
|
|
373
|
+
|
|
374
|
+
## Locale & i18n
|
|
375
|
+
|
|
376
|
+
The calendar uses `Intl.DateTimeFormat` for all date/time formatting. Pass a BCP 47 locale tag to the `Calendar` shell:
|
|
377
|
+
|
|
378
|
+
```svelte
|
|
379
|
+
<Calendar {views} {adapter} locale="pl-PL" dir="rtl" />
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
| Prop | Type | Default | Description |
|
|
383
|
+
|------|------|---------|-------------|
|
|
384
|
+
| `locale` | `string` | `'en-US'` | BCP 47 tag — controls weekday names, month names, date formats |
|
|
385
|
+
| `dir` | `'ltr' \| 'rtl' \| 'auto'` | — | Text direction (for Arabic, Hebrew, etc.) |
|
|
386
|
+
|
|
387
|
+
The `locale` prop automatically switches between 12-hour and 24-hour time display based on the locale's hour cycle.
|
|
388
|
+
|
|
389
|
+
### Programmatic locale control
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
import { setDefaultLocale, getDefaultLocale, is24HourLocale } from '@nomideusz/svelte-calendar';
|
|
393
|
+
|
|
394
|
+
setDefaultLocale('de-DE'); // All formatting functions now use German
|
|
395
|
+
is24HourLocale('en-US'); // false (12h)
|
|
396
|
+
is24HourLocale('de-DE'); // true (24h)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
All formatting functions (`weekdayShort`, `monthLong`, `fmtDay`, `fmtWeekRange`, etc.) accept an optional `locale` parameter to override per-call.
|
|
400
|
+
|
|
401
|
+
## Timezones
|
|
402
|
+
|
|
403
|
+
Timezone utilities are included via `date-fns-tz`:
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import {
|
|
407
|
+
toZonedTime,
|
|
408
|
+
fromZonedTime,
|
|
409
|
+
nowInZone,
|
|
410
|
+
formatInTimeZone,
|
|
411
|
+
} from '@nomideusz/svelte-calendar';
|
|
412
|
+
|
|
413
|
+
// Display a UTC date in a specific timezone
|
|
414
|
+
const nyTime = toZonedTime(utcDate, 'America/New_York');
|
|
415
|
+
|
|
416
|
+
// Convert back to UTC for storage
|
|
417
|
+
const utc = fromZonedTime(displayDate, 'America/New_York');
|
|
418
|
+
|
|
419
|
+
// Current time in Tokyo
|
|
420
|
+
const tokyoNow = nowInZone('Asia/Tokyo');
|
|
421
|
+
|
|
422
|
+
// Locale-aware formatting in a timezone
|
|
423
|
+
formatInTimeZone(date, 'Europe/Warsaw', { hour: 'numeric', minute: '2-digit' }, 'pl-PL');
|
|
424
|
+
// → "14:30"
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
The engine's `createViewState` also accepts a `timezone` option:
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
const viewState = createViewState({
|
|
431
|
+
defaultView: 'week-grid',
|
|
432
|
+
timezone: 'America/New_York', // stored on viewState.timezone
|
|
433
|
+
});
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Architecture
|
|
437
|
+
|
|
438
|
+
```
|
|
439
|
+
src/lib/
|
|
440
|
+
├── core/ # Clock, time utils, locale, types
|
|
441
|
+
├── engine/ # Reactive state: event-store, view-state, selection, drag
|
|
442
|
+
├── adapters/ # Data layer: memory, recurring, REST adapters
|
|
443
|
+
├── primitives/ # Low-level UI atoms: NowIndicator, EventBlock, TimeGutter...
|
|
444
|
+
├── calendar/ # Calendar shell, Toolbar
|
|
445
|
+
├── views/ # View components (day/, week/, agenda/, schedule/, settings/)
|
|
446
|
+
└── theme/ # Preset themes and token definitions
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## Engine
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
import {
|
|
453
|
+
createEventStore,
|
|
454
|
+
createViewState,
|
|
455
|
+
createSelection,
|
|
456
|
+
createDragState,
|
|
457
|
+
} from '@nomideusz/svelte-calendar';
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
- **`createEventStore(adapter)`** — reactive event list with fetch/add/update/remove
|
|
461
|
+
- **`createViewState(options)`** — current view, date range, navigation (prev/next/today)
|
|
462
|
+
- **`createSelection()`** — selected event tracking
|
|
463
|
+
- **`createDragState()`** — drag-to-create and drag-to-move state machine
|
|
464
|
+
|
|
465
|
+
## Adapters
|
|
466
|
+
|
|
467
|
+
| Adapter | Use |
|
|
468
|
+
|---------|-----|
|
|
469
|
+
| `createMemoryAdapter(events, options?)` | In-memory — great for demos and prototyping. Supports `colorMap` and `autoColor` (including theme-aware). |
|
|
470
|
+
| `createRecurringAdapter(schedule, options?)` | Weekly recurring schedules — auto-projects onto viewed weeks. Read-only. Supports `colorMap` and `autoColor`. |
|
|
471
|
+
| `createRestAdapter(options)` | Fetch from a REST API using `/events` endpoints with configurable headers and response mappers (`mapEvents`, `mapEvent`). |
|
|
472
|
+
|
|
473
|
+
## Standalone Views
|
|
474
|
+
|
|
475
|
+
Each view works independently without the Calendar shell:
|
|
476
|
+
|
|
477
|
+
```svelte
|
|
478
|
+
<!-- Horizontal day timeline -->
|
|
479
|
+
<DayTimeline style={midnight} events={events} />
|
|
480
|
+
|
|
481
|
+
<!-- Vertical day grid with elastic night compression -->
|
|
482
|
+
<DayGrid style={midnight} events={events} height={600} elastic />
|
|
483
|
+
|
|
484
|
+
<!-- Agenda in day mode -->
|
|
485
|
+
<Agenda mode="day" events={events} height={520} />
|
|
486
|
+
|
|
487
|
+
<!-- Week density heatmap -->
|
|
488
|
+
<WeekHeatmap style={parchment} events={events} height={320} />
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Embeddable Widget
|
|
492
|
+
|
|
493
|
+
Drop a single `<script>` tag into **any** HTML page — no Svelte, no build tools, no npm needed.
|
|
494
|
+
|
|
495
|
+
### From CDN
|
|
496
|
+
|
|
497
|
+
```html
|
|
498
|
+
<script src="https://cdn.jsdelivr.net/npm/@nomideusz/svelte-calendar/widget/widget.js"></script>
|
|
499
|
+
|
|
500
|
+
<day-calendar
|
|
501
|
+
api="https://myschool.com/api/events"
|
|
502
|
+
theme="neutral"
|
|
503
|
+
height="600"
|
|
504
|
+
></day-calendar>
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
That's it. Two lines.
|
|
508
|
+
|
|
509
|
+
### With inline events (no API)
|
|
510
|
+
|
|
511
|
+
```html
|
|
512
|
+
<script src="https://cdn.jsdelivr.net/npm/@nomideusz/svelte-calendar/widget/widget.js"></script>
|
|
513
|
+
|
|
514
|
+
<day-calendar
|
|
515
|
+
theme="midnight"
|
|
516
|
+
height="500"
|
|
517
|
+
events='[
|
|
518
|
+
{ "id": "1", "title": "Yoga Flow", "start": "2025-03-01T09:00", "end": "2025-03-01T10:00", "color": "#818cf8" },
|
|
519
|
+
{ "id": "2", "title": "Meditation", "start": "2025-03-01T12:00", "end": "2025-03-01T12:45", "color": "#34d399" }
|
|
520
|
+
]'
|
|
521
|
+
></day-calendar>
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Widget attributes
|
|
525
|
+
|
|
526
|
+
| Attribute | Default | Description |
|
|
527
|
+
|-----------|---------|-------------|
|
|
528
|
+
| `api` | — | REST API base URL — fetches from `{api}/events?start=...&end=...` |
|
|
529
|
+
| `events` | — | JSON string of events (alternative to `api`) |
|
|
530
|
+
| `theme` | `neutral` | Preset: `midnight`, `parchment`, `indigo`, `neutral`, `bare` |
|
|
531
|
+
| `view` | `week-grid` | Default view: `day-grid`, `week-grid`, `day-timeline`, `day-agenda`, `week-agenda`, `week-heatmap` |
|
|
532
|
+
| `height` | `600` | Height in pixels |
|
|
533
|
+
| `locale` | — | BCP 47 locale (`en-US`, `pl-PL`, `ar-SA`, etc.) |
|
|
534
|
+
| `dir` | — | Text direction: `ltr`, `rtl`, `auto` |
|
|
535
|
+
| `mondaystart` | `true` | Start week on Monday (`true`/`false`) |
|
|
536
|
+
| `headers` | — | JSON string of HTTP headers for the REST adapter |
|
|
537
|
+
|
|
538
|
+
> `events` and `headers` must be valid JSON strings. Invalid JSON is ignored (`events`) or throws during adapter setup (`headers`).
|
|
539
|
+
|
|
540
|
+
### REST API contract
|
|
541
|
+
|
|
542
|
+
When using the `api` attribute, the widget expects your endpoint to accept:
|
|
543
|
+
|
|
544
|
+
```
|
|
545
|
+
GET {api}/events?start={ISO}&end={ISO}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
And return either:
|
|
549
|
+
- `[{ id, title, start, end, color? }, ...]`
|
|
550
|
+
- `{ events: [{ id, title, start, end, color? }, ...] }`
|
|
551
|
+
|
|
552
|
+
## Development
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
pnpm install
|
|
556
|
+
pnpm dev # SvelteKit dev server (demo app)
|
|
557
|
+
pnpm check # Type check
|
|
558
|
+
pnpm run package # Build the library into dist/
|
|
559
|
+
pnpm run build:widget # Build standalone widget.js
|
|
560
|
+
|
|
561
|
+
# npm publish only runs `package` automatically (via prepublishOnly)
|
|
562
|
+
# Run this too if you changed widget code:
|
|
563
|
+
pnpm run build:widget
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
## License
|
|
567
|
+
|
|
568
|
+
MIT
|