@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.
- package/README.md +193 -406
- 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 +137 -29
- package/dist/calendar/Calendar.svelte.d.ts +10 -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 +12 -6
- package/dist/engine/view-state.svelte.js +8 -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 -1514
- package/dist/views/agenda/Agenda.svelte.d.ts +0 -13
- 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} +8 -16
- package/dist/views/planner/PlannerWeek.svelte +826 -0
- package/dist/views/{week/WeekGrid.svelte.d.ts → planner/PlannerWeek.svelte.d.ts} +5 -9
- package/dist/views/planner/index.d.ts +1 -0
- package/dist/views/planner/index.js +1 -0
- package/dist/widget/CalendarWidget.svelte +54 -35
- package/dist/widget/CalendarWidget.svelte.d.ts +1 -1
- package/dist/widget/widget.d.ts +1 -7
- package/dist/widget/widget.js +44 -3
- package/package.json +5 -2
- package/widget/svelte-calendar.css +1 -0
- package/widget/widget.js +6 -144
- package/dist/calendar/Toolbar.svelte +0 -241
- package/dist/calendar/Toolbar.svelte.d.ts +0 -19
- package/dist/views/day/DayGrid.svelte +0 -1101
- package/dist/views/day/DayTimeline.svelte +0 -532
- package/dist/views/day/DayTimeline.svelte.d.ts +0 -4
- package/dist/views/day/index.d.ts +0 -2
- package/dist/views/day/index.js +0 -2
- package/dist/views/schedule/WeekSchedule.svelte +0 -133
- package/dist/views/schedule/WeekSchedule.svelte.d.ts +0 -38
- package/dist/views/schedule/index.d.ts +0 -1
- package/dist/views/schedule/index.js +0 -2
- package/dist/views/settings/Settings.svelte +0 -307
- package/dist/views/settings/Settings.svelte.d.ts +0 -23
- package/dist/views/settings/index.d.ts +0 -2
- package/dist/views/settings/index.js +0 -1
- package/dist/views/week/WeekGrid.svelte +0 -631
- package/dist/views/week/WeekHeatmap.svelte +0 -460
- package/dist/views/week/WeekHeatmap.svelte.d.ts +0 -8
- package/dist/views/week/index.d.ts +0 -2
- package/dist/views/week/index.js +0 -2
package/README.md
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# @nomideusz/svelte-calendar
|
|
2
2
|
|
|
3
|
-
A themeable
|
|
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
|
-
>
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
61
|
-
{ id: 'week-
|
|
62
|
-
{ id: 'day-agenda',
|
|
63
|
-
{ id: 'week-agenda',
|
|
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-
|
|
72
|
-
theme={
|
|
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
|
|
45
|
+
oneventcreate={(range) => console.log('create', range)}
|
|
76
46
|
/>
|
|
77
47
|
```
|
|
78
48
|
|
|
79
|
-
##
|
|
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
|
-
|
|
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
|
-
| `
|
|
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
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
102
|
+
Utility helpers are exported for custom views:
|
|
143
103
|
|
|
144
|
-
|
|
104
|
+
```ts
|
|
105
|
+
import { isAllDay, isMultiDay, segmentForDay } from '@nomideusz/svelte-calendar';
|
|
145
106
|
|
|
146
|
-
|
|
147
|
-
|
|
107
|
+
segmentForDay(event, dayTimestamp);
|
|
108
|
+
// → { ev, start, end, isStart, isEnd, dayIndex, totalDays, allDay } | null
|
|
148
109
|
```
|
|
149
110
|
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
+
## Recurring Schedules
|
|
159
119
|
|
|
160
120
|
```svelte
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
214
|
-
// Harmonious palette seeded from indigo accent
|
|
177
|
+
// Auto-assign from accent color
|
|
215
178
|
const adapter = createMemoryAdapter(events, { autoColor: '#6366f1' });
|
|
216
179
|
|
|
217
|
-
//
|
|
218
|
-
const adapter =
|
|
180
|
+
// Fixed vivid palette
|
|
181
|
+
const adapter = createMemoryAdapter(events, { autoColor: true });
|
|
219
182
|
```
|
|
220
183
|
|
|
221
|
-
|
|
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
|
-
|
|
186
|
+
## Localization (i18n)
|
|
227
187
|
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
247
|
-
|
|
215
|
+
// Read current labels
|
|
216
|
+
const labels = getLabels();
|
|
217
|
+
```
|
|
248
218
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
256
|
-
```
|
|
250
|
+
</details>
|
|
257
251
|
|
|
258
252
|
## Themes
|
|
259
253
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
|
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
|
|
327
|
-
| `--dt-accent` | Accent color
|
|
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` |
|
|
334
|
-
| `--dt-serif`
|
|
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
|
|
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
|
-
|
|
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
|
|
323
|
+
pnpm dev # SvelteKit dev server
|
|
537
324
|
pnpm check # Type check
|
|
538
|
-
pnpm run package # Build
|
|
539
|
-
pnpm run build:widget # Build
|
|
325
|
+
pnpm run package # Build library
|
|
326
|
+
pnpm run build:widget # Build widget.js
|
|
540
327
|
```
|
|
541
328
|
|
|
542
329
|
## License
|