@nomideusz/svelte-calendar 0.1.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 (77) hide show
  1. package/README.md +168 -0
  2. package/dist/adapters/index.d.ts +4 -0
  3. package/dist/adapters/index.js +2 -0
  4. package/dist/adapters/memory.d.ts +14 -0
  5. package/dist/adapters/memory.js +33 -0
  6. package/dist/adapters/rest.d.ts +20 -0
  7. package/dist/adapters/rest.js +48 -0
  8. package/dist/adapters/types.d.ts +23 -0
  9. package/dist/adapters/types.js +1 -0
  10. package/dist/assets/favicon.svg +1 -0
  11. package/dist/calendar/Calendar.svelte +213 -0
  12. package/dist/calendar/Calendar.svelte.d.ts +45 -0
  13. package/dist/calendar/Toolbar.svelte +241 -0
  14. package/dist/calendar/Toolbar.svelte.d.ts +19 -0
  15. package/dist/calendar/index.d.ts +3 -0
  16. package/dist/calendar/index.js +3 -0
  17. package/dist/core/clock.svelte.d.ts +21 -0
  18. package/dist/core/clock.svelte.js +53 -0
  19. package/dist/core/index.d.ts +6 -0
  20. package/dist/core/index.js +8 -0
  21. package/dist/core/locale.d.ts +38 -0
  22. package/dist/core/locale.js +96 -0
  23. package/dist/core/time.d.ts +37 -0
  24. package/dist/core/time.js +68 -0
  25. package/dist/core/types.d.ts +91 -0
  26. package/dist/core/types.js +11 -0
  27. package/dist/engine/drag.svelte.d.ts +31 -0
  28. package/dist/engine/drag.svelte.js +58 -0
  29. package/dist/engine/event-store.svelte.d.ts +47 -0
  30. package/dist/engine/event-store.svelte.js +120 -0
  31. package/dist/engine/index.d.ts +8 -0
  32. package/dist/engine/index.js +5 -0
  33. package/dist/engine/selection.svelte.d.ts +24 -0
  34. package/dist/engine/selection.svelte.js +58 -0
  35. package/dist/engine/view-state.svelte.d.ts +24 -0
  36. package/dist/engine/view-state.svelte.js +74 -0
  37. package/dist/index.d.ts +13 -0
  38. package/dist/index.js +14 -0
  39. package/dist/primitives/DayHeader.svelte +103 -0
  40. package/dist/primitives/DayHeader.svelte.d.ts +15 -0
  41. package/dist/primitives/EmptySlot.svelte +101 -0
  42. package/dist/primitives/EmptySlot.svelte.d.ts +16 -0
  43. package/dist/primitives/EventBlock.svelte +241 -0
  44. package/dist/primitives/EventBlock.svelte.d.ts +24 -0
  45. package/dist/primitives/NowIndicator.svelte +175 -0
  46. package/dist/primitives/NowIndicator.svelte.d.ts +22 -0
  47. package/dist/primitives/TimeGutter.svelte +105 -0
  48. package/dist/primitives/TimeGutter.svelte.d.ts +14 -0
  49. package/dist/primitives/index.d.ts +5 -0
  50. package/dist/primitives/index.js +6 -0
  51. package/dist/theme/index.d.ts +2 -0
  52. package/dist/theme/index.js +2 -0
  53. package/dist/theme/presets.d.ts +24 -0
  54. package/dist/theme/presets.js +89 -0
  55. package/dist/views/agenda/Agenda.svelte +1404 -0
  56. package/dist/views/agenda/Agenda.svelte.d.ts +20 -0
  57. package/dist/views/agenda/index.d.ts +1 -0
  58. package/dist/views/agenda/index.js +1 -0
  59. package/dist/views/day/DayGrid.svelte +1063 -0
  60. package/dist/views/day/DayGrid.svelte.d.ts +33 -0
  61. package/dist/views/day/DayTimeline.svelte +505 -0
  62. package/dist/views/day/DayTimeline.svelte.d.ts +4 -0
  63. package/dist/views/day/index.d.ts +2 -0
  64. package/dist/views/day/index.js +2 -0
  65. package/dist/views/index.d.ts +5 -0
  66. package/dist/views/index.js +5 -0
  67. package/dist/views/settings/Settings.svelte +303 -0
  68. package/dist/views/settings/Settings.svelte.d.ts +23 -0
  69. package/dist/views/settings/index.d.ts +2 -0
  70. package/dist/views/settings/index.js +1 -0
  71. package/dist/views/week/WeekGrid.svelte +594 -0
  72. package/dist/views/week/WeekGrid.svelte.d.ts +21 -0
  73. package/dist/views/week/WeekHeatmap.svelte +453 -0
  74. package/dist/views/week/WeekHeatmap.svelte.d.ts +4 -0
  75. package/dist/views/week/index.d.ts +2 -0
  76. package/dist/views/week/index.js +2 -0
  77. package/package.json +62 -0
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # @svelte-calendar/core
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** | DayTimeline (horizontal) | WeekGrid (vertical) | The primary planner — time blocks on a scrollable grid. |
13
+ | **Agenda** | DayAgenda | WeekAgenda | List / feed — Done, Now, Next (day) or grouped-by-day scroll (week). |
14
+ | **Heatmap** | — | WeekHeatmap | Density view — 24 cells per day showing busy/free intensity. Week-only. |
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @svelte-calendar/core
20
+ ```
21
+
22
+ > **Peer dependency:** Svelte 5 (`^5.0.0`)
23
+
24
+ ## Quick Start
25
+
26
+ ```svelte
27
+ <script lang="ts">
28
+ import {
29
+ Calendar,
30
+ DayTimeline,
31
+ DayAgenda,
32
+ WeekGrid,
33
+ WeekAgenda,
34
+ WeekHeatmap,
35
+ createMemoryAdapter,
36
+ midnight,
37
+ } from '@svelte-calendar/core';
38
+ import type { CalendarView, TimelineEvent } from '@svelte-calendar/core';
39
+
40
+ const events: TimelineEvent[] = [
41
+ { id: '1', title: 'Yoga Flow', start: new Date('2025-03-01T09:00'), end: new Date('2025-03-01T10:00'), color: '#818cf8' },
42
+ { id: '2', title: 'Meditation', start: new Date('2025-03-01T12:00'), end: new Date('2025-03-01T12:45'), color: '#34d399' },
43
+ ];
44
+
45
+ // Adapters provide the data layer (in-memory, REST, etc.)
46
+ const adapter = createMemoryAdapter(events);
47
+
48
+ // Concepts are paired by label — switching Day↔Week preserves the concept
49
+ const views: CalendarView[] = [
50
+ { id: 'day-grid', label: 'Grid', granularity: 'day', component: DayTimeline },
51
+ { id: 'week-grid', label: 'Grid', granularity: 'week', component: WeekGrid },
52
+ { id: 'day-agenda', label: 'Agenda', granularity: 'day', component: DayAgenda },
53
+ { id: 'week-agenda', label: 'Agenda', granularity: 'week', component: WeekAgenda },
54
+ { id: 'week-heatmap', label: 'Heatmap', granularity: 'week', component: WeekHeatmap },
55
+ ];
56
+ </script>
57
+
58
+ <Calendar
59
+ {views}
60
+ {adapter}
61
+ defaultView="week-grid"
62
+ theme={midnight}
63
+ height={600}
64
+ oneventclick={(ev) => console.log('clicked', ev.id)}
65
+ oneventcreate={(range) => console.log('create', range.start, range.end)}
66
+ />
67
+ ```
68
+
69
+ ## Themes
70
+
71
+ Three built-in presets each view reads from the same `--dt-*` CSS custom property contract:
72
+
73
+ | Preset | Tone |
74
+ |--------|------|
75
+ | `midnight` | Dark (deep navy/slate) |
76
+ | `parchment` | Warm light (cream, amber accents) |
77
+ | `indigo` | Cool light (white surface, indigo accents) |
78
+
79
+ ```svelte
80
+ <script>
81
+ import { midnight, parchment, indigo, presets } from '@svelte-calendar/core';
82
+ </script>
83
+
84
+ <!-- Apply directly -->
85
+ <WeekGrid style={midnight} events={events} />
86
+
87
+ <!-- Or use the presets map -->
88
+ <WeekGrid style={presets['parchment']} events={events} />
89
+ ```
90
+
91
+ ### Custom Themes
92
+
93
+ Pass any string of `--dt-*` custom property declarations:
94
+
95
+ ```ts
96
+ const custom = `
97
+ --dt-bg: #0a0a0a;
98
+ --dt-surface: #111;
99
+ --dt-border: rgba(255,255,255,0.06);
100
+ --dt-text: #e0e0e0;
101
+ --dt-accent: #10b981;
102
+ /* ...see presets.ts for the full token list */
103
+ `;
104
+ ```
105
+
106
+ ## Architecture
107
+
108
+ ```
109
+ src/lib/
110
+ +-- core/ # Clock, time utils, locale, types
111
+ +-- engine/ # Reactive state: event-store, view-state, selection, drag
112
+ +-- adapters/ # Data layer: memory adapter, REST adapter
113
+ +-- primitives/ # Low-level UI atoms: NowIndicator, EventBlock, TimeGutter...
114
+ +-- calendar/ # Calendar shell, Toolbar
115
+ +-- views/ # The 6 view components (day/ + week/)
116
+ +-- theme/ # Preset themes and token definitions
117
+ ```
118
+
119
+ ## Engine
120
+
121
+ ```ts
122
+ import {
123
+ createEventStore,
124
+ createViewState,
125
+ createSelection,
126
+ createDragState,
127
+ } from '@svelte-calendar/core';
128
+ ```
129
+
130
+ - **`createEventStore(adapter)`** reactive event list with fetch/add/update/remove
131
+ - **`createViewState(options)`** current view, date range, navigation (prev/next/today)
132
+ - **`createSelection()`** selected event tracking
133
+ - **`createDragState()`** drag-to-create and drag-to-move state machine
134
+
135
+ ## Adapters
136
+
137
+ | Adapter | Use |
138
+ |---------|-----|
139
+ | `createMemoryAdapter(events)` | In-memory great for demos and prototyping |
140
+ | `createRestAdapter(options)` | Fetch from a REST API with configurable endpoints |
141
+
142
+ ## Standalone Views
143
+
144
+ Each view works independently without the Calendar shell:
145
+
146
+ ```svelte
147
+ <!-- Horizontal day timeline -->
148
+ <DayTimeline style={midnight} events={events} />
149
+
150
+ <!-- Vertical day grid with elastic night compression -->
151
+ <DayGrid style={midnight} events={events} height={600} elastic />
152
+
153
+ <!-- Week density heatmap -->
154
+ <WeekHeatmap style={parchment} events={events} height={320} />
155
+ ```
156
+
157
+ ## Development
158
+
159
+ ```bash
160
+ npm install
161
+ npm run dev # SvelteKit dev server (demo app)
162
+ npm run check # Type check
163
+ npm run package # Build the library into dist/
164
+ ```
165
+
166
+ ## License
167
+
168
+ MIT
@@ -0,0 +1,4 @@
1
+ export type { CalendarAdapter, DateRange } from './types.js';
2
+ export { createMemoryAdapter } from './memory.js';
3
+ export { createRestAdapter } from './rest.js';
4
+ export type { RestAdapterOptions } from './rest.js';
@@ -0,0 +1,2 @@
1
+ export { createMemoryAdapter } from './memory.js';
2
+ export { createRestAdapter } from './rest.js';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * In-memory adapter — the default for demos and testing.
3
+ *
4
+ * Events are stored in a plain array. No persistence across page loads.
5
+ * Perfect for prototyping and unit tests.
6
+ *
7
+ * Usage:
8
+ * import { createMemoryAdapter } from './';
9
+ * const adapter = createMemoryAdapter(initialEvents);
10
+ * const store = createEventStore(adapter);
11
+ */
12
+ import type { TimelineEvent } from '../core/types.js';
13
+ import type { CalendarAdapter } from './types.js';
14
+ export declare function createMemoryAdapter(initial?: TimelineEvent[]): CalendarAdapter;
@@ -0,0 +1,33 @@
1
+ let counter = 0;
2
+ function uid() {
3
+ return `mem-${Date.now()}-${++counter}`;
4
+ }
5
+ export function createMemoryAdapter(initial = []) {
6
+ const events = [...initial];
7
+ function overlaps(ev, range) {
8
+ return ev.start < range.end && ev.end > range.start;
9
+ }
10
+ return {
11
+ async fetchEvents(range) {
12
+ return events.filter((ev) => overlaps(ev, range));
13
+ },
14
+ async createEvent(data) {
15
+ const ev = { ...data, id: uid() };
16
+ events.push(ev);
17
+ return ev;
18
+ },
19
+ async updateEvent(id, patch) {
20
+ const idx = events.findIndex((e) => e.id === id);
21
+ if (idx < 0)
22
+ throw new Error(`Event not found: ${id}`);
23
+ events[idx] = { ...events[idx], ...patch, id };
24
+ return events[idx];
25
+ },
26
+ async deleteEvent(id) {
27
+ const idx = events.findIndex((e) => e.id === id);
28
+ if (idx < 0)
29
+ throw new Error(`Event not found: ${id}`);
30
+ events.splice(idx, 1);
31
+ },
32
+ };
33
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * REST adapter skeleton — connect to any REST API.
3
+ *
4
+ * Override the URL builder and response mapper for your API shape.
5
+ * This is a starting point; production use should add auth headers,
6
+ * error handling, retry logic, etc.
7
+ */
8
+ import type { TimelineEvent } from '../core/types.js';
9
+ import type { CalendarAdapter } from './types.js';
10
+ export interface RestAdapterOptions {
11
+ /** Base URL of the API (e.g. "https://api.example.com/v1") */
12
+ baseUrl: string;
13
+ /** Custom headers (e.g. Authorization) */
14
+ headers?: Record<string, string>;
15
+ /** Map API response to TimelineEvent[] */
16
+ mapEvents?: (data: unknown) => TimelineEvent[];
17
+ /** Map API response to a single TimelineEvent */
18
+ mapEvent?: (data: unknown) => TimelineEvent;
19
+ }
20
+ export declare function createRestAdapter(options: RestAdapterOptions): CalendarAdapter;
@@ -0,0 +1,48 @@
1
+ export function createRestAdapter(options) {
2
+ const { baseUrl, headers = {} } = options;
3
+ const mapEvents = options.mapEvents ?? ((data) => data);
4
+ const mapEvent = options.mapEvent ?? ((data) => data);
5
+ async function request(path, init) {
6
+ const res = await fetch(`${baseUrl}${path}`, {
7
+ ...init,
8
+ headers: {
9
+ 'Content-Type': 'application/json',
10
+ ...headers,
11
+ ...(init?.headers ?? {}),
12
+ },
13
+ });
14
+ if (!res.ok) {
15
+ throw new Error(`Calendar API error: ${res.status} ${res.statusText}`);
16
+ }
17
+ if (res.status === 204)
18
+ return undefined;
19
+ return res.json();
20
+ }
21
+ return {
22
+ async fetchEvents(range) {
23
+ const params = new URLSearchParams({
24
+ start: range.start.toISOString(),
25
+ end: range.end.toISOString(),
26
+ });
27
+ const data = await request(`/events?${params}`);
28
+ return mapEvents(data);
29
+ },
30
+ async createEvent(event) {
31
+ const data = await request('/events', {
32
+ method: 'POST',
33
+ body: JSON.stringify(event),
34
+ });
35
+ return mapEvent(data);
36
+ },
37
+ async updateEvent(id, patch) {
38
+ const data = await request(`/events/${id}`, {
39
+ method: 'PATCH',
40
+ body: JSON.stringify(patch),
41
+ });
42
+ return mapEvent(data);
43
+ },
44
+ async deleteEvent(id) {
45
+ await request(`/events/${id}`, { method: 'DELETE' });
46
+ },
47
+ };
48
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * CalendarAdapter — the integration contract.
3
+ *
4
+ * Any data source (REST API, GraphQL, local storage, in-memory)
5
+ * implements this interface. The event store calls these methods
6
+ * and the adapter handles persistence.
7
+ */
8
+ import type { TimelineEvent } from '../core/types.js';
9
+ /** Simple date range for queries */
10
+ export interface DateRange {
11
+ start: Date;
12
+ end: Date;
13
+ }
14
+ export interface CalendarAdapter {
15
+ /** Fetch events that overlap the given date range */
16
+ fetchEvents(range: DateRange): Promise<TimelineEvent[]>;
17
+ /** Create a new event, return it with a server-assigned ID */
18
+ createEvent(event: Omit<TimelineEvent, 'id'>): Promise<TimelineEvent>;
19
+ /** Update an event, return the full updated event */
20
+ updateEvent(id: string, patch: Partial<TimelineEvent>): Promise<TimelineEvent>;
21
+ /** Delete an event by ID */
22
+ deleteEvent(id: string): Promise<void>;
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
@@ -0,0 +1,213 @@
1
+ <!--
2
+ Calendar — the unified orchestrator.
3
+
4
+ Brings together: adapter → event store → view state + selection → toolbar + active view.
5
+ Provides context so any descendant view can read the store/state via getContext().
6
+
7
+ Usage:
8
+ <Calendar
9
+ adapter={myAdapter}
10
+ views={[
11
+ { id: 'week-terrain', label: 'Terrain', component: WeekTimelineV4 },
12
+ { id: 'week-agenda', label: 'Agenda', component: WeekTimelineV5 },
13
+ { id: 'day', label: 'Day', component: DayTimeline },
14
+ ]}
15
+ defaultView="week-terrain"
16
+ theme={presets.midnight}
17
+ height={600}
18
+ oneventclick={handleClick}
19
+ oneventcreate={handleCreate}
20
+ />
21
+ -->
22
+ <script lang="ts">
23
+ import { setContext, untrack, type Component } from 'svelte';
24
+ import type { CalendarAdapter } from '../adapters/types.js';
25
+ import type { CalendarViewId } from '../engine/view-state.svelte.js';
26
+ import { createEventStore, type EventStore } from '../engine/event-store.svelte.js';
27
+ import { createViewState, type ViewState } from '../engine/view-state.svelte.js';
28
+ import { createSelection, type Selection } from '../engine/selection.svelte.js';
29
+ import { createDragState, type DragState } from '../engine/drag.svelte.js';
30
+ import type { TimelineEvent } from '../core/types.js';
31
+ import Toolbar from './Toolbar.svelte';
32
+
33
+ /** One view registration */
34
+ export interface CalendarView {
35
+ id: CalendarViewId;
36
+ label: string;
37
+ /** day or week */
38
+ granularity: 'day' | 'week';
39
+ /** The Svelte component to render */
40
+ component: Component<Record<string, unknown>>;
41
+ /** Extra props to pass through (e.g. hourHeight, specialized settings) */
42
+ props?: Record<string, unknown>;
43
+ }
44
+
45
+ interface Props {
46
+ /** Data adapter (required) */
47
+ adapter: CalendarAdapter;
48
+ /** Registered views */
49
+ views?: CalendarView[];
50
+ /** Initial view */
51
+ defaultView?: CalendarViewId;
52
+ /** CSS theme string (--dt-* inline style) */
53
+ theme?: string;
54
+ /** Start week on Monday */
55
+ mondayStart?: boolean;
56
+ /** Total height */
57
+ height?: number;
58
+ /** Show toolbar */
59
+ showToolbar?: boolean;
60
+ /** Links to display in toolbar */
61
+ links?: { href: string; label: string }[];
62
+
63
+ // ── Callbacks ──
64
+ oneventclick?: (event: TimelineEvent) => void;
65
+ oneventcreate?: (range: { start: Date; end: Date }) => void;
66
+ oneventmove?: (event: TimelineEvent, newStart: Date, newEnd: Date) => void;
67
+ }
68
+
69
+ let {
70
+ adapter,
71
+ views = [],
72
+ defaultView = 'week-grid',
73
+ theme = '',
74
+ mondayStart = true,
75
+ height = 600,
76
+ showToolbar = true,
77
+ links = [],
78
+ oneventclick,
79
+ oneventcreate,
80
+ oneventmove,
81
+ }: Props = $props();
82
+
83
+ // ── Create reactive state ──
84
+ const store: EventStore = $derived(createEventStore(adapter));
85
+ const viewState: ViewState = createViewState(untrack(() => ({
86
+ defaultView,
87
+ mondayStart,
88
+ })));
89
+ const selection: Selection = createSelection();
90
+ const drag: DragState = createDragState();
91
+
92
+ // ── Drag commit handler ──
93
+ // Views call this on pointer-up to process drag results.
94
+ function commitDrag(): void {
95
+ const mode = drag.mode;
96
+ const payload = drag.commit();
97
+ if (!payload) return;
98
+
99
+ if ((mode === 'move' || mode === 'resize-start' || mode === 'resize-end') && payload.eventId) {
100
+ store.move(payload.eventId, payload.start, payload.end);
101
+ const ev = store.byId(payload.eventId);
102
+ if (ev) oneventmove?.(ev, payload.start, payload.end);
103
+ } else if (mode === 'create') {
104
+ oneventcreate?.({ start: payload.start, end: payload.end });
105
+ }
106
+ }
107
+
108
+ // Context is set once at mount; engine objects hold $state internally
109
+ // so consumers reading from them stay reactive automatically.
110
+ // Wrap store in a getter since it's $derived and may change when adapter changes.
111
+ setContext('calendar:store', { get current() { return store; } });
112
+ setContext('calendar:viewState', viewState);
113
+ setContext('calendar:selection', selection);
114
+ setContext('calendar:drag', drag);
115
+ setContext('calendar:commitDrag', commitDrag);
116
+ // Callbacks: provide a getter-based object so context consumers
117
+ // always see the latest callback references.
118
+ setContext('calendar:callbacks', {
119
+ get oneventclick() { return oneventclick; },
120
+ get oneventcreate() { return oneventcreate; },
121
+ get oneventmove() { return oneventmove; },
122
+ });
123
+
124
+ // ── Load events when range changes ──
125
+ $effect(() => {
126
+ const { start, end } = viewState.range;
127
+ store.load({ start, end });
128
+ });
129
+
130
+ // ── Resolve active view ──
131
+ const activeView = $derived(views.find((v) => v.id === viewState.view) ?? views[0]);
132
+
133
+ const toolbarViews = $derived(
134
+ views.map((v) => ({ id: v.id, label: v.label, granularity: v.granularity })),
135
+ );
136
+ </script>
137
+
138
+ <div class="cal" style="{theme}; --cal-h: {height}px">
139
+ {#if showToolbar}
140
+ <Toolbar {viewState} views={toolbarViews} {links} />
141
+ {/if}
142
+
143
+ <div class="cal-body">
144
+ {#if activeView}
145
+ {@const Comp = activeView.component}
146
+ <Comp
147
+ events={store.events}
148
+ style={theme}
149
+ height={height - (showToolbar ? 48 : 0)}
150
+ mondayStart={viewState.mondayStart}
151
+ focusDate={viewState.focusDate}
152
+ oneventclick={oneventclick}
153
+ oneventcreate={oneventcreate}
154
+ selectedEventId={selection.selectedId}
155
+ {...activeView.props ?? {}}
156
+ />
157
+ {:else}
158
+ <div class="cal-empty">No views registered.</div>
159
+ {/if}
160
+ </div>
161
+
162
+ {#if store.loading}
163
+ <div class="cal-loading"></div>
164
+ {/if}
165
+ </div>
166
+
167
+ <style>
168
+ .cal {
169
+ position: relative;
170
+ height: var(--cal-h, 600px);
171
+ background: var(--dt-bg, #0b0e14);
172
+ border-radius: 12px;
173
+ overflow: hidden;
174
+ display: flex;
175
+ flex-direction: column;
176
+ border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
177
+ }
178
+
179
+ .cal-body {
180
+ flex: 1;
181
+ min-height: 0;
182
+ overflow: hidden;
183
+ }
184
+
185
+ .cal-empty {
186
+ display: flex;
187
+ align-items: center;
188
+ justify-content: center;
189
+ height: 100%;
190
+ font: 400 13px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
191
+ color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
192
+ }
193
+
194
+ .cal-loading {
195
+ position: absolute;
196
+ top: 0;
197
+ left: 0;
198
+ right: 0;
199
+ height: 2px;
200
+ background: linear-gradient(
201
+ 90deg,
202
+ transparent 0%,
203
+ var(--dt-accent, #ef4444) 50%,
204
+ transparent 100%
205
+ );
206
+ animation: cal-slide 1.2s ease-in-out infinite;
207
+ }
208
+
209
+ @keyframes cal-slide {
210
+ 0% { transform: translateX(-100%); }
211
+ 100% { transform: translateX(100%); }
212
+ }
213
+ </style>
@@ -0,0 +1,45 @@
1
+ import { type Component } from 'svelte';
2
+ import type { CalendarAdapter } from '../adapters/types.js';
3
+ import type { CalendarViewId } from '../engine/view-state.svelte.js';
4
+ import type { TimelineEvent } from '../core/types.js';
5
+ /** One view registration */
6
+ export interface CalendarView {
7
+ id: CalendarViewId;
8
+ label: string;
9
+ /** day or week */
10
+ granularity: 'day' | 'week';
11
+ /** The Svelte component to render */
12
+ component: Component<Record<string, unknown>>;
13
+ /** Extra props to pass through (e.g. hourHeight, specialized settings) */
14
+ props?: Record<string, unknown>;
15
+ }
16
+ interface Props {
17
+ /** Data adapter (required) */
18
+ adapter: CalendarAdapter;
19
+ /** Registered views */
20
+ views?: CalendarView[];
21
+ /** Initial view */
22
+ defaultView?: CalendarViewId;
23
+ /** CSS theme string (--dt-* inline style) */
24
+ theme?: string;
25
+ /** Start week on Monday */
26
+ mondayStart?: boolean;
27
+ /** Total height */
28
+ height?: number;
29
+ /** Show toolbar */
30
+ showToolbar?: boolean;
31
+ /** Links to display in toolbar */
32
+ links?: {
33
+ href: string;
34
+ label: string;
35
+ }[];
36
+ oneventclick?: (event: TimelineEvent) => void;
37
+ oneventcreate?: (range: {
38
+ start: Date;
39
+ end: Date;
40
+ }) => void;
41
+ oneventmove?: (event: TimelineEvent, newStart: Date, newEnd: Date) => void;
42
+ }
43
+ declare const Calendar: Component<Props, {}, "">;
44
+ type Calendar = ReturnType<typeof Calendar>;
45
+ export default Calendar;