@nomideusz/svelte-calendar 0.6.3 → 0.6.5
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 +192 -10
- 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/assets/favicon.svg +9 -1
- package/dist/calendar/Calendar.svelte +161 -90
- 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/DayHeader.svelte +6 -6
- package/dist/primitives/EmptySlot.svelte +3 -3
- package/dist/primitives/EventBlock.svelte +99 -14
- package/dist/primitives/NowIndicator.svelte +5 -5
- package/dist/views/agenda/AgendaDay.svelte +215 -116
- package/dist/views/agenda/AgendaWeek.svelte +166 -113
- package/dist/views/mobile/MobileDay.svelte +45 -21
- package/dist/views/mobile/MobileWeek.svelte +85 -35
- package/dist/views/planner/PlannerDay.svelte +116 -60
- package/dist/views/planner/PlannerWeek.svelte +228 -166
- 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 +9 -2
|
@@ -9,14 +9,15 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Answers: "What's coming up and when do I need to be ready?"
|
|
11
11
|
*/
|
|
12
|
-
import { getContext, type Snippet } from 'svelte';
|
|
13
12
|
import { createClock } from '../../core/clock.svelte.js';
|
|
14
13
|
import type { TimelineEvent } from '../../core/types.js';
|
|
15
14
|
import { sod, DAY_MS, startOfWeek, dayNum, isAllDay, isMultiDay } from '../../core/time.js';
|
|
16
|
-
import { weekdayLong, monthLong,
|
|
17
|
-
import
|
|
15
|
+
import { weekdayLong, monthLong, getLabels } from '../../core/locale.js';
|
|
16
|
+
import { useCalendarContext } from '../shared/context.svelte.js';
|
|
17
|
+
import { fmtTime, duration, timeUntilMs, progress, groupIntoSlots } from '../shared/format.js';
|
|
18
18
|
|
|
19
19
|
const L = $derived(getLabels());
|
|
20
|
+
const ctx = useCalendarContext();
|
|
20
21
|
|
|
21
22
|
interface Props {
|
|
22
23
|
mondayStart?: boolean;
|
|
@@ -42,19 +43,17 @@
|
|
|
42
43
|
}: Props = $props();
|
|
43
44
|
|
|
44
45
|
const clock = createClock();
|
|
45
|
-
const viewState =
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const isMobile = $derived(mobileCtx?.current ?? false);
|
|
57
|
-
const autoHeight = $derived(autoHeightCtx?.current ?? false);
|
|
46
|
+
const viewState = $derived(ctx.viewState);
|
|
47
|
+
const showNav = $derived(ctx.showNav);
|
|
48
|
+
const equalDays = $derived(ctx.equalDays);
|
|
49
|
+
const showDates = $derived(ctx.showDates);
|
|
50
|
+
const hideDays = $derived(ctx.hideDays);
|
|
51
|
+
const isMobile = $derived(ctx.isMobile);
|
|
52
|
+
const autoHeight = $derived(ctx.autoHeight);
|
|
53
|
+
const compact = $derived(ctx.compact);
|
|
54
|
+
const dayHeaderSnippet = $derived(ctx.dayHeaderSnippet);
|
|
55
|
+
const oneventhover = $derived(ctx.oneventhover);
|
|
56
|
+
const disabledSet = $derived(ctx.disabledSet);
|
|
58
57
|
|
|
59
58
|
// ── Swipe navigation (mobile) ──────────────────────
|
|
60
59
|
let swipeStartX = 0;
|
|
@@ -77,68 +76,12 @@
|
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
// ──
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
const oneventhover = $derived(callbacksCtx?.oneventhover);
|
|
87
|
-
const disabledDates = $derived(disabledDatesCtx?.current);
|
|
88
|
-
const disabledSet = $derived(new Set(disabledDates?.map(d => sod(d.getTime())) ?? []));
|
|
89
|
-
|
|
90
|
-
// ── Format helpers ──────────────────────────────────
|
|
91
|
-
function fmtTime(d: Date): string {
|
|
92
|
-
return _fmtTime(d, locale);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function duration(ev: TimelineEvent): string {
|
|
96
|
-
return fmtDuration(ev.start, ev.end);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function timeUntilMs(ms: number): string {
|
|
100
|
-
const diff = ms - clock.tick;
|
|
101
|
-
if (diff <= 0) return L.now;
|
|
102
|
-
const tMins = Math.floor(diff / 60000);
|
|
103
|
-
if (tMins < 60) return `in ${tMins}m`;
|
|
104
|
-
const hrs = Math.floor(tMins / 60);
|
|
105
|
-
const rm = tMins % 60;
|
|
106
|
-
if (hrs < 24) return rm > 0 ? `in ${hrs}h ${rm}m` : `in ${hrs}h`;
|
|
107
|
-
const days = Math.floor(hrs / 24);
|
|
108
|
-
return `in ${days}d`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function progress(ev: TimelineEvent): number {
|
|
112
|
-
const s = ev.start.getTime();
|
|
113
|
-
const e = ev.end.getTime();
|
|
114
|
-
return Math.min(1, Math.max(0, (clock.tick - s) / (e - s)));
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ── Overlap grouping ────────────────────────────────
|
|
118
|
-
interface TimeSlot {
|
|
119
|
-
startMs: number;
|
|
120
|
-
endMs: number;
|
|
121
|
-
events: TimelineEvent[];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function groupIntoSlots(evts: TimelineEvent[]): TimeSlot[] {
|
|
125
|
-
const sorted = [...evts].sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
126
|
-
const slots: TimeSlot[] = [];
|
|
127
|
-
for (const ev of sorted) {
|
|
128
|
-
const last = slots[slots.length - 1];
|
|
129
|
-
if (last && ev.start.getTime() < last.endMs) {
|
|
130
|
-
last.events.push(ev);
|
|
131
|
-
last.endMs = Math.max(last.endMs, ev.end.getTime());
|
|
132
|
-
} else {
|
|
133
|
-
slots.push({
|
|
134
|
-
startMs: ev.start.getTime(),
|
|
135
|
-
endMs: ev.end.getTime(),
|
|
136
|
-
events: [ev],
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return slots;
|
|
141
|
-
}
|
|
79
|
+
// ── Format helpers (delegated to shared/format.ts) ──
|
|
80
|
+
// fmtTime, duration, groupIntoSlots imported at top
|
|
81
|
+
// Thin wrappers that bind locale / clock.tick:
|
|
82
|
+
const fmt = (d: Date) => fmtTime(d, locale);
|
|
83
|
+
const eta = (ms: number) => timeUntilMs(ms, clock.tick);
|
|
84
|
+
const prog = (ev: TimelineEvent) => progress(ev, clock.tick);
|
|
142
85
|
|
|
143
86
|
// ── Event handlers ──────────────────────────────────
|
|
144
87
|
function handleClick(ev: TimelineEvent): void {
|
|
@@ -259,10 +202,14 @@
|
|
|
259
202
|
<div
|
|
260
203
|
class="ag-card"
|
|
261
204
|
class:ag-card--selected={selectedEventId === ev.id}
|
|
205
|
+
class:ag-card--cancelled={ev.status === 'cancelled'}
|
|
206
|
+
class:ag-card--tentative={ev.status === 'tentative'}
|
|
207
|
+
class:ag-card--full={ev.status === 'full'}
|
|
208
|
+
class:ag-card--limited={ev.status === 'limited'}
|
|
262
209
|
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
263
210
|
role="button"
|
|
264
211
|
tabindex="0"
|
|
265
|
-
aria-label="{ev.title}, {
|
|
212
|
+
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)}"
|
|
266
213
|
onclick={() => handleClick(ev)}
|
|
267
214
|
onpointerenter={() => oneventhover?.(ev)}
|
|
268
215
|
onkeydown={(e) => handleKeydown(e, ev)}
|
|
@@ -272,11 +219,14 @@
|
|
|
272
219
|
{#if ev.subtitle}
|
|
273
220
|
<span class="ag-card-sub">{ev.subtitle}</span>
|
|
274
221
|
{/if}
|
|
222
|
+
{#if ev.location}
|
|
223
|
+
<span class="ag-card-loc">{ev.location}</span>
|
|
224
|
+
{/if}
|
|
275
225
|
<span class="ag-card-meta">
|
|
276
226
|
{#if isNow}
|
|
277
|
-
{L.until} {
|
|
227
|
+
{L.until} {fmt(ev.end)}
|
|
278
228
|
{:else}
|
|
279
|
-
{
|
|
229
|
+
{fmt(ev.start)} – {fmt(ev.end)}
|
|
280
230
|
{/if}
|
|
281
231
|
<span class="ag-card-dur">{duration(ev)}</span>
|
|
282
232
|
{#if eta}
|
|
@@ -292,7 +242,7 @@
|
|
|
292
242
|
{/if}
|
|
293
243
|
{#if isNow}
|
|
294
244
|
<div class="ag-card-progress">
|
|
295
|
-
<div class="ag-card-progress-fill" style:width="{
|
|
245
|
+
<div class="ag-card-progress-fill" style:width="{prog(ev) * 100}%"></div>
|
|
296
246
|
</div>
|
|
297
247
|
{/if}
|
|
298
248
|
</div>
|
|
@@ -376,6 +326,40 @@
|
|
|
376
326
|
|
|
377
327
|
{#if day.events.length === 0}
|
|
378
328
|
<div class="ag-wday-empty">{L.noEvents}</div>
|
|
329
|
+
{:else if compact}
|
|
330
|
+
<!-- Compact: minimal dot + time + title rows for all days -->
|
|
331
|
+
<div class="ag-wday-compact">
|
|
332
|
+
{#each day.timedEvents as ev (ev.id)}
|
|
333
|
+
<div
|
|
334
|
+
class="ag-compact"
|
|
335
|
+
class:ag-compact--selected={selectedEventId === ev.id}
|
|
336
|
+
class:ag-compact--cancelled={ev.status === 'cancelled'}
|
|
337
|
+
class:ag-compact--tentative={ev.status === 'tentative'}
|
|
338
|
+
class:ag-compact--full={ev.status === 'full'}
|
|
339
|
+
class:ag-compact--limited={ev.status === 'limited'}
|
|
340
|
+
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
341
|
+
role="button"
|
|
342
|
+
tabindex="0"
|
|
343
|
+
aria-label="{ev.title}, {fmt(ev.start)}, {duration(ev)}"
|
|
344
|
+
onclick={() => handleClick(ev)}
|
|
345
|
+
onpointerenter={() => oneventhover?.(ev)}
|
|
346
|
+
onkeydown={(e) => handleKeydown(e, ev)}
|
|
347
|
+
>
|
|
348
|
+
<span class="ag-compact-dot"></span>
|
|
349
|
+
<span class="ag-compact-time">{fmt(ev.start)}</span>
|
|
350
|
+
<span class="ag-compact-title">{ev.title}</span>
|
|
351
|
+
{#if ev.subtitle}
|
|
352
|
+
<span class="ag-compact-sub">{ev.subtitle}</span>
|
|
353
|
+
{/if}
|
|
354
|
+
{#if ev.tags?.length}
|
|
355
|
+
{#each ev.tags as tag}
|
|
356
|
+
<span class="ag-compact-tag">{tag}</span>
|
|
357
|
+
{/each}
|
|
358
|
+
{/if}
|
|
359
|
+
<span class="ag-compact-dur">{duration(ev)}</span>
|
|
360
|
+
</div>
|
|
361
|
+
{/each}
|
|
362
|
+
</div>
|
|
379
363
|
{:else if equalDays}
|
|
380
364
|
<!-- Equal days: card layout for all days, no time-relative badges -->
|
|
381
365
|
<div class="ag-wday-expanded">
|
|
@@ -408,7 +392,7 @@
|
|
|
408
392
|
|
|
409
393
|
<div class="ag-wslot-cards" class:ag-wslot-cards--multi={slot.events.length > 1}>
|
|
410
394
|
{#each slot.events as ev (ev.id)}
|
|
411
|
-
{@render eventCard(ev, false, day.tier === 'today' ?
|
|
395
|
+
{@render eventCard(ev, false, day.tier === 'today' ? eta(ev.start.getTime()) : undefined)}
|
|
412
396
|
{/each}
|
|
413
397
|
</div>
|
|
414
398
|
</div>
|
|
@@ -424,15 +408,22 @@
|
|
|
424
408
|
<div
|
|
425
409
|
class="ag-compact"
|
|
426
410
|
class:ag-compact--selected={selectedEventId === ev.id}
|
|
411
|
+
class:ag-compact--cancelled={ev.status === 'cancelled'}
|
|
412
|
+
class:ag-compact--tentative={ev.status === 'tentative'}
|
|
413
|
+
class:ag-compact--full={ev.status === 'full'}
|
|
414
|
+
class:ag-compact--limited={ev.status === 'limited'}
|
|
427
415
|
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
428
416
|
role="button"
|
|
429
417
|
tabindex="0"
|
|
430
|
-
aria-label="{ev.title}, {
|
|
418
|
+
aria-label="{ev.title}, {fmt(ev.start)}, {duration(ev)}"
|
|
431
419
|
onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
|
|
432
420
|
>
|
|
433
421
|
<span class="ag-compact-dot"></span>
|
|
434
|
-
<span class="ag-compact-time">{
|
|
422
|
+
<span class="ag-compact-time">{fmt(ev.start)}</span>
|
|
435
423
|
<span class="ag-compact-title">{ev.title}</span>
|
|
424
|
+
{#if ev.location}
|
|
425
|
+
<span class="ag-compact-loc">{ev.location}</span>
|
|
426
|
+
{/if}
|
|
436
427
|
{#if ev.subtitle}
|
|
437
428
|
<span class="ag-compact-sub">{ev.subtitle}</span>
|
|
438
429
|
{/if}
|
|
@@ -493,7 +484,7 @@
|
|
|
493
484
|
z-index: 20;
|
|
494
485
|
display: flex;
|
|
495
486
|
gap: 2px;
|
|
496
|
-
background: color-mix(in srgb, var(--dt-surface, #
|
|
487
|
+
background: color-mix(in srgb, var(--dt-surface, var(--dt-bg, #ffffff)) 85%, transparent);
|
|
497
488
|
backdrop-filter: blur(6px);
|
|
498
489
|
-webkit-backdrop-filter: blur(6px);
|
|
499
490
|
border-radius: 8px;
|
|
@@ -505,7 +496,7 @@
|
|
|
505
496
|
background: transparent;
|
|
506
497
|
color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
|
|
507
498
|
cursor: pointer;
|
|
508
|
-
font: 600 11px / 1 var(--dt-sans,
|
|
499
|
+
font: 600 11px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
509
500
|
padding: 6px 12px;
|
|
510
501
|
border-radius: 6px;
|
|
511
502
|
letter-spacing: 0.04em;
|
|
@@ -532,7 +523,7 @@
|
|
|
532
523
|
pointer-events: none;
|
|
533
524
|
}
|
|
534
525
|
.ag-nav-pill:focus-visible {
|
|
535
|
-
outline: 2px solid color-mix(in srgb, var(--dt-accent, #
|
|
526
|
+
outline: 2px solid color-mix(in srgb, var(--dt-accent, #2563eb) 55%, transparent);
|
|
536
527
|
outline-offset: 2px;
|
|
537
528
|
}
|
|
538
529
|
|
|
@@ -544,6 +535,9 @@
|
|
|
544
535
|
display: flex;
|
|
545
536
|
flex-direction: column;
|
|
546
537
|
height: 100%;
|
|
538
|
+
width: 100%;
|
|
539
|
+
min-width: 0;
|
|
540
|
+
box-sizing: border-box;
|
|
547
541
|
color: var(--dt-text, rgba(255, 255, 255, 0.92));
|
|
548
542
|
font-family: var(--dt-sans, system-ui, sans-serif);
|
|
549
543
|
}
|
|
@@ -555,7 +549,11 @@
|
|
|
555
549
|
/* ═══ Body ═══ */
|
|
556
550
|
.ag-body {
|
|
557
551
|
flex: 1;
|
|
552
|
+
min-width: 0;
|
|
558
553
|
overflow-y: auto;
|
|
554
|
+
overflow-x: hidden;
|
|
555
|
+
box-sizing: border-box;
|
|
556
|
+
padding-top: 44px;
|
|
559
557
|
scrollbar-width: thin;
|
|
560
558
|
scrollbar-color: var(--dt-border) transparent;
|
|
561
559
|
}
|
|
@@ -583,22 +581,22 @@
|
|
|
583
581
|
gap: 4px;
|
|
584
582
|
padding: 2px 8px;
|
|
585
583
|
border-radius: 5px;
|
|
586
|
-
background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, #
|
|
584
|
+
background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
587
585
|
border: 1px solid color-mix(in srgb, var(--ev-color) 18%, transparent);
|
|
588
586
|
cursor: pointer;
|
|
589
587
|
transition: background 0.15s, border-color 0.15s;
|
|
590
588
|
}
|
|
591
589
|
.ag-allday-chip:hover {
|
|
592
|
-
background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #
|
|
590
|
+
background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
593
591
|
border-color: color-mix(in srgb, var(--ev-color) 30%, transparent);
|
|
594
592
|
}
|
|
595
593
|
.ag-allday-chip:focus-visible {
|
|
596
|
-
outline: 2px solid var(--dt-accent, #
|
|
594
|
+
outline: 2px solid var(--dt-accent, #2563eb);
|
|
597
595
|
outline-offset: 2px;
|
|
598
596
|
}
|
|
599
597
|
.ag-allday-chip--selected {
|
|
600
598
|
border-color: var(--ev-color);
|
|
601
|
-
background: color-mix(in srgb, var(--ev-color) 18%, var(--dt-surface, #
|
|
599
|
+
background: color-mix(in srgb, var(--ev-color) 18%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
602
600
|
}
|
|
603
601
|
.ag-allday-dot {
|
|
604
602
|
width: 5px;
|
|
@@ -618,23 +616,40 @@
|
|
|
618
616
|
display: flex;
|
|
619
617
|
align-items: stretch;
|
|
620
618
|
border-radius: 6px;
|
|
621
|
-
background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, #
|
|
619
|
+
background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
622
620
|
border: 1px solid color-mix(in srgb, var(--ev-color) 8%, var(--dt-border, rgba(255, 255, 255, 0.06)));
|
|
623
621
|
overflow: hidden;
|
|
624
622
|
cursor: pointer;
|
|
625
623
|
transition: background 150ms, border-color 150ms;
|
|
626
624
|
}
|
|
627
625
|
.ag-card:hover {
|
|
628
|
-
background: color-mix(in srgb, var(--ev-color) 20%, var(--dt-surface, #
|
|
626
|
+
background: color-mix(in srgb, var(--ev-color) 20%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
629
627
|
border-color: color-mix(in srgb, var(--ev-color) 30%, transparent);
|
|
630
628
|
}
|
|
631
629
|
.ag-card:focus-visible {
|
|
632
|
-
outline: 2px solid var(--dt-accent, #
|
|
630
|
+
outline: 2px solid var(--dt-accent, #2563eb);
|
|
633
631
|
outline-offset: 2px;
|
|
634
632
|
}
|
|
635
633
|
.ag-card--selected {
|
|
636
634
|
border-color: var(--ev-color);
|
|
637
|
-
background: color-mix(in srgb, var(--ev-color) 20%, var(--dt-surface, #
|
|
635
|
+
background: color-mix(in srgb, var(--ev-color) 20%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
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;
|
|
638
653
|
}
|
|
639
654
|
.ag-card-body {
|
|
640
655
|
padding: 7px 10px;
|
|
@@ -647,11 +662,10 @@
|
|
|
647
662
|
.ag-card-title {
|
|
648
663
|
font-size: 13px;
|
|
649
664
|
font-weight: 600;
|
|
650
|
-
line-height: 1.
|
|
651
|
-
|
|
652
|
-
overflow: hidden;
|
|
653
|
-
text-overflow: ellipsis;
|
|
665
|
+
line-height: 1.3;
|
|
666
|
+
word-break: break-word;
|
|
654
667
|
flex: 1;
|
|
668
|
+
min-width: 0;
|
|
655
669
|
}
|
|
656
670
|
.ag-card-meta {
|
|
657
671
|
display: flex;
|
|
@@ -678,6 +692,11 @@
|
|
|
678
692
|
color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
|
|
679
693
|
line-height: 1;
|
|
680
694
|
}
|
|
695
|
+
.ag-card-loc {
|
|
696
|
+
font-size: 10px;
|
|
697
|
+
color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
|
|
698
|
+
line-height: 1;
|
|
699
|
+
}
|
|
681
700
|
.ag-card-tags {
|
|
682
701
|
display: flex;
|
|
683
702
|
gap: 4px;
|
|
@@ -710,7 +729,7 @@
|
|
|
710
729
|
border-bottom: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
|
|
711
730
|
}
|
|
712
731
|
.ag-wday--today {
|
|
713
|
-
background: color-mix(in srgb, var(--dt-accent, #
|
|
732
|
+
background: color-mix(in srgb, var(--dt-accent, #2563eb) 2%, transparent);
|
|
714
733
|
}
|
|
715
734
|
.ag-wday--tomorrow .ag-card {
|
|
716
735
|
opacity: 0.82;
|
|
@@ -722,8 +741,6 @@
|
|
|
722
741
|
padding: 8px 20px;
|
|
723
742
|
}
|
|
724
743
|
.ag-wday--disabled {
|
|
725
|
-
opacity: 0.35;
|
|
726
|
-
pointer-events: none;
|
|
727
744
|
position: relative;
|
|
728
745
|
}
|
|
729
746
|
.ag-wday--disabled::after {
|
|
@@ -747,7 +764,7 @@
|
|
|
747
764
|
display: flex;
|
|
748
765
|
justify-content: space-between;
|
|
749
766
|
align-items: center;
|
|
750
|
-
padding:
|
|
767
|
+
padding: 8px 20px;
|
|
751
768
|
}
|
|
752
769
|
.ag-wday-head-left {
|
|
753
770
|
display: flex;
|
|
@@ -759,8 +776,8 @@
|
|
|
759
776
|
font-weight: 600;
|
|
760
777
|
letter-spacing: 0.08em;
|
|
761
778
|
text-transform: uppercase;
|
|
762
|
-
color: var(--dt-accent, #
|
|
763
|
-
background: color-mix(in srgb, var(--dt-accent, #
|
|
779
|
+
color: var(--dt-accent, #2563eb);
|
|
780
|
+
background: color-mix(in srgb, var(--dt-accent, #2563eb) 12%, transparent);
|
|
764
781
|
padding: 2px 7px;
|
|
765
782
|
border-radius: 3px;
|
|
766
783
|
}
|
|
@@ -783,7 +800,7 @@
|
|
|
783
800
|
}
|
|
784
801
|
|
|
785
802
|
.ag-wday-empty {
|
|
786
|
-
padding: 2px 20px
|
|
803
|
+
padding: 2px 20px 6px;
|
|
787
804
|
font-size: 11px;
|
|
788
805
|
color: var(--dt-text-3, rgba(255, 255, 255, 0.2));
|
|
789
806
|
font-style: italic;
|
|
@@ -807,7 +824,7 @@
|
|
|
807
824
|
font-weight: 700;
|
|
808
825
|
letter-spacing: 0.08em;
|
|
809
826
|
text-transform: uppercase;
|
|
810
|
-
color: var(--dt-accent, #
|
|
827
|
+
color: var(--dt-accent, #2563eb);
|
|
811
828
|
}
|
|
812
829
|
.ag-wslot-cards {
|
|
813
830
|
display: flex;
|
|
@@ -828,14 +845,16 @@
|
|
|
828
845
|
|
|
829
846
|
/* Compact day events */
|
|
830
847
|
.ag-wday-compact {
|
|
831
|
-
padding: 0 20px
|
|
848
|
+
padding: 0 20px 6px;
|
|
832
849
|
}
|
|
833
850
|
.ag-compact {
|
|
834
851
|
display: flex;
|
|
835
852
|
align-items: center;
|
|
836
|
-
gap:
|
|
837
|
-
padding:
|
|
853
|
+
gap: 6px;
|
|
854
|
+
padding: 3px 0;
|
|
838
855
|
cursor: pointer;
|
|
856
|
+
min-width: 0;
|
|
857
|
+
overflow: hidden;
|
|
839
858
|
}
|
|
840
859
|
.ag-compact--selected {
|
|
841
860
|
background: color-mix(in srgb, var(--ev-color) 10%, transparent);
|
|
@@ -847,7 +866,7 @@
|
|
|
847
866
|
color: var(--dt-text);
|
|
848
867
|
}
|
|
849
868
|
.ag-compact:focus-visible {
|
|
850
|
-
outline: 2px solid var(--dt-accent, #
|
|
869
|
+
outline: 2px solid var(--dt-accent, #2563eb);
|
|
851
870
|
outline-offset: 2px;
|
|
852
871
|
}
|
|
853
872
|
.ag-compact-dot {
|
|
@@ -861,14 +880,16 @@
|
|
|
861
880
|
font-size: 11px;
|
|
862
881
|
font-family: var(--dt-mono, monospace);
|
|
863
882
|
color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
|
|
864
|
-
min-width:
|
|
883
|
+
min-width: 40px;
|
|
865
884
|
flex-shrink: 0;
|
|
885
|
+
white-space: nowrap;
|
|
866
886
|
}
|
|
867
887
|
.ag-compact-title {
|
|
868
888
|
font-size: 12px;
|
|
869
889
|
font-weight: 500;
|
|
870
890
|
color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
|
|
871
891
|
flex: 1;
|
|
892
|
+
min-width: 0;
|
|
872
893
|
white-space: nowrap;
|
|
873
894
|
overflow: hidden;
|
|
874
895
|
text-overflow: ellipsis;
|
|
@@ -879,11 +900,40 @@
|
|
|
879
900
|
font-family: var(--dt-mono, monospace);
|
|
880
901
|
color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
|
|
881
902
|
flex-shrink: 0;
|
|
903
|
+
white-space: nowrap;
|
|
882
904
|
}
|
|
883
905
|
.ag-compact-sub {
|
|
884
906
|
font-size: 10px;
|
|
885
907
|
color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
|
|
886
908
|
flex-shrink: 0;
|
|
909
|
+
white-space: nowrap;
|
|
910
|
+
overflow: hidden;
|
|
911
|
+
text-overflow: ellipsis;
|
|
912
|
+
max-width: 120px;
|
|
913
|
+
}
|
|
914
|
+
.ag-compact-loc {
|
|
915
|
+
font-size: 9px;
|
|
916
|
+
color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
|
|
917
|
+
flex-shrink: 0;
|
|
918
|
+
white-space: nowrap;
|
|
919
|
+
overflow: hidden;
|
|
920
|
+
text-overflow: ellipsis;
|
|
921
|
+
max-width: 100px;
|
|
922
|
+
}
|
|
923
|
+
.ag-compact--cancelled {
|
|
924
|
+
opacity: 0.5;
|
|
925
|
+
}
|
|
926
|
+
.ag-compact--cancelled .ag-compact-title {
|
|
927
|
+
text-decoration: line-through;
|
|
928
|
+
}
|
|
929
|
+
.ag-compact--tentative {
|
|
930
|
+
opacity: 0.65;
|
|
931
|
+
}
|
|
932
|
+
.ag-compact--full {
|
|
933
|
+
opacity: 0.55;
|
|
934
|
+
}
|
|
935
|
+
.ag-compact--limited {
|
|
936
|
+
opacity: 0.65;
|
|
887
937
|
}
|
|
888
938
|
.ag-compact-tag {
|
|
889
939
|
font: 500 8px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
@@ -893,6 +943,9 @@
|
|
|
893
943
|
border-radius: 3px;
|
|
894
944
|
white-space: nowrap;
|
|
895
945
|
flex-shrink: 0;
|
|
946
|
+
max-width: 80px;
|
|
947
|
+
overflow: hidden;
|
|
948
|
+
text-overflow: ellipsis;
|
|
896
949
|
}
|
|
897
950
|
.ag-compact-more {
|
|
898
951
|
font-size: 11px;
|
|
@@ -6,11 +6,10 @@
|
|
|
6
6
|
Events positioned absolutely within hour lanes.
|
|
7
7
|
-->
|
|
8
8
|
<script lang="ts">
|
|
9
|
-
import {
|
|
9
|
+
import { onMount } from 'svelte';
|
|
10
|
+
import { useCalendarContext } from '../shared/context.svelte.js';
|
|
10
11
|
import { createClock } from '../../core/clock.svelte.js';
|
|
11
12
|
import type { TimelineEvent, BlockedSlot } from '../../core/types.js';
|
|
12
|
-
import type { DragState } from '../../engine/drag.svelte.js';
|
|
13
|
-
import type { ViewState } from '../../engine/view-state.svelte.js';
|
|
14
13
|
import { DAY_MS, HOUR_MS, sod, isAllDay, isMultiDay, segmentForDay } from '../../core/time.js';
|
|
15
14
|
import type { DaySegment } from '../../core/time.js';
|
|
16
15
|
import { fmtH, fmtTime, getLabels } from '../../core/locale.js';
|
|
@@ -45,20 +44,14 @@
|
|
|
45
44
|
}: Props = $props();
|
|
46
45
|
|
|
47
46
|
// ── Context ────────────────────────────────────────
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const blockedSlots = $derived(blockedSlotsCtx?.current);
|
|
57
|
-
const minDuration = $derived(minDurationCtx?.current);
|
|
58
|
-
const autoHeight = $derived(autoHeightCtx?.current ?? false);
|
|
59
|
-
const oneventhover = $derived(callbacksCtx?.oneventhover);
|
|
60
|
-
const disabledDates = $derived(disabledDatesCtx?.current);
|
|
61
|
-
const disabledSet = $derived(new Set(disabledDates?.map(d => sod(d.getTime())) ?? []));
|
|
47
|
+
const ctx = useCalendarContext();
|
|
48
|
+
const viewState = $derived(ctx.viewState);
|
|
49
|
+
const autoHeight = $derived(ctx.autoHeight);
|
|
50
|
+
const oneventhover = $derived(ctx.oneventhover);
|
|
51
|
+
const disabledSet = $derived(ctx.disabledSet);
|
|
52
|
+
const loadRangeCtx = $derived(ctx.loadRange);
|
|
53
|
+
const minDuration = $derived(ctx.minDuration);
|
|
54
|
+
const blockedSlots = $derived(ctx.blockedSlots);
|
|
62
55
|
|
|
63
56
|
const clock = createClock();
|
|
64
57
|
|
|
@@ -186,8 +179,8 @@
|
|
|
186
179
|
ev: info.ev,
|
|
187
180
|
top: info.top,
|
|
188
181
|
height: info.height,
|
|
189
|
-
left: `calc(${GUTTER_W}px + ${(info.col / info.totalCols) * 100}%
|
|
190
|
-
width: `calc(
|
|
182
|
+
left: `calc(${GUTTER_W}px + ${(info.col / info.totalCols) * 100}% - ${(GUTTER_W * info.col) / info.totalCols}px)`,
|
|
183
|
+
width: `calc(${100 / info.totalCols}% - ${GUTTER_W / info.totalCols + 2}px)`,
|
|
191
184
|
isCurrent: info.isCurrent,
|
|
192
185
|
isNext: info.isNext,
|
|
193
186
|
col: info.col,
|
|
@@ -321,7 +314,6 @@
|
|
|
321
314
|
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleGridClick(e as unknown as MouseEvent); }}
|
|
322
315
|
role="grid"
|
|
323
316
|
tabindex="-1"
|
|
324
|
-
style:--mb-grid-w="300"
|
|
325
317
|
>
|
|
326
318
|
<div class="mb-grid-inner" style:height="{gridHeight}px">
|
|
327
319
|
<!-- Hour lanes -->
|
|
@@ -360,6 +352,10 @@
|
|
|
360
352
|
class:mb-event--selected={selectedEventId === p.ev.id}
|
|
361
353
|
class:mb-event--current={p.isCurrent}
|
|
362
354
|
class:mb-event--next={p.isNext}
|
|
355
|
+
class:mb-event--cancelled={p.ev.status === 'cancelled'}
|
|
356
|
+
class:mb-event--tentative={p.ev.status === 'tentative'}
|
|
357
|
+
class:mb-event--full={p.ev.status === 'full'}
|
|
358
|
+
class:mb-event--limited={p.ev.status === 'limited'}
|
|
363
359
|
style:top="{p.top}px"
|
|
364
360
|
style:height="{p.height}px"
|
|
365
361
|
style:left={p.left}
|
|
@@ -367,7 +363,7 @@
|
|
|
367
363
|
style:--ev-color={p.ev.color ?? 'var(--dt-accent)'}
|
|
368
364
|
onclick={(e) => { e.stopPropagation(); oneventclick?.(p.ev); }}
|
|
369
365
|
onpointerenter={() => oneventhover?.(p.ev)}
|
|
370
|
-
aria-label="{p.ev.title}{p.isCurrent ? `, ${L.inProgress}` : ''}{p.isNext ? `, ${L.upNext}` : ''}"
|
|
366
|
+
aria-label="{p.ev.title}{p.ev.status === 'cancelled' ? ' (cancelled)' : ''}{p.ev.status === 'tentative' ? ' (tentative)' : ''}{p.ev.status === 'full' ? ' (full)' : ''}{p.ev.status === 'limited' ? ' (limited)' : ''}{p.isCurrent ? `, ${L.inProgress}` : ''}{p.isNext ? `, ${L.upNext}` : ''}"
|
|
371
367
|
>
|
|
372
368
|
<div class="mb-ev-stripe"></div>
|
|
373
369
|
<div class="mb-ev-body">
|
|
@@ -378,6 +374,9 @@
|
|
|
378
374
|
{#if p.ev.subtitle && p.height > 48}
|
|
379
375
|
<span class="mb-ev-sub">{p.ev.subtitle}</span>
|
|
380
376
|
{/if}
|
|
377
|
+
{#if p.ev.location && p.height > 56}
|
|
378
|
+
<span class="mb-ev-loc">{p.ev.location}</span>
|
|
379
|
+
{/if}
|
|
381
380
|
{#if p.ev.tags?.length && p.height > 56}
|
|
382
381
|
<div class="mb-ev-tags">
|
|
383
382
|
{#each p.ev.tags as tag}
|
|
@@ -611,6 +610,23 @@
|
|
|
611
610
|
background: color-mix(in srgb, var(--ev-color) 8%, var(--dt-surface, #f9fafb));
|
|
612
611
|
border: 1px dashed color-mix(in srgb, var(--ev-color) 35%, transparent);
|
|
613
612
|
}
|
|
613
|
+
.mb-event--cancelled {
|
|
614
|
+
opacity: 0.5;
|
|
615
|
+
}
|
|
616
|
+
.mb-event--cancelled .mb-ev-title {
|
|
617
|
+
text-decoration: line-through;
|
|
618
|
+
}
|
|
619
|
+
.mb-event--tentative {
|
|
620
|
+
opacity: 0.65;
|
|
621
|
+
border: 1px dashed color-mix(in srgb, var(--ev-color) 35%, transparent);
|
|
622
|
+
}
|
|
623
|
+
.mb-event--full {
|
|
624
|
+
opacity: 0.55;
|
|
625
|
+
}
|
|
626
|
+
.mb-event--limited {
|
|
627
|
+
opacity: 0.65;
|
|
628
|
+
border: 1px dashed color-mix(in srgb, var(--ev-color) 35%, transparent);
|
|
629
|
+
}
|
|
614
630
|
|
|
615
631
|
.mb-ev-stripe {
|
|
616
632
|
width: 4px;
|
|
@@ -650,6 +666,14 @@
|
|
|
650
666
|
text-overflow: ellipsis;
|
|
651
667
|
}
|
|
652
668
|
|
|
669
|
+
.mb-ev-loc {
|
|
670
|
+
font: 400 10px/1 var(--dt-sans, system-ui, sans-serif);
|
|
671
|
+
color: var(--dt-text-3, rgba(0, 0, 0, 0.3));
|
|
672
|
+
white-space: nowrap;
|
|
673
|
+
overflow: hidden;
|
|
674
|
+
text-overflow: ellipsis;
|
|
675
|
+
}
|
|
676
|
+
|
|
653
677
|
.mb-ev-tags {
|
|
654
678
|
display: flex;
|
|
655
679
|
gap: 4px;
|