@nomideusz/svelte-calendar 0.14.1 → 0.15.2
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/dist/calendar/Calendar.svelte +450 -372
- package/dist/calendar/Calendar.svelte.d.ts +2 -1
- package/dist/headless/types.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/primitives/DayHeader.svelte +1 -7
- package/dist/primitives/EmptySlot.svelte +8 -10
- package/dist/primitives/EventBlock.svelte +12 -22
- package/dist/primitives/FloatingPanel.svelte +191 -0
- package/dist/primitives/FloatingPanel.svelte.d.ts +23 -0
- package/dist/primitives/NowIndicator.svelte +2 -13
- package/dist/primitives/TimeGutter.svelte +1 -7
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.js +1 -0
- package/dist/views/agenda/AgendaDay.svelte +68 -45
- package/dist/views/agenda/AgendaWeek.svelte +120 -105
- package/dist/views/mobile/MobileDay.svelte +313 -276
- package/dist/views/mobile/MobileWeek.svelte +91 -84
- package/dist/views/month/MonthGrid.svelte +101 -104
- package/dist/views/planner/PlannerWeek.svelte +475 -407
- package/dist/views/planner/PlannerWeek.svelte.d.ts +1 -1
- package/dist/widget/CalendarWidget.svelte +64 -83
- package/package.json +17 -10
- package/widget/widget.js +12723 -12203
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
<script lang="ts"
|
|
1
|
+
<script lang="ts">/**
|
|
2
|
+
* AgendaWeek — rolling N-day agenda view.
|
|
3
|
+
*
|
|
4
|
+
* "The Week Ahead":
|
|
5
|
+
* Today + tomorrow expanded with time slots/countdowns.
|
|
6
|
+
* Future days compact (dot + time + title).
|
|
7
|
+
* Past days dimmed.
|
|
8
|
+
*
|
|
9
|
+
* Answers: "What's coming up and when do I need to be ready?"
|
|
10
|
+
*/
|
|
11
|
+
import { createClock } from "../../core/clock.svelte.js";
|
|
2
12
|
import { sod, DAY_MS, startOfWeek, dayNum, isAllDay, isMultiDay, segmentForDay } from "../../core/time.js";
|
|
3
13
|
import { weekdayLong, monthLong } from "../../core/locale.js";
|
|
4
14
|
import { useCalendarContext } from "../shared/context.svelte.js";
|
|
@@ -6,16 +16,7 @@ import EventContent from "../shared/EventContent.svelte";
|
|
|
6
16
|
import { fmtTime, duration, timeUntilMs, progress, groupIntoSlots } from "../shared/format.js";
|
|
7
17
|
const ctx = useCalendarContext();
|
|
8
18
|
const L = $derived(ctx.labels);
|
|
9
|
-
let {
|
|
10
|
-
mondayStart = true,
|
|
11
|
-
locale,
|
|
12
|
-
height,
|
|
13
|
-
events = [],
|
|
14
|
-
style = "",
|
|
15
|
-
focusDate,
|
|
16
|
-
oneventclick,
|
|
17
|
-
selectedEventId = null
|
|
18
|
-
} = $props();
|
|
19
|
+
let { mondayStart = true, locale, height, events = [], style = "", focusDate, oneventclick, selectedEventId = null } = $props();
|
|
19
20
|
const clock = createClock(ctx.timezone);
|
|
20
21
|
const viewState = $derived(ctx.viewState);
|
|
21
22
|
const equalDays = $derived(ctx.equalDays);
|
|
@@ -24,139 +25,153 @@ const hideDays = $derived(ctx.hideDays);
|
|
|
24
25
|
const isMobile = $derived(ctx.isMobile);
|
|
25
26
|
const autoHeight = $derived(ctx.autoHeight);
|
|
26
27
|
const compact = $derived(ctx.compact);
|
|
28
|
+
// Timetable layout is a desktop affordance — stacked list stays on mobile.
|
|
27
29
|
const cols = $derived(ctx.columns && !isMobile);
|
|
28
30
|
const dayHeaderSnippet = $derived(ctx.dayHeaderSnippet);
|
|
29
31
|
const oneventhover = $derived(ctx.oneventhover);
|
|
30
32
|
const ondayclick = $derived(ctx.ondayclick);
|
|
31
33
|
const disabledSet = $derived(ctx.disabledSet);
|
|
32
34
|
function clickDay(ms) {
|
|
33
|
-
|
|
35
|
+
ondayclick?.(new Date(ms));
|
|
34
36
|
}
|
|
37
|
+
// ── Day-to-day card drag (timetable columns) ────────
|
|
38
|
+
// This view has no hour axis, so a drag can only choose a DAY: the card
|
|
39
|
+
// keeps its time of day. Wired through the same oneventmove the planner
|
|
40
|
+
// uses (readOnly-gated and timezone-unzoned at the Calendar layer);
|
|
41
|
+
// per-event data.readOnly opts out — same convention as PlannerWeek.
|
|
35
42
|
const oneventmove = $derived(ctx.oneventmove);
|
|
36
43
|
let dragId = $state(null);
|
|
37
44
|
let dropDay = $state(null);
|
|
38
45
|
const canDrag = (ev) => cols && !!oneventmove && !ev.data?.readOnly && !isAllDay(ev) && !isMultiDay(ev);
|
|
46
|
+
/** Valid drop day for the card in flight: not past, not closed. */
|
|
39
47
|
const isDropDay = (day) => dragId !== null && day.tier !== "past" && !disabledSet.has(day.ms);
|
|
40
48
|
function onCardDragStart(e, ev) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
dragId = String(ev.id);
|
|
50
|
+
if (e.dataTransfer) {
|
|
51
|
+
e.dataTransfer.effectAllowed = "move";
|
|
52
|
+
e.dataTransfer.setData("text/plain", String(ev.id));
|
|
53
|
+
}
|
|
46
54
|
}
|
|
47
55
|
function onDayDrop(e, day) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
if (!isDropDay(day)) return;
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
e.stopPropagation();
|
|
59
|
+
const ev = events.find((x) => String(x.id) === dragId);
|
|
60
|
+
dragId = null;
|
|
61
|
+
dropDay = null;
|
|
62
|
+
if (!ev) return;
|
|
63
|
+
// Same-day drop is a no-op, not a zero-minute "move" for the host to reject.
|
|
64
|
+
if (sod(ev.start.getTime()) === day.ms) return;
|
|
65
|
+
const start = new Date(day.ms + (ev.start.getTime() - sod(ev.start.getTime())));
|
|
66
|
+
const end = new Date(start.getTime() + (ev.end.getTime() - ev.start.getTime()));
|
|
67
|
+
oneventmove?.(ev, start, end);
|
|
59
68
|
}
|
|
69
|
+
// ── Swipe navigation (mobile, touch only) ──────────
|
|
60
70
|
let swipeStartX = 0;
|
|
61
71
|
let swipeStartY = 0;
|
|
62
72
|
let swipeActive = false;
|
|
63
73
|
const SWIPE_THRESHOLD = 50;
|
|
64
74
|
function onPointerDown(e) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
if (!isMobile || e.pointerType !== "touch") return;
|
|
76
|
+
swipeActive = true;
|
|
77
|
+
swipeStartX = e.clientX;
|
|
78
|
+
swipeStartY = e.clientY;
|
|
69
79
|
}
|
|
70
80
|
function onPointerUp(e) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
if (!swipeActive || e.pointerType !== "touch") return;
|
|
82
|
+
swipeActive = false;
|
|
83
|
+
const dx = e.clientX - swipeStartX;
|
|
84
|
+
const dy = e.clientY - swipeStartY;
|
|
85
|
+
if (Math.abs(dx) > SWIPE_THRESHOLD && Math.abs(dx) > Math.abs(dy) * 1.4) {
|
|
86
|
+
if (dx > 0) viewState?.prev();
|
|
87
|
+
else viewState?.next();
|
|
88
|
+
}
|
|
79
89
|
}
|
|
80
90
|
function onPointerCancel() {
|
|
81
|
-
|
|
91
|
+
swipeActive = false;
|
|
82
92
|
}
|
|
93
|
+
// ── Compact-day disclosure ("+N more" → expand) ─────
|
|
83
94
|
let expandedDays = $state([]);
|
|
84
95
|
function toggleDayExpand(ms) {
|
|
85
|
-
|
|
96
|
+
expandedDays = expandedDays.includes(ms) ? expandedDays.filter((m) => m !== ms) : [...expandedDays, ms];
|
|
86
97
|
}
|
|
98
|
+
// ── Completed-events disclosure ("✓ N completed" → expand) ─────
|
|
87
99
|
let expandedPast = $state([]);
|
|
88
100
|
function togglePastExpand(ms) {
|
|
89
|
-
|
|
101
|
+
expandedPast = expandedPast.includes(ms) ? expandedPast.filter((m) => m !== ms) : [...expandedPast, ms];
|
|
90
102
|
}
|
|
103
|
+
// ── Format helpers (delegated to shared/format.ts) ──
|
|
104
|
+
// fmtTime, duration, groupIntoSlots imported at top
|
|
105
|
+
// Thin wrappers that bind locale / clock.tick:
|
|
91
106
|
const fmt = (d) => fmtTime(d, locale);
|
|
92
107
|
const eta = (ms) => timeUntilMs(ms, clock.tick, L);
|
|
93
108
|
const prog = (ev) => progress(ev, clock.tick);
|
|
109
|
+
// ── Event handlers ──────────────────────────────────
|
|
94
110
|
function handleClick(ev) {
|
|
95
|
-
|
|
111
|
+
oneventclick?.(ev);
|
|
96
112
|
}
|
|
97
|
-
const weekStartMs = $derived(
|
|
98
|
-
focusDate ? viewState?.dayCount === 7 ? startOfWeek(sod(focusDate.getTime()), mondayStart) : sod(focusDate.getTime()) : viewState?.dayCount === 7 ? startOfWeek(clock.today, mondayStart) : clock.today
|
|
99
|
-
);
|
|
113
|
+
const weekStartMs = $derived(focusDate ? viewState?.dayCount === 7 ? startOfWeek(sod(focusDate.getTime()), mondayStart) : sod(focusDate.getTime()) : viewState?.dayCount === 7 ? startOfWeek(clock.today, mondayStart) : clock.today);
|
|
100
114
|
const customDays = $derived(viewState?.dayCount ?? 7);
|
|
101
115
|
const weekDays = $derived.by(() => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
116
|
+
const now = clock.tick;
|
|
117
|
+
const todayMs = clock.today;
|
|
118
|
+
const tomorrowMs = todayMs + DAY_MS;
|
|
119
|
+
const out = [];
|
|
120
|
+
for (let i = 0; i < customDays; i++) {
|
|
121
|
+
const ms = weekStartMs + i * DAY_MS;
|
|
122
|
+
const dEnd = ms + DAY_MS;
|
|
123
|
+
const dayEvts = events.filter((ev) => ev.start.getTime() < dEnd && ev.end.getTime() > ms).sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
124
|
+
const allDayEvts = dayEvts.filter((ev) => isAllDay(ev) || isMultiDay(ev));
|
|
125
|
+
const timedEvts = dayEvts.filter((ev) => !isAllDay(ev) && !isMultiDay(ev));
|
|
126
|
+
const totalMinutes = timedEvts.reduce((sum, ev) => {
|
|
127
|
+
const s = Math.max(ev.start.getTime(), ms);
|
|
128
|
+
const e = Math.min(ev.end.getTime(), dEnd);
|
|
129
|
+
return sum + (e - s) / 6e4;
|
|
130
|
+
}, 0);
|
|
131
|
+
const pastEvents = [];
|
|
132
|
+
const currentEvents = [];
|
|
133
|
+
const upcomingEvents = [];
|
|
134
|
+
for (const ev of timedEvts) {
|
|
135
|
+
if (ev.end.getTime() <= now) pastEvents.push(ev);
|
|
136
|
+
else if (ev.start.getTime() <= now && ev.end.getTime() > now) currentEvents.push(ev);
|
|
137
|
+
else upcomingEvents.push(ev);
|
|
138
|
+
}
|
|
139
|
+
let tier;
|
|
140
|
+
if (equalDays) {
|
|
141
|
+
tier = "upcoming";
|
|
142
|
+
} else if (ms === todayMs) {
|
|
143
|
+
tier = "today";
|
|
144
|
+
} else if (ms === tomorrowMs) {
|
|
145
|
+
tier = "tomorrow";
|
|
146
|
+
} else if (ms < todayMs) {
|
|
147
|
+
tier = "past";
|
|
148
|
+
} else {
|
|
149
|
+
tier = "upcoming";
|
|
150
|
+
}
|
|
151
|
+
out.push({
|
|
152
|
+
ms,
|
|
153
|
+
dayName: weekdayLong(ms, locale),
|
|
154
|
+
dateLabel: `${monthLong(ms, locale)} ${dayNum(ms)}`,
|
|
155
|
+
tier,
|
|
156
|
+
isToday: ms === todayMs,
|
|
157
|
+
events: dayEvts,
|
|
158
|
+
allDayEvents: allDayEvts,
|
|
159
|
+
timedEvents: timedEvts,
|
|
160
|
+
pastEvents,
|
|
161
|
+
currentEvents,
|
|
162
|
+
upcomingEvents,
|
|
163
|
+
totalHours: Math.round(totalMinutes / 60 * 10) / 10
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// Filter hidden days if hideDays is set
|
|
167
|
+
if (hideDays?.length) {
|
|
168
|
+
return out.filter((d) => {
|
|
169
|
+
const jsDay = new Date(d.ms).getDay();
|
|
170
|
+
const iso = jsDay === 0 ? 7 : jsDay;
|
|
171
|
+
return !hideDays.includes(iso);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
160
175
|
});
|
|
161
176
|
</script>
|
|
162
177
|
|