@nomideusz/svelte-calendar 0.3.1 → 0.5.1

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