@nomideusz/svelte-calendar 0.6.4 → 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.
@@ -300,6 +300,36 @@
300
300
  let evDragId = $state<string | null>(null);
301
301
  let evDragEvent: TimelineEvent | null = null;
302
302
 
303
+ const dragPreviewEvent = $derived.by(() => {
304
+ const payload = drag?.active && drag.mode === 'move' ? drag.payload : null;
305
+ if (!payload?.eventId) return null;
306
+ const ev = events.find((event) => event.id === payload.eventId);
307
+ if (!ev) return null;
308
+ return { ...ev, start: payload.start, end: payload.end };
309
+ });
310
+
311
+ function isDraggedEvent(eventId: string): boolean {
312
+ return dragPreviewEvent?.id === eventId;
313
+ }
314
+
315
+ function timedEventsForDay(day: DayCell): TimelineEvent[] {
316
+ if (!dragPreviewEvent) return day.events;
317
+ return day.events.filter((ev) => ev.id !== dragPreviewEvent.id);
318
+ }
319
+
320
+ function dragPreviewTimedForDay(dayMs: number): TimelineEvent | null {
321
+ const ev = dragPreviewEvent;
322
+ if (!ev || isAllDay(ev) || isMultiDay(ev)) return null;
323
+ const dayEnd = dayMs + DAY_MS;
324
+ return ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs ? ev : null;
325
+ }
326
+
327
+ function dragPreviewSegmentForDay(dayMs: number): DaySegment | null {
328
+ const ev = dragPreviewEvent;
329
+ if (!ev || (!isAllDay(ev) && !isMultiDay(ev))) return null;
330
+ return segmentForDay(ev, dayMs);
331
+ }
332
+
303
333
  function getCellWidth(): number {
304
334
  const cell = el?.querySelector('.wg-cell');
305
335
  return cell ? cell.getBoundingClientRect().width : 100;
@@ -374,6 +404,26 @@
374
404
  }
375
405
  </script>
376
406
 
407
+ {#snippet allDaySegmentContent(seg: DaySegment)}
408
+ {#if seg.isStart}
409
+ <span class="wg-ad-title">{seg.ev.title}</span>
410
+ {:else}
411
+ <span class="wg-ad-cont" aria-hidden="true">◂</span>
412
+ <span class="wg-ad-title">{seg.ev.title}</span>
413
+ {/if}
414
+ {#if !seg.isEnd && seg.totalDays > 1}
415
+ <span class="wg-ad-arrow" aria-hidden="true">▸</span>
416
+ {/if}
417
+ {/snippet}
418
+
419
+ {#snippet timedEventContent(ev: TimelineEvent)}
420
+ <span class="wg-ev-time">{fmtAmPm(ev.start)}</span>
421
+ <span class="wg-ev-title">{ev.title}</span>
422
+ {#if ev.location}
423
+ <span class="wg-ev-loc">{ev.location}</span>
424
+ {/if}
425
+ {/snippet}
426
+
377
427
  <div class="wg" class:wg--auto={autoHeight} style={style || undefined} style:height={autoHeight ? undefined : (height ? `${height}px` : '100%')}>
378
428
  <div
379
429
  class="wg-body"
@@ -388,11 +438,17 @@
388
438
  <!-- Day columns (header inside each cell) -->
389
439
  <div class="wg-days">
390
440
  {#each week.days as day (day.ms)}
441
+ {@const visibleAllDaySegments = day.allDaySegments.filter((seg) => !isDraggedEvent(seg.ev.id))}
442
+ {@const visibleTimedEvents = timedEventsForDay(day)}
443
+ {@const previewTimedEvent = dragPreviewTimedForDay(day.ms)}
444
+ {@const previewSegment = dragPreviewSegmentForDay(day.ms)}
391
445
  <div
392
446
  class="wg-cell"
393
447
  class:wg-cell--today={day.isToday}
394
448
  class:wg-cell--past={day.isPast}
395
- class:wg-cell--weekend={day.isWeekend} class:wg-cell--disabled={disabledSet.has(day.ms)} role="gridcell"
449
+ class:wg-cell--weekend={day.isWeekend}
450
+ class:wg-cell--disabled={disabledSet.has(day.ms)}
451
+ role="gridcell"
396
452
  tabindex="0"
397
453
  aria-label="{new Date(day.ms).toLocaleDateString(locale ?? 'en-US', { weekday: 'long', month: 'short', day: 'numeric' })}{day.isToday ? ` (${L.today.toLowerCase()})` : ''}, {L.nEvents(day.events.length)}"
398
454
  onclick={(e) => handleDayCellClick(day.ms, e)}
@@ -435,9 +491,9 @@
435
491
  {/if}
436
492
 
437
493
  <!-- All-day / multi-day events -->
438
- {#if day.allDaySegments.length > 0}
494
+ {#if visibleAllDaySegments.length > 0 || previewSegment}
439
495
  <div class="wg-allday">
440
- {#each day.allDaySegments as seg (seg.ev.id)}
496
+ {#each visibleAllDaySegments as seg (seg.ev.id)}
441
497
  <div
442
498
  class="wg-ad"
443
499
  class:wg-ad--start={seg.isStart}
@@ -451,23 +507,27 @@
451
507
  onpointerdown={(e) => onEventPointerDown(e, seg.ev)}
452
508
  onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); oneventclick?.(seg.ev); } }}
453
509
  >
454
- {#if seg.isStart}
455
- <span class="wg-ad-title">{seg.ev.title}</span>
456
- {:else}
457
- <span class="wg-ad-cont" aria-hidden="true">◂</span>
458
- <span class="wg-ad-title">{seg.ev.title}</span>
459
- {/if}
460
- {#if !seg.isEnd && seg.totalDays > 1}
461
- <span class="wg-ad-arrow" aria-hidden="true">▸</span>
462
- {/if}
510
+ {@render allDaySegmentContent(seg)}
463
511
  </div>
464
512
  {/each}
513
+ {#if previewSegment}
514
+ <div
515
+ class="wg-ad wg-ad--drag-preview"
516
+ class:wg-ad--start={previewSegment.isStart}
517
+ class:wg-ad--end={previewSegment.isEnd}
518
+ class:wg-ad--mid={!previewSegment.isStart && !previewSegment.isEnd}
519
+ style:--ev-color={previewSegment.ev.color ?? 'var(--dt-accent)'}
520
+ aria-hidden="true"
521
+ >
522
+ {@render allDaySegmentContent(previewSegment)}
523
+ </div>
524
+ {/if}
465
525
  </div>
466
526
  {/if}
467
527
 
468
528
  <!-- Timed events -->
469
529
  <div class="wg-cell-events">
470
- {#each day.events.slice(0, MAX_EVENTS_SHOWN) as ev (ev.id)}
530
+ {#each visibleTimedEvents.slice(0, MAX_EVENTS_SHOWN) as ev (ev.id)}
471
531
  <div
472
532
  class="wg-ev"
473
533
  class:wg-ev--selected={selectedEventId === ev.id}
@@ -482,18 +542,24 @@
482
542
  role="button"
483
543
  tabindex="0"
484
544
  aria-label="{ev.title}{ev.status === 'cancelled' ? ` (cancelled)` : ''}{ev.status === 'tentative' ? ` (tentative)` : ''}{ev.status === 'full' ? ` (full)` : ''}{ev.status === 'limited' ? ` (limited)` : ''}{ev.start.getTime() <= clock.tick && ev.end.getTime() > clock.tick ? ` (${L.inProgress})` : ''}"
485
- onpointerdown={(e) => onEventPointerDown(e, ev)} onpointerenter={() => oneventhover?.(ev)}
545
+ onpointerdown={(e) => onEventPointerDown(e, ev)}
546
+ onpointerenter={() => oneventhover?.(ev)}
486
547
  onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); oneventclick?.(ev); } }}
487
548
  >
488
- <span class="wg-ev-time">{fmtAmPm(ev.start)}</span>
489
- <span class="wg-ev-title">{ev.title}</span>
490
- {#if ev.location}
491
- <span class="wg-ev-loc">{ev.location}</span>
492
- {/if}
549
+ {@render timedEventContent(ev)}
493
550
  </div>
494
551
  {/each}
495
- {#if day.events.length > MAX_EVENTS_SHOWN}
496
- <div class="wg-ev-more">{L.nMore(day.events.length - MAX_EVENTS_SHOWN)}</div>
552
+ {#if previewTimedEvent}
553
+ <div
554
+ class="wg-ev wg-ev--drag-preview"
555
+ style:--ev-color={previewTimedEvent.color ?? 'var(--dt-accent)'}
556
+ aria-hidden="true"
557
+ >
558
+ {@render timedEventContent(previewTimedEvent)}
559
+ </div>
560
+ {/if}
561
+ {#if visibleTimedEvents.length > MAX_EVENTS_SHOWN}
562
+ <div class="wg-ev-more">{L.nMore(visibleTimedEvents.length - MAX_EVENTS_SHOWN)}</div>
497
563
  {/if}
498
564
  </div>
499
565
  </div>
@@ -534,6 +600,8 @@
534
600
  flex: 1;
535
601
  overflow-y: auto;
536
602
  overflow-x: hidden;
603
+ box-sizing: border-box;
604
+ padding-top: 48px;
537
605
  scrollbar-width: thin;
538
606
  scrollbar-color: var(--dt-scrollbar, rgba(0, 0, 0, 0.08)) transparent;
539
607
  }
@@ -557,8 +625,8 @@
557
625
 
558
626
  .wg-week--current {
559
627
  background: var(--dt-today-bg, rgba(239, 68, 68, 0.02));
560
- border: 2.5px solid var(--dt-accent, #ef4444);
561
- box-shadow: 0 0 0 1px color-mix(in srgb, var(--dt-accent, #ef4444) 15%, transparent);
628
+ border: 2.5px solid var(--dt-accent, #2563eb);
629
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--dt-accent, #2563eb) 15%, transparent);
562
630
  }
563
631
 
564
632
  /* ─── Week body ──────────────────────────────────── */
@@ -604,8 +672,6 @@
604
672
 
605
673
  /* ─── Disabled cell ──────────────────────────────── */
606
674
  .wg-cell--disabled {
607
- opacity: 0.35;
608
- pointer-events: none;
609
675
  background: repeating-linear-gradient(
610
676
  45deg,
611
677
  transparent,
@@ -668,7 +734,7 @@
668
734
  }
669
735
 
670
736
  .wg-cell-hd--today .wg-day-wd {
671
- color: var(--dt-accent, #ef4444);
737
+ color: var(--dt-accent, #2563eb);
672
738
  font-weight: 600;
673
739
  }
674
740
 
@@ -682,7 +748,7 @@
682
748
  }
683
749
 
684
750
  .wg-day-num--today {
685
- color: var(--dt-accent, #ef4444);
751
+ color: var(--dt-accent, #2563eb);
686
752
  font-weight: 900;
687
753
  }
688
754
 
@@ -719,15 +785,25 @@
719
785
  gap: 3px;
720
786
  padding: 2px 5px;
721
787
  border-radius: 3px;
722
- background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #10141c));
788
+ background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, var(--dt-bg, #ffffff)));
723
789
  cursor: pointer;
724
790
  overflow: hidden;
725
791
  transition: background 0.12s;
726
792
  min-height: 18px;
727
793
  }
728
794
 
795
+ .wg-ad--drag-preview {
796
+ position: relative;
797
+ z-index: 8;
798
+ opacity: 0.95;
799
+ pointer-events: none;
800
+ box-shadow: 0 6px 18px color-mix(in srgb, var(--ev-color) 26%, rgba(0, 0, 0, 0.22));
801
+ outline: 1px solid color-mix(in srgb, var(--ev-color) 42%, transparent);
802
+ cursor: grabbing;
803
+ }
804
+
729
805
  .wg-ad:hover {
730
- background: color-mix(in srgb, var(--ev-color) 32%, var(--dt-surface, #10141c));
806
+ background: color-mix(in srgb, var(--ev-color) 32%, var(--dt-surface, var(--dt-bg, #ffffff)));
731
807
  }
732
808
 
733
809
  .wg-ad--start {
@@ -787,14 +863,25 @@
787
863
  gap: 3px 5px;
788
864
  padding: 3px 6px;
789
865
  border-radius: 4px;
790
- background: color-mix(in srgb, var(--ev-color) 15%, var(--dt-surface, #10141c));
866
+ background: color-mix(in srgb, var(--ev-color) 15%, var(--dt-surface, var(--dt-bg, #ffffff)));
791
867
  cursor: pointer;
792
868
  overflow: hidden;
793
869
  transition: background 0.12s;
794
870
  }
795
871
 
796
872
  .wg-ev:hover {
797
- background: color-mix(in srgb, var(--ev-color) 25%, var(--dt-surface, #10141c));
873
+ background: color-mix(in srgb, var(--ev-color) 25%, var(--dt-surface, var(--dt-bg, #ffffff)));
874
+ }
875
+
876
+ .wg-ev--drag-preview {
877
+ position: relative;
878
+ z-index: 8;
879
+ opacity: 0.95;
880
+ pointer-events: none;
881
+ background: color-mix(in srgb, var(--ev-color) 28%, var(--dt-surface, var(--dt-bg, #ffffff)));
882
+ box-shadow: 0 6px 18px color-mix(in srgb, var(--ev-color) 24%, rgba(0, 0, 0, 0.22));
883
+ outline: 1px solid color-mix(in srgb, var(--ev-color) 42%, transparent);
884
+ cursor: grabbing;
798
885
  }
799
886
 
800
887
  .wg-ev--selected {
@@ -802,7 +889,7 @@
802
889
  }
803
890
 
804
891
  .wg-ev--current {
805
- background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #10141c));
892
+ background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, var(--dt-bg, #ffffff)));
806
893
  }
807
894
 
808
895
  .wg-ev--cancelled {
@@ -869,7 +956,7 @@
869
956
  z-index: 20;
870
957
  display: flex;
871
958
  gap: 2px;
872
- background: color-mix(in srgb, var(--dt-surface, #10141c) 85%, transparent);
959
+ background: color-mix(in srgb, var(--dt-surface, var(--dt-bg, #ffffff)) 85%, transparent);
873
960
  backdrop-filter: blur(6px);
874
961
  -webkit-backdrop-filter: blur(6px);
875
962
  border-radius: 8px;
@@ -886,7 +973,7 @@
886
973
  background: transparent;
887
974
  color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
888
975
  cursor: pointer;
889
- font: 600 11px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
976
+ font: 600 11px / 1 var(--dt-sans, system-ui, sans-serif);
890
977
  padding: 6px 12px;
891
978
  border-radius: 6px;
892
979
  letter-spacing: 0.04em;
@@ -900,18 +987,18 @@
900
987
  color: var(--dt-text, rgba(226, 232, 240, 0.85));
901
988
  }
902
989
  .wg-nav-pill:focus-visible {
903
- outline: 2px solid color-mix(in srgb, var(--dt-accent, #ef4444) 55%, transparent);
990
+ outline: 2px solid color-mix(in srgb, var(--dt-accent, #2563eb) 55%, transparent);
904
991
  outline-offset: 2px;
905
992
  }
906
993
 
907
994
  /* ─── Focus-visible ──────────────────────────────── */
908
995
  .wg-cell:focus-visible {
909
- outline: 2px solid var(--dt-accent, #ef4444);
996
+ outline: 2px solid var(--dt-accent, #2563eb);
910
997
  outline-offset: -2px;
911
998
  }
912
999
 
913
1000
  .wg-ev:focus-visible {
914
- outline: 2px solid var(--ev-color, var(--dt-accent, #ef4444));
1001
+ outline: 2px solid var(--ev-color, var(--dt-accent, #2563eb));
915
1002
  outline-offset: 1px;
916
1003
  }
917
1004
 
package/package.json CHANGED
@@ -1,77 +1,79 @@
1
1
  {
2
- "name": "@nomideusz/svelte-calendar",
3
- "version": "0.6.4",
4
- "description": "A themeable Svelte 5 calendar with Day and Week views — Planner and Agenda.",
5
- "type": "module",
6
- "license": "MIT",
7
- "svelte": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "svelte": "./dist/index.js",
13
- "default": "./dist/index.js"
14
- },
15
- "./widget": "./widget/widget.js"
16
- },
17
- "files": [
18
- "dist",
19
- "widget",
20
- "!dist/**/*.test.*",
21
- "!dist/**/*.spec.*"
22
- ],
23
- "peerDependencies": {
24
- "svelte": "^5.0.0",
25
- "@chenglou/pretext": "^0.0.4"
26
- },
27
- "peerDependenciesMeta": {
28
- "@chenglou/pretext": {
29
- "optional": true
30
- }
31
- },
32
- "devDependencies": {
33
- "@chenglou/pretext": "^0.0.4",
34
- "@sveltejs/adapter-auto": "^7.0.0",
35
- "@sveltejs/kit": "^2.50.2",
36
- "@sveltejs/package": "^2.5.7",
37
- "@sveltejs/vite-plugin-svelte": "^6.2.4",
38
- "@types/node": "^22.0.0",
39
- "@vercel/analytics": "^1.6.1",
40
- "jsdom": "^28.1.0",
41
- "marked": "^17.0.3",
42
- "svelte": "^5.51.0",
43
- "svelte-check": "^4.3.6",
44
- "typescript": "^5.9.3",
45
- "vite": "^7.3.1",
46
- "vitest": "^4.0.18"
47
- },
48
- "dependencies": {
49
- "date-fns": "^4.1.0",
50
- "date-fns-tz": "^3.2.0"
51
- },
52
- "keywords": [
53
- "svelte",
54
- "calendar",
55
- "scheduler",
56
- "timeline",
57
- "week",
58
- "day",
59
- "booking",
60
- "svelte5"
61
- ],
62
- "repository": {
63
- "type": "git",
64
- "url": "https://github.com/nomideusz/svelte-calendar"
65
- },
66
- "scripts": {
67
- "dev": "vite dev",
68
- "build": "vite build",
69
- "package": "svelte-kit sync && svelte-package",
70
- "build:widget": "vite build --config vite.config.widget.ts",
71
- "preview": "vite preview",
72
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
73
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
74
- "test": "vitest run",
75
- "test:watch": "vitest"
76
- }
77
- }
2
+ "name": "@nomideusz/svelte-calendar",
3
+ "version": "0.6.5",
4
+ "description": "A themeable Svelte 5 calendar with Day and Week views — Planner and Agenda.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "svelte": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "svelte": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./widget": "./widget/widget.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "widget",
20
+ "!dist/**/*.test.*",
21
+ "!dist/**/*.spec.*"
22
+ ],
23
+ "scripts": {
24
+ "dev": "vite dev",
25
+ "build": "vite build",
26
+ "package": "svelte-kit sync && svelte-package",
27
+ "build:widget": "vite build --config vite.config.widget.ts",
28
+ "prepublishOnly": "npm run package",
29
+ "preview": "vite preview",
30
+ "prepare": "svelte-kit sync || echo ''",
31
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
32
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest"
35
+ },
36
+ "peerDependencies": {
37
+ "svelte": "^5.0.0",
38
+ "@chenglou/pretext": "^0.0.4"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "@chenglou/pretext": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "devDependencies": {
46
+ "@chenglou/pretext": "^0.0.4",
47
+ "@sveltejs/adapter-auto": "^7.0.0",
48
+ "@sveltejs/kit": "^2.50.2",
49
+ "@sveltejs/package": "^2.5.7",
50
+ "@sveltejs/vite-plugin-svelte": "^6.2.4",
51
+ "@types/node": "^22.0.0",
52
+ "@vercel/analytics": "^1.6.1",
53
+ "jsdom": "^28.1.0",
54
+ "marked": "^17.0.3",
55
+ "svelte": "^5.51.0",
56
+ "svelte-check": "^4.3.6",
57
+ "typescript": "^5.9.3",
58
+ "vite": "^7.3.1",
59
+ "vitest": "^4.0.18"
60
+ },
61
+ "dependencies": {
62
+ "date-fns": "^4.1.0",
63
+ "date-fns-tz": "^3.2.0"
64
+ },
65
+ "keywords": [
66
+ "svelte",
67
+ "calendar",
68
+ "scheduler",
69
+ "timeline",
70
+ "week",
71
+ "day",
72
+ "booking",
73
+ "svelte5"
74
+ ],
75
+ "repository": {
76
+ "type": "git",
77
+ "url": "https://github.com/nomideusz/svelte-calendar"
78
+ }
79
+ }