@bimetal/calendar-svelte-components 0.29.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 (87) hide show
  1. package/LICENSE +129 -0
  2. package/dist/binding/config.d.ts +6 -0
  3. package/dist/binding/config.d.ts.map +1 -0
  4. package/dist/binding/config.js +5 -0
  5. package/dist/binding/context.d.ts +31 -0
  6. package/dist/binding/context.d.ts.map +1 -0
  7. package/dist/binding/context.js +55 -0
  8. package/dist/binding/controller.d.ts +24 -0
  9. package/dist/binding/controller.d.ts.map +1 -0
  10. package/dist/binding/controller.js +9 -0
  11. package/dist/binding/index.d.ts +23 -0
  12. package/dist/binding/index.d.ts.map +1 -0
  13. package/dist/binding/index.js +24 -0
  14. package/dist/binding/types.d.ts +95 -0
  15. package/dist/binding/types.d.ts.map +1 -0
  16. package/dist/binding/types.js +11 -0
  17. package/dist/binding/view-configs.d.ts +6 -0
  18. package/dist/binding/view-configs.d.ts.map +1 -0
  19. package/dist/binding/view-configs.js +5 -0
  20. package/dist/components/Calendar.svelte +263 -0
  21. package/dist/components/Calendar.svelte.d.ts +24 -0
  22. package/dist/components/Calendar.svelte.d.ts.map +1 -0
  23. package/dist/components/CalendarHeader.svelte +47 -0
  24. package/dist/components/CalendarHeader.svelte.d.ts +15 -0
  25. package/dist/components/CalendarHeader.svelte.d.ts.map +1 -0
  26. package/dist/components/CategoryFilter.svelte +29 -0
  27. package/dist/components/CategoryFilter.svelte.d.ts +10 -0
  28. package/dist/components/CategoryFilter.svelte.d.ts.map +1 -0
  29. package/dist/components/EventBar.svelte +187 -0
  30. package/dist/components/EventBar.svelte.d.ts +10 -0
  31. package/dist/components/EventBar.svelte.d.ts.map +1 -0
  32. package/dist/components/EventDialog.svelte +312 -0
  33. package/dist/components/EventDialog.svelte.d.ts +15 -0
  34. package/dist/components/EventDialog.svelte.d.ts.map +1 -0
  35. package/dist/components/EventHistory.svelte +89 -0
  36. package/dist/components/EventHistory.svelte.d.ts +10 -0
  37. package/dist/components/EventHistory.svelte.d.ts.map +1 -0
  38. package/dist/components/ResourceFilter.svelte +28 -0
  39. package/dist/components/ResourceFilter.svelte.d.ts +10 -0
  40. package/dist/components/ResourceFilter.svelte.d.ts.map +1 -0
  41. package/dist/components/UndoToast.svelte +47 -0
  42. package/dist/components/UndoToast.svelte.d.ts +4 -0
  43. package/dist/components/UndoToast.svelte.d.ts.map +1 -0
  44. package/dist/index.d.ts +20 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +23 -0
  47. package/dist/styles/index.css +1 -0
  48. package/dist/views/agenda/AgendaEvent.svelte +55 -0
  49. package/dist/views/agenda/AgendaEvent.svelte.d.ts +14 -0
  50. package/dist/views/agenda/AgendaEvent.svelte.d.ts.map +1 -0
  51. package/dist/views/agenda/AgendaView.svelte +155 -0
  52. package/dist/views/agenda/AgendaView.svelte.d.ts +8 -0
  53. package/dist/views/agenda/AgendaView.svelte.d.ts.map +1 -0
  54. package/dist/views/day/AllDayEvent.svelte +49 -0
  55. package/dist/views/day/AllDayEvent.svelte.d.ts +12 -0
  56. package/dist/views/day/AllDayEvent.svelte.d.ts.map +1 -0
  57. package/dist/views/day/DayView.svelte +252 -0
  58. package/dist/views/day/DayView.svelte.d.ts +8 -0
  59. package/dist/views/day/DayView.svelte.d.ts.map +1 -0
  60. package/dist/views/month/MonthView.svelte +66 -0
  61. package/dist/views/month/MonthView.svelte.d.ts +8 -0
  62. package/dist/views/month/MonthView.svelte.d.ts.map +1 -0
  63. package/dist/views/month/TimedEntry.svelte +51 -0
  64. package/dist/views/month/TimedEntry.svelte.d.ts +12 -0
  65. package/dist/views/month/TimedEntry.svelte.d.ts.map +1 -0
  66. package/dist/views/month/WeekRow.svelte +224 -0
  67. package/dist/views/month/WeekRow.svelte.d.ts +12 -0
  68. package/dist/views/month/WeekRow.svelte.d.ts.map +1 -0
  69. package/dist/views/resource/ResourceView.svelte +227 -0
  70. package/dist/views/resource/ResourceView.svelte.d.ts +11 -0
  71. package/dist/views/resource/ResourceView.svelte.d.ts.map +1 -0
  72. package/dist/views/timeline/TimelineView.svelte +178 -0
  73. package/dist/views/timeline/TimelineView.svelte.d.ts +12 -0
  74. package/dist/views/timeline/TimelineView.svelte.d.ts.map +1 -0
  75. package/dist/views/week/AllDayRow.svelte +74 -0
  76. package/dist/views/week/AllDayRow.svelte.d.ts +9 -0
  77. package/dist/views/week/AllDayRow.svelte.d.ts.map +1 -0
  78. package/dist/views/week/TimeEvent.svelte +192 -0
  79. package/dist/views/week/TimeEvent.svelte.d.ts +25 -0
  80. package/dist/views/week/TimeEvent.svelte.d.ts.map +1 -0
  81. package/dist/views/week/WeekView.svelte +232 -0
  82. package/dist/views/week/WeekView.svelte.d.ts +8 -0
  83. package/dist/views/week/WeekView.svelte.d.ts.map +1 -0
  84. package/dist/views/year/YearView.svelte +92 -0
  85. package/dist/views/year/YearView.svelte.d.ts +8 -0
  86. package/dist/views/year/YearView.svelte.d.ts.map +1 -0
  87. package/package.json +56 -0
@@ -0,0 +1,312 @@
1
+ <script lang="ts">
2
+ import type { DialogState, EventDraft } from '@bimetal/calendar-headless';
3
+ import { getCalendarConfig, getCalendarContext } from '../binding/index.js';
4
+ import type { DomainEvent } from '@bimetal/calendar-data';
5
+ import { tick } from 'svelte';
6
+ import { focusTrap } from '@bimetal/svelte';
7
+ import EventHistory from './EventHistory.svelte';
8
+
9
+ let {
10
+ dialog,
11
+ setDraft,
12
+ onSave,
13
+ onDelete,
14
+ onClose,
15
+ }: {
16
+ /** The controller-owned dialog state (draft, flags, validation). */
17
+ dialog: DialogState;
18
+ /** Patch the controlled draft from raw input (controller applies the policy). */
19
+ setDraft: (patch: Partial<EventDraft>) => void;
20
+ /** Assemble + validate + save the held draft (controller owns all of it). */
21
+ onSave: () => void;
22
+ onDelete: (eventId: string) => void;
23
+ onClose: () => void;
24
+ } = $props();
25
+
26
+ const config = getCalendarConfig();
27
+ const ctx = getCalendarContext<{
28
+ getHistory: (entityId: string) => DomainEvent[];
29
+ }>();
30
+
31
+ let dialogEl: HTMLDivElement | undefined = $state();
32
+ let showHistory = $state(false);
33
+
34
+ // The controller OWNS the draft + all flags; this component is fully
35
+ // controlled — no local field state, no assembly, no id minting (B-CAL-2).
36
+ const draft = $derived(dialog.draft);
37
+ const isNew = $derived(dialog.isNew);
38
+ const isInstance = $derived(dialog.isInstance);
39
+ const validationErrors = $derived(dialog.validationErrors);
40
+ const event = $derived(dialog.event);
41
+
42
+ const history = $derived(event ? ctx.getHistory(event.id) : []);
43
+
44
+ function positionDialog() {
45
+ const el = dialogEl;
46
+ if (!el) return;
47
+ const anchorRect = dialog.anchorRect;
48
+ const dialogRect = el.getBoundingClientRect();
49
+ const vw = window.innerWidth;
50
+ const vh = window.innerHeight;
51
+
52
+ let left = anchorRect.right + 8;
53
+ if (left + dialogRect.width > vw - 16) {
54
+ left = anchorRect.left - dialogRect.width - 8;
55
+ }
56
+ if (left < 16) {
57
+ left = Math.max(16, anchorRect.left + anchorRect.width / 2 - dialogRect.width / 2);
58
+ }
59
+
60
+ let top = anchorRect.top;
61
+ if (top + dialogRect.height > vh - 16) {
62
+ top = vh - dialogRect.height - 16;
63
+ }
64
+ if (top < 16) top = 16;
65
+
66
+ el.style.left = `${left}px`;
67
+ el.style.top = `${top}px`;
68
+ }
69
+
70
+ // Reposition whenever the anchor changes (mirrors React's effect on anchorRect).
71
+ $effect(() => {
72
+ void dialog.anchorRect;
73
+ void tick().then(positionDialog);
74
+ });
75
+
76
+ // v0.29 Tastatur: focus lifecycle via the shared `focusTrap` action (contract §3).
77
+ // Escape is element-scoped (no window keydown); focus restores to the opener else the
78
+ // `.calendar` root via the trap's closest()-anchor (the dialog is a `.calendar`
79
+ // descendant). Click-outside becomes the backdrop element's onclick below.
80
+
81
+ function handleKeyDown(e: KeyboardEvent) {
82
+ if (e.key === 'Enter' && !e.shiftKey) {
83
+ e.preventDefault();
84
+ onSave();
85
+ }
86
+ }
87
+ </script>
88
+
89
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
90
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
91
+ <div class="dialog-backdrop" onclick={onClose}></div>
92
+ <!-- svelte-ignore a11y_interactive_supports_focus -->
93
+ <div
94
+ bind:this={dialogEl}
95
+ class="event-dialog"
96
+ role="dialog"
97
+ aria-modal="true"
98
+ aria-label={isNew ? config.locale.newEvent : config.locale.editEvent}
99
+ onkeydown={handleKeyDown}
100
+ use:focusTrap={{ onEscape: onClose }}
101
+ >
102
+ {#if showHistory && event}
103
+ <EventHistory
104
+ eventId={event.id}
105
+ {history}
106
+ onBack={() => { showHistory = false; }}
107
+ />
108
+ {:else}
109
+ <!-- Header -->
110
+ <div class="dialog-header">
111
+ <span class="dialog-title">
112
+ {isNew ? config.locale.newEvent : config.locale.editEvent}
113
+ </span>
114
+ <button class="dialog-close" aria-label={config.locale.close} onclick={onClose}>&times;</button>
115
+ </div>
116
+
117
+ <!-- Body -->
118
+ <div class="dialog-body">
119
+ <input
120
+ class="dialog-input dialog-input--title"
121
+ type="text"
122
+ placeholder={config.locale.titlePlaceholder}
123
+ value={draft.title}
124
+ oninput={(e) => setDraft({ title: (e.target as HTMLInputElement).value })}
125
+ />
126
+
127
+ <label class="dialog-toggle">
128
+ <span>{config.locale.allDay}</span>
129
+ <input
130
+ type="checkbox"
131
+ checked={draft.allDay}
132
+ onchange={(e) => setDraft({ allDay: (e.target as HTMLInputElement).checked })}
133
+ />
134
+ </label>
135
+
136
+ {#if !isInstance}
137
+ <div class="dialog-recurrence">
138
+ <label class="dialog-toggle">
139
+ <span>Wiederholen</span>
140
+ <input
141
+ type="checkbox"
142
+ checked={draft.recurring}
143
+ onchange={(e) => setDraft({ recurring: (e.target as HTMLInputElement).checked })}
144
+ />
145
+ </label>
146
+ {#if draft.recurring}
147
+ <div class="dialog-row" style="gap: 8px; align-items: center;">
148
+ <span style="font-size: 13px;">Alle</span>
149
+ <input class="dialog-input" type="number" min="1" max="99" style="width: 50px;"
150
+ value={draft.recInterval}
151
+ oninput={(e) => setDraft({ recInterval: parseInt((e.target as HTMLInputElement).value) })} />
152
+ <select class="dialog-input"
153
+ value={draft.recFreq}
154
+ onchange={(e) => setDraft({ recFreq: (e.target as HTMLSelectElement).value as EventDraft['recFreq'] })}>
155
+ <option value="daily">Tage</option>
156
+ <option value="weekly">Wochen</option>
157
+ <option value="monthly">Monate</option>
158
+ <option value="yearly">Jahre</option>
159
+ </select>
160
+ <label class="dialog-label" style="flex-direction: row; align-items: center; gap: 6px;">
161
+ <span style="font-size: 13px;">bis</span>
162
+ <input class="dialog-input" type="date"
163
+ value={draft.recUntil}
164
+ onchange={(e) => setDraft({ recUntil: (e.target as HTMLInputElement).value })} />
165
+ </label>
166
+ </div>
167
+ {/if}
168
+ </div>
169
+ {/if}
170
+
171
+ {#if isInstance}
172
+ <div style="font-size: 12px; color: var(--cal-color-text-muted); padding: 4px 0;">
173
+ ↻ Instanz einer wiederkehrenden Serie
174
+ </div>
175
+ {/if}
176
+
177
+ <!-- Start date row -->
178
+ <div class="dialog-row">
179
+ <label class="dialog-label">
180
+ <span>{config.locale.from}</span>
181
+ <input
182
+ class="dialog-input"
183
+ type="date"
184
+ value={draft.startDate}
185
+ onchange={(e) => setDraft({ startDate: (e.target as HTMLInputElement).value })}
186
+ />
187
+ </label>
188
+ {#if !draft.allDay}
189
+ <input
190
+ class="dialog-input dialog-input--time"
191
+ type="time"
192
+ value={draft.startTime}
193
+ onchange={(e) => setDraft({ startTime: (e.target as HTMLInputElement).value })}
194
+ />
195
+ {/if}
196
+ </div>
197
+
198
+ <!-- End date row -->
199
+ <div class="dialog-row">
200
+ <label class="dialog-label">
201
+ <span>{config.locale.to}</span>
202
+ <input
203
+ class="dialog-input"
204
+ type="date"
205
+ value={draft.endDate}
206
+ min={draft.startDate}
207
+ onchange={(e) => setDraft({ endDate: (e.target as HTMLInputElement).value })}
208
+ />
209
+ </label>
210
+ {#if !draft.allDay}
211
+ <input
212
+ class="dialog-input dialog-input--time"
213
+ type="time"
214
+ value={draft.endTime}
215
+ onchange={(e) => setDraft({ endTime: (e.target as HTMLInputElement).value })}
216
+ />
217
+ {/if}
218
+ </div>
219
+
220
+ <!-- Category -->
221
+ <label class="dialog-label">
222
+ <span>{config.locale.category}</span>
223
+ <div class="dialog-category-row">
224
+ <select
225
+ class="dialog-input"
226
+ value={draft.category}
227
+ onchange={(e) => setDraft({ category: (e.target as HTMLSelectElement).value })}
228
+ >
229
+ {#each config.categories as cat (cat.name)}
230
+ <option value={cat.name}>{cat.name}</option>
231
+ {/each}
232
+ </select>
233
+ <span class="category-dot" style:background-color={draft.color}></span>
234
+ </div>
235
+ </label>
236
+
237
+ {#if config.resources.length > 0}
238
+ <label class="dialog-label">
239
+ <span>{config.locale.resources ?? 'Resources'}</span>
240
+ <select
241
+ class="dialog-input"
242
+ value={draft.resourceId}
243
+ onchange={(e) => setDraft({ resourceId: (e.target as HTMLSelectElement).value })}
244
+ >
245
+ <option value="">—</option>
246
+ {#each config.resources as r (r.id)}
247
+ <option value={r.id}>{r.name}</option>
248
+ {/each}
249
+ </select>
250
+ </label>
251
+ {/if}
252
+
253
+ <!-- Location -->
254
+ <label class="dialog-label">
255
+ <span>{config.locale.location}</span>
256
+ <input
257
+ class="dialog-input"
258
+ type="text"
259
+ value={draft.location}
260
+ oninput={(e) => setDraft({ location: (e.target as HTMLInputElement).value })}
261
+ />
262
+ </label>
263
+
264
+ <!-- Description -->
265
+ <label class="dialog-label">
266
+ <span>{config.locale.description}</span>
267
+ <textarea
268
+ class="dialog-input dialog-input--description"
269
+ value={draft.description}
270
+ oninput={(e) => setDraft({ description: (e.target as HTMLTextAreaElement).value })}
271
+ ></textarea>
272
+ </label>
273
+ </div>
274
+
275
+ <!-- Validation errors -->
276
+ {#if validationErrors.length > 0}
277
+ <div class="dialog-errors">
278
+ {#each validationErrors as err, i (i)}
279
+ <div class="dialog-error">{err}</div>
280
+ {/each}
281
+ </div>
282
+ {/if}
283
+
284
+ <!-- Footer -->
285
+ <div class="dialog-footer">
286
+ {#if !isNew}
287
+ {#if config.locale.delete !== false}
288
+ <button class="dialog-btn dialog-btn--delete" onclick={() => onDelete(event!.id)}>
289
+ {config.locale.delete}
290
+ </button>
291
+ {/if}
292
+ {#if history.length > 0}
293
+ <button class="dialog-btn dialog-btn--history" onclick={() => { showHistory = true; }}>
294
+ {config.locale.history}
295
+ </button>
296
+ {/if}
297
+ {/if}
298
+ <div class="dialog-footer-right">
299
+ <button class="dialog-btn dialog-btn--cancel" onclick={onClose}>
300
+ {config.locale.cancel}
301
+ </button>
302
+ <button
303
+ class="dialog-btn dialog-btn--save"
304
+ onclick={onSave}
305
+ disabled={!draft.title.trim()}
306
+ >
307
+ {isNew ? config.locale.create : config.locale.save}
308
+ </button>
309
+ </div>
310
+ </div>
311
+ {/if}
312
+ </div>
@@ -0,0 +1,15 @@
1
+ import type { DialogState, EventDraft } from '@bimetal/calendar-headless';
2
+ type $$ComponentProps = {
3
+ /** The controller-owned dialog state (draft, flags, validation). */
4
+ dialog: DialogState;
5
+ /** Patch the controlled draft from raw input (controller applies the policy). */
6
+ setDraft: (patch: Partial<EventDraft>) => void;
7
+ /** Assemble + validate + save the held draft (controller owns all of it). */
8
+ onSave: () => void;
9
+ onDelete: (eventId: string) => void;
10
+ onClose: () => void;
11
+ };
12
+ declare const EventDialog: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type EventDialog = ReturnType<typeof EventDialog>;
14
+ export default EventDialog;
15
+ //# sourceMappingURL=EventDialog.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventDialog.svelte.d.ts","sourceRoot":"","sources":["../../src/components/EventDialog.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAOzE,KAAK,gBAAgB,GAAI;IACtB,oEAAoE;IACpE,MAAM,EAAE,WAAW,CAAC;IACpB,iFAAiF;IACjF,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;IAC/C,6EAA6E;IAC7E,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAyOJ,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,89 @@
1
+ <script lang="ts">
2
+ import type { DomainEvent } from '@bimetal/calendar-data';
3
+ import { getCalendarConfig } from '../binding/index.js';
4
+
5
+ const eventTypeColors: Record<string, string> = {
6
+ 'bimetal.calendar.CalendarEventCreated': '#4CD964',
7
+ 'bimetal.calendar.CalendarEventUpdated': '#007AFF',
8
+ 'bimetal.calendar.CalendarEventDeleted': '#FF3B30',
9
+ 'bimetal.calendar.CalendarEventReverted': '#FF9500',
10
+ };
11
+
12
+ let {
13
+ eventId,
14
+ history,
15
+ onBack,
16
+ }: {
17
+ eventId: string;
18
+ history: DomainEvent[];
19
+ onBack: () => void;
20
+ } = $props();
21
+
22
+ const config = getCalendarConfig();
23
+ const eventTypeLabels = config.locale.historyEventTypes;
24
+
25
+ function formatTime(timestamp: number): string {
26
+ return new Date(timestamp).toLocaleString(config.locale.code, {
27
+ day: '2-digit', month: '2-digit', year: 'numeric',
28
+ hour: '2-digit', minute: '2-digit',
29
+ });
30
+ }
31
+
32
+ function getSummary(event: DomainEvent): string {
33
+ const payload = event.payload as Record<string, unknown>;
34
+ switch (event.type) {
35
+ case 'bimetal.calendar.CalendarEventCreated': {
36
+ const ev = payload.event as { title?: string };
37
+ return ev?.title ?? '';
38
+ }
39
+ case 'bimetal.calendar.CalendarEventUpdated': {
40
+ const changes = payload.changes as Record<string, unknown> | undefined;
41
+ if (!changes) return '';
42
+ return Object.keys(changes).join(', ');
43
+ }
44
+ default:
45
+ return '';
46
+ }
47
+ }
48
+ </script>
49
+
50
+ <div class="event-history">
51
+ <div class="event-history__header">
52
+ <button class="event-history__back" onclick={onBack}>&larr;</button>
53
+ <span class="event-history__title">{config.locale.historyTitle}</span>
54
+ </div>
55
+
56
+ {#if history.length === 0}
57
+ <div class="event-history__loading">{config.locale.historyLoading}</div>
58
+ {/if}
59
+
60
+ <div class="event-history__timeline">
61
+ {#each history as entry, idx (entry.envelope.id ?? idx)}
62
+ <div class="event-history__entry">
63
+ <div class="event-history__dot-col">
64
+ <div
65
+ class="event-history__dot"
66
+ style:background-color={eventTypeColors[entry.type] ?? '#8E8E93'}
67
+ ></div>
68
+ {#if idx < history.length - 1}
69
+ <div class="event-history__line"></div>
70
+ {/if}
71
+ </div>
72
+ <div class="event-history__content">
73
+ <div class="event-history__entry-header">
74
+ <span
75
+ class="event-history__badge"
76
+ style:background-color={eventTypeColors[entry.type] ?? '#8E8E93'}
77
+ >
78
+ {eventTypeLabels[entry.type] ?? entry.type}
79
+ </span>
80
+ <span class="event-history__time">{formatTime(entry.envelope.timestamp)}</span>
81
+ </div>
82
+ {#if getSummary(entry)}
83
+ <div class="event-history__summary">{getSummary(entry)}</div>
84
+ {/if}
85
+ </div>
86
+ </div>
87
+ {/each}
88
+ </div>
89
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { DomainEvent } from '@bimetal/calendar-data';
2
+ type $$ComponentProps = {
3
+ eventId: string;
4
+ history: DomainEvent[];
5
+ onBack: () => void;
6
+ };
7
+ declare const EventHistory: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type EventHistory = ReturnType<typeof EventHistory>;
9
+ export default EventHistory;
10
+ //# sourceMappingURL=EventHistory.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventHistory.svelte.d.ts","sourceRoot":"","sources":["../../src/components/EventHistory.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGzD,KAAK,gBAAgB,GAAI;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAmFJ,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import type { Resource } from '@bimetal/calendar-core';
3
+
4
+ let {
5
+ resources,
6
+ hiddenResources,
7
+ onToggle,
8
+ }: {
9
+ resources: Resource[];
10
+ hiddenResources: ReadonlySet<string>;
11
+ onToggle: (resourceId: string) => void;
12
+ } = $props();
13
+ </script>
14
+
15
+ {#if resources.length > 0}
16
+ <div class="resource-filter">
17
+ {#each resources as r (r.id)}
18
+ <button
19
+ type="button"
20
+ class="resource-filter__pill{hiddenResources.has(r.id) ? ' resource-filter__pill--hidden' : ''}"
21
+ style:border-left-color={r.color ?? 'transparent'}
22
+ onclick={() => onToggle(r.id)}
23
+ >
24
+ {r.name}
25
+ </button>
26
+ {/each}
27
+ </div>
28
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { Resource } from '@bimetal/calendar-core';
2
+ type $$ComponentProps = {
3
+ resources: Resource[];
4
+ hiddenResources: ReadonlySet<string>;
5
+ onToggle: (resourceId: string) => void;
6
+ };
7
+ declare const ResourceFilter: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type ResourceFilter = ReturnType<typeof ResourceFilter>;
9
+ export default ResourceFilter;
10
+ //# sourceMappingURL=ResourceFilter.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResourceFilter.svelte.d.ts","sourceRoot":"","sources":["../../src/components/ResourceFilter.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEtD,KAAK,gBAAgB,GAAI;IACtB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,CAAC;AAuBJ,QAAA,MAAM,cAAc,sDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import { getCalendarContext, getCalendarConfig } from '../binding/index.js';
3
+ import { DEFAULT_UNDO_DISMISS_MS } from '@bimetal/calendar-headless';
4
+ import { onDestroy } from 'svelte';
5
+
6
+ const ctx = getCalendarContext<{
7
+ lastAction: { entityId: string; description: string } | null;
8
+ onUndo: (entityId: string) => Promise<void>;
9
+ dismissUndo: () => void;
10
+ }>();
11
+ const config = getCalendarConfig();
12
+
13
+ let timer: ReturnType<typeof setTimeout> | null = null;
14
+ let prevAction: typeof ctx.lastAction = null;
15
+
16
+ $effect(() => {
17
+ const action = ctx.lastAction;
18
+ if (action !== prevAction) {
19
+ prevAction = action;
20
+ if (timer) clearTimeout(timer);
21
+ if (action) {
22
+ timer = setTimeout(() => ctx.dismissUndo(), DEFAULT_UNDO_DISMISS_MS);
23
+ }
24
+ }
25
+ });
26
+
27
+ onDestroy(() => {
28
+ if (timer) clearTimeout(timer);
29
+ });
30
+
31
+ async function handleUndo() {
32
+ const action = ctx.lastAction;
33
+ if (!action) return;
34
+ await ctx.onUndo(action.entityId);
35
+ ctx.dismissUndo();
36
+ }
37
+ </script>
38
+
39
+ {#if ctx.lastAction}
40
+ <div class="undo-toast" role="status">
41
+ <span class="undo-toast__text">{ctx.lastAction.description}</span>
42
+ <button class="undo-toast__btn" onclick={handleUndo}>
43
+ {config.locale.undo ?? 'Undo'}
44
+ </button>
45
+ <button class="undo-toast__dismiss" onclick={() => ctx.dismissUndo()}>&times;</button>
46
+ </div>
47
+ {/if}
@@ -0,0 +1,4 @@
1
+ declare const UndoToast: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type UndoToast = ReturnType<typeof UndoToast>;
3
+ export default UndoToast;
4
+ //# sourceMappingURL=UndoToast.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UndoToast.svelte.d.ts","sourceRoot":"","sources":["../../src/components/UndoToast.svelte.ts"],"names":[],"mappings":"AAyDA,QAAA,MAAM,SAAS,2DAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,20 @@
1
+ export { default as Calendar } from './components/Calendar.svelte';
2
+ export { default as CalendarHeader } from './components/CalendarHeader.svelte';
3
+ export { default as CategoryFilter } from './components/CategoryFilter.svelte';
4
+ export { default as EventBar } from './components/EventBar.svelte';
5
+ export { default as EventDialog } from './components/EventDialog.svelte';
6
+ export { default as EventHistory } from './components/EventHistory.svelte';
7
+ export { default as UndoToast } from './components/UndoToast.svelte';
8
+ export { default as MonthView } from './views/month/MonthView.svelte';
9
+ export { default as WeekRow } from './views/month/WeekRow.svelte';
10
+ export { default as WeekView } from './views/week/WeekView.svelte';
11
+ export { default as AllDayRow } from './views/week/AllDayRow.svelte';
12
+ export { default as TimeEvent } from './views/week/TimeEvent.svelte';
13
+ export { default as DayView } from './views/day/DayView.svelte';
14
+ export { default as AgendaView } from './views/agenda/AgendaView.svelte';
15
+ export { default as YearView } from './views/year/YearView.svelte';
16
+ export { default as ResourceView } from './views/resource/ResourceView.svelte';
17
+ export { default as TimelineView } from './views/timeline/TimelineView.svelte';
18
+ export * from './binding/index.js';
19
+ export * from '@bimetal/svelte';
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAG/E,cAAc,oBAAoB,CAAC;AAKnC,cAAc,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ export { default as Calendar } from './components/Calendar.svelte';
2
+ export { default as CalendarHeader } from './components/CalendarHeader.svelte';
3
+ export { default as CategoryFilter } from './components/CategoryFilter.svelte';
4
+ export { default as EventBar } from './components/EventBar.svelte';
5
+ export { default as EventDialog } from './components/EventDialog.svelte';
6
+ export { default as EventHistory } from './components/EventHistory.svelte';
7
+ export { default as UndoToast } from './components/UndoToast.svelte';
8
+ export { default as MonthView } from './views/month/MonthView.svelte';
9
+ export { default as WeekRow } from './views/month/WeekRow.svelte';
10
+ export { default as WeekView } from './views/week/WeekView.svelte';
11
+ export { default as AllDayRow } from './views/week/AllDayRow.svelte';
12
+ export { default as TimeEvent } from './views/week/TimeEvent.svelte';
13
+ export { default as DayView } from './views/day/DayView.svelte';
14
+ export { default as AgendaView } from './views/agenda/AgendaView.svelte';
15
+ export { default as YearView } from './views/year/YearView.svelte';
16
+ export { default as ResourceView } from './views/resource/ResourceView.svelte';
17
+ export { default as TimelineView } from './views/timeline/TimelineView.svelte';
18
+ // ── Calendar Svelte binding (context/config/controller binding) ──
19
+ export * from './binding/index.js';
20
+ // Re-export the adapter package so consumers get providers/stores/types from a
21
+ // single dependency. Notably required for the "Calendar-Orchester selbst
22
+ // aufziehen"-Pfad (see doc/03-cookbook/07).
23
+ export * from '@bimetal/svelte';
@@ -0,0 +1 @@
1
+ @import '@bimetal/calendar-themes';
@@ -0,0 +1,55 @@
1
+ <script lang="ts">
2
+ import type { CalendarEvent } from '@bimetal/calendar-core';
3
+ import { getRecurrenceInstanceMetadata } from '@bimetal/calendar-core';
4
+ import { getEventSlots } from '../../binding/index.js';
5
+
6
+ let {
7
+ ev,
8
+ timeLabel,
9
+ category,
10
+ isSelected,
11
+ onEntryMouseDown,
12
+ onEntryClick,
13
+ onEntryDoubleClick,
14
+ }: {
15
+ ev: CalendarEvent;
16
+ timeLabel: string;
17
+ category: string;
18
+ isSelected: boolean;
19
+ onEntryMouseDown: () => void;
20
+ onEntryClick: (e: MouseEvent, id: string, instanceDate?: number) => void;
21
+ onEntryDoubleClick: (e: MouseEvent, id: string, instanceDate?: number) => void;
22
+ } = $props();
23
+
24
+ const slots = $derived(getEventSlots(ev));
25
+ const renderedTitle = $derived(slots.title ?? ev.title);
26
+ const extraClass = $derived(slots.className ? ' ' + slots.className : '');
27
+ const slotStyleStr = $derived(slots.style
28
+ ? Object.entries(slots.style).map(([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}: ${v}`).join('; ')
29
+ : '');
30
+ </script>
31
+
32
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
33
+ <div
34
+ class="agenda-view__event{isSelected ? ' agenda-view__event--selected' : ''}{extraClass}"
35
+ style={slotStyleStr}
36
+ role="button"
37
+ tabindex="0"
38
+ onmousedown={onEntryMouseDown}
39
+ onclick={(e) => onEntryClick(e, ev.id, getRecurrenceInstanceMetadata(ev)?.instanceDate)}
40
+ ondblclick={(e) => onEntryDoubleClick(e, ev.id, getRecurrenceInstanceMetadata(ev)?.instanceDate)}
41
+ >
42
+ {#if slots.content}
43
+ {#if typeof slots.content === 'string'}{slots.content}{:else}{@render slots.content()}{/if}
44
+ {:else}
45
+ <div class="agenda-view__event-color" style:background-color={ev.metadata.color ?? '#007AFF'}></div>
46
+ <div class="agenda-view__event-content">
47
+ <div class="agenda-view__event-title">{renderedTitle}</div>
48
+ <div class="agenda-view__event-time">{timeLabel}</div>
49
+ </div>
50
+ <div class="agenda-view__event-calendar">{category}</div>
51
+ {/if}
52
+ {#if slots.badges}
53
+ {#if typeof slots.badges === 'string'}{slots.badges}{:else}{@render slots.badges()}{/if}
54
+ {/if}
55
+ </div>
@@ -0,0 +1,14 @@
1
+ import type { CalendarEvent } from '@bimetal/calendar-core';
2
+ type $$ComponentProps = {
3
+ ev: CalendarEvent;
4
+ timeLabel: string;
5
+ category: string;
6
+ isSelected: boolean;
7
+ onEntryMouseDown: () => void;
8
+ onEntryClick: (e: MouseEvent, id: string, instanceDate?: number) => void;
9
+ onEntryDoubleClick: (e: MouseEvent, id: string, instanceDate?: number) => void;
10
+ };
11
+ declare const AgendaEvent: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type AgendaEvent = ReturnType<typeof AgendaEvent>;
13
+ export default AgendaEvent;
14
+ //# sourceMappingURL=AgendaEvent.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgendaEvent.svelte.d.ts","sourceRoot":"","sources":["../../../src/views/agenda/AgendaEvent.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAI3D,KAAK,gBAAgB,GAAI;IACtB,EAAE,EAAE,aAAa,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACzE,kBAAkB,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAChF,CAAC;AA2CJ,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}