@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
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
left/right to navigate weeks.
|
|
7
7
|
-->
|
|
8
8
|
<script lang="ts">
|
|
9
|
-
import {
|
|
9
|
+
import { useCalendarContext } from '../shared/context.svelte.js';
|
|
10
10
|
import { createClock } from '../../core/clock.svelte.js';
|
|
11
11
|
import type { TimelineEvent } from '../../core/types.js';
|
|
12
|
-
import type { ViewState } from '../../engine/view-state.svelte.js';
|
|
13
12
|
import { DAY_MS, sod, isAllDay, isMultiDay } from '../../core/time.js';
|
|
14
13
|
import { startOfWeek as sowFn } from '../../core/time.js';
|
|
15
14
|
import { fmtTime as _fmtTime, weekdayShort, getLabels } from '../../core/locale.js';
|
|
@@ -44,22 +43,16 @@
|
|
|
44
43
|
}: Props = $props();
|
|
45
44
|
|
|
46
45
|
// ── Context ────────────────────────────────────────
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
const autoHeight = $derived(autoHeightCtx?.current ?? false);
|
|
58
|
-
const showDates = $derived(showDatesCtx?.current ?? true);
|
|
59
|
-
const hideDays = $derived(hideDaysCtx?.current);
|
|
60
|
-
const oneventhover = $derived(callbacksCtx?.oneventhover);
|
|
61
|
-
const disabledDates = $derived(disabledDatesCtx?.current);
|
|
62
|
-
const disabledSet = $derived(new Set(disabledDates?.map(d => sod(d.getTime())) ?? []));
|
|
46
|
+
const ctx = useCalendarContext();
|
|
47
|
+
const viewState = $derived(ctx.viewState);
|
|
48
|
+
const equalDays = $derived(ctx.equalDays);
|
|
49
|
+
const showDates = $derived(ctx.showDates);
|
|
50
|
+
const hideDays = $derived(ctx.hideDays);
|
|
51
|
+
const autoHeight = $derived(ctx.autoHeight);
|
|
52
|
+
const oneventhover = $derived(ctx.oneventhover);
|
|
53
|
+
const disabledSet = $derived(ctx.disabledSet);
|
|
54
|
+
const loadRangeCtx = $derived(ctx.loadRange);
|
|
55
|
+
const minDuration = $derived(ctx.minDuration);
|
|
63
56
|
|
|
64
57
|
const clock = createClock();
|
|
65
58
|
|
|
@@ -192,6 +185,12 @@
|
|
|
192
185
|
viewState.setView(dayView);
|
|
193
186
|
}
|
|
194
187
|
}
|
|
188
|
+
|
|
189
|
+
function handleDayKeydown(e: KeyboardEvent, dayMs: number) {
|
|
190
|
+
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
191
|
+
e.preventDefault();
|
|
192
|
+
handleDayTap(dayMs);
|
|
193
|
+
}
|
|
195
194
|
</script>
|
|
196
195
|
|
|
197
196
|
<div
|
|
@@ -208,15 +207,21 @@
|
|
|
208
207
|
<!-- Vertical day list -->
|
|
209
208
|
<div class="mw-list" role="list">
|
|
210
209
|
{#each dayCells as cell (cell.ms)}
|
|
211
|
-
<
|
|
210
|
+
<div
|
|
212
211
|
class="mw-row"
|
|
213
212
|
class:mw-row--today={cell.isToday}
|
|
214
213
|
class:mw-row--past={cell.isPast}
|
|
215
214
|
class:mw-row--weekend={cell.isWeekend}
|
|
216
215
|
class:mw-row--disabled={cell.isDisabled}
|
|
217
|
-
|
|
218
|
-
aria-label="{cell.dayName} {cell.dayNum}"
|
|
216
|
+
role="listitem"
|
|
219
217
|
>
|
|
218
|
+
<button
|
|
219
|
+
class="mw-row-target"
|
|
220
|
+
disabled={cell.isDisabled}
|
|
221
|
+
onclick={() => handleDayTap(cell.ms)}
|
|
222
|
+
onkeydown={(e) => handleDayKeydown(e, cell.ms)}
|
|
223
|
+
aria-label="{cell.dayName} {cell.dayNum}"
|
|
224
|
+
></button>
|
|
220
225
|
<!-- Date column -->
|
|
221
226
|
<div class="mw-date">
|
|
222
227
|
<span class="mw-day-name" class:mw-day-name--today={cell.isToday}>{cell.dayName}</span>
|
|
@@ -231,14 +236,17 @@
|
|
|
231
236
|
<span class="mw-empty">{L.noEvents}</span>
|
|
232
237
|
{:else}
|
|
233
238
|
{#each cell.events.slice(0, MAX_EVENTS) as ev (ev.id)}
|
|
234
|
-
<
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
235
241
|
class="mw-ev"
|
|
236
242
|
class:mw-ev--selected={selectedEventId === ev.id}
|
|
237
243
|
class:mw-ev--allday={isAllDay(ev) || isMultiDay(ev)}
|
|
238
244
|
class:mw-ev--current={!isAllDay(ev) && !isMultiDay(ev) && ev.start.getTime() <= clock.tick && ev.end.getTime() > clock.tick}
|
|
245
|
+
class:mw-ev--cancelled={ev.status === 'cancelled'}
|
|
246
|
+
class:mw-ev--tentative={ev.status === 'tentative'}
|
|
247
|
+
class:mw-ev--full={ev.status === 'full'}
|
|
248
|
+
class:mw-ev--limited={ev.status === 'limited'}
|
|
239
249
|
style:--ev-color={ev.color ?? 'var(--dt-accent)'}
|
|
240
|
-
role="button"
|
|
241
|
-
tabindex="0"
|
|
242
250
|
onclick={(e) => { e.stopPropagation(); oneventclick?.(ev); }}
|
|
243
251
|
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); oneventclick?.(ev); } }}
|
|
244
252
|
onpointerenter={() => oneventhover?.(ev)}
|
|
@@ -252,7 +260,7 @@
|
|
|
252
260
|
<span class="mw-ev-time">{fmtTime(ev.start)}</span>
|
|
253
261
|
{/if}
|
|
254
262
|
</div>
|
|
255
|
-
</
|
|
263
|
+
</button>
|
|
256
264
|
{/each}
|
|
257
265
|
{#if cell.totalCount > MAX_EVENTS}
|
|
258
266
|
<span class="mw-ev-more">{L.nMore(cell.totalCount - MAX_EVENTS)}</span>
|
|
@@ -262,7 +270,7 @@
|
|
|
262
270
|
|
|
263
271
|
<!-- Chevron -->
|
|
264
272
|
<svg class="mw-chevron" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14" aria-hidden="true"><path d="M6 3l5 5-5 5"/></svg>
|
|
265
|
-
</
|
|
273
|
+
</div>
|
|
266
274
|
{/each}
|
|
267
275
|
</div>
|
|
268
276
|
</div>
|
|
@@ -296,10 +304,9 @@
|
|
|
296
304
|
display: flex;
|
|
297
305
|
align-items: center;
|
|
298
306
|
gap: 12px;
|
|
307
|
+
position: relative;
|
|
299
308
|
padding: 10px 12px;
|
|
300
|
-
border: none;
|
|
301
309
|
background: transparent;
|
|
302
|
-
cursor: pointer;
|
|
303
310
|
transition: background 120ms;
|
|
304
311
|
text-align: left;
|
|
305
312
|
width: 100%;
|
|
@@ -310,7 +317,7 @@
|
|
|
310
317
|
.mw-row:last-child {
|
|
311
318
|
border-bottom: none;
|
|
312
319
|
}
|
|
313
|
-
.mw-row:active {
|
|
320
|
+
.mw-row:has(.mw-row-target:active) {
|
|
314
321
|
background: color-mix(in srgb, var(--dt-accent, #2563eb) 6%, transparent);
|
|
315
322
|
}
|
|
316
323
|
.mw-row--today {
|
|
@@ -320,8 +327,30 @@
|
|
|
320
327
|
opacity: 0.5;
|
|
321
328
|
}
|
|
322
329
|
.mw-row--disabled {
|
|
323
|
-
|
|
324
|
-
|
|
330
|
+
background-image: repeating-linear-gradient(
|
|
331
|
+
135deg,
|
|
332
|
+
transparent,
|
|
333
|
+
transparent 6px,
|
|
334
|
+
color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 4%, transparent) 6px,
|
|
335
|
+
color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 4%, transparent) 12px
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
.mw-row-target {
|
|
339
|
+
position: absolute;
|
|
340
|
+
inset: 0;
|
|
341
|
+
z-index: 0;
|
|
342
|
+
border: none;
|
|
343
|
+
background: transparent;
|
|
344
|
+
cursor: pointer;
|
|
345
|
+
padding: 0;
|
|
346
|
+
-webkit-tap-highlight-color: transparent;
|
|
347
|
+
}
|
|
348
|
+
.mw-row-target:disabled {
|
|
349
|
+
cursor: default;
|
|
350
|
+
}
|
|
351
|
+
.mw-row-target:focus-visible {
|
|
352
|
+
outline: 2px solid var(--dt-accent, #2563eb);
|
|
353
|
+
outline-offset: -2px;
|
|
325
354
|
}
|
|
326
355
|
|
|
327
356
|
/* ─── Date column ────────────────────────────────── */
|
|
@@ -332,6 +361,9 @@
|
|
|
332
361
|
width: 40px;
|
|
333
362
|
flex-shrink: 0;
|
|
334
363
|
gap: 2px;
|
|
364
|
+
position: relative;
|
|
365
|
+
z-index: 1;
|
|
366
|
+
pointer-events: none;
|
|
335
367
|
}
|
|
336
368
|
|
|
337
369
|
.mw-day-name {
|
|
@@ -367,6 +399,8 @@
|
|
|
367
399
|
display: flex;
|
|
368
400
|
flex-direction: column;
|
|
369
401
|
gap: 4px;
|
|
402
|
+
position: relative;
|
|
403
|
+
z-index: 2;
|
|
370
404
|
}
|
|
371
405
|
|
|
372
406
|
.mw-empty {
|
|
@@ -402,6 +436,23 @@
|
|
|
402
436
|
.mw-ev--allday {
|
|
403
437
|
background: color-mix(in srgb, var(--ev-color) 14%, var(--dt-surface, #f9fafb));
|
|
404
438
|
}
|
|
439
|
+
.mw-ev--cancelled {
|
|
440
|
+
opacity: 0.5;
|
|
441
|
+
}
|
|
442
|
+
.mw-ev--cancelled .mw-ev-title {
|
|
443
|
+
text-decoration: line-through;
|
|
444
|
+
}
|
|
445
|
+
.mw-ev--tentative {
|
|
446
|
+
opacity: 0.65;
|
|
447
|
+
border: 1px dashed color-mix(in srgb, var(--ev-color) 35%, transparent);
|
|
448
|
+
}
|
|
449
|
+
.mw-ev--full {
|
|
450
|
+
opacity: 0.55;
|
|
451
|
+
}
|
|
452
|
+
.mw-ev--limited {
|
|
453
|
+
opacity: 0.65;
|
|
454
|
+
border: 1px dashed color-mix(in srgb, var(--ev-color) 35%, transparent);
|
|
455
|
+
}
|
|
405
456
|
|
|
406
457
|
.mw-ev-stripe {
|
|
407
458
|
width: 3px;
|
|
@@ -447,13 +498,12 @@
|
|
|
447
498
|
.mw-chevron {
|
|
448
499
|
flex-shrink: 0;
|
|
449
500
|
color: var(--dt-text-3, rgba(0, 0, 0, 0.2));
|
|
501
|
+
position: relative;
|
|
502
|
+
z-index: 1;
|
|
503
|
+
pointer-events: none;
|
|
450
504
|
}
|
|
451
505
|
|
|
452
506
|
/* ─── Focus ──────────────────────────────────────── */
|
|
453
|
-
.mw-row:focus-visible {
|
|
454
|
-
outline: 2px solid var(--dt-accent, #2563eb);
|
|
455
|
-
outline-offset: -2px;
|
|
456
|
-
}
|
|
457
507
|
.mw-ev:focus-visible {
|
|
458
508
|
outline: 2px solid var(--ev-color, var(--dt-accent));
|
|
459
509
|
outline-offset: 1px;
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
Events are positioned as horizontal cards with overlap support.
|
|
7
7
|
-->
|
|
8
8
|
<script lang="ts">
|
|
9
|
-
import {
|
|
9
|
+
import { onMount, tick, untrack } from 'svelte';
|
|
10
|
+
import { useCalendarContext } from '../shared/context.svelte.js';
|
|
10
11
|
import { fly } from 'svelte/transition';
|
|
11
12
|
import { createClock } from '../../core/clock.svelte.js';
|
|
13
|
+
import { createTextMeasure, type ContentFit } from '../../core/measure.js';
|
|
12
14
|
import type { TimelineEvent, BlockedSlot } from '../../core/types.js';
|
|
13
15
|
import type { DragState } from '../../engine/drag.svelte.js';
|
|
14
16
|
import type { ViewState } from '../../engine/view-state.svelte.js';
|
|
@@ -58,33 +60,21 @@
|
|
|
58
60
|
}: Props = $props();
|
|
59
61
|
|
|
60
62
|
// ── Context (available when inside Calendar) ──
|
|
61
|
-
const
|
|
62
|
-
const commitDragCtx = getContext<() => void>('calendar:commitDrag') as (() => void) | undefined;
|
|
63
|
-
const snapIntervalCtx = getContext<{ current: number }>('calendar:snapInterval');
|
|
64
|
-
const viewState = getContext<ViewState>('calendar:viewState') as ViewState | undefined;
|
|
65
|
-
const loadRangeCtx = getContext<{ current: { start: Date; end: Date } | null; set: (r: { start: Date; end: Date } | null) => void }>('calendar:loadRange') as { current: { start: Date; end: Date } | null; set: (r: { start: Date; end: Date } | null) => void } | undefined;
|
|
66
|
-
|
|
63
|
+
const ctx = useCalendarContext();
|
|
67
64
|
const clock = createClock();
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const blockedSlots = $derived(blockedSlotsCtx?.current);
|
|
82
|
-
const dayHeaderSnippet = $derived(dayHeaderSnippetCtx?.current);
|
|
83
|
-
const minDuration = $derived(minDurationCtx?.current);
|
|
84
|
-
const autoHeight = $derived(autoHeightCtx?.current ?? false);
|
|
85
|
-
const oneventhover = $derived(callbacksCtx?.oneventhover);
|
|
86
|
-
const disabledDates = $derived(disabledDatesCtx?.current);
|
|
87
|
-
const disabledSet = $derived(new Set(disabledDates?.map(d => sod(d.getTime())) ?? []));
|
|
65
|
+
const drag = $derived(ctx.drag);
|
|
66
|
+
const commitDragCtx = $derived(ctx.commitDrag);
|
|
67
|
+
const viewState = $derived(ctx.viewState);
|
|
68
|
+
const loadRangeCtx = $derived(ctx.loadRange);
|
|
69
|
+
const showNav = $derived(ctx.showNav);
|
|
70
|
+
const showDates = $derived(ctx.showDates);
|
|
71
|
+
const blockedSlots = $derived(ctx.blockedSlots);
|
|
72
|
+
const dayHeaderSnippet = $derived(ctx.dayHeaderSnippet);
|
|
73
|
+
const minDuration = $derived(ctx.minDuration);
|
|
74
|
+
const autoHeight = $derived(ctx.autoHeight);
|
|
75
|
+
const oneventhover = $derived(ctx.oneventhover);
|
|
76
|
+
const disabledSet = $derived(ctx.disabledSet);
|
|
77
|
+
const SNAP_MS = $derived(ctx.snapInterval * 60_000);
|
|
88
78
|
|
|
89
79
|
// ─── State ──────────────────────────────────────────
|
|
90
80
|
let following = $state(true);
|
|
@@ -113,7 +103,6 @@
|
|
|
113
103
|
const startHour = $derived(visibleHours?.[0] ?? 0);
|
|
114
104
|
const endHour = $derived(visibleHours?.[1] ?? 24);
|
|
115
105
|
const hourCount = $derived(Math.max(1, endHour - startHour));
|
|
116
|
-
const SNAP_MS = $derived((snapIntervalCtx?.current ?? 15) * 60_000);
|
|
117
106
|
const DAY_GAP = 2;
|
|
118
107
|
|
|
119
108
|
const dateLabel = $derived(
|
|
@@ -207,6 +196,16 @@
|
|
|
207
196
|
const EVENT_GAP = 5;
|
|
208
197
|
const MIN_EVENT_H = 32;
|
|
209
198
|
|
|
199
|
+
// Text measure for smart content fitting
|
|
200
|
+
const measure = createTextMeasure({
|
|
201
|
+
titleFont: '600 13px system-ui, sans-serif',
|
|
202
|
+
secondaryFont: '400 10px system-ui, sans-serif',
|
|
203
|
+
tagFont: '500 8px system-ui, sans-serif',
|
|
204
|
+
titleLineHeight: 16,
|
|
205
|
+
secondaryLineHeight: 13,
|
|
206
|
+
contentGap: 6,
|
|
207
|
+
});
|
|
208
|
+
|
|
210
209
|
interface PositionedEvent {
|
|
211
210
|
ev: TimelineEvent;
|
|
212
211
|
x: number;
|
|
@@ -218,6 +217,7 @@
|
|
|
218
217
|
isCurrent: boolean;
|
|
219
218
|
isNext: boolean;
|
|
220
219
|
isDragged: boolean;
|
|
220
|
+
fit: ContentFit;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
const positionedEvents = $derived.by(() => {
|
|
@@ -297,20 +297,39 @@
|
|
|
297
297
|
const result: PositionedEvent[] = infos.map(({ startMs: _s, endMs: _e, ...info }) => {
|
|
298
298
|
const laneH = Math.max(MIN_EVENT_H, availH / info.groupMaxRow - EVENT_GAP);
|
|
299
299
|
const topPx = contentTop + info.row * (availH / info.groupMaxRow);
|
|
300
|
-
|
|
300
|
+
const fit = measure.fitContent({
|
|
301
|
+
title: info.ev.title,
|
|
302
|
+
subtitle: info.ev.subtitle,
|
|
303
|
+
location: info.ev.location,
|
|
304
|
+
time: `${fmtTime(info.ev.start, locale)} – ${fmtTime(info.ev.end, locale)}`,
|
|
305
|
+
tags: info.ev.tags,
|
|
306
|
+
maxWidth: info.width - 16,
|
|
307
|
+
maxHeight: laneH - 16,
|
|
308
|
+
});
|
|
309
|
+
return { ...info, topPx, heightPx: laneH, isNext: info.isNext, fit };
|
|
301
310
|
});
|
|
302
311
|
|
|
303
312
|
if (draggedEv && dragP) {
|
|
304
313
|
const x = timeToPx(dragP.start.getTime());
|
|
305
314
|
const xEnd = timeToPx(dragP.end.getTime());
|
|
315
|
+
const dragH = Math.max(MIN_EVENT_H, availH - EVENT_GAP);
|
|
316
|
+
const dragW = Math.max(xEnd - x, 28);
|
|
306
317
|
result.push({
|
|
307
|
-
ev: draggedEv, x, width:
|
|
318
|
+
ev: draggedEv, x, width: dragW,
|
|
308
319
|
row: 0, groupMaxRow: 1,
|
|
309
320
|
topPx: contentTop,
|
|
310
|
-
heightPx:
|
|
321
|
+
heightPx: dragH,
|
|
311
322
|
isCurrent: draggedEv.start.getTime() <= now && draggedEv.end.getTime() > now,
|
|
312
323
|
isNext: false,
|
|
313
324
|
isDragged: true,
|
|
325
|
+
fit: measure.fitContent({
|
|
326
|
+
title: draggedEv.title,
|
|
327
|
+
subtitle: draggedEv.subtitle,
|
|
328
|
+
location: draggedEv.location,
|
|
329
|
+
tags: draggedEv.tags,
|
|
330
|
+
maxWidth: dragW - 16,
|
|
331
|
+
maxHeight: dragH - 16,
|
|
332
|
+
}),
|
|
314
333
|
});
|
|
315
334
|
}
|
|
316
335
|
|
|
@@ -493,7 +512,7 @@
|
|
|
493
512
|
let evDragEvent: TimelineEvent | null = null;
|
|
494
513
|
|
|
495
514
|
function onEventPointerDown(e: PointerEvent, ev: TimelineEvent) {
|
|
496
|
-
if (e.button !== 0 || !drag || readOnly) return;
|
|
515
|
+
if (e.button !== 0 || !drag || readOnly || ev.data?.readOnly) return;
|
|
497
516
|
e.stopPropagation();
|
|
498
517
|
evDragStartX = e.clientX;
|
|
499
518
|
evDragOriginPx = timeToPx(ev.start.getTime());
|
|
@@ -623,6 +642,11 @@
|
|
|
623
642
|
class:fs-event--current={p.isCurrent}
|
|
624
643
|
class:fs-event--next={p.isNext}
|
|
625
644
|
class:fs-event--dragging={p.isDragged}
|
|
645
|
+
class:fs-event--readonly={p.ev.data?.readOnly}
|
|
646
|
+
class:fs-event--cancelled={p.ev.status === 'cancelled'}
|
|
647
|
+
class:fs-event--tentative={p.ev.status === 'tentative'}
|
|
648
|
+
class:fs-event--full={p.ev.status === 'full'}
|
|
649
|
+
class:fs-event--limited={p.ev.status === 'limited'}
|
|
626
650
|
style:left="{p.x}px"
|
|
627
651
|
style:width="{p.width}px"
|
|
628
652
|
style:top="{p.topPx}px"
|
|
@@ -630,7 +654,7 @@
|
|
|
630
654
|
style:--ev-color={p.ev.color ?? 'var(--dt-accent)'}
|
|
631
655
|
role="button"
|
|
632
656
|
tabindex="0"
|
|
633
|
-
aria-label="{p.ev.title}{p.isCurrent ? ` (${L.inProgress})` : ''}{p.isNext ? ` (${L.upNext})` : ''}"
|
|
657
|
+
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})` : ''}"
|
|
634
658
|
onpointerdown={(e) => onEventPointerDown(e, p.ev)}
|
|
635
659
|
onpointerenter={() => oneventhover?.(p.ev)}
|
|
636
660
|
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); oneventclick?.(p.ev); } }}
|
|
@@ -641,14 +665,17 @@
|
|
|
641
665
|
{:else if p.isNext}
|
|
642
666
|
<span class="fs-ev-next-badge" aria-hidden="true">{L.upNext}</span>
|
|
643
667
|
{/if}
|
|
644
|
-
{#if p.
|
|
668
|
+
{#if p.fit.time}
|
|
645
669
|
<span class="fs-ev-time">{fmtTime(p.ev.start, locale)} – {fmtTime(p.ev.end, locale)}</span>
|
|
646
670
|
{/if}
|
|
647
671
|
<span class="fs-ev-title">{p.ev.title}</span>
|
|
648
|
-
{#if p.ev.subtitle && p.
|
|
672
|
+
{#if p.ev.subtitle && p.fit.subtitle}
|
|
649
673
|
<span class="fs-ev-sub">{p.ev.subtitle}</span>
|
|
650
674
|
{/if}
|
|
651
|
-
{#if p.ev.
|
|
675
|
+
{#if p.ev.location && p.fit.location}
|
|
676
|
+
<span class="fs-ev-loc">{p.ev.location}</span>
|
|
677
|
+
{/if}
|
|
678
|
+
{#if p.ev.tags?.length && p.fit.tags}
|
|
652
679
|
<span class="fs-ev-tags">
|
|
653
680
|
{#each p.ev.tags as tag}
|
|
654
681
|
<span class="fs-ev-tag">{tag}</span>
|
|
@@ -866,7 +893,7 @@
|
|
|
866
893
|
bottom: 0;
|
|
867
894
|
left: 0;
|
|
868
895
|
width: 2px;
|
|
869
|
-
background: var(--dt-accent, #
|
|
896
|
+
background: var(--dt-accent, #2563eb);
|
|
870
897
|
box-shadow: 0 0 8px var(--dt-glow, rgba(239, 68, 68, 0.35));
|
|
871
898
|
}
|
|
872
899
|
.fs-now-tag {
|
|
@@ -874,8 +901,8 @@
|
|
|
874
901
|
top: 8px;
|
|
875
902
|
left: 8px;
|
|
876
903
|
font: 700 11px/1 var(--dt-mono, ui-monospace, monospace);
|
|
877
|
-
color: var(--dt-accent, #
|
|
878
|
-
background: color-mix(in srgb, var(--dt-bg, #
|
|
904
|
+
color: var(--dt-accent, #2563eb);
|
|
905
|
+
background: color-mix(in srgb, var(--dt-bg, #ffffff) 92%, var(--dt-accent, #2563eb));
|
|
879
906
|
border: 1px solid var(--dt-accent-dim, rgba(239, 68, 68, 0.18));
|
|
880
907
|
padding: 3px 6px;
|
|
881
908
|
border-radius: 4px;
|
|
@@ -899,7 +926,7 @@
|
|
|
899
926
|
letter-spacing: 0.04em;
|
|
900
927
|
text-transform: uppercase;
|
|
901
928
|
color: var(--dt-text, rgba(226, 232, 240, 0.85));
|
|
902
|
-
background: color-mix(in srgb, var(--dt-surface, #
|
|
929
|
+
background: color-mix(in srgb, var(--dt-surface, var(--dt-bg, #ffffff)) 85%, transparent);
|
|
903
930
|
backdrop-filter: blur(6px);
|
|
904
931
|
-webkit-backdrop-filter: blur(6px);
|
|
905
932
|
padding: 8px 16px;
|
|
@@ -917,7 +944,7 @@
|
|
|
917
944
|
z-index: 20;
|
|
918
945
|
display: flex;
|
|
919
946
|
gap: 2px;
|
|
920
|
-
background: color-mix(in srgb, var(--dt-surface, #
|
|
947
|
+
background: color-mix(in srgb, var(--dt-surface, var(--dt-bg, #ffffff)) 85%, transparent);
|
|
921
948
|
backdrop-filter: blur(6px);
|
|
922
949
|
-webkit-backdrop-filter: blur(6px);
|
|
923
950
|
border-radius: 8px;
|
|
@@ -929,7 +956,7 @@
|
|
|
929
956
|
background: transparent;
|
|
930
957
|
color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
|
|
931
958
|
cursor: pointer;
|
|
932
|
-
font: 600 11px / 1 var(--dt-sans,
|
|
959
|
+
font: 600 11px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
933
960
|
padding: 6px 12px;
|
|
934
961
|
border-radius: 6px;
|
|
935
962
|
letter-spacing: 0.04em;
|
|
@@ -956,7 +983,7 @@
|
|
|
956
983
|
pointer-events: none;
|
|
957
984
|
}
|
|
958
985
|
.fs-nav-pill:focus-visible {
|
|
959
|
-
outline: 2px solid color-mix(in srgb, var(--dt-accent, #
|
|
986
|
+
outline: 2px solid color-mix(in srgb, var(--dt-accent, #2563eb) 55%, transparent);
|
|
960
987
|
outline-offset: 2px;
|
|
961
988
|
}
|
|
962
989
|
|
|
@@ -981,7 +1008,7 @@
|
|
|
981
1008
|
gap: 4px;
|
|
982
1009
|
padding: 2px 8px;
|
|
983
1010
|
border-radius: 4px;
|
|
984
|
-
background: color-mix(in srgb, var(--ev-color) 18%, var(--dt-surface, #
|
|
1011
|
+
background: color-mix(in srgb, var(--ev-color) 18%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
985
1012
|
border-left: 3px solid var(--ev-color);
|
|
986
1013
|
white-space: nowrap;
|
|
987
1014
|
flex-shrink: 0;
|
|
@@ -989,14 +1016,14 @@
|
|
|
989
1016
|
transition: background 0.15s;
|
|
990
1017
|
}
|
|
991
1018
|
.fs-ad:hover {
|
|
992
|
-
background: color-mix(in srgb, var(--ev-color) 28%, var(--dt-surface, #
|
|
1019
|
+
background: color-mix(in srgb, var(--ev-color) 28%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
993
1020
|
}
|
|
994
1021
|
.fs-ad:focus-visible {
|
|
995
|
-
outline: 2px solid color-mix(in srgb, var(--dt-accent, #
|
|
1022
|
+
outline: 2px solid color-mix(in srgb, var(--dt-accent, #2563eb) 55%, transparent);
|
|
996
1023
|
outline-offset: 2px;
|
|
997
1024
|
}
|
|
998
1025
|
.fs-ad--selected {
|
|
999
|
-
background: color-mix(in srgb, var(--ev-color) 30%, var(--dt-surface, #
|
|
1026
|
+
background: color-mix(in srgb, var(--ev-color) 30%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
1000
1027
|
border-left-width: 4px;
|
|
1001
1028
|
}
|
|
1002
1029
|
|
|
@@ -1029,7 +1056,7 @@
|
|
|
1029
1056
|
z-index: 6;
|
|
1030
1057
|
border-radius: 6px;
|
|
1031
1058
|
cursor: pointer;
|
|
1032
|
-
background: color-mix(in srgb, var(--ev-color) 15%, var(--dt-surface, #
|
|
1059
|
+
background: color-mix(in srgb, var(--ev-color) 15%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
1033
1060
|
overflow: hidden;
|
|
1034
1061
|
display: flex;
|
|
1035
1062
|
align-items: center;
|
|
@@ -1037,18 +1064,18 @@
|
|
|
1037
1064
|
transition: box-shadow 120ms, background 120ms;
|
|
1038
1065
|
}
|
|
1039
1066
|
.fs-event:hover {
|
|
1040
|
-
background: color-mix(in srgb, var(--ev-color) 25%, var(--dt-surface, #
|
|
1067
|
+
background: color-mix(in srgb, var(--ev-color) 25%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
1041
1068
|
box-shadow: 0 2px 12px color-mix(in srgb, var(--ev-color) 25%, transparent);
|
|
1042
1069
|
}
|
|
1043
1070
|
.fs-event--selected {
|
|
1044
1071
|
box-shadow: 0 0 0 2px var(--ev-color), 0 2px 14px color-mix(in srgb, var(--ev-color) 35%, transparent);
|
|
1045
1072
|
}
|
|
1046
1073
|
.fs-event--current {
|
|
1047
|
-
background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #
|
|
1074
|
+
background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
1048
1075
|
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ev-color) 20%, transparent);
|
|
1049
1076
|
}
|
|
1050
1077
|
.fs-event--next {
|
|
1051
|
-
background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, #
|
|
1078
|
+
background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
1052
1079
|
border: 1px dashed color-mix(in srgb, var(--ev-color) 35%, transparent);
|
|
1053
1080
|
}
|
|
1054
1081
|
.fs-event--dragging {
|
|
@@ -1058,17 +1085,39 @@
|
|
|
1058
1085
|
cursor: grabbing;
|
|
1059
1086
|
transition: none;
|
|
1060
1087
|
}
|
|
1088
|
+
.fs-event--cancelled {
|
|
1089
|
+
opacity: 0.5;
|
|
1090
|
+
}
|
|
1091
|
+
.fs-event--cancelled .fs-ev-title {
|
|
1092
|
+
text-decoration: line-through;
|
|
1093
|
+
}
|
|
1094
|
+
.fs-event--tentative {
|
|
1095
|
+
opacity: 0.65;
|
|
1096
|
+
border: 1px dashed color-mix(in srgb, var(--ev-color) 40%, transparent);
|
|
1097
|
+
}
|
|
1098
|
+
.fs-event--full {
|
|
1099
|
+
opacity: 0.55;
|
|
1100
|
+
}
|
|
1101
|
+
.fs-event--limited {
|
|
1102
|
+
opacity: 0.65;
|
|
1103
|
+
border: 1px dashed color-mix(in srgb, var(--ev-color) 40%, transparent);
|
|
1104
|
+
}
|
|
1105
|
+
.fs-event--readonly {
|
|
1106
|
+
cursor: default;
|
|
1107
|
+
}
|
|
1061
1108
|
|
|
1062
|
-
/* Event inner
|
|
1109
|
+
/* Event inner */
|
|
1063
1110
|
.fs-ev-inner {
|
|
1064
|
-
writing-mode: vertical-rl;
|
|
1065
|
-
transform: rotate(180deg);
|
|
1066
1111
|
display: flex;
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1112
|
+
flex-direction: column;
|
|
1113
|
+
align-items: flex-start;
|
|
1114
|
+
justify-content: center;
|
|
1115
|
+
gap: 3px;
|
|
1116
|
+
width: 100%;
|
|
1117
|
+
height: 100%;
|
|
1070
1118
|
overflow: hidden;
|
|
1071
|
-
|
|
1119
|
+
box-sizing: border-box;
|
|
1120
|
+
padding: 6px 8px;
|
|
1072
1121
|
}
|
|
1073
1122
|
.fs-ev-live {
|
|
1074
1123
|
flex-shrink: 0;
|
|
@@ -1109,7 +1158,14 @@
|
|
|
1109
1158
|
overflow: hidden;
|
|
1110
1159
|
text-overflow: ellipsis;
|
|
1111
1160
|
}
|
|
1112
|
-
.fs-ev-
|
|
1161
|
+
.fs-ev-loc {
|
|
1162
|
+
font: 400 10px/1 var(--dt-sans, system-ui, sans-serif);
|
|
1163
|
+
color: var(--dt-text-3, rgba(148, 163, 184, 0.5));
|
|
1164
|
+
white-space: nowrap;
|
|
1165
|
+
overflow: hidden;
|
|
1166
|
+
text-overflow: ellipsis;
|
|
1167
|
+
}
|
|
1168
|
+
.fs-ev-tags { display: flex; gap: 4px; flex-wrap: wrap; }
|
|
1113
1169
|
.fs-ev-tag {
|
|
1114
1170
|
font: 500 8px/1 var(--dt-sans, system-ui, sans-serif);
|
|
1115
1171
|
color: var(--ev-color, var(--dt-accent));
|
|
@@ -1121,7 +1177,7 @@
|
|
|
1121
1177
|
|
|
1122
1178
|
/* ─── Focus-visible ──────────────────────────────── */
|
|
1123
1179
|
.fs-event:focus-visible {
|
|
1124
|
-
outline: 2px solid var(--dt-accent, #
|
|
1180
|
+
outline: 2px solid var(--dt-accent, #2563eb);
|
|
1125
1181
|
outline-offset: 2px;
|
|
1126
1182
|
}
|
|
1127
1183
|
</style>
|