@nomideusz/svelte-calendar 0.7.5 → 0.9.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/README.md +33 -5
- package/dist/calendar/Calendar.svelte +379 -497
- package/dist/calendar/Calendar.svelte.d.ts +13 -2
- package/dist/core/clock.svelte.d.ts +1 -1
- package/dist/core/clock.svelte.js +20 -9
- package/dist/core/locale.d.ts +4 -0
- package/dist/core/locale.js +4 -0
- package/dist/core/timezone.d.ts +12 -0
- package/dist/core/timezone.js +31 -0
- package/dist/engine/view-state.svelte.d.ts +2 -2
- package/dist/engine/view-state.svelte.js +20 -0
- package/dist/headless/create-calendar.svelte.js +29 -7
- package/dist/headless/types.d.ts +8 -6
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/primitives/DayHeader.svelte +9 -25
- package/dist/primitives/EmptySlot.svelte +15 -31
- package/dist/primitives/EventBlock.svelte +33 -61
- package/dist/primitives/NowIndicator.svelte +16 -42
- package/dist/primitives/TimeGutter.svelte +9 -23
- package/dist/views/agenda/Agenda.svelte +3 -10
- package/dist/views/agenda/AgendaDay.svelte +195 -167
- package/dist/views/agenda/AgendaWeek.svelte +141 -195
- package/dist/views/mobile/Mobile.svelte +3 -10
- package/dist/views/mobile/MobileDay.svelte +422 -266
- package/dist/views/mobile/MobileWeek.svelte +131 -186
- package/dist/views/month/MonthGrid.svelte +334 -0
- package/dist/views/month/MonthGrid.svelte.d.ts +15 -0
- package/dist/views/planner/Planner.svelte +3 -10
- package/dist/views/planner/PlannerDay.svelte +636 -546
- package/dist/views/planner/PlannerWeek.svelte +299 -412
- package/dist/views/shared/EventContent.svelte +16 -0
- package/dist/views/shared/EventContent.svelte.d.ts +9 -0
- package/dist/views/shared/context.svelte.d.ts +2 -0
- package/dist/views/shared/context.svelte.js +2 -0
- package/dist/widget/CalendarWidget.svelte +79 -116
- package/package.json +3 -2
- package/widget/svelte-calendar.css +321 -10
- package/widget/widget.js +2508 -1056
|
@@ -11,48 +11,22 @@
|
|
|
11
11
|
<NowIndicator mode="dot" position={nowPx} time={clock.hm} />
|
|
12
12
|
<NowIndicator mode="badge" />
|
|
13
13
|
-->
|
|
14
|
-
<script lang="ts">
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
seconds?: string;
|
|
31
|
-
/** Show the time label */
|
|
32
|
-
showLabel?: boolean;
|
|
33
|
-
/** Accent color override */
|
|
34
|
-
color?: string;
|
|
35
|
-
/** Custom content slot */
|
|
36
|
-
children?: Snippet;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let {
|
|
40
|
-
mode = 'line',
|
|
41
|
-
position = 0,
|
|
42
|
-
orientation = 'vertical',
|
|
43
|
-
time = '',
|
|
44
|
-
seconds = '',
|
|
45
|
-
showLabel = true,
|
|
46
|
-
color,
|
|
47
|
-
children,
|
|
48
|
-
}: Props = $props();
|
|
49
|
-
|
|
50
|
-
const posStyle = $derived(
|
|
51
|
-
orientation === 'vertical'
|
|
52
|
-
? `left: ${position}px`
|
|
53
|
-
: `top: ${position}px`,
|
|
54
|
-
);
|
|
55
|
-
const colorVar = $derived(color ? `--ni-color: ${color}` : '');
|
|
14
|
+
<script lang="ts">import { getLabels } from "../core/locale.js";
|
|
15
|
+
const L = $derived(getLabels());
|
|
16
|
+
let {
|
|
17
|
+
mode = "line",
|
|
18
|
+
position = 0,
|
|
19
|
+
orientation = "vertical",
|
|
20
|
+
time = "",
|
|
21
|
+
seconds = "",
|
|
22
|
+
showLabel = true,
|
|
23
|
+
color,
|
|
24
|
+
children
|
|
25
|
+
} = $props();
|
|
26
|
+
const posStyle = $derived(
|
|
27
|
+
orientation === "vertical" ? `left: ${position}px` : `top: ${position}px`
|
|
28
|
+
);
|
|
29
|
+
const colorVar = $derived(color ? `--ni-color: ${color}` : "");
|
|
56
30
|
</script>
|
|
57
31
|
|
|
58
32
|
{#if mode === 'badge'}
|
|
@@ -5,29 +5,15 @@
|
|
|
5
5
|
"horizontal" — ticks along a horizontal track (for day timeline-style layouts)
|
|
6
6
|
"vertical" — label column along a vertical grid (for WeekTimeline style)
|
|
7
7
|
-->
|
|
8
|
-
<script lang="ts">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
halfHour?: boolean;
|
|
18
|
-
/** Hours to display (default 0-23) */
|
|
19
|
-
hours?: readonly number[];
|
|
20
|
-
/** Custom hour formatter */
|
|
21
|
-
formatHour?: (h: number) => string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let {
|
|
25
|
-
orientation = 'horizontal',
|
|
26
|
-
hourSize = 120,
|
|
27
|
-
halfHour = true,
|
|
28
|
-
hours = HOURS,
|
|
29
|
-
formatHour = fmtH,
|
|
30
|
-
}: Props = $props();
|
|
8
|
+
<script lang="ts">import { HOURS } from "../core/time.js";
|
|
9
|
+
import { fmtH } from "../core/locale.js";
|
|
10
|
+
let {
|
|
11
|
+
orientation = "horizontal",
|
|
12
|
+
hourSize = 120,
|
|
13
|
+
halfHour = true,
|
|
14
|
+
hours = HOURS,
|
|
15
|
+
formatHour = fmtH
|
|
16
|
+
} = $props();
|
|
31
17
|
</script>
|
|
32
18
|
|
|
33
19
|
{#if orientation === 'horizontal'}
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
mode?: 'day' | 'week';
|
|
7
|
-
[key: string]: unknown;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
let { mode = 'day', ...rest }: Props = $props();
|
|
1
|
+
<script lang="ts">import AgendaDay from "./AgendaDay.svelte";
|
|
2
|
+
import AgendaWeek from "./AgendaWeek.svelte";
|
|
3
|
+
let { mode = "day", ...rest } = $props();
|
|
11
4
|
</script>
|
|
12
5
|
|
|
13
6
|
{#if mode === 'day'}
|
|
@@ -1,141 +1,95 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// ── Day derivations ─────────────────────────────────
|
|
95
|
-
const dayMs = $derived(focusDate ? sod(focusDate.getTime()) : clock.today);
|
|
96
|
-
const dayEnd = $derived(dayMs + DAY_MS);
|
|
97
|
-
const isToday = $derived(dayMs === clock.today);
|
|
98
|
-
const isPastDay = $derived(equalDays ? false : dayMs < clock.today);
|
|
99
|
-
|
|
100
|
-
/** All events for this day, sorted chronologically */
|
|
101
|
-
const dayEvents = $derived.by((): TimelineEvent[] => {
|
|
102
|
-
return events
|
|
103
|
-
.filter((ev) => ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs)
|
|
104
|
-
.sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
/** All-day / multi-day events shown in a separate strip */
|
|
108
|
-
const allDayBanner = $derived(dayEvents.filter((ev) => isAllDay(ev) || isMultiDay(ev)));
|
|
109
|
-
|
|
110
|
-
/** Timed events (non-all-day) for normal slot rendering */
|
|
111
|
-
const timedDayEvents = $derived(dayEvents.filter((ev) => !isAllDay(ev) && !isMultiDay(ev)));
|
|
112
|
-
|
|
113
|
-
const dayCat = $derived.by(() => {
|
|
114
|
-
const now = clock.tick;
|
|
115
|
-
const past: TimelineEvent[] = [];
|
|
116
|
-
const current: TimelineEvent[] = [];
|
|
117
|
-
const upcoming: TimelineEvent[] = [];
|
|
118
|
-
for (const ev of timedDayEvents) {
|
|
119
|
-
const s = ev.start.getTime();
|
|
120
|
-
const e = ev.end.getTime();
|
|
121
|
-
if (e <= now) past.push(ev);
|
|
122
|
-
else if (s <= now && e > now) current.push(ev);
|
|
123
|
-
else upcoming.push(ev);
|
|
124
|
-
}
|
|
125
|
-
return { past, current, upcomingSlots: groupIntoSlots(upcoming), totalUp: upcoming.length };
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
/** Flat list of next upcoming events (max 5) for the "Up next" column */
|
|
129
|
-
const upcomingNext = $derived.by((): TimelineEvent[] => {
|
|
130
|
-
const all: TimelineEvent[] = [];
|
|
131
|
-
for (const slot of dayCat.upcomingSlots) {
|
|
132
|
-
for (const ev of slot.events) {
|
|
133
|
-
all.push(ev);
|
|
134
|
-
if (all.length >= 5) return all;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return all;
|
|
138
|
-
});
|
|
1
|
+
<script lang="ts">import { createClock } from "../../core/clock.svelte.js";
|
|
2
|
+
import { sod, DAY_MS, isAllDay, isMultiDay } from "../../core/time.js";
|
|
3
|
+
import { getLabels } from "../../core/locale.js";
|
|
4
|
+
import { useCalendarContext } from "../shared/context.svelte.js";
|
|
5
|
+
import EventContent from "../shared/EventContent.svelte";
|
|
6
|
+
import { fmtTime, duration, timeUntilMs, progress, groupIntoSlots } from "../shared/format.js";
|
|
7
|
+
const L = $derived(getLabels());
|
|
8
|
+
const ctx = useCalendarContext();
|
|
9
|
+
const emptySnippet = $derived(ctx.emptySnippet);
|
|
10
|
+
let {
|
|
11
|
+
locale,
|
|
12
|
+
height,
|
|
13
|
+
events = [],
|
|
14
|
+
style = "",
|
|
15
|
+
focusDate,
|
|
16
|
+
oneventclick,
|
|
17
|
+
selectedEventId = null
|
|
18
|
+
} = $props();
|
|
19
|
+
const clock = createClock(ctx.timezone);
|
|
20
|
+
const viewState = $derived(ctx.viewState);
|
|
21
|
+
const equalDays = $derived(ctx.equalDays);
|
|
22
|
+
const isMobile = $derived(ctx.isMobile);
|
|
23
|
+
const autoHeight = $derived(ctx.autoHeight);
|
|
24
|
+
const compact = $derived(ctx.compact);
|
|
25
|
+
const oneventhover = $derived(ctx.oneventhover);
|
|
26
|
+
const disabledSet = $derived(ctx.disabledSet);
|
|
27
|
+
let swipeStartX = 0;
|
|
28
|
+
let swipeStartY = 0;
|
|
29
|
+
const SWIPE_THRESHOLD = 50;
|
|
30
|
+
function onPointerDown(e) {
|
|
31
|
+
if (!isMobile) return;
|
|
32
|
+
swipeStartX = e.clientX;
|
|
33
|
+
swipeStartY = e.clientY;
|
|
34
|
+
}
|
|
35
|
+
function onPointerUp(e) {
|
|
36
|
+
if (!isMobile) return;
|
|
37
|
+
const dx = e.clientX - swipeStartX;
|
|
38
|
+
const dy = e.clientY - swipeStartY;
|
|
39
|
+
if (Math.abs(dx) > SWIPE_THRESHOLD && Math.abs(dx) > Math.abs(dy) * 1.4) {
|
|
40
|
+
if (dx > 0) viewState?.prev();
|
|
41
|
+
else viewState?.next();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const fmt = (d) => fmtTime(d, locale);
|
|
45
|
+
const eta = (ms) => timeUntilMs(ms, clock.tick);
|
|
46
|
+
const prog = (ev) => progress(ev, clock.tick);
|
|
47
|
+
function handleClick(ev) {
|
|
48
|
+
oneventclick?.(ev);
|
|
49
|
+
}
|
|
50
|
+
function handleKeydown(e, ev) {
|
|
51
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
oneventclick?.(ev);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const dayMs = $derived(focusDate ? sod(focusDate.getTime()) : clock.today);
|
|
57
|
+
const dayEnd = $derived(dayMs + DAY_MS);
|
|
58
|
+
const isToday = $derived(dayMs === clock.today);
|
|
59
|
+
const isPastDay = $derived(equalDays ? false : dayMs < clock.today);
|
|
60
|
+
const dayEvents = $derived.by(() => {
|
|
61
|
+
return events.filter((ev) => ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs).sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
62
|
+
});
|
|
63
|
+
const allDayBanner = $derived(dayEvents.filter((ev) => isAllDay(ev) || isMultiDay(ev)));
|
|
64
|
+
const timedDayEvents = $derived(dayEvents.filter((ev) => !isAllDay(ev) && !isMultiDay(ev)));
|
|
65
|
+
const dayCat = $derived.by(() => {
|
|
66
|
+
const now = clock.tick;
|
|
67
|
+
const past = [];
|
|
68
|
+
const current = [];
|
|
69
|
+
const upcoming = [];
|
|
70
|
+
for (const ev of timedDayEvents) {
|
|
71
|
+
const s = ev.start.getTime();
|
|
72
|
+
const e = ev.end.getTime();
|
|
73
|
+
if (e <= now) past.push(ev);
|
|
74
|
+
else if (s <= now && e > now) current.push(ev);
|
|
75
|
+
else upcoming.push(ev);
|
|
76
|
+
}
|
|
77
|
+
return { past, current, upcomingSlots: groupIntoSlots(upcoming), totalUp: upcoming.length };
|
|
78
|
+
});
|
|
79
|
+
const upcomingNext = $derived.by(() => {
|
|
80
|
+
const all = [];
|
|
81
|
+
for (const slot of dayCat.upcomingSlots) {
|
|
82
|
+
for (const ev of slot.events) all.push(ev);
|
|
83
|
+
}
|
|
84
|
+
return all;
|
|
85
|
+
});
|
|
86
|
+
const UPCOMING_CARDS = 4;
|
|
87
|
+
const DONE_VISIBLE = 3;
|
|
88
|
+
let showAllDone = $state(false);
|
|
89
|
+
const visibleDone = $derived(
|
|
90
|
+
showAllDone ? dayCat.past : dayCat.past.slice(-DONE_VISIBLE)
|
|
91
|
+
);
|
|
92
|
+
const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.length - DONE_VISIBLE));
|
|
139
93
|
</script>
|
|
140
94
|
|
|
141
95
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
@@ -179,7 +133,9 @@
|
|
|
179
133
|
<!-- ─── Compact: minimal text rows ─── -->
|
|
180
134
|
<div class="ag-compact-list">
|
|
181
135
|
{#if timedDayEvents.length === 0 && allDayBanner.length === 0}
|
|
182
|
-
<div class="ag-q-empty">
|
|
136
|
+
<div class="ag-q-empty">
|
|
137
|
+
{#if emptySnippet}{@render emptySnippet()}{:else}{L.nothingScheduledYet}{/if}
|
|
138
|
+
</div>
|
|
183
139
|
{:else}
|
|
184
140
|
{#each timedDayEvents as ev (ev.id)}
|
|
185
141
|
<div
|
|
@@ -195,6 +151,7 @@
|
|
|
195
151
|
onpointerenter={() => oneventhover?.(ev)}
|
|
196
152
|
onkeydown={(e) => handleKeydown(e, ev)}
|
|
197
153
|
>
|
|
154
|
+
<EventContent event={ev}>
|
|
198
155
|
<span class="ag-compact-row-dot"></span>
|
|
199
156
|
<span class="ag-compact-row-time">{fmt(ev.start)}</span>
|
|
200
157
|
<span class="ag-compact-row-title">{ev.title}</span>
|
|
@@ -207,6 +164,7 @@
|
|
|
207
164
|
{/each}
|
|
208
165
|
{/if}
|
|
209
166
|
<span class="ag-compact-row-dur">{duration(ev)}</span>
|
|
167
|
+
</EventContent>
|
|
210
168
|
</div>
|
|
211
169
|
{/each}
|
|
212
170
|
{/if}
|
|
@@ -215,28 +173,8 @@
|
|
|
215
173
|
{:else if isToday}
|
|
216
174
|
<!-- ─── Today: "The Queue" — upcoming is the hero ─── -->
|
|
217
175
|
<div class="ag-q">
|
|
218
|
-
<!-- NOW column:
|
|
176
|
+
<!-- NOW column: the live strip first, completed collapsed below -->
|
|
219
177
|
<div class="ag-q-status">
|
|
220
|
-
{#if dayCat.past.length > 0}
|
|
221
|
-
<div class="ag-q-done-section">
|
|
222
|
-
<div class="ag-q-label">{L.done}</div>
|
|
223
|
-
{#each dayCat.past as ev (ev.id)}
|
|
224
|
-
<div
|
|
225
|
-
class="ag-q-done-item"
|
|
226
|
-
class:ag-q-done-item--selected={selectedEventId === ev.id}
|
|
227
|
-
role="button"
|
|
228
|
-
tabindex="0"
|
|
229
|
-
aria-label="{ev.title}, {L.completed}, {fmt(ev.start)}"
|
|
230
|
-
onclick={() => handleClick(ev)}
|
|
231
|
-
onkeydown={(e) => handleKeydown(e, ev)}
|
|
232
|
-
>
|
|
233
|
-
<span class="ag-q-done-check">✓</span>
|
|
234
|
-
<span class="ag-q-done-title">{ev.title}</span>
|
|
235
|
-
</div>
|
|
236
|
-
{/each}
|
|
237
|
-
</div>
|
|
238
|
-
{/if}
|
|
239
|
-
|
|
240
178
|
<div class="ag-q-label">{L.now} <span class="ag-q-clock">{clock.hm}</span></div>
|
|
241
179
|
{#if dayCat.current.length > 0}
|
|
242
180
|
{#each dayCat.current as ev (ev.id)}
|
|
@@ -251,6 +189,7 @@
|
|
|
251
189
|
>
|
|
252
190
|
<div class="ag-q-now-dot"></div>
|
|
253
191
|
<div class="ag-q-now-title">{ev.title}</div>
|
|
192
|
+
{#if ev.subtitle}<div class="ag-q-now-sub">{ev.subtitle}</div>{/if}
|
|
254
193
|
<div class="ag-q-now-time">{L.until} {fmt(ev.end)}</div>
|
|
255
194
|
<div class="ag-q-now-track">
|
|
256
195
|
<div class="ag-q-now-fill" style:width="{prog(ev) * 100}%"></div>
|
|
@@ -262,6 +201,31 @@
|
|
|
262
201
|
<div class="ag-q-free-label">{L.free}</div>
|
|
263
202
|
</div>
|
|
264
203
|
{/if}
|
|
204
|
+
|
|
205
|
+
{#if dayCat.past.length > 0}
|
|
206
|
+
<div class="ag-q-done-section">
|
|
207
|
+
<div class="ag-q-label">{L.done}</div>
|
|
208
|
+
{#each visibleDone as ev (ev.id)}
|
|
209
|
+
<div
|
|
210
|
+
class="ag-q-done-item"
|
|
211
|
+
class:ag-q-done-item--selected={selectedEventId === ev.id}
|
|
212
|
+
role="button"
|
|
213
|
+
tabindex="0"
|
|
214
|
+
aria-label="{ev.title}, {L.completed}, {fmt(ev.start)}"
|
|
215
|
+
onclick={() => handleClick(ev)}
|
|
216
|
+
onkeydown={(e) => handleKeydown(e, ev)}
|
|
217
|
+
>
|
|
218
|
+
<span class="ag-q-done-check">✓</span>
|
|
219
|
+
<span class="ag-q-done-title">{ev.title}</span>
|
|
220
|
+
</div>
|
|
221
|
+
{/each}
|
|
222
|
+
{#if hiddenDoneCount > 0}
|
|
223
|
+
<button class="ag-q-done-toggle" onclick={() => (showAllDone = !showAllDone)}>
|
|
224
|
+
{showAllDone ? L.showLess : L.nCompleted(hiddenDoneCount)}
|
|
225
|
+
</button>
|
|
226
|
+
{/if}
|
|
227
|
+
</div>
|
|
228
|
+
{/if}
|
|
265
229
|
</div>
|
|
266
230
|
|
|
267
231
|
<!-- NEXT: the hero center column -->
|
|
@@ -273,6 +237,28 @@
|
|
|
273
237
|
</div>
|
|
274
238
|
{:else}
|
|
275
239
|
{#each upcomingNext as ev, i (ev.id)}
|
|
240
|
+
{#if i >= UPCOMING_CARDS}
|
|
241
|
+
<div
|
|
242
|
+
class="ag-compact-row ag-compact-row--queue"
|
|
243
|
+
class:ag-compact-row--selected={selectedEventId === ev.id}
|
|
244
|
+
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
245
|
+
role="button"
|
|
246
|
+
tabindex="0"
|
|
247
|
+
aria-label="{ev.title}, {fmt(ev.start)}, {duration(ev)}"
|
|
248
|
+
onclick={() => handleClick(ev)}
|
|
249
|
+
onpointerenter={() => oneventhover?.(ev)}
|
|
250
|
+
onkeydown={(e) => handleKeydown(e, ev)}
|
|
251
|
+
>
|
|
252
|
+
<EventContent event={ev}>
|
|
253
|
+
<span class="ag-compact-row-dot"></span>
|
|
254
|
+
<span class="ag-compact-row-time">{fmt(ev.start)}</span>
|
|
255
|
+
<span class="ag-compact-row-title">{ev.title}</span>
|
|
256
|
+
{#if ev.subtitle}
|
|
257
|
+
<span class="ag-compact-row-sub">{ev.subtitle}</span>
|
|
258
|
+
{/if}
|
|
259
|
+
</EventContent>
|
|
260
|
+
</div>
|
|
261
|
+
{:else}
|
|
276
262
|
<div
|
|
277
263
|
class="ag-card ag-card--q"
|
|
278
264
|
class:ag-card--hero={i === 0}
|
|
@@ -286,6 +272,7 @@
|
|
|
286
272
|
onkeydown={(e) => handleKeydown(e, ev)}
|
|
287
273
|
>
|
|
288
274
|
<div class="ag-card-body">
|
|
275
|
+
<EventContent event={ev}>
|
|
289
276
|
<div class="ag-card-top">
|
|
290
277
|
<span class="ag-card-title">{ev.title}</span>
|
|
291
278
|
<span class="ag-card-eta">{eta(ev.start.getTime())}</span>
|
|
@@ -304,8 +291,10 @@
|
|
|
304
291
|
{/each}
|
|
305
292
|
</div>
|
|
306
293
|
{/if}
|
|
294
|
+
</EventContent>
|
|
307
295
|
</div>
|
|
308
296
|
</div>
|
|
297
|
+
{/if}
|
|
309
298
|
{/each}
|
|
310
299
|
{/if}
|
|
311
300
|
</div>
|
|
@@ -341,7 +330,9 @@
|
|
|
341
330
|
<!-- ─── Future day: "The Plan" — everything is ahead ─── -->
|
|
342
331
|
<div class="ag-plan">
|
|
343
332
|
{#if timedDayEvents.length === 0 && allDayBanner.length === 0}
|
|
344
|
-
<div class="ag-q-empty">
|
|
333
|
+
<div class="ag-q-empty">
|
|
334
|
+
{#if emptySnippet}{@render emptySnippet()}{:else}{L.nothingScheduledYet}{/if}
|
|
335
|
+
</div>
|
|
345
336
|
{:else}
|
|
346
337
|
{#each timedDayEvents as ev, i (ev.id)}
|
|
347
338
|
<div
|
|
@@ -359,6 +350,7 @@
|
|
|
359
350
|
onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
|
|
360
351
|
>
|
|
361
352
|
<div class="ag-card-body">
|
|
353
|
+
<EventContent event={ev}>
|
|
362
354
|
<div class="ag-card-top">
|
|
363
355
|
<span class="ag-card-order">{i + 1}</span>
|
|
364
356
|
<span class="ag-card-title">{ev.title}</span>
|
|
@@ -380,6 +372,7 @@
|
|
|
380
372
|
{/each}
|
|
381
373
|
</div>
|
|
382
374
|
{/if}
|
|
375
|
+
</EventContent>
|
|
383
376
|
</div>
|
|
384
377
|
</div>
|
|
385
378
|
{/each}
|
|
@@ -596,9 +589,11 @@
|
|
|
596
589
|
|
|
597
590
|
/* ── Queue card variant ── */
|
|
598
591
|
.ag-card--q {
|
|
599
|
-
margin-bottom: 6px;
|
|
600
592
|
transition: border-color 150ms, transform 100ms;
|
|
601
593
|
}
|
|
594
|
+
.ag-compact-row--queue {
|
|
595
|
+
margin: 0;
|
|
596
|
+
}
|
|
602
597
|
|
|
603
598
|
.ag-card--q .ag-card-body {
|
|
604
599
|
gap: 3px;
|
|
@@ -750,10 +745,31 @@
|
|
|
750
745
|
.ag-q-status::-webkit-scrollbar {
|
|
751
746
|
display: none;
|
|
752
747
|
}
|
|
748
|
+
.ag-q-done-toggle {
|
|
749
|
+
align-self: flex-start;
|
|
750
|
+
margin-top: 2px;
|
|
751
|
+
padding: 3px 8px;
|
|
752
|
+
border: 1px solid var(--dt-border);
|
|
753
|
+
border-radius: 999px;
|
|
754
|
+
background: none;
|
|
755
|
+
font-family: var(--dt-mono);
|
|
756
|
+
font-size: 11px;
|
|
757
|
+
color: var(--dt-text-3);
|
|
758
|
+
cursor: pointer;
|
|
759
|
+
}
|
|
760
|
+
.ag-q-done-toggle:hover {
|
|
761
|
+
color: var(--dt-text);
|
|
762
|
+
border-color: var(--dt-text-3);
|
|
763
|
+
}
|
|
764
|
+
.ag-q-now-sub {
|
|
765
|
+
font-size: 12px;
|
|
766
|
+
color: var(--dt-text-2);
|
|
767
|
+
margin-top: 1px;
|
|
768
|
+
}
|
|
753
769
|
.ag-q-done-section {
|
|
754
|
-
margin-
|
|
755
|
-
padding-
|
|
756
|
-
border-
|
|
770
|
+
margin-top: 12px;
|
|
771
|
+
padding-top: 10px;
|
|
772
|
+
border-top: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
|
|
757
773
|
}
|
|
758
774
|
.ag-q-clock {
|
|
759
775
|
font-size: 10px;
|
|
@@ -764,6 +780,7 @@
|
|
|
764
780
|
}
|
|
765
781
|
.ag-q-now {
|
|
766
782
|
padding: 8px 10px;
|
|
783
|
+
margin-bottom: 8px;
|
|
767
784
|
border-radius: 8px;
|
|
768
785
|
background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
769
786
|
border: 1px solid color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
|
|
@@ -840,6 +857,7 @@
|
|
|
840
857
|
scrollbar-width: none;
|
|
841
858
|
display: flex;
|
|
842
859
|
flex-direction: column;
|
|
860
|
+
gap: 8px;
|
|
843
861
|
}
|
|
844
862
|
.ag-q-queue::-webkit-scrollbar {
|
|
845
863
|
display: none;
|
|
@@ -966,10 +984,11 @@
|
|
|
966
984
|
.ag-compact-list::-webkit-scrollbar { display: none; }
|
|
967
985
|
.ag-compact-row {
|
|
968
986
|
display: flex;
|
|
969
|
-
align-items:
|
|
987
|
+
align-items: baseline;
|
|
970
988
|
gap: 8px;
|
|
971
989
|
padding: 4px 0;
|
|
972
990
|
cursor: pointer;
|
|
991
|
+
min-width: 0;
|
|
973
992
|
}
|
|
974
993
|
.ag-compact-row--selected {
|
|
975
994
|
background: color-mix(in srgb, var(--ev-color) 10%, transparent);
|
|
@@ -988,6 +1007,7 @@
|
|
|
988
1007
|
border-radius: 50%;
|
|
989
1008
|
background: var(--ev-color, var(--dt-accent));
|
|
990
1009
|
flex-shrink: 0;
|
|
1010
|
+
align-self: center;
|
|
991
1011
|
}
|
|
992
1012
|
.ag-compact-row-time {
|
|
993
1013
|
font-size: 11px;
|
|
@@ -995,6 +1015,7 @@
|
|
|
995
1015
|
color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
|
|
996
1016
|
min-width: 64px;
|
|
997
1017
|
flex-shrink: 0;
|
|
1018
|
+
line-height: 1.4;
|
|
998
1019
|
}
|
|
999
1020
|
.ag-compact-row-title {
|
|
1000
1021
|
font-size: 12px;
|
|
@@ -1005,17 +1026,24 @@
|
|
|
1005
1026
|
overflow: hidden;
|
|
1006
1027
|
text-overflow: ellipsis;
|
|
1007
1028
|
transition: color 150ms;
|
|
1029
|
+
line-height: 1.4;
|
|
1008
1030
|
}
|
|
1009
1031
|
.ag-compact-row-dur {
|
|
1010
1032
|
font-size: 10px;
|
|
1011
1033
|
font-family: var(--dt-mono, monospace);
|
|
1012
1034
|
color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
|
|
1013
1035
|
flex-shrink: 0;
|
|
1036
|
+
line-height: 1.4;
|
|
1014
1037
|
}
|
|
1015
1038
|
.ag-compact-row-sub {
|
|
1016
1039
|
font-size: 10px;
|
|
1017
1040
|
color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
|
|
1018
|
-
flex-shrink:
|
|
1041
|
+
flex-shrink: 1;
|
|
1042
|
+
max-width: 45%;
|
|
1043
|
+
white-space: nowrap;
|
|
1044
|
+
overflow: hidden;
|
|
1045
|
+
text-overflow: ellipsis;
|
|
1046
|
+
line-height: 1.4;
|
|
1019
1047
|
}
|
|
1020
1048
|
.ag-compact-row-tag {
|
|
1021
1049
|
font: 500 8px / 1 var(--dt-sans, system-ui, sans-serif);
|