@nomideusz/svelte-calendar 0.5.2 → 0.6.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.
Files changed (44) hide show
  1. package/README.md +408 -178
  2. package/dist/adapters/memory.d.ts +7 -9
  3. package/dist/adapters/memory.js +8 -23
  4. package/dist/adapters/recurring.d.ts +3 -8
  5. package/dist/adapters/recurring.js +27 -30
  6. package/dist/calendar/Calendar.svelte +457 -46
  7. package/dist/calendar/Calendar.svelte.d.ts +55 -4
  8. package/dist/core/index.d.ts +2 -2
  9. package/dist/core/index.js +1 -1
  10. package/dist/core/locale.js +2 -2
  11. package/dist/core/palette.d.ts +5 -0
  12. package/dist/core/palette.js +8 -0
  13. package/dist/core/types.d.ts +14 -0
  14. package/dist/engine/event-store.svelte.d.ts +0 -17
  15. package/dist/engine/event-store.svelte.js +30 -17
  16. package/dist/engine/index.d.ts +1 -1
  17. package/dist/engine/view-state.svelte.d.ts +10 -5
  18. package/dist/engine/view-state.svelte.js +32 -16
  19. package/dist/index.d.ts +7 -7
  20. package/dist/index.js +3 -4
  21. package/dist/theme/auto.d.ts +53 -0
  22. package/dist/theme/auto.js +534 -0
  23. package/dist/theme/index.d.ts +3 -1
  24. package/dist/theme/index.js +3 -1
  25. package/dist/theme/presets.d.ts +20 -6
  26. package/dist/theme/presets.js +19 -6
  27. package/dist/views/agenda/AgendaDay.svelte +116 -37
  28. package/dist/views/agenda/AgendaWeek.svelte +219 -70
  29. package/dist/views/agenda/index.d.ts +0 -2
  30. package/dist/views/agenda/index.js +0 -2
  31. package/dist/views/index.d.ts +3 -2
  32. package/dist/views/index.js +3 -2
  33. package/dist/views/mobile/Mobile.svelte +17 -0
  34. package/dist/views/mobile/Mobile.svelte.d.ts +7 -0
  35. package/dist/views/mobile/MobileDay.svelte +665 -0
  36. package/dist/views/mobile/MobileDay.svelte.d.ts +20 -0
  37. package/dist/views/mobile/MobileWeek.svelte +456 -0
  38. package/dist/views/mobile/MobileWeek.svelte.d.ts +20 -0
  39. package/dist/views/mobile/index.d.ts +1 -0
  40. package/dist/views/mobile/index.js +1 -0
  41. package/dist/views/planner/PlannerDay.svelte +144 -10
  42. package/dist/views/planner/PlannerWeek.svelte +145 -40
  43. package/dist/widget/CalendarWidget.svelte +2 -17
  44. package/package.json +2 -1
@@ -44,6 +44,42 @@
44
44
 
45
45
  const clock = createClock();
46
46
  const viewState = getContext<ViewState>('calendar:viewState') as ViewState | undefined;
47
+ const showNavCtx = getContext<{ current: boolean }>('calendar:showNavigation') as { current: boolean } | undefined;
48
+ const equalDaysCtx = getContext<{ current: boolean }>('calendar:equalDays') as { current: boolean } | undefined;
49
+ const showDatesCtx = getContext<{ current: boolean }>('calendar:showDates') as { current: boolean } | undefined;
50
+ const mobileCtx = getContext<{ current: boolean }>('calendar:mobile') as { current: boolean } | undefined;
51
+ const showNav = $derived(showNavCtx?.current ?? true);
52
+ const equalDays = $derived(equalDaysCtx?.current ?? false);
53
+ const showDates = $derived(showDatesCtx?.current ?? true);
54
+ const isMobile = $derived(mobileCtx?.current ?? false);
55
+
56
+ // ── Swipe navigation (mobile) ──────────────────────
57
+ let swipeStartX = 0;
58
+ let swipeStartY = 0;
59
+ const SWIPE_THRESHOLD = 50;
60
+
61
+ function onPointerDown(e: PointerEvent) {
62
+ if (!isMobile) return;
63
+ swipeStartX = e.clientX;
64
+ swipeStartY = e.clientY;
65
+ }
66
+
67
+ function onPointerUp(e: PointerEvent) {
68
+ if (!isMobile) return;
69
+ const dx = e.clientX - swipeStartX;
70
+ const dy = e.clientY - swipeStartY;
71
+ if (Math.abs(dx) > SWIPE_THRESHOLD && Math.abs(dx) > Math.abs(dy) * 1.4) {
72
+ if (dx > 0) viewState?.prev();
73
+ else viewState?.next();
74
+ }
75
+ }
76
+
77
+ // ── New feature contexts ──
78
+ const callbacksCtx = getContext<{ oneventhover?: (event: TimelineEvent) => void }>('calendar:callbacks') as { oneventhover?: (event: TimelineEvent) => void } | undefined;
79
+ const disabledDatesCtx = getContext<{ current: Date[] | undefined }>('calendar:disabledDates') as { current: Date[] | undefined } | undefined;
80
+ const oneventhover = $derived(callbacksCtx?.oneventhover);
81
+ const disabledDates = $derived(disabledDatesCtx?.current);
82
+ const disabledSet = $derived(new Set(disabledDates?.map(d => sod(d.getTime())) ?? []));
47
83
 
48
84
  // ── Format helpers ──────────────────────────────────
49
85
  function fmtTime(d: Date): string {
@@ -114,14 +150,18 @@
114
150
  const dayMs = $derived(focusDate ? sod(focusDate.getTime()) : clock.today);
115
151
  const dayEnd = $derived(dayMs + DAY_MS);
116
152
  const isToday = $derived(dayMs === clock.today);
117
- const isPastDay = $derived(dayMs < clock.today);
153
+ const isPastDay = $derived(equalDays ? false : dayMs < clock.today);
118
154
 
119
155
  const dateLabel = $derived(
120
- new Date(dayMs).toLocaleDateString(locale ?? 'en-US', {
121
- weekday: 'long',
122
- month: 'long',
123
- day: 'numeric',
124
- })
156
+ showDates
157
+ ? new Date(dayMs).toLocaleDateString(locale ?? 'en-US', {
158
+ weekday: 'long',
159
+ month: 'long',
160
+ day: 'numeric',
161
+ })
162
+ : new Date(dayMs).toLocaleDateString(locale ?? 'en-US', {
163
+ weekday: 'long',
164
+ })
125
165
  );
126
166
 
127
167
  /** All events for this day, sorted chronologically */
@@ -165,10 +205,15 @@
165
205
  });
166
206
  </script>
167
207
 
208
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
168
209
  <div
169
210
  class="ag ag--day"
211
+ class:ag--disabled={disabledSet.has(dayMs)}
212
+ class:ag--mobile={isMobile}
170
213
  style={style || undefined}
171
214
  style:height={height ? `${height}px` : undefined}
215
+ onpointerdown={onPointerDown}
216
+ onpointerup={onPointerUp}
172
217
  >
173
218
  <div class="ag-body" role="list" aria-label={L.todaysLineup}>
174
219
  {#if allDayBanner.length > 0}
@@ -185,6 +230,7 @@
185
230
  tabindex="0"
186
231
  aria-label="{ev.title}, {L.allDay}"
187
232
  onclick={() => handleClick(ev)}
233
+ onpointerenter={() => oneventhover?.(ev)}
188
234
  onkeydown={(e) => handleKeydown(e, ev)}
189
235
  >
190
236
  <span class="ag-allday-dot"></span>
@@ -230,8 +276,7 @@
230
276
  role="button"
231
277
  tabindex="0"
232
278
  aria-label="{ev.title}, {L.happeningNow}, {L.percentComplete(Math.round(progress(ev) * 100))}"
233
- onclick={() => handleClick(ev)}
234
- onkeydown={(e) => handleKeydown(e, ev)}
279
+ onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
235
280
  >
236
281
  <div class="ag-q-now-dot"></div>
237
282
  <div class="ag-q-now-title">{ev.title}</div>
@@ -266,9 +311,9 @@
266
311
  tabindex="0"
267
312
  aria-label="{ev.title}, {fmtTime(ev.start)}, {duration(ev)}"
268
313
  onclick={() => handleClick(ev)}
314
+ onpointerenter={() => oneventhover?.(ev)}
269
315
  onkeydown={(e) => handleKeydown(e, ev)}
270
316
  >
271
- <div class="ag-card-stripe"></div>
272
317
  <div class="ag-card-body">
273
318
  <div class="ag-card-top">
274
319
  <span class="ag-card-title">{ev.title}</span>
@@ -309,8 +354,7 @@
309
354
  role="button"
310
355
  tabindex="0"
311
356
  aria-label="{ev.title}, {fmtTime(ev.start)} to {fmtTime(ev.end)}"
312
- onclick={() => handleClick(ev)}
313
- onkeydown={(e) => handleKeydown(e, ev)}
357
+ onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
314
358
  >
315
359
  <span class="ag-log-check">✓</span>
316
360
  <span class="ag-log-time">{fmtTime(ev.start)}</span>
@@ -337,10 +381,8 @@
337
381
  role="button"
338
382
  tabindex="0"
339
383
  aria-label="{ev.title}, {fmtTime(ev.start)} to {fmtTime(ev.end)}, {duration(ev)}"
340
- onclick={() => handleClick(ev)}
341
- onkeydown={(e) => handleKeydown(e, ev)}
384
+ onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
342
385
  >
343
- <div class="ag-card-stripe"></div>
344
386
  <div class="ag-card-body">
345
387
  <div class="ag-card-top">
346
388
  <span class="ag-card-order">{i + 1}</span>
@@ -368,9 +410,12 @@
368
410
  {/if}
369
411
  </div>
370
412
 
371
- <div class="ag-date-label">{dateLabel}</div>
413
+ {#if !isMobile}
414
+ <div class="ag-date-label">{dateLabel}</div>
415
+ {/if}
372
416
 
373
- <!-- ── Floating nav pills ── -->
417
+ <!-- ── Floating nav pills (desktop only — mobile uses Calendar header) ── -->
418
+ {#if showNav && !isMobile}
374
419
  <nav class="ag-nav" aria-label={L.dayNavigation}>
375
420
  <button
376
421
  class="ag-nav-pill ag-nav-today"
@@ -396,6 +441,7 @@
396
441
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><path d="M6 3l5 5-5 5"/></svg>
397
442
  </button>
398
443
  </nav>
444
+ {/if}
399
445
  </div>
400
446
 
401
447
  <style>
@@ -483,6 +529,11 @@
483
529
  font-family: var(--dt-sans, system-ui, sans-serif);
484
530
  }
485
531
 
532
+ .ag--disabled {
533
+ opacity: 0.4;
534
+ pointer-events: none;
535
+ }
536
+
486
537
  /* ═══ Body ═══ */
487
538
  .ag-body {
488
539
  flex: 1;
@@ -563,13 +614,14 @@
563
614
  display: flex;
564
615
  align-items: stretch;
565
616
  border-radius: 10px;
566
- background: var(--dt-surface, #191919);
567
- border: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
617
+ background: color-mix(in srgb, var(--ev-color) 15%, var(--dt-surface, #191919));
618
+ border: 1px solid color-mix(in srgb, var(--ev-color) 10%, var(--dt-border, rgba(255, 255, 255, 0.06)));
568
619
  overflow: hidden;
569
620
  cursor: pointer;
570
- transition: border-color 150ms;
621
+ transition: background 150ms, border-color 150ms;
571
622
  }
572
623
  .ag-card:hover {
624
+ background: color-mix(in srgb, var(--ev-color) 25%, var(--dt-surface, #191919));
573
625
  border-color: color-mix(in srgb, var(--ev-color) 40%, transparent);
574
626
  }
575
627
  .ag-card:focus-visible {
@@ -578,12 +630,7 @@
578
630
  }
579
631
  .ag-card--selected {
580
632
  border-color: var(--ev-color);
581
- background: color-mix(in srgb, var(--ev-color) 6%, var(--dt-surface, #191919));
582
- }
583
- .ag-card-stripe {
584
- width: 3px;
585
- background: var(--ev-color, var(--dt-accent));
586
- flex-shrink: 0;
633
+ background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #191919));
587
634
  }
588
635
  .ag-card-body {
589
636
  padding: 10px 12px;
@@ -642,9 +689,7 @@
642
689
  margin-bottom: 6px;
643
690
  transition: border-color 150ms, transform 100ms;
644
691
  }
645
- .ag-card--q .ag-card-stripe {
646
- width: 3.5px;
647
- }
692
+
648
693
  .ag-card--q .ag-card-body {
649
694
  gap: 3px;
650
695
  }
@@ -660,8 +705,8 @@
660
705
  white-space: nowrap;
661
706
  }
662
707
  .ag-card--hero {
663
- background: color-mix(in srgb, var(--ev-color) 8%, var(--dt-surface, #191919));
664
- border-color: color-mix(in srgb, var(--ev-color) 25%, transparent);
708
+ background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #191919));
709
+ border-color: color-mix(in srgb, var(--ev-color) 30%, transparent);
665
710
  }
666
711
  .ag-card--hero .ag-card-title {
667
712
  font-size: 16px;
@@ -678,9 +723,7 @@
678
723
  }
679
724
 
680
725
  /* ── Plan card variant ── */
681
- .ag-card--plan .ag-card-stripe {
682
- width: 3.5px;
683
- }
726
+
684
727
  .ag-card--plan .ag-card-body {
685
728
  padding: 12px 14px;
686
729
  gap: 3px;
@@ -699,8 +742,8 @@
699
742
  font-size: 14px;
700
743
  }
701
744
  .ag-card--first {
702
- background: color-mix(in srgb, var(--ev-color) 5%, var(--dt-surface, #191919));
703
- border-color: color-mix(in srgb, var(--ev-color) 15%, transparent);
745
+ background: color-mix(in srgb, var(--ev-color) 20%, var(--dt-surface, #191919));
746
+ border-color: color-mix(in srgb, var(--ev-color) 25%, transparent);
704
747
  }
705
748
  .ag-card--first .ag-card-title {
706
749
  font-size: 16px;
@@ -723,6 +766,41 @@
723
766
  padding: 8px 0 10px;
724
767
  min-height: 0;
725
768
  }
769
+ /* Mobile: stack queue columns vertically */
770
+ .ag--mobile .ag-q {
771
+ grid-template-columns: 1fr;
772
+ }
773
+ .ag--mobile .ag-q-status {
774
+ border-right: none;
775
+ border-bottom: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
776
+ padding-bottom: 10px;
777
+ margin-bottom: 8px;
778
+ }
779
+ /* Mobile: larger touch targets */
780
+ .ag--mobile .ag-card-body {
781
+ padding: 14px 16px;
782
+ }
783
+ .ag--mobile .ag-card-title {
784
+ font-size: 15px;
785
+ }
786
+ .ag--mobile .ag-card--hero .ag-card-title {
787
+ font-size: 18px;
788
+ }
789
+ .ag--mobile .ag-card--hero .ag-card-body {
790
+ padding: 16px 18px;
791
+ }
792
+ .ag--mobile .ag-log-row {
793
+ padding: 12px 0;
794
+ }
795
+ .ag--mobile .ag-card--plan .ag-card-body {
796
+ padding: 14px 16px;
797
+ }
798
+ .ag--mobile .ag-card--plan .ag-card-title {
799
+ font-size: 15px;
800
+ }
801
+ .ag--mobile .ag-nav-pill {
802
+ padding: 10px 16px;
803
+ }
726
804
  .ag-q-label {
727
805
  font-size: 8px;
728
806
  font-weight: 600;
@@ -770,13 +848,14 @@
770
848
  .ag-q-now {
771
849
  padding: 8px 10px;
772
850
  border-radius: 8px;
773
- background: var(--dt-surface, #191919);
851
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, var(--dt-surface, #191919));
774
852
  border: 1px solid color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
775
853
  cursor: pointer;
776
- transition: border-color 150ms;
854
+ transition: background 150ms, border-color 150ms;
777
855
  margin-right: 10px;
778
856
  }
779
857
  .ag-q-now:hover {
858
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 25%, var(--dt-surface, #191919));
780
859
  border-color: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 35%, transparent);
781
860
  }
782
861
  .ag-q-now:focus-visible {