@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
|
@@ -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'}
|
|
@@ -642,6 +650,7 @@
|
|
|
642
650
|
style:--ev-color={p.ev.color ?? 'var(--dt-accent)'}
|
|
643
651
|
role="button"
|
|
644
652
|
tabindex="0"
|
|
653
|
+
title={p.ev.title}
|
|
645
654
|
aria-label="{p.ev.title}{p.ev.status === 'cancelled' ? ' (cancelled)' : ''}{p.ev.status === 'tentative' ? ' (tentative)' : ''}{p.ev.status === 'full' ? ' (full)' : ''}{p.ev.status === 'limited' ? ' (limited)' : ''}{p.isCurrent ? ` (${L.inProgress})` : ''}{p.isNext ? ` (${L.upNext})` : ''}"
|
|
646
655
|
onpointerdown={(e) => onEventPointerDown(e, p.ev)}
|
|
647
656
|
onpointerenter={() => oneventhover?.(p.ev)}
|
|
@@ -653,6 +662,7 @@
|
|
|
653
662
|
{:else if p.isNext}
|
|
654
663
|
<span class="fs-ev-next-badge" aria-hidden="true">{L.upNext}</span>
|
|
655
664
|
{/if}
|
|
665
|
+
<EventContent event={p.ev}>
|
|
656
666
|
{#if p.fit.time}
|
|
657
667
|
<span class="fs-ev-time">{fmtTime(p.ev.start, locale)} – {fmtTime(p.ev.end, locale)}</span>
|
|
658
668
|
{/if}
|
|
@@ -670,9 +680,41 @@
|
|
|
670
680
|
{/each}
|
|
671
681
|
</span>
|
|
672
682
|
{/if}
|
|
683
|
+
</EventContent>
|
|
673
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}
|
|
674
697
|
</div>
|
|
675
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}
|
|
676
718
|
</div>
|
|
677
719
|
</div>
|
|
678
720
|
|
|
@@ -975,8 +1017,7 @@
|
|
|
975
1017
|
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ev-color) 20%, transparent);
|
|
976
1018
|
}
|
|
977
1019
|
.fs-event--next {
|
|
978
|
-
|
|
979
|
-
border: 1px dashed color-mix(in srgb, var(--ev-color) 35%, transparent);
|
|
1020
|
+
border-color: color-mix(in srgb, var(--ev-color) 75%, transparent);
|
|
980
1021
|
}
|
|
981
1022
|
.fs-event--dragging {
|
|
982
1023
|
opacity: 0.85;
|
|
@@ -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,
|
|
@@ -1053,7 +1143,13 @@
|
|
|
1053
1143
|
color: var(--dt-text, rgba(0, 0, 0, 0.87));
|
|
1054
1144
|
overflow: hidden;
|
|
1055
1145
|
text-overflow: ellipsis;
|
|
1056
|
-
|
|
1146
|
+
/* In vertical writing mode line boxes stack as columns — allow a
|
|
1147
|
+
second column before truncating so overlapping (short) cards keep
|
|
1148
|
+
readable names. Full name is in the title tooltip. */
|
|
1149
|
+
display: -webkit-box;
|
|
1150
|
+
-webkit-box-orient: vertical;
|
|
1151
|
+
-webkit-line-clamp: 2;
|
|
1152
|
+
white-space: normal;
|
|
1057
1153
|
max-height: 100%;
|
|
1058
1154
|
flex-shrink: 0;
|
|
1059
1155
|
}
|