@nomideusz/svelte-calendar 1.2.0 → 1.4.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 +143 -93
- package/dist/adapters/mapped.d.ts +7 -7
- package/dist/adapters/mapped.js +5 -2
- package/dist/adapters/memory.d.ts +7 -6
- package/dist/adapters/memory.js +3 -4
- package/dist/adapters/recurring.d.ts +6 -6
- package/dist/adapters/recurring.js +12 -14
- package/dist/calendar/Calendar.svelte +226 -304
- package/dist/calendar/Calendar.svelte.d.ts +1 -1
- package/dist/primitives/DayHeader.svelte +7 -7
- package/dist/primitives/EmptySlot.svelte +6 -6
- package/dist/primitives/EventBlock.svelte +19 -19
- package/dist/primitives/FloatingPanel.svelte +4 -4
- package/dist/primitives/NowIndicator.svelte +5 -5
- package/dist/primitives/TimeGutter.svelte +4 -4
- package/dist/theme/auto.d.ts +5 -0
- package/dist/theme/auto.js +16 -3
- package/dist/theme/presets.d.ts +2 -2
- package/dist/theme/presets.js +2 -2
- package/dist/views/agenda/AgendaDay.svelte +207 -323
- package/dist/views/agenda/AgendaWeek.svelte +145 -222
- package/dist/views/mobile/MobileDay.svelte +79 -60
- package/dist/views/mobile/MobileWeek.svelte +48 -38
- package/dist/views/month/MonthGrid.svelte +92 -36
- package/dist/views/planner/PlannerScroll.svelte +78 -72
- package/dist/views/planner/PlannerWeek.svelte +145 -90
- package/dist/views/shared/context.svelte.d.ts +2 -0
- package/dist/views/shared/context.svelte.js +1 -0
- package/dist/views/shared/format.d.ts +3 -0
- package/dist/views/shared/format.js +10 -1
- package/package.json +1 -1
- package/widget/widget.js +816 -725
|
@@ -28,6 +28,39 @@ const todayMs = $derived(clock.today);
|
|
|
28
28
|
// cell tap (day drill-down) carry the interaction.
|
|
29
29
|
const dotsMode = $derived(isMobile);
|
|
30
30
|
const MAX_CHIPS = 3;
|
|
31
|
+
// How many chips a cell has room for, measured, never more than MAX_CHIPS.
|
|
32
|
+
// A cell that overflows must also show its "+N more", so it gets one row
|
|
33
|
+
// fewer when that row would not fit beside its chips: a hidden event is
|
|
34
|
+
// always counted, never clipped away. Auto height and dots grow instead.
|
|
35
|
+
let fitAll = $state(MAX_CHIPS);
|
|
36
|
+
let fitMore = $state(MAX_CHIPS);
|
|
37
|
+
function measureFit() {
|
|
38
|
+
const cell = bodyEl?.querySelector(".mg-cell");
|
|
39
|
+
const chip = bodyEl?.querySelector(".mg-chip");
|
|
40
|
+
if (!cell || !chip) return;
|
|
41
|
+
const num = cell.querySelector(".mg-daynum");
|
|
42
|
+
const chips = cell.querySelector(".mg-chips");
|
|
43
|
+
const cs = getComputedStyle(cell);
|
|
44
|
+
const gap = chips ? parseFloat(getComputedStyle(chips).rowGap) || 0 : 0;
|
|
45
|
+
const room = cell.clientHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom) - (num?.offsetHeight ?? 0) - (parseFloat(cs.rowGap) || 0);
|
|
46
|
+
const chipH = chip.offsetHeight;
|
|
47
|
+
const moreH = bodyEl?.querySelector(".mg-more")?.offsetHeight ?? chipH;
|
|
48
|
+
if (!chipH) return;
|
|
49
|
+
fitAll = Math.min(MAX_CHIPS, Math.floor((room + gap) / (chipH + gap)));
|
|
50
|
+
fitMore = Math.max(0, Math.min(MAX_CHIPS, Math.floor((room - moreH) / (chipH + gap))));
|
|
51
|
+
}
|
|
52
|
+
$effect(() => {
|
|
53
|
+
if (!bodyEl || autoHeight || dotsMode) {
|
|
54
|
+
fitAll = fitMore = MAX_CHIPS;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
void events;
|
|
58
|
+
measureFit();
|
|
59
|
+
const ro = new ResizeObserver(measureFit);
|
|
60
|
+
ro.observe(bodyEl);
|
|
61
|
+
document.fonts?.ready.then(measureFit);
|
|
62
|
+
return () => ro.disconnect();
|
|
63
|
+
});
|
|
31
64
|
const range = $derived(viewState?.range);
|
|
32
65
|
const focusMonth = $derived((focusDate ?? new Date()).getMonth());
|
|
33
66
|
$effect(() => {
|
|
@@ -106,11 +139,18 @@ function cellKeydown(e, cell) {
|
|
|
106
139
|
case " ":
|
|
107
140
|
if (!cell.isDisabled) {
|
|
108
141
|
e.preventDefault();
|
|
109
|
-
|
|
142
|
+
activateCell(cell);
|
|
110
143
|
}
|
|
111
144
|
break;
|
|
112
145
|
}
|
|
113
146
|
}
|
|
147
|
+
/** The cell's own action: drill into the day; on phones (dots, no
|
|
148
|
+
* ondayclick) the whole cell opens its overflow instead of a tiny "+N". */
|
|
149
|
+
function activateCell(cell) {
|
|
150
|
+
if (cell.isDisabled) return;
|
|
151
|
+
if (ondayclick) ondayclick(cell.date);
|
|
152
|
+
else if (dotsMode && cell.overflow > 0) expandedMs = expandedMs === cell.ms ? null : cell.ms;
|
|
153
|
+
}
|
|
114
154
|
function overflowClick(e, cell) {
|
|
115
155
|
e.stopPropagation();
|
|
116
156
|
if (ondayclick) ondayclick(cell.date);
|
|
@@ -143,6 +183,7 @@ const weeks = $derived.by(() => {
|
|
|
143
183
|
const date = new Date(cellMs);
|
|
144
184
|
const jsDay = date.getDay();
|
|
145
185
|
const dayEvents = eventsForDay(cellMs);
|
|
186
|
+
const shown = dayEvents.length <= fitAll ? dayEvents.length : fitMore;
|
|
146
187
|
row.push({
|
|
147
188
|
ms: cellMs,
|
|
148
189
|
date,
|
|
@@ -151,9 +192,9 @@ const weeks = $derived.by(() => {
|
|
|
151
192
|
isToday: cellMs === todayMs,
|
|
152
193
|
isWeekend: jsDay === 0 || jsDay === 6,
|
|
153
194
|
isDisabled: disabledSet.has(cellMs),
|
|
154
|
-
chips: dayEvents.slice(0,
|
|
195
|
+
chips: dayEvents.slice(0, shown),
|
|
155
196
|
all: dayEvents,
|
|
156
|
-
overflow:
|
|
197
|
+
overflow: dayEvents.length - shown
|
|
157
198
|
});
|
|
158
199
|
}
|
|
159
200
|
rows.push(row);
|
|
@@ -202,18 +243,25 @@ function chipTime(e) {
|
|
|
202
243
|
aria-current={cell.isToday ? 'date' : undefined}
|
|
203
244
|
data-ms={cell.ms}
|
|
204
245
|
tabindex={isFocusable(cell) ? (cell.ms === rovingMs ? 0 : -1) : undefined}
|
|
205
|
-
onclick={() =>
|
|
206
|
-
if (!cell.isDisabled) ondayclick?.(cell.date);
|
|
207
|
-
}}
|
|
246
|
+
onclick={() => activateCell(cell)}
|
|
208
247
|
onfocusin={() => (focusMs = cell.ms)}
|
|
209
248
|
onkeydown={(e) => cellKeydown(e, cell)}
|
|
210
249
|
>
|
|
211
250
|
<span class="mg-daynum" class:mg-daynum--today={cell.isToday}>
|
|
212
251
|
{cell.dayNum}
|
|
213
252
|
</span>
|
|
214
|
-
|
|
253
|
+
<!-- Phones: dots are a picture of the day; the cell (its label counts
|
|
254
|
+
the events) is the one tap and tab target. -->
|
|
255
|
+
<div class="mg-chips" aria-hidden={dotsMode || undefined}>
|
|
215
256
|
{#each expandedMs === cell.ms ? cell.all : cell.chips as ev (ev.id + cell.ms)}
|
|
216
|
-
{#if
|
|
257
|
+
{#if dotsMode}
|
|
258
|
+
<span
|
|
259
|
+
class="mg-chip"
|
|
260
|
+
class:mg-chip--selected={ev.id === selectedEventId}
|
|
261
|
+
class:mg-chip--cancelled={ev.status === 'cancelled'}
|
|
262
|
+
style:--mg-chip-color={ev.color ?? (isAllDay(ev) ? 'var(--dt-accent)' : 'var(--dt-event, var(--dt-accent))')}
|
|
263
|
+
><span class="mg-chip-dot"></span></span>
|
|
264
|
+
{:else if eventSnippet}
|
|
217
265
|
<button
|
|
218
266
|
type="button"
|
|
219
267
|
class="mg-chip mg-chip--custom"
|
|
@@ -232,7 +280,7 @@ function chipTime(e) {
|
|
|
232
280
|
class="mg-chip"
|
|
233
281
|
class:mg-chip--selected={ev.id === selectedEventId}
|
|
234
282
|
class:mg-chip--cancelled={ev.status === 'cancelled'}
|
|
235
|
-
style:--mg-chip-color={ev.color ?? 'var(--dt-accent)'}
|
|
283
|
+
style:--mg-chip-color={ev.color ?? (isAllDay(ev) ? 'var(--dt-accent)' : 'var(--dt-event, var(--dt-accent))')}
|
|
236
284
|
title={ev.title}
|
|
237
285
|
aria-label="{ev.title}{chipTime(ev) ? `, ${chipTime(ev)}` : ''}"
|
|
238
286
|
onclick={(e) => {
|
|
@@ -247,7 +295,11 @@ function chipTime(e) {
|
|
|
247
295
|
</button>
|
|
248
296
|
{/if}
|
|
249
297
|
{/each}
|
|
250
|
-
{#if
|
|
298
|
+
{#if dotsMode}
|
|
299
|
+
{#if cell.overflow > 0 && expandedMs !== cell.ms}
|
|
300
|
+
<span class="mg-more">+{cell.overflow}</span>
|
|
301
|
+
{/if}
|
|
302
|
+
{:else if cell.overflow > 0 && expandedMs !== cell.ms}
|
|
251
303
|
<button
|
|
252
304
|
type="button"
|
|
253
305
|
class="mg-more"
|
|
@@ -255,7 +307,7 @@ function chipTime(e) {
|
|
|
255
307
|
aria-label={L.nMore(cell.overflow)}
|
|
256
308
|
onclick={(e) => overflowClick(e, cell)}
|
|
257
309
|
>
|
|
258
|
-
{
|
|
310
|
+
{L.nMore(cell.overflow)}
|
|
259
311
|
</button>
|
|
260
312
|
{:else if expandedMs === cell.ms}
|
|
261
313
|
<button
|
|
@@ -300,13 +352,11 @@ function chipTime(e) {
|
|
|
300
352
|
}
|
|
301
353
|
.mg-head-cell {
|
|
302
354
|
padding: 6px 8px;
|
|
303
|
-
font
|
|
355
|
+
font: 500 12px/1.2 var(--dt-sans, system-ui, sans-serif);
|
|
304
356
|
font-variant-numeric: tabular-nums;
|
|
305
|
-
font-size: 11px;
|
|
306
|
-
font-weight: 600;
|
|
307
357
|
text-transform: uppercase;
|
|
308
|
-
letter-spacing: 0.
|
|
309
|
-
color: var(--dt-text-
|
|
358
|
+
letter-spacing: 0.08em;
|
|
359
|
+
color: var(--dt-text-2, rgba(15, 15, 15, 0.66));
|
|
310
360
|
}
|
|
311
361
|
|
|
312
362
|
.mg-body {
|
|
@@ -340,14 +390,15 @@ function chipTime(e) {
|
|
|
340
390
|
border-right: none;
|
|
341
391
|
}
|
|
342
392
|
.mg-cell--weekend {
|
|
343
|
-
background: var(--dt-weekend-bg, rgba(
|
|
393
|
+
background: var(--dt-weekend-bg, rgba(15, 15, 15, 0.02));
|
|
344
394
|
}
|
|
345
|
-
/* Dim only the day number for adjacent-month cells — their events stay
|
|
395
|
+
/* Dim only the day number for adjacent-month cells — their events stay
|
|
396
|
+
legible. text-2, not text-3: a 22px numeral still needs 4.5:1. */
|
|
346
397
|
.mg-cell--out {
|
|
347
398
|
background: var(--dt-surface, transparent);
|
|
348
399
|
}
|
|
349
400
|
.mg-cell--out .mg-daynum {
|
|
350
|
-
color: var(--dt-text-
|
|
401
|
+
color: var(--dt-text-2, rgba(15, 15, 15, 0.66));
|
|
351
402
|
}
|
|
352
403
|
.mg-cell--today {
|
|
353
404
|
background: var(--dt-today-bg);
|
|
@@ -363,10 +414,10 @@ function chipTime(e) {
|
|
|
363
414
|
cursor: pointer;
|
|
364
415
|
}
|
|
365
416
|
.mg-cell--clickable:hover {
|
|
366
|
-
background: var(--dt-hover, rgba(
|
|
417
|
+
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
367
418
|
}
|
|
368
419
|
.mg-cell:focus-visible {
|
|
369
|
-
outline: 2px solid var(--dt-accent, #
|
|
420
|
+
outline: 2px solid var(--dt-accent, #1d4fd7);
|
|
370
421
|
outline-offset: -2px;
|
|
371
422
|
}
|
|
372
423
|
|
|
@@ -406,19 +457,17 @@ function chipTime(e) {
|
|
|
406
457
|
background: none;
|
|
407
458
|
padding: 2px 4px;
|
|
408
459
|
border-radius: 0;
|
|
409
|
-
font
|
|
410
|
-
font-size: 12px;
|
|
411
|
-
line-height: 1.3;
|
|
460
|
+
font: 400 12.5px/1.3 var(--dt-sans, system-ui, sans-serif);
|
|
412
461
|
color: var(--dt-text);
|
|
413
462
|
cursor: pointer;
|
|
414
463
|
min-width: 0;
|
|
415
464
|
text-align: left;
|
|
416
465
|
}
|
|
417
466
|
.mg-chip:hover {
|
|
418
|
-
background: var(--dt-hover, rgba(
|
|
467
|
+
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
419
468
|
}
|
|
420
469
|
.mg-chip:focus-visible {
|
|
421
|
-
box-shadow: 0 0 0 2px var(--dt-accent, #
|
|
470
|
+
box-shadow: 0 0 0 2px var(--dt-accent, #1d4fd7);
|
|
422
471
|
outline: none;
|
|
423
472
|
}
|
|
424
473
|
.mg-chip--selected {
|
|
@@ -442,9 +491,7 @@ function chipTime(e) {
|
|
|
442
491
|
}
|
|
443
492
|
.mg-chip-time {
|
|
444
493
|
flex: none;
|
|
445
|
-
font-family: var(--dt-sans, system-ui, sans-serif);
|
|
446
494
|
font-variant-numeric: tabular-nums;
|
|
447
|
-
font-size: 11px;
|
|
448
495
|
color: var(--dt-text-2);
|
|
449
496
|
}
|
|
450
497
|
.mg-chip-title {
|
|
@@ -458,18 +505,17 @@ function chipTime(e) {
|
|
|
458
505
|
background: none;
|
|
459
506
|
padding: 1px 4px;
|
|
460
507
|
border-radius: 0;
|
|
461
|
-
font
|
|
508
|
+
font: 400 12.5px/1.3 var(--dt-sans, system-ui, sans-serif);
|
|
462
509
|
font-variant-numeric: tabular-nums;
|
|
463
|
-
font-size: 11px;
|
|
464
510
|
color: var(--dt-text-2);
|
|
465
511
|
cursor: pointer;
|
|
466
512
|
}
|
|
467
513
|
.mg-more:hover {
|
|
468
|
-
background: var(--dt-hover, rgba(
|
|
514
|
+
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
469
515
|
color: var(--dt-text);
|
|
470
516
|
}
|
|
471
517
|
.mg-more:focus-visible {
|
|
472
|
-
box-shadow: 0 0 0 2px var(--dt-accent, #
|
|
518
|
+
box-shadow: 0 0 0 2px var(--dt-accent, #1d4fd7);
|
|
473
519
|
outline: none;
|
|
474
520
|
}
|
|
475
521
|
|
|
@@ -490,13 +536,20 @@ function chipTime(e) {
|
|
|
490
536
|
/* ── Dots mode (mobile) ─────────────────────────────
|
|
491
537
|
Cells are too narrow for text chips, so events render as colored
|
|
492
538
|
dots in a wrapping row. The cell itself stays the tap target
|
|
493
|
-
(day drill-down); dots
|
|
539
|
+
(day drill-down) and its label counts the events; dots are aria-hidden. */
|
|
494
540
|
.mg--dots .mg-chips {
|
|
495
541
|
flex-direction: row;
|
|
496
542
|
flex-wrap: wrap;
|
|
497
543
|
align-items: center;
|
|
498
544
|
gap: 3px;
|
|
499
545
|
}
|
|
546
|
+
/* The cell is the target: at least a 44px tap, dots never take the hit. */
|
|
547
|
+
.mg--dots .mg-cell {
|
|
548
|
+
min-height: 44px;
|
|
549
|
+
}
|
|
550
|
+
.mg--dots .mg-chips {
|
|
551
|
+
pointer-events: none;
|
|
552
|
+
}
|
|
500
553
|
.mg--dots .mg-chip {
|
|
501
554
|
padding: 3px;
|
|
502
555
|
min-height: 0;
|
|
@@ -505,14 +558,17 @@ function chipTime(e) {
|
|
|
505
558
|
.mg--dots .mg-chip-time {
|
|
506
559
|
display: none;
|
|
507
560
|
}
|
|
561
|
+
/* Marks are circles, like every dot in the calendar */
|
|
508
562
|
.mg--dots .mg-chip-dot {
|
|
509
|
-
|
|
510
|
-
|
|
563
|
+
align-self: center;
|
|
564
|
+
width: 7px;
|
|
565
|
+
height: 7px;
|
|
566
|
+
border-radius: 50%;
|
|
511
567
|
}
|
|
512
568
|
.mg--dots .mg-more {
|
|
513
569
|
padding: 0 3px;
|
|
514
570
|
min-height: 0;
|
|
515
571
|
align-self: center;
|
|
516
|
-
font-size:
|
|
572
|
+
font-size: 11px;
|
|
517
573
|
}
|
|
518
574
|
</style>
|