@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.
- package/dist/components/Calendar.svelte +64 -31
- package/dist/components/Calendar.svelte.d.ts +14 -2
- package/dist/components/Calendar.svelte.d.ts.map +1 -1
- package/dist/components/CalendarHeader.svelte +10 -10
- package/dist/components/CategoryFilter.svelte +2 -2
- package/dist/components/EventBar.svelte +16 -12
- package/dist/components/EventBar.svelte.d.ts.map +1 -1
- package/dist/components/EventDialog.svelte +50 -50
- package/dist/components/EventHistory.svelte +17 -21
- package/dist/components/EventHistory.svelte.d.ts.map +1 -1
- package/dist/components/ResourceFilter.svelte +2 -2
- package/dist/components/UndoToast.svelte +5 -5
- package/dist/views/agenda/AgendaEvent.svelte +14 -13
- package/dist/views/agenda/AgendaEvent.svelte.d.ts +2 -1
- package/dist/views/agenda/AgendaEvent.svelte.d.ts.map +1 -1
- package/dist/views/agenda/AgendaView.svelte +57 -115
- package/dist/views/agenda/AgendaView.svelte.d.ts +3 -2
- package/dist/views/agenda/AgendaView.svelte.d.ts.map +1 -1
- package/dist/views/day/AllDayEvent.svelte +1 -1
- package/dist/views/day/DayView.svelte +16 -16
- package/dist/views/month/MonthView.svelte +4 -4
- package/dist/views/month/TimedEntry.svelte +10 -4
- package/dist/views/month/TimedEntry.svelte.d.ts.map +1 -1
- package/dist/views/month/WeekRow.svelte +13 -13
- package/dist/views/resource/ResourceView.svelte +18 -18
- package/dist/views/timeline/TimelineView.svelte +13 -13
- package/dist/views/week/AllDayRow.svelte +3 -3
- package/dist/views/week/TimeEvent.svelte +18 -7
- package/dist/views/week/TimeEvent.svelte.d.ts.map +1 -1
- package/dist/views/week/WeekView.svelte +15 -15
- package/dist/views/year/YearView.svelte +12 -12
- 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
|
-
|
|
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}>←</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}>←</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;
|
|
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="
|
|
41
|
-
<span class="
|
|
42
|
-
<button class="
|
|
43
|
-
{config.locale.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="
|
|
45
|
+
<button class="bm-cal__toast-dismiss" onclick={() => ctx.dismissUndo()}>×</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
|
-
|
|
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
|
-
|
|
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(
|
|
25
|
-
const renderedTitle = $derived(slots.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="
|
|
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,
|
|
40
|
-
ondblclick={(e) => onEntryDoubleClick(e,
|
|
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="
|
|
46
|
-
<div class="
|
|
47
|
-
<div class="
|
|
48
|
-
<div class="
|
|
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="
|
|
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
|
-
|
|
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;
|
|
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
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
18
|
+
agenda,
|
|
19
|
+
setViewport,
|
|
21
20
|
}: {
|
|
22
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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(
|
|
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
|
|
120
|
-
{#if
|
|
121
|
-
<div class="
|
|
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
|
-
{#
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
</span>
|
|
130
|
-
<div class="
|
|
131
|
-
<span class="
|
|
132
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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 {
|
|
1
|
+
import type { AgendaWindow } from '@bimetal/calendar-headless';
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
-
|
|
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":"
|
|
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="
|
|
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
|
|
159
|
+
<div class="bm-cal-day">
|
|
160
160
|
<!-- Header -->
|
|
161
|
-
<div class="
|
|
162
|
-
<span class="
|
|
163
|
-
<span class="
|
|
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="
|
|
171
|
-
<div class="
|
|
172
|
-
<div class="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
247
|
-
<div class="
|
|
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="
|
|
48
|
-
<div class="
|
|
49
|
-
<div class="
|
|
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="
|
|
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;
|
|
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"}
|