@nomideusz/svelte-calendar 0.7.4 → 0.8.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 +19 -4
- package/dist/calendar/Calendar.svelte +364 -497
- package/dist/calendar/Calendar.svelte.d.ts +6 -2
- package/dist/core/clock.svelte.js +12 -5
- package/dist/core/locale.d.ts +4 -0
- package/dist/core/locale.js +4 -0
- package/dist/engine/view-state.svelte.d.ts +2 -2
- package/dist/engine/view-state.svelte.js +20 -0
- package/dist/headless/create-calendar.svelte.js +18 -2
- package/dist/headless/types.d.ts +6 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/primitives/DayHeader.svelte +9 -25
- package/dist/primitives/EmptySlot.svelte +15 -31
- package/dist/primitives/EventBlock.svelte +33 -61
- package/dist/primitives/NowIndicator.svelte +16 -42
- package/dist/primitives/TimeGutter.svelte +9 -23
- package/dist/views/agenda/Agenda.svelte +3 -10
- package/dist/views/agenda/AgendaDay.svelte +195 -167
- package/dist/views/agenda/AgendaWeek.svelte +141 -195
- package/dist/views/mobile/Mobile.svelte +3 -10
- package/dist/views/mobile/MobileDay.svelte +422 -266
- package/dist/views/mobile/MobileWeek.svelte +131 -186
- package/dist/views/month/MonthGrid.svelte +334 -0
- package/dist/views/month/MonthGrid.svelte.d.ts +15 -0
- package/dist/views/planner/Planner.svelte +3 -10
- package/dist/views/planner/PlannerDay.svelte +645 -549
- package/dist/views/planner/PlannerWeek.svelte +299 -412
- package/dist/views/shared/EventContent.svelte +16 -0
- package/dist/views/shared/EventContent.svelte.d.ts +9 -0
- package/dist/views/shared/context.svelte.d.ts +1 -0
- package/dist/views/shared/context.svelte.js +1 -0
- package/dist/widget/CalendarWidget.svelte +79 -116
- package/package.json +3 -2
- package/widget/svelte-calendar.css +329 -13
- package/widget/widget.js +1945 -1031
|
@@ -8,413 +8,298 @@
|
|
|
8
8
|
• Events are clean horizontal bars with colour fill, "9AM- 10AM Title" inline.
|
|
9
9
|
• Generous whitespace, thin dividers, minimal chrome.
|
|
10
10
|
-->
|
|
11
|
-
<script lang="ts">
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
let evDragId = $state<string | null>(null);
|
|
304
|
-
let evDragEvent: TimelineEvent | null = null;
|
|
305
|
-
|
|
306
|
-
const dragPreviewEvent = $derived.by(() => {
|
|
307
|
-
const payload = drag?.active && drag.mode === 'move' ? drag.payload : null;
|
|
308
|
-
if (!payload?.eventId) return null;
|
|
309
|
-
const ev = events.find((event) => event.id === payload.eventId);
|
|
310
|
-
if (!ev) return null;
|
|
311
|
-
return { ...ev, start: payload.start, end: payload.end };
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
function isDraggedEvent(eventId: string): boolean {
|
|
315
|
-
return dragPreviewEvent?.id === eventId;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function timedEventsForDay(day: DayCell): TimelineEvent[] {
|
|
319
|
-
if (!dragPreviewEvent) return day.events;
|
|
320
|
-
return day.events.filter((ev) => ev.id !== dragPreviewEvent.id);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Crossfade keys for the previews, snapshotted at render time. Transition
|
|
324
|
-
// params are evaluated lazily at unmount — after the drag payload is
|
|
325
|
-
// already null — so they must never read the reactive preview directly.
|
|
326
|
-
// Plain Map (not $state): written during render, read only by transitions.
|
|
327
|
-
const previewKeySnapshot = new Map<number | 'timed', string>();
|
|
328
|
-
|
|
329
|
-
function dragPreviewTimedForDay(dayMs: number): TimelineEvent | null {
|
|
330
|
-
const ev = dragPreviewEvent;
|
|
331
|
-
if (!ev || isAllDay(ev) || isMultiDay(ev)) return null;
|
|
332
|
-
const dayEnd = dayMs + DAY_MS;
|
|
333
|
-
const hit = ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs;
|
|
334
|
-
if (hit) previewKeySnapshot.set('timed', ev.id);
|
|
335
|
-
return hit ? ev : null;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function dragPreviewSegmentForDay(dayMs: number): DaySegment | null {
|
|
339
|
-
const ev = dragPreviewEvent;
|
|
340
|
-
if (!ev || (!isAllDay(ev) && !isMultiDay(ev))) return null;
|
|
341
|
-
const seg = segmentForDay(ev, dayMs);
|
|
342
|
-
if (seg) previewKeySnapshot.set(dayMs, `${ev.id}:${seg.dayIndex}`);
|
|
343
|
-
return seg;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function getCellWidth(): number {
|
|
347
|
-
const cell = el?.querySelector('.wg-cell');
|
|
348
|
-
return cell ? cell.getBoundingClientRect().width : 100;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function getRowHeight(): number {
|
|
352
|
-
const row = el?.querySelector('.wg-week');
|
|
353
|
-
return row ? row.getBoundingClientRect().height + 24 : 200; // 24 ≈ vertical margin between weeks
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function onEventPointerDown(e: PointerEvent, ev: TimelineEvent) {
|
|
357
|
-
if (e.button !== 0 || !drag || readOnly || ev.data?.readOnly) return;
|
|
358
|
-
e.stopPropagation();
|
|
359
|
-
evDragStartX = e.clientX;
|
|
360
|
-
evDragStartY = e.clientY;
|
|
361
|
-
evDragStarted = false;
|
|
362
|
-
evDragId = ev.id;
|
|
363
|
-
evDragEvent = ev;
|
|
364
|
-
|
|
365
|
-
window.addEventListener('pointermove', onEvWindowPointerMove);
|
|
366
|
-
window.addEventListener('pointerup', onEvWindowPointerUp, { once: true });
|
|
367
|
-
window.addEventListener('pointercancel', onEvWindowPointerCancel, { once: true });
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function onEvWindowPointerMove(e: PointerEvent) {
|
|
371
|
-
const ev = evDragEvent;
|
|
372
|
-
if (!drag || !ev || evDragId !== ev.id) return;
|
|
373
|
-
const dx = e.clientX - evDragStartX;
|
|
374
|
-
const dy = e.clientY - evDragStartY;
|
|
375
|
-
if (!evDragStarted && Math.abs(dx) + Math.abs(dy) < DRAG_THRESHOLD) return;
|
|
376
|
-
|
|
377
|
-
if (!evDragStarted) {
|
|
378
|
-
evDragStarted = true;
|
|
379
|
-
evDragging = true;
|
|
380
|
-
drag.beginMove(ev.id, ev.start, ev.end);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const cellW = getCellWidth();
|
|
384
|
-
const rowH = getRowHeight();
|
|
385
|
-
const dayOffset = Math.round(dx / cellW);
|
|
386
|
-
const weekOffset = Math.round(dy / rowH);
|
|
387
|
-
const deltaMs = (dayOffset + weekOffset * 7) * DAY_MS;
|
|
388
|
-
drag.updatePointer(
|
|
389
|
-
new Date(ev.start.getTime() + deltaMs),
|
|
390
|
-
new Date(ev.end.getTime() + deltaMs),
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function cleanupEvDrag() {
|
|
395
|
-
window.removeEventListener('pointermove', onEvWindowPointerMove);
|
|
396
|
-
window.removeEventListener('pointerup', onEvWindowPointerUp);
|
|
397
|
-
window.removeEventListener('pointercancel', onEvWindowPointerCancel);
|
|
398
|
-
evDragStarted = false;
|
|
399
|
-
evDragging = false;
|
|
400
|
-
evDragId = null;
|
|
401
|
-
evDragEvent = null;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function onEvWindowPointerUp() {
|
|
405
|
-
if (!drag) { cleanupEvDrag(); return; }
|
|
406
|
-
if (!evDragStarted) {
|
|
407
|
-
if (evDragEvent) oneventclick?.(evDragEvent);
|
|
408
|
-
} else {
|
|
409
|
-
commitDragCtx?.();
|
|
410
|
-
}
|
|
411
|
-
cleanupEvDrag();
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function onEvWindowPointerCancel() {
|
|
415
|
-
if (drag && evDragStarted) drag.cancel();
|
|
416
|
-
cleanupEvDrag();
|
|
417
|
-
}
|
|
11
|
+
<script lang="ts">import { onMount, tick, untrack } from "svelte";
|
|
12
|
+
import { flip } from "svelte/animate";
|
|
13
|
+
import { crossfade } from "svelte/transition";
|
|
14
|
+
import { prefersReducedMotion } from "svelte/motion";
|
|
15
|
+
import { useCalendarContext } from "../shared/context.svelte.js";
|
|
16
|
+
import EventContent from "../shared/EventContent.svelte";
|
|
17
|
+
import { createClock } from "../../core/clock.svelte.js";
|
|
18
|
+
import { DAY_MS, HOUR_MS, sod } from "../../core/time.js";
|
|
19
|
+
import { startOfWeek as sowFn, fractionalHour, isAllDay, isMultiDay, segmentForDay } from "../../core/time.js";
|
|
20
|
+
import { weekdayShort, monthLong, fmtTime as _fmtTime, getLabels } from "../../core/locale.js";
|
|
21
|
+
const L = $derived(getLabels());
|
|
22
|
+
let {
|
|
23
|
+
mondayStart = true,
|
|
24
|
+
locale,
|
|
25
|
+
height = 520,
|
|
26
|
+
events = [],
|
|
27
|
+
style = "",
|
|
28
|
+
focusDate,
|
|
29
|
+
oneventclick,
|
|
30
|
+
oneventcreate,
|
|
31
|
+
selectedEventId = null,
|
|
32
|
+
readOnly = false
|
|
33
|
+
} = $props();
|
|
34
|
+
const ctx = useCalendarContext();
|
|
35
|
+
const clock = createClock();
|
|
36
|
+
const ANIM = $derived(prefersReducedMotion.current ? 0 : 180);
|
|
37
|
+
const [previewSend, previewReceive] = crossfade({ duration: () => prefersReducedMotion.current ? 0 : 160 });
|
|
38
|
+
const drag = $derived(ctx.drag);
|
|
39
|
+
const commitDragCtx = $derived(ctx.commitDrag);
|
|
40
|
+
const viewState = $derived(ctx.viewState);
|
|
41
|
+
const loadRangeCtx = $derived(ctx.loadRange);
|
|
42
|
+
const equalDays = $derived(ctx.equalDays);
|
|
43
|
+
const showDates = $derived(ctx.showDates);
|
|
44
|
+
const hideDays = $derived(ctx.hideDays);
|
|
45
|
+
const blockedSlots = $derived(ctx.blockedSlots);
|
|
46
|
+
const dayHeaderSnippet = $derived(ctx.dayHeaderSnippet);
|
|
47
|
+
const minDuration = $derived(ctx.minDuration);
|
|
48
|
+
const autoHeight = $derived(ctx.autoHeight);
|
|
49
|
+
const oneventhover = $derived(ctx.oneventhover);
|
|
50
|
+
const disabledSet = $derived(ctx.disabledSet);
|
|
51
|
+
const INITIAL_BUFFER = 52;
|
|
52
|
+
const EXTEND_BY = 26;
|
|
53
|
+
const EDGE_PX = 200;
|
|
54
|
+
let bufferBefore = $state(INITIAL_BUFFER);
|
|
55
|
+
let bufferAfter = $state(INITIAL_BUFFER);
|
|
56
|
+
const MAX_EVENTS_SHOWN = 5;
|
|
57
|
+
const _initMs = untrack(() => sod(focusDate?.getTime() ?? Date.now()));
|
|
58
|
+
let internalFocusMs = $state(_initMs);
|
|
59
|
+
let lastExternalMs = _initMs;
|
|
60
|
+
let el;
|
|
61
|
+
function scrollWeekIntoContainer(targetMs, behavior = "auto") {
|
|
62
|
+
if (!el) return;
|
|
63
|
+
let target = null;
|
|
64
|
+
if (targetMs !== void 0) {
|
|
65
|
+
const rows = el.querySelectorAll("[data-week]");
|
|
66
|
+
for (const row of rows) {
|
|
67
|
+
const weekMs = Number(row.dataset.week);
|
|
68
|
+
if (weekMs <= targetMs && targetMs < weekMs + customDays * DAY_MS) {
|
|
69
|
+
target = row;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!target) target = el.querySelector(".wg-week--current");
|
|
75
|
+
if (!target) return;
|
|
76
|
+
const targetTop = target.offsetTop - (el.clientHeight - target.offsetHeight) / 2;
|
|
77
|
+
el.scrollTo({ top: Math.max(0, targetTop), behavior });
|
|
78
|
+
}
|
|
79
|
+
const todayMs = $derived(clock.today);
|
|
80
|
+
const customDays = $derived(viewState?.dayCount ?? 7);
|
|
81
|
+
const anchorPeriodStart = $derived(
|
|
82
|
+
customDays === 7 ? sowFn(internalFocusMs, mondayStart) : sod(internalFocusMs)
|
|
83
|
+
);
|
|
84
|
+
$effect(() => {
|
|
85
|
+
if (!loadRangeCtx) return;
|
|
86
|
+
const rangeStart = new Date(anchorPeriodStart - bufferBefore * customDays * DAY_MS);
|
|
87
|
+
const rangeEnd = new Date(anchorPeriodStart + (bufferAfter + 1) * customDays * DAY_MS);
|
|
88
|
+
loadRangeCtx.set({ start: rangeStart, end: rangeEnd });
|
|
89
|
+
return () => loadRangeCtx.set(null);
|
|
90
|
+
});
|
|
91
|
+
const weeks = $derived.by(() => {
|
|
92
|
+
const result = [];
|
|
93
|
+
for (let w = -bufferBefore; w <= bufferAfter; w++) {
|
|
94
|
+
const periodStart = anchorPeriodStart + w * customDays * DAY_MS;
|
|
95
|
+
const isCurrent = todayMs >= periodStart && todayMs < periodStart + customDays * DAY_MS;
|
|
96
|
+
const days = [];
|
|
97
|
+
for (let d = 0; d < customDays; d++) {
|
|
98
|
+
const ms = periodStart + d * DAY_MS;
|
|
99
|
+
const date = new Date(ms);
|
|
100
|
+
const dayNum = date.getDate();
|
|
101
|
+
const dow = date.getDay();
|
|
102
|
+
const isWeekend = dow === 0 || dow === 6;
|
|
103
|
+
const isToday = ms === todayMs;
|
|
104
|
+
const isPast = equalDays ? false : ms < todayMs;
|
|
105
|
+
const isFirstOfMonth = dayNum === 1;
|
|
106
|
+
const monthLabel2 = d === 0 || isFirstOfMonth ? monthLong(ms, locale).toUpperCase() : null;
|
|
107
|
+
const dayEnd = ms + DAY_MS;
|
|
108
|
+
const dayEventsAll = events.filter((ev) => ev.start.getTime() < dayEnd && ev.end.getTime() > ms).sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
109
|
+
const timedEvents = [];
|
|
110
|
+
const allDaySegments = [];
|
|
111
|
+
for (const ev of dayEventsAll) {
|
|
112
|
+
if (isAllDay(ev) || isMultiDay(ev)) {
|
|
113
|
+
const seg = segmentForDay(ev, ms);
|
|
114
|
+
if (seg) allDaySegments.push(seg);
|
|
115
|
+
} else {
|
|
116
|
+
timedEvents.push(ev);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
days.push({ ms, dayNum, isToday, isPast, isWeekend, isFirstOfMonth, monthLabel: monthLabel2, events: timedEvents, allDaySegments });
|
|
120
|
+
}
|
|
121
|
+
const startDate = new Date(periodStart);
|
|
122
|
+
const showMonth = customDays === 7 ? startDate.getDate() <= 7 : startDate.getDate() <= customDays;
|
|
123
|
+
const monthLabel = showMonth ? monthLong(periodStart, locale).toUpperCase() : null;
|
|
124
|
+
result.push({ weekStart: periodStart, isCurrent, monthLabel, days });
|
|
125
|
+
}
|
|
126
|
+
if (hideDays?.length) {
|
|
127
|
+
for (const row of result) {
|
|
128
|
+
row.days = row.days.filter((d) => {
|
|
129
|
+
const isoDay = new Date(d.ms).getDay();
|
|
130
|
+
const iso = isoDay === 0 ? 7 : isoDay;
|
|
131
|
+
return !hideDays.includes(iso);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
});
|
|
137
|
+
function fmtAmPm(d) {
|
|
138
|
+
return _fmtTime(d, locale);
|
|
139
|
+
}
|
|
140
|
+
function fmtNowTime(tick2) {
|
|
141
|
+
return _fmtTime(new Date(tick2), locale);
|
|
142
|
+
}
|
|
143
|
+
const nowFrac = $derived(fractionalHour(clock.tick) / 24);
|
|
144
|
+
onMount(async () => {
|
|
145
|
+
await tick();
|
|
146
|
+
scrollWeekIntoContainer();
|
|
147
|
+
});
|
|
148
|
+
$effect(() => {
|
|
149
|
+
const ext = focusDate ? sod(focusDate.getTime()) : clock.today;
|
|
150
|
+
if (ext !== lastExternalMs) {
|
|
151
|
+
lastExternalMs = ext;
|
|
152
|
+
internalFocusMs = ext;
|
|
153
|
+
tick().then(() => scrollWeekIntoContainer(ext));
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
function syncFocusFromScroll() {
|
|
157
|
+
if (!el || !viewState) return;
|
|
158
|
+
const centerY = el.scrollTop + el.clientHeight / 2;
|
|
159
|
+
const rows = el.querySelectorAll("[data-week]");
|
|
160
|
+
for (const row of rows) {
|
|
161
|
+
if (row.offsetTop + row.offsetHeight >= centerY) {
|
|
162
|
+
const ms = Number(row.dataset.week);
|
|
163
|
+
if (Number.isFinite(ms) && ms !== lastExternalMs) {
|
|
164
|
+
lastExternalMs = ms;
|
|
165
|
+
viewState.setFocusDate(new Date(ms));
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
let extending = false;
|
|
172
|
+
function handleUserScroll() {
|
|
173
|
+
syncFocusFromScroll();
|
|
174
|
+
if (!el || extending) return;
|
|
175
|
+
if (el.scrollTop < EDGE_PX) {
|
|
176
|
+
extending = true;
|
|
177
|
+
const oldHeight = el.scrollHeight;
|
|
178
|
+
bufferBefore += EXTEND_BY;
|
|
179
|
+
tick().then(() => {
|
|
180
|
+
el.scrollTop += el.scrollHeight - oldHeight;
|
|
181
|
+
extending = false;
|
|
182
|
+
});
|
|
183
|
+
} else {
|
|
184
|
+
const bottomRemaining = el.scrollHeight - el.clientHeight - el.scrollTop;
|
|
185
|
+
if (bottomRemaining < EDGE_PX) {
|
|
186
|
+
bufferAfter += EXTEND_BY;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function handleDayCellClick(ms, e) {
|
|
191
|
+
const target = e.target;
|
|
192
|
+
if (target.closest(".wg-ev")) return;
|
|
193
|
+
if (readOnly || !oneventcreate) return;
|
|
194
|
+
if (disabledSet.has(ms)) return;
|
|
195
|
+
const durMin = minDuration ? Math.max(60, minDuration) : 60;
|
|
196
|
+
const start = new Date(ms + 9 * HOUR_MS);
|
|
197
|
+
const end = new Date(start.getTime() + durMin * 6e4);
|
|
198
|
+
oneventcreate({ start, end });
|
|
199
|
+
}
|
|
200
|
+
const DRAG_THRESHOLD = 8;
|
|
201
|
+
let evDragStartX = 0;
|
|
202
|
+
let evDragStartY = 0;
|
|
203
|
+
let evDragStarted = false;
|
|
204
|
+
let evDragging = $state(false);
|
|
205
|
+
let evDragId = $state(null);
|
|
206
|
+
let evDragEvent = null;
|
|
207
|
+
const dragPreviewEvent = $derived.by(() => {
|
|
208
|
+
const payload = drag?.active && drag.mode === "move" ? drag.payload : null;
|
|
209
|
+
if (!payload?.eventId) return null;
|
|
210
|
+
const ev = events.find((event) => event.id === payload.eventId);
|
|
211
|
+
if (!ev) return null;
|
|
212
|
+
return { ...ev, start: payload.start, end: payload.end };
|
|
213
|
+
});
|
|
214
|
+
function isDraggedEvent(eventId) {
|
|
215
|
+
return dragPreviewEvent?.id === eventId;
|
|
216
|
+
}
|
|
217
|
+
function timedEventsForDay(day) {
|
|
218
|
+
if (!dragPreviewEvent) return day.events;
|
|
219
|
+
return day.events.filter((ev) => ev.id !== dragPreviewEvent.id);
|
|
220
|
+
}
|
|
221
|
+
const previewKeySnapshot = /* @__PURE__ */ new Map();
|
|
222
|
+
function dragPreviewTimedForDay(dayMs) {
|
|
223
|
+
const ev = dragPreviewEvent;
|
|
224
|
+
if (!ev || isAllDay(ev) || isMultiDay(ev)) return null;
|
|
225
|
+
const dayEnd = dayMs + DAY_MS;
|
|
226
|
+
const hit = ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs;
|
|
227
|
+
if (hit) previewKeySnapshot.set("timed", ev.id);
|
|
228
|
+
return hit ? ev : null;
|
|
229
|
+
}
|
|
230
|
+
function dragPreviewSegmentForDay(dayMs) {
|
|
231
|
+
const ev = dragPreviewEvent;
|
|
232
|
+
if (!ev || !isAllDay(ev) && !isMultiDay(ev)) return null;
|
|
233
|
+
const seg = segmentForDay(ev, dayMs);
|
|
234
|
+
if (seg) previewKeySnapshot.set(dayMs, `${ev.id}:${seg.dayIndex}`);
|
|
235
|
+
return seg;
|
|
236
|
+
}
|
|
237
|
+
function getCellWidth() {
|
|
238
|
+
const cell = el?.querySelector(".wg-cell");
|
|
239
|
+
return cell ? cell.getBoundingClientRect().width : 100;
|
|
240
|
+
}
|
|
241
|
+
function getRowHeight() {
|
|
242
|
+
const row = el?.querySelector(".wg-week");
|
|
243
|
+
return row ? row.getBoundingClientRect().height + 24 : 200;
|
|
244
|
+
}
|
|
245
|
+
function onEventPointerDown(e, ev) {
|
|
246
|
+
if (e.button !== 0 || !drag || readOnly || ev.data?.readOnly) return;
|
|
247
|
+
e.stopPropagation();
|
|
248
|
+
evDragStartX = e.clientX;
|
|
249
|
+
evDragStartY = e.clientY;
|
|
250
|
+
evDragStarted = false;
|
|
251
|
+
evDragId = ev.id;
|
|
252
|
+
evDragEvent = ev;
|
|
253
|
+
window.addEventListener("pointermove", onEvWindowPointerMove);
|
|
254
|
+
window.addEventListener("pointerup", onEvWindowPointerUp, { once: true });
|
|
255
|
+
window.addEventListener("pointercancel", onEvWindowPointerCancel, { once: true });
|
|
256
|
+
}
|
|
257
|
+
function onEvWindowPointerMove(e) {
|
|
258
|
+
const ev = evDragEvent;
|
|
259
|
+
if (!drag || !ev || evDragId !== ev.id) return;
|
|
260
|
+
const dx = e.clientX - evDragStartX;
|
|
261
|
+
const dy = e.clientY - evDragStartY;
|
|
262
|
+
if (!evDragStarted && Math.abs(dx) + Math.abs(dy) < DRAG_THRESHOLD) return;
|
|
263
|
+
if (!evDragStarted) {
|
|
264
|
+
evDragStarted = true;
|
|
265
|
+
evDragging = true;
|
|
266
|
+
drag.beginMove(ev.id, ev.start, ev.end);
|
|
267
|
+
}
|
|
268
|
+
const cellW = getCellWidth();
|
|
269
|
+
const rowH = getRowHeight();
|
|
270
|
+
const dayOffset = Math.round(dx / cellW);
|
|
271
|
+
const weekOffset = Math.round(dy / rowH);
|
|
272
|
+
const deltaMs = (dayOffset + weekOffset * 7) * DAY_MS;
|
|
273
|
+
drag.updatePointer(
|
|
274
|
+
new Date(ev.start.getTime() + deltaMs),
|
|
275
|
+
new Date(ev.end.getTime() + deltaMs)
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
function cleanupEvDrag() {
|
|
279
|
+
window.removeEventListener("pointermove", onEvWindowPointerMove);
|
|
280
|
+
window.removeEventListener("pointerup", onEvWindowPointerUp);
|
|
281
|
+
window.removeEventListener("pointercancel", onEvWindowPointerCancel);
|
|
282
|
+
evDragStarted = false;
|
|
283
|
+
evDragging = false;
|
|
284
|
+
evDragId = null;
|
|
285
|
+
evDragEvent = null;
|
|
286
|
+
}
|
|
287
|
+
function onEvWindowPointerUp() {
|
|
288
|
+
if (!drag) {
|
|
289
|
+
cleanupEvDrag();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (!evDragStarted) {
|
|
293
|
+
if (evDragEvent) oneventclick?.(evDragEvent);
|
|
294
|
+
} else {
|
|
295
|
+
commitDragCtx?.();
|
|
296
|
+
}
|
|
297
|
+
cleanupEvDrag();
|
|
298
|
+
}
|
|
299
|
+
function onEvWindowPointerCancel() {
|
|
300
|
+
if (drag && evDragStarted) drag.cancel();
|
|
301
|
+
cleanupEvDrag();
|
|
302
|
+
}
|
|
418
303
|
</script>
|
|
419
304
|
|
|
420
305
|
{#snippet allDaySegmentContent(seg: DaySegment)}
|
|
@@ -430,11 +315,13 @@
|
|
|
430
315
|
{/snippet}
|
|
431
316
|
|
|
432
317
|
{#snippet timedEventContent(ev: TimelineEvent)}
|
|
433
|
-
<
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
318
|
+
<EventContent event={ev}>
|
|
319
|
+
<span class="wg-ev-time">{fmtAmPm(ev.start)}</span>
|
|
320
|
+
<span class="wg-ev-title">{ev.title}</span>
|
|
321
|
+
{#if ev.location}
|
|
322
|
+
<span class="wg-ev-loc">{ev.location}</span>
|
|
323
|
+
{/if}
|
|
324
|
+
</EventContent>
|
|
438
325
|
{/snippet}
|
|
439
326
|
|
|
440
327
|
<div class="wg" class:wg--auto={autoHeight} style={style || undefined} style:height={autoHeight ? undefined : (height ? `${height}px` : '100%')}>
|