@nomideusz/svelte-calendar 0.5.2 → 0.6.3
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 +408 -178
- package/dist/adapters/memory.d.ts +7 -9
- package/dist/adapters/memory.js +8 -23
- package/dist/adapters/recurring.d.ts +8 -6
- package/dist/adapters/recurring.js +28 -30
- package/dist/calendar/Calendar.svelte +776 -356
- package/dist/calendar/Calendar.svelte.d.ts +57 -6
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/core/locale.js +2 -2
- package/dist/core/palette.d.ts +5 -0
- package/dist/core/palette.js +8 -0
- package/dist/core/types.d.ts +14 -0
- package/dist/engine/event-store.svelte.d.ts +0 -17
- package/dist/engine/event-store.svelte.js +30 -17
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/view-state.svelte.d.ts +10 -5
- package/dist/engine/view-state.svelte.js +32 -16
- package/dist/index.d.ts +7 -7
- package/dist/index.js +3 -4
- package/dist/primitives/DayHeader.svelte +103 -103
- package/dist/primitives/EmptySlot.svelte +105 -105
- package/dist/primitives/EventBlock.svelte +312 -312
- package/dist/primitives/NowIndicator.svelte +178 -178
- package/dist/primitives/TimeGutter.svelte +104 -104
- package/dist/theme/auto.d.ts +53 -0
- package/dist/theme/auto.js +534 -0
- package/dist/theme/index.d.ts +3 -1
- package/dist/theme/index.js +3 -1
- package/dist/theme/presets.d.ts +20 -6
- package/dist/theme/presets.js +55 -42
- package/dist/views/agenda/AgendaDay.svelte +1073 -976
- package/dist/views/agenda/AgendaWeek.svelte +934 -775
- package/dist/views/agenda/index.d.ts +0 -2
- package/dist/views/agenda/index.js +0 -2
- package/dist/views/index.d.ts +3 -2
- package/dist/views/index.js +3 -2
- package/dist/views/mobile/Mobile.svelte +17 -0
- package/dist/views/mobile/Mobile.svelte.d.ts +7 -0
- package/dist/views/mobile/MobileDay.svelte +702 -0
- package/dist/views/mobile/MobileDay.svelte.d.ts +20 -0
- package/dist/views/mobile/MobileWeek.svelte +461 -0
- package/dist/views/mobile/MobileWeek.svelte.d.ts +20 -0
- package/dist/views/mobile/index.d.ts +1 -0
- package/dist/views/mobile/index.js +1 -0
- package/dist/views/planner/PlannerDay.svelte +1129 -957
- package/dist/views/planner/PlannerWeek.svelte +949 -840
- package/dist/widget/CalendarWidget.svelte +142 -157
- package/package.json +2 -1
|
@@ -1,840 +1,949 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Planner week mode — Multi-week vertical scroll.
|
|
3
|
-
|
|
4
|
-
Modelled after Hey Calendar's week view:
|
|
5
|
-
• Weeks stack vertically. Scroll up = past, down = future.
|
|
6
|
-
• Day headers in each week row: "MON 23", "TUE 24", with accent pill for today.
|
|
7
|
-
• Month label in left gutter, vertical bottom-to-top.
|
|
8
|
-
• Events are clean horizontal bars with colour fill, "9AM- 10AM Title" inline.
|
|
9
|
-
• Generous whitespace, thin dividers, minimal chrome.
|
|
10
|
-
-->
|
|
11
|
-
<script lang="ts">
|
|
12
|
-
import { getContext, onMount, tick, untrack } from 'svelte';
|
|
13
|
-
import { createClock } from '../../core/clock.svelte.js';
|
|
14
|
-
import type { TimelineEvent } from '../../core/types.js';
|
|
15
|
-
import type { DragState } from '../../engine/drag.svelte.js';
|
|
16
|
-
import type { ViewState } from '../../engine/view-state.svelte.js';
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
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
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// ───
|
|
84
|
-
const
|
|
85
|
-
const
|
|
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
|
-
const
|
|
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
|
-
const
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
const
|
|
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
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
.wg-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
.wg-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
.wg-
|
|
651
|
-
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
border:
|
|
794
|
-
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
.wg-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1
|
+
<!--
|
|
2
|
+
Planner week mode — Multi-week vertical scroll.
|
|
3
|
+
|
|
4
|
+
Modelled after Hey Calendar's week view:
|
|
5
|
+
• Weeks stack vertically. Scroll up = past, down = future.
|
|
6
|
+
• Day headers in each week row: "MON 23", "TUE 24", with accent pill for today.
|
|
7
|
+
• Month label in left gutter, vertical bottom-to-top.
|
|
8
|
+
• Events are clean horizontal bars with colour fill, "9AM- 10AM Title" inline.
|
|
9
|
+
• Generous whitespace, thin dividers, minimal chrome.
|
|
10
|
+
-->
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import { getContext, onMount, tick, untrack, type Snippet } from 'svelte';
|
|
13
|
+
import { createClock } from '../../core/clock.svelte.js';
|
|
14
|
+
import type { TimelineEvent, BlockedSlot } from '../../core/types.js';
|
|
15
|
+
import type { DragState } from '../../engine/drag.svelte.js';
|
|
16
|
+
import type { ViewState } from '../../engine/view-state.svelte.js';
|
|
17
|
+
import { DAY_MS, HOUR_MS, sod } from '../../core/time.js';
|
|
18
|
+
import { startOfWeek as sowFn, fractionalHour, isAllDay, isMultiDay, segmentForDay } from '../../core/time.js';
|
|
19
|
+
import type { DaySegment } from '../../core/time.js';
|
|
20
|
+
import { weekdayShort, monthLong, fmtTime as _fmtTime, getLabels } from '../../core/locale.js';
|
|
21
|
+
|
|
22
|
+
const L = $derived(getLabels());
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
mondayStart?: boolean;
|
|
26
|
+
locale?: string;
|
|
27
|
+
height?: number | null;
|
|
28
|
+
events?: TimelineEvent[];
|
|
29
|
+
style?: string;
|
|
30
|
+
focusDate?: Date;
|
|
31
|
+
oneventclick?: (event: TimelineEvent) => void;
|
|
32
|
+
oneventcreate?: (range: { start: Date; end: Date }) => void;
|
|
33
|
+
selectedEventId?: string | null;
|
|
34
|
+
readOnly?: boolean;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let {
|
|
39
|
+
mondayStart = true,
|
|
40
|
+
locale,
|
|
41
|
+
height = 520,
|
|
42
|
+
events = [],
|
|
43
|
+
style = '',
|
|
44
|
+
focusDate,
|
|
45
|
+
oneventclick,
|
|
46
|
+
oneventcreate,
|
|
47
|
+
selectedEventId = null,
|
|
48
|
+
readOnly = false,
|
|
49
|
+
}: Props = $props();
|
|
50
|
+
|
|
51
|
+
// ── Drag support (available when inside Calendar) ──
|
|
52
|
+
const drag = getContext<DragState>('calendar:drag') as DragState | undefined;
|
|
53
|
+
const commitDragCtx = getContext<() => void>('calendar:commitDrag') as (() => void) | undefined;
|
|
54
|
+
const viewState = getContext<ViewState>('calendar:viewState') as ViewState | undefined;
|
|
55
|
+
const loadRangeCtx = getContext<{ current: { start: Date; end: Date } | null; set: (r: { start: Date; end: Date } | null) => void }>('calendar:loadRange') as { current: { start: Date; end: Date } | null; set: (r: { start: Date; end: Date } | null) => void } | undefined;
|
|
56
|
+
|
|
57
|
+
const clock = createClock();
|
|
58
|
+
const showNavCtx = getContext<{ current: boolean }>('calendar:showNavigation') as { current: boolean } | undefined;
|
|
59
|
+
const equalDaysCtx = getContext<{ current: boolean }>('calendar:equalDays') as { current: boolean } | undefined;
|
|
60
|
+
const showDatesCtx = getContext<{ current: boolean }>('calendar:showDates') as { current: boolean } | undefined;
|
|
61
|
+
const hideDaysCtx = getContext<{ current: number[] | undefined }>('calendar:hideDays') as { current: number[] | undefined } | undefined;
|
|
62
|
+
const showNav = $derived(showNavCtx?.current ?? true);
|
|
63
|
+
const equalDays = $derived(equalDaysCtx?.current ?? false);
|
|
64
|
+
const showDates = $derived(showDatesCtx?.current ?? true);
|
|
65
|
+
const hideDays = $derived(hideDaysCtx?.current);
|
|
66
|
+
|
|
67
|
+
// ── New feature contexts ──
|
|
68
|
+
const blockedSlotsCtx = getContext<{ current: BlockedSlot[] | undefined }>('calendar:blockedSlots') as { current: BlockedSlot[] | undefined } | undefined;
|
|
69
|
+
const dayHeaderSnippetCtx = getContext<{ current: Snippet<[{ date: Date; isToday: boolean; dayName: string }]> | undefined }>('calendar:dayHeaderSnippet') as { current: Snippet<[{ date: Date; isToday: boolean; dayName: string }]> | undefined } | undefined;
|
|
70
|
+
const minDurationCtx = getContext<{ current: number | undefined }>('calendar:minDuration') as { current: number | undefined } | undefined;
|
|
71
|
+
const callbacksCtx = getContext<{ oneventhover?: (event: TimelineEvent) => void }>('calendar:callbacks') as { oneventhover?: (event: TimelineEvent) => void } | undefined;
|
|
72
|
+
const disabledDatesCtx = getContext<{ current: Date[] | undefined }>('calendar:disabledDates') as { current: Date[] | undefined } | undefined;
|
|
73
|
+
const autoHeightCtx = getContext<{ current: boolean }>('calendar:autoHeight') as { current: boolean } | undefined;
|
|
74
|
+
|
|
75
|
+
const blockedSlots = $derived(blockedSlotsCtx?.current);
|
|
76
|
+
const dayHeaderSnippet = $derived(dayHeaderSnippetCtx?.current);
|
|
77
|
+
const minDuration = $derived(minDurationCtx?.current);
|
|
78
|
+
const autoHeight = $derived(autoHeightCtx?.current ?? false);
|
|
79
|
+
const oneventhover = $derived(callbacksCtx?.oneventhover);
|
|
80
|
+
const disabledDates = $derived(disabledDatesCtx?.current);
|
|
81
|
+
const disabledSet = $derived(new Set(disabledDates?.map(d => sod(d.getTime())) ?? []));
|
|
82
|
+
|
|
83
|
+
// ─── Infinite-scroll config ────────────────────────
|
|
84
|
+
const BUFFER_WEEKS = 6; // weeks rendered on each side of anchor
|
|
85
|
+
const EDGE_WEEKS = 2; // rebase when this close to an edge
|
|
86
|
+
const SHIFT_WEEKS = 3; // weeks to shift per rebase
|
|
87
|
+
const MAX_EVENTS_SHOWN = 5;
|
|
88
|
+
|
|
89
|
+
// Internal anchor drives layout; syncs bidirectionally with focusDate.
|
|
90
|
+
const _initMs = untrack(() => sod(focusDate?.getTime() ?? Date.now()));
|
|
91
|
+
let internalFocusMs = $state(_initMs);
|
|
92
|
+
let lastExternalMs = _initMs;
|
|
93
|
+
let rebasing = false;
|
|
94
|
+
|
|
95
|
+
let el: HTMLDivElement;
|
|
96
|
+
let scrolled = $state(false);
|
|
97
|
+
|
|
98
|
+
function scrollCurrentWeekIntoContainer(behavior: ScrollBehavior = 'auto') {
|
|
99
|
+
if (!el) return;
|
|
100
|
+
const current = el.querySelector<HTMLElement>('.wg-week--current');
|
|
101
|
+
if (!current) return;
|
|
102
|
+
const targetTop = current.offsetTop - (el.clientHeight - current.offsetHeight) / 2;
|
|
103
|
+
el.scrollTo({ top: Math.max(0, targetTop), behavior });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Derived ────────────────────────────────────────
|
|
107
|
+
const todayMs = $derived(clock.today);
|
|
108
|
+
const customDays = $derived(viewState?.dayCount ?? 7);
|
|
109
|
+
const anchorPeriodStart = $derived(
|
|
110
|
+
customDays === 7
|
|
111
|
+
? sowFn(internalFocusMs, mondayStart)
|
|
112
|
+
: sod(internalFocusMs)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// ─── Declare load range for entire visible buffer ────────
|
|
116
|
+
// Instead of calling store.load() directly, we tell Calendar
|
|
117
|
+
// what range we need. Calendar's single $effect handles loading.
|
|
118
|
+
$effect(() => {
|
|
119
|
+
if (!loadRangeCtx) return;
|
|
120
|
+
const rangeStart = new Date(anchorPeriodStart - BUFFER_WEEKS * customDays * DAY_MS);
|
|
121
|
+
const rangeEnd = new Date(anchorPeriodStart + (BUFFER_WEEKS + 1) * customDays * DAY_MS);
|
|
122
|
+
loadRangeCtx.set({ start: rangeStart, end: rangeEnd });
|
|
123
|
+
return () => loadRangeCtx.set(null);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ─── Week data ──────────────────────────────────────
|
|
127
|
+
interface WeekRow {
|
|
128
|
+
weekStart: number;
|
|
129
|
+
isCurrent: boolean;
|
|
130
|
+
monthLabel: string | null;
|
|
131
|
+
days: DayCell[];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
interface DayCell {
|
|
135
|
+
ms: number;
|
|
136
|
+
dayNum: number;
|
|
137
|
+
isToday: boolean;
|
|
138
|
+
isPast: boolean;
|
|
139
|
+
isWeekend: boolean;
|
|
140
|
+
isFirstOfMonth: boolean;
|
|
141
|
+
monthLabel: string | null;
|
|
142
|
+
events: TimelineEvent[];
|
|
143
|
+
/** All-day or multi-day event segments for this day */
|
|
144
|
+
allDaySegments: DaySegment[];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const weeks = $derived.by(() => {
|
|
148
|
+
const result: WeekRow[] = [];
|
|
149
|
+
|
|
150
|
+
for (let w = -BUFFER_WEEKS; w <= BUFFER_WEEKS; w++) {
|
|
151
|
+
const periodStart = anchorPeriodStart + w * customDays * DAY_MS;
|
|
152
|
+
const isCurrent = todayMs >= periodStart && todayMs < periodStart + customDays * DAY_MS;
|
|
153
|
+
const days: DayCell[] = [];
|
|
154
|
+
|
|
155
|
+
for (let d = 0; d < customDays; d++) {
|
|
156
|
+
const ms = periodStart + d * DAY_MS;
|
|
157
|
+
const date = new Date(ms);
|
|
158
|
+
const dayNum = date.getDate();
|
|
159
|
+
const dow = date.getDay();
|
|
160
|
+
const isWeekend = dow === 0 || dow === 6;
|
|
161
|
+
const isToday = ms === todayMs;
|
|
162
|
+
const isPast = equalDays ? false : ms < todayMs;
|
|
163
|
+
const isFirstOfMonth = dayNum === 1;
|
|
164
|
+
const monthLabel = (d === 0 || isFirstOfMonth)
|
|
165
|
+
? monthLong(ms, locale).toUpperCase()
|
|
166
|
+
: null;
|
|
167
|
+
|
|
168
|
+
const dayEnd = ms + DAY_MS;
|
|
169
|
+
const dayEventsAll = events
|
|
170
|
+
.filter((ev) => ev.start.getTime() < dayEnd && ev.end.getTime() > ms)
|
|
171
|
+
.sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
172
|
+
|
|
173
|
+
// Separate all-day / multi-day from timed events
|
|
174
|
+
const timedEvents: TimelineEvent[] = [];
|
|
175
|
+
const allDaySegments: DaySegment[] = [];
|
|
176
|
+
for (const ev of dayEventsAll) {
|
|
177
|
+
if (isAllDay(ev) || isMultiDay(ev)) {
|
|
178
|
+
const seg = segmentForDay(ev, ms);
|
|
179
|
+
if (seg) allDaySegments.push(seg);
|
|
180
|
+
} else {
|
|
181
|
+
timedEvents.push(ev);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
days.push({ ms, dayNum, isToday, isPast, isWeekend, isFirstOfMonth, monthLabel, events: timedEvents, allDaySegments });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Month label: show when first day of period is day 1-7 (for 7-day), or first day of period (for custom)
|
|
189
|
+
const startDate = new Date(periodStart);
|
|
190
|
+
const showMonth = customDays === 7 ? startDate.getDate() <= 7 : startDate.getDate() <= customDays;
|
|
191
|
+
const monthLabel = showMonth ? monthLong(periodStart, locale).toUpperCase() : null;
|
|
192
|
+
|
|
193
|
+
result.push({ weekStart: periodStart, isCurrent, monthLabel, days });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Filter hidden days if hideDays is set
|
|
197
|
+
if (hideDays?.length) {
|
|
198
|
+
for (const row of result) {
|
|
199
|
+
row.days = row.days.filter((d) => {
|
|
200
|
+
const isoDay = new Date(d.ms).getDay();
|
|
201
|
+
// Convert JS day (0=Sun) to ISO (7=Sun)
|
|
202
|
+
const iso = isoDay === 0 ? 7 : isoDay;
|
|
203
|
+
return !hideDays.includes(iso);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return result;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// ─── Format helpers ─────────────────────────────────
|
|
212
|
+
function fmtAmPm(d: Date): string {
|
|
213
|
+
return _fmtTime(d, locale);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function fmtNowTime(tick: number): string {
|
|
217
|
+
return _fmtTime(new Date(tick), locale);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ─── Now indicator fraction ─────────────────────────
|
|
221
|
+
const nowFrac = $derived(fractionalHour(clock.tick) / 24);
|
|
222
|
+
|
|
223
|
+
// ─── Scroll to current week on mount ────────────────
|
|
224
|
+
onMount(async () => {
|
|
225
|
+
await tick();
|
|
226
|
+
scrollCurrentWeekIntoContainer();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ─── Infinite-scroll helpers ────────────────────────
|
|
230
|
+
|
|
231
|
+
/** Detect external focusDate changes (from nav arrows). */
|
|
232
|
+
$effect(() => {
|
|
233
|
+
const ext = focusDate ? sod(focusDate.getTime()) : clock.today;
|
|
234
|
+
if (ext !== lastExternalMs && !rebasing) {
|
|
235
|
+
lastExternalMs = ext;
|
|
236
|
+
internalFocusMs = ext;
|
|
237
|
+
tick().then(() => scrollCurrentWeekIntoContainer('smooth'));
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
/** Check if viewport is near top/bottom edge; if so, rebase the window. */
|
|
242
|
+
function checkVerticalEdges() {
|
|
243
|
+
if (!el || !viewState || rebasing) return;
|
|
244
|
+
const rows = el.querySelectorAll<HTMLElement>('[data-week]');
|
|
245
|
+
if (!rows.length) return;
|
|
246
|
+
|
|
247
|
+
const avgH = el.scrollHeight / rows.length;
|
|
248
|
+
const threshold = avgH * EDGE_WEEKS;
|
|
249
|
+
const maxScroll = el.scrollHeight - el.clientHeight;
|
|
250
|
+
|
|
251
|
+
if (el.scrollTop < threshold) {
|
|
252
|
+
rebaseWeeks(-1);
|
|
253
|
+
} else if (maxScroll > 0 && maxScroll - el.scrollTop < threshold) {
|
|
254
|
+
rebaseWeeks(1);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Shift the rendered week window by SHIFT_WEEKS in the given direction. */
|
|
259
|
+
function rebaseWeeks(direction: number) {
|
|
260
|
+
if (rebasing) return;
|
|
261
|
+
rebasing = true;
|
|
262
|
+
|
|
263
|
+
// Save an anchor: first visible week row and its visual offset.
|
|
264
|
+
const rows = el.querySelectorAll<HTMLElement>('[data-week]');
|
|
265
|
+
let anchorWeekMs = 0;
|
|
266
|
+
let anchorTop = 0;
|
|
267
|
+
for (const row of rows) {
|
|
268
|
+
const top = row.offsetTop - el.scrollTop;
|
|
269
|
+
if (top + row.offsetHeight > 0) {
|
|
270
|
+
anchorWeekMs = Number(row.dataset.week);
|
|
271
|
+
anchorTop = top;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const shift = SHIFT_WEEKS * direction;
|
|
277
|
+
internalFocusMs += shift * customDays * DAY_MS;
|
|
278
|
+
lastExternalMs = internalFocusMs;
|
|
279
|
+
viewState?.setFocusDate(new Date(internalFocusMs));
|
|
280
|
+
|
|
281
|
+
tick().then(() => {
|
|
282
|
+
// Restore the anchor week to its previous visual position.
|
|
283
|
+
const anchor = el.querySelector<HTMLElement>(`[data-week="${anchorWeekMs}"]`);
|
|
284
|
+
if (anchor) {
|
|
285
|
+
el.scrollTop = anchor.offsetTop - anchorTop;
|
|
286
|
+
}
|
|
287
|
+
rebasing = false;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Push the currently visible centre week to viewState. */
|
|
292
|
+
function syncFocusFromScroll() {
|
|
293
|
+
if (!el || !viewState || rebasing) return;
|
|
294
|
+
const centerY = el.scrollTop + el.clientHeight / 2;
|
|
295
|
+
const rows = el.querySelectorAll<HTMLElement>('[data-week]');
|
|
296
|
+
for (const row of rows) {
|
|
297
|
+
if (row.offsetTop + row.offsetHeight >= centerY) {
|
|
298
|
+
const ms = Number(row.dataset.week);
|
|
299
|
+
if (ms && ms !== lastExternalMs) {
|
|
300
|
+
lastExternalMs = ms;
|
|
301
|
+
viewState.setFocusDate(new Date(ms));
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function isCurrentWeekVisible(): boolean {
|
|
309
|
+
if (!el) return false;
|
|
310
|
+
const current = el.querySelector<HTMLElement>('.wg-week--current');
|
|
311
|
+
if (!current) return false;
|
|
312
|
+
const top = current.offsetTop - el.scrollTop;
|
|
313
|
+
const bottom = top + current.offsetHeight;
|
|
314
|
+
return bottom > 0 && top < el.clientHeight;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
let suppressScroll = false;
|
|
318
|
+
|
|
319
|
+
function handleUserScroll() {
|
|
320
|
+
if (suppressScroll) return;
|
|
321
|
+
scrolled = !isCurrentWeekVisible();
|
|
322
|
+
if (!rebasing) {
|
|
323
|
+
checkVerticalEdges();
|
|
324
|
+
syncFocusFromScroll();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function jumpToday() {
|
|
329
|
+
internalFocusMs = clock.today;
|
|
330
|
+
lastExternalMs = clock.today;
|
|
331
|
+
viewState?.goToday();
|
|
332
|
+
suppressScroll = true;
|
|
333
|
+
scrolled = false;
|
|
334
|
+
tick().then(() => {
|
|
335
|
+
scrollCurrentWeekIntoContainer('smooth');
|
|
336
|
+
// Keep suppressed until smooth scroll finishes, then recheck
|
|
337
|
+
setTimeout(() => {
|
|
338
|
+
suppressScroll = false;
|
|
339
|
+
scrolled = !isCurrentWeekVisible();
|
|
340
|
+
}, 600);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function handleDayCellClick(ms: number, e: Event) {
|
|
345
|
+
const target = e.target as HTMLElement;
|
|
346
|
+
if (target.closest('.wg-ev')) return;
|
|
347
|
+
if (readOnly || !oneventcreate) return;
|
|
348
|
+
// Check disabled dates
|
|
349
|
+
if (disabledSet.has(ms)) return;
|
|
350
|
+
// Check blocked slots (all-day block check: if entire day is blocked, prevent)
|
|
351
|
+
const durMin = minDuration ? Math.max(60, minDuration) : 60;
|
|
352
|
+
const start = new Date(ms + 9 * HOUR_MS);
|
|
353
|
+
const end = new Date(start.getTime() + durMin * 60_000);
|
|
354
|
+
oneventcreate({ start, end });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ─── Event drag-to-move ───────────────────────────────────────
|
|
358
|
+
const DRAG_THRESHOLD = 8;
|
|
359
|
+
let evDragStartX = 0;
|
|
360
|
+
let evDragStartY = 0;
|
|
361
|
+
let evDragStarted = false;
|
|
362
|
+
let evDragging = $state(false);
|
|
363
|
+
let evDragId = $state<string | null>(null);
|
|
364
|
+
let evDragEvent: TimelineEvent | null = null;
|
|
365
|
+
|
|
366
|
+
function getCellWidth(): number {
|
|
367
|
+
const cell = el?.querySelector('.wg-cell');
|
|
368
|
+
return cell ? cell.getBoundingClientRect().width : 100;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function getRowHeight(): number {
|
|
372
|
+
const row = el?.querySelector('.wg-week');
|
|
373
|
+
return row ? row.getBoundingClientRect().height + 24 : 200; // 24 ≈ vertical margin between weeks
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function onEventPointerDown(e: PointerEvent, ev: TimelineEvent) {
|
|
377
|
+
if (e.button !== 0 || !drag || readOnly) return;
|
|
378
|
+
e.stopPropagation();
|
|
379
|
+
evDragStartX = e.clientX;
|
|
380
|
+
evDragStartY = e.clientY;
|
|
381
|
+
evDragStarted = false;
|
|
382
|
+
evDragId = ev.id;
|
|
383
|
+
evDragEvent = ev;
|
|
384
|
+
|
|
385
|
+
window.addEventListener('pointermove', onEvWindowPointerMove);
|
|
386
|
+
window.addEventListener('pointerup', onEvWindowPointerUp, { once: true });
|
|
387
|
+
window.addEventListener('pointercancel', onEvWindowPointerCancel, { once: true });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function onEvWindowPointerMove(e: PointerEvent) {
|
|
391
|
+
const ev = evDragEvent;
|
|
392
|
+
if (!drag || !ev || evDragId !== ev.id) return;
|
|
393
|
+
const dx = e.clientX - evDragStartX;
|
|
394
|
+
const dy = e.clientY - evDragStartY;
|
|
395
|
+
if (!evDragStarted && Math.abs(dx) + Math.abs(dy) < DRAG_THRESHOLD) return;
|
|
396
|
+
|
|
397
|
+
if (!evDragStarted) {
|
|
398
|
+
evDragStarted = true;
|
|
399
|
+
evDragging = true;
|
|
400
|
+
drag.beginMove(ev.id, ev.start, ev.end);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const cellW = getCellWidth();
|
|
404
|
+
const rowH = getRowHeight();
|
|
405
|
+
const dayOffset = Math.round(dx / cellW);
|
|
406
|
+
const weekOffset = Math.round(dy / rowH);
|
|
407
|
+
const deltaMs = (dayOffset + weekOffset * 7) * DAY_MS;
|
|
408
|
+
drag.updatePointer(
|
|
409
|
+
new Date(ev.start.getTime() + deltaMs),
|
|
410
|
+
new Date(ev.end.getTime() + deltaMs),
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function cleanupEvDrag() {
|
|
415
|
+
window.removeEventListener('pointermove', onEvWindowPointerMove);
|
|
416
|
+
window.removeEventListener('pointerup', onEvWindowPointerUp);
|
|
417
|
+
window.removeEventListener('pointercancel', onEvWindowPointerCancel);
|
|
418
|
+
evDragStarted = false;
|
|
419
|
+
evDragging = false;
|
|
420
|
+
evDragId = null;
|
|
421
|
+
evDragEvent = null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function onEvWindowPointerUp() {
|
|
425
|
+
if (!drag) { cleanupEvDrag(); return; }
|
|
426
|
+
if (!evDragStarted) {
|
|
427
|
+
if (evDragEvent) oneventclick?.(evDragEvent);
|
|
428
|
+
} else {
|
|
429
|
+
commitDragCtx?.();
|
|
430
|
+
}
|
|
431
|
+
cleanupEvDrag();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function onEvWindowPointerCancel() {
|
|
435
|
+
if (drag && evDragStarted) drag.cancel();
|
|
436
|
+
cleanupEvDrag();
|
|
437
|
+
}
|
|
438
|
+
</script>
|
|
439
|
+
|
|
440
|
+
<div class="wg" class:wg--auto={autoHeight} style={style || undefined} style:height={autoHeight ? undefined : (height ? `${height}px` : '100%')}>
|
|
441
|
+
<div
|
|
442
|
+
class="wg-body"
|
|
443
|
+
bind:this={el}
|
|
444
|
+
onscroll={handleUserScroll}
|
|
445
|
+
role="grid"
|
|
446
|
+
aria-label={L.multiWeekGrid}
|
|
447
|
+
>
|
|
448
|
+
{#each weeks as week (week.weekStart)}
|
|
449
|
+
<div class="wg-week" class:wg-week--current={week.isCurrent} data-week={week.weekStart}>
|
|
450
|
+
<div class="wg-week-body">
|
|
451
|
+
<!-- Day columns (header inside each cell) -->
|
|
452
|
+
<div class="wg-days">
|
|
453
|
+
{#each week.days as day (day.ms)}
|
|
454
|
+
<div
|
|
455
|
+
class="wg-cell"
|
|
456
|
+
class:wg-cell--today={day.isToday}
|
|
457
|
+
class:wg-cell--past={day.isPast}
|
|
458
|
+
class:wg-cell--weekend={day.isWeekend} class:wg-cell--disabled={disabledSet.has(day.ms)} role="gridcell"
|
|
459
|
+
tabindex="0"
|
|
460
|
+
aria-label="{new Date(day.ms).toLocaleDateString(locale ?? 'en-US', { weekday: 'long', month: 'short', day: 'numeric' })}{day.isToday ? ` (${L.today.toLowerCase()})` : ''}, {L.nEvents(day.events.length)}"
|
|
461
|
+
onclick={(e) => handleDayCellClick(day.ms, e)}
|
|
462
|
+
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleDayCellClick(day.ms, e); } }}
|
|
463
|
+
>
|
|
464
|
+
<!-- Day label in top-right corner -->
|
|
465
|
+
<div class="wg-cell-hd" class:wg-cell-hd--today={day.isToday}>
|
|
466
|
+
{#if showDates}
|
|
467
|
+
<span class="wg-day-num" class:wg-day-num--today={day.isToday}>
|
|
468
|
+
{day.dayNum}
|
|
469
|
+
</span>
|
|
470
|
+
{/if}
|
|
471
|
+
<span class="wg-day-wd">{weekdayShort(day.ms, locale)}</span>
|
|
472
|
+
</div>
|
|
473
|
+
|
|
474
|
+
{#if day.monthLabel && showDates}
|
|
475
|
+
<span class="wg-cell-month">{day.monthLabel}</span>
|
|
476
|
+
{/if}
|
|
477
|
+
|
|
478
|
+
<!-- Custom day header snippet -->
|
|
479
|
+
{#if dayHeaderSnippet}
|
|
480
|
+
<div class="wg-cell-custom-header">
|
|
481
|
+
{@render dayHeaderSnippet({ date: new Date(day.ms), isToday: day.isToday, dayName: weekdayShort(day.ms, locale) })}
|
|
482
|
+
</div>
|
|
483
|
+
{/if}
|
|
484
|
+
|
|
485
|
+
<!-- Blocked slots indicator -->
|
|
486
|
+
{#if blockedSlots?.length}
|
|
487
|
+
{@const jsDay = new Date(day.ms).getDay()}
|
|
488
|
+
{@const isoDay = jsDay === 0 ? 7 : jsDay}
|
|
489
|
+
{#each blockedSlots as slot}
|
|
490
|
+
{#if !slot.day || slot.day === isoDay}
|
|
491
|
+
<div class="wg-blocked" aria-label={slot.label || 'Unavailable'}>
|
|
492
|
+
{#if slot.label}
|
|
493
|
+
<span class="wg-blocked-label">{slot.label}</span>
|
|
494
|
+
{/if}
|
|
495
|
+
</div>
|
|
496
|
+
{/if}
|
|
497
|
+
{/each}
|
|
498
|
+
{/if}
|
|
499
|
+
|
|
500
|
+
<!-- All-day / multi-day events -->
|
|
501
|
+
{#if day.allDaySegments.length > 0}
|
|
502
|
+
<div class="wg-allday">
|
|
503
|
+
{#each day.allDaySegments as seg (seg.ev.id)}
|
|
504
|
+
<div
|
|
505
|
+
class="wg-ad"
|
|
506
|
+
class:wg-ad--start={seg.isStart}
|
|
507
|
+
class:wg-ad--end={seg.isEnd}
|
|
508
|
+
class:wg-ad--mid={!seg.isStart && !seg.isEnd}
|
|
509
|
+
class:wg-ad--selected={selectedEventId === seg.ev.id}
|
|
510
|
+
style:--ev-color={seg.ev.color ?? 'var(--dt-accent)'}
|
|
511
|
+
role="button"
|
|
512
|
+
tabindex="0"
|
|
513
|
+
aria-label="{seg.ev.title}{seg.totalDays > 1 ? `, ${L.dayNOfTotal(seg.dayIndex, seg.totalDays)}` : `, ${L.allDay}`}"
|
|
514
|
+
onpointerdown={(e) => onEventPointerDown(e, seg.ev)}
|
|
515
|
+
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); oneventclick?.(seg.ev); } }}
|
|
516
|
+
>
|
|
517
|
+
{#if seg.isStart}
|
|
518
|
+
<span class="wg-ad-title">{seg.ev.title}</span>
|
|
519
|
+
{:else}
|
|
520
|
+
<span class="wg-ad-cont" aria-hidden="true">◂</span>
|
|
521
|
+
<span class="wg-ad-title">{seg.ev.title}</span>
|
|
522
|
+
{/if}
|
|
523
|
+
{#if !seg.isEnd && seg.totalDays > 1}
|
|
524
|
+
<span class="wg-ad-arrow" aria-hidden="true">▸</span>
|
|
525
|
+
{/if}
|
|
526
|
+
</div>
|
|
527
|
+
{/each}
|
|
528
|
+
</div>
|
|
529
|
+
{/if}
|
|
530
|
+
|
|
531
|
+
<!-- Timed events -->
|
|
532
|
+
<div class="wg-cell-events">
|
|
533
|
+
{#each day.events.slice(0, MAX_EVENTS_SHOWN) as ev (ev.id)}
|
|
534
|
+
<div
|
|
535
|
+
class="wg-ev"
|
|
536
|
+
class:wg-ev--selected={selectedEventId === ev.id}
|
|
537
|
+
class:wg-ev--current={ev.start.getTime() <= clock.tick && ev.end.getTime() > clock.tick}
|
|
538
|
+
class:wg-ev--dragging={evDragging && evDragId === ev.id}
|
|
539
|
+
style:--ev-color={ev.color ?? 'var(--dt-accent)'}
|
|
540
|
+
role="button"
|
|
541
|
+
tabindex="0"
|
|
542
|
+
aria-label="{ev.title}{ev.start.getTime() <= clock.tick && ev.end.getTime() > clock.tick ? ` (${L.inProgress})` : ''}"
|
|
543
|
+
onpointerdown={(e) => onEventPointerDown(e, ev)} onpointerenter={() => oneventhover?.(ev)}
|
|
544
|
+
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); oneventclick?.(ev); } }}
|
|
545
|
+
>
|
|
546
|
+
<span class="wg-ev-time">{fmtAmPm(ev.start)}</span>
|
|
547
|
+
<span class="wg-ev-title">{ev.title}</span>
|
|
548
|
+
</div>
|
|
549
|
+
{/each}
|
|
550
|
+
{#if day.events.length > MAX_EVENTS_SHOWN}
|
|
551
|
+
<div class="wg-ev-more">{L.nMore(day.events.length - MAX_EVENTS_SHOWN)}</div>
|
|
552
|
+
{/if}
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
{/each}
|
|
556
|
+
</div>
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
{/each}
|
|
560
|
+
</div>
|
|
561
|
+
|
|
562
|
+
{#if showNav && scrolled}
|
|
563
|
+
<nav class="wg-nav" aria-label={L.weekNavigation}>
|
|
564
|
+
<button
|
|
565
|
+
class="wg-nav-pill"
|
|
566
|
+
onclick={jumpToday}
|
|
567
|
+
aria-label={L.goToToday}
|
|
568
|
+
>
|
|
569
|
+
{L.today}
|
|
570
|
+
</button>
|
|
571
|
+
</nav>
|
|
572
|
+
{/if}
|
|
573
|
+
</div>
|
|
574
|
+
|
|
575
|
+
<style>
|
|
576
|
+
/* ─── Container ──────────────────────────────────── */
|
|
577
|
+
.wg {
|
|
578
|
+
position: relative;
|
|
579
|
+
overflow: hidden;
|
|
580
|
+
display: flex;
|
|
581
|
+
flex-direction: column;
|
|
582
|
+
user-select: none;
|
|
583
|
+
font-variant-numeric: tabular-nums;
|
|
584
|
+
}
|
|
585
|
+
.wg--auto { overflow: visible; }
|
|
586
|
+
|
|
587
|
+
/* ─── Scrollable body ────────────────────────────── */
|
|
588
|
+
.wg-body {
|
|
589
|
+
flex: 1;
|
|
590
|
+
overflow-y: auto;
|
|
591
|
+
overflow-x: hidden;
|
|
592
|
+
scrollbar-width: thin;
|
|
593
|
+
scrollbar-color: var(--dt-scrollbar, rgba(0, 0, 0, 0.08)) transparent;
|
|
594
|
+
}
|
|
595
|
+
.wg--auto .wg-body { overflow-y: visible; }
|
|
596
|
+
|
|
597
|
+
.wg-body::-webkit-scrollbar { width: 4px; }
|
|
598
|
+
.wg-body::-webkit-scrollbar-thumb {
|
|
599
|
+
background: var(--dt-scrollbar, rgba(0, 0, 0, 0.1));
|
|
600
|
+
border-radius: 4px;
|
|
601
|
+
}
|
|
602
|
+
.wg-body::-webkit-scrollbar-track { background: transparent; }
|
|
603
|
+
|
|
604
|
+
/* ─── Week row ───────────────────────────────────── */
|
|
605
|
+
.wg-week {
|
|
606
|
+
display: flex;
|
|
607
|
+
border-radius: 10px;
|
|
608
|
+
margin: 12px 8px;
|
|
609
|
+
border: 1.5px solid var(--dt-border, rgba(0, 0, 0, 0.08));
|
|
610
|
+
overflow: hidden;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.wg-week--current {
|
|
614
|
+
background: var(--dt-today-bg, rgba(239, 68, 68, 0.02));
|
|
615
|
+
border: 2.5px solid var(--dt-accent, #ef4444);
|
|
616
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--dt-accent, #ef4444) 15%, transparent);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/* ─── Week body ──────────────────────────────────── */
|
|
620
|
+
.wg-week-body {
|
|
621
|
+
flex: 1;
|
|
622
|
+
min-width: 0;
|
|
623
|
+
display: flex;
|
|
624
|
+
flex-direction: column;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/* ─── Day columns ────────────────────────────────── */
|
|
628
|
+
.wg-days {
|
|
629
|
+
display: flex;
|
|
630
|
+
flex: 1;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.wg-cell {
|
|
634
|
+
flex: 1;
|
|
635
|
+
position: relative;
|
|
636
|
+
min-height: 170px;
|
|
637
|
+
padding: 4px 4px 8px;
|
|
638
|
+
border-right: 1px solid var(--dt-border, rgba(0, 0, 0, 0.06));
|
|
639
|
+
cursor: pointer;
|
|
640
|
+
transition: background 0.15s;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.wg-cell:last-child { border-right: none; }
|
|
644
|
+
.wg-cell:hover { background: var(--dt-hover, rgba(0, 0, 0, 0.015)); }
|
|
645
|
+
|
|
646
|
+
.wg-cell--today { background: var(--dt-today-bg, rgba(239, 68, 68, 0.03)); }
|
|
647
|
+
.wg-cell--today:hover { background: rgba(239, 68, 68, 0.05); }
|
|
648
|
+
|
|
649
|
+
/* Dim all cells by default; current week overrides to full brightness */
|
|
650
|
+
.wg-cell { opacity: 0.55; }
|
|
651
|
+
.wg-cell:hover { opacity: 0.75; }
|
|
652
|
+
.wg-week--current .wg-cell { opacity: 1; }
|
|
653
|
+
.wg-week--current .wg-cell--past { opacity: 0.8; }
|
|
654
|
+
.wg-week--current .wg-cell--past:hover { opacity: 0.9; }
|
|
655
|
+
|
|
656
|
+
/* equalDays: when no cells are marked past, all are full brightness */
|
|
657
|
+
|
|
658
|
+
.wg-cell--weekend { background: var(--dt-weekend-bg, rgba(0, 0, 0, 0.012)); }
|
|
659
|
+
|
|
660
|
+
/* ─── Disabled cell ──────────────────────────────── */
|
|
661
|
+
.wg-cell--disabled {
|
|
662
|
+
opacity: 0.35;
|
|
663
|
+
pointer-events: none;
|
|
664
|
+
background: repeating-linear-gradient(
|
|
665
|
+
45deg,
|
|
666
|
+
transparent,
|
|
667
|
+
transparent 6px,
|
|
668
|
+
var(--dt-border, rgba(0, 0, 0, 0.06)) 6px,
|
|
669
|
+
var(--dt-border, rgba(0, 0, 0, 0.06)) 7px
|
|
670
|
+
) !important;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/* ─── Blocked slot indicator ─────────────────────── */
|
|
674
|
+
.wg-blocked {
|
|
675
|
+
display: flex;
|
|
676
|
+
align-items: center;
|
|
677
|
+
gap: 3px;
|
|
678
|
+
padding: 2px 4px;
|
|
679
|
+
border-radius: 3px;
|
|
680
|
+
background: repeating-linear-gradient(
|
|
681
|
+
-45deg,
|
|
682
|
+
color-mix(in srgb, var(--dt-text, rgba(0,0,0,0.85)) 4%, transparent),
|
|
683
|
+
color-mix(in srgb, var(--dt-text, rgba(0,0,0,0.85)) 4%, transparent) 3px,
|
|
684
|
+
transparent 3px,
|
|
685
|
+
transparent 6px
|
|
686
|
+
);
|
|
687
|
+
margin-bottom: 2px;
|
|
688
|
+
min-height: 14px;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.wg-blocked-label {
|
|
692
|
+
font: 500 8px/1 var(--dt-sans, system-ui, sans-serif);
|
|
693
|
+
color: var(--dt-text-3, rgba(0, 0, 0, 0.3));
|
|
694
|
+
text-transform: uppercase;
|
|
695
|
+
letter-spacing: 0.04em;
|
|
696
|
+
white-space: nowrap;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/* ─── Custom day header ──────────────────────────── */
|
|
700
|
+
.wg-cell-custom-header {
|
|
701
|
+
padding: 0 4px 2px;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/* ─── Cell header (day label top-right) ──────────── */
|
|
705
|
+
.wg-cell-hd {
|
|
706
|
+
display: flex;
|
|
707
|
+
align-items: center;
|
|
708
|
+
justify-content: flex-end;
|
|
709
|
+
gap: 4px;
|
|
710
|
+
padding: 4px 5px 2px 0;
|
|
711
|
+
margin-bottom: 2px;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.wg-day-wd {
|
|
715
|
+
font: 400 10px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
716
|
+
letter-spacing: 0.04em;
|
|
717
|
+
text-transform: uppercase;
|
|
718
|
+
color: var(--dt-text-3, rgba(0, 0, 0, 0.3));
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.wg-week--current .wg-day-wd {
|
|
722
|
+
color: var(--dt-text-2, rgba(0, 0, 0, 0.5));
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.wg-cell-hd--today .wg-day-wd {
|
|
726
|
+
color: var(--dt-accent, #ef4444);
|
|
727
|
+
font-weight: 600;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
.wg-day-num {
|
|
731
|
+
font: 700 14px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
732
|
+
color: var(--dt-text, rgba(0, 0, 0, 0.85));
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.wg-week--current .wg-day-num {
|
|
736
|
+
color: var(--dt-text, rgba(0, 0, 0, 0.95));
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
.wg-day-num--today {
|
|
740
|
+
color: var(--dt-accent, #ef4444);
|
|
741
|
+
font-weight: 900;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/* ─── Month label (top-left, behind events) ─────── */
|
|
745
|
+
.wg-cell-month {
|
|
746
|
+
position: absolute;
|
|
747
|
+
left: 4px;
|
|
748
|
+
top: 4px;
|
|
749
|
+
writing-mode: vertical-rl;
|
|
750
|
+
transform: rotate(180deg);
|
|
751
|
+
font: 800 22px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
752
|
+
letter-spacing: 0.02em;
|
|
753
|
+
text-transform: uppercase;
|
|
754
|
+
color: color-mix(in srgb, var(--dt-text, rgba(255,255,255,0.85)) 4%, transparent);
|
|
755
|
+
pointer-events: none;
|
|
756
|
+
white-space: nowrap;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
.wg-week--current .wg-cell-month {
|
|
760
|
+
color: color-mix(in srgb, var(--dt-text, rgba(255,255,255,0.85)) 8%, transparent);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/* ─── All-day / multi-day events ─────────────────── */
|
|
764
|
+
.wg-allday {
|
|
765
|
+
display: flex;
|
|
766
|
+
flex-direction: column;
|
|
767
|
+
gap: 2px;
|
|
768
|
+
margin-bottom: 4px;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.wg-ad {
|
|
772
|
+
display: flex;
|
|
773
|
+
align-items: center;
|
|
774
|
+
gap: 3px;
|
|
775
|
+
padding: 2px 5px;
|
|
776
|
+
border-radius: 3px;
|
|
777
|
+
background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #10141c));
|
|
778
|
+
cursor: pointer;
|
|
779
|
+
overflow: hidden;
|
|
780
|
+
transition: background 0.12s;
|
|
781
|
+
min-height: 18px;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
.wg-ad:hover {
|
|
785
|
+
background: color-mix(in srgb, var(--ev-color) 32%, var(--dt-surface, #10141c));
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.wg-ad--start {
|
|
789
|
+
border-left: 2.5px solid var(--ev-color);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.wg-ad--mid {
|
|
793
|
+
border-radius: 0;
|
|
794
|
+
border-left: 1px dashed color-mix(in srgb, var(--ev-color) 40%, transparent);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.wg-ad--end:not(.wg-ad--start) {
|
|
798
|
+
border-radius: 0 3px 3px 0;
|
|
799
|
+
border-left: 1px dashed color-mix(in srgb, var(--ev-color) 40%, transparent);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.wg-ad--selected {
|
|
803
|
+
box-shadow: 0 0 0 1.5px var(--ev-color);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.wg-ad-title {
|
|
807
|
+
font: 500 10px / 1.1 var(--dt-sans, system-ui, sans-serif);
|
|
808
|
+
color: var(--dt-text, rgba(0, 0, 0, 0.85));
|
|
809
|
+
white-space: nowrap;
|
|
810
|
+
overflow: hidden;
|
|
811
|
+
text-overflow: ellipsis;
|
|
812
|
+
flex: 1;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.wg-ad-cont {
|
|
816
|
+
font-size: 8px;
|
|
817
|
+
color: var(--ev-color);
|
|
818
|
+
flex-shrink: 0;
|
|
819
|
+
line-height: 1;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.wg-ad-arrow {
|
|
823
|
+
font-size: 8px;
|
|
824
|
+
color: var(--ev-color);
|
|
825
|
+
flex-shrink: 0;
|
|
826
|
+
margin-left: auto;
|
|
827
|
+
line-height: 1;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/* ─── Events ─────────────────────────────────────── */
|
|
831
|
+
.wg-cell-events {
|
|
832
|
+
position: relative;
|
|
833
|
+
display: flex;
|
|
834
|
+
flex-direction: column;
|
|
835
|
+
gap: 3px;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.wg-ev {
|
|
839
|
+
display: flex;
|
|
840
|
+
align-items: center;
|
|
841
|
+
flex-wrap: wrap;
|
|
842
|
+
gap: 3px 5px;
|
|
843
|
+
padding: 3px 6px;
|
|
844
|
+
border-radius: 4px;
|
|
845
|
+
background: color-mix(in srgb, var(--ev-color) 15%, var(--dt-surface, #10141c));
|
|
846
|
+
cursor: pointer;
|
|
847
|
+
overflow: hidden;
|
|
848
|
+
transition: background 0.12s;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
.wg-ev:hover {
|
|
852
|
+
background: color-mix(in srgb, var(--ev-color) 25%, var(--dt-surface, #10141c));
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
.wg-ev--selected {
|
|
856
|
+
box-shadow: 0 0 0 1.5px var(--ev-color);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.wg-ev--current {
|
|
860
|
+
background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #10141c));
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
.wg-ev-time {
|
|
864
|
+
font: 400 10px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
865
|
+
color: var(--dt-text-3, rgba(0, 0, 0, 0.4));
|
|
866
|
+
flex-shrink: 0;
|
|
867
|
+
white-space: nowrap;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
.wg-ev-title {
|
|
871
|
+
font: 500 12px / 1.1 var(--dt-sans, system-ui, sans-serif);
|
|
872
|
+
color: var(--dt-text, rgba(0, 0, 0, 0.85));
|
|
873
|
+
white-space: nowrap;
|
|
874
|
+
overflow: hidden;
|
|
875
|
+
text-overflow: ellipsis;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
.wg-ev-more {
|
|
879
|
+
font: 500 10px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
880
|
+
color: var(--dt-text-3, rgba(0, 0, 0, 0.35));
|
|
881
|
+
padding: 2px 8px;
|
|
882
|
+
cursor: pointer;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.wg-ev-more:hover {
|
|
886
|
+
color: var(--dt-text-2, rgba(0, 0, 0, 0.55));
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/* ─── Navigation pill ────────────────────────────── */
|
|
890
|
+
.wg-nav {
|
|
891
|
+
position: absolute;
|
|
892
|
+
top: 22px;
|
|
893
|
+
right: 14px;
|
|
894
|
+
z-index: 20;
|
|
895
|
+
display: flex;
|
|
896
|
+
gap: 2px;
|
|
897
|
+
background: color-mix(in srgb, var(--dt-surface, #10141c) 85%, transparent);
|
|
898
|
+
backdrop-filter: blur(6px);
|
|
899
|
+
-webkit-backdrop-filter: blur(6px);
|
|
900
|
+
border-radius: 8px;
|
|
901
|
+
padding: 2px;
|
|
902
|
+
border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
|
|
903
|
+
animation: wg-nav-in 200ms ease both;
|
|
904
|
+
}
|
|
905
|
+
@keyframes wg-nav-in {
|
|
906
|
+
from { opacity: 0; transform: translateY(-6px); }
|
|
907
|
+
to { opacity: 1; transform: translateY(0); }
|
|
908
|
+
}
|
|
909
|
+
.wg-nav-pill {
|
|
910
|
+
border: none;
|
|
911
|
+
background: transparent;
|
|
912
|
+
color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
|
|
913
|
+
cursor: pointer;
|
|
914
|
+
font: 600 11px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
915
|
+
padding: 6px 12px;
|
|
916
|
+
border-radius: 6px;
|
|
917
|
+
letter-spacing: 0.04em;
|
|
918
|
+
text-transform: uppercase;
|
|
919
|
+
transition: background 100ms, color 100ms;
|
|
920
|
+
display: inline-flex;
|
|
921
|
+
align-items: center;
|
|
922
|
+
justify-content: center;
|
|
923
|
+
}
|
|
924
|
+
.wg-nav-pill:hover {
|
|
925
|
+
color: var(--dt-text, rgba(226, 232, 240, 0.85));
|
|
926
|
+
}
|
|
927
|
+
.wg-nav-pill:focus-visible {
|
|
928
|
+
outline: 2px solid color-mix(in srgb, var(--dt-accent, #ef4444) 55%, transparent);
|
|
929
|
+
outline-offset: 2px;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/* ─── Focus-visible ──────────────────────────────── */
|
|
933
|
+
.wg-cell:focus-visible {
|
|
934
|
+
outline: 2px solid var(--dt-accent, #ef4444);
|
|
935
|
+
outline-offset: -2px;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
.wg-ev:focus-visible {
|
|
939
|
+
outline: 2px solid var(--ev-color, var(--dt-accent, #ef4444));
|
|
940
|
+
outline-offset: 1px;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.wg-ev--dragging {
|
|
944
|
+
opacity: 0.6;
|
|
945
|
+
cursor: grabbing;
|
|
946
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
947
|
+
}
|
|
948
|
+
</style>
|
|
949
|
+
|