@nomideusz/svelte-calendar 0.10.0 → 0.12.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 +2 -16
- package/dist/calendar/Calendar.svelte +43 -5
- package/dist/calendar/Calendar.svelte.d.ts +8 -0
- package/dist/core/index.d.ts +0 -2
- package/dist/core/index.js +0 -2
- package/dist/engine/view-state.svelte.d.ts +5 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/theme/auto.js +56 -21
- package/dist/theme/presets.d.ts +6 -5
- package/dist/theme/presets.js +2 -3
- package/dist/views/agenda/AgendaDay.svelte +69 -32
- package/dist/views/agenda/AgendaWeek.svelte +220 -83
- package/dist/views/mobile/MobileDay.svelte +11 -3
- package/dist/views/mobile/MobileWeek.svelte +6 -0
- package/dist/views/mobile/swipe.d.ts +1 -0
- package/dist/views/mobile/swipe.js +9 -0
- package/dist/views/month/MonthGrid.svelte +35 -2
- package/dist/views/planner/Planner.svelte +3 -7
- package/dist/views/planner/PlannerWeek.svelte +16 -7
- package/dist/views/planner/PlannerWeek.svelte.d.ts +2 -0
- package/dist/views/shared/context.svelte.d.ts +1 -0
- package/dist/views/shared/context.svelte.js +1 -0
- package/package.json +1 -8
- package/widget/widget.js +1381 -2428
- package/dist/core/measure.d.ts +0 -104
- package/dist/core/measure.js +0 -195
- package/dist/views/planner/PlannerDay.svelte +0 -1279
- package/dist/views/planner/PlannerDay.svelte.d.ts +0 -30
|
@@ -24,6 +24,7 @@ const hideDays = $derived(ctx.hideDays);
|
|
|
24
24
|
const isMobile = $derived(ctx.isMobile);
|
|
25
25
|
const autoHeight = $derived(ctx.autoHeight);
|
|
26
26
|
const compact = $derived(ctx.compact);
|
|
27
|
+
const cols = $derived(ctx.columns && !isMobile);
|
|
27
28
|
const dayHeaderSnippet = $derived(ctx.dayHeaderSnippet);
|
|
28
29
|
const oneventhover = $derived(ctx.oneventhover);
|
|
29
30
|
const disabledSet = $derived(ctx.disabledSet);
|
|
@@ -54,6 +55,10 @@ let expandedDays = $state([]);
|
|
|
54
55
|
function toggleDayExpand(ms) {
|
|
55
56
|
expandedDays = expandedDays.includes(ms) ? expandedDays.filter((m) => m !== ms) : [...expandedDays, ms];
|
|
56
57
|
}
|
|
58
|
+
let expandedPast = $state([]);
|
|
59
|
+
function togglePastExpand(ms) {
|
|
60
|
+
expandedPast = expandedPast.includes(ms) ? expandedPast.filter((m) => m !== ms) : [...expandedPast, ms];
|
|
61
|
+
}
|
|
57
62
|
const fmt = (d) => fmtTime(d, locale);
|
|
58
63
|
const eta = (ms) => timeUntilMs(ms, clock.tick, L);
|
|
59
64
|
const prog = (ev) => progress(ev, clock.tick);
|
|
@@ -178,18 +183,71 @@ const weekDays = $derived.by(() => {
|
|
|
178
183
|
</button>
|
|
179
184
|
{/snippet}
|
|
180
185
|
|
|
186
|
+
<!-- ═══ Shared compact row snippet ═══ -->
|
|
187
|
+
{#snippet compactRow(ev: TimelineEvent, showLoc: boolean, done: boolean)}
|
|
188
|
+
<button
|
|
189
|
+
type="button"
|
|
190
|
+
class="ag-compact"
|
|
191
|
+
class:ag-compact--selected={selectedEventId === ev.id}
|
|
192
|
+
class:ag-compact--done={done}
|
|
193
|
+
class:ag-compact--cancelled={ev.status === 'cancelled'}
|
|
194
|
+
class:ag-compact--tentative={ev.status === 'tentative'}
|
|
195
|
+
class:ag-compact--full={ev.status === 'full'}
|
|
196
|
+
class:ag-compact--limited={ev.status === 'limited'}
|
|
197
|
+
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
198
|
+
aria-label="{ev.title}{done ? `, ${L.completed}` : ''}, {fmt(ev.start)}, {duration(ev)}"
|
|
199
|
+
onclick={() => handleClick(ev)}
|
|
200
|
+
onpointerenter={() => oneventhover?.(ev)}
|
|
201
|
+
>
|
|
202
|
+
<EventContent event={ev}>
|
|
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>
|
|
236
|
+
{/snippet}
|
|
237
|
+
|
|
181
238
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
182
239
|
<div
|
|
183
240
|
class="ag ag--week"
|
|
184
241
|
class:ag--mobile={isMobile}
|
|
185
242
|
class:ag--auto={autoHeight}
|
|
243
|
+
class:ag--cols={cols}
|
|
186
244
|
style={style || undefined}
|
|
187
245
|
style:height={height ? `${height}px` : undefined}
|
|
188
246
|
onpointerdown={onPointerDown}
|
|
189
247
|
onpointerup={onPointerUp}
|
|
190
248
|
onpointercancel={onPointerCancel}
|
|
191
249
|
>
|
|
192
|
-
<div class="ag-body" role="list" aria-label={L.weekAhead}>
|
|
250
|
+
<div class="ag-body" role="list" aria-label={L.weekAhead} style:--ag-cols={weekDays.length}>
|
|
193
251
|
{#each weekDays as day (day.ms)}
|
|
194
252
|
{@const expanded = day.tier === 'today' || day.tier === 'tomorrow'}
|
|
195
253
|
{#if day.tier === 'past'}
|
|
@@ -207,7 +265,14 @@ const weekDays = $derived.by(() => {
|
|
|
207
265
|
{/if}
|
|
208
266
|
</div>
|
|
209
267
|
{#if day.timedEvents.length > 0}
|
|
210
|
-
|
|
268
|
+
{@render pastToggle(day.ms, day.timedEvents.length, true)}
|
|
269
|
+
{#if expandedPast.includes(day.ms)}
|
|
270
|
+
<div class="ag-wday-compact">
|
|
271
|
+
{#each day.timedEvents as ev (ev.id)}
|
|
272
|
+
{@render compactRow(ev, false, true)}
|
|
273
|
+
{/each}
|
|
274
|
+
</div>
|
|
275
|
+
{/if}
|
|
211
276
|
{:else if day.events.length === 0}
|
|
212
277
|
<div class="ag-wday-past-line ag-wday-past-line--summary">{L.noEvents}</div>
|
|
213
278
|
{/if}
|
|
@@ -265,42 +330,19 @@ const weekDays = $derived.by(() => {
|
|
|
265
330
|
|
|
266
331
|
{#if day.events.length === 0}
|
|
267
332
|
<div class="ag-wday-empty">{L.noEvents}</div>
|
|
268
|
-
{:else if compact}
|
|
269
|
-
<!-- Compact: minimal dot + time + title rows for all days
|
|
333
|
+
{:else if compact && !cols}
|
|
334
|
+
<!-- Compact: minimal dot + time + title rows for all days.
|
|
335
|
+
Ignored in columns mode — single-line rows truncate to
|
|
336
|
+
nothing at column width; cards wrap instead. -->
|
|
270
337
|
<div class="ag-wday-compact">
|
|
271
338
|
{#each day.timedEvents as ev (ev.id)}
|
|
272
|
-
|
|
273
|
-
type="button"
|
|
274
|
-
class="ag-compact"
|
|
275
|
-
class:ag-compact--selected={selectedEventId === ev.id}
|
|
276
|
-
class:ag-compact--cancelled={ev.status === 'cancelled'}
|
|
277
|
-
class:ag-compact--tentative={ev.status === 'tentative'}
|
|
278
|
-
class:ag-compact--full={ev.status === 'full'}
|
|
279
|
-
class:ag-compact--limited={ev.status === 'limited'}
|
|
280
|
-
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
281
|
-
aria-label="{ev.title}, {fmt(ev.start)}, {duration(ev)}"
|
|
282
|
-
onclick={() => handleClick(ev)}
|
|
283
|
-
onpointerenter={() => oneventhover?.(ev)}
|
|
284
|
-
>
|
|
285
|
-
<EventContent event={ev}>
|
|
286
|
-
<span class="ag-compact-dot"></span>
|
|
287
|
-
<span class="ag-compact-time">{fmt(ev.start)}</span>
|
|
288
|
-
<span class="ag-compact-title">{ev.title}</span>
|
|
289
|
-
{#if ev.subtitle}
|
|
290
|
-
<span class="ag-compact-sub">{ev.subtitle}</span>
|
|
291
|
-
{/if}
|
|
292
|
-
{#if ev.tags?.length}
|
|
293
|
-
{#each ev.tags as tag}
|
|
294
|
-
<span class="ag-compact-tag">{tag}</span>
|
|
295
|
-
{/each}
|
|
296
|
-
{/if}
|
|
297
|
-
<span class="ag-compact-dur">{duration(ev)}</span>
|
|
298
|
-
</EventContent>
|
|
299
|
-
</button>
|
|
339
|
+
{@render compactRow(ev, false, false)}
|
|
300
340
|
{/each}
|
|
301
341
|
</div>
|
|
302
|
-
{:else if equalDays}
|
|
303
|
-
<!-- Equal days: card layout
|
|
342
|
+
{:else if equalDays || (cols && !expanded)}
|
|
343
|
+
<!-- Equal days (or timetable columns): card layout, no time-relative
|
|
344
|
+
badges. In columns, today/tomorrow still fall through to the
|
|
345
|
+
expanded branch below for now/ETA treatment. -->
|
|
304
346
|
<div class="ag-wday-expanded">
|
|
305
347
|
{#each groupIntoSlots(day.timedEvents) as slot (slot.startMs)}
|
|
306
348
|
<div class="ag-wslot">
|
|
@@ -337,7 +379,12 @@ const weekDays = $derived.by(() => {
|
|
|
337
379
|
</div>
|
|
338
380
|
{/each}
|
|
339
381
|
{#if day.pastEvents.length > 0}
|
|
340
|
-
|
|
382
|
+
{@render pastToggle(day.ms, day.pastEvents.length, false)}
|
|
383
|
+
{#if expandedPast.includes(day.ms)}
|
|
384
|
+
{#each day.pastEvents as ev (ev.id)}
|
|
385
|
+
{@render compactRow(ev, false, true)}
|
|
386
|
+
{/each}
|
|
387
|
+
{/if}
|
|
341
388
|
{/if}
|
|
342
389
|
</div>
|
|
343
390
|
{:else}
|
|
@@ -345,37 +392,7 @@ const weekDays = $derived.by(() => {
|
|
|
345
392
|
{@const dayExpanded = expandedDays.includes(day.ms)}
|
|
346
393
|
<div class="ag-wday-compact">
|
|
347
394
|
{#each (dayExpanded ? day.timedEvents : day.timedEvents.slice(0, 4)) as ev (ev.id)}
|
|
348
|
-
|
|
349
|
-
type="button"
|
|
350
|
-
class="ag-compact"
|
|
351
|
-
class:ag-compact--selected={selectedEventId === ev.id}
|
|
352
|
-
class:ag-compact--cancelled={ev.status === 'cancelled'}
|
|
353
|
-
class:ag-compact--tentative={ev.status === 'tentative'}
|
|
354
|
-
class:ag-compact--full={ev.status === 'full'}
|
|
355
|
-
class:ag-compact--limited={ev.status === 'limited'}
|
|
356
|
-
style:--ev-color={ev.color || 'var(--dt-accent)'}
|
|
357
|
-
aria-label="{ev.title}, {fmt(ev.start)}, {duration(ev)}"
|
|
358
|
-
onclick={() => handleClick(ev)}
|
|
359
|
-
onpointerenter={() => oneventhover?.(ev)}
|
|
360
|
-
>
|
|
361
|
-
<EventContent event={ev}>
|
|
362
|
-
<span class="ag-compact-dot"></span>
|
|
363
|
-
<span class="ag-compact-time">{fmt(ev.start)}</span>
|
|
364
|
-
<span class="ag-compact-title">{ev.title}</span>
|
|
365
|
-
{#if ev.location}
|
|
366
|
-
<span class="ag-compact-loc">{ev.location}</span>
|
|
367
|
-
{/if}
|
|
368
|
-
{#if ev.subtitle}
|
|
369
|
-
<span class="ag-compact-sub">{ev.subtitle}</span>
|
|
370
|
-
{/if}
|
|
371
|
-
{#if ev.tags?.length}
|
|
372
|
-
{#each ev.tags as tag}
|
|
373
|
-
<span class="ag-compact-tag">{tag}</span>
|
|
374
|
-
{/each}
|
|
375
|
-
{/if}
|
|
376
|
-
<span class="ag-compact-dur">{duration(ev)}</span>
|
|
377
|
-
</EventContent>
|
|
378
|
-
</button>
|
|
395
|
+
{@render compactRow(ev, true, false)}
|
|
379
396
|
{/each}
|
|
380
397
|
{#if day.timedEvents.length > 4}
|
|
381
398
|
<button
|
|
@@ -421,7 +438,8 @@ const weekDays = $derived.by(() => {
|
|
|
421
438
|
.ag-card,
|
|
422
439
|
.ag-allday-chip,
|
|
423
440
|
.ag-compact,
|
|
424
|
-
.ag-compact-more
|
|
441
|
+
.ag-compact-more,
|
|
442
|
+
.ag-past-toggle {
|
|
425
443
|
font: inherit;
|
|
426
444
|
color: inherit;
|
|
427
445
|
text-align: left;
|
|
@@ -439,10 +457,15 @@ const weekDays = $derived.by(() => {
|
|
|
439
457
|
overflow-y: auto;
|
|
440
458
|
overflow-x: hidden;
|
|
441
459
|
box-sizing: border-box;
|
|
442
|
-
padding-top:
|
|
460
|
+
/* No padding-top here: the sticky day headers pin at the scrollport
|
|
461
|
+
edge, and container padding would leave a see-through band above
|
|
462
|
+
them where scrolled cards bleed out. */
|
|
443
463
|
scrollbar-width: thin;
|
|
444
464
|
scrollbar-color: var(--dt-border) transparent;
|
|
445
465
|
}
|
|
466
|
+
.ag-wday:first-child .ag-wday-head {
|
|
467
|
+
padding-top: 12px;
|
|
468
|
+
}
|
|
446
469
|
.ag--auto .ag-body {
|
|
447
470
|
overflow-y: visible;
|
|
448
471
|
}
|
|
@@ -678,6 +701,10 @@ const weekDays = $derived.by(() => {
|
|
|
678
701
|
top: 0;
|
|
679
702
|
background: var(--dt-bg, #fff);
|
|
680
703
|
z-index: 1;
|
|
704
|
+
/* Own compositor layer: without it, fast (async) scrolling repaints
|
|
705
|
+
the pinned header a frame late and a gap flashes above it. */
|
|
706
|
+
transform: translateZ(0);
|
|
707
|
+
will-change: transform;
|
|
681
708
|
}
|
|
682
709
|
.ag-wday-head-left {
|
|
683
710
|
display: flex;
|
|
@@ -759,6 +786,43 @@ const weekDays = $derived.by(() => {
|
|
|
759
786
|
.ag-wday-past-line--summary {
|
|
760
787
|
padding: 0 20px 8px;
|
|
761
788
|
}
|
|
789
|
+
/* "✓ N completed" is a disclosure — tap to reveal the finished events */
|
|
790
|
+
.ag-past-toggle {
|
|
791
|
+
display: inline-flex;
|
|
792
|
+
align-items: center;
|
|
793
|
+
gap: 5px;
|
|
794
|
+
cursor: pointer;
|
|
795
|
+
min-height: 32px;
|
|
796
|
+
transition: color 150ms;
|
|
797
|
+
-webkit-tap-highlight-color: transparent;
|
|
798
|
+
}
|
|
799
|
+
.ag-past-toggle:hover,
|
|
800
|
+
.ag-past-toggle:active {
|
|
801
|
+
color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
|
|
802
|
+
}
|
|
803
|
+
.ag-past-toggle:focus-visible {
|
|
804
|
+
outline: none;
|
|
805
|
+
box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
|
|
806
|
+
border-radius: 4px;
|
|
807
|
+
}
|
|
808
|
+
.ag-past-chevron {
|
|
809
|
+
transition: transform 120ms;
|
|
810
|
+
}
|
|
811
|
+
.ag-past-chevron--open {
|
|
812
|
+
transform: rotate(180deg);
|
|
813
|
+
}
|
|
814
|
+
@media (prefers-reduced-motion: reduce) {
|
|
815
|
+
.ag-past-chevron { transition: none; }
|
|
816
|
+
}
|
|
817
|
+
/* Revealed completed events: dim + strike, single token layer */
|
|
818
|
+
.ag-compact--done .ag-compact-title {
|
|
819
|
+
text-decoration: line-through;
|
|
820
|
+
text-decoration-color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
|
|
821
|
+
}
|
|
822
|
+
.ag-compact--done .ag-compact-time {
|
|
823
|
+
color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
|
|
824
|
+
font-weight: 400;
|
|
825
|
+
}
|
|
762
826
|
|
|
763
827
|
/* Compact day events */
|
|
764
828
|
.ag-wday-compact {
|
|
@@ -776,8 +840,10 @@ const weekDays = $derived.by(() => {
|
|
|
776
840
|
.ag-compact--selected {
|
|
777
841
|
background: color-mix(in srgb, var(--ev-color) 10%, transparent);
|
|
778
842
|
border-radius: 4px;
|
|
779
|
-
|
|
780
|
-
|
|
843
|
+
/* The highlight bleeds into the gutter via a spread shadow — zero
|
|
844
|
+
layout impact, so nothing shifts or clips even when the host
|
|
845
|
+
reduces the gutters below the bleed width. */
|
|
846
|
+
box-shadow: 0 0 0 6px color-mix(in srgb, var(--ev-color) 10%, transparent);
|
|
781
847
|
}
|
|
782
848
|
.ag-compact:hover .ag-compact-title,
|
|
783
849
|
.ag-compact:active .ag-compact-title {
|
|
@@ -792,29 +858,52 @@ const weekDays = $derived.by(() => {
|
|
|
792
858
|
box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
|
|
793
859
|
border-radius: 4px;
|
|
794
860
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
border-radius: 50%;
|
|
799
|
-
background: var(--ev-color, var(--dt-accent));
|
|
800
|
-
flex-shrink: 0;
|
|
801
|
-
align-self: center;
|
|
802
|
-
}
|
|
861
|
+
/* The time label doubles as the class-color signal (replaces the old
|
|
862
|
+
dot): the event color mixed toward the text color, so it stays
|
|
863
|
+
legible on any palette and costs zero horizontal space. */
|
|
803
864
|
.ag-compact-time {
|
|
804
865
|
font-size: 11px;
|
|
805
866
|
font-family: var(--dt-mono, monospace);
|
|
806
|
-
|
|
867
|
+
font-weight: 500;
|
|
868
|
+
color: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 60%, var(--dt-text, rgba(0, 0, 0, 0.87)));
|
|
807
869
|
min-width: 40px;
|
|
808
870
|
flex-shrink: 0;
|
|
809
871
|
white-space: nowrap;
|
|
810
872
|
line-height: 1.4;
|
|
811
873
|
}
|
|
874
|
+
/* Title + location + subtitle + tags cluster. One line while it fits; on
|
|
875
|
+
mobile the metadata wraps to a second line instead of crushing the title. */
|
|
876
|
+
.ag-compact-main {
|
|
877
|
+
display: flex;
|
|
878
|
+
align-items: baseline;
|
|
879
|
+
gap: 6px;
|
|
880
|
+
flex: 1;
|
|
881
|
+
min-width: 0;
|
|
882
|
+
}
|
|
883
|
+
.ag--mobile .ag-compact-main {
|
|
884
|
+
flex-wrap: wrap;
|
|
885
|
+
row-gap: 2px;
|
|
886
|
+
}
|
|
887
|
+
/* Mobile: size the title by its content when deciding line breaks — a long
|
|
888
|
+
title claims the first line whole (ellipsizing only against the full row)
|
|
889
|
+
and pushes location/subtitle/tags down instead of truncating at 35%. */
|
|
890
|
+
.ag--mobile .ag-compact-title {
|
|
891
|
+
flex-basis: auto;
|
|
892
|
+
}
|
|
893
|
+
/* On their own wrapped line the metadata gets the full width — the tight
|
|
894
|
+
desktop caps would truncate it beside empty space. */
|
|
895
|
+
.ag--mobile .ag-compact-loc,
|
|
896
|
+
.ag--mobile .ag-compact-sub {
|
|
897
|
+
max-width: 100%;
|
|
898
|
+
}
|
|
812
899
|
.ag-compact-title {
|
|
813
900
|
font-size: 12px;
|
|
814
901
|
font-weight: 500;
|
|
815
902
|
color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
|
|
816
903
|
flex: 1;
|
|
817
|
-
|
|
904
|
+
/* The title is the row's identity — never let subtitle/tags/duration
|
|
905
|
+
squeeze it out on narrow screens (min-width: 0 resolves to 0px). */
|
|
906
|
+
min-width: 35%;
|
|
818
907
|
white-space: nowrap;
|
|
819
908
|
overflow: hidden;
|
|
820
909
|
text-overflow: ellipsis;
|
|
@@ -832,7 +921,8 @@ const weekDays = $derived.by(() => {
|
|
|
832
921
|
.ag-compact-sub {
|
|
833
922
|
font-size: 10px;
|
|
834
923
|
color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
|
|
835
|
-
flex-shrink:
|
|
924
|
+
flex-shrink: 3;
|
|
925
|
+
min-width: 0;
|
|
836
926
|
white-space: nowrap;
|
|
837
927
|
overflow: hidden;
|
|
838
928
|
text-overflow: ellipsis;
|
|
@@ -842,7 +932,8 @@ const weekDays = $derived.by(() => {
|
|
|
842
932
|
.ag-compact-loc {
|
|
843
933
|
font-size: 10px;
|
|
844
934
|
color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
|
|
845
|
-
flex-shrink:
|
|
935
|
+
flex-shrink: 3;
|
|
936
|
+
min-width: 0;
|
|
846
937
|
white-space: nowrap;
|
|
847
938
|
overflow: hidden;
|
|
848
939
|
text-overflow: ellipsis;
|
|
@@ -870,7 +961,8 @@ const weekDays = $derived.by(() => {
|
|
|
870
961
|
padding: 1px 4px;
|
|
871
962
|
border-radius: 3px;
|
|
872
963
|
white-space: nowrap;
|
|
873
|
-
flex-shrink:
|
|
964
|
+
flex-shrink: 1;
|
|
965
|
+
min-width: 2.5em;
|
|
874
966
|
max-width: 80px;
|
|
875
967
|
overflow: hidden;
|
|
876
968
|
text-overflow: ellipsis;
|
|
@@ -892,6 +984,51 @@ const weekDays = $derived.by(() => {
|
|
|
892
984
|
border-radius: 4px;
|
|
893
985
|
}
|
|
894
986
|
|
|
987
|
+
/* ═══ Timetable columns (desktop) ═══ */
|
|
988
|
+
.ag--cols .ag-body {
|
|
989
|
+
display: grid;
|
|
990
|
+
grid-template-columns: repeat(var(--ag-cols, 7), minmax(0, 1fr));
|
|
991
|
+
}
|
|
992
|
+
/* Columns stretch to the tallest day, so the separator runs full height */
|
|
993
|
+
.ag--cols .ag-wday {
|
|
994
|
+
border-bottom: none;
|
|
995
|
+
border-inline-start: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
|
|
996
|
+
min-width: 0;
|
|
997
|
+
}
|
|
998
|
+
.ag--cols .ag-wday:first-child {
|
|
999
|
+
border-inline-start: none;
|
|
1000
|
+
}
|
|
1001
|
+
/* Uniform head padding — the :first-child top bump would misalign columns.
|
|
1002
|
+
Also overrides the past-day head variant (extra class = higher specificity). */
|
|
1003
|
+
.ag--cols .ag-wday .ag-wday-head {
|
|
1004
|
+
padding: 12px 10px 8px;
|
|
1005
|
+
}
|
|
1006
|
+
/* Badge + name + date won't fit one line in a ~160px column */
|
|
1007
|
+
.ag--cols .ag-wday-head-left {
|
|
1008
|
+
flex-wrap: wrap;
|
|
1009
|
+
row-gap: 2px;
|
|
1010
|
+
}
|
|
1011
|
+
.ag--cols .ag-wday-expanded,
|
|
1012
|
+
.ag--cols .ag-wday-compact,
|
|
1013
|
+
.ag--cols .ag-wday-empty,
|
|
1014
|
+
.ag--cols .ag-wday-past-line--summary {
|
|
1015
|
+
padding-left: 10px;
|
|
1016
|
+
padding-right: 10px;
|
|
1017
|
+
}
|
|
1018
|
+
.ag--cols .ag-allday {
|
|
1019
|
+
padding-left: 10px;
|
|
1020
|
+
padding-right: 10px;
|
|
1021
|
+
}
|
|
1022
|
+
/* Narrow cards: long titles get a third line, meta wraps instead of clipping */
|
|
1023
|
+
.ag--cols .ag-card-title {
|
|
1024
|
+
-webkit-line-clamp: 3;
|
|
1025
|
+
line-clamp: 3;
|
|
1026
|
+
}
|
|
1027
|
+
.ag--cols .ag-card-meta {
|
|
1028
|
+
flex-wrap: wrap;
|
|
1029
|
+
row-gap: 3px;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
895
1032
|
/* ═══ Mobile adaptations ═══ */
|
|
896
1033
|
.ag--mobile .ag-wday-head {
|
|
897
1034
|
padding: 12px 16px;
|
|
@@ -398,6 +398,7 @@ $effect(() => {
|
|
|
398
398
|
ontouchstart={swipe.ontouchstart}
|
|
399
399
|
ontouchmove={swipe.ontouchmove}
|
|
400
400
|
ontouchend={swipe.ontouchend}
|
|
401
|
+
ontouchcancel={swipe.ontouchcancel}
|
|
401
402
|
>
|
|
402
403
|
<div
|
|
403
404
|
class="mb-swipe"
|
|
@@ -504,17 +505,20 @@ $effect(() => {
|
|
|
504
505
|
<div class="mb-ev-stripe"></div>
|
|
505
506
|
<div class="mb-ev-body">
|
|
506
507
|
<EventContent event={p.ev}>
|
|
508
|
+
<!-- Line thresholds must track real content height (title 18 +
|
|
509
|
+
time 13 + sub 14 + loc 12 + tags 19 + padding 8) — showing
|
|
510
|
+
a line the block can't hold clips text at both ends. -->
|
|
507
511
|
<span class="mb-ev-title">{p.ev.title}</span>
|
|
508
512
|
{#if p.height > 32}
|
|
509
513
|
<span class="mb-ev-time">{fmtTime(p.ev.start, locale)} – {fmtTime(p.ev.end, locale)}</span>
|
|
510
514
|
{/if}
|
|
511
|
-
{#if p.ev.subtitle && p.height >
|
|
515
|
+
{#if p.ev.subtitle && p.height > 56}
|
|
512
516
|
<span class="mb-ev-sub">{p.ev.subtitle}</span>
|
|
513
517
|
{/if}
|
|
514
|
-
{#if p.ev.location && p.height >
|
|
518
|
+
{#if p.ev.location && p.height > 72}
|
|
515
519
|
<span class="mb-ev-loc">{p.ev.location}</span>
|
|
516
520
|
{/if}
|
|
517
|
-
{#if p.ev.tags?.length && p.height >
|
|
521
|
+
{#if p.ev.tags?.length && p.height > 88}
|
|
518
522
|
<div class="mb-ev-tags">
|
|
519
523
|
{#each p.ev.tags as tag}
|
|
520
524
|
<span class="mb-ev-tag">{tag}</span>
|
|
@@ -751,6 +755,9 @@ $effect(() => {
|
|
|
751
755
|
|
|
752
756
|
.mb-hour-label {
|
|
753
757
|
width: 40px;
|
|
758
|
+
/* border-box keeps the label inside the 40px gutter that events
|
|
759
|
+
start at — content-box pushed digits flush under the event edge */
|
|
760
|
+
box-sizing: border-box;
|
|
754
761
|
flex-shrink: 0;
|
|
755
762
|
font: 500 11px/1 var(--dt-mono, ui-monospace, monospace);
|
|
756
763
|
color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
|
|
@@ -801,6 +808,7 @@ $effect(() => {
|
|
|
801
808
|
|
|
802
809
|
.mb-now-label {
|
|
803
810
|
width: 40px;
|
|
811
|
+
box-sizing: border-box;
|
|
804
812
|
flex-shrink: 0;
|
|
805
813
|
text-align: right;
|
|
806
814
|
padding-right: 6px;
|
|
@@ -143,6 +143,7 @@ function handleDayKeydown(e, dayMs) {
|
|
|
143
143
|
ontouchstart={swipe.ontouchstart}
|
|
144
144
|
ontouchmove={swipe.ontouchmove}
|
|
145
145
|
ontouchend={swipe.ontouchend}
|
|
146
|
+
ontouchcancel={swipe.ontouchcancel}
|
|
146
147
|
>
|
|
147
148
|
<!-- Vertical day list -->
|
|
148
149
|
<div
|
|
@@ -251,6 +252,7 @@ function handleDayKeydown(e, dayMs) {
|
|
|
251
252
|
.mw-list {
|
|
252
253
|
flex: 1;
|
|
253
254
|
overflow-y: auto;
|
|
255
|
+
overflow-x: hidden;
|
|
254
256
|
overscroll-behavior: contain;
|
|
255
257
|
-webkit-overflow-scrolling: touch;
|
|
256
258
|
scrollbar-width: thin;
|
|
@@ -270,6 +272,10 @@ function handleDayKeydown(e, dayMs) {
|
|
|
270
272
|
align-items: center;
|
|
271
273
|
gap: 12px;
|
|
272
274
|
position: relative;
|
|
275
|
+
/* border-box: width 100% + padding otherwise overflows the list by
|
|
276
|
+
24px, which iOS turns into a horizontal pan that clips the date
|
|
277
|
+
column off the left edge */
|
|
278
|
+
box-sizing: border-box;
|
|
273
279
|
padding: 10px 12px;
|
|
274
280
|
background: transparent;
|
|
275
281
|
transition: background 120ms;
|
|
@@ -60,5 +60,14 @@ export function createSwipe(cb) {
|
|
|
60
60
|
dx = 0;
|
|
61
61
|
cb.onend(dir);
|
|
62
62
|
},
|
|
63
|
+
ontouchcancel() {
|
|
64
|
+
// The browser took over the gesture (scroll, edge-swipe, system UI).
|
|
65
|
+
// Without this, the view stays stuck at the last swipe offset.
|
|
66
|
+
if (!tracking)
|
|
67
|
+
return;
|
|
68
|
+
tracking = false;
|
|
69
|
+
dx = 0;
|
|
70
|
+
cb.onend(0);
|
|
71
|
+
},
|
|
63
72
|
};
|
|
64
73
|
}
|
|
@@ -31,7 +31,8 @@ const eventSnippet = $derived(ctx.eventSnippet);
|
|
|
31
31
|
const loadRangeCtx = $derived(ctx.loadRange);
|
|
32
32
|
const clock = createClock(ctx.timezone);
|
|
33
33
|
const todayMs = $derived(clock.today);
|
|
34
|
-
const
|
|
34
|
+
const dotsMode = $derived(isMobile);
|
|
35
|
+
const MAX_CHIPS = 3;
|
|
35
36
|
const range = $derived(viewState?.range);
|
|
36
37
|
const focusMonth = $derived((focusDate ?? /* @__PURE__ */ new Date()).getMonth());
|
|
37
38
|
$effect(() => {
|
|
@@ -157,6 +158,7 @@ function chipTime(e) {
|
|
|
157
158
|
<div
|
|
158
159
|
class="mg"
|
|
159
160
|
class:mg--auto={autoHeight}
|
|
161
|
+
class:mg--dots={dotsMode}
|
|
160
162
|
style={style || undefined}
|
|
161
163
|
style:height={autoHeight ? undefined : height ? `${height}px` : '100%'}
|
|
162
164
|
role="grid"
|
|
@@ -223,6 +225,7 @@ function chipTime(e) {
|
|
|
223
225
|
class:mg-chip--cancelled={ev.status === 'cancelled'}
|
|
224
226
|
style:--mg-chip-color={ev.color ?? 'var(--dt-accent)'}
|
|
225
227
|
title={ev.title}
|
|
228
|
+
aria-label="{ev.title}{chipTime(ev) ? `, ${chipTime(ev)}` : ''}"
|
|
226
229
|
onclick={(e) => {
|
|
227
230
|
e.stopPropagation();
|
|
228
231
|
oneventclick?.(ev);
|
|
@@ -240,9 +243,10 @@ function chipTime(e) {
|
|
|
240
243
|
type="button"
|
|
241
244
|
class="mg-more"
|
|
242
245
|
aria-expanded={ondayclick ? undefined : false}
|
|
246
|
+
aria-label={L.nMore(cell.overflow)}
|
|
243
247
|
onclick={(e) => overflowClick(e, cell)}
|
|
244
248
|
>
|
|
245
|
-
{L.nMore(cell.overflow)}
|
|
249
|
+
{dotsMode ? `+${cell.overflow}` : L.nMore(cell.overflow)}
|
|
246
250
|
</button>
|
|
247
251
|
{:else if expandedMs === cell.ms}
|
|
248
252
|
<button
|
|
@@ -467,4 +471,33 @@ function chipTime(e) {
|
|
|
467
471
|
min-height: 30px;
|
|
468
472
|
}
|
|
469
473
|
}
|
|
474
|
+
|
|
475
|
+
/* ── Dots mode (mobile) ─────────────────────────────
|
|
476
|
+
Cells are too narrow for text chips, so events render as colored
|
|
477
|
+
dots in a wrapping row. The cell itself stays the tap target
|
|
478
|
+
(day drill-down); dots keep their title/aria-label for a11y. */
|
|
479
|
+
.mg--dots .mg-chips {
|
|
480
|
+
flex-direction: row;
|
|
481
|
+
flex-wrap: wrap;
|
|
482
|
+
align-items: center;
|
|
483
|
+
gap: 3px;
|
|
484
|
+
}
|
|
485
|
+
.mg--dots .mg-chip {
|
|
486
|
+
padding: 3px;
|
|
487
|
+
min-height: 0;
|
|
488
|
+
}
|
|
489
|
+
.mg--dots .mg-chip-title,
|
|
490
|
+
.mg--dots .mg-chip-time {
|
|
491
|
+
display: none;
|
|
492
|
+
}
|
|
493
|
+
.mg--dots .mg-chip-dot {
|
|
494
|
+
width: 8px;
|
|
495
|
+
height: 8px;
|
|
496
|
+
}
|
|
497
|
+
.mg--dots .mg-more {
|
|
498
|
+
padding: 0 3px;
|
|
499
|
+
min-height: 0;
|
|
500
|
+
align-self: center;
|
|
501
|
+
font-size: 10px;
|
|
502
|
+
}
|
|
470
503
|
</style>
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
<script lang="ts">import
|
|
2
|
-
import PlannerWeek from "./PlannerWeek.svelte";
|
|
1
|
+
<script lang="ts">import PlannerWeek from "./PlannerWeek.svelte";
|
|
3
2
|
let { mode = "week", ...rest } = $props();
|
|
4
3
|
</script>
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{:else}
|
|
9
|
-
<PlannerWeek {...rest} />
|
|
10
|
-
{/if}
|
|
5
|
+
<!-- Both planner modes are the same vertical time grid; day is one column. -->
|
|
6
|
+
<PlannerWeek {mode} {...rest} />
|