@nomideusz/svelte-calendar 0.5.2 → 0.6.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.
Files changed (44) hide show
  1. package/README.md +408 -178
  2. package/dist/adapters/memory.d.ts +7 -9
  3. package/dist/adapters/memory.js +8 -23
  4. package/dist/adapters/recurring.d.ts +3 -8
  5. package/dist/adapters/recurring.js +27 -30
  6. package/dist/calendar/Calendar.svelte +457 -46
  7. package/dist/calendar/Calendar.svelte.d.ts +55 -4
  8. package/dist/core/index.d.ts +2 -2
  9. package/dist/core/index.js +1 -1
  10. package/dist/core/locale.js +2 -2
  11. package/dist/core/palette.d.ts +5 -0
  12. package/dist/core/palette.js +8 -0
  13. package/dist/core/types.d.ts +14 -0
  14. package/dist/engine/event-store.svelte.d.ts +0 -17
  15. package/dist/engine/event-store.svelte.js +30 -17
  16. package/dist/engine/index.d.ts +1 -1
  17. package/dist/engine/view-state.svelte.d.ts +10 -5
  18. package/dist/engine/view-state.svelte.js +32 -16
  19. package/dist/index.d.ts +7 -7
  20. package/dist/index.js +3 -4
  21. package/dist/theme/auto.d.ts +53 -0
  22. package/dist/theme/auto.js +534 -0
  23. package/dist/theme/index.d.ts +3 -1
  24. package/dist/theme/index.js +3 -1
  25. package/dist/theme/presets.d.ts +20 -6
  26. package/dist/theme/presets.js +19 -6
  27. package/dist/views/agenda/AgendaDay.svelte +116 -37
  28. package/dist/views/agenda/AgendaWeek.svelte +219 -70
  29. package/dist/views/agenda/index.d.ts +0 -2
  30. package/dist/views/agenda/index.js +0 -2
  31. package/dist/views/index.d.ts +3 -2
  32. package/dist/views/index.js +3 -2
  33. package/dist/views/mobile/Mobile.svelte +17 -0
  34. package/dist/views/mobile/Mobile.svelte.d.ts +7 -0
  35. package/dist/views/mobile/MobileDay.svelte +665 -0
  36. package/dist/views/mobile/MobileDay.svelte.d.ts +20 -0
  37. package/dist/views/mobile/MobileWeek.svelte +456 -0
  38. package/dist/views/mobile/MobileWeek.svelte.d.ts +20 -0
  39. package/dist/views/mobile/index.d.ts +1 -0
  40. package/dist/views/mobile/index.js +1 -0
  41. package/dist/views/planner/PlannerDay.svelte +144 -10
  42. package/dist/views/planner/PlannerWeek.svelte +145 -40
  43. package/dist/widget/CalendarWidget.svelte +2 -17
  44. package/package.json +2 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @nomideusz/svelte-calendar
2
2
 
3
- A themeable **Svelte 5** calendar with **Day/Week Planner & Agenda** views.
3
+ A themeable **Svelte 5** calendar with **Day/Week Planner & Agenda** views, touch-first mobile views, and a smart auto-theme that adapts to any page.
4
4
 
5
5
  ## Installation
6
6
 
@@ -13,145 +13,353 @@ pnpm add @nomideusz/svelte-calendar
13
13
  ## Quick Start
14
14
 
15
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
- ];
16
+ <script>
17
+ import { Calendar, createMemoryAdapter } from '@nomideusz/svelte-calendar';
18
+
19
+ const adapter = createMemoryAdapter([
20
+ { id: '1', title: 'Team Sync', start: new Date('2026-03-01T09:00'), end: new Date('2026-03-01T10:00') },
21
+ { id: '2', title: 'Lunch', start: new Date('2026-03-01T12:00'), end: new Date('2026-03-01T13:00') },
22
+ ]);
23
+ </script>
24
+
25
+ <Calendar {adapter} />
26
+ ```
27
+
28
+ That's it — 6 views (Day/Week × Planner, Agenda, Mobile), auto-coloring, drag-and-drop, all out of the box. The default `auto` theme probes your page's background, accent color, and fonts, so the calendar adapts to any design.
29
+
30
+ ## Views
31
+
32
+ Switch between **Planner** (time grid) and **Agenda** (list) in Day or Week mode:
33
+
34
+ ```svelte
35
+ <Calendar {adapter} view="week-planner" /> <!-- default -->
36
+ <Calendar {adapter} view="day-planner" />
37
+ <Calendar {adapter} view="week-agenda" />
38
+ <Calendar {adapter} view="day-agenda" />
39
+ ```
40
+
41
+ Users can also switch via the built-in Day/Week pills.
42
+
43
+ Hide the pills when your app controls the view externally:
44
+
45
+ ```svelte
46
+ <Calendar {adapter} view="day-planner" showModePills={false} />
47
+ ```
48
+
49
+ ## Mobile
50
+
51
+ On narrow screens (`< 768px`), the calendar automatically remaps Planner views to touch-first Mobile views with swipe navigation, a centralized header with Day/Week pills, and a compact layout:
52
+
53
+ ```svelte
54
+ <!-- Auto-detect (default) — switches at 768px -->
55
+ <Calendar {adapter} />
56
+
57
+ <!-- Force mobile layout -->
58
+ <Calendar {adapter} mobile={true} />
59
+
60
+ <!-- Force desktop layout -->
61
+ <Calendar {adapter} mobile={false} />
62
+ ```
63
+
64
+ Mobile views include:
65
+ - **MobileDay** — vertical time grid with hour labels, swipe left/right to change days, all-day event chips at the top, tap-to-create
66
+ - **MobileWeek** — vertical day list showing each day's events with relative labels (Today, Tomorrow, etc.)
67
+
68
+ Agenda views keep their list-based layout on mobile but hide desktop floating navigation in favor of the centralized header.
69
+
70
+ ## Callbacks
71
+
72
+ ```svelte
73
+ <Calendar
74
+ {adapter}
75
+ oneventclick={(event) => console.log('Clicked', event.title)}
76
+ oneventcreate={(range) => console.log('New slot', range.start, range.end)}
77
+ oneventmove={(event, start, end) => console.log('Moved', event.title, start, end)}
78
+ onviewchange={(viewId) => console.log('View', viewId)}
79
+ />
80
+ ```
81
+
82
+ Set `readOnly` to disable drag, resize, and click-to-create:
83
+
84
+ ```svelte
85
+ <Calendar {adapter} readOnly />
86
+ ```
87
+
88
+ Hide nav controls (prev/next/today) and treat all days equally (no past-day dimming):
89
+
90
+ ```svelte
91
+ <!-- Yoga studio: fixed weekly schedule, no browsing, all days equal -->
92
+ <Calendar
93
+ {adapter}
94
+ view="week-agenda"
95
+ readOnly
96
+ showNavigation={false}
97
+ showDates={false}
98
+ equalDays
99
+ />
100
+ ```
101
+
102
+ Hide weekends for a workweek view:
103
+
104
+ ```svelte
105
+ <!-- Office planner: Mon–Fri only -->
106
+ <Calendar {adapter} view="week-planner" hideDays={[6, 7]} />
107
+ ```
108
+
109
+ Control which date the calendar shows from your app:
110
+
111
+ ```svelte
112
+ <script>
113
+ let date = $state(new Date());
114
+ </script>
115
+
116
+ <Calendar
117
+ {adapter}
118
+ currentDate={date}
119
+ ondatechange={(d) => date = d}
120
+ />
121
+ ```
122
+
123
+ Show a rolling 3-day view:
124
+
125
+ ```svelte
126
+ <Calendar {adapter} view="week-planner" days={3} />
127
+ ```
128
+
129
+ Block lunch hours and enforce 30–120 min events:
27
130
 
28
- const adapter = createMemoryAdapter(events);
131
+ ```svelte
132
+ <script>
133
+ import type { BlockedSlot } from '@nomideusz/svelte-calendar';
29
134
 
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' } },
135
+ const blocked: BlockedSlot[] = [
136
+ { start: 12, end: 13, label: 'Lunch' }, // every day 12–1 PM
137
+ { day: 6, start: 0, end: 24, label: 'Saturday' }, // block all Saturday
35
138
  ];
36
139
  </script>
37
140
 
141
+ <Calendar {adapter} blockedSlots={blocked} minDuration={30} maxDuration={120} />
142
+ ```
143
+
144
+ Disable specific dates:
145
+
146
+ ```svelte
38
147
  <Calendar
39
- {views}
40
148
  {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)}
149
+ disabledDates={[new Date('2026-03-25'), new Date('2026-04-01')]}
46
150
  />
47
151
  ```
48
152
 
49
- ## Calendar Props
153
+ Custom day headers and hover previews:
50
154
 
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 |
155
+ ```svelte
156
+ <Calendar {adapter} oneventhover={(ev) => showTooltip(ev)}>
157
+ {#snippet dayHeader({ date, isToday, dayName })}
158
+ <span style:font-weight={isToday ? 'bold' : 'normal'}>{dayName}</span>
159
+ {/snippet}
160
+ </Calendar>
161
+ ```
71
162
 
72
- ## TimelineEvent
163
+ ## Themes
164
+
165
+ Three built-in presets:
166
+
167
+ | Preset | Description |
168
+ |--------|-------------|
169
+ | `auto` | **Default.** Probes the host page at mount — background, accent color, fonts, light/dark mode — and generates matching `--dt-*` tokens automatically. Reactively watches for host theme changes. |
170
+ | `neutral` | Explicit light theme. White bg, blue accent, inherits host fonts. Use when embedding standalone. |
171
+ | `midnight` | Explicit dark theme. Charcoal bg, red accent. |
172
+
173
+ ```svelte
174
+ <script>
175
+ import { Calendar, neutral, midnight } from '@nomideusz/svelte-calendar';
176
+ </script>
177
+
178
+ <Calendar {adapter} /> <!-- auto: adapts to host page -->
179
+ <Calendar {adapter} theme={neutral} /> <!-- explicit light mode -->
180
+ <Calendar {adapter} theme={midnight} /> <!-- explicit dark mode -->
181
+ ```
182
+
183
+ ### Smart Auto Theme
184
+
185
+ The default `auto` preset probes the host page's design and generates a calendar that blends in — no configuration needed. It detects:
186
+
187
+ - **Background color** — from CSS variables, inline styles, or computed styles
188
+ - **Light/dark mode** — from background luminance
189
+ - **Accent/brand color** — from CSS variables (`--accent`, `--primary`, `--bs-primary`, etc.), link colors, or button colors
190
+ - **Text color** — validated for contrast against the background
191
+ - **Fonts** — inherited via CSS cascade
192
+
193
+ It also watches for changes (e.g. dark mode toggle) and updates automatically.
194
+
195
+ Fine-tune auto-detection with the `autoTheme` prop:
196
+
197
+ ```svelte
198
+ <!-- Force dark mode even if the page background is light -->
199
+ <Calendar {adapter} autoTheme={{ mode: 'dark' }} />
200
+
201
+ <!-- Override the accent color (skip probing) -->
202
+ <Calendar {adapter} autoTheme={{ accent: '#e11d48' }} />
203
+
204
+ <!-- Override the font stack -->
205
+ <Calendar {adapter} autoTheme={{ font: '"Poppins", sans-serif' }} />
206
+
207
+ <!-- Combine overrides -->
208
+ <Calendar {adapter} autoTheme={{ mode: 'dark', accent: '#10b981' }} />
209
+
210
+ <!-- Disable auto-probing entirely (passive CSS inheritance only) -->
211
+ <Calendar {adapter} autoTheme={false} />
212
+ ```
213
+
214
+ ### Manual CSS Variables
215
+
216
+ Override any design token by setting `--dt-*` custom properties on an ancestor:
217
+
218
+ ```html
219
+ <!-- Wrap in a div with your overrides -->
220
+ <div style="--dt-accent: #e11d48; --dt-bg: #1a1a2e; --dt-text: rgba(255,255,255,0.87);">
221
+ <Calendar {adapter} />
222
+ </div>
223
+ ```
224
+
225
+ Or set them at the page level:
226
+
227
+ ```css
228
+ :root {
229
+ --dt-bg: #fafafa;
230
+ --dt-accent: #2563eb;
231
+ --dt-text: rgba(0, 0, 0, 0.87);
232
+ --dt-border: rgba(0, 0, 0, 0.08);
233
+ }
234
+ ```
235
+
236
+ The auto-probe reads these first — if you set `--dt-bg` or `--accent` on `:root`, the calendar picks them up.
237
+
238
+ ### Extending Presets
239
+
240
+ Build on a preset by appending overrides:
241
+
242
+ ```ts
243
+ import { neutral } from '@nomideusz/svelte-calendar';
244
+
245
+ // neutral base + custom accent + rounded feel
246
+ const custom = `${neutral}; --dt-accent: #e11d48;`;
247
+ ```
248
+
249
+ ```svelte
250
+ <Calendar {adapter} theme={custom} />
251
+ ```
252
+
253
+ <details>
254
+ <summary>All design tokens</summary>
255
+
256
+ | Token | Purpose |
257
+ |-------|---------|
258
+ | `--dt-stage-bg` | Background behind the calendar (page area) |
259
+ | `--dt-bg` | Calendar card background |
260
+ | `--dt-surface` | Elevated surface (alternating rows, headers) |
261
+ | `--dt-border` | Default border |
262
+ | `--dt-border-day` | Day-column dividers |
263
+ | `--dt-text` | Primary text |
264
+ | `--dt-text-2` | Secondary text |
265
+ | `--dt-text-3` | Tertiary text |
266
+ | `--dt-accent` | Accent color |
267
+ | `--dt-accent-dim` | Accent at ~12% opacity |
268
+ | `--dt-glow` | Accent glow / focus ring |
269
+ | `--dt-today-bg` | Today column highlight |
270
+ | `--dt-btn-text` | Button label color |
271
+ | `--dt-scrollbar` | Scrollbar thumb |
272
+ | `--dt-success` | Completed indicator |
273
+ | `--dt-serif` / `--dt-sans` / `--dt-mono` | Font stacks |
274
+
275
+ </details>
276
+
277
+ ## Events
278
+
279
+ ### TimelineEvent
73
280
 
74
281
  | Field | Type | Description |
75
282
  |-------|------|-------------|
76
283
  | `id` | `string` | Unique identifier |
77
284
  | `title` | `string` | Event title |
78
285
  | `start` / `end` | `Date` | Time range |
79
- | `color` | `string?` | Accent color |
80
- | `category` | `string?` | For grouping / colorMap |
81
- | `subtitle` | `string?` | Secondary text |
286
+ | `color` | `string?` | Accent color (auto-assigned if omitted) |
287
+ | `category` | `string?` | Grouping key events with the same category share a color |
288
+ | `subtitle` | `string?` | Secondary text below the title |
82
289
  | `tags` | `string[]?` | Small accent-colored pills |
83
290
  | `allDay` | `boolean?` | Render as an all-day event |
84
- | `data` | `Record?` | Arbitrary payload |
291
+ | `data` | `Record?` | Arbitrary payload for your app |
85
292
 
86
- ## Multi-day & All-day Events
293
+ ### Auto-Coloring
87
294
 
88
- Events that span multiple days or are flagged `allDay: true` are rendered in a
89
- dedicated strip above timed events in every view:
295
+ Omit `color` and events are auto-assigned a vivid palette color, grouped by `category` (or `title`):
90
296
 
91
297
  ```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') },
298
+ const events = [
299
+ { id: '1', title: 'Yoga', category: 'wellness', start: ..., end: ... },
300
+ { id: '2', title: 'Pilates', category: 'wellness', start: ..., end: ... }, // same color
301
+ { id: '3', title: 'Standup', start: ..., end: ... }, // different color
99
302
  ];
100
303
  ```
101
304
 
102
- Utility helpers are exported for custom views:
305
+ Generate a theme-harmonious palette from any accent color:
103
306
 
104
307
  ```ts
105
- import { isAllDay, isMultiDay, segmentForDay } from '@nomideusz/svelte-calendar';
308
+ import { createMemoryAdapter, generatePalette } from '@nomideusz/svelte-calendar';
106
309
 
107
- segmentForDay(event, dayTimestamp);
108
- // { ev, start, end, isStart, isEnd, dayIndex, totalDays, allDay } | null
310
+ // Colors that harmonize with your theme's accent
311
+ const palette = generatePalette('#e11d48');
312
+ const adapter = createMemoryAdapter(events, { palette });
109
313
  ```
110
314
 
111
- ## Views
315
+ ### Multi-day & All-day
316
+
317
+ Events spanning multiple days or flagged `allDay: true` render in a dedicated strip above timed events:
318
+
319
+ ```ts
320
+ const events = [
321
+ { id: '1', title: 'Conference', start: new Date('2026-03-15'), end: new Date('2026-03-18'), allDay: true },
322
+ { id: '2', title: 'Sprint', start: new Date('2026-03-15T00:00'), end: new Date('2026-03-17T00:00') },
323
+ ];
324
+ ```
325
+
326
+ ### Custom Event Rendering
112
327
 
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) |
328
+ Use the `event` snippet to fully control how events look:
329
+
330
+ ```svelte
331
+ <Calendar {adapter}>
332
+ {#snippet event(ev)}
333
+ <div style="padding: 4px 8px;">
334
+ <strong>{ev.title}</strong>
335
+ {#if ev.subtitle}<small>{ev.subtitle}</small>{/if}
336
+ </div>
337
+ {/snippet}
338
+ </Calendar>
339
+ ```
117
340
 
118
341
  ## Recurring Schedules
119
342
 
343
+ For fixed repeating events (class timetables, office hours):
344
+
120
345
  ```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,
346
+ <script>
347
+ import { Calendar, createRecurringAdapter } from '@nomideusz/svelte-calendar';
348
+
349
+ const adapter = createRecurringAdapter([
350
+ { id: '1', title: 'Yoga', dayOfWeek: 1, startTime: '07:00', endTime: '08:30' },
351
+ { id: '2', title: 'Standup', frequency: 'daily', startTime: '09:00', endTime: '09:15',
352
+ startDate: '2026-03-01', until: '2026-03-31' },
353
+ { id: '3', title: 'Review', frequency: 'monthly', dayOfMonth: 15,
142
354
  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
- ];
355
+ ]);
149
356
  </script>
150
357
 
151
- <Calendar {views} {adapter} defaultView="week-planner" theme={neutral} readOnly />
358
+ <Calendar {adapter} readOnly />
152
359
  ```
153
360
 
154
- ### RecurringEvent
361
+ <details>
362
+ <summary>RecurringEvent fields</summary>
155
363
 
156
364
  | Field | Type | Default | Description |
157
365
  |-------|------|---------|-------------|
@@ -166,66 +374,79 @@ segmentForDay(event, dayTimestamp);
166
374
  | `startDate` | `string` | — | First occurrence `"YYYY-MM-DD"` |
167
375
  | `until` | `string` | — | Last occurrence `"YYYY-MM-DD"` |
168
376
  | `count` | `number` | — | Max occurrences from `startDate` |
169
- | `color` | `string?` | — | Accent color |
377
+ | `color` | `string?` | — | Accent color (auto-assigned if omitted) |
170
378
 
171
- ## Color Map & Auto-Coloring
379
+ </details>
172
380
 
173
- ```ts
174
- // Explicit mapping
175
- const adapter = createMemoryAdapter(events, { colorMap: { yoga: '#818cf8', wellness: '#34d399' } });
381
+ ## REST Adapter
176
382
 
177
- // Auto-assign from accent color
178
- const adapter = createMemoryAdapter(events, { autoColor: '#6366f1' });
383
+ Connect to any REST API:
179
384
 
180
- // Fixed vivid palette
181
- const adapter = createMemoryAdapter(events, { autoColor: true });
385
+ ```ts
386
+ import { Calendar, createRestAdapter } from '@nomideusz/svelte-calendar';
387
+
388
+ const adapter = createRestAdapter({
389
+ baseUrl: 'https://api.example.com/v1',
390
+ headers: { Authorization: 'Bearer TOKEN' },
391
+ // Optional: map your API shape to TimelineEvent[]
392
+ mapEvents: (data) => data.items.map(item => ({
393
+ id: item.id,
394
+ title: item.name,
395
+ start: new Date(item.startAt),
396
+ end: new Date(item.endAt),
397
+ })),
398
+ });
182
399
  ```
183
400
 
184
- Events with explicit `color` always take priority.
401
+ The adapter calls `GET /events?start=...&end=...`, `POST /events`, `PATCH /events/:id`, and `DELETE /events/:id`.
402
+
403
+ ## Custom Adapter
404
+
405
+ Implement the `CalendarAdapter` interface to connect any data source:
406
+
407
+ ```ts
408
+ import type { CalendarAdapter, DateRange, TimelineEvent } from '@nomideusz/svelte-calendar';
409
+
410
+ const adapter: CalendarAdapter = {
411
+ fetchEvents: async (range: DateRange) => { /* return TimelineEvent[] */ },
412
+ createEvent: async (event) => { /* return created TimelineEvent with id */ },
413
+ updateEvent: async (id, patch) => { /* return updated TimelineEvent */ },
414
+ deleteEvent: async (id) => { /* void */ },
415
+ };
416
+ ```
185
417
 
186
418
  ## Localization (i18n)
187
419
 
188
- All UI strings are configurable via the labels system:
420
+ The `locale` prop controls date/time formatting (BCP 47):
421
+
422
+ ```svelte
423
+ <Calendar {adapter} locale="de-DE" />
424
+ ```
425
+
426
+ Override UI labels for full translation:
189
427
 
190
428
  ```ts
191
- import { setLabels, resetLabels, getLabels } from '@nomideusz/svelte-calendar';
192
- import type { CalendarLabels } from '@nomideusz/svelte-calendar';
429
+ import { setLabels } from '@nomideusz/svelte-calendar';
193
430
 
194
- // Override any subset — unset keys stay English
195
431
  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',
432
+ today: 'Heute', day: 'Tag', week: 'Woche',
206
433
  noEvents: 'Keine Termine',
207
- goToToday: 'Heute anzeigen',
208
434
  nMore: (n) => `+${n} weitere`,
209
- nEvents: (n) => `${n} Termin${n === 1 ? '' : 'e'}`,
210
435
  });
211
-
212
- // Reset to English
213
- resetLabels();
214
-
215
- // Read current labels
216
- const labels = getLabels();
217
436
  ```
218
437
 
438
+ Call `resetLabels()` to restore English defaults.
439
+
219
440
  <details>
220
- <summary><strong>Full label keys</strong></summary>
441
+ <summary>All label keys</summary>
221
442
 
222
443
  | Key | Default | Description |
223
444
  |-----|---------|-------------|
224
445
  | `today` | `'Today'` | Relative day label / nav button |
225
446
  | `yesterday` | `'Yesterday'` | Relative day label |
226
447
  | `tomorrow` | `'Tomorrow'` | Relative day label |
227
- | `day` | `'Day'` | Granularity pill |
228
- | `week` | `'Week'` | Granularity pill |
448
+ | `day` | `'Day'` | Mode pill |
449
+ | `week` | `'Week'` | Mode pill |
229
450
  | `planner` | `'Planner'` | View label |
230
451
  | `agenda` | `'Agenda'` | View label |
231
452
  | `now` | `'now'` | Live indicator badge |
@@ -249,55 +470,26 @@ const labels = getLabels();
249
470
 
250
471
  </details>
251
472
 
252
- ## Themes
253
-
254
- | Preset | Description |
255
- |--------|-------------|
256
- | `midnight` | Dark navy/slate, red accent |
257
- | `neutral` | White/gray, blue accent, inherits fonts — **recommended** |
473
+ ## Timezone Support
258
474
 
259
- ```svelte
260
- <Calendar {views} {adapter} theme={neutral} />
261
- ```
262
-
263
- Customize any token:
475
+ Convert events between timezones using the built-in helpers:
264
476
 
265
477
  ```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
- ```
478
+ import { toZonedTime, fromZonedTime, nowInZone } from '@nomideusz/svelte-calendar';
274
479
 
275
- <details>
276
- <summary><strong>Full token reference</strong></summary>
480
+ // Display a UTC date in a specific timezone
481
+ const local = toZonedTime(utcDate, 'America/New_York');
277
482
 
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 |
483
+ // Convert back to UTC before saving
484
+ const utc = fromZonedTime(localDate, 'America/New_York');
295
485
 
296
- </details>
486
+ // Current time in a timezone
487
+ const now = nowInZone('Asia/Tokyo');
488
+ ```
297
489
 
298
490
  ## Embeddable Widget
299
491
 
300
- Drop into any HTML page - no build tools needed:
492
+ Drop into any HTML page no build tools needed:
301
493
 
302
494
  ```html
303
495
  <script src="https://cdn.jsdelivr.net/npm/@nomideusz/svelte-calendar/widget/widget.js"></script>
@@ -309,12 +501,50 @@ Drop into any HTML page - no build tools needed:
309
501
  ></day-calendar>
310
502
  ```
311
503
 
312
- ## Standalone Views
504
+ ## All Props
313
505
 
314
- ```svelte
315
- <Planner style={midnight} mode="day" events={events} height={600} />
316
- <Agenda mode="day" events={events} height={520} />
317
- ```
506
+ <details>
507
+ <summary>Full Calendar props reference</summary>
508
+
509
+ | Prop | Type | Default | Description |
510
+ |------|------|---------|-------------|
511
+ | `adapter` | `CalendarAdapter` | *required* | Data layer (memory, recurring, REST, or custom) |
512
+ | `views` | `CalendarView[]` | all 4 built-in | Registered view components |
513
+ | `view` | `string` | first view | Active view ID |
514
+ | `theme` | `string` | `auto` | CSS theme string (`--dt-*` custom properties). `auto` probes the host page and generates matching tokens. |
515
+ | `autoTheme` | `AutoThemeOptions \| false` | `{}` | Fine-tune auto-detection: `{ mode, accent, font }`. Set `false` to disable probing. |
516
+ | `mobile` | `'auto' \| boolean` | `'auto'` | Mobile mode. `'auto'` detects via `matchMedia(<768px)`. Remaps Planner→Mobile views. |
517
+ | `height` | `number` | `600` | Total height in pixels |
518
+ | `borderRadius` | `number` | `12` | Border radius in pixels. Set to `0` for no rounding. |
519
+ | `locale` | `string` | `'en-US'` | BCP 47 locale tag |
520
+ | `dir` | `'ltr' \| 'rtl' \| 'auto'` | — | Text direction |
521
+ | `mondayStart` | `boolean` | `true` | Start week on Monday |
522
+ | `readOnly` | `boolean` | `false` | Disable drag, resize, and click-to-create |
523
+ | `visibleHours` | `[number, number]` | — | Crop grid to `[startHour, endHour)` |
524
+ | `initialDate` | `Date` | today | Date to focus on at mount |
525
+ | `snapInterval` | `number` | `15` | Drag snap in minutes |
526
+ | `showModePills` | `boolean` | `true` | Show the Day/Week mode pills |
527
+ | `showNavigation` | `boolean` | `true` | Show prev/next/today navigation |
528
+ | `equalDays` | `boolean` | `false` | Treat all days equally (no past-day dimming/collapsing) |
529
+ | `showDates` | `boolean` | `true` | Show date numbers in headers. `false` = day names only (Mon, Tue, …) |
530
+ | `hideDays` | `number[]` | — | ISO weekdays to hide (1=Mon … 7=Sun). E.g. `[6, 7]` hides weekends |
531
+ | `currentDate` | `Date` | — | Controlled focus date (drives which date the calendar shows) |
532
+ | `days` | `number` | `7` | Number of days shown in week views (e.g. `3` for a rolling 3-day view) |
533
+ | `blockedSlots` | `BlockedSlot[]` | — | Time ranges that cannot be booked (hatched overlay in planner views) |
534
+ | `disabledDates` | `Date[]` | — | Dates that are fully disabled (dimmed, no interaction) |
535
+ | `minDuration` | `number` | — | Minimum event duration in minutes (enforced on create & resize) |
536
+ | `maxDuration` | `number` | — | Maximum event duration in minutes (enforced on create & resize) |
537
+ | `dayHeader` | `Snippet<[{ date, isToday, dayName }]>` | — | Custom day header snippet for planner/agenda views |
538
+ | `oneventclick` | `(event) => void` | — | Event clicked |
539
+ | `oneventcreate` | `(range) => void` | — | New time range selected |
540
+ | `oneventmove` | `(event, start, end) => void` | — | Event dragged to new time |
541
+ | `onviewchange` | `(viewId) => void` | — | Active view changed |
542
+ | `oneventhover` | `(event) => void` | — | Pointer enters an event (for tooltips, previews) |
543
+ | `ondatechange` | `(date) => void` | — | Focused date changed (navigation, scroll, etc.) |
544
+ | `event` | `Snippet<[TimelineEvent]>` | — | Custom event rendering |
545
+ | `empty` | `Snippet` | — | Empty state content |
546
+
547
+ </details>
318
548
 
319
549
  ## Development
320
550