@bimetal/calendar-svelte-components 0.29.0 → 0.30.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.
@@ -203,7 +203,7 @@
203
203
  {:else if snapshot.currentView === 'day'}
204
204
  <DayView currentDate={snapshot.currentDate} />
205
205
  {:else if snapshot.currentView === 'agenda'}
206
- <AgendaView currentDate={snapshot.currentDate} />
206
+ <AgendaView agenda={snapshot.agenda!} setViewport={ctrl.setViewport} />
207
207
  {:else if snapshot.currentView === 'resource'}
208
208
  <ResourceView
209
209
  currentDate={snapshot.currentDate}
@@ -4,6 +4,7 @@
4
4
  import { getCalendarContext, getCalendarConfig, buildDragInfo, getEventSlots, toTimeStr } from '../binding/index.js';
5
5
  import type { DragInfo } from '../binding/index.js';
6
6
  import { createClickIntent, writeCalendarDragData, installTransparentDragImage, eventBarWantsDarkText } from '@bimetal/calendar-headless';
7
+ import { probeRender } from '@bimetal/svelte';
7
8
 
8
9
  let {
9
10
  item,
@@ -108,6 +109,11 @@
108
109
  ? Object.entries(slots.style).map(([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}: ${v}`).join('; ')
109
110
  : '');
110
111
  const fullStyle = $derived(slotStyle ? `${barStyle} ${slotStyle}` : barStyle);
112
+
113
+ // v0.30 Slice 3 perf probe: re-runs (post-DOM) ONLY when this bar's DOM-driving deriveds change.
114
+ // `selectedClass` is equality-gated, so a selection elsewhere does NOT re-run unchanged bars —
115
+ // proving Svelte 5's fine-grained reactivity is identity-true even with the context selection read.
116
+ $effect(() => { void fullStyle; void selectedClass; void renderedTitle; probeRender(event.id); });
111
117
  </script>
112
118
 
113
119
  {#if isCompact}
@@ -1 +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"}
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;AAO1E,KAAK,gBAAgB,GAAI;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAuJJ,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -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,18 +32,18 @@
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="agenda-view__row agenda-view__event{isSelected ? ' agenda-view__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-color" style:background-color={event.metadata.color ?? '#007AFF'}></div>
46
47
  <div class="agenda-view__event-content">
47
48
  <div class="agenda-view__event-title">{renderedTitle}</div>
48
49
  <div class="agenda-view__event-time">{timeLabel}</div>
@@ -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}
64
+ <div class="agenda-view" bind:this={scrollEl}>
65
+ {#if agenda.empty}
121
66
  <div class="agenda-view__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>
68
+ {#if agenda.offsetTop > 0}
69
+ <div class="agenda-view__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="agenda-view__row agenda-view__row--header{row.isToday ? ' agenda-view__row--today' : ''}">
74
+ <span class="agenda-view__date-number">{row.dayOfMonth}</span>
130
75
  <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>
76
+ <span class="agenda-view__date-weekday">{row.weekday}</span>
77
+ <span class="agenda-view__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="agenda-view__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"}
@@ -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,6 +24,11 @@
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 -->
@@ -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"}
@@ -4,6 +4,7 @@
4
4
  import { getCalendarContext, getCalendarConfig, toTimeStr, buildDragInfo, getEventSlots } from '../../binding/index.js';
5
5
  import type { DragInfo } from '../../binding/index.js';
6
6
  import { createClickIntent, writeCalendarDragData, installTransparentDragImage } from '@bimetal/calendar-headless';
7
+ import { probeRender } from '@bimetal/svelte';
7
8
 
8
9
  /**
9
10
  * Layout-Position eines Termins (Pilot-Direktive 2026-06-21). Zwei
@@ -132,6 +133,18 @@
132
133
  ? Object.entries(slots.style).map(([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}: ${v}`).join('; ')
133
134
  : '');
134
135
  const style = $derived(slotStyleStr ? `${baseStyle} ${slotStyleStr}` : baseStyle);
136
+
137
+ // The week timed-layout rebuilds its `item`/`event` wrappers every snapshot, so the raw `event`
138
+ // prop ref churns even for unchanged events. Route the id through an equality-gated `$derived` so
139
+ // the probe below tracks the STABLE id string, not the churning prop ref (the DOM-driving
140
+ // deriveds `style`/`renderedTitle`/… already gate the same churn to their stable values).
141
+ const eventId = $derived(event.id);
142
+
143
+ // v0.30 Slice 3 perf probe: re-runs (post-DOM) ONLY when this event's DOM-driving deriveds
144
+ // change. `style` folds in all `layout` geometry (top/height/left/width fractions), and
145
+ // `isSelected` is equality-gated — so a selection or geometry change elsewhere does NOT
146
+ // re-run unchanged events, proving fine-grained reactivity even with the context selection read.
147
+ $effect(() => { void style; void isSelected; void renderedTitle; void timeLabel; void duration; probeRender(eventId); });
135
148
  </script>
136
149
 
137
150
  <div
@@ -1 +1 @@
1
- {"version":3,"file":"TimeEvent.svelte.d.ts","sourceRoot":"","sources":["../../../src/views/week/TimeEvent.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAO5E;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AACF,KAAK,gBAAgB,GAAI;IACtB,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAgJJ,QAAA,MAAM,SAAS,sDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"TimeEvent.svelte.d.ts","sourceRoot":"","sources":["../../../src/views/week/TimeEvent.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAQ5E;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AACF,KAAK,gBAAgB,GAAI;IACtB,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AA6JJ,QAAA,MAAM,SAAS,sDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bimetal/calendar-svelte-components",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "Default Svelte 5 calendar UI components",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -27,13 +27,13 @@
27
27
  "prepublishOnly": "npm run build"
28
28
  },
29
29
  "dependencies": {
30
- "@bimetal/svelte": "^0.29.0",
31
- "@bimetal/core": "^0.29.0",
32
- "@bimetal/calendar-core": "^0.29.0",
33
- "@bimetal/calendar-data": "^0.29.0",
34
- "@bimetal/calendar-headless": "^0.29.0",
35
- "@bimetal/calendar-rules": "^0.29.0",
36
- "@bimetal/calendar-themes": "^0.29.0"
30
+ "@bimetal/svelte": "^0.30.0",
31
+ "@bimetal/core": "^0.30.0",
32
+ "@bimetal/calendar-core": "^0.30.0",
33
+ "@bimetal/calendar-data": "^0.30.0",
34
+ "@bimetal/calendar-headless": "^0.30.0",
35
+ "@bimetal/calendar-rules": "^0.30.0",
36
+ "@bimetal/calendar-themes": "^0.30.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "svelte": "^5.46.4"