@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,263 @@
1
+ <script lang="ts">
2
+ import type { CalendarDateTime } from '@bimetal/core';
3
+ import { getYear, getMonth } from '@bimetal/core';
4
+ import type { CalendarStore } from '@bimetal/calendar-data';
5
+ import type { CalendarRule } from '@bimetal/calendar-rules';
6
+ import { parseDragInfo } from '@bimetal/calendar-headless';
7
+ import {
8
+ setCalendarContext,
9
+ setCalendarConfig,
10
+ createCalendarControllerBinding,
11
+ } from '../binding/index.js';
12
+ import type {
13
+ CalendarConfig, CalendarConfigInput, CustomViewDefinition, EventDialogMode, RenderEventFn, AnchorRect,
14
+ } from '../binding/index.js';
15
+ import { onDestroy, untrack } from 'svelte';
16
+ import CalendarHeader from './CalendarHeader.svelte';
17
+ import EventDialog from './EventDialog.svelte';
18
+ import UndoToast from './UndoToast.svelte';
19
+ import MonthView from '../views/month/MonthView.svelte';
20
+ import WeekView from '../views/week/WeekView.svelte';
21
+ import DayView from '../views/day/DayView.svelte';
22
+ import AgendaView from '../views/agenda/AgendaView.svelte';
23
+ import YearView from '../views/year/YearView.svelte';
24
+ import CategoryFilter from './CategoryFilter.svelte';
25
+ import ResourceFilter from './ResourceFilter.svelte';
26
+ import ResourceView from '../views/resource/ResourceView.svelte';
27
+ import TimelineView from '../views/timeline/TimelineView.svelte';
28
+
29
+ let {
30
+ store,
31
+ rules = undefined,
32
+ config: configInput = undefined,
33
+ darkMode = false,
34
+ showCategoryFilter = false,
35
+ showResourceFilter = false,
36
+ customViews = undefined,
37
+ eventDialog = { kind: 'builtin' },
38
+ onEventClick: consumerOnEventClick = undefined,
39
+ onNewEventRequested: consumerOnNewEventRequested = undefined,
40
+ renderEvent = undefined,
41
+ onError = undefined,
42
+ }: {
43
+ store: CalendarStore;
44
+ rules?: CalendarRule[];
45
+ config?: CalendarConfigInput;
46
+ darkMode?: boolean;
47
+ showCategoryFilter?: boolean;
48
+ showResourceFilter?: boolean;
49
+ customViews?: readonly CustomViewDefinition[];
50
+ eventDialog?: EventDialogMode;
51
+ onEventClick?: (eventId: string, anchorRect: AnchorRect, instanceDate?: number) => void;
52
+ onNewEventRequested?: (defaultDate: CalendarDateTime, anchorRect: AnchorRect, defaultResourceId?: string) => void;
53
+ renderEvent?: RenderEventFn;
54
+ /** Controller error channel: domain rejections surface as toasts; any other
55
+ * error (e.g. ConcurrencyError) is passed here instead of rejecting. */
56
+ onError?: (err: unknown) => void;
57
+ } = $props();
58
+
59
+ // ── Canonical controller binding ──────────────────────────────
60
+ // The controller owns ALL interaction state and semantics. This component is
61
+ // a thin bridge: it subscribes to the snapshot, forwards intents, and parses
62
+ // DOM events into DragInfo. The controller is created ONCE (under `untrack`):
63
+ // `store` is immutable after mount — runes + `untrack` deliberately do NOT
64
+ // recreate it when the `store` prop changes; remount the component (e.g. via
65
+ // `{#key store}`) to switch stores. config/rules/customViews/eventDialog are
66
+ // captured here only as the initial value and then pushed reactively via the
67
+ // `$effect`s below, so the initial-value reads are deliberate.
68
+ const binding = untrack(() => createCalendarControllerBinding({
69
+ store,
70
+ config: configInput,
71
+ rules,
72
+ customViews,
73
+ eventDialogKind: eventDialog.kind,
74
+ // The consumer's `none`-mode callbacks are read fresh on each fire, so they
75
+ // never go stale even though the controller is created once.
76
+ onEventClickExternal: (id, rect, inst) => consumerOnEventClick?.(id, rect, inst),
77
+ onNewEventRequestedExternal: (date, rect, rid) => consumerOnNewEventRequested?.(date, rect, rid),
78
+ onError: (err) => onError?.(err),
79
+ }));
80
+ const ctrl = binding.ctrl;
81
+
82
+ // `$binding` auto-subscribes via the store contract; the snapshot is
83
+ // identity-stable until an intent changes something.
84
+ const snapshot = $derived($binding);
85
+
86
+ // ── Reactive option updates (config is reactive, not mount-captured) ──
87
+ $effect(() => { ctrl.setConfig(configInput); });
88
+ $effect(() => { ctrl.setRules(rules); });
89
+ $effect(() => { ctrl.setCustomViews(customViews); });
90
+ // F8: dialog mode is a dynamic prop — keep the controller in sync after mount.
91
+ $effect(() => { ctrl.setEventDialogKind(eventDialog.kind); });
92
+
93
+ onDestroy(() => binding.destroy());
94
+
95
+ // ── v0.29 Tastatur: root-scoped keydown → controller (Ctrl/Cmd+Z undo, Escape
96
+ // clears selection). Bound on the `.calendar` root below (onkeydown) — NOT a window
97
+ // listener (no document-level routing). Semantics live in the controller. ──
98
+ function onKey(e: KeyboardEvent) {
99
+ ctrl.handleKeydown(e);
100
+ }
101
+
102
+ // ── DOM-event → controller-intent bindings (the only place DOM is parsed) ──
103
+ function handleDropEvent(e: DragEvent, dropDate: CalendarDateTime) {
104
+ const info = parseDragInfo(e.dataTransfer);
105
+ if (info) void ctrl.dropAllDay(info, dropDate);
106
+ }
107
+ function handleDropTimedEvent(e: DragEvent, dropDate: CalendarDateTime, dropTime: string) {
108
+ const info = parseDragInfo(e.dataTransfer);
109
+ if (info) void ctrl.dropTimed(info, dropDate, dropTime);
110
+ }
111
+ function handleResourceDrop(e: DragEvent, dropDate: CalendarDateTime, dropTime: string, resourceId: string) {
112
+ const info = parseDragInfo(e.dataTransfer);
113
+ if (info) void ctrl.dropResource(info, dropDate, dropTime, resourceId);
114
+ }
115
+ function handleTimelineDrop(e: DragEvent, dropDate: CalendarDateTime, targetResourceId: string) {
116
+ const info = parseDragInfo(e.dataTransfer);
117
+ if (info) void ctrl.dropTimeline(info, dropDate, targetResourceId);
118
+ }
119
+
120
+ function handleResourceCellClick(date: CalendarDateTime, time: string, resourceId: string, anchorRect: AnchorRect) {
121
+ ctrl.openCreateFromResourceCell(date, time, resourceId, anchorRect);
122
+ }
123
+ function handleTimelineCellClick(date: CalendarDateTime, resourceId: string, anchorRect: AnchorRect) {
124
+ ctrl.openCreate(date, anchorRect, resourceId);
125
+ }
126
+
127
+ // ── Calendar context (shape UNCHANGED so child views need no changes) ──
128
+ // Getters read straight off the reactive snapshot, so views stay live.
129
+ // F9: config is provided via a reactive `$state` holder kept in sync with the
130
+ // snapshot. Child views hold a stable reference (`getCalendarConfig()`) and
131
+ // read its properties in their templates, so a post-mount `setConfig`
132
+ // (locale/timezone/granularity) now propagates — matching React/Vue, instead
133
+ // of freezing the mount-time value under `untrack`.
134
+ const configHolder = $state<CalendarConfig>(untrack(() => ({ ...snapshot.config })));
135
+ $effect(() => { Object.assign(configHolder, snapshot.config); });
136
+ setCalendarConfig(configHolder);
137
+ setCalendarContext({
138
+ get events() { return [...snapshot.events]; },
139
+ get view() { return snapshot.currentView; },
140
+ // B-CAL-1: das gezogene Event mit bereits neu gebautem timeRange. Views
141
+ // tauschen es per id ein, rekonstruieren nichts. `null` ohne Drag.
142
+ get previewEvent() { return snapshot.previewEvent; },
143
+ // B-CAL-3: die hover-expandierte Monatszelle (Tag-epochMs) + ihre Intents,
144
+ // Eigentum des Controllers — das Binding hält KEIN expand-`$state`.
145
+ get expandedMonthDay() { return snapshot.expandedMonthDay; },
146
+ onExpandMonthCell: (dayEpochMs: number) => ctrl.expandMonthCell(dayEpochMs),
147
+ onCollapseMonthCell: () => ctrl.collapseMonthCell(),
148
+ onEventClick: (eventId: string, anchorRect: AnchorRect, instanceDate?: number) => ctrl.openEdit(eventId, anchorRect, instanceDate),
149
+ onDayCellClick: (date: CalendarDateTime, anchorRect: AnchorRect) => ctrl.openCreate(date, anchorRect),
150
+ onDropEvent: handleDropEvent,
151
+ onDropTimedEvent: handleDropTimedEvent,
152
+ onDragOverDate: (date: CalendarDateTime) => ctrl.updatePreview(date),
153
+ onDragOverDateTime: (date: CalendarDateTime, time: string) => ctrl.updateTimedPreview(date, time),
154
+ onDragStart: ctrl.startDrag,
155
+ onHidePreview: () => ctrl.hidePreview(),
156
+ onDragEnd: () => ctrl.endDrag(),
157
+ get selectedEventId() { return snapshot.selection?.eventId ?? null; },
158
+ get selectedInstanceDate() { return snapshot.selection?.instanceDate; },
159
+ onSelectEvent: (eventId: string, instanceDate?: number) => ctrl.selectEvent(eventId, instanceDate),
160
+ onClearSelection: () => ctrl.clearSelection(),
161
+ onUndo: (entityId: string) => ctrl.undo(entityId),
162
+ getHistory: (entityId: string) => ctrl.getHistory(entityId),
163
+ get lastAction() { return snapshot.lastAction; },
164
+ dismissUndo: () => ctrl.dismissUndo(),
165
+ get renderEvent() { return renderEvent; },
166
+ });
167
+ </script>
168
+
169
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
170
+ <div class="calendar" class:dark={darkMode} onkeydown={onKey}>
171
+ <CalendarHeader
172
+ title={snapshot.title}
173
+ currentView={snapshot.currentView}
174
+ viewConfigs={[...snapshot.viewConfigList]}
175
+ onPrev={() => ctrl.prev()}
176
+ onNext={() => ctrl.next()}
177
+ onToday={() => ctrl.today()}
178
+ onViewChange={(v) => ctrl.setView(v)}
179
+ />
180
+
181
+ {#if showCategoryFilter && snapshot.config.categories.length > 0}
182
+ <CategoryFilter
183
+ categories={snapshot.config.categories}
184
+ hiddenCategories={snapshot.hiddenCategories}
185
+ onToggle={(name) => ctrl.toggleCategory(name)}
186
+ />
187
+ {/if}
188
+
189
+ {#if showResourceFilter && snapshot.config.resources.length > 0}
190
+ <ResourceFilter
191
+ resources={snapshot.config.resources}
192
+ hiddenResources={snapshot.hiddenResources}
193
+ onToggle={(id) => ctrl.toggleResource(id)}
194
+ />
195
+ {/if}
196
+
197
+ {#if snapshot.currentView === 'year'}
198
+ <YearView year={getYear(snapshot.currentDate)} onMonthClick={(y, m) => ctrl.goToMonth(y, m)} />
199
+ {:else if snapshot.currentView === 'month'}
200
+ <MonthView year={getYear(snapshot.currentDate)} month={getMonth(snapshot.currentDate)} />
201
+ {:else if snapshot.currentView === 'week'}
202
+ <WeekView currentDate={snapshot.currentDate} />
203
+ {:else if snapshot.currentView === 'day'}
204
+ <DayView currentDate={snapshot.currentDate} />
205
+ {:else if snapshot.currentView === 'agenda'}
206
+ <AgendaView currentDate={snapshot.currentDate} />
207
+ {:else if snapshot.currentView === 'resource'}
208
+ <ResourceView
209
+ currentDate={snapshot.currentDate}
210
+ visibleResources={snapshot.visibleResources}
211
+ onResourceCellClick={handleResourceCellClick}
212
+ onResourceDrop={handleResourceDrop}
213
+ />
214
+ {:else if snapshot.currentView === 'timeline'}
215
+ <TimelineView
216
+ currentDate={snapshot.currentDate}
217
+ days={snapshot.config.core.timelineDays ?? 7}
218
+ visibleResources={snapshot.visibleResources}
219
+ onTimelineCellClick={handleTimelineCellClick}
220
+ onTimelineDrop={handleTimelineDrop}
221
+ />
222
+ {:else if customViews && customViews.find(v => v.id === snapshot.currentView)}
223
+ {@const custom = customViews.find(v => v.id === snapshot.currentView)!}
224
+ {@const CustomComp = custom.component}
225
+ <CustomComp
226
+ currentDate={snapshot.currentDate}
227
+ events={snapshot.events}
228
+ config={snapshot.config}
229
+ onNewEvent={(d, anchorRect) => ctrl.openCreate(d, anchorRect ?? new DOMRect(0, 0, 0, 0))}
230
+ onEventClick={(eventId, anchorRect, instanceDate) => ctrl.openEdit(eventId, anchorRect, instanceDate)}
231
+ selectedEventId={snapshot.selection?.eventId ?? null}
232
+ selectedInstanceDate={snapshot.selection?.instanceDate}
233
+ onSelectEvent={(eventId, instanceDate) => ctrl.selectEvent(eventId, instanceDate)}
234
+ onClearSelection={() => ctrl.clearSelection()}
235
+ />
236
+ {/if}
237
+
238
+ {#if snapshot.dialog && eventDialog.kind === 'builtin'}
239
+ <EventDialog
240
+ dialog={snapshot.dialog}
241
+ setDraft={(patch) => ctrl.setDraft(patch)}
242
+ onSave={() => void ctrl.saveDialogFromInput()}
243
+ onDelete={(id) => ctrl.deleteDialog(id)}
244
+ onClose={() => ctrl.closeDialog()}
245
+ />
246
+ {/if}
247
+
248
+ {#if snapshot.dialog && eventDialog.kind === 'custom'}
249
+ {@const CustomDialog = eventDialog.component}
250
+ <CustomDialog
251
+ event={snapshot.dialog.event}
252
+ defaultDate={snapshot.dialog.defaultDate}
253
+ anchorRect={snapshot.dialog.anchorRect}
254
+ defaultResourceId={snapshot.dialog.defaultResourceId}
255
+ config={snapshot.config}
256
+ onSave={(evt) => ctrl.saveDialog(evt)}
257
+ onDelete={(id) => ctrl.deleteDialog(id)}
258
+ onClose={() => ctrl.closeDialog()}
259
+ />
260
+ {/if}
261
+
262
+ <UndoToast />
263
+ </div>
@@ -0,0 +1,24 @@
1
+ import type { CalendarDateTime } from '@bimetal/core';
2
+ import type { CalendarStore } from '@bimetal/calendar-data';
3
+ import type { CalendarRule } from '@bimetal/calendar-rules';
4
+ import type { CalendarConfigInput, CustomViewDefinition, EventDialogMode, RenderEventFn, AnchorRect } from '../binding/index.js';
5
+ type $$ComponentProps = {
6
+ store: CalendarStore;
7
+ rules?: CalendarRule[];
8
+ config?: CalendarConfigInput;
9
+ darkMode?: boolean;
10
+ showCategoryFilter?: boolean;
11
+ showResourceFilter?: boolean;
12
+ customViews?: readonly CustomViewDefinition[];
13
+ eventDialog?: EventDialogMode;
14
+ onEventClick?: (eventId: string, anchorRect: AnchorRect, instanceDate?: number) => void;
15
+ onNewEventRequested?: (defaultDate: CalendarDateTime, anchorRect: AnchorRect, defaultResourceId?: string) => void;
16
+ renderEvent?: RenderEventFn;
17
+ /** Controller error channel: domain rejections surface as toasts; any other
18
+ * error (e.g. ConcurrencyError) is passed here instead of rejecting. */
19
+ onError?: (err: unknown) => void;
20
+ };
21
+ declare const Calendar: import("svelte").Component<$$ComponentProps, {}, "">;
22
+ type Calendar = ReturnType<typeof Calendar>;
23
+ export default Calendar;
24
+ //# sourceMappingURL=Calendar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Calendar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/Calendar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAO5D,OAAO,KAAK,EACQ,mBAAmB,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EACtG,MAAM,qBAAqB,CAAC;AAe9B,KAAK,gBAAgB,GAAI;IACtB,KAAK,EAAE,aAAa,CAAC;IACrB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC9C,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACxF,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,UAAU,EAAE,iBAAiB,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClH,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B;6EACyE;IACzE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAClC,CAAC;AAkMJ,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import type { ViewType } from '@bimetal/calendar-core';
3
+ import type { ViewConfig } from '../binding/index.js';
4
+ import { getCalendarConfig } from '../binding/index.js';
5
+
6
+ let {
7
+ title,
8
+ currentView,
9
+ viewConfigs,
10
+ onPrev,
11
+ onNext,
12
+ onToday,
13
+ onViewChange,
14
+ }: {
15
+ title: string;
16
+ currentView: ViewType;
17
+ viewConfigs: ViewConfig[];
18
+ onPrev: () => void;
19
+ onNext: () => void;
20
+ onToday: () => void;
21
+ onViewChange: (view: ViewType) => void;
22
+ } = $props();
23
+
24
+ const config = getCalendarConfig();
25
+ </script>
26
+
27
+ <div class="calendar-header">
28
+ <h1 class="month-title">{title}</h1>
29
+ <div class="header-controls">
30
+ <div class="view-switcher">
31
+ {#each viewConfigs as vc (vc.type)}
32
+ <button
33
+ class="view-btn"
34
+ class:view-btn--active={currentView === vc.type}
35
+ onclick={() => onViewChange(vc.type)}
36
+ >
37
+ {vc.label}
38
+ </button>
39
+ {/each}
40
+ </div>
41
+ <div class="nav-controls">
42
+ <button class="nav-btn" aria-label={config.locale.prev} onclick={onPrev}>&lsaquo;</button>
43
+ <button class="today-btn" onclick={onToday}>{config.locale.today}</button>
44
+ <button class="nav-btn" aria-label={config.locale.next} onclick={onNext}>&rsaquo;</button>
45
+ </div>
46
+ </div>
47
+ </div>
@@ -0,0 +1,15 @@
1
+ import type { ViewType } from '@bimetal/calendar-core';
2
+ import type { ViewConfig } from '../binding/index.js';
3
+ type $$ComponentProps = {
4
+ title: string;
5
+ currentView: ViewType;
6
+ viewConfigs: ViewConfig[];
7
+ onPrev: () => void;
8
+ onNext: () => void;
9
+ onToday: () => void;
10
+ onViewChange: (view: ViewType) => void;
11
+ };
12
+ declare const CalendarHeader: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type CalendarHeader = ReturnType<typeof CalendarHeader>;
14
+ export default CalendarHeader;
15
+ //# sourceMappingURL=CalendarHeader.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CalendarHeader.svelte.d.ts","sourceRoot":"","sources":["../../src/components/CalendarHeader.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGrD,KAAK,gBAAgB,GAAI;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,QAAQ,CAAC;IACtB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;CACxC,CAAC;AAuCJ,QAAA,MAAM,cAAc,sDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import type { Category } from '../binding/index.js';
3
+
4
+ let {
5
+ categories,
6
+ hiddenCategories,
7
+ onToggle,
8
+ }: {
9
+ categories: Category[];
10
+ hiddenCategories: ReadonlySet<string>;
11
+ onToggle: (name: string) => void;
12
+ } = $props();
13
+ </script>
14
+
15
+ {#if categories.length > 0}
16
+ <div class="category-filter">
17
+ {#each categories as cat (cat.name)}
18
+ {@const isHidden = hiddenCategories.has(cat.name)}
19
+ <button
20
+ class="category-filter__pill{isHidden ? ' category-filter__pill--hidden' : ''}"
21
+ style:background-color={cat.color}
22
+ onclick={() => onToggle(cat.name)}
23
+ type="button"
24
+ >
25
+ {cat.name}
26
+ </button>
27
+ {/each}
28
+ </div>
29
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { Category } from '../binding/index.js';
2
+ type $$ComponentProps = {
3
+ categories: Category[];
4
+ hiddenCategories: ReadonlySet<string>;
5
+ onToggle: (name: string) => void;
6
+ };
7
+ declare const CategoryFilter: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type CategoryFilter = ReturnType<typeof CategoryFilter>;
9
+ export default CategoryFilter;
10
+ //# sourceMappingURL=CategoryFilter.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CategoryFilter.svelte.d.ts","sourceRoot":"","sources":["../../src/components/CategoryFilter.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEnD,KAAK,gBAAgB,GAAI;IACtB,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAwBJ,QAAA,MAAM,cAAc,sDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,187 @@
1
+ <script lang="ts">
2
+ import type { CalendarDateTime, LayoutItem } from '@bimetal/calendar-core';
3
+ import { daysBetween, getRecurrenceInstanceMetadata, isExpandedRecurrenceInstance } from '@bimetal/calendar-core';
4
+ import { getCalendarContext, getCalendarConfig, buildDragInfo, getEventSlots, toTimeStr } from '../binding/index.js';
5
+ import type { DragInfo } from '../binding/index.js';
6
+ import { createClickIntent, writeCalendarDragData, installTransparentDragImage, eventBarWantsDarkText } from '@bimetal/calendar-headless';
7
+
8
+ let {
9
+ item,
10
+ weekDates,
11
+ ghost = false,
12
+ }: {
13
+ item: LayoutItem;
14
+ weekDates: CalendarDateTime[];
15
+ ghost?: boolean;
16
+ } = $props();
17
+
18
+ const ctx = getCalendarContext<{
19
+ onEventClick: (eventId: string, anchorRect: DOMRect, instanceDate?: number) => void;
20
+ onSelectEvent: (eventId: string, instanceDate?: number) => void;
21
+ selectedEventId: string | null;
22
+ selectedInstanceDate: number | undefined;
23
+ onDragStart: (info: DragInfo) => void;
24
+ onDragEnd: () => void;
25
+ }>();
26
+ const config = getCalendarConfig();
27
+
28
+ const click = createClickIntent();
29
+
30
+ function handleMouseDown() {
31
+ click.markMouseDown();
32
+ }
33
+
34
+ function handleDragStart(e: DragEvent) {
35
+ click.markDragged();
36
+ const { segment, dayIndex, daySpan } = item;
37
+ const { event } = segment;
38
+ const bar = e.currentTarget as HTMLElement;
39
+ const barRect = bar.getBoundingClientRect();
40
+ const colWidth = barRect.width / daySpan;
41
+ const grabColInBar = Math.min(
42
+ Math.max(0, Math.floor((e.clientX - barRect.left) / colWidth)),
43
+ daySpan - 1,
44
+ );
45
+
46
+ const grabbedDate = weekDates[dayIndex + grabColInBar];
47
+ const offset = daysBetween(event.timeRange.start, grabbedDate);
48
+
49
+ const info = buildDragInfo(event, 'move', { dayOffset: offset });
50
+ writeCalendarDragData(e.dataTransfer!, info);
51
+ ctx.onDragStart(info);
52
+ }
53
+
54
+ function handleResizeDragStart(e: DragEvent, edge: 'start' | 'end') {
55
+ e.stopPropagation();
56
+ click.markDragged();
57
+ installTransparentDragImage(e.dataTransfer!);
58
+ const { event } = item.segment;
59
+ const info = buildDragInfo(event, 'resize', { edge });
60
+ writeCalendarDragData(e.dataTransfer!, info);
61
+ ctx.onDragStart(info);
62
+ }
63
+
64
+ function handleClick(e: MouseEvent) {
65
+ e.stopPropagation();
66
+ if (!click.accept(config.clickThresholdMs)) return;
67
+ const instanceDate = getRecurrenceInstanceMetadata(item.segment.event)?.instanceDate;
68
+ ctx.onSelectEvent(item.segment.event.id, instanceDate);
69
+ }
70
+
71
+ function handleDoubleClick(e: MouseEvent) {
72
+ e.stopPropagation();
73
+ if (!click.accept(config.clickThresholdMs)) return;
74
+ const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
75
+ const instanceDate = getRecurrenceInstanceMetadata(item.segment.event)?.instanceDate;
76
+ ctx.onEventClick(item.segment.event.id, rect, instanceDate);
77
+ }
78
+
79
+ const segment = $derived(item.segment);
80
+ const event = $derived(segment.event);
81
+ const isStart = $derived(segment.isStart);
82
+ const isEnd = $derived(segment.isEnd);
83
+ const startCol = $derived(item.dayIndex);
84
+ const span = $derived(item.daySpan);
85
+ const lane = $derived(item.column);
86
+ const isCompact = $derived(span === 1 && isStart && isEnd && !event.allDay);
87
+ const isBar = $derived(!isCompact);
88
+ const color = $derived(event.metadata.color ?? '#007AFF');
89
+ const isRecurring = $derived(isExpandedRecurrenceInstance(event) || !!event.recurrence);
90
+ const ghostClass = $derived(ghost ? ' event-bar--ghost' : '');
91
+ const instanceDate = $derived(getRecurrenceInstanceMetadata(event)?.instanceDate);
92
+ const isSelected = $derived(ctx.selectedEventId === event.id && ctx.selectedInstanceDate === instanceDate);
93
+ const selectedClass = $derived(isSelected ? ' event-bar--selected' : '');
94
+
95
+ const gridStyle = $derived(`grid-column: ${startCol + 1} / span ${span}; grid-row: ${lane + 1};`);
96
+ const barStyle = $derived(
97
+ isCompact
98
+ ? gridStyle
99
+ : `${gridStyle} background-color: ${color}; border-radius: ${isStart ? 4 : 0}px ${isEnd ? 4 : 0}px ${isEnd ? 4 : 0}px ${isStart ? 4 : 0}px;`
100
+ );
101
+
102
+ const timeStr = $derived(toTimeStr(event.timeRange.start));
103
+
104
+ const slots = $derived(getEventSlots(event));
105
+ const renderedTitle = $derived(slots.title ?? event.title);
106
+ const extraClass = $derived(slots.className ? ' ' + slots.className : '');
107
+ const slotStyle = $derived(slots.style
108
+ ? Object.entries(slots.style).map(([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}: ${v}`).join('; ')
109
+ : '');
110
+ const fullStyle = $derived(slotStyle ? `${barStyle} ${slotStyle}` : barStyle);
111
+ </script>
112
+
113
+ {#if isCompact}
114
+ <div
115
+ class="event-bar event-bar--single{ghostClass}{selectedClass}{extraClass}"
116
+ style={fullStyle}
117
+ draggable={!ghost}
118
+ role="button"
119
+ tabindex="0"
120
+ onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleClick(e as any); }}
121
+ onmousedown={handleMouseDown}
122
+ ondragstart={handleDragStart}
123
+ ondragend={ctx.onDragEnd}
124
+ onclick={handleClick}
125
+ ondblclick={handleDoubleClick}
126
+ title={renderedTitle}
127
+ >
128
+ {#if slots.content}
129
+ {#if typeof slots.content === 'string'}{slots.content}{:else}{@render slots.content()}{/if}
130
+ {:else}
131
+ <span class="event-dot" style:background-color={color}></span>
132
+ <span class="event-time">{timeStr}</span>
133
+ <span class="event-title event-title--dark">{isRecurring ? '↻ ' : ''}{renderedTitle}</span>
134
+ {/if}
135
+ {#if slots.badges}
136
+ {#if typeof slots.badges === 'string'}{slots.badges}{:else}{@render slots.badges()}{/if}
137
+ {/if}
138
+ </div>
139
+ {:else}
140
+ <div
141
+ class="event-bar event-bar--multi{ghostClass}{selectedClass}{extraClass}"
142
+ style={fullStyle}
143
+ draggable={!ghost}
144
+ role="button"
145
+ tabindex="0"
146
+ onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleClick(e as any); }}
147
+ onmousedown={handleMouseDown}
148
+ ondragstart={handleDragStart}
149
+ ondragend={ctx.onDragEnd}
150
+ onclick={handleClick}
151
+ ondblclick={handleDoubleClick}
152
+ title={renderedTitle}
153
+ >
154
+ {#if isBar && isStart && !ghost}
155
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions a11y_no_noninteractive_tabindex a11y_no_static_element_interactions -->
156
+ <div
157
+ class="resize-handle resize-handle--start"
158
+ role="separator"
159
+ aria-label="Resize start"
160
+ draggable={true}
161
+ ondragstart={(e) => handleResizeDragStart(e, 'start')}
162
+ ondragend={ctx.onDragEnd}
163
+ onmousedown={(e) => e.stopPropagation()}
164
+ ></div>
165
+ {/if}
166
+ {#if slots.content}
167
+ {#if typeof slots.content === 'string'}{slots.content}{:else}{@render slots.content()}{/if}
168
+ {:else}
169
+ <span class="event-title{eventBarWantsDarkText(color) ? ' event-title--on-light' : ''}">{isRecurring ? '↻ ' : ''}{renderedTitle}</span>
170
+ {/if}
171
+ {#if slots.badges}
172
+ {#if typeof slots.badges === 'string'}{slots.badges}{:else}{@render slots.badges()}{/if}
173
+ {/if}
174
+ {#if isBar && isEnd && !ghost}
175
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions a11y_no_noninteractive_tabindex a11y_no_static_element_interactions -->
176
+ <div
177
+ class="resize-handle resize-handle--end"
178
+ role="separator"
179
+ aria-label="Resize end"
180
+ draggable={true}
181
+ ondragstart={(e) => handleResizeDragStart(e, 'end')}
182
+ ondragend={ctx.onDragEnd}
183
+ onmousedown={(e) => e.stopPropagation()}
184
+ ></div>
185
+ {/if}
186
+ </div>
187
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { CalendarDateTime, LayoutItem } from '@bimetal/calendar-core';
2
+ type $$ComponentProps = {
3
+ item: LayoutItem;
4
+ weekDates: CalendarDateTime[];
5
+ ghost?: boolean;
6
+ };
7
+ declare const EventBar: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type EventBar = ReturnType<typeof EventBar>;
9
+ export default EventBar;
10
+ //# sourceMappingURL=EventBar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventBar.svelte.d.ts","sourceRoot":"","sources":["../../src/components/EventBar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAM1E,KAAK,gBAAgB,GAAI;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAiJJ,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}