@bimetal/calendar-svelte-components 0.29.0 → 0.32.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 (32) hide show
  1. package/dist/components/Calendar.svelte +64 -31
  2. package/dist/components/Calendar.svelte.d.ts +14 -2
  3. package/dist/components/Calendar.svelte.d.ts.map +1 -1
  4. package/dist/components/CalendarHeader.svelte +10 -10
  5. package/dist/components/CategoryFilter.svelte +2 -2
  6. package/dist/components/EventBar.svelte +16 -12
  7. package/dist/components/EventBar.svelte.d.ts.map +1 -1
  8. package/dist/components/EventDialog.svelte +50 -50
  9. package/dist/components/EventHistory.svelte +17 -21
  10. package/dist/components/EventHistory.svelte.d.ts.map +1 -1
  11. package/dist/components/ResourceFilter.svelte +2 -2
  12. package/dist/components/UndoToast.svelte +5 -5
  13. package/dist/views/agenda/AgendaEvent.svelte +14 -13
  14. package/dist/views/agenda/AgendaEvent.svelte.d.ts +2 -1
  15. package/dist/views/agenda/AgendaEvent.svelte.d.ts.map +1 -1
  16. package/dist/views/agenda/AgendaView.svelte +57 -115
  17. package/dist/views/agenda/AgendaView.svelte.d.ts +3 -2
  18. package/dist/views/agenda/AgendaView.svelte.d.ts.map +1 -1
  19. package/dist/views/day/AllDayEvent.svelte +1 -1
  20. package/dist/views/day/DayView.svelte +16 -16
  21. package/dist/views/month/MonthView.svelte +4 -4
  22. package/dist/views/month/TimedEntry.svelte +10 -4
  23. package/dist/views/month/TimedEntry.svelte.d.ts.map +1 -1
  24. package/dist/views/month/WeekRow.svelte +13 -13
  25. package/dist/views/resource/ResourceView.svelte +18 -18
  26. package/dist/views/timeline/TimelineView.svelte +13 -13
  27. package/dist/views/week/AllDayRow.svelte +3 -3
  28. package/dist/views/week/TimeEvent.svelte +18 -7
  29. package/dist/views/week/TimeEvent.svelte.d.ts.map +1 -1
  30. package/dist/views/week/WeekView.svelte +15 -15
  31. package/dist/views/year/YearView.svelte +12 -12
  32. package/package.json +9 -8
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { DomainEvent } from '@bimetal/calendar-data';
3
+ import { formatHistoryTimestamp } from '@bimetal/calendar-core';
3
4
  import { getCalendarConfig } from '../binding/index.js';
4
5
 
5
6
  const eventTypeColors: Record<string, string> = {
@@ -22,12 +23,7 @@
22
23
  const config = getCalendarConfig();
23
24
  const eventTypeLabels = config.locale.historyEventTypes;
24
25
 
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
- }
26
+ const formatTime = (timestamp: number): string => formatHistoryTimestamp(timestamp, config.locale.code);
31
27
 
32
28
  function getSummary(event: DomainEvent): string {
33
29
  const payload = event.payload as Record<string, unknown>;
@@ -47,40 +43,40 @@
47
43
  }
48
44
  </script>
49
45
 
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>
46
+ <div class="bm-cal-event-history">
47
+ <div class="bm-cal-event-history__header">
48
+ <button class="bm-cal-event-history__back" onclick={onBack}>&larr;</button>
49
+ <span class="bm-cal-event-history__title">{config.locale.historyTitle}</span>
54
50
  </div>
55
51
 
56
52
  {#if history.length === 0}
57
- <div class="event-history__loading">{config.locale.historyLoading}</div>
53
+ <div class="bm-cal-event-history__loading">{config.locale.historyLoading}</div>
58
54
  {/if}
59
55
 
60
- <div class="event-history__timeline">
56
+ <div class="bm-cal-event-history__timeline">
61
57
  {#each history as entry, idx (entry.envelope.id ?? idx)}
62
- <div class="event-history__entry">
63
- <div class="event-history__dot-col">
58
+ <div class="bm-cal-event-history__entry">
59
+ <div class="bm-cal-event-history__dot-col">
64
60
  <div
65
- class="event-history__dot"
61
+ class="bm-cal-event-history__dot"
66
62
  style:background-color={eventTypeColors[entry.type] ?? '#8E8E93'}
67
63
  ></div>
68
64
  {#if idx < history.length - 1}
69
- <div class="event-history__line"></div>
65
+ <div class="bm-cal-event-history__line"></div>
70
66
  {/if}
71
67
  </div>
72
- <div class="event-history__content">
73
- <div class="event-history__entry-header">
68
+ <div class="bm-cal-event-history__content">
69
+ <div class="bm-cal-event-history__entry-header">
74
70
  <span
75
- class="event-history__badge"
71
+ class="bm-cal-event-history__badge"
76
72
  style:background-color={eventTypeColors[entry.type] ?? '#8E8E93'}
77
73
  >
78
74
  {eventTypeLabels[entry.type] ?? entry.type}
79
75
  </span>
80
- <span class="event-history__time">{formatTime(entry.envelope.timestamp)}</span>
76
+ <span class="bm-cal-event-history__time">{formatTime(entry.envelope.timestamp)}</span>
81
77
  </div>
82
78
  {#if getSummary(entry)}
83
- <div class="event-history__summary">{getSummary(entry)}</div>
79
+ <div class="bm-cal-event-history__summary">{getSummary(entry)}</div>
84
80
  {/if}
85
81
  </div>
86
82
  </div>
@@ -1 +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"}
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;AAIzD,KAAK,gBAAgB,GAAI;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AA+EJ,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -13,11 +13,11 @@
13
13
  </script>
14
14
 
15
15
  {#if resources.length > 0}
16
- <div class="resource-filter">
16
+ <div class="bm-cal-resource-filter">
17
17
  {#each resources as r (r.id)}
18
18
  <button
19
19
  type="button"
20
- class="resource-filter__pill{hiddenResources.has(r.id) ? ' resource-filter__pill--hidden' : ''}"
20
+ class="bm-cal-resource-filter__pill{hiddenResources.has(r.id) ? ' bm-cal-resource-filter__pill--hidden' : ''}"
21
21
  style:border-left-color={r.color ?? 'transparent'}
22
22
  onclick={() => onToggle(r.id)}
23
23
  >
@@ -37,11 +37,11 @@
37
37
  </script>
38
38
 
39
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'}
40
+ <div class="bm-cal__toast" role="status">
41
+ <span class="bm-cal__toast-text">{ctx.lastAction.description}</span>
42
+ <button class="bm-cal__toast-btn" onclick={handleUndo}>
43
+ {config.locale.undo}
44
44
  </button>
45
- <button class="undo-toast__dismiss" onclick={() => ctx.dismissUndo()}>&times;</button>
45
+ <button class="bm-cal__toast-dismiss" onclick={() => ctx.dismissUndo()}>&times;</button>
46
46
  </div>
47
47
  {/if}
@@ -1,28 +1,29 @@
1
1
  <script lang="ts">
2
2
  import type { CalendarEvent } from '@bimetal/calendar-core';
3
- import { getRecurrenceInstanceMetadata } from '@bimetal/calendar-core';
4
3
  import { getEventSlots } from '../../binding/index.js';
5
4
 
6
5
  let {
7
- ev,
6
+ event,
8
7
  timeLabel,
9
8
  category,
10
9
  isSelected,
10
+ instanceDate,
11
11
  onEntryMouseDown,
12
12
  onEntryClick,
13
13
  onEntryDoubleClick,
14
14
  }: {
15
- ev: CalendarEvent;
15
+ event: CalendarEvent;
16
16
  timeLabel: string;
17
17
  category: string;
18
18
  isSelected: boolean;
19
+ instanceDate?: number;
19
20
  onEntryMouseDown: () => void;
20
21
  onEntryClick: (e: MouseEvent, id: string, instanceDate?: number) => void;
21
22
  onEntryDoubleClick: (e: MouseEvent, id: string, instanceDate?: number) => void;
22
23
  } = $props();
23
24
 
24
- const slots = $derived(getEventSlots(ev));
25
- const renderedTitle = $derived(slots.title ?? ev.title);
25
+ const slots = $derived(getEventSlots(event));
26
+ const renderedTitle = $derived(slots.title ?? event.title);
26
27
  const extraClass = $derived(slots.className ? ' ' + slots.className : '');
27
28
  const slotStyleStr = $derived(slots.style
28
29
  ? Object.entries(slots.style).map(([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}: ${v}`).join('; ')
@@ -31,23 +32,23 @@
31
32
 
32
33
  <!-- svelte-ignore a11y_click_events_have_key_events -->
33
34
  <div
34
- class="agenda-view__event{isSelected ? ' agenda-view__event--selected' : ''}{extraClass}"
35
+ class="bm-cal-agenda__row bm-cal-agenda__event{isSelected ? ' bm-cal-agenda__event--selected' : ''}{extraClass}"
35
36
  style={slotStyleStr}
36
37
  role="button"
37
38
  tabindex="0"
38
39
  onmousedown={onEntryMouseDown}
39
- onclick={(e) => onEntryClick(e, ev.id, getRecurrenceInstanceMetadata(ev)?.instanceDate)}
40
- ondblclick={(e) => onEntryDoubleClick(e, ev.id, getRecurrenceInstanceMetadata(ev)?.instanceDate)}
40
+ onclick={(e) => onEntryClick(e, event.id, instanceDate)}
41
+ ondblclick={(e) => onEntryDoubleClick(e, event.id, instanceDate)}
41
42
  >
42
43
  {#if slots.content}
43
44
  {#if typeof slots.content === 'string'}{slots.content}{:else}{@render slots.content()}{/if}
44
45
  {: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>
46
+ <div class="bm-cal-agenda__event-color" style:background-color={event.metadata.color ?? '#007AFF'}></div>
47
+ <div class="bm-cal-agenda__event-content">
48
+ <div class="bm-cal-agenda__event-title">{renderedTitle}</div>
49
+ <div class="bm-cal-agenda__event-time">{timeLabel}</div>
49
50
  </div>
50
- <div class="agenda-view__event-calendar">{category}</div>
51
+ <div class="bm-cal-agenda__event-calendar">{category}</div>
51
52
  {/if}
52
53
  {#if slots.badges}
53
54
  {#if typeof slots.badges === 'string'}{slots.badges}{:else}{@render slots.badges()}{/if}
@@ -1,9 +1,10 @@
1
1
  import type { CalendarEvent } from '@bimetal/calendar-core';
2
2
  type $$ComponentProps = {
3
- ev: CalendarEvent;
3
+ event: CalendarEvent;
4
4
  timeLabel: string;
5
5
  category: string;
6
6
  isSelected: boolean;
7
+ instanceDate?: number;
7
8
  onEntryMouseDown: () => void;
8
9
  onEntryClick: (e: MouseEvent, id: string, instanceDate?: number) => void;
9
10
  onEntryDoubleClick: (e: MouseEvent, id: string, instanceDate?: number) => void;
@@ -1 +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"}
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;AAG3D,KAAK,gBAAgB,GAAI;IACtB,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,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"}
@@ -1,25 +1,25 @@
1
1
  <script lang="ts">
2
- import type { CalendarEvent, CalendarDateTime } from '@bimetal/calendar-core';
3
- import {
4
- addDays, isSameDay, startOfDay, isBefore, isAfter,
5
- getDayOfMonth,
6
- createTimeRange, overlaps, formatDateTime, now,
7
- getRecurrenceInstanceMetadata, isSingleDayAllDay,
8
- } from '@bimetal/calendar-core';
9
- import { getCalendarContext, getCalendarConfig, toDateStr, toTimeStr } from '../../binding/index.js';
10
- import { DEFAULT_AGENDA_RANGE_DAYS, createClickIntent } from '@bimetal/calendar-headless';
2
+ import type { CalendarEvent } from '@bimetal/calendar-core';
3
+ import type { AgendaWindow } from '@bimetal/calendar-headless';
4
+ import { createClickIntent } from '@bimetal/calendar-headless';
5
+ import { getCalendarContext, getCalendarConfig } from '../../binding/index.js';
11
6
  import AgendaEvent from './AgendaEvent.svelte';
12
7
 
13
- interface DayGroup {
14
- date: CalendarDateTime;
15
- dateStr: string;
16
- events: CalendarEvent[];
17
- }
8
+ /**
9
+ * The agenda view — a WINDOWED flat list (v0.30 Slice 5b). The controller owns the
10
+ * whole derivation: it flattens the day-grouped agenda to uniform-height rows
11
+ * (header | event), formats every time label, computes selection, and windows the
12
+ * heavy build to the visible slice. This binding only renders `agenda.rows` + the
13
+ * px spacers and reports the scroll container's metrics via `setViewport` — no
14
+ * grouping, no formatting, no window math here.
15
+ */
18
16
 
19
17
  let {
20
- currentDate,
18
+ agenda,
19
+ setViewport,
21
20
  }: {
22
- currentDate: CalendarDateTime;
21
+ agenda: AgendaWindow;
22
+ setViewport: (scrollTop: number, viewportHeight: number) => void;
23
23
  } = $props();
24
24
 
25
25
  const config = getCalendarConfig();
@@ -27,60 +27,29 @@
27
27
  events: CalendarEvent[];
28
28
  onEventClick: (eventId: string, anchorRect: DOMRect, instanceDate?: number) => void;
29
29
  onSelectEvent: (eventId: string, instanceDate?: number) => void;
30
- selectedEventId: string | null;
31
- selectedInstanceDate: number | undefined;
32
30
  }>();
33
31
 
34
32
  const click = createClickIntent();
35
33
 
36
- const rangeStartDt = $derived(startOfDay(currentDate));
37
- const rangeEndDt = $derived(addDays(rangeStartDt, DEFAULT_AGENDA_RANGE_DAYS));
38
- const agendaRange = $derived(createTimeRange(rangeStartDt, rangeEndDt));
39
-
40
- const currentNow = $derived(now(config.core.timezone, config.core.clock));
41
-
42
- const dayGroups = $derived.by((): DayGroup[] => {
43
- const groups = new Map<string, CalendarEvent[]>();
44
-
45
- for (const ev of ctx.events) {
46
- if (!overlaps(ev.timeRange, agendaRange)) continue;
47
-
48
- const evStart = isBefore(ev.timeRange.start, rangeStartDt) ? rangeStartDt : ev.timeRange.start;
49
- const evEnd = isAfter(ev.timeRange.end, rangeEndDt) ? rangeEndDt : ev.timeRange.end;
50
-
51
- let d = startOfDay(evStart);
52
- const end = startOfDay(evEnd);
53
- const limit = addDays(end, 1);
54
- while (isBefore(d, limit) && isBefore(d, rangeEndDt)) {
55
- const ds = toDateStr(d);
56
- const list = groups.get(ds) || [];
57
- list.push(ev);
58
- groups.set(ds, list);
59
- d = addDays(d, 1);
60
- }
61
- }
62
-
63
- const result: DayGroup[] = [];
64
- for (let i = 0; i < DEFAULT_AGENDA_RANGE_DAYS; i++) {
65
- const date = addDays(rangeStartDt, i);
66
- const dateStr = toDateStr(date);
67
- const dayEvents = groups.get(dateStr);
68
- if (dayEvents && dayEvents.length > 0) {
69
- dayEvents.sort((a, b) => {
70
- if (a.allDay !== b.allDay) return a.allDay ? -1 : 1;
71
- return a.timeRange.start.epochMs - b.timeRange.start.epochMs;
72
- });
73
- result.push({ date, dateStr, events: dayEvents });
74
- }
75
- }
76
- return result;
34
+ let scrollEl: HTMLDivElement | undefined = $state();
35
+
36
+ // Report the scroll container's metrics so the controller windows the rows.
37
+ $effect(() => {
38
+ const el = scrollEl;
39
+ if (!el) return;
40
+ const report = () => setViewport(el.scrollTop, el.clientHeight);
41
+ report();
42
+ el.addEventListener('scroll', report, { passive: true });
43
+ const ro = new ResizeObserver(report);
44
+ ro.observe(el);
45
+ return () => { el.removeEventListener('scroll', report); ro.disconnect(); };
77
46
  });
78
47
 
79
48
  function handleMouseDown() {
80
49
  click.markMouseDown();
81
50
  }
82
51
 
83
- function handleEventClick(e: MouseEvent, eventId: string, instanceDate?: number) {
52
+ function handleEventClick(_e: MouseEvent, eventId: string, instanceDate?: number) {
84
53
  if (!click.accept(config.clickThresholdMs)) return;
85
54
  ctx.onSelectEvent(eventId, instanceDate);
86
55
  }
@@ -90,66 +59,39 @@
90
59
  const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
91
60
  ctx.onEventClick(eventId, rect, instanceDate);
92
61
  }
93
-
94
- function isEventSelected(ev: CalendarEvent): boolean {
95
- const id = getRecurrenceInstanceMetadata(ev)?.instanceDate;
96
- return ctx.selectedEventId === ev.id && ctx.selectedInstanceDate === id;
97
- }
98
-
99
- function formatEventTime(ev: CalendarEvent, date: CalendarDateTime): string {
100
- if (ev.allDay) {
101
- if (isSingleDayAllDay(ev.timeRange)) {
102
- return config.locale.allDayEvent;
103
- }
104
- const startStr = formatDateTime(ev.timeRange.start, { locale: config.locale.code, dateStyle: 'short' });
105
- const endStr = formatDateTime(addDays(ev.timeRange.end, -1), { locale: config.locale.code, dateStyle: 'short' });
106
- return `${config.locale.allDayEvent} (${startStr} \u2013 ${endStr})`;
107
- }
108
- const isFirst = isSameDay(ev.timeRange.start, date);
109
- const isLast = isSameDay(ev.timeRange.end, date);
110
- const startTime = toTimeStr(ev.timeRange.start);
111
- const endTime = toTimeStr(ev.timeRange.end);
112
- if (isFirst && isLast) return `${startTime} \u2013 ${endTime}`;
113
- if (isFirst) return `${config.locale.timeFrom} ${startTime}`;
114
- if (isLast) return `${config.locale.timeUntil} ${endTime}`;
115
- return config.locale.allDayEvent;
116
- }
117
62
  </script>
118
63
 
119
- <div class="agenda-view">
120
- {#if dayGroups.length === 0}
121
- <div class="agenda-view__empty">{config.locale.noEvents}</div>
64
+ <div class="bm-cal-agenda" bind:this={scrollEl}>
65
+ {#if agenda.empty}
66
+ <div class="bm-cal-agenda__empty">{config.locale.noEvents}</div>
122
67
  {:else}
123
- {#each dayGroups as group (group.dateStr)}
124
- {@const isGroupToday = isSameDay(group.date, currentNow)}
125
- <div class="agenda-view__day">
126
- <div class="agenda-view__date-header{isGroupToday ? ' agenda-view__date-header--today' : ''}">
127
- <span class="agenda-view__date-number{isGroupToday ? ' agenda-view__date-number--today' : ''}">
128
- {getDayOfMonth(group.date)}
129
- </span>
130
- <div class="agenda-view__date-text">
131
- <span class="agenda-view__date-weekday">
132
- {formatDateTime(group.date, { locale: config.locale.code, dateStyle: 'full' }).split(',')[0]}
133
- </span>
134
- <span class="agenda-view__date-full">
135
- {formatDateTime(group.date, { locale: config.locale.code, dateStyle: 'long' }).replace(/\d+\.?\s*/, '')}
136
- </span>
68
+ {#if agenda.offsetTop > 0}
69
+ <div class="bm-cal-agenda__spacer" style:height={`${agenda.offsetTop}px`} aria-hidden="true"></div>
70
+ {/if}
71
+ {#each agenda.rows as row (row.key)}
72
+ {#if row.kind === 'header'}
73
+ <div class="bm-cal-agenda__row bm-cal-agenda__row--header{row.isToday ? ' bm-cal-agenda__row--today' : ''}">
74
+ <span class="bm-cal-agenda__date-number">{row.dayOfMonth}</span>
75
+ <div class="bm-cal-agenda__date-text">
76
+ <span class="bm-cal-agenda__date-weekday">{row.weekday}</span>
77
+ <span class="bm-cal-agenda__date-full">{row.dateFull}</span>
137
78
  </div>
138
79
  </div>
139
- <div class="agenda-view__events">
140
- {#each group.events as ev (`${ev.id}-${group.dateStr}`)}
141
- <AgendaEvent
142
- {ev}
143
- timeLabel={formatEventTime(ev, group.date)}
144
- category={(ev.metadata.category as string | undefined) ?? 'default'}
145
- isSelected={isEventSelected(ev)}
146
- onEntryMouseDown={handleMouseDown}
147
- onEntryClick={handleEventClick}
148
- onEntryDoubleClick={handleEventDoubleClick}
149
- />
150
- {/each}
151
- </div>
152
- </div>
80
+ {:else}
81
+ <AgendaEvent
82
+ event={row.event}
83
+ timeLabel={row.timeLabel}
84
+ category={row.category}
85
+ isSelected={row.isSelected}
86
+ instanceDate={row.instanceDate ?? undefined}
87
+ onEntryMouseDown={handleMouseDown}
88
+ onEntryClick={handleEventClick}
89
+ onEntryDoubleClick={handleEventDoubleClick}
90
+ />
91
+ {/if}
153
92
  {/each}
93
+ {#if agenda.offsetBottom > 0}
94
+ <div class="bm-cal-agenda__spacer" style:height={`${agenda.offsetBottom}px`} aria-hidden="true"></div>
95
+ {/if}
154
96
  {/if}
155
97
  </div>
@@ -1,6 +1,7 @@
1
- import type { CalendarDateTime } from '@bimetal/calendar-core';
1
+ import type { AgendaWindow } from '@bimetal/calendar-headless';
2
2
  type $$ComponentProps = {
3
- currentDate: CalendarDateTime;
3
+ agenda: AgendaWindow;
4
+ setViewport: (scrollTop: number, viewportHeight: number) => void;
4
5
  };
5
6
  declare const AgendaView: import("svelte").Component<$$ComponentProps, {}, "">;
6
7
  type AgendaView = ReturnType<typeof AgendaView>;
@@ -1 +1 @@
1
- {"version":3,"file":"AgendaView.svelte.d.ts","sourceRoot":"","sources":["../../../src/views/agenda/AgendaView.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAiB7E,KAAK,gBAAgB,GAAI;IACtB,WAAW,EAAE,gBAAgB,CAAC;CAC/B,CAAC;AA2IJ,QAAA,MAAM,UAAU,sDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"AgendaView.svelte.d.ts","sourceRoot":"","sources":["../../../src/views/agenda/AgendaView.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAK9D,KAAK,gBAAgB,GAAI;IACtB,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;CAClE,CAAC;AAyFJ,QAAA,MAAM,UAAU,sDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -29,7 +29,7 @@
29
29
 
30
30
  <!-- svelte-ignore a11y_click_events_have_key_events -->
31
31
  <div
32
- class="day-view__allday-event{isSelected ? ' day-view__allday-event--selected' : ''}{extraClass}"
32
+ class="bm-cal-day__allday-event{isSelected ? ' bm-cal-day__allday-event--selected' : ''}{extraClass}"
33
33
  style={fullStyle}
34
34
  role="button"
35
35
  tabindex="0"
@@ -118,14 +118,14 @@
118
118
 
119
119
  function handleGridClick(e: MouseEvent) {
120
120
  if (!click.accept(config.clickThresholdMs)) return;
121
- if ((e.target as HTMLElement).closest('.time-event')) return;
121
+ if ((e.target as HTMLElement).closest('.bm-cal-time-event')) return;
122
122
  // Single click on empty grid deselects; create-dialog moves to dblclick.
123
123
  if (ctx.selectedEventId !== null) ctx.onClearSelection();
124
124
  }
125
125
 
126
126
  function handleGridDoubleClick(e: MouseEvent) {
127
127
  if (!click.accept(config.clickThresholdMs)) return;
128
- if ((e.target as HTMLElement).closest('.time-event')) return;
128
+ if ((e.target as HTMLElement).closest('.bm-cal-time-event')) return;
129
129
  const time = getTimeFromMouseY(e.clientY);
130
130
  // Tag + gesnapte Zeit über den geteilten Parser (kein split, wie WeekView).
131
131
  const clickedDt = parseDateTimeStr(toDateStr(currentDate), time, currentDate.timezone);
@@ -156,20 +156,20 @@
156
156
  }
157
157
  </script>
158
158
 
159
- <div class="day-view">
159
+ <div class="bm-cal-day">
160
160
  <!-- Header -->
161
- <div class="day-view__header">
162
- <span class="day-view__day-name">{dayName}</span>
163
- <span class="day-view__day-number{isToday ? ' day-view__day-number--today' : ''}">
161
+ <div class="bm-cal-day__header">
162
+ <span class="bm-cal-day__day-name">{dayName}</span>
163
+ <span class="bm-cal-day__day-number{isToday ? ' bm-cal-day__day-number--today' : ''}">
164
164
  {getDayOfMonth(currentDate)}
165
165
  </span>
166
166
  </div>
167
167
 
168
168
  <!-- All-day events -->
169
169
  {#if allDayEvents.length > 0}
170
- <div class="day-view__allday">
171
- <div class="day-view__allday-label">{config.locale.allDayLabel}</div>
172
- <div class="day-view__allday-list">
170
+ <div class="bm-cal-day__allday">
171
+ <div class="bm-cal-day__allday-label">{config.locale.allDayLabel}</div>
172
+ <div class="bm-cal-day__allday-list">
173
173
  {#each allDayEvents as ev (ev.id)}
174
174
  <AllDayEvent
175
175
  {ev}
@@ -186,7 +186,7 @@
186
186
  <!-- Time grid -->
187
187
  <div
188
188
  bind:this={timeGridEl}
189
- class="day-view__time-grid"
189
+ class="bm-cal-day__time-grid"
190
190
  role="grid"
191
191
  tabindex="0"
192
192
  ondragover={handleGridDragOver}
@@ -199,7 +199,7 @@
199
199
  >
200
200
  <div
201
201
  bind:this={gridInnerEl}
202
- class="day-view__time-grid-inner"
202
+ class="bm-cal-day__time-grid-inner"
203
203
  style="height: {24 * DEFAULT_HOUR_HEIGHT}px;"
204
204
  >
205
205
  <!-- Hour rows -->
@@ -207,10 +207,10 @@
207
207
  {@const wh = config.core.workingHours}
208
208
  {@const isOff = wh ? (hr < wh.start || hr >= wh.end) : false}
209
209
  <div
210
- class="day-view__hour-row{isOff ? ' hour-slot--off' : ''}"
210
+ class="bm-cal-day__hour-row{isOff ? ' bm-cal__hour-slot--off' : ''}"
211
211
  style="top: {hr * DEFAULT_HOUR_HEIGHT}px; height: {DEFAULT_HOUR_HEIGHT}px;"
212
212
  >
213
- <div class="day-view__hour-label">
213
+ <div class="bm-cal-day__hour-label">
214
214
  {hr > 0 ? `${String(hr).padStart(2, '0')}:00` : ''}
215
215
  </div>
216
216
  </div>
@@ -218,7 +218,7 @@
218
218
 
219
219
  <!-- Day column -->
220
220
  <div
221
- class="day-view__day-col"
221
+ class="bm-cal-day__day-col"
222
222
  style="left: {DEFAULT_TIME_LABEL_WIDTH_DAY}px; width: calc(100% - {DEFAULT_TIME_LABEL_WIDTH_DAY}px);"
223
223
  ></div>
224
224
 
@@ -243,8 +243,8 @@
243
243
 
244
244
  <!-- Now line -->
245
245
  {#if isToday}
246
- <div class="day-view__now-line" style="top: {nowHours * DEFAULT_HOUR_HEIGHT}px;">
247
- <div class="day-view__now-dot"></div>
246
+ <div class="bm-cal-day__now-line" style="top: {nowHours * DEFAULT_HOUR_HEIGHT}px;">
247
+ <div class="bm-cal-day__now-dot"></div>
248
248
  </div>
249
249
  {/if}
250
250
  </div>
@@ -44,11 +44,11 @@
44
44
  });
45
45
  </script>
46
46
 
47
- <div class="month-grid">
48
- <div class="day-headers">
49
- <div class="header-spacer"></div>
47
+ <div class="bm-cal__month-grid">
48
+ <div class="bm-cal__day-headers">
49
+ <div class="bm-cal__header-spacer"></div>
50
50
  {#each config.locale.dayNamesShort as name (name)}
51
- <div class="day-header">{name}</div>
51
+ <div class="bm-cal__day-header">{name}</div>
52
52
  {/each}
53
53
  </div>
54
54
 
@@ -2,6 +2,7 @@
2
2
  import type { CalendarEvent } from '@bimetal/calendar-core';
3
3
  import { getRecurrenceInstanceMetadata } from '@bimetal/calendar-core';
4
4
  import { getEventSlots, toTimeStr } from '../../binding/index.js';
5
+ import { probeRender } from '@bimetal/svelte';
5
6
 
6
7
  let {
7
8
  ev,
@@ -23,11 +24,16 @@
23
24
  const slotStyleStr = $derived(slots.style
24
25
  ? Object.entries(slots.style).map(([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}: ${v}`).join('; ')
25
26
  : '');
27
+
28
+ // v0.30 Slice 3 perf probe: re-runs (post-DOM) ONLY when this entry's DOM-driving values change.
29
+ // `isSelected` is a per-leaf prop (so a selection elsewhere doesn't touch this leaf) and `ev` is
30
+ // the Slice-1 ref-stable entity (stable across snapshots unless THIS event changed).
31
+ $effect(() => { void renderedTitle; void slotStyleStr; void extraClass; void isSelected; void ev; probeRender(ev.id); });
26
32
  </script>
27
33
 
28
34
  <!-- svelte-ignore a11y_click_events_have_key_events -->
29
35
  <div
30
- class="timed-entry{isSelected ? ' timed-entry--selected' : ''}{extraClass}"
36
+ class="bm-cal-timed-entry{isSelected ? ' bm-cal-timed-entry--selected' : ''}{extraClass}"
31
37
  style={slotStyleStr}
32
38
  role="button"
33
39
  tabindex="0"
@@ -39,11 +45,11 @@
39
45
  {#if slots.content}
40
46
  {#if typeof slots.content === 'string'}{slots.content}{:else}{@render slots.content()}{/if}
41
47
  {:else}
42
- <span class="timed-entry__dot" style:background-color={ev.metadata.color ?? '#007AFF'}></span>
43
- <span class="timed-entry__time">
48
+ <span class="bm-cal-timed-entry__dot" style:background-color={ev.metadata.color ?? '#007AFF'}></span>
49
+ <span class="bm-cal-timed-entry__time">
44
50
  {toTimeStr(ev.timeRange.start)}
45
51
  </span>
46
- <span class="timed-entry__title">{renderedTitle}</span>
52
+ <span class="bm-cal-timed-entry__title">{renderedTitle}</span>
47
53
  {/if}
48
54
  {#if slots.badges}
49
55
  {#if typeof slots.badges === 'string'}{slots.badges}{:else}{@render slots.badges()}{/if}
@@ -1 +1 @@
1
- {"version":3,"file":"TimedEntry.svelte.d.ts","sourceRoot":"","sources":["../../../src/views/month/TimedEntry.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAI3D,KAAK,gBAAgB,GAAI;IACtB,EAAE,EAAE,aAAa,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAC1C,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;AAwCJ,QAAA,MAAM,UAAU,sDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"TimedEntry.svelte.d.ts","sourceRoot":"","sources":["../../../src/views/month/TimedEntry.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAK3D,KAAK,gBAAgB,GAAI;IACtB,EAAE,EAAE,aAAa,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAC1C,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;AA8CJ,QAAA,MAAM,UAAU,sDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}