@nomideusz/svelte-calendar 0.7.5 → 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 +636 -546
- 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 +321 -10
- package/widget/widget.js +1944 -1031
|
@@ -5,552 +5,559 @@
|
|
|
5
5
|
The now-line tracks the current time. Drag to scroll through days.
|
|
6
6
|
Events are positioned as horizontal cards with overlap support.
|
|
7
7
|
-->
|
|
8
|
-
<script lang="ts">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
8
|
+
<script lang="ts">import { onMount, tick, untrack } from "svelte";
|
|
9
|
+
import { useCalendarContext } from "../shared/context.svelte.js";
|
|
10
|
+
import EventContent from "../shared/EventContent.svelte";
|
|
11
|
+
import { fly } from "svelte/transition";
|
|
12
|
+
import { createClock } from "../../core/clock.svelte.js";
|
|
13
|
+
import { createTextMeasure } from "../../core/measure.js";
|
|
14
|
+
import { DAY_MS, HOUR_MS, sod } from "../../core/time.js";
|
|
15
|
+
import { isAllDay, isMultiDay, segmentForDay } from "../../core/time.js";
|
|
16
|
+
import { fmtH, fmtTime, weekdayShort } from "../../core/locale.js";
|
|
17
|
+
import { getLabels } from "../../core/locale.js";
|
|
18
|
+
const L = $derived(getLabels());
|
|
19
|
+
let {
|
|
20
|
+
height = 520,
|
|
21
|
+
events = [],
|
|
22
|
+
style = "",
|
|
23
|
+
locale,
|
|
24
|
+
focusDate,
|
|
25
|
+
oneventclick,
|
|
26
|
+
oneventcreate,
|
|
27
|
+
selectedEventId = null,
|
|
28
|
+
readOnly = false,
|
|
29
|
+
visibleHours
|
|
30
|
+
} = $props();
|
|
31
|
+
const ctx = useCalendarContext();
|
|
32
|
+
const clock = createClock();
|
|
33
|
+
const drag = $derived(ctx.drag);
|
|
34
|
+
const commitDragCtx = $derived(ctx.commitDrag);
|
|
35
|
+
const viewState = $derived(ctx.viewState);
|
|
36
|
+
const loadRangeCtx = $derived(ctx.loadRange);
|
|
37
|
+
const blockedSlots = $derived(ctx.blockedSlots);
|
|
38
|
+
const dayHeaderSnippet = $derived(ctx.dayHeaderSnippet);
|
|
39
|
+
const minDuration = $derived(ctx.minDuration);
|
|
40
|
+
const autoHeight = $derived(ctx.autoHeight);
|
|
41
|
+
const oneventhover = $derived(ctx.oneventhover);
|
|
42
|
+
const disabledSet = $derived(ctx.disabledSet);
|
|
43
|
+
const SNAP_MS = $derived(ctx.snapInterval * 6e4);
|
|
44
|
+
let following = $state(true);
|
|
45
|
+
let scrollDragging = $state(false);
|
|
46
|
+
let wasDragging = false;
|
|
47
|
+
let el;
|
|
48
|
+
let containerW = $state(0);
|
|
49
|
+
let containerH = $state(520);
|
|
50
|
+
let dragStartX = 0;
|
|
51
|
+
let dragScrollStart = 0;
|
|
52
|
+
let rafId = 0;
|
|
53
|
+
const BUFFER_DAYS = 7;
|
|
54
|
+
const EDGE_DAYS = 2;
|
|
55
|
+
const SHIFT_DAYS = 5;
|
|
56
|
+
const _initMs = untrack(() => sod(focusDate?.getTime() ?? Date.now()));
|
|
57
|
+
let internalCenterMs = $state(_initMs);
|
|
58
|
+
let lastExternalMs = _initMs;
|
|
59
|
+
let rebasing = false;
|
|
60
|
+
let visibleDayMs = $state(_initMs);
|
|
61
|
+
const startHour = $derived(visibleHours?.[0] ?? 0);
|
|
62
|
+
const endHour = $derived(visibleHours?.[1] ?? 24);
|
|
63
|
+
const hourCount = $derived(Math.max(1, endHour - startHour));
|
|
64
|
+
const DAY_GAP = 2;
|
|
65
|
+
const count = 1 + 2 * BUFFER_DAYS;
|
|
66
|
+
const origin = $derived(internalCenterMs - BUFFER_DAYS * DAY_MS);
|
|
67
|
+
$effect(() => {
|
|
68
|
+
if (!loadRangeCtx) return;
|
|
69
|
+
const rangeStart = new Date(internalCenterMs - BUFFER_DAYS * DAY_MS);
|
|
70
|
+
const rangeEnd = new Date(internalCenterMs + (BUFFER_DAYS + 1) * DAY_MS);
|
|
71
|
+
loadRangeCtx.set({ start: rangeStart, end: rangeEnd });
|
|
72
|
+
return () => loadRangeCtx.set(null);
|
|
73
|
+
});
|
|
74
|
+
const MIN_HOUR_W = 60;
|
|
75
|
+
const hourWidth = $derived(containerW > 0 ? Math.max(MIN_HOUR_W, containerW / hourCount) : 110);
|
|
76
|
+
const dayWidth = $derived(hourCount * hourWidth);
|
|
77
|
+
const totalWidth = $derived(count * (dayWidth + DAY_GAP));
|
|
78
|
+
const days = $derived.by(() => {
|
|
79
|
+
const result = [];
|
|
80
|
+
for (let i = 0; i < count; i++) {
|
|
81
|
+
const ms = origin + i * DAY_MS;
|
|
82
|
+
result.push({
|
|
83
|
+
ms,
|
|
84
|
+
today: ms === clock.today,
|
|
85
|
+
past: ms < clock.today,
|
|
86
|
+
x: i * (dayWidth + DAY_GAP)
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
});
|
|
91
|
+
function timeToPx(ms) {
|
|
92
|
+
const elapsed = ms - origin;
|
|
93
|
+
const dayIndex = Math.floor(elapsed / DAY_MS);
|
|
94
|
+
const hourInDay = (elapsed - dayIndex * DAY_MS) / HOUR_MS;
|
|
95
|
+
const hourOffset = hourInDay - startHour;
|
|
96
|
+
return dayIndex * (dayWidth + DAY_GAP) + hourOffset * hourWidth;
|
|
97
|
+
}
|
|
98
|
+
function pxToTime(px) {
|
|
99
|
+
const dayStride = dayWidth + DAY_GAP;
|
|
100
|
+
const dayIndex = Math.max(0, Math.min(count - 1, Math.floor(px / dayStride)));
|
|
101
|
+
const localPx = px - dayIndex * dayStride;
|
|
102
|
+
const hour = startHour + localPx / hourWidth;
|
|
103
|
+
return origin + dayIndex * DAY_MS + hour * HOUR_MS;
|
|
104
|
+
}
|
|
105
|
+
const nowPx = $derived(timeToPx(clock.tick));
|
|
106
|
+
const timedEvents = $derived(events.filter((ev) => !isAllDay(ev) && !isMultiDay(ev)));
|
|
107
|
+
const allDayEvents = $derived.by(() => {
|
|
108
|
+
const segs = [];
|
|
109
|
+
for (const ev of events) {
|
|
110
|
+
if (!isAllDay(ev) && !isMultiDay(ev)) continue;
|
|
111
|
+
const seg = segmentForDay(ev, visibleDayMs);
|
|
112
|
+
if (seg) segs.push(seg);
|
|
113
|
+
}
|
|
114
|
+
return segs;
|
|
115
|
+
});
|
|
116
|
+
const CONTENT_TOP = 56;
|
|
117
|
+
const ALLDAY_H = 24;
|
|
118
|
+
const contentTop = $derived(CONTENT_TOP + (allDayEvents.length > 0 ? ALLDAY_H + 4 : 0));
|
|
119
|
+
const EVENT_GAP = 5;
|
|
120
|
+
const MIN_EVENT_H = 32;
|
|
121
|
+
const measure = createTextMeasure({
|
|
122
|
+
titleFont: "600 13px system-ui, sans-serif",
|
|
123
|
+
secondaryFont: "400 10px system-ui, sans-serif",
|
|
124
|
+
tagFont: "500 8px system-ui, sans-serif",
|
|
125
|
+
titleLineHeight: 16,
|
|
126
|
+
secondaryLineHeight: 13,
|
|
127
|
+
contentGap: 6
|
|
128
|
+
});
|
|
129
|
+
const positionedEvents = $derived.by(() => {
|
|
130
|
+
const now = clock.tick;
|
|
131
|
+
const dragP = drag?.active && (drag.mode === "move" || drag.mode === "resize-start" || drag.mode === "resize-end") ? drag.payload : null;
|
|
132
|
+
const staticEvents = [];
|
|
133
|
+
let draggedEv = null;
|
|
134
|
+
for (const ev of timedEvents) {
|
|
135
|
+
if (dragP?.eventId === ev.id) draggedEv = ev;
|
|
136
|
+
else staticEvents.push(ev);
|
|
137
|
+
}
|
|
138
|
+
const sorted = [...staticEvents].sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
139
|
+
const todayStart = sod(now);
|
|
140
|
+
const todayEnd = todayStart + DAY_MS;
|
|
141
|
+
let nextEventId = null;
|
|
142
|
+
for (const ev of sorted) {
|
|
143
|
+
const s = ev.start.getTime();
|
|
144
|
+
const e = ev.end.getTime();
|
|
145
|
+
if (s >= todayStart && s < todayEnd && s > now && !(s <= now && e > now)) {
|
|
146
|
+
nextEventId = ev.id;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const infos = sorted.map((ev) => {
|
|
151
|
+
const s = ev.start.getTime();
|
|
152
|
+
const e = ev.end.getTime();
|
|
153
|
+
const x = timeToPx(s);
|
|
154
|
+
const xEnd = timeToPx(e);
|
|
155
|
+
return {
|
|
156
|
+
ev,
|
|
157
|
+
x,
|
|
158
|
+
width: Math.max(xEnd - x, 28),
|
|
159
|
+
row: 0,
|
|
160
|
+
groupMaxRow: 1,
|
|
161
|
+
isCurrent: s <= now && e > now,
|
|
162
|
+
isNext: ev.id === nextEventId,
|
|
163
|
+
isDragged: false,
|
|
164
|
+
startMs: s,
|
|
165
|
+
endMs: e
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
const par = infos.map((_, i) => i);
|
|
169
|
+
function find(i) {
|
|
170
|
+
while (par[i] !== i) {
|
|
171
|
+
par[i] = par[par[i]];
|
|
172
|
+
i = par[i];
|
|
173
|
+
}
|
|
174
|
+
return i;
|
|
175
|
+
}
|
|
176
|
+
for (let i = 0; i < infos.length; i++) {
|
|
177
|
+
for (let j = i + 1; j < infos.length; j++) {
|
|
178
|
+
if (infos[j].startMs < infos[i].endMs) par[find(i)] = find(j);
|
|
179
|
+
else break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const groups = /* @__PURE__ */ new Map();
|
|
183
|
+
for (let i = 0; i < infos.length; i++) {
|
|
184
|
+
const root = find(i);
|
|
185
|
+
if (!groups.has(root)) groups.set(root, []);
|
|
186
|
+
groups.get(root).push(i);
|
|
187
|
+
}
|
|
188
|
+
for (const [, indices] of groups) {
|
|
189
|
+
const rows = [];
|
|
190
|
+
for (const idx of indices) {
|
|
191
|
+
const inf = infos[idx];
|
|
192
|
+
let row = 0;
|
|
193
|
+
for (let r = 0; r < rows.length; r++) {
|
|
194
|
+
if (rows[r] <= inf.startMs) {
|
|
195
|
+
row = r;
|
|
196
|
+
rows[r] = inf.endMs;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
row = r + 1;
|
|
200
|
+
}
|
|
201
|
+
if (row >= rows.length) rows.push(inf.endMs);
|
|
202
|
+
infos[idx].row = row;
|
|
203
|
+
}
|
|
204
|
+
for (const idx of indices) infos[idx].groupMaxRow = rows.length;
|
|
205
|
+
}
|
|
206
|
+
const availH = containerH - contentTop - 8;
|
|
207
|
+
const result = infos.map(({ startMs: _s, endMs: _e, ...info }) => {
|
|
208
|
+
const laneH = Math.max(MIN_EVENT_H, availH / info.groupMaxRow - EVENT_GAP);
|
|
209
|
+
const topPx = contentTop + info.row * (availH / info.groupMaxRow);
|
|
210
|
+
const fit = measure.fitContent({
|
|
211
|
+
title: info.ev.title,
|
|
212
|
+
subtitle: info.ev.subtitle,
|
|
213
|
+
location: info.ev.location,
|
|
214
|
+
time: `${fmtTime(info.ev.start, locale)} \u2013 ${fmtTime(info.ev.end, locale)}`,
|
|
215
|
+
tags: info.ev.tags,
|
|
216
|
+
maxWidth: laneH - 16,
|
|
217
|
+
maxHeight: info.width - 16
|
|
218
|
+
});
|
|
219
|
+
return { ...info, topPx, heightPx: laneH, isNext: info.isNext, fit };
|
|
220
|
+
});
|
|
221
|
+
if (draggedEv && dragP) {
|
|
222
|
+
const x = timeToPx(dragP.start.getTime());
|
|
223
|
+
const xEnd = timeToPx(dragP.end.getTime());
|
|
224
|
+
const dragH = Math.max(MIN_EVENT_H, availH - EVENT_GAP);
|
|
225
|
+
const dragW = Math.max(xEnd - x, 28);
|
|
226
|
+
result.push({
|
|
227
|
+
ev: draggedEv,
|
|
228
|
+
x,
|
|
229
|
+
width: dragW,
|
|
230
|
+
row: 0,
|
|
231
|
+
groupMaxRow: 1,
|
|
232
|
+
topPx: contentTop,
|
|
233
|
+
heightPx: dragH,
|
|
234
|
+
isCurrent: draggedEv.start.getTime() <= now && draggedEv.end.getTime() > now,
|
|
235
|
+
isNext: false,
|
|
236
|
+
isDragged: true,
|
|
237
|
+
fit: measure.fitContent({
|
|
238
|
+
title: draggedEv.title,
|
|
239
|
+
subtitle: draggedEv.subtitle,
|
|
240
|
+
location: draggedEv.location,
|
|
241
|
+
tags: draggedEv.tags,
|
|
242
|
+
maxWidth: dragH - 16,
|
|
243
|
+
maxHeight: dragW - 16
|
|
244
|
+
})
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return result;
|
|
248
|
+
});
|
|
249
|
+
$effect(() => {
|
|
250
|
+
const ext = focusDate ? sod(focusDate.getTime()) : clock.today;
|
|
251
|
+
if (ext !== lastExternalMs && !rebasing) {
|
|
252
|
+
lastExternalMs = ext;
|
|
253
|
+
internalCenterMs = ext;
|
|
254
|
+
visibleDayMs = ext;
|
|
255
|
+
following = ext === clock.today;
|
|
256
|
+
tick().then(() => {
|
|
257
|
+
if (el) {
|
|
258
|
+
const focusX = BUFFER_DAYS * (dayWidth + DAY_GAP);
|
|
259
|
+
el.scrollLeft = focusX + dayWidth / 2 - el.clientWidth / 2;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
function checkEdges() {
|
|
265
|
+
if (!el || !viewState || rebasing) return;
|
|
266
|
+
const stride = dayWidth + DAY_GAP;
|
|
267
|
+
const threshold = stride * EDGE_DAYS;
|
|
268
|
+
const maxScroll = el.scrollWidth - el.clientWidth;
|
|
269
|
+
if (el.scrollLeft < threshold) {
|
|
270
|
+
rebase(-1);
|
|
271
|
+
} else if (maxScroll > 0 && maxScroll - el.scrollLeft < threshold) {
|
|
272
|
+
rebase(1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function rebase(direction) {
|
|
276
|
+
if (rebasing) return;
|
|
277
|
+
rebasing = true;
|
|
278
|
+
const shift = SHIFT_DAYS * direction;
|
|
279
|
+
const stride = dayWidth + DAY_GAP;
|
|
280
|
+
const adj = -shift * stride;
|
|
281
|
+
if (el) el.scrollLeft += adj;
|
|
282
|
+
dragScrollStart += adj;
|
|
283
|
+
internalCenterMs += shift * DAY_MS;
|
|
284
|
+
lastExternalMs = internalCenterMs;
|
|
285
|
+
viewState?.setFocusDate(new Date(internalCenterMs));
|
|
286
|
+
tick().then(() => {
|
|
287
|
+
rebasing = false;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
function syncFocusFromScroll() {
|
|
291
|
+
if (!el || !viewState || following || rebasing) return;
|
|
292
|
+
const centerX = el.scrollLeft + el.clientWidth / 2;
|
|
293
|
+
const centerDayMs = sod(pxToTime(centerX));
|
|
294
|
+
visibleDayMs = centerDayMs;
|
|
295
|
+
if (centerDayMs !== lastExternalMs) {
|
|
296
|
+
lastExternalMs = centerDayMs;
|
|
297
|
+
viewState.setFocusDate(new Date(centerDayMs));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
onMount(() => {
|
|
301
|
+
const ro = new ResizeObserver((entries) => {
|
|
302
|
+
for (const entry of entries) {
|
|
303
|
+
containerW = entry.contentRect.width;
|
|
304
|
+
containerH = entry.contentRect.height;
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
ro.observe(el);
|
|
308
|
+
function frame() {
|
|
309
|
+
if (following && el && !scrollDragging) {
|
|
310
|
+
if (internalCenterMs !== clock.today) {
|
|
311
|
+
internalCenterMs = clock.today;
|
|
312
|
+
lastExternalMs = clock.today;
|
|
313
|
+
viewState?.goToday();
|
|
314
|
+
}
|
|
315
|
+
visibleDayMs = clock.today;
|
|
316
|
+
const todayD = days.find((d) => d.today);
|
|
317
|
+
if (todayD) {
|
|
318
|
+
el.scrollLeft = todayD.x + dayWidth / 2 - el.clientWidth / 2;
|
|
319
|
+
}
|
|
320
|
+
} else if (el && !rebasing) {
|
|
321
|
+
checkEdges();
|
|
322
|
+
syncFocusFromScroll();
|
|
323
|
+
}
|
|
324
|
+
rafId = requestAnimationFrame(frame);
|
|
325
|
+
}
|
|
326
|
+
rafId = requestAnimationFrame(frame);
|
|
327
|
+
return () => {
|
|
328
|
+
cancelAnimationFrame(rafId);
|
|
329
|
+
ro.disconnect();
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
const SCROLL_THRESHOLD = 3;
|
|
333
|
+
function onPointerDown(e) {
|
|
334
|
+
if (e.button !== 0) return;
|
|
335
|
+
if (e.target.closest(".fs-event")) return;
|
|
336
|
+
if (readOnly) {
|
|
337
|
+
dragStartX = e.clientX;
|
|
338
|
+
dragScrollStart = el.scrollLeft;
|
|
339
|
+
window.addEventListener("pointermove", onScrollMove);
|
|
340
|
+
window.addEventListener("pointerup", onScrollUp, { once: true });
|
|
341
|
+
window.addEventListener("pointercancel", onScrollUp, { once: true });
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
onCreatePointerDown(e);
|
|
345
|
+
}
|
|
346
|
+
function onScrollMove(e) {
|
|
347
|
+
const dx = e.clientX - dragStartX;
|
|
348
|
+
if (!scrollDragging && Math.abs(dx) >= SCROLL_THRESHOLD) {
|
|
349
|
+
scrollDragging = true;
|
|
350
|
+
wasDragging = true;
|
|
351
|
+
following = false;
|
|
352
|
+
}
|
|
353
|
+
if (scrollDragging) el.scrollLeft = dragScrollStart - dx;
|
|
354
|
+
}
|
|
355
|
+
function onScrollUp() {
|
|
356
|
+
window.removeEventListener("pointermove", onScrollMove);
|
|
357
|
+
window.removeEventListener("pointerup", onScrollUp);
|
|
358
|
+
window.removeEventListener("pointercancel", onScrollUp);
|
|
359
|
+
scrollDragging = false;
|
|
360
|
+
}
|
|
361
|
+
function isBlockedAt(dayMs, hour) {
|
|
362
|
+
if (!blockedSlots?.length) return false;
|
|
363
|
+
const jsDay = new Date(dayMs).getDay();
|
|
364
|
+
const isoDay = jsDay === 0 ? 7 : jsDay;
|
|
365
|
+
return blockedSlots.some((slot) => {
|
|
366
|
+
if (slot.day && slot.day !== isoDay) return false;
|
|
367
|
+
return hour >= slot.start && hour < slot.end;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
function handleTrackClick(e) {
|
|
371
|
+
if (wasDragging) {
|
|
372
|
+
wasDragging = false;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (!oneventcreate || readOnly) return;
|
|
376
|
+
if (e.target.closest(".fs-event")) return;
|
|
377
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
378
|
+
const clickX = e.clientX - rect.left + el.scrollLeft;
|
|
379
|
+
for (const d of days) {
|
|
380
|
+
if (clickX >= d.x && clickX < d.x + dayWidth) {
|
|
381
|
+
if (disabledSet.has(d.ms)) return;
|
|
382
|
+
const frac = (clickX - d.x) / dayWidth;
|
|
383
|
+
const clickHour = startHour + frac * hourCount;
|
|
384
|
+
if (isBlockedAt(d.ms, clickHour)) return;
|
|
385
|
+
const hour = Math.floor(clickHour);
|
|
386
|
+
const durMin = minDuration ? Math.max(60, minDuration) : 60;
|
|
387
|
+
const start = new Date(d.ms + hour * HOUR_MS);
|
|
388
|
+
const end = new Date(start.getTime() + durMin * 6e4);
|
|
389
|
+
oneventcreate({ start, end });
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const CREATE_THRESHOLD = 4;
|
|
395
|
+
let createStartX = 0;
|
|
396
|
+
let createAnchorMs = 0;
|
|
397
|
+
let createStarted = false;
|
|
398
|
+
function trackX(e) {
|
|
399
|
+
return e.clientX - el.getBoundingClientRect().left + el.scrollLeft;
|
|
400
|
+
}
|
|
401
|
+
function onCreatePointerDown(e) {
|
|
402
|
+
if (!drag || !oneventcreate) return;
|
|
403
|
+
createStartX = e.clientX;
|
|
404
|
+
createAnchorMs = pxToTime(trackX(e));
|
|
405
|
+
createStarted = false;
|
|
406
|
+
window.addEventListener("pointermove", onCreateMove);
|
|
407
|
+
window.addEventListener("pointerup", onCreateUp, { once: true });
|
|
408
|
+
window.addEventListener("pointercancel", onCreateCancel, { once: true });
|
|
409
|
+
}
|
|
410
|
+
function onCreateMove(e) {
|
|
411
|
+
if (!drag) return;
|
|
412
|
+
if (!createStarted) {
|
|
413
|
+
if (Math.abs(e.clientX - createStartX) < CREATE_THRESHOLD) return;
|
|
414
|
+
if (disabledSet.has(sod(createAnchorMs))) return;
|
|
415
|
+
createStarted = true;
|
|
416
|
+
wasDragging = true;
|
|
417
|
+
createAnchorMs = Math.floor(createAnchorMs / SNAP_MS) * SNAP_MS;
|
|
418
|
+
drag.beginCreate(new Date(createAnchorMs), new Date(createAnchorMs + SNAP_MS));
|
|
419
|
+
}
|
|
420
|
+
const snapped = Math.round(pxToTime(trackX(e)) / SNAP_MS) * SNAP_MS;
|
|
421
|
+
drag.updatePointer(
|
|
422
|
+
new Date(Math.min(createAnchorMs, snapped)),
|
|
423
|
+
new Date(Math.max(createAnchorMs + SNAP_MS, snapped))
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
function cleanupCreateDrag() {
|
|
427
|
+
window.removeEventListener("pointermove", onCreateMove);
|
|
428
|
+
window.removeEventListener("pointerup", onCreateUp);
|
|
429
|
+
window.removeEventListener("pointercancel", onCreateCancel);
|
|
430
|
+
createStarted = false;
|
|
431
|
+
}
|
|
432
|
+
function onCreateUp() {
|
|
433
|
+
if (drag && createStarted) {
|
|
434
|
+
commitDragCtx?.();
|
|
435
|
+
setTimeout(() => {
|
|
436
|
+
wasDragging = false;
|
|
437
|
+
}, 0);
|
|
438
|
+
}
|
|
439
|
+
cleanupCreateDrag();
|
|
440
|
+
}
|
|
441
|
+
function onCreateCancel() {
|
|
442
|
+
if (drag && createStarted) {
|
|
443
|
+
drag.cancel();
|
|
444
|
+
setTimeout(() => {
|
|
445
|
+
wasDragging = false;
|
|
446
|
+
}, 0);
|
|
447
|
+
}
|
|
448
|
+
cleanupCreateDrag();
|
|
449
|
+
}
|
|
450
|
+
const DRAG_THRESHOLD = 5;
|
|
451
|
+
let evDragStartX = 0;
|
|
452
|
+
let evDragOriginPx = 0;
|
|
453
|
+
let evDragStarted = false;
|
|
454
|
+
let evDragging = $state(false);
|
|
455
|
+
let evDragId = $state(null);
|
|
456
|
+
let evDragEvent = null;
|
|
457
|
+
function onEventPointerDown(e, ev) {
|
|
458
|
+
if (e.button !== 0 || !drag || readOnly || ev.data?.readOnly) return;
|
|
459
|
+
e.stopPropagation();
|
|
460
|
+
evDragStartX = e.clientX;
|
|
461
|
+
evDragOriginPx = timeToPx(ev.start.getTime());
|
|
462
|
+
evDragStarted = false;
|
|
463
|
+
evDragId = ev.id;
|
|
464
|
+
evDragEvent = ev;
|
|
465
|
+
window.addEventListener("pointermove", onEvMove);
|
|
466
|
+
window.addEventListener("pointerup", onEvUp, { once: true });
|
|
467
|
+
window.addEventListener("pointercancel", onEvCancel, { once: true });
|
|
468
|
+
}
|
|
469
|
+
function onEvMove(e) {
|
|
470
|
+
const ev = evDragEvent;
|
|
471
|
+
if (!drag || !ev || evDragId !== ev.id) return;
|
|
472
|
+
const dx = e.clientX - evDragStartX;
|
|
473
|
+
if (!evDragStarted && Math.abs(dx) < DRAG_THRESHOLD) return;
|
|
474
|
+
if (!evDragStarted) {
|
|
475
|
+
evDragStarted = true;
|
|
476
|
+
evDragging = true;
|
|
477
|
+
drag.beginMove(ev.id, ev.start, ev.end);
|
|
478
|
+
}
|
|
479
|
+
const duration = ev.end.getTime() - ev.start.getTime();
|
|
480
|
+
const raw = pxToTime(evDragOriginPx + dx);
|
|
481
|
+
const snapped = Math.round(raw / SNAP_MS) * SNAP_MS;
|
|
482
|
+
drag.updatePointer(new Date(snapped), new Date(snapped + duration));
|
|
483
|
+
}
|
|
484
|
+
function cleanupEvDrag() {
|
|
485
|
+
window.removeEventListener("pointermove", onEvMove);
|
|
486
|
+
window.removeEventListener("pointerup", onEvUp);
|
|
487
|
+
window.removeEventListener("pointercancel", onEvCancel);
|
|
488
|
+
evDragStarted = false;
|
|
489
|
+
evDragging = false;
|
|
490
|
+
evDragId = null;
|
|
491
|
+
evDragEvent = null;
|
|
492
|
+
}
|
|
493
|
+
function onEvUp() {
|
|
494
|
+
if (!drag) {
|
|
495
|
+
cleanupEvDrag();
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (!evDragStarted && evDragEvent) oneventclick?.(evDragEvent);
|
|
499
|
+
else if (evDragStarted) commitDragCtx?.();
|
|
500
|
+
cleanupEvDrag();
|
|
501
|
+
}
|
|
502
|
+
function onEvCancel() {
|
|
503
|
+
if (drag && evDragStarted) drag.cancel();
|
|
504
|
+
cleanupEvDrag();
|
|
505
|
+
}
|
|
506
|
+
let rsStartX = 0;
|
|
507
|
+
let rsStarted = false;
|
|
508
|
+
let rsEdge = "end";
|
|
509
|
+
let rsEvent = null;
|
|
510
|
+
function onResizePointerDown(e, ev, edge) {
|
|
511
|
+
if (e.button !== 0 || !drag || readOnly || ev.data?.readOnly) return;
|
|
512
|
+
e.stopPropagation();
|
|
513
|
+
rsStartX = e.clientX;
|
|
514
|
+
rsStarted = false;
|
|
515
|
+
rsEdge = edge;
|
|
516
|
+
rsEvent = ev;
|
|
517
|
+
window.addEventListener("pointermove", onResizeMove);
|
|
518
|
+
window.addEventListener("pointerup", onResizeUp, { once: true });
|
|
519
|
+
window.addEventListener("pointercancel", onResizeCancel, { once: true });
|
|
520
|
+
}
|
|
521
|
+
function onResizeMove(e) {
|
|
522
|
+
const ev = rsEvent;
|
|
523
|
+
if (!drag || !ev) return;
|
|
524
|
+
if (!rsStarted) {
|
|
525
|
+
if (Math.abs(e.clientX - rsStartX) < CREATE_THRESHOLD) return;
|
|
526
|
+
rsStarted = true;
|
|
527
|
+
drag.beginResize(ev.id, rsEdge, ev.start, ev.end);
|
|
528
|
+
}
|
|
529
|
+
const snapped = Math.round(pxToTime(trackX(e)) / SNAP_MS) * SNAP_MS;
|
|
530
|
+
if (rsEdge === "end") {
|
|
531
|
+
const end = Math.max(snapped, ev.start.getTime() + SNAP_MS);
|
|
532
|
+
drag.updatePointer(ev.start, new Date(end));
|
|
533
|
+
} else {
|
|
534
|
+
const start = Math.min(snapped, ev.end.getTime() - SNAP_MS);
|
|
535
|
+
drag.updatePointer(new Date(start), ev.end);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function cleanupResize() {
|
|
539
|
+
window.removeEventListener("pointermove", onResizeMove);
|
|
540
|
+
window.removeEventListener("pointerup", onResizeUp);
|
|
541
|
+
window.removeEventListener("pointercancel", onResizeCancel);
|
|
542
|
+
rsStarted = false;
|
|
543
|
+
rsEvent = null;
|
|
544
|
+
}
|
|
545
|
+
function onResizeUp() {
|
|
546
|
+
if (drag && rsStarted) {
|
|
547
|
+
wasDragging = true;
|
|
548
|
+
commitDragCtx?.();
|
|
549
|
+
setTimeout(() => {
|
|
550
|
+
wasDragging = false;
|
|
551
|
+
}, 0);
|
|
552
|
+
} else if (rsEvent && !rsStarted) {
|
|
553
|
+
oneventclick?.(rsEvent);
|
|
554
|
+
}
|
|
555
|
+
cleanupResize();
|
|
556
|
+
}
|
|
557
|
+
function onResizeCancel() {
|
|
558
|
+
if (drag && rsStarted) drag.cancel();
|
|
559
|
+
cleanupResize();
|
|
560
|
+
}
|
|
554
561
|
</script>
|
|
555
562
|
|
|
556
563
|
<div class="fs" class:fs--auto={autoHeight} style={style || undefined} style:height={autoHeight ? undefined : (height ? `${height}px` : '100%')} role="region" aria-label={L.dayPlanner}>
|
|
@@ -630,6 +637,7 @@
|
|
|
630
637
|
class:fs-event--current={p.isCurrent}
|
|
631
638
|
class:fs-event--next={p.isNext}
|
|
632
639
|
class:fs-event--dragging={p.isDragged}
|
|
640
|
+
class:fs-event--resizing={p.isDragged && drag?.mode !== 'move'}
|
|
633
641
|
class:fs-event--readonly={p.ev.data?.readOnly}
|
|
634
642
|
class:fs-event--cancelled={p.ev.status === 'cancelled'}
|
|
635
643
|
class:fs-event--tentative={p.ev.status === 'tentative'}
|
|
@@ -654,6 +662,7 @@
|
|
|
654
662
|
{:else if p.isNext}
|
|
655
663
|
<span class="fs-ev-next-badge" aria-hidden="true">{L.upNext}</span>
|
|
656
664
|
{/if}
|
|
665
|
+
<EventContent event={p.ev}>
|
|
657
666
|
{#if p.fit.time}
|
|
658
667
|
<span class="fs-ev-time">{fmtTime(p.ev.start, locale)} – {fmtTime(p.ev.end, locale)}</span>
|
|
659
668
|
{/if}
|
|
@@ -671,9 +680,41 @@
|
|
|
671
680
|
{/each}
|
|
672
681
|
</span>
|
|
673
682
|
{/if}
|
|
683
|
+
</EventContent>
|
|
674
684
|
</div>
|
|
685
|
+
{#if !readOnly && !p.ev.data?.readOnly && !p.isDragged}
|
|
686
|
+
<div
|
|
687
|
+
class="fs-ev-handle fs-ev-handle--start"
|
|
688
|
+
aria-hidden="true"
|
|
689
|
+
onpointerdown={(e) => onResizePointerDown(e, p.ev, 'start')}
|
|
690
|
+
></div>
|
|
691
|
+
<div
|
|
692
|
+
class="fs-ev-handle fs-ev-handle--end"
|
|
693
|
+
aria-hidden="true"
|
|
694
|
+
onpointerdown={(e) => onResizePointerDown(e, p.ev, 'end')}
|
|
695
|
+
></div>
|
|
696
|
+
{/if}
|
|
675
697
|
</div>
|
|
676
698
|
{/each}
|
|
699
|
+
|
|
700
|
+
<!-- Drag-to-create ghost -->
|
|
701
|
+
{#if !readOnly && drag?.active && drag.mode === 'create' && drag.payload}
|
|
702
|
+
{@const gx = timeToPx(drag.payload.start.getTime())}
|
|
703
|
+
{@const gw = Math.max(timeToPx(drag.payload.end.getTime()) - gx, 8)}
|
|
704
|
+
{@const gh = Math.max(MIN_EVENT_H, containerH - contentTop - 8 - EVENT_GAP)}
|
|
705
|
+
<div
|
|
706
|
+
class="fs-create-ghost"
|
|
707
|
+
style:left="{gx}px"
|
|
708
|
+
style:width="{gw}px"
|
|
709
|
+
style:top="{contentTop}px"
|
|
710
|
+
style:height="{gh}px"
|
|
711
|
+
aria-hidden="true"
|
|
712
|
+
>
|
|
713
|
+
<span class="fs-create-ghost-time">
|
|
714
|
+
{fmtTime(drag.payload.start, locale)} – {fmtTime(drag.payload.end, locale)}
|
|
715
|
+
</span>
|
|
716
|
+
</div>
|
|
717
|
+
{/if}
|
|
677
718
|
</div>
|
|
678
719
|
</div>
|
|
679
720
|
|
|
@@ -986,6 +1027,55 @@
|
|
|
986
1027
|
/* fast ease toward the snapped cursor position */
|
|
987
1028
|
transition: left 80ms ease-out, width 80ms ease-out;
|
|
988
1029
|
}
|
|
1030
|
+
.fs-event--resizing {
|
|
1031
|
+
cursor: ew-resize;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/* ─── Resize handles ─────────────────────────────── */
|
|
1035
|
+
.fs-ev-handle {
|
|
1036
|
+
position: absolute;
|
|
1037
|
+
top: 0;
|
|
1038
|
+
bottom: 0;
|
|
1039
|
+
width: 6px;
|
|
1040
|
+
z-index: 2;
|
|
1041
|
+
cursor: ew-resize;
|
|
1042
|
+
touch-action: none;
|
|
1043
|
+
}
|
|
1044
|
+
.fs-ev-handle--start { left: 0; }
|
|
1045
|
+
.fs-ev-handle--end { right: 0; }
|
|
1046
|
+
.fs-ev-handle::after {
|
|
1047
|
+
content: '';
|
|
1048
|
+
position: absolute;
|
|
1049
|
+
top: 20%;
|
|
1050
|
+
bottom: 20%;
|
|
1051
|
+
left: 2px;
|
|
1052
|
+
width: 2px;
|
|
1053
|
+
border-radius: 2px;
|
|
1054
|
+
background: var(--ev-color);
|
|
1055
|
+
opacity: 0;
|
|
1056
|
+
transition: opacity 120ms;
|
|
1057
|
+
}
|
|
1058
|
+
.fs-event:hover .fs-ev-handle::after { opacity: 0.55; }
|
|
1059
|
+
|
|
1060
|
+
/* ─── Drag-to-create ghost ───────────────────────── */
|
|
1061
|
+
.fs-create-ghost {
|
|
1062
|
+
position: absolute;
|
|
1063
|
+
z-index: 40;
|
|
1064
|
+
border-radius: 6px;
|
|
1065
|
+
background: color-mix(in srgb, var(--dt-accent, #2563eb) 14%, transparent);
|
|
1066
|
+
border: 1px dashed color-mix(in srgb, var(--dt-accent, #2563eb) 60%, transparent);
|
|
1067
|
+
display: flex;
|
|
1068
|
+
align-items: flex-start;
|
|
1069
|
+
justify-content: center;
|
|
1070
|
+
overflow: hidden;
|
|
1071
|
+
pointer-events: none;
|
|
1072
|
+
}
|
|
1073
|
+
.fs-create-ghost-time {
|
|
1074
|
+
font: 600 10px/1 var(--dt-mono, ui-monospace, monospace);
|
|
1075
|
+
color: var(--dt-accent, #2563eb);
|
|
1076
|
+
padding: 6px 4px;
|
|
1077
|
+
white-space: nowrap;
|
|
1078
|
+
}
|
|
989
1079
|
|
|
990
1080
|
@media (prefers-reduced-motion: reduce) {
|
|
991
1081
|
.fs-event,
|