@nomideusz/svelte-calendar 0.9.0 → 0.11.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 +15 -7
- package/dist/calendar/Calendar.svelte +202 -60
- package/dist/calendar/Calendar.svelte.d.ts +7 -0
- package/dist/core/locale.d.ts +15 -1
- package/dist/core/locale.js +9 -2
- package/dist/theme/auto.js +27 -6
- package/dist/theme/presets.d.ts +4 -4
- package/dist/theme/presets.js +11 -7
- package/dist/views/agenda/AgendaDay.svelte +327 -126
- package/dist/views/agenda/AgendaWeek.svelte +345 -121
- package/dist/views/mobile/MobileDay.svelte +416 -213
- package/dist/views/mobile/MobileWeek.svelte +142 -74
- package/dist/views/mobile/swipe.d.ts +29 -0
- package/dist/views/mobile/swipe.js +73 -0
- package/dist/views/month/MonthGrid.svelte +200 -31
- package/dist/views/planner/PlannerDay.svelte +155 -80
- package/dist/views/planner/PlannerWeek.svelte +1155 -630
- package/dist/views/planner/PlannerWeek.svelte.d.ts +2 -0
- package/dist/views/shared/context.svelte.d.ts +3 -0
- package/dist/views/shared/context.svelte.js +13 -9
- package/dist/views/shared/format.d.ts +2 -1
- package/dist/views/shared/format.js +8 -5
- package/dist/widget/CalendarWidget.svelte +33 -5
- package/dist/widget/CalendarWidget.svelte.d.ts +16 -2
- package/dist/widget/widget.d.ts +9 -0
- package/dist/widget/widget.js +59 -13
- package/package.json +1 -1
- package/widget/widget.js +3364 -2783
- package/widget/svelte-calendar.css +0 -3054
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<script lang="ts">import { createClock } from "../../core/clock.svelte.js";
|
|
2
|
-
import { sod, DAY_MS, startOfWeek, dayNum, isAllDay, isMultiDay } from "../../core/time.js";
|
|
3
|
-
import { weekdayLong, monthLong
|
|
2
|
+
import { sod, DAY_MS, startOfWeek, dayNum, isAllDay, isMultiDay, segmentForDay } from "../../core/time.js";
|
|
3
|
+
import { weekdayLong, monthLong } from "../../core/locale.js";
|
|
4
4
|
import { useCalendarContext } from "../shared/context.svelte.js";
|
|
5
5
|
import EventContent from "../shared/EventContent.svelte";
|
|
6
6
|
import { fmtTime, duration, timeUntilMs, progress, groupIntoSlots } from "../shared/format.js";
|
|
7
|
-
const L = $derived(getLabels());
|
|
8
7
|
const ctx = useCalendarContext();
|
|
8
|
+
const L = $derived(ctx.labels);
|
|
9
9
|
let {
|
|
10
10
|
mondayStart = true,
|
|
11
11
|
locale,
|
|
12
|
-
height
|
|
12
|
+
height,
|
|
13
13
|
events = [],
|
|
14
14
|
style = "",
|
|
15
15
|
focusDate,
|
|
@@ -29,14 +29,17 @@ const oneventhover = $derived(ctx.oneventhover);
|
|
|
29
29
|
const disabledSet = $derived(ctx.disabledSet);
|
|
30
30
|
let swipeStartX = 0;
|
|
31
31
|
let swipeStartY = 0;
|
|
32
|
+
let swipeActive = false;
|
|
32
33
|
const SWIPE_THRESHOLD = 50;
|
|
33
34
|
function onPointerDown(e) {
|
|
34
|
-
if (!isMobile) return;
|
|
35
|
+
if (!isMobile || e.pointerType !== "touch") return;
|
|
36
|
+
swipeActive = true;
|
|
35
37
|
swipeStartX = e.clientX;
|
|
36
38
|
swipeStartY = e.clientY;
|
|
37
39
|
}
|
|
38
40
|
function onPointerUp(e) {
|
|
39
|
-
if (!
|
|
41
|
+
if (!swipeActive || e.pointerType !== "touch") return;
|
|
42
|
+
swipeActive = false;
|
|
40
43
|
const dx = e.clientX - swipeStartX;
|
|
41
44
|
const dy = e.clientY - swipeStartY;
|
|
42
45
|
if (Math.abs(dx) > SWIPE_THRESHOLD && Math.abs(dx) > Math.abs(dy) * 1.4) {
|
|
@@ -44,18 +47,23 @@ function onPointerUp(e) {
|
|
|
44
47
|
else viewState?.next();
|
|
45
48
|
}
|
|
46
49
|
}
|
|
50
|
+
function onPointerCancel() {
|
|
51
|
+
swipeActive = false;
|
|
52
|
+
}
|
|
53
|
+
let expandedDays = $state([]);
|
|
54
|
+
function toggleDayExpand(ms) {
|
|
55
|
+
expandedDays = expandedDays.includes(ms) ? expandedDays.filter((m) => m !== ms) : [...expandedDays, ms];
|
|
56
|
+
}
|
|
57
|
+
let expandedPast = $state([]);
|
|
58
|
+
function togglePastExpand(ms) {
|
|
59
|
+
expandedPast = expandedPast.includes(ms) ? expandedPast.filter((m) => m !== ms) : [...expandedPast, ms];
|
|
60
|
+
}
|
|
47
61
|
const fmt = (d) => fmtTime(d, locale);
|
|
48
|
-
const eta = (ms) => timeUntilMs(ms, clock.tick);
|
|
62
|
+
const eta = (ms) => timeUntilMs(ms, clock.tick, L);
|
|
49
63
|
const prog = (ev) => progress(ev, clock.tick);
|
|
50
64
|
function handleClick(ev) {
|
|
51
65
|
oneventclick?.(ev);
|
|
52
66
|
}
|
|
53
|
-
function handleKeydown(e, ev) {
|
|
54
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
55
|
-
e.preventDefault();
|
|
56
|
-
oneventclick?.(ev);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
67
|
const weekStartMs = $derived(
|
|
60
68
|
focusDate ? viewState?.dayCount === 7 ? startOfWeek(sod(focusDate.getTime()), mondayStart) : sod(focusDate.getTime()) : viewState?.dayCount === 7 ? startOfWeek(clock.today, mondayStart) : clock.today
|
|
61
69
|
);
|
|
@@ -101,6 +109,7 @@ const weekDays = $derived.by(() => {
|
|
|
101
109
|
dayName: weekdayLong(ms, locale),
|
|
102
110
|
dateLabel: `${monthLong(ms, locale)} ${dayNum(ms)}`,
|
|
103
111
|
tier,
|
|
112
|
+
isToday: ms === todayMs,
|
|
104
113
|
events: dayEvts,
|
|
105
114
|
allDayEvents: allDayEvts,
|
|
106
115
|
timedEvents: timedEvts,
|
|
@@ -123,7 +132,8 @@ const weekDays = $derived.by(() => {
|
|
|
123
132
|
|
|
124
133
|
<!-- ═══ Shared event card snippet ═══ -->
|
|
125
134
|
{#snippet eventCard(ev: TimelineEvent, isNow: boolean, eta?: string)}
|
|
126
|
-
<
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
127
137
|
class="ag-card"
|
|
128
138
|
class:ag-card--selected={selectedEventId === ev.id}
|
|
129
139
|
class:ag-card--cancelled={ev.status === 'cancelled'}
|
|
@@ -131,12 +141,9 @@ const weekDays = $derived.by(() => {
|
|
|
131
141
|
class:ag-card--full={ev.status === 'full'}
|
|
132
142
|
class:ag-card--limited={ev.status === 'limited'}
|
|
133
143
|
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
134
|
-
role="button"
|
|
135
|
-
tabindex="0"
|
|
136
144
|
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)}"
|
|
137
145
|
onclick={() => handleClick(ev)}
|
|
138
146
|
onpointerenter={() => oneventhover?.(ev)}
|
|
139
|
-
onkeydown={(e) => handleKeydown(e, ev)}
|
|
140
147
|
>
|
|
141
148
|
<div class="ag-card-body">
|
|
142
149
|
<EventContent event={ev}>
|
|
@@ -168,11 +175,64 @@ const weekDays = $derived.by(() => {
|
|
|
168
175
|
</EventContent>
|
|
169
176
|
{#if isNow}
|
|
170
177
|
<div class="ag-card-progress">
|
|
171
|
-
<div class="ag-card-progress-fill" style:
|
|
178
|
+
<div class="ag-card-progress-fill" style:transform="scaleX({prog(ev)})"></div>
|
|
172
179
|
</div>
|
|
173
180
|
{/if}
|
|
174
181
|
</div>
|
|
175
|
-
</
|
|
182
|
+
</button>
|
|
183
|
+
{/snippet}
|
|
184
|
+
|
|
185
|
+
<!-- ═══ Shared compact row snippet ═══ -->
|
|
186
|
+
{#snippet compactRow(ev: TimelineEvent, showLoc: boolean, done: boolean)}
|
|
187
|
+
<button
|
|
188
|
+
type="button"
|
|
189
|
+
class="ag-compact"
|
|
190
|
+
class:ag-compact--selected={selectedEventId === ev.id}
|
|
191
|
+
class:ag-compact--done={done}
|
|
192
|
+
class:ag-compact--cancelled={ev.status === 'cancelled'}
|
|
193
|
+
class:ag-compact--tentative={ev.status === 'tentative'}
|
|
194
|
+
class:ag-compact--full={ev.status === 'full'}
|
|
195
|
+
class:ag-compact--limited={ev.status === 'limited'}
|
|
196
|
+
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
197
|
+
aria-label="{ev.title}{done ? `, ${L.completed}` : ''}, {fmt(ev.start)}, {duration(ev)}"
|
|
198
|
+
onclick={() => handleClick(ev)}
|
|
199
|
+
onpointerenter={() => oneventhover?.(ev)}
|
|
200
|
+
>
|
|
201
|
+
<EventContent event={ev}>
|
|
202
|
+
<span class="ag-compact-dot"></span>
|
|
203
|
+
<span class="ag-compact-time">{fmt(ev.start)}</span>
|
|
204
|
+
<div class="ag-compact-main">
|
|
205
|
+
<span class="ag-compact-title">{ev.title}</span>
|
|
206
|
+
{#if showLoc && ev.location}
|
|
207
|
+
<span class="ag-compact-loc">{ev.location}</span>
|
|
208
|
+
{/if}
|
|
209
|
+
{#if ev.subtitle}
|
|
210
|
+
<span class="ag-compact-sub">{ev.subtitle}</span>
|
|
211
|
+
{/if}
|
|
212
|
+
{#if ev.tags?.length}
|
|
213
|
+
{#each ev.tags as tag}
|
|
214
|
+
<span class="ag-compact-tag">{tag}</span>
|
|
215
|
+
{/each}
|
|
216
|
+
{/if}
|
|
217
|
+
</div>
|
|
218
|
+
<span class="ag-compact-dur">{duration(ev)}</span>
|
|
219
|
+
</EventContent>
|
|
220
|
+
</button>
|
|
221
|
+
{/snippet}
|
|
222
|
+
|
|
223
|
+
<!-- ═══ "✓ N completed" disclosure toggle ═══ -->
|
|
224
|
+
{#snippet pastToggle(ms: number, count: number, summary: boolean)}
|
|
225
|
+
{@const open = expandedPast.includes(ms)}
|
|
226
|
+
<button
|
|
227
|
+
type="button"
|
|
228
|
+
class="ag-wday-past-line ag-past-toggle"
|
|
229
|
+
class:ag-wday-past-line--summary={summary}
|
|
230
|
+
aria-expanded={open}
|
|
231
|
+
onclick={() => togglePastExpand(ms)}
|
|
232
|
+
>
|
|
233
|
+
✓ {L.nCompleted(count)}
|
|
234
|
+
<svg class="ag-past-chevron" class:ag-past-chevron--open={open} viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><path d="M4 6l4 4 4-4"/></svg>
|
|
235
|
+
</button>
|
|
176
236
|
{/snippet}
|
|
177
237
|
|
|
178
238
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
@@ -181,8 +241,10 @@ const weekDays = $derived.by(() => {
|
|
|
181
241
|
class:ag--mobile={isMobile}
|
|
182
242
|
class:ag--auto={autoHeight}
|
|
183
243
|
style={style || undefined}
|
|
244
|
+
style:height={height ? `${height}px` : undefined}
|
|
184
245
|
onpointerdown={onPointerDown}
|
|
185
246
|
onpointerup={onPointerUp}
|
|
247
|
+
onpointercancel={onPointerCancel}
|
|
186
248
|
>
|
|
187
249
|
<div class="ag-body" role="list" aria-label={L.weekAhead}>
|
|
188
250
|
{#each weekDays as day (day.ms)}
|
|
@@ -201,6 +263,18 @@ const weekDays = $derived.by(() => {
|
|
|
201
263
|
</div>
|
|
202
264
|
{/if}
|
|
203
265
|
</div>
|
|
266
|
+
{#if day.timedEvents.length > 0}
|
|
267
|
+
{@render pastToggle(day.ms, day.timedEvents.length, true)}
|
|
268
|
+
{#if expandedPast.includes(day.ms)}
|
|
269
|
+
<div class="ag-wday-compact">
|
|
270
|
+
{#each day.timedEvents as ev (ev.id)}
|
|
271
|
+
{@render compactRow(ev, false, true)}
|
|
272
|
+
{/each}
|
|
273
|
+
</div>
|
|
274
|
+
{/if}
|
|
275
|
+
{:else if day.events.length === 0}
|
|
276
|
+
<div class="ag-wday-past-line ag-wday-past-line--summary">{L.noEvents}</div>
|
|
277
|
+
{/if}
|
|
204
278
|
</div>
|
|
205
279
|
{:else}
|
|
206
280
|
<div
|
|
@@ -214,7 +288,7 @@ const weekDays = $derived.by(() => {
|
|
|
214
288
|
<!-- Day header -->
|
|
215
289
|
<div class="ag-wday-head">
|
|
216
290
|
<div class="ag-wday-head-left">
|
|
217
|
-
{#if day.
|
|
291
|
+
{#if day.isToday}
|
|
218
292
|
<span class="ag-wday-badge">{L.today}</span>
|
|
219
293
|
{:else if day.tier === 'tomorrow'}
|
|
220
294
|
<span class="ag-wday-badge ag-wday-badge--muted">{L.tomorrow}</span>
|
|
@@ -232,20 +306,23 @@ const weekDays = $derived.by(() => {
|
|
|
232
306
|
{#if day.allDayEvents.length > 0}
|
|
233
307
|
<div class="ag-allday">
|
|
234
308
|
{#each day.allDayEvents as ev (ev.id)}
|
|
235
|
-
|
|
309
|
+
{@const seg = segmentForDay(ev, day.ms)}
|
|
310
|
+
{@const isCont = seg !== null && seg.totalDays > 1}
|
|
311
|
+
<button
|
|
312
|
+
type="button"
|
|
236
313
|
class="ag-allday-chip"
|
|
237
314
|
class:ag-allday-chip--selected={selectedEventId === ev.id}
|
|
238
315
|
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
239
|
-
|
|
240
|
-
tabindex="0"
|
|
241
|
-
aria-label="{ev.title}, {L.allDay}"
|
|
316
|
+
aria-label="{ev.title}, {isCont && seg ? L.dayNOfTotal(seg.dayIndex, seg.totalDays) : L.allDay}"
|
|
242
317
|
onclick={() => handleClick(ev)}
|
|
243
318
|
onpointerenter={() => oneventhover?.(ev)}
|
|
244
|
-
onkeydown={(e) => handleKeydown(e, ev)}
|
|
245
319
|
>
|
|
246
320
|
<span class="ag-allday-dot"></span>
|
|
247
321
|
<span class="ag-allday-title">{ev.title}</span>
|
|
248
|
-
|
|
322
|
+
{#if isCont && seg}
|
|
323
|
+
<span class="ag-allday-span">{seg.dayIndex}/{seg.totalDays}</span>
|
|
324
|
+
{/if}
|
|
325
|
+
</button>
|
|
249
326
|
{/each}
|
|
250
327
|
</div>
|
|
251
328
|
{/if}
|
|
@@ -256,36 +333,7 @@ const weekDays = $derived.by(() => {
|
|
|
256
333
|
<!-- Compact: minimal dot + time + title rows for all days -->
|
|
257
334
|
<div class="ag-wday-compact">
|
|
258
335
|
{#each day.timedEvents as ev (ev.id)}
|
|
259
|
-
|
|
260
|
-
class="ag-compact"
|
|
261
|
-
class:ag-compact--selected={selectedEventId === ev.id}
|
|
262
|
-
class:ag-compact--cancelled={ev.status === 'cancelled'}
|
|
263
|
-
class:ag-compact--tentative={ev.status === 'tentative'}
|
|
264
|
-
class:ag-compact--full={ev.status === 'full'}
|
|
265
|
-
class:ag-compact--limited={ev.status === 'limited'}
|
|
266
|
-
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
267
|
-
role="button"
|
|
268
|
-
tabindex="0"
|
|
269
|
-
aria-label="{ev.title}, {fmt(ev.start)}, {duration(ev)}"
|
|
270
|
-
onclick={() => handleClick(ev)}
|
|
271
|
-
onpointerenter={() => oneventhover?.(ev)}
|
|
272
|
-
onkeydown={(e) => handleKeydown(e, ev)}
|
|
273
|
-
>
|
|
274
|
-
<EventContent event={ev}>
|
|
275
|
-
<span class="ag-compact-dot"></span>
|
|
276
|
-
<span class="ag-compact-time">{fmt(ev.start)}</span>
|
|
277
|
-
<span class="ag-compact-title">{ev.title}</span>
|
|
278
|
-
{#if ev.subtitle}
|
|
279
|
-
<span class="ag-compact-sub">{ev.subtitle}</span>
|
|
280
|
-
{/if}
|
|
281
|
-
{#if ev.tags?.length}
|
|
282
|
-
{#each ev.tags as tag}
|
|
283
|
-
<span class="ag-compact-tag">{tag}</span>
|
|
284
|
-
{/each}
|
|
285
|
-
{/if}
|
|
286
|
-
<span class="ag-compact-dur">{duration(ev)}</span>
|
|
287
|
-
</EventContent>
|
|
288
|
-
</div>
|
|
336
|
+
{@render compactRow(ev, false, false)}
|
|
289
337
|
{/each}
|
|
290
338
|
</div>
|
|
291
339
|
{:else if equalDays}
|
|
@@ -326,47 +374,30 @@ const weekDays = $derived.by(() => {
|
|
|
326
374
|
</div>
|
|
327
375
|
{/each}
|
|
328
376
|
{#if day.pastEvents.length > 0}
|
|
329
|
-
|
|
377
|
+
{@render pastToggle(day.ms, day.pastEvents.length, false)}
|
|
378
|
+
{#if expandedPast.includes(day.ms)}
|
|
379
|
+
{#each day.pastEvents as ev (ev.id)}
|
|
380
|
+
{@render compactRow(ev, false, true)}
|
|
381
|
+
{/each}
|
|
382
|
+
{/if}
|
|
330
383
|
{/if}
|
|
331
384
|
</div>
|
|
332
385
|
{:else}
|
|
333
386
|
<!-- Compact: future days get minimal rows -->
|
|
387
|
+
{@const dayExpanded = expandedDays.includes(day.ms)}
|
|
334
388
|
<div class="ag-wday-compact">
|
|
335
|
-
{#each day.timedEvents.slice(0, 4) as ev (ev.id)}
|
|
336
|
-
|
|
337
|
-
class="ag-compact"
|
|
338
|
-
class:ag-compact--selected={selectedEventId === ev.id}
|
|
339
|
-
class:ag-compact--cancelled={ev.status === 'cancelled'}
|
|
340
|
-
class:ag-compact--tentative={ev.status === 'tentative'}
|
|
341
|
-
class:ag-compact--full={ev.status === 'full'}
|
|
342
|
-
class:ag-compact--limited={ev.status === 'limited'}
|
|
343
|
-
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
344
|
-
role="button"
|
|
345
|
-
tabindex="0"
|
|
346
|
-
aria-label="{ev.title}, {fmt(ev.start)}, {duration(ev)}"
|
|
347
|
-
onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
|
|
348
|
-
>
|
|
349
|
-
<EventContent event={ev}>
|
|
350
|
-
<span class="ag-compact-dot"></span>
|
|
351
|
-
<span class="ag-compact-time">{fmt(ev.start)}</span>
|
|
352
|
-
<span class="ag-compact-title">{ev.title}</span>
|
|
353
|
-
{#if ev.location}
|
|
354
|
-
<span class="ag-compact-loc">{ev.location}</span>
|
|
355
|
-
{/if}
|
|
356
|
-
{#if ev.subtitle}
|
|
357
|
-
<span class="ag-compact-sub">{ev.subtitle}</span>
|
|
358
|
-
{/if}
|
|
359
|
-
{#if ev.tags?.length}
|
|
360
|
-
{#each ev.tags as tag}
|
|
361
|
-
<span class="ag-compact-tag">{tag}</span>
|
|
362
|
-
{/each}
|
|
363
|
-
{/if}
|
|
364
|
-
<span class="ag-compact-dur">{duration(ev)}</span>
|
|
365
|
-
</EventContent>
|
|
366
|
-
</div>
|
|
389
|
+
{#each (dayExpanded ? day.timedEvents : day.timedEvents.slice(0, 4)) as ev (ev.id)}
|
|
390
|
+
{@render compactRow(ev, true, false)}
|
|
367
391
|
{/each}
|
|
368
392
|
{#if day.timedEvents.length > 4}
|
|
369
|
-
<
|
|
393
|
+
<button
|
|
394
|
+
type="button"
|
|
395
|
+
class="ag-compact-more"
|
|
396
|
+
aria-expanded={dayExpanded}
|
|
397
|
+
onclick={() => toggleDayExpand(day.ms)}
|
|
398
|
+
>
|
|
399
|
+
{dayExpanded ? L.showLess : L.nMore(day.timedEvents.length - 4)}
|
|
400
|
+
</button>
|
|
370
401
|
{/if}
|
|
371
402
|
</div>
|
|
372
403
|
{/if}
|
|
@@ -397,6 +428,23 @@ const weekDays = $derived.by(() => {
|
|
|
397
428
|
overflow: visible;
|
|
398
429
|
}
|
|
399
430
|
|
|
431
|
+
/* Button UA reset for interactive cards/rows (real <button>s for a11y).
|
|
432
|
+
Placed first so later component rules override it. */
|
|
433
|
+
.ag-card,
|
|
434
|
+
.ag-allday-chip,
|
|
435
|
+
.ag-compact,
|
|
436
|
+
.ag-compact-more,
|
|
437
|
+
.ag-past-toggle {
|
|
438
|
+
font: inherit;
|
|
439
|
+
color: inherit;
|
|
440
|
+
text-align: left;
|
|
441
|
+
background: none;
|
|
442
|
+
border: none;
|
|
443
|
+
padding: 0;
|
|
444
|
+
margin: 0;
|
|
445
|
+
box-sizing: border-box;
|
|
446
|
+
}
|
|
447
|
+
|
|
400
448
|
/* ═══ Body ═══ */
|
|
401
449
|
.ag-body {
|
|
402
450
|
flex: 1;
|
|
@@ -404,10 +452,15 @@ const weekDays = $derived.by(() => {
|
|
|
404
452
|
overflow-y: auto;
|
|
405
453
|
overflow-x: hidden;
|
|
406
454
|
box-sizing: border-box;
|
|
407
|
-
padding-top:
|
|
455
|
+
/* No padding-top here: the sticky day headers pin at the scrollport
|
|
456
|
+
edge, and container padding would leave a see-through band above
|
|
457
|
+
them where scrolled cards bleed out. */
|
|
408
458
|
scrollbar-width: thin;
|
|
409
459
|
scrollbar-color: var(--dt-border) transparent;
|
|
410
460
|
}
|
|
461
|
+
.ag-wday:first-child .ag-wday-head {
|
|
462
|
+
padding-top: 12px;
|
|
463
|
+
}
|
|
411
464
|
.ag--auto .ag-body {
|
|
412
465
|
overflow-y: visible;
|
|
413
466
|
}
|
|
@@ -437,13 +490,14 @@ const weekDays = $derived.by(() => {
|
|
|
437
490
|
cursor: pointer;
|
|
438
491
|
transition: background 0.15s, border-color 0.15s;
|
|
439
492
|
}
|
|
440
|
-
.ag-allday-chip:hover
|
|
493
|
+
.ag-allday-chip:hover,
|
|
494
|
+
.ag-allday-chip:active {
|
|
441
495
|
background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
442
496
|
border-color: color-mix(in srgb, var(--ev-color) 30%, transparent);
|
|
443
497
|
}
|
|
444
498
|
.ag-allday-chip:focus-visible {
|
|
445
|
-
outline:
|
|
446
|
-
|
|
499
|
+
outline: none;
|
|
500
|
+
box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
|
|
447
501
|
}
|
|
448
502
|
.ag-allday-chip--selected {
|
|
449
503
|
border-color: var(--ev-color);
|
|
@@ -461,6 +515,11 @@ const weekDays = $derived.by(() => {
|
|
|
461
515
|
color: var(--dt-text, rgba(0, 0, 0, 0.87));
|
|
462
516
|
white-space: nowrap;
|
|
463
517
|
}
|
|
518
|
+
.ag-allday-span {
|
|
519
|
+
font: 500 10px/1.2 var(--dt-mono, monospace);
|
|
520
|
+
color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
|
|
521
|
+
white-space: nowrap;
|
|
522
|
+
}
|
|
464
523
|
|
|
465
524
|
/* ═══ Shared: event card ═══ */
|
|
466
525
|
.ag-card {
|
|
@@ -473,13 +532,14 @@ const weekDays = $derived.by(() => {
|
|
|
473
532
|
cursor: pointer;
|
|
474
533
|
transition: background 150ms, border-color 150ms;
|
|
475
534
|
}
|
|
476
|
-
.ag-card:hover
|
|
535
|
+
.ag-card:hover,
|
|
536
|
+
.ag-card:active {
|
|
477
537
|
background: color-mix(in srgb, var(--ev-color) 20%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
478
538
|
border-color: color-mix(in srgb, var(--ev-color) 30%, transparent);
|
|
479
539
|
}
|
|
480
540
|
.ag-card:focus-visible {
|
|
481
|
-
outline:
|
|
482
|
-
|
|
541
|
+
outline: none;
|
|
542
|
+
box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
|
|
483
543
|
}
|
|
484
544
|
.ag-card--selected {
|
|
485
545
|
border-color: var(--ev-color);
|
|
@@ -517,6 +577,11 @@ const weekDays = $derived.by(() => {
|
|
|
517
577
|
word-break: break-word;
|
|
518
578
|
flex: 1;
|
|
519
579
|
min-width: 0;
|
|
580
|
+
display: -webkit-box;
|
|
581
|
+
-webkit-box-orient: vertical;
|
|
582
|
+
-webkit-line-clamp: 2;
|
|
583
|
+
line-clamp: 2;
|
|
584
|
+
overflow: hidden;
|
|
520
585
|
}
|
|
521
586
|
.ag-card-meta {
|
|
522
587
|
display: flex;
|
|
@@ -532,10 +597,9 @@ const weekDays = $derived.by(() => {
|
|
|
532
597
|
}
|
|
533
598
|
.ag-card-eta {
|
|
534
599
|
margin-left: auto;
|
|
535
|
-
font-size:
|
|
600
|
+
font-size: 11px;
|
|
536
601
|
font-weight: 600;
|
|
537
|
-
color: color-mix(in srgb, var(--
|
|
538
|
-
opacity: 0.85;
|
|
602
|
+
color: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 60%, var(--ev-color));
|
|
539
603
|
letter-spacing: 0.02em;
|
|
540
604
|
}
|
|
541
605
|
.ag-card-sub {
|
|
@@ -554,7 +618,7 @@ const weekDays = $derived.by(() => {
|
|
|
554
618
|
flex-wrap: wrap;
|
|
555
619
|
}
|
|
556
620
|
.ag-card-tag {
|
|
557
|
-
font: 500
|
|
621
|
+
font: 500 10px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
558
622
|
color: var(--ev-color, var(--dt-accent));
|
|
559
623
|
background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
|
|
560
624
|
padding: 2px 5px;
|
|
@@ -570,9 +634,16 @@ const weekDays = $derived.by(() => {
|
|
|
570
634
|
}
|
|
571
635
|
.ag-card-progress-fill {
|
|
572
636
|
height: 100%;
|
|
637
|
+
width: 100%;
|
|
573
638
|
background: var(--ev-color, var(--dt-accent));
|
|
574
639
|
border-radius: 2px;
|
|
575
|
-
|
|
640
|
+
transform-origin: left;
|
|
641
|
+
transition: transform 1s linear;
|
|
642
|
+
}
|
|
643
|
+
@media (prefers-reduced-motion: reduce) {
|
|
644
|
+
.ag-card-progress-fill {
|
|
645
|
+
transition: none;
|
|
646
|
+
}
|
|
576
647
|
}
|
|
577
648
|
|
|
578
649
|
/* ═══ Week day groups ═══ */
|
|
@@ -588,11 +659,13 @@ const weekDays = $derived.by(() => {
|
|
|
588
659
|
.ag-wday--tomorrow .ag-card {
|
|
589
660
|
opacity: 0.82;
|
|
590
661
|
}
|
|
591
|
-
|
|
592
|
-
|
|
662
|
+
/* Past days: token-based text dim instead of subtree opacity (readability) */
|
|
663
|
+
.ag-wday--past .ag-wday-name {
|
|
664
|
+
color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
|
|
665
|
+
font-weight: 500;
|
|
593
666
|
}
|
|
594
667
|
.ag-wday--past .ag-wday-head {
|
|
595
|
-
padding: 8px 20px;
|
|
668
|
+
padding: 8px 20px 2px;
|
|
596
669
|
}
|
|
597
670
|
.ag-wday--disabled {
|
|
598
671
|
position: relative;
|
|
@@ -605,8 +678,8 @@ const weekDays = $derived.by(() => {
|
|
|
605
678
|
135deg,
|
|
606
679
|
transparent,
|
|
607
680
|
transparent 4px,
|
|
608
|
-
rgba(
|
|
609
|
-
rgba(
|
|
681
|
+
color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 8%, transparent) 4px,
|
|
682
|
+
color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 8%, transparent) 8px
|
|
610
683
|
);
|
|
611
684
|
pointer-events: none;
|
|
612
685
|
}
|
|
@@ -619,6 +692,14 @@ const weekDays = $derived.by(() => {
|
|
|
619
692
|
justify-content: space-between;
|
|
620
693
|
align-items: center;
|
|
621
694
|
padding: 8px 20px;
|
|
695
|
+
position: sticky;
|
|
696
|
+
top: 0;
|
|
697
|
+
background: var(--dt-bg, #fff);
|
|
698
|
+
z-index: 1;
|
|
699
|
+
/* Own compositor layer: without it, fast (async) scrolling repaints
|
|
700
|
+
the pinned header a frame late and a gap flashes above it. */
|
|
701
|
+
transform: translateZ(0);
|
|
702
|
+
will-change: transform;
|
|
622
703
|
}
|
|
623
704
|
.ag-wday-head-left {
|
|
624
705
|
display: flex;
|
|
@@ -676,7 +757,7 @@ const weekDays = $derived.by(() => {
|
|
|
676
757
|
padding: 2px 0;
|
|
677
758
|
}
|
|
678
759
|
.ag-wslot-now {
|
|
679
|
-
font-size:
|
|
760
|
+
font-size: 10px;
|
|
680
761
|
font-weight: 700;
|
|
681
762
|
letter-spacing: 0.08em;
|
|
682
763
|
text-transform: uppercase;
|
|
@@ -693,9 +774,47 @@ const weekDays = $derived.by(() => {
|
|
|
693
774
|
gap: 4px;
|
|
694
775
|
}
|
|
695
776
|
.ag-wday-past-line {
|
|
696
|
-
font-size:
|
|
777
|
+
font-size: 11px;
|
|
697
778
|
color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
|
|
698
779
|
padding: 6px 0 0;
|
|
780
|
+
}
|
|
781
|
+
.ag-wday-past-line--summary {
|
|
782
|
+
padding: 0 20px 8px;
|
|
783
|
+
}
|
|
784
|
+
/* "✓ N completed" is a disclosure — tap to reveal the finished events */
|
|
785
|
+
.ag-past-toggle {
|
|
786
|
+
display: inline-flex;
|
|
787
|
+
align-items: center;
|
|
788
|
+
gap: 5px;
|
|
789
|
+
cursor: pointer;
|
|
790
|
+
min-height: 32px;
|
|
791
|
+
transition: color 150ms;
|
|
792
|
+
-webkit-tap-highlight-color: transparent;
|
|
793
|
+
}
|
|
794
|
+
.ag-past-toggle:hover,
|
|
795
|
+
.ag-past-toggle:active {
|
|
796
|
+
color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
|
|
797
|
+
}
|
|
798
|
+
.ag-past-toggle:focus-visible {
|
|
799
|
+
outline: none;
|
|
800
|
+
box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
|
|
801
|
+
border-radius: 4px;
|
|
802
|
+
}
|
|
803
|
+
.ag-past-chevron {
|
|
804
|
+
transition: transform 120ms;
|
|
805
|
+
}
|
|
806
|
+
.ag-past-chevron--open {
|
|
807
|
+
transform: rotate(180deg);
|
|
808
|
+
}
|
|
809
|
+
@media (prefers-reduced-motion: reduce) {
|
|
810
|
+
.ag-past-chevron { transition: none; }
|
|
811
|
+
}
|
|
812
|
+
/* Revealed completed events: dim + strike, single token layer */
|
|
813
|
+
.ag-compact--done .ag-compact-title {
|
|
814
|
+
text-decoration: line-through;
|
|
815
|
+
text-decoration-color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
|
|
816
|
+
}
|
|
817
|
+
.ag-compact--done .ag-compact-dot {
|
|
699
818
|
opacity: 0.5;
|
|
700
819
|
}
|
|
701
820
|
|
|
@@ -710,19 +829,31 @@ const weekDays = $derived.by(() => {
|
|
|
710
829
|
padding: 3px 0;
|
|
711
830
|
cursor: pointer;
|
|
712
831
|
min-width: 0;
|
|
832
|
+
width: 100%;
|
|
713
833
|
}
|
|
714
834
|
.ag-compact--selected {
|
|
715
835
|
background: color-mix(in srgb, var(--ev-color) 10%, transparent);
|
|
716
836
|
border-radius: 4px;
|
|
837
|
+
/* Highlight gutter comes from negative margins so the row's content
|
|
838
|
+
stays aligned with its unselected siblings (no tap-shift). */
|
|
717
839
|
padding-left: 6px;
|
|
718
840
|
padding-right: 6px;
|
|
841
|
+
margin-left: -6px;
|
|
842
|
+
margin-right: -6px;
|
|
843
|
+
width: calc(100% + 12px);
|
|
719
844
|
}
|
|
720
|
-
.ag-compact:hover .ag-compact-title
|
|
845
|
+
.ag-compact:hover .ag-compact-title,
|
|
846
|
+
.ag-compact:active .ag-compact-title {
|
|
721
847
|
color: var(--dt-text);
|
|
722
848
|
}
|
|
849
|
+
.ag-compact:active {
|
|
850
|
+
background: color-mix(in srgb, var(--ev-color) 8%, transparent);
|
|
851
|
+
border-radius: 4px;
|
|
852
|
+
}
|
|
723
853
|
.ag-compact:focus-visible {
|
|
724
|
-
outline:
|
|
725
|
-
|
|
854
|
+
outline: none;
|
|
855
|
+
box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
|
|
856
|
+
border-radius: 4px;
|
|
726
857
|
}
|
|
727
858
|
.ag-compact-dot {
|
|
728
859
|
width: 5px;
|
|
@@ -741,12 +872,45 @@ const weekDays = $derived.by(() => {
|
|
|
741
872
|
white-space: nowrap;
|
|
742
873
|
line-height: 1.4;
|
|
743
874
|
}
|
|
875
|
+
/* Title + location + subtitle + tags cluster. One line while it fits; on
|
|
876
|
+
mobile the metadata wraps to a second line instead of crushing the title. */
|
|
877
|
+
.ag-compact-main {
|
|
878
|
+
display: flex;
|
|
879
|
+
align-items: baseline;
|
|
880
|
+
gap: 6px;
|
|
881
|
+
flex: 1;
|
|
882
|
+
min-width: 0;
|
|
883
|
+
}
|
|
884
|
+
.ag--mobile .ag-compact-main {
|
|
885
|
+
flex-wrap: wrap;
|
|
886
|
+
row-gap: 2px;
|
|
887
|
+
}
|
|
888
|
+
/* Mobile: size the title by its content when deciding line breaks — a long
|
|
889
|
+
title claims the first line whole (ellipsizing only against the full row)
|
|
890
|
+
and pushes location/subtitle/tags down instead of truncating at 35%. */
|
|
891
|
+
.ag--mobile .ag-compact-title {
|
|
892
|
+
flex-basis: auto;
|
|
893
|
+
}
|
|
894
|
+
/* On their own wrapped line the metadata gets the full width — the tight
|
|
895
|
+
desktop caps would truncate it beside empty space. */
|
|
896
|
+
.ag--mobile .ag-compact-loc,
|
|
897
|
+
.ag--mobile .ag-compact-sub {
|
|
898
|
+
max-width: 100%;
|
|
899
|
+
}
|
|
900
|
+
/* Wrapped rows are two/three lines tall — center-aligning the dot floats
|
|
901
|
+
it between lines; pin it optically to the first (title) line instead. */
|
|
902
|
+
.ag--mobile .ag-compact-dot {
|
|
903
|
+
align-self: flex-start;
|
|
904
|
+
margin-top: 8px;
|
|
905
|
+
}
|
|
744
906
|
.ag-compact-title {
|
|
745
907
|
font-size: 12px;
|
|
746
908
|
font-weight: 500;
|
|
747
909
|
color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
|
|
748
910
|
flex: 1;
|
|
749
|
-
|
|
911
|
+
/* The title is the row's identity — never let subtitle/tags/duration
|
|
912
|
+
squeeze it out on narrow screens (min-width: 0 resolves to 0px). */
|
|
913
|
+
min-width: 35%;
|
|
750
914
|
white-space: nowrap;
|
|
751
915
|
overflow: hidden;
|
|
752
916
|
text-overflow: ellipsis;
|
|
@@ -764,7 +928,8 @@ const weekDays = $derived.by(() => {
|
|
|
764
928
|
.ag-compact-sub {
|
|
765
929
|
font-size: 10px;
|
|
766
930
|
color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
|
|
767
|
-
flex-shrink:
|
|
931
|
+
flex-shrink: 3;
|
|
932
|
+
min-width: 0;
|
|
768
933
|
white-space: nowrap;
|
|
769
934
|
overflow: hidden;
|
|
770
935
|
text-overflow: ellipsis;
|
|
@@ -772,9 +937,10 @@ const weekDays = $derived.by(() => {
|
|
|
772
937
|
line-height: 1.4;
|
|
773
938
|
}
|
|
774
939
|
.ag-compact-loc {
|
|
775
|
-
font-size:
|
|
940
|
+
font-size: 10px;
|
|
776
941
|
color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
|
|
777
|
-
flex-shrink:
|
|
942
|
+
flex-shrink: 3;
|
|
943
|
+
min-width: 0;
|
|
778
944
|
white-space: nowrap;
|
|
779
945
|
overflow: hidden;
|
|
780
946
|
text-overflow: ellipsis;
|
|
@@ -796,21 +962,33 @@ const weekDays = $derived.by(() => {
|
|
|
796
962
|
opacity: 0.65;
|
|
797
963
|
}
|
|
798
964
|
.ag-compact-tag {
|
|
799
|
-
font: 500
|
|
965
|
+
font: 500 10px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
800
966
|
color: var(--ev-color, var(--dt-accent));
|
|
801
967
|
background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 12%, transparent);
|
|
802
968
|
padding: 1px 4px;
|
|
803
969
|
border-radius: 3px;
|
|
804
970
|
white-space: nowrap;
|
|
805
|
-
flex-shrink:
|
|
971
|
+
flex-shrink: 1;
|
|
972
|
+
min-width: 2.5em;
|
|
806
973
|
max-width: 80px;
|
|
807
974
|
overflow: hidden;
|
|
808
975
|
text-overflow: ellipsis;
|
|
809
976
|
}
|
|
810
977
|
.ag-compact-more {
|
|
811
978
|
font-size: 11px;
|
|
812
|
-
color: var(--dt-text
|
|
979
|
+
color: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 72%, transparent);
|
|
813
980
|
padding: 2px 0 0 13px;
|
|
981
|
+
cursor: pointer;
|
|
982
|
+
display: block;
|
|
983
|
+
}
|
|
984
|
+
.ag-compact-more:hover,
|
|
985
|
+
.ag-compact-more:active {
|
|
986
|
+
color: var(--dt-text, rgba(0, 0, 0, 0.87));
|
|
987
|
+
}
|
|
988
|
+
.ag-compact-more:focus-visible {
|
|
989
|
+
outline: none;
|
|
990
|
+
box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
|
|
991
|
+
border-radius: 4px;
|
|
814
992
|
}
|
|
815
993
|
|
|
816
994
|
/* ═══ Mobile adaptations ═══ */
|
|
@@ -827,17 +1005,63 @@ const weekDays = $derived.by(() => {
|
|
|
827
1005
|
padding: 12px 14px;
|
|
828
1006
|
}
|
|
829
1007
|
.ag--mobile .ag-card-title {
|
|
830
|
-
font-size:
|
|
1008
|
+
font-size: 15px;
|
|
1009
|
+
}
|
|
1010
|
+
.ag--mobile .ag-card-meta {
|
|
1011
|
+
font-size: 12px;
|
|
1012
|
+
}
|
|
1013
|
+
.ag--mobile .ag-card-sub {
|
|
1014
|
+
font-size: 12px;
|
|
1015
|
+
}
|
|
1016
|
+
.ag--mobile .ag-card-loc {
|
|
1017
|
+
font-size: 12px;
|
|
1018
|
+
}
|
|
1019
|
+
.ag--mobile .ag-card-eta {
|
|
1020
|
+
font-size: 12px;
|
|
1021
|
+
}
|
|
1022
|
+
.ag--mobile .ag-card-tag {
|
|
1023
|
+
font-size: 11px;
|
|
831
1024
|
}
|
|
832
1025
|
.ag--mobile .ag-compact {
|
|
833
1026
|
padding: 8px 0;
|
|
834
1027
|
}
|
|
835
1028
|
.ag--mobile .ag-compact-title {
|
|
836
|
-
font-size:
|
|
1029
|
+
font-size: 15px;
|
|
837
1030
|
}
|
|
838
1031
|
.ag--mobile .ag-compact-time {
|
|
839
1032
|
font-size: 12px;
|
|
840
1033
|
}
|
|
1034
|
+
.ag--mobile .ag-compact-dur {
|
|
1035
|
+
font-size: 12px;
|
|
1036
|
+
}
|
|
1037
|
+
.ag--mobile .ag-compact-sub {
|
|
1038
|
+
font-size: 12px;
|
|
1039
|
+
}
|
|
1040
|
+
.ag--mobile .ag-compact-loc {
|
|
1041
|
+
font-size: 11px;
|
|
1042
|
+
}
|
|
1043
|
+
.ag--mobile .ag-compact-tag {
|
|
1044
|
+
font-size: 11px;
|
|
1045
|
+
}
|
|
1046
|
+
.ag--mobile .ag-compact-more {
|
|
1047
|
+
font-size: 12px;
|
|
1048
|
+
padding-top: 6px;
|
|
1049
|
+
}
|
|
1050
|
+
.ag--mobile .ag-allday-span {
|
|
1051
|
+
font-size: 11px;
|
|
1052
|
+
}
|
|
1053
|
+
.ag--mobile .ag-wday-badge {
|
|
1054
|
+
font-size: 11px;
|
|
1055
|
+
}
|
|
1056
|
+
.ag--mobile .ag-wslot-now {
|
|
1057
|
+
font-size: 11px;
|
|
1058
|
+
}
|
|
1059
|
+
.ag--mobile .ag-wday-empty {
|
|
1060
|
+
font-size: 12px;
|
|
1061
|
+
}
|
|
1062
|
+
.ag--mobile .ag-wday-past-line {
|
|
1063
|
+
font-size: 12px;
|
|
1064
|
+
}
|
|
841
1065
|
.ag--mobile .ag-wslot-cards--multi {
|
|
842
1066
|
grid-template-columns: 1fr;
|
|
843
1067
|
}
|