@nomideusz/svelte-calendar 0.4.0 → 0.5.2
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 +331 -568
- package/dist/adapters/recurring.d.ts +45 -8
- package/dist/adapters/recurring.js +227 -49
- package/dist/adapters/rest.js +6 -1
- package/dist/calendar/Calendar.svelte +118 -30
- package/dist/calendar/Calendar.svelte.d.ts +9 -8
- package/dist/calendar/index.d.ts +0 -1
- package/dist/calendar/index.js +0 -1
- package/dist/core/index.d.ts +5 -4
- package/dist/core/index.js +2 -3
- package/dist/core/locale.d.ts +74 -0
- package/dist/core/locale.js +96 -4
- package/dist/core/time.d.ts +30 -0
- package/dist/core/time.js +42 -0
- package/dist/core/types.d.ts +1 -72
- package/dist/core/types.js +1 -8
- package/dist/engine/event-store.svelte.js +2 -2
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/view-state.svelte.d.ts +11 -6
- package/dist/engine/view-state.svelte.js +5 -4
- package/dist/index.d.ts +6 -7
- package/dist/index.js +4 -4
- package/dist/primitives/EmptySlot.svelte +6 -16
- package/dist/primitives/EventBlock.svelte +6 -19
- package/dist/primitives/NowIndicator.svelte +7 -4
- package/dist/primitives/TimeGutter.svelte +1 -1
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/theme/presets.d.ts +11 -19
- package/dist/theme/presets.js +25 -111
- package/dist/views/agenda/Agenda.svelte +17 -1516
- package/dist/views/agenda/Agenda.svelte.d.ts +0 -14
- package/dist/views/agenda/AgendaDay.svelte +976 -0
- package/dist/views/agenda/AgendaDay.svelte.d.ts +14 -0
- package/dist/views/agenda/AgendaWeek.svelte +775 -0
- package/dist/views/agenda/AgendaWeek.svelte.d.ts +15 -0
- package/dist/views/agenda/index.d.ts +2 -0
- package/dist/views/agenda/index.js +2 -0
- package/dist/views/index.d.ts +2 -6
- package/dist/views/index.js +2 -6
- package/dist/views/planner/Planner.svelte +17 -0
- package/dist/views/planner/Planner.svelte.d.ts +7 -0
- package/dist/views/planner/PlannerDay.svelte +957 -0
- package/dist/views/{day/DayGrid.svelte.d.ts → planner/PlannerDay.svelte.d.ts} +6 -16
- package/dist/views/planner/PlannerWeek.svelte +840 -0
- package/dist/views/{week/WeekGrid.svelte.d.ts → planner/PlannerWeek.svelte.d.ts} +4 -9
- package/dist/views/planner/index.d.ts +1 -0
- package/dist/views/planner/index.js +1 -0
- package/dist/widget/CalendarWidget.svelte +54 -33
- package/dist/widget/CalendarWidget.svelte.d.ts +1 -1
- package/package.json +70 -65
- package/widget/svelte-calendar.css +1 -0
- package/widget/widget.js +122 -260
- package/dist/calendar/Toolbar.svelte +0 -244
- package/dist/calendar/Toolbar.svelte.d.ts +0 -21
- package/dist/views/day/DayGrid.svelte +0 -1104
- package/dist/views/day/DayTimeline.svelte +0 -533
- package/dist/views/day/DayTimeline.svelte.d.ts +0 -8
- package/dist/views/day/index.d.ts +0 -2
- package/dist/views/day/index.js +0 -2
- package/dist/views/schedule/WeekSchedule.svelte +0 -137
- package/dist/views/schedule/WeekSchedule.svelte.d.ts +0 -40
- package/dist/views/schedule/index.d.ts +0 -1
- package/dist/views/schedule/index.js +0 -2
- package/dist/views/settings/Settings.svelte +0 -585
- package/dist/views/settings/Settings.svelte.d.ts +0 -39
- package/dist/views/settings/index.d.ts +0 -2
- package/dist/views/settings/index.js +0 -1
- package/dist/views/week/WeekGrid.svelte +0 -632
- package/dist/views/week/WeekHeatmap.svelte +0 -461
- package/dist/views/week/WeekHeatmap.svelte.d.ts +0 -9
- package/dist/views/week/index.d.ts +0 -2
- package/dist/views/week/index.js +0 -2
package/README.md
CHANGED
|
@@ -1,568 +1,331 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
{ id: '
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<Calendar {views} {adapter} readOnly />
|
|
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
|
-
<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
|
|
1
|
+
# @nomideusz/svelte-calendar
|
|
2
|
+
|
|
3
|
+
A themeable **Svelte 5** calendar with **Day/Week Planner & Agenda** views.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @nomideusz/svelte-calendar
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
> Requires Svelte 5 (`^5.0.0`)
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```svelte
|
|
16
|
+
<script lang="ts">
|
|
17
|
+
import {
|
|
18
|
+
Calendar, Planner, Agenda,
|
|
19
|
+
createMemoryAdapter, neutral,
|
|
20
|
+
} from '@nomideusz/svelte-calendar';
|
|
21
|
+
import type { CalendarView, TimelineEvent } from '@nomideusz/svelte-calendar';
|
|
22
|
+
|
|
23
|
+
const events: TimelineEvent[] = [
|
|
24
|
+
{ id: '1', title: 'Yoga Flow', start: new Date('2025-03-01T09:00'), end: new Date('2025-03-01T10:00'), color: '#818cf8' },
|
|
25
|
+
{ id: '2', title: 'Meditation', start: new Date('2025-03-01T12:00'), end: new Date('2025-03-01T12:45'), color: '#34d399' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const adapter = createMemoryAdapter(events);
|
|
29
|
+
|
|
30
|
+
const views: CalendarView[] = [
|
|
31
|
+
{ id: 'day-planner', label: 'Planner', granularity: 'day', component: Planner, props: { mode: 'day' } },
|
|
32
|
+
{ id: 'week-planner', label: 'Planner', granularity: 'week', component: Planner, props: { mode: 'week' } },
|
|
33
|
+
{ id: 'day-agenda', label: 'Agenda', granularity: 'day', component: Agenda, props: { mode: 'day' } },
|
|
34
|
+
{ id: 'week-agenda',label: 'Agenda', granularity: 'week', component: Agenda, props: { mode: 'week' } },
|
|
35
|
+
];
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<Calendar
|
|
39
|
+
{views}
|
|
40
|
+
{adapter}
|
|
41
|
+
defaultView="week-planner"
|
|
42
|
+
theme={neutral}
|
|
43
|
+
height={600}
|
|
44
|
+
oneventclick={(ev) => console.log('clicked', ev.id)}
|
|
45
|
+
oneventcreate={(range) => console.log('create', range)}
|
|
46
|
+
/>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Calendar Props
|
|
50
|
+
|
|
51
|
+
| Prop | Type | Default | Description |
|
|
52
|
+
|------|------|---------|-------------|
|
|
53
|
+
| `adapter` | `CalendarAdapter` | *required* | Data layer (memory, recurring, REST) |
|
|
54
|
+
| `views` | `CalendarView[]` | `[]` | Registered view components |
|
|
55
|
+
| `defaultView` | `string` | `'week-planner'` | Initial view ID |
|
|
56
|
+
| `theme` | `string` | `''` | CSS theme string (`--dt-*` custom properties) |
|
|
57
|
+
| `height` | `number` | `600` | Total height in pixels |
|
|
58
|
+
| `locale` | `string` | `'en-US'` | BCP 47 locale tag |
|
|
59
|
+
| `dir` | `'ltr' \| 'rtl' \| 'auto'` | - | Text direction |
|
|
60
|
+
| `mondayStart` | `boolean` | `true` | Start week on Monday |
|
|
61
|
+
| `readOnly` | `boolean` | `false` | Disable drag, resize, and click-to-create |
|
|
62
|
+
| `visibleHours` | `[number, number]` | - | Crop grid to `[startHour, endHour)` |
|
|
63
|
+
| `initialDate` | `Date` | today | Date to focus on at mount |
|
|
64
|
+
| `snapInterval` | `number` | `15` | Drag snap in minutes |
|
|
65
|
+
| `oneventclick` | `(event) => void` | - | Event clicked |
|
|
66
|
+
| `oneventcreate` | `(range) => void` | - | New time range selected |
|
|
67
|
+
| `oneventmove` | `(event, start, end) => void` | - | Event dragged to new time |
|
|
68
|
+
| `onviewchange` | `(viewId) => void` | - | Active view changed |
|
|
69
|
+
| `event` | `Snippet<[TimelineEvent]>` | - | Custom event rendering |
|
|
70
|
+
| `empty` | `Snippet` | - | Empty state content |
|
|
71
|
+
|
|
72
|
+
## TimelineEvent
|
|
73
|
+
|
|
74
|
+
| Field | Type | Description |
|
|
75
|
+
|-------|------|-------------|
|
|
76
|
+
| `id` | `string` | Unique identifier |
|
|
77
|
+
| `title` | `string` | Event title |
|
|
78
|
+
| `start` / `end` | `Date` | Time range |
|
|
79
|
+
| `color` | `string?` | Accent color |
|
|
80
|
+
| `category` | `string?` | For grouping / colorMap |
|
|
81
|
+
| `subtitle` | `string?` | Secondary text |
|
|
82
|
+
| `tags` | `string[]?` | Small accent-colored pills |
|
|
83
|
+
| `allDay` | `boolean?` | Render as an all-day event |
|
|
84
|
+
| `data` | `Record?` | Arbitrary payload |
|
|
85
|
+
|
|
86
|
+
## Multi-day & All-day Events
|
|
87
|
+
|
|
88
|
+
Events that span multiple days or are flagged `allDay: true` are rendered in a
|
|
89
|
+
dedicated strip above timed events in every view:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const events: TimelineEvent[] = [
|
|
93
|
+
// Explicit all-day flag
|
|
94
|
+
{ id: '1', title: 'Conference', start: new Date('2025-03-15'), end: new Date('2025-03-18'), allDay: true },
|
|
95
|
+
// Auto-detected: starts at midnight and spans ≥ 24 h
|
|
96
|
+
{ id: '2', title: 'Sprint', start: new Date('2025-03-15T00:00'), end: new Date('2025-03-17T00:00') },
|
|
97
|
+
// Overnight timed event — also renders in the all-day strip
|
|
98
|
+
{ id: '3', title: 'Hackathon', start: new Date('2025-03-15T18:00'), end: new Date('2025-03-16T06:00') },
|
|
99
|
+
];
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Utility helpers are exported for custom views:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { isAllDay, isMultiDay, segmentForDay } from '@nomideusz/svelte-calendar';
|
|
106
|
+
|
|
107
|
+
segmentForDay(event, dayTimestamp);
|
|
108
|
+
// → { ev, start, end, isStart, isEnd, dayIndex, totalDays, allDay } | null
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Views
|
|
112
|
+
|
|
113
|
+
| Concept | Day | Week | Description |
|
|
114
|
+
|---------|-----|------|-------------|
|
|
115
|
+
| **Planner** | `mode="day"` | `mode="week"` | Time blocks on a scrollable grid |
|
|
116
|
+
| **Agenda** | `mode="day"` | `mode="week"` | List view — Done, Now, Next (day) or grouped-by-day (week) |
|
|
117
|
+
|
|
118
|
+
## Recurring Schedules
|
|
119
|
+
|
|
120
|
+
```svelte
|
|
121
|
+
<script lang="ts">
|
|
122
|
+
import { Calendar, Planner, createRecurringAdapter, neutral } from '@nomideusz/svelte-calendar';
|
|
123
|
+
import type { CalendarView, RecurringEvent } from '@nomideusz/svelte-calendar';
|
|
124
|
+
|
|
125
|
+
const schedule: RecurringEvent[] = [
|
|
126
|
+
// Every Monday
|
|
127
|
+
{ id: '1', title: 'Morning Yoga', dayOfWeek: 1, startTime: '07:00', endTime: '08:30', color: '#818cf8' },
|
|
128
|
+
// Mon/Wed/Fri during semester
|
|
129
|
+
{ id: '2', title: 'Math', dayOfWeek: [1, 3, 5], startTime: '09:00', endTime: '10:00',
|
|
130
|
+
startDate: '2025-09-01', until: '2025-12-15' },
|
|
131
|
+
// Biweekly team sync
|
|
132
|
+
{ id: '3', title: 'Sync', frequency: 'weekly', interval: 2, dayOfWeek: 2,
|
|
133
|
+
startTime: '14:00', endTime: '15:00', startDate: '2025-03-01' },
|
|
134
|
+
// Daily standup, March only
|
|
135
|
+
{ id: '4', title: 'Standup', frequency: 'daily', startTime: '09:00', endTime: '09:15',
|
|
136
|
+
startDate: '2025-03-01', until: '2025-03-31' },
|
|
137
|
+
// Workshop — 8 Saturday sessions
|
|
138
|
+
{ id: '5', title: 'Workshop', dayOfWeek: 6, startTime: '10:00', endTime: '12:00',
|
|
139
|
+
startDate: '2025-03-01', count: 8 },
|
|
140
|
+
// Monthly review on the 15th
|
|
141
|
+
{ id: '6', title: 'Review', frequency: 'monthly', dayOfMonth: 15,
|
|
142
|
+
startTime: '10:00', endTime: '11:00' },
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
const adapter = createRecurringAdapter(schedule);
|
|
146
|
+
const views: CalendarView[] = [
|
|
147
|
+
{ id: 'week-planner', label: 'Planner', granularity: 'week', component: Planner, props: { mode: 'week' } },
|
|
148
|
+
];
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<Calendar {views} {adapter} defaultView="week-planner" theme={neutral} readOnly />
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### RecurringEvent
|
|
155
|
+
|
|
156
|
+
| Field | Type | Default | Description |
|
|
157
|
+
|-------|------|---------|-------------|
|
|
158
|
+
| `id` | `string` | *required* | Unique identifier |
|
|
159
|
+
| `title` | `string` | *required* | Event title |
|
|
160
|
+
| `startTime` | `string` | *required* | Start time `"HH:MM"` |
|
|
161
|
+
| `endTime` | `string` | *required* | End time `"HH:MM"` |
|
|
162
|
+
| `frequency` | `'daily' \| 'weekly' \| 'monthly'` | `'weekly'` | Recurrence frequency |
|
|
163
|
+
| `interval` | `number` | `1` | Repeat every N periods (e.g. `2` = biweekly) |
|
|
164
|
+
| `dayOfWeek` | `number \| number[]` | — | ISO weekday 1=Mon…7=Sun. Required for weekly. |
|
|
165
|
+
| `dayOfMonth` | `number` | `1` | Day of month (1–31). For monthly. |
|
|
166
|
+
| `startDate` | `string` | — | First occurrence `"YYYY-MM-DD"` |
|
|
167
|
+
| `until` | `string` | — | Last occurrence `"YYYY-MM-DD"` |
|
|
168
|
+
| `count` | `number` | — | Max occurrences from `startDate` |
|
|
169
|
+
| `color` | `string?` | — | Accent color |
|
|
170
|
+
|
|
171
|
+
## Color Map & Auto-Coloring
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
// Explicit mapping
|
|
175
|
+
const adapter = createMemoryAdapter(events, { colorMap: { yoga: '#818cf8', wellness: '#34d399' } });
|
|
176
|
+
|
|
177
|
+
// Auto-assign from accent color
|
|
178
|
+
const adapter = createMemoryAdapter(events, { autoColor: '#6366f1' });
|
|
179
|
+
|
|
180
|
+
// Fixed vivid palette
|
|
181
|
+
const adapter = createMemoryAdapter(events, { autoColor: true });
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Events with explicit `color` always take priority.
|
|
185
|
+
|
|
186
|
+
## Localization (i18n)
|
|
187
|
+
|
|
188
|
+
All UI strings are configurable via the labels system:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { setLabels, resetLabels, getLabels } from '@nomideusz/svelte-calendar';
|
|
192
|
+
import type { CalendarLabels } from '@nomideusz/svelte-calendar';
|
|
193
|
+
|
|
194
|
+
// Override any subset — unset keys stay English
|
|
195
|
+
setLabels({
|
|
196
|
+
today: 'Heute',
|
|
197
|
+
yesterday: 'Gestern',
|
|
198
|
+
tomorrow: 'Morgen',
|
|
199
|
+
day: 'Tag',
|
|
200
|
+
week: 'Woche',
|
|
201
|
+
now: 'jetzt',
|
|
202
|
+
free: 'frei',
|
|
203
|
+
allDay: 'Ganztägig',
|
|
204
|
+
done: 'Erledigt',
|
|
205
|
+
upNext: 'Als Nächstes',
|
|
206
|
+
noEvents: 'Keine Termine',
|
|
207
|
+
goToToday: 'Heute anzeigen',
|
|
208
|
+
nMore: (n) => `+${n} weitere`,
|
|
209
|
+
nEvents: (n) => `${n} Termin${n === 1 ? '' : 'e'}`,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Reset to English
|
|
213
|
+
resetLabels();
|
|
214
|
+
|
|
215
|
+
// Read current labels
|
|
216
|
+
const labels = getLabels();
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
<details>
|
|
220
|
+
<summary><strong>Full label keys</strong></summary>
|
|
221
|
+
|
|
222
|
+
| Key | Default | Description |
|
|
223
|
+
|-----|---------|-------------|
|
|
224
|
+
| `today` | `'Today'` | Relative day label / nav button |
|
|
225
|
+
| `yesterday` | `'Yesterday'` | Relative day label |
|
|
226
|
+
| `tomorrow` | `'Tomorrow'` | Relative day label |
|
|
227
|
+
| `day` | `'Day'` | Granularity pill |
|
|
228
|
+
| `week` | `'Week'` | Granularity pill |
|
|
229
|
+
| `planner` | `'Planner'` | View label |
|
|
230
|
+
| `agenda` | `'Agenda'` | View label |
|
|
231
|
+
| `now` | `'now'` | Live indicator badge |
|
|
232
|
+
| `free` | `'free'` | Empty slot hint |
|
|
233
|
+
| `allDay` | `'All day'` | All-day event label |
|
|
234
|
+
| `done` | `'Done'` | Past section header |
|
|
235
|
+
| `upNext` | `'Up next'` | Upcoming section header |
|
|
236
|
+
| `until` | `'until'` | Time-until prefix |
|
|
237
|
+
| `noEvents` | `'No events'` | Empty day |
|
|
238
|
+
| `nothingScheduled` | `'Nothing scheduled'` | Empty state |
|
|
239
|
+
| `allDoneForToday` | `'All done for today'` | Completed state |
|
|
240
|
+
| `goToToday` | `'Go to today'` | Nav button aria |
|
|
241
|
+
| `previousDay` / `nextDay` | `'Previous day'` / `'Next day'` | Nav aria |
|
|
242
|
+
| `previousWeek` / `nextWeek` | `'Previous week'` / `'Next week'` | Nav aria |
|
|
243
|
+
| `calendar` | `'Calendar'` | Root region aria |
|
|
244
|
+
| `nMore(n)` | `` `+${n} more` `` | Overflow count |
|
|
245
|
+
| `nEvents(n)` | `` `${n} event(s)` `` | Event count aria |
|
|
246
|
+
| `nCompleted(n)` | `` `${n} completed` `` | Completed count |
|
|
247
|
+
| `dayNOfTotal(i, t)` | `` `day ${i} of ${t}` `` | Multi-day segment |
|
|
248
|
+
| `percentComplete(p)` | `` `${p}% complete` `` | Progress aria |
|
|
249
|
+
|
|
250
|
+
</details>
|
|
251
|
+
|
|
252
|
+
## Themes
|
|
253
|
+
|
|
254
|
+
| Preset | Description |
|
|
255
|
+
|--------|-------------|
|
|
256
|
+
| `midnight` | Dark navy/slate, red accent |
|
|
257
|
+
| `neutral` | White/gray, blue accent, inherits fonts — **recommended** |
|
|
258
|
+
|
|
259
|
+
```svelte
|
|
260
|
+
<Calendar {views} {adapter} theme={neutral} />
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Customize any token:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
const myTheme = `
|
|
267
|
+
${neutral}
|
|
268
|
+
--dt-accent: #e11d48;
|
|
269
|
+
--dt-bg: var(--my-app-surface);
|
|
270
|
+
--dt-text: var(--my-app-text);
|
|
271
|
+
`;
|
|
272
|
+
```
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
<details>
|
|
276
|
+
<summary><strong>Full token reference</strong></summary>
|
|
277
|
+
|
|
278
|
+
| Token | Purpose |
|
|
279
|
+
|-------|---------|
|
|
280
|
+
| `--dt-bg` | Main background |
|
|
281
|
+
| `--dt-surface` | Elevated surface |
|
|
282
|
+
| `--dt-border` | Default border |
|
|
283
|
+
| `--dt-border-day` | Day-column dividers |
|
|
284
|
+
| `--dt-text` | Primary text |
|
|
285
|
+
| `--dt-text-2` | Secondary text |
|
|
286
|
+
| `--dt-text-3` | Tertiary text |
|
|
287
|
+
| `--dt-accent` | Accent color |
|
|
288
|
+
| `--dt-accent-dim` | Accent at ~12% opacity |
|
|
289
|
+
| `--dt-glow` | Accent glow / focus ring |
|
|
290
|
+
| `--dt-today-bg` | Today column highlight |
|
|
291
|
+
| `--dt-btn-text` | Button label color |
|
|
292
|
+
| `--dt-scrollbar` | Scrollbar thumb |
|
|
293
|
+
| `--dt-success` | Completed indicator |
|
|
294
|
+
| `--dt-serif` / `--dt-sans` / `--dt-mono` | Font stacks |
|
|
295
|
+
|
|
296
|
+
</details>
|
|
297
|
+
|
|
298
|
+
## Embeddable Widget
|
|
299
|
+
|
|
300
|
+
Drop into any HTML page - no build tools needed:
|
|
301
|
+
|
|
302
|
+
```html
|
|
303
|
+
<script src="https://cdn.jsdelivr.net/npm/@nomideusz/svelte-calendar/widget/widget.js"></script>
|
|
304
|
+
|
|
305
|
+
<day-calendar
|
|
306
|
+
api="https://myschool.com/api/events"
|
|
307
|
+
theme="neutral"
|
|
308
|
+
height="600"
|
|
309
|
+
></day-calendar>
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Standalone Views
|
|
313
|
+
|
|
314
|
+
```svelte
|
|
315
|
+
<Planner style={midnight} mode="day" events={events} height={600} />
|
|
316
|
+
<Agenda mode="day" events={events} height={520} />
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Development
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
pnpm install
|
|
323
|
+
pnpm dev # SvelteKit dev server
|
|
324
|
+
pnpm check # Type check
|
|
325
|
+
pnpm run package # Build library
|
|
326
|
+
pnpm run build:widget # Build widget.js
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## License
|
|
330
|
+
|
|
331
|
+
MIT
|