@nomideusz/svelte-calendar 0.6.3 → 0.6.4
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 +183 -5
- package/dist/adapters/composite.d.ts +15 -0
- package/dist/adapters/composite.js +65 -0
- package/dist/adapters/index.d.ts +5 -1
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/mapped.d.ts +170 -0
- package/dist/adapters/mapped.js +343 -0
- package/dist/adapters/recurring.d.ts +4 -0
- package/dist/adapters/recurring.js +5 -10
- package/dist/adapters/types.d.ts +11 -2
- package/dist/calendar/Calendar.svelte +147 -76
- package/dist/calendar/Calendar.svelte.d.ts +12 -0
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.js +2 -0
- package/dist/core/measure.d.ts +104 -0
- package/dist/core/measure.js +195 -0
- package/dist/core/time.js +2 -2
- package/dist/core/types.d.ts +32 -0
- package/dist/engine/event-store.svelte.js +22 -7
- package/dist/headless/create-agenda.svelte.d.ts +67 -0
- package/dist/headless/create-agenda.svelte.js +139 -0
- package/dist/headless/create-calendar.svelte.d.ts +2 -0
- package/dist/headless/create-calendar.svelte.js +380 -0
- package/dist/headless/index.d.ts +4 -0
- package/dist/headless/index.js +2 -0
- package/dist/headless/types.d.ts +219 -0
- package/dist/headless/types.js +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +4 -2
- package/dist/primitives/EventBlock.svelte +87 -2
- package/dist/views/agenda/AgendaDay.svelte +183 -89
- package/dist/views/agenda/AgendaWeek.svelte +148 -95
- package/dist/views/mobile/MobileDay.svelte +43 -16
- package/dist/views/mobile/MobileWeek.svelte +32 -17
- package/dist/views/planner/PlannerDay.svelte +91 -36
- package/dist/views/planner/PlannerWeek.svelte +107 -132
- package/dist/views/shared/context.svelte.d.ts +47 -0
- package/dist/views/shared/context.svelte.js +47 -0
- package/dist/views/shared/format.d.ts +19 -0
- package/dist/views/shared/format.js +46 -0
- package/dist/views/shared/index.d.ts +4 -0
- package/dist/views/shared/index.js +2 -0
- package/package.json +76 -71
|
@@ -48,13 +48,19 @@
|
|
|
48
48
|
}: Props = $props();
|
|
49
49
|
|
|
50
50
|
const accentColor = $derived(event.color || 'var(--dt-accent, #ef4444)');
|
|
51
|
+
const isCancelled = $derived(event.status === 'cancelled');
|
|
52
|
+
const isTentative = $derived(event.status === 'tentative');
|
|
53
|
+
const isFull = $derived(event.status === 'full');
|
|
54
|
+
const isLimited = $derived(event.status === 'limited');
|
|
51
55
|
|
|
52
56
|
const ariaLabel = $derived.by(() => {
|
|
53
57
|
const t = event.title;
|
|
54
58
|
const time = `${fmtTime(event.start)} to ${fmtTime(event.end)}`;
|
|
55
59
|
const dur = fmtDuration(event.start, event.end);
|
|
56
|
-
const
|
|
57
|
-
|
|
60
|
+
const loc = event.location ? `, ${event.location}` : '';
|
|
61
|
+
const statusStr = isCancelled ? ', cancelled' : isTentative ? ', tentative' : isFull ? ', full' : isLimited ? ', limited' : '';
|
|
62
|
+
const activeStr = active ? `, ${L.happeningNow}` : past ? `, ${L.past}` : '';
|
|
63
|
+
return `${t}${loc}, ${time}, ${dur}${statusStr}${activeStr}`;
|
|
58
64
|
});
|
|
59
65
|
|
|
60
66
|
function handleKeydown(e: KeyboardEvent) {
|
|
@@ -79,6 +85,9 @@
|
|
|
79
85
|
{#if event.subtitle}
|
|
80
86
|
<span class="eb-subtitle">{event.subtitle}</span>
|
|
81
87
|
{/if}
|
|
88
|
+
{#if event.location}
|
|
89
|
+
<span class="eb-location">📍 {event.location}</span>
|
|
90
|
+
{/if}
|
|
82
91
|
{#if showTime}
|
|
83
92
|
<span class="eb-time">{fmtTime(event.start)} – {fmtTime(event.end)}</span>
|
|
84
93
|
{/if}
|
|
@@ -92,6 +101,10 @@
|
|
|
92
101
|
{/each}
|
|
93
102
|
</div>
|
|
94
103
|
{/if}
|
|
104
|
+
{#if isCancelled}<span class="eb-status-badge eb-cancelled-badge">Cancelled</span>{/if}
|
|
105
|
+
{#if isTentative}<span class="eb-status-badge eb-tentative-badge">Tentative</span>{/if}
|
|
106
|
+
{#if isFull}<span class="eb-status-badge eb-full-badge">Full</span>{/if}
|
|
107
|
+
{#if isLimited}<span class="eb-status-badge eb-limited-badge">Limited</span>{/if}
|
|
95
108
|
{#if active}<span class="eb-live-badge">{L.now}</span>{/if}
|
|
96
109
|
</div>
|
|
97
110
|
{:else}
|
|
@@ -102,6 +115,9 @@
|
|
|
102
115
|
{#if event.subtitle}
|
|
103
116
|
<span class="eb-subtitle">{event.subtitle}</span>
|
|
104
117
|
{/if}
|
|
118
|
+
{#if event.location}
|
|
119
|
+
<span class="eb-location">📍 {event.location}</span>
|
|
120
|
+
{/if}
|
|
105
121
|
{#if showDuration}
|
|
106
122
|
<span class="eb-dur">{fmtDuration(event.start, event.end)}</span>
|
|
107
123
|
{/if}
|
|
@@ -112,6 +128,9 @@
|
|
|
112
128
|
{/each}
|
|
113
129
|
</span>
|
|
114
130
|
{/if}
|
|
131
|
+
{#if isCancelled}<span class="eb-status-badge eb-cancelled-badge">Cancelled</span>{/if}
|
|
132
|
+
{#if isFull}<span class="eb-status-badge eb-full-badge">Full</span>{/if}
|
|
133
|
+
{#if isLimited}<span class="eb-status-badge eb-limited-badge">Limited</span>{/if}
|
|
115
134
|
{#if active}<span class="eb-live-badge">{L.now}</span>{/if}
|
|
116
135
|
{/if}
|
|
117
136
|
{/snippet}
|
|
@@ -121,6 +140,10 @@
|
|
|
121
140
|
class="eb eb-{variant}"
|
|
122
141
|
class:eb-active={active}
|
|
123
142
|
class:eb-past={past}
|
|
143
|
+
class:eb-cancelled={isCancelled}
|
|
144
|
+
class:eb-tentative={isTentative}
|
|
145
|
+
class:eb-full={isFull}
|
|
146
|
+
class:eb-limited={isLimited}
|
|
124
147
|
class:eb-editable={editable}
|
|
125
148
|
style="--eb-color: {accentColor}"
|
|
126
149
|
role="button"
|
|
@@ -137,6 +160,10 @@
|
|
|
137
160
|
class="eb eb-{variant}"
|
|
138
161
|
class:eb-active={active}
|
|
139
162
|
class:eb-past={past}
|
|
163
|
+
class:eb-cancelled={isCancelled}
|
|
164
|
+
class:eb-tentative={isTentative}
|
|
165
|
+
class:eb-full={isFull}
|
|
166
|
+
class:eb-limited={isLimited}
|
|
140
167
|
class:eb-editable={editable}
|
|
141
168
|
style="--eb-color: {accentColor}"
|
|
142
169
|
role="article"
|
|
@@ -167,6 +194,31 @@
|
|
|
167
194
|
.eb-past {
|
|
168
195
|
opacity: 0.5;
|
|
169
196
|
}
|
|
197
|
+
.eb-cancelled {
|
|
198
|
+
opacity: 0.45;
|
|
199
|
+
}
|
|
200
|
+
.eb-cancelled .eb-title {
|
|
201
|
+
text-decoration: line-through;
|
|
202
|
+
}
|
|
203
|
+
.eb-tentative {
|
|
204
|
+
opacity: 0.7;
|
|
205
|
+
}
|
|
206
|
+
.eb-tentative .eb-stripe {
|
|
207
|
+
background: repeating-linear-gradient(
|
|
208
|
+
45deg,
|
|
209
|
+
var(--_color),
|
|
210
|
+
var(--_color) 2px,
|
|
211
|
+
transparent 2px,
|
|
212
|
+
transparent 4px
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
.eb-full {
|
|
216
|
+
opacity: 0.55;
|
|
217
|
+
}
|
|
218
|
+
.eb-limited .eb-stripe {
|
|
219
|
+
border-left: 2px dashed var(--_color);
|
|
220
|
+
background: none;
|
|
221
|
+
}
|
|
170
222
|
|
|
171
223
|
/* ── Chip ── */
|
|
172
224
|
.eb-chip {
|
|
@@ -291,6 +343,39 @@
|
|
|
291
343
|
background: color-mix(in srgb, var(--_color) 18%, transparent);
|
|
292
344
|
color: var(--_color);
|
|
293
345
|
}
|
|
346
|
+
.eb-location {
|
|
347
|
+
font: 400 9px / 1.2 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
348
|
+
color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
|
|
349
|
+
overflow: hidden;
|
|
350
|
+
text-overflow: ellipsis;
|
|
351
|
+
white-space: nowrap;
|
|
352
|
+
}
|
|
353
|
+
.eb-status-badge {
|
|
354
|
+
display: inline-flex;
|
|
355
|
+
align-items: center;
|
|
356
|
+
font: 700 8px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
357
|
+
letter-spacing: 0.06em;
|
|
358
|
+
text-transform: uppercase;
|
|
359
|
+
padding: 2px 6px;
|
|
360
|
+
border-radius: 3px;
|
|
361
|
+
width: fit-content;
|
|
362
|
+
}
|
|
363
|
+
.eb-cancelled-badge {
|
|
364
|
+
color: var(--dt-text-2, rgba(148, 163, 184, 0.65));
|
|
365
|
+
background: color-mix(in srgb, var(--dt-text-2, rgba(148, 163, 184, 0.3)) 15%, transparent);
|
|
366
|
+
}
|
|
367
|
+
.eb-tentative-badge {
|
|
368
|
+
color: var(--_color);
|
|
369
|
+
background: color-mix(in srgb, var(--_color) 12%, transparent);
|
|
370
|
+
}
|
|
371
|
+
.eb-full-badge {
|
|
372
|
+
color: #ef4444;
|
|
373
|
+
background: color-mix(in srgb, #ef4444 15%, transparent);
|
|
374
|
+
}
|
|
375
|
+
.eb-limited-badge {
|
|
376
|
+
color: var(--_color);
|
|
377
|
+
background: color-mix(in srgb, var(--_color) 12%, transparent);
|
|
378
|
+
}
|
|
294
379
|
.eb-live-badge {
|
|
295
380
|
display: inline-flex;
|
|
296
381
|
align-items: center;
|
|
@@ -12,14 +12,16 @@
|
|
|
12
12
|
* Future day ("The Plan"):
|
|
13
13
|
* Clean numbered schedule list.
|
|
14
14
|
*/
|
|
15
|
-
import { getContext } from 'svelte';
|
|
16
15
|
import { createClock } from '../../core/clock.svelte.js';
|
|
17
16
|
import type { TimelineEvent } from '../../core/types.js';
|
|
18
17
|
import { sod, DAY_MS, isAllDay, isMultiDay } from '../../core/time.js';
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
18
|
+
import { getLabels } from '../../core/locale.js';
|
|
19
|
+
import { useCalendarContext } from '../shared/context.svelte.js';
|
|
20
|
+
import { fmtTime, duration, timeUntilMs, progress, groupIntoSlots } from '../shared/format.js';
|
|
21
|
+
import type { TimeSlot } from '../shared/format.js';
|
|
21
22
|
|
|
22
23
|
const L = $derived(getLabels());
|
|
24
|
+
const ctx = useCalendarContext();
|
|
23
25
|
|
|
24
26
|
interface Props {
|
|
25
27
|
locale?: string;
|
|
@@ -43,17 +45,15 @@
|
|
|
43
45
|
}: Props = $props();
|
|
44
46
|
|
|
45
47
|
const clock = createClock();
|
|
46
|
-
const viewState =
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const isMobile = $derived(mobileCtx?.current ?? false);
|
|
56
|
-
const autoHeight = $derived(autoHeightCtx?.current ?? false);
|
|
48
|
+
const viewState = $derived(ctx.viewState);
|
|
49
|
+
const showNav = $derived(ctx.showNav);
|
|
50
|
+
const equalDays = $derived(ctx.equalDays);
|
|
51
|
+
const showDates = $derived(ctx.showDates);
|
|
52
|
+
const isMobile = $derived(ctx.isMobile);
|
|
53
|
+
const autoHeight = $derived(ctx.autoHeight);
|
|
54
|
+
const compact = $derived(ctx.compact);
|
|
55
|
+
const oneventhover = $derived(ctx.oneventhover);
|
|
56
|
+
const disabledSet = $derived(ctx.disabledSet);
|
|
57
57
|
|
|
58
58
|
// ── Swipe navigation (mobile) ──────────────────────
|
|
59
59
|
let swipeStartX = 0;
|
|
@@ -76,65 +76,10 @@
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
// ──
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const disabledDates = $derived(disabledDatesCtx?.current);
|
|
84
|
-
const disabledSet = $derived(new Set(disabledDates?.map(d => sod(d.getTime())) ?? []));
|
|
85
|
-
|
|
86
|
-
// ── Format helpers ──────────────────────────────────
|
|
87
|
-
function fmtTime(d: Date): string {
|
|
88
|
-
return _fmtTime(d, locale);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function duration(ev: TimelineEvent): string {
|
|
92
|
-
return fmtDuration(ev.start, ev.end);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function timeUntilMs(ms: number): string {
|
|
96
|
-
const diff = ms - clock.tick;
|
|
97
|
-
if (diff <= 0) return L.now;
|
|
98
|
-
const tMins = Math.floor(diff / 60000);
|
|
99
|
-
if (tMins < 60) return `in ${tMins}m`;
|
|
100
|
-
const hrs = Math.floor(tMins / 60);
|
|
101
|
-
const rm = tMins % 60;
|
|
102
|
-
if (hrs < 24) return rm > 0 ? `in ${hrs}h ${rm}m` : `in ${hrs}h`;
|
|
103
|
-
const days = Math.floor(hrs / 24);
|
|
104
|
-
return `in ${days}d`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function progress(ev: TimelineEvent): number {
|
|
108
|
-
const s = ev.start.getTime();
|
|
109
|
-
const e = ev.end.getTime();
|
|
110
|
-
return Math.min(1, Math.max(0, (clock.tick - s) / (e - s)));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ── Overlap grouping ────────────────────────────────
|
|
114
|
-
interface TimeSlot {
|
|
115
|
-
startMs: number;
|
|
116
|
-
endMs: number;
|
|
117
|
-
events: TimelineEvent[];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function groupIntoSlots(evts: TimelineEvent[]): TimeSlot[] {
|
|
121
|
-
const sorted = [...evts].sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
122
|
-
const slots: TimeSlot[] = [];
|
|
123
|
-
for (const ev of sorted) {
|
|
124
|
-
const last = slots[slots.length - 1];
|
|
125
|
-
if (last && ev.start.getTime() < last.endMs) {
|
|
126
|
-
last.events.push(ev);
|
|
127
|
-
last.endMs = Math.max(last.endMs, ev.end.getTime());
|
|
128
|
-
} else {
|
|
129
|
-
slots.push({
|
|
130
|
-
startMs: ev.start.getTime(),
|
|
131
|
-
endMs: ev.end.getTime(),
|
|
132
|
-
events: [ev],
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return slots;
|
|
137
|
-
}
|
|
79
|
+
// ── Format helpers (delegated to shared/format.ts) ──
|
|
80
|
+
const fmt = (d: Date) => fmtTime(d, locale);
|
|
81
|
+
const eta = (ms: number) => timeUntilMs(ms, clock.tick);
|
|
82
|
+
const prog = (ev: TimelineEvent) => progress(ev, clock.tick);
|
|
138
83
|
|
|
139
84
|
// ── Event handlers ──────────────────────────────────
|
|
140
85
|
function handleClick(ev: TimelineEvent): void {
|
|
@@ -244,7 +189,44 @@
|
|
|
244
189
|
</div>
|
|
245
190
|
{/if}
|
|
246
191
|
|
|
247
|
-
{#if
|
|
192
|
+
{#if compact}
|
|
193
|
+
<!-- ─── Compact: minimal text rows ─── -->
|
|
194
|
+
<div class="ag-compact-list">
|
|
195
|
+
{#if timedDayEvents.length === 0 && allDayBanner.length === 0}
|
|
196
|
+
<div class="ag-q-empty">{L.nothingScheduledYet}</div>
|
|
197
|
+
{:else}
|
|
198
|
+
{#each timedDayEvents as ev (ev.id)}
|
|
199
|
+
<div
|
|
200
|
+
class="ag-compact-row"
|
|
201
|
+
class:ag-compact-row--selected={selectedEventId === ev.id}
|
|
202
|
+
class:ag-compact-row--cancelled={ev.status === 'cancelled'}
|
|
203
|
+
class:ag-compact-row--tentative={ev.status === 'tentative'}
|
|
204
|
+
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
205
|
+
role="button"
|
|
206
|
+
tabindex="0"
|
|
207
|
+
aria-label="{ev.title}, {fmt(ev.start)}, {duration(ev)}"
|
|
208
|
+
onclick={() => handleClick(ev)}
|
|
209
|
+
onpointerenter={() => oneventhover?.(ev)}
|
|
210
|
+
onkeydown={(e) => handleKeydown(e, ev)}
|
|
211
|
+
>
|
|
212
|
+
<span class="ag-compact-row-dot"></span>
|
|
213
|
+
<span class="ag-compact-row-time">{fmt(ev.start)}</span>
|
|
214
|
+
<span class="ag-compact-row-title">{ev.title}</span>
|
|
215
|
+
{#if ev.subtitle}
|
|
216
|
+
<span class="ag-compact-row-sub">{ev.subtitle}</span>
|
|
217
|
+
{/if}
|
|
218
|
+
{#if ev.tags?.length}
|
|
219
|
+
{#each ev.tags as tag}
|
|
220
|
+
<span class="ag-compact-row-tag">{tag}</span>
|
|
221
|
+
{/each}
|
|
222
|
+
{/if}
|
|
223
|
+
<span class="ag-compact-row-dur">{duration(ev)}</span>
|
|
224
|
+
</div>
|
|
225
|
+
{/each}
|
|
226
|
+
{/if}
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
{:else if isToday}
|
|
248
230
|
<!-- ─── Today: "The Queue" — upcoming is the hero ─── -->
|
|
249
231
|
<div class="ag-q">
|
|
250
232
|
<!-- NOW column: past events stacked above NOW strip -->
|
|
@@ -258,7 +240,7 @@
|
|
|
258
240
|
class:ag-q-done-item--selected={selectedEventId === ev.id}
|
|
259
241
|
role="button"
|
|
260
242
|
tabindex="0"
|
|
261
|
-
aria-label="{ev.title}, {L.completed}, {
|
|
243
|
+
aria-label="{ev.title}, {L.completed}, {fmt(ev.start)}"
|
|
262
244
|
onclick={() => handleClick(ev)}
|
|
263
245
|
onkeydown={(e) => handleKeydown(e, ev)}
|
|
264
246
|
>
|
|
@@ -278,14 +260,14 @@
|
|
|
278
260
|
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
279
261
|
role="button"
|
|
280
262
|
tabindex="0"
|
|
281
|
-
aria-label="{ev.title}, {L.happeningNow}, {L.percentComplete(Math.round(
|
|
263
|
+
aria-label="{ev.title}, {L.happeningNow}, {L.percentComplete(Math.round(prog(ev) * 100))}"
|
|
282
264
|
onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
|
|
283
265
|
>
|
|
284
266
|
<div class="ag-q-now-dot"></div>
|
|
285
267
|
<div class="ag-q-now-title">{ev.title}</div>
|
|
286
|
-
<div class="ag-q-now-time">{L.until} {
|
|
268
|
+
<div class="ag-q-now-time">{L.until} {fmt(ev.end)}</div>
|
|
287
269
|
<div class="ag-q-now-track">
|
|
288
|
-
<div class="ag-q-now-fill" style:width="{
|
|
270
|
+
<div class="ag-q-now-fill" style:width="{prog(ev) * 100}%"></div>
|
|
289
271
|
</div>
|
|
290
272
|
</div>
|
|
291
273
|
{/each}
|
|
@@ -312,7 +294,7 @@
|
|
|
312
294
|
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
313
295
|
role="button"
|
|
314
296
|
tabindex="0"
|
|
315
|
-
aria-label="{ev.title}, {
|
|
297
|
+
aria-label="{ev.title}, {fmt(ev.start)}, {duration(ev)}"
|
|
316
298
|
onclick={() => handleClick(ev)}
|
|
317
299
|
onpointerenter={() => oneventhover?.(ev)}
|
|
318
300
|
onkeydown={(e) => handleKeydown(e, ev)}
|
|
@@ -320,13 +302,13 @@
|
|
|
320
302
|
<div class="ag-card-body">
|
|
321
303
|
<div class="ag-card-top">
|
|
322
304
|
<span class="ag-card-title">{ev.title}</span>
|
|
323
|
-
<span class="ag-card-eta">{
|
|
305
|
+
<span class="ag-card-eta">{eta(ev.start.getTime())}</span>
|
|
324
306
|
</div>
|
|
325
307
|
{#if ev.subtitle}
|
|
326
308
|
<span class="ag-card-sub">{ev.subtitle}</span>
|
|
327
309
|
{/if}
|
|
328
310
|
<div class="ag-card-meta">
|
|
329
|
-
{
|
|
311
|
+
{fmt(ev.start)} – {fmt(ev.end)}
|
|
330
312
|
<span class="ag-card-dur">{duration(ev)}</span>
|
|
331
313
|
</div>
|
|
332
314
|
{#if ev.tags?.length}
|
|
@@ -356,11 +338,11 @@
|
|
|
356
338
|
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
357
339
|
role="button"
|
|
358
340
|
tabindex="0"
|
|
359
|
-
aria-label="{ev.title}, {
|
|
341
|
+
aria-label="{ev.title}, {fmt(ev.start)} to {fmt(ev.end)}"
|
|
360
342
|
onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
|
|
361
343
|
>
|
|
362
344
|
<span class="ag-log-check">✓</span>
|
|
363
|
-
<span class="ag-log-time">{
|
|
345
|
+
<span class="ag-log-time">{fmt(ev.start)}</span>
|
|
364
346
|
<span class="ag-log-dot" style:background={ev.color || 'var(--dt-accent)'}></span>
|
|
365
347
|
<span class="ag-log-title">{ev.title}</span>
|
|
366
348
|
<span class="ag-log-dur">{duration(ev)}</span>
|
|
@@ -380,10 +362,14 @@
|
|
|
380
362
|
class="ag-card ag-card--plan"
|
|
381
363
|
class:ag-card--first={i === 0}
|
|
382
364
|
class:ag-card--selected={selectedEventId === ev.id}
|
|
365
|
+
class:ag-card--cancelled={ev.status === 'cancelled'}
|
|
366
|
+
class:ag-card--tentative={ev.status === 'tentative'}
|
|
367
|
+
class:ag-card--full={ev.status === 'full'}
|
|
368
|
+
class:ag-card--limited={ev.status === 'limited'}
|
|
383
369
|
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
384
370
|
role="button"
|
|
385
371
|
tabindex="0"
|
|
386
|
-
aria-label="{ev.title}, {
|
|
372
|
+
aria-label="{ev.title}{ev.status === 'cancelled' ? ' (cancelled)' : ''}{ev.status === 'tentative' ? ' (tentative)' : ''}{ev.status === 'full' ? ' (full)' : ''}{ev.status === 'limited' ? ' (limited)' : ''}, {fmt(ev.start)} to {fmt(ev.end)}, {duration(ev)}"
|
|
387
373
|
onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
|
|
388
374
|
>
|
|
389
375
|
<div class="ag-card-body">
|
|
@@ -394,8 +380,11 @@
|
|
|
394
380
|
{#if ev.subtitle}
|
|
395
381
|
<span class="ag-card-sub">{ev.subtitle}</span>
|
|
396
382
|
{/if}
|
|
383
|
+
{#if ev.location}
|
|
384
|
+
<span class="ag-card-loc">{ev.location}</span>
|
|
385
|
+
{/if}
|
|
397
386
|
<div class="ag-card-meta">
|
|
398
|
-
{
|
|
387
|
+
{fmt(ev.start)} – {fmt(ev.end)}
|
|
399
388
|
<span class="ag-card-dur">{duration(ev)}</span>
|
|
400
389
|
</div>
|
|
401
390
|
{#if ev.tags?.length}
|
|
@@ -528,6 +517,9 @@
|
|
|
528
517
|
display: flex;
|
|
529
518
|
flex-direction: column;
|
|
530
519
|
height: 100%;
|
|
520
|
+
width: 100%;
|
|
521
|
+
min-width: 0;
|
|
522
|
+
box-sizing: border-box;
|
|
531
523
|
color: var(--dt-text, rgba(255, 255, 255, 0.92));
|
|
532
524
|
font-family: var(--dt-sans, system-ui, sans-serif);
|
|
533
525
|
}
|
|
@@ -543,9 +535,11 @@
|
|
|
543
535
|
.ag-body {
|
|
544
536
|
flex: 1;
|
|
545
537
|
min-height: 0;
|
|
538
|
+
min-width: 0;
|
|
546
539
|
display: flex;
|
|
547
540
|
flex-direction: column;
|
|
548
541
|
overflow-y: auto;
|
|
542
|
+
overflow-x: hidden;
|
|
549
543
|
padding-top: 44px;
|
|
550
544
|
scrollbar-width: thin;
|
|
551
545
|
scrollbar-color: var(--dt-border) transparent;
|
|
@@ -640,6 +634,23 @@
|
|
|
640
634
|
border-color: var(--ev-color);
|
|
641
635
|
background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #191919));
|
|
642
636
|
}
|
|
637
|
+
.ag-card--cancelled {
|
|
638
|
+
opacity: 0.5;
|
|
639
|
+
}
|
|
640
|
+
.ag-card--cancelled .ag-card-title {
|
|
641
|
+
text-decoration: line-through;
|
|
642
|
+
}
|
|
643
|
+
.ag-card--tentative {
|
|
644
|
+
opacity: 0.65;
|
|
645
|
+
border-style: dashed;
|
|
646
|
+
}
|
|
647
|
+
.ag-card--full {
|
|
648
|
+
opacity: 0.55;
|
|
649
|
+
}
|
|
650
|
+
.ag-card--limited {
|
|
651
|
+
opacity: 0.65;
|
|
652
|
+
border-style: dashed;
|
|
653
|
+
}
|
|
643
654
|
.ag-card-body {
|
|
644
655
|
padding: 10px 12px;
|
|
645
656
|
display: flex;
|
|
@@ -653,15 +664,15 @@
|
|
|
653
664
|
justify-content: space-between;
|
|
654
665
|
align-items: flex-start;
|
|
655
666
|
gap: 8px;
|
|
667
|
+
min-width: 0;
|
|
656
668
|
}
|
|
657
669
|
.ag-card-title {
|
|
658
670
|
font-size: 13px;
|
|
659
671
|
font-weight: 600;
|
|
660
|
-
line-height: 1.
|
|
661
|
-
|
|
662
|
-
overflow: hidden;
|
|
663
|
-
text-overflow: ellipsis;
|
|
672
|
+
line-height: 1.3;
|
|
673
|
+
word-break: break-word;
|
|
664
674
|
flex: 1;
|
|
675
|
+
min-width: 0;
|
|
665
676
|
}
|
|
666
677
|
.ag-card-meta {
|
|
667
678
|
font-size: 11px;
|
|
@@ -678,6 +689,11 @@
|
|
|
678
689
|
color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
|
|
679
690
|
line-height: 1;
|
|
680
691
|
}
|
|
692
|
+
.ag-card-loc {
|
|
693
|
+
font-size: 10px;
|
|
694
|
+
color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
|
|
695
|
+
line-height: 1;
|
|
696
|
+
}
|
|
681
697
|
.ag-card-tags {
|
|
682
698
|
display: flex;
|
|
683
699
|
gap: 4px;
|
|
@@ -1057,6 +1073,84 @@
|
|
|
1057
1073
|
flex-shrink: 0;
|
|
1058
1074
|
}
|
|
1059
1075
|
|
|
1076
|
+
/* ═══ Compact Day ═══ */
|
|
1077
|
+
.ag-compact-list {
|
|
1078
|
+
flex: 1;
|
|
1079
|
+
padding: 8px 20px 12px;
|
|
1080
|
+
overflow-y: auto;
|
|
1081
|
+
scrollbar-width: none;
|
|
1082
|
+
}
|
|
1083
|
+
.ag-compact-list::-webkit-scrollbar { display: none; }
|
|
1084
|
+
.ag-compact-row {
|
|
1085
|
+
display: flex;
|
|
1086
|
+
align-items: center;
|
|
1087
|
+
gap: 8px;
|
|
1088
|
+
padding: 4px 0;
|
|
1089
|
+
cursor: pointer;
|
|
1090
|
+
}
|
|
1091
|
+
.ag-compact-row--selected {
|
|
1092
|
+
background: color-mix(in srgb, var(--ev-color) 10%, transparent);
|
|
1093
|
+
border-radius: 4px;
|
|
1094
|
+
padding-left: 6px;
|
|
1095
|
+
padding-right: 6px;
|
|
1096
|
+
}
|
|
1097
|
+
.ag-compact-row:hover .ag-compact-row-title { color: var(--dt-text); }
|
|
1098
|
+
.ag-compact-row:focus-visible {
|
|
1099
|
+
outline: 2px solid var(--dt-accent, #ff6b4a);
|
|
1100
|
+
outline-offset: 2px;
|
|
1101
|
+
}
|
|
1102
|
+
.ag-compact-row-dot {
|
|
1103
|
+
width: 5px;
|
|
1104
|
+
height: 5px;
|
|
1105
|
+
border-radius: 50%;
|
|
1106
|
+
background: var(--ev-color, var(--dt-accent));
|
|
1107
|
+
flex-shrink: 0;
|
|
1108
|
+
}
|
|
1109
|
+
.ag-compact-row-time {
|
|
1110
|
+
font-size: 11px;
|
|
1111
|
+
font-family: var(--dt-mono, monospace);
|
|
1112
|
+
color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
|
|
1113
|
+
min-width: 64px;
|
|
1114
|
+
flex-shrink: 0;
|
|
1115
|
+
}
|
|
1116
|
+
.ag-compact-row-title {
|
|
1117
|
+
font-size: 12px;
|
|
1118
|
+
font-weight: 500;
|
|
1119
|
+
color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
|
|
1120
|
+
flex: 1;
|
|
1121
|
+
white-space: nowrap;
|
|
1122
|
+
overflow: hidden;
|
|
1123
|
+
text-overflow: ellipsis;
|
|
1124
|
+
transition: color 150ms;
|
|
1125
|
+
}
|
|
1126
|
+
.ag-compact-row-dur {
|
|
1127
|
+
font-size: 10px;
|
|
1128
|
+
font-family: var(--dt-mono, monospace);
|
|
1129
|
+
color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
|
|
1130
|
+
flex-shrink: 0;
|
|
1131
|
+
}
|
|
1132
|
+
.ag-compact-row-sub {
|
|
1133
|
+
font-size: 10px;
|
|
1134
|
+
color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
|
|
1135
|
+
flex-shrink: 0;
|
|
1136
|
+
}
|
|
1137
|
+
.ag-compact-row-tag {
|
|
1138
|
+
font: 500 8px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
1139
|
+
color: var(--ev-color, var(--dt-accent));
|
|
1140
|
+
background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 12%, transparent);
|
|
1141
|
+
padding: 1px 4px;
|
|
1142
|
+
border-radius: 3px;
|
|
1143
|
+
white-space: nowrap;
|
|
1144
|
+
flex-shrink: 0;
|
|
1145
|
+
}
|
|
1146
|
+
.ag-compact-row--cancelled { opacity: 0.5; }
|
|
1147
|
+
.ag-compact-row--cancelled .ag-compact-row-title { text-decoration: line-through; }
|
|
1148
|
+
.ag-compact-row--tentative { opacity: 0.65; }
|
|
1149
|
+
/* Mobile: larger touch targets for compact rows */
|
|
1150
|
+
.ag--mobile .ag-compact-row { padding: 8px 0; }
|
|
1151
|
+
.ag--mobile .ag-compact-row-title { font-size: 14px; }
|
|
1152
|
+
.ag--mobile .ag-compact-row-time { font-size: 12px; }
|
|
1153
|
+
|
|
1060
1154
|
/* ═══ Future Day: "The Plan" ═══ */
|
|
1061
1155
|
.ag-plan {
|
|
1062
1156
|
flex: 1;
|