@nomideusz/svelte-calendar 1.4.2 → 1.5.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 +12 -0
- package/dist/calendar/Calendar.svelte +9 -3
- package/dist/calendar/Calendar.svelte.d.ts +7 -0
- package/dist/views/agenda/AgendaDay.svelte +21 -16
- package/dist/views/agenda/AgendaWeek.svelte +11 -5
- package/dist/views/mobile/MobileDay.svelte +14 -3
- package/dist/views/mobile/MobileWeek.svelte +6 -3
- package/dist/views/month/MonthGrid.svelte +12 -8
- package/dist/views/planner/PlannerScroll.svelte +8 -5
- package/dist/views/planner/PlannerWeek.svelte +20 -33
- package/dist/views/shared/opening-top.d.ts +15 -0
- package/dist/views/shared/opening-top.js +41 -0
- package/package.json +1 -1
- package/widget/widget.js +55 -24
package/README.md
CHANGED
|
@@ -470,6 +470,8 @@ Component fallbacks use system fonts and a neutral blue accent, so the package s
|
|
|
470
470
|
| `--dt-text-3` | Tertiary text |
|
|
471
471
|
| `--dt-accent` | Accent color |
|
|
472
472
|
| `--dt-accent-dim` | Accent at ~12% opacity (selection, highlights) |
|
|
473
|
+
| `--dt-ev-fill` | Event wash, as a percentage of the event's colour (default `0%`; the `eventFill` prop sets it) |
|
|
474
|
+
| `--dt-ev-bar` | Width of an event's colour bar (default `3px`; the `eventBar` prop sets it) |
|
|
473
475
|
| `--dt-event` | Bar color of an event without its own `color` (the presets set it to the text color, so such events draw in ink) |
|
|
474
476
|
| `--dt-accent-fg` | Text on the planner's today-number badge (default `#fff`; not set by the presets) |
|
|
475
477
|
| `--dt-btn-text` | Text on accent-filled buttons and badges |
|
|
@@ -535,6 +537,14 @@ const palette = generatePalette('#e11d48'); // (accent?, count = 15)
|
|
|
535
537
|
const adapter = createMemoryAdapter(events, { palette });
|
|
536
538
|
```
|
|
537
539
|
|
|
540
|
+
Events are transparent by default: a bar in the event's colour, ink text on the paper. To give them a background, or change the bar:
|
|
541
|
+
|
|
542
|
+
```svelte
|
|
543
|
+
<Calendar {adapter} eventFill={0.15} /> <!-- a light wash in each event's colour -->
|
|
544
|
+
<Calendar {adapter} eventFill={0.15} eventBar={0} /> <!-- wash only, no bar -->
|
|
545
|
+
<Calendar {adapter} eventBar={5} /> <!-- a heavier bar -->
|
|
546
|
+
```
|
|
547
|
+
|
|
538
548
|
Related exports: `VIVID_PALETTE` (the vivid palette, the default before 1.4) and `extractAccent(themeString)` (the accent color of a `--dt-*` theme string).
|
|
539
549
|
|
|
540
550
|
### Multi-day & All-day
|
|
@@ -1106,6 +1116,8 @@ Every `<Calendar>` prop (`CalendarProps`), grouped by what it controls.
|
|
|
1106
1116
|
| `theme` | `string` | `auto` | CSS theme string of `--dt-*` properties. `auto` probes the host page. |
|
|
1107
1117
|
| `autoTheme` | `AutoThemeOptions \| false` | `undefined` (probe) | Fine-tune auto-detection: `{ mode, accent, font }`. `false` disables probing. |
|
|
1108
1118
|
| `borderRadius` | `number` | `0` | Corner radius in px (square by default) |
|
|
1119
|
+
| `eventFill` | `number` | `0` | Wash each event in its own colour, 0–1 (`0.15` = a light tint). Sets `--dt-ev-fill` |
|
|
1120
|
+
| `eventBar` | `number` | `3` | Width in px of the colour bar on an event's leading edge; `0` removes it. Sets `--dt-ev-bar` |
|
|
1109
1121
|
|
|
1110
1122
|
### Locale and timezone
|
|
1111
1123
|
|
|
@@ -95,7 +95,7 @@ import { wrapAdapterWithTimezone, toZonedTime, fromZonedTime } from "../core/tim
|
|
|
95
95
|
import { isReadOnlyError, isNotFoundError } from "../adapters/errors.js";
|
|
96
96
|
/** Breakpoint (px) at which auto-mobile activates */
|
|
97
97
|
const MOBILE_BREAKPOINT = 768;
|
|
98
|
-
let { adapter, views = defaultViews, view: activeViewId, theme = auto, autoTheme, mondayStart = true, height: heightProp = 600, borderRadius = 0, dir, locale, labels: labelsProp, readOnly = false, visibleHours, initialDate, snapInterval = 15, minColumnWidth = 110, showModePills = true, showNavigation = true, equalDays = false, showDates = true, hideDays, currentDate, blockedSlots, days, minDuration, maxDuration, disabledDates, compact = false, columns = false, mobile: mobileProp = "auto", event: eventSnippet, empty: emptySnippet, dayHeader: dayHeaderSnippet, header: headerSnippet, navigation: navigationSnippet, oneventclick, oneventcreate, onexternaldrop, oneventmove, onviewchange, ondatechange, oneventhover, ondayclick, onerror, timezone } = $props();
|
|
98
|
+
let { adapter, views = defaultViews, view: activeViewId, theme = auto, autoTheme, mondayStart = true, height: heightProp = 600, borderRadius = 0, eventFill, eventBar, dir, locale, labels: labelsProp, readOnly = false, visibleHours, initialDate, snapInterval = 15, minColumnWidth = 110, showModePills = true, showNavigation = true, equalDays = false, showDates = true, hideDays, currentDate, blockedSlots, days, minDuration, maxDuration, disabledDates, compact = false, columns = false, mobile: mobileProp = "auto", event: eventSnippet, empty: emptySnippet, dayHeader: dayHeaderSnippet, header: headerSnippet, navigation: navigationSnippet, oneventclick, oneventcreate, onexternaldrop, oneventmove, onviewchange, ondatechange, oneventhover, ondayclick, onerror, timezone } = $props();
|
|
99
99
|
// In readOnly mode, suppress mutation callbacks. With a timezone, the
|
|
100
100
|
// drag plane is zoned wall-clock — hosts always receive real instants.
|
|
101
101
|
const unzone = (d) => timezone ? fromZonedTime(d, timezone) : d;
|
|
@@ -672,7 +672,7 @@ const navCtx = $derived({
|
|
|
672
672
|
<div
|
|
673
673
|
class="cal"
|
|
674
674
|
bind:this={calEl}
|
|
675
|
-
style="{effectiveTheme}; {heightProp === 'auto' ? '' : `--cal-h: ${heightProp}px;`} --cal-r: {borderRadius}px"
|
|
675
|
+
style="{effectiveTheme}; {heightProp === 'auto' ? '' : `--cal-h: ${heightProp}px;`} --cal-r: {borderRadius}px;{eventFill == null ? '' : ` --dt-ev-fill: ${eventFill * 100}%;`}{eventBar == null ? '' : ` --dt-ev-bar: ${eventBar}px;`}"
|
|
676
676
|
class:cal--auto={heightProp === 'auto'}
|
|
677
677
|
role="region"
|
|
678
678
|
aria-label={L.calendar}
|
|
@@ -759,7 +759,8 @@ const navCtx = $derived({
|
|
|
759
759
|
<span class="cal-sr" role="status" aria-live="polite" aria-atomic="true">{noteText}</span>
|
|
760
760
|
{/if}
|
|
761
761
|
|
|
762
|
-
|
|
762
|
+
<!-- mobile='auto' doesn't know phone vs desktop until measured: hidden till then (SSR), shown anyway after 1.5s -->
|
|
763
|
+
<div class="cal-body" class:cal-body--unmeasured={mobileProp === 'auto' && containerWidth === 0}>
|
|
763
764
|
{#if activeView}
|
|
764
765
|
{@const Comp = activeView.component}
|
|
765
766
|
<Comp
|
|
@@ -1002,6 +1003,11 @@ const navCtx = $derived({
|
|
|
1002
1003
|
.cal--auto .cal-body {
|
|
1003
1004
|
overflow: visible;
|
|
1004
1005
|
}
|
|
1006
|
+
.cal-body--unmeasured {
|
|
1007
|
+
visibility: hidden;
|
|
1008
|
+
animation: cal-reveal 0s 1.5s forwards;
|
|
1009
|
+
}
|
|
1010
|
+
@keyframes cal-reveal { to { visibility: visible; } }
|
|
1005
1011
|
|
|
1006
1012
|
.cal-empty {
|
|
1007
1013
|
display: flex;
|
|
@@ -71,6 +71,13 @@ export interface CalendarProps {
|
|
|
71
71
|
height?: number | 'auto';
|
|
72
72
|
/** Border radius in px (default: 0, square-cut). */
|
|
73
73
|
borderRadius?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Wash each event in its own colour, 0–1 (default 0: transparent). Text
|
|
76
|
+
* stays the theme's ink, so a light tint (0.1–0.2) reads best.
|
|
77
|
+
*/
|
|
78
|
+
eventFill?: number;
|
|
79
|
+
/** Width in px of the colour bar on an event's leading edge (default 3). 0 removes it. */
|
|
80
|
+
eventBar?: number;
|
|
74
81
|
/** Text direction: 'ltr' (default), 'rtl', or 'auto' */
|
|
75
82
|
dir?: 'ltr' | 'rtl' | 'auto';
|
|
76
83
|
/** BCP 47 locale tag (e.g. 'en-US', 'ar-SA') — sets lang and locale for formatting */
|
|
@@ -526,7 +526,7 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
526
526
|
}
|
|
527
527
|
.ag-body::-webkit-scrollbar-thumb {
|
|
528
528
|
background: var(--dt-border);
|
|
529
|
-
border-radius:
|
|
529
|
+
border-radius: 0;
|
|
530
530
|
}
|
|
531
531
|
|
|
532
532
|
/* ═══ All-day strip ═══ */
|
|
@@ -600,6 +600,9 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
600
600
|
align-items: stretch;
|
|
601
601
|
border-radius: 0;
|
|
602
602
|
background: transparent;
|
|
603
|
+
/* eventFill: a wash in the event's colour; hover/current set background-color under it */
|
|
604
|
+
--_fill: color-mix(in srgb, var(--ev-color) var(--dt-ev-fill, 0%), transparent);
|
|
605
|
+
background-image: linear-gradient(var(--_fill), var(--_fill));
|
|
603
606
|
border: none;
|
|
604
607
|
border-bottom: 1px solid var(--dt-border, rgba(15, 15, 15, 0.3));
|
|
605
608
|
overflow: hidden;
|
|
@@ -612,12 +615,12 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
612
615
|
left: 0;
|
|
613
616
|
top: 0;
|
|
614
617
|
bottom: 0;
|
|
615
|
-
width: 3px;
|
|
618
|
+
width: var(--dt-ev-bar, 3px);
|
|
616
619
|
background: var(--ev-color);
|
|
617
620
|
}
|
|
618
621
|
.ag-card:hover,
|
|
619
622
|
.ag-card:active {
|
|
620
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
623
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
621
624
|
}
|
|
622
625
|
.ag-card:focus-visible {
|
|
623
626
|
outline: none;
|
|
@@ -859,6 +862,9 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
859
862
|
margin-bottom: 8px;
|
|
860
863
|
border-radius: 0;
|
|
861
864
|
background: var(--dt-accent-dim, color-mix(in srgb, var(--dt-accent, #1d4fd7) 12%, transparent));
|
|
865
|
+
/* eventFill: a wash in the event's colour; hover/current set background-color under it */
|
|
866
|
+
--_fill: color-mix(in srgb, var(--ev-color) var(--dt-ev-fill, 0%), transparent);
|
|
867
|
+
background-image: linear-gradient(var(--_fill), var(--_fill));
|
|
862
868
|
border: none;
|
|
863
869
|
border-bottom: 1px solid var(--dt-border, rgba(15, 15, 15, 0.3));
|
|
864
870
|
cursor: pointer;
|
|
@@ -870,12 +876,13 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
870
876
|
left: 0;
|
|
871
877
|
top: 0;
|
|
872
878
|
bottom: 0;
|
|
873
|
-
width: 3px;
|
|
879
|
+
width: var(--dt-ev-bar, 3px);
|
|
874
880
|
background: var(--ev-color, var(--dt-accent));
|
|
875
881
|
}
|
|
876
882
|
.ag-q-now:hover,
|
|
877
883
|
.ag-q-now:active {
|
|
878
884
|
background:
|
|
885
|
+
linear-gradient(var(--_fill), var(--_fill)),
|
|
879
886
|
linear-gradient(var(--dt-hover, rgba(15, 15, 15, 0.04)), var(--dt-hover, rgba(15, 15, 15, 0.04))),
|
|
880
887
|
var(--dt-accent-dim, color-mix(in srgb, var(--dt-accent, #1d4fd7) 12%, transparent));
|
|
881
888
|
}
|
|
@@ -892,16 +899,8 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
892
899
|
border-radius: 50%;
|
|
893
900
|
background: var(--ev-color, var(--dt-accent, #1d4fd7));
|
|
894
901
|
margin-bottom: 8px;
|
|
895
|
-
animation: ag-pulse 2.5s ease-in-out infinite;
|
|
896
|
-
}
|
|
897
|
-
@keyframes ag-pulse {
|
|
898
|
-
0%, 100% { opacity: 1; }
|
|
899
|
-
50% { opacity: 0.4; }
|
|
900
902
|
}
|
|
901
903
|
@media (prefers-reduced-motion: reduce) {
|
|
902
|
-
.ag-q-now-dot {
|
|
903
|
-
animation: none;
|
|
904
|
-
}
|
|
905
904
|
.ag-q-now-fill {
|
|
906
905
|
transition: none;
|
|
907
906
|
}
|
|
@@ -1006,6 +1005,9 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
1006
1005
|
display: none;
|
|
1007
1006
|
}
|
|
1008
1007
|
.ag-log-row {
|
|
1008
|
+
/* eventFill: a wash in the event's colour; hover/current set background-color under it */
|
|
1009
|
+
--_fill: color-mix(in srgb, var(--ev-color) var(--dt-ev-fill, 0%), transparent);
|
|
1010
|
+
background-image: linear-gradient(var(--_fill), var(--_fill));
|
|
1009
1011
|
display: flex;
|
|
1010
1012
|
align-items: baseline;
|
|
1011
1013
|
gap: 10px;
|
|
@@ -1024,7 +1026,7 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
1024
1026
|
}
|
|
1025
1027
|
.ag-log-row:hover,
|
|
1026
1028
|
.ag-log-row:active {
|
|
1027
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1029
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1028
1030
|
}
|
|
1029
1031
|
.ag-log-row:focus-visible {
|
|
1030
1032
|
outline: none;
|
|
@@ -1050,7 +1052,7 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
1050
1052
|
/* The colour dot of a past row becomes its 3px start bar (dimmed: it is over). */
|
|
1051
1053
|
.ag-log-dot {
|
|
1052
1054
|
align-self: stretch;
|
|
1053
|
-
width: 3px;
|
|
1055
|
+
width: var(--dt-ev-bar, 3px);
|
|
1054
1056
|
border-radius: 0;
|
|
1055
1057
|
flex-shrink: 0;
|
|
1056
1058
|
opacity: 0.45;
|
|
@@ -1088,6 +1090,9 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
1088
1090
|
.ag-compact-list::-webkit-scrollbar { display: none; }
|
|
1089
1091
|
/* A compact row: 3px start bar, hairline beneath, ink text. */
|
|
1090
1092
|
.ag-compact-row {
|
|
1093
|
+
/* eventFill: a wash in the event's colour; hover/current set background-color under it */
|
|
1094
|
+
--_fill: color-mix(in srgb, var(--ev-color) var(--dt-ev-fill, 0%), transparent);
|
|
1095
|
+
background-image: linear-gradient(var(--_fill), var(--_fill));
|
|
1091
1096
|
position: relative;
|
|
1092
1097
|
display: flex;
|
|
1093
1098
|
align-items: baseline;
|
|
@@ -1104,7 +1109,7 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
1104
1109
|
left: 0;
|
|
1105
1110
|
top: 0;
|
|
1106
1111
|
bottom: 0;
|
|
1107
|
-
width: 3px;
|
|
1112
|
+
width: var(--dt-ev-bar, 3px);
|
|
1108
1113
|
background: var(--ev-color, var(--dt-accent));
|
|
1109
1114
|
}
|
|
1110
1115
|
.ag-compact-row--selected {
|
|
@@ -1114,7 +1119,7 @@ const hiddenDoneCount = $derived(showAllDone ? 0 : Math.max(0, dayCat.past.lengt
|
|
|
1114
1119
|
.ag-compact-row:active .ag-compact-row-title { color: var(--dt-text); }
|
|
1115
1120
|
.ag-compact-row:hover,
|
|
1116
1121
|
.ag-compact-row:active {
|
|
1117
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1122
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1118
1123
|
}
|
|
1119
1124
|
.ag-compact-row:focus-visible {
|
|
1120
1125
|
outline: none;
|
|
@@ -579,7 +579,7 @@ const weekDays = $derived.by(() => {
|
|
|
579
579
|
}
|
|
580
580
|
.ag-body::-webkit-scrollbar-thumb {
|
|
581
581
|
background: var(--dt-border);
|
|
582
|
-
border-radius:
|
|
582
|
+
border-radius: 0;
|
|
583
583
|
}
|
|
584
584
|
|
|
585
585
|
/* ═══ All-day chips ═══ */
|
|
@@ -639,6 +639,9 @@ const weekDays = $derived.by(() => {
|
|
|
639
639
|
align-items: stretch;
|
|
640
640
|
border-radius: 0;
|
|
641
641
|
background: transparent;
|
|
642
|
+
/* eventFill: a wash in the event's colour; hover/current set background-color under it */
|
|
643
|
+
--_fill: color-mix(in srgb, var(--ev-color) var(--dt-ev-fill, 0%), transparent);
|
|
644
|
+
background-image: linear-gradient(var(--_fill), var(--_fill));
|
|
642
645
|
border: none;
|
|
643
646
|
border-bottom: 1px solid var(--dt-border, rgba(15, 15, 15, 0.3));
|
|
644
647
|
overflow: hidden;
|
|
@@ -651,12 +654,12 @@ const weekDays = $derived.by(() => {
|
|
|
651
654
|
left: 0;
|
|
652
655
|
top: 0;
|
|
653
656
|
bottom: 0;
|
|
654
|
-
width: 3px;
|
|
657
|
+
width: var(--dt-ev-bar, 3px);
|
|
655
658
|
background: var(--ev-color);
|
|
656
659
|
}
|
|
657
660
|
.ag-card:hover,
|
|
658
661
|
.ag-card:active {
|
|
659
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
662
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
660
663
|
}
|
|
661
664
|
.ag-card:focus-visible {
|
|
662
665
|
outline: none;
|
|
@@ -939,6 +942,9 @@ const weekDays = $derived.by(() => {
|
|
|
939
942
|
}
|
|
940
943
|
/* A compact row: 3px start bar, hairline beneath, ink text. */
|
|
941
944
|
.ag-compact {
|
|
945
|
+
/* eventFill: a wash in the event's colour; hover/current set background-color under it */
|
|
946
|
+
--_fill: color-mix(in srgb, var(--ev-color) var(--dt-ev-fill, 0%), transparent);
|
|
947
|
+
background-image: linear-gradient(var(--_fill), var(--_fill));
|
|
942
948
|
position: relative;
|
|
943
949
|
display: flex;
|
|
944
950
|
align-items: baseline;
|
|
@@ -955,7 +961,7 @@ const weekDays = $derived.by(() => {
|
|
|
955
961
|
left: 0;
|
|
956
962
|
top: 0;
|
|
957
963
|
bottom: 0;
|
|
958
|
-
width: 3px;
|
|
964
|
+
width: var(--dt-ev-bar, 3px);
|
|
959
965
|
background: var(--ev-color, var(--dt-accent));
|
|
960
966
|
}
|
|
961
967
|
.ag-compact--selected {
|
|
@@ -967,7 +973,7 @@ const weekDays = $derived.by(() => {
|
|
|
967
973
|
}
|
|
968
974
|
.ag-compact:hover,
|
|
969
975
|
.ag-compact:active {
|
|
970
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
976
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
971
977
|
}
|
|
972
978
|
.ag-compact:focus-visible {
|
|
973
979
|
outline: none;
|
|
@@ -13,6 +13,7 @@ import { HOUR_MS, sod, addDaysMs, diffDays, isAllDay, segmentForDay } from "../.
|
|
|
13
13
|
import { fmtHParts, fmtTime } from "../../core/locale.js";
|
|
14
14
|
import { createSwipe } from "./swipe.js";
|
|
15
15
|
import { dateLong } from "../shared/format.js";
|
|
16
|
+
import { openingTop } from "../shared/opening-top.js";
|
|
16
17
|
let { height = null, events = [], style = "", locale, focusDate, oneventclick, oneventcreate, selectedEventId = null, readOnly = false, visibleHours } = $props();
|
|
17
18
|
// ── Context ────────────────────────────────────────
|
|
18
19
|
const ctx = useCalendarContext();
|
|
@@ -459,13 +460,18 @@ function onResizeCancel() {
|
|
|
459
460
|
cleanupResize();
|
|
460
461
|
}
|
|
461
462
|
// ── Auto-scroll to now ─────────────────────────────
|
|
463
|
+
// The hour before now, its label clear of the top, no event sliced there
|
|
464
|
+
// (openingTop). The grid's 8px padding sits above hour 0.
|
|
462
465
|
let gridEl;
|
|
463
466
|
$effect(() => {
|
|
464
467
|
void dayMs;
|
|
465
468
|
const el = gridEl;
|
|
466
469
|
if (!el) return;
|
|
467
470
|
untrack(() => {
|
|
468
|
-
if (nowOffset
|
|
471
|
+
if (nowOffset < 0) return;
|
|
472
|
+
const line = Math.max(0, Math.floor(nowOffset / HOUR_HEIGHT - 1)) * HOUR_HEIGHT;
|
|
473
|
+
const y = openingTop(positionedEvents, line, HOUR_HEIGHT, 8, nowOffset, el.clientHeight - 8);
|
|
474
|
+
el.scrollTop = y && y + 8;
|
|
469
475
|
});
|
|
470
476
|
});
|
|
471
477
|
</script>
|
|
@@ -962,6 +968,9 @@ $effect(() => {
|
|
|
962
968
|
border-radius: 0;
|
|
963
969
|
cursor: pointer;
|
|
964
970
|
background: transparent;
|
|
971
|
+
/* eventFill: a wash in the event's colour; hover/current set background-color under it */
|
|
972
|
+
--_fill: color-mix(in srgb, var(--ev-color) var(--dt-ev-fill, 0%), transparent);
|
|
973
|
+
background-image: linear-gradient(var(--_fill), var(--_fill));
|
|
965
974
|
border: none;
|
|
966
975
|
border-bottom: 1px solid var(--dt-border, rgba(15, 15, 15, 0.3));
|
|
967
976
|
box-sizing: border-box;
|
|
@@ -976,7 +985,7 @@ $effect(() => {
|
|
|
976
985
|
}
|
|
977
986
|
.mb-event:hover,
|
|
978
987
|
.mb-event:active {
|
|
979
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
988
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
980
989
|
}
|
|
981
990
|
/* Short blocks keep their duration-proportional height, but get a 44px
|
|
982
991
|
transparent hit-slop so taps still land. */
|
|
@@ -1003,6 +1012,7 @@ $effect(() => {
|
|
|
1003
1012
|
laid over the paper, opaque, so the line runs beneath its text. */
|
|
1004
1013
|
.mb-event.mb-event--current {
|
|
1005
1014
|
background:
|
|
1015
|
+
linear-gradient(var(--_fill), var(--_fill)),
|
|
1006
1016
|
linear-gradient(var(--dt-accent-dim, rgba(29, 79, 215, 0.12)), var(--dt-accent-dim, rgba(29, 79, 215, 0.12))),
|
|
1007
1017
|
var(--dt-paper, none),
|
|
1008
1018
|
var(--dt-bg, #efebe9);
|
|
@@ -1010,6 +1020,7 @@ $effect(() => {
|
|
|
1010
1020
|
.mb-event.mb-event--current:hover,
|
|
1011
1021
|
.mb-event.mb-event--current:active {
|
|
1012
1022
|
background:
|
|
1023
|
+
linear-gradient(var(--_fill), var(--_fill)),
|
|
1013
1024
|
linear-gradient(var(--dt-hover, rgba(15, 15, 15, 0.04)), var(--dt-hover, rgba(15, 15, 15, 0.04))),
|
|
1014
1025
|
var(--dt-paper, none),
|
|
1015
1026
|
var(--dt-bg, #efebe9);
|
|
@@ -1121,7 +1132,7 @@ $effect(() => {
|
|
|
1121
1132
|
}
|
|
1122
1133
|
|
|
1123
1134
|
.mb-ev-stripe {
|
|
1124
|
-
width: 3px;
|
|
1135
|
+
width: var(--dt-ev-bar, 3px);
|
|
1125
1136
|
background: var(--ev-color);
|
|
1126
1137
|
flex-shrink: 0;
|
|
1127
1138
|
border-radius: 0;
|
|
@@ -422,6 +422,9 @@ function handleDayKeydown(e, dayMs) {
|
|
|
422
422
|
min-height: 44px;
|
|
423
423
|
border-radius: 0;
|
|
424
424
|
background: transparent;
|
|
425
|
+
/* eventFill: a wash in the event's colour; hover/current set background-color under it */
|
|
426
|
+
--_fill: color-mix(in srgb, var(--ev-color) var(--dt-ev-fill, 0%), transparent);
|
|
427
|
+
background-image: linear-gradient(var(--_fill), var(--_fill));
|
|
425
428
|
overflow: hidden;
|
|
426
429
|
cursor: pointer;
|
|
427
430
|
transition: background 120ms;
|
|
@@ -434,13 +437,13 @@ function handleDayKeydown(e, dayMs) {
|
|
|
434
437
|
}
|
|
435
438
|
.mw-ev:hover,
|
|
436
439
|
.mw-ev:active {
|
|
437
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
440
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
438
441
|
}
|
|
439
442
|
.mw-ev--selected {
|
|
440
443
|
box-shadow: inset 0 0 0 1px var(--ev-color);
|
|
441
444
|
}
|
|
442
445
|
.mw-ev--current {
|
|
443
|
-
background: var(--dt-accent-dim, rgba(29, 79, 215, 0.12));
|
|
446
|
+
background-color: var(--dt-accent-dim, rgba(29, 79, 215, 0.12));
|
|
444
447
|
}
|
|
445
448
|
/* All day: a solid band on the event colour, no tint. */
|
|
446
449
|
.mw-ev--allday {
|
|
@@ -486,7 +489,7 @@ function handleDayKeydown(e, dayMs) {
|
|
|
486
489
|
}
|
|
487
490
|
|
|
488
491
|
.mw-ev-stripe {
|
|
489
|
-
width: 3px;
|
|
492
|
+
width: var(--dt-ev-bar, 3px);
|
|
490
493
|
align-self: stretch;
|
|
491
494
|
background: var(--ev-color, var(--dt-accent));
|
|
492
495
|
flex-shrink: 0;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
MonthGrid — classic month overview.
|
|
3
3
|
|
|
4
4
|
Week-aligned grid (4–6 rows, driven by the view-state 'month' range).
|
|
5
|
-
Each cell lists
|
|
5
|
+
Each cell lists as many events as fit plus a "+N more" overflow; clicking
|
|
6
6
|
an event fires oneventclick, clicking a day (or the overflow) fires
|
|
7
7
|
ondayclick — the natural month → day drill-down.
|
|
8
8
|
-->
|
|
@@ -29,7 +29,7 @@ const todayMs = $derived(clock.today);
|
|
|
29
29
|
// cell tap (day drill-down) carry the interaction.
|
|
30
30
|
const dotsMode = $derived(isMobile);
|
|
31
31
|
const MAX_CHIPS = 3;
|
|
32
|
-
// How many chips a cell has room for, measured
|
|
32
|
+
// How many chips a cell has room for, measured (MAX_CHIPS until then).
|
|
33
33
|
// A cell that overflows must also show its "+N more", so it gets one row
|
|
34
34
|
// fewer when that row would not fit beside its chips: a hidden event is
|
|
35
35
|
// always counted, never clipped away. Auto height and dots grow instead.
|
|
@@ -47,8 +47,8 @@ function measureFit() {
|
|
|
47
47
|
const chipH = chip.offsetHeight;
|
|
48
48
|
const moreH = bodyEl?.querySelector(".mg-more")?.offsetHeight ?? chipH;
|
|
49
49
|
if (!chipH) return;
|
|
50
|
-
fitAll = Math.
|
|
51
|
-
fitMore = Math.max(0, Math.
|
|
50
|
+
fitAll = Math.max(0, Math.floor((room + gap) / (chipH + gap)));
|
|
51
|
+
fitMore = Math.max(0, Math.floor((room - moreH) / (chipH + gap)));
|
|
52
52
|
}
|
|
53
53
|
$effect(() => {
|
|
54
54
|
if (!bodyEl || autoHeight || dotsMode) {
|
|
@@ -456,7 +456,10 @@ function chipTime(e) {
|
|
|
456
456
|
gap: 5px;
|
|
457
457
|
border: none;
|
|
458
458
|
background: none;
|
|
459
|
-
|
|
459
|
+
/* eventFill: a wash in the event's colour; hover/current set background-color under it */
|
|
460
|
+
--_fill: color-mix(in srgb, var(--mg-chip-color) var(--dt-ev-fill, 0%), transparent);
|
|
461
|
+
background-image: linear-gradient(var(--_fill), var(--_fill));
|
|
462
|
+
padding: 1px 4px;
|
|
460
463
|
border-radius: 0;
|
|
461
464
|
font: 400 12.5px/1.3 var(--dt-sans, system-ui, sans-serif);
|
|
462
465
|
color: var(--dt-text);
|
|
@@ -465,14 +468,14 @@ function chipTime(e) {
|
|
|
465
468
|
text-align: left;
|
|
466
469
|
}
|
|
467
470
|
.mg-chip:hover {
|
|
468
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
471
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
469
472
|
}
|
|
470
473
|
.mg-chip:focus-visible {
|
|
471
474
|
box-shadow: 0 0 0 2px var(--dt-accent, #1d4fd7);
|
|
472
475
|
outline: none;
|
|
473
476
|
}
|
|
474
477
|
.mg-chip--selected {
|
|
475
|
-
background: var(--dt-accent-dim);
|
|
478
|
+
background-color: var(--dt-accent-dim);
|
|
476
479
|
}
|
|
477
480
|
.mg-chip--cancelled {
|
|
478
481
|
text-decoration: line-through;
|
|
@@ -486,7 +489,7 @@ function chipTime(e) {
|
|
|
486
489
|
.mg-chip-dot {
|
|
487
490
|
flex: none;
|
|
488
491
|
align-self: stretch;
|
|
489
|
-
width: 3px;
|
|
492
|
+
width: var(--dt-ev-bar, 3px);
|
|
490
493
|
border-radius: 0;
|
|
491
494
|
background: var(--mg-chip-color);
|
|
492
495
|
}
|
|
@@ -558,6 +561,7 @@ function chipTime(e) {
|
|
|
558
561
|
}
|
|
559
562
|
.mg--dots .mg-chip {
|
|
560
563
|
padding: 3px;
|
|
564
|
+
background-image: none; /* a mark, not a chip: no eventFill wash */
|
|
561
565
|
min-height: 0;
|
|
562
566
|
}
|
|
563
567
|
.mg--dots .mg-chip-title,
|
|
@@ -1372,6 +1372,9 @@ function onCellKeydown(e, ms) {
|
|
|
1372
1372
|
padding: 0 3px 0 9px;
|
|
1373
1373
|
border-radius: 0;
|
|
1374
1374
|
background: transparent;
|
|
1375
|
+
/* eventFill: a wash in the event's colour; hover/current set background-color under it */
|
|
1376
|
+
--_fill: color-mix(in srgb, var(--ev-color) var(--dt-ev-fill, 0%), transparent);
|
|
1377
|
+
background-image: linear-gradient(var(--_fill), var(--_fill));
|
|
1375
1378
|
cursor: pointer;
|
|
1376
1379
|
overflow: hidden;
|
|
1377
1380
|
transition: background 0.12s, box-shadow 0.12s;
|
|
@@ -1381,12 +1384,12 @@ function onCellKeydown(e, ms) {
|
|
|
1381
1384
|
content: '';
|
|
1382
1385
|
position: absolute;
|
|
1383
1386
|
inset: 0 auto 0 0;
|
|
1384
|
-
width: 3px;
|
|
1387
|
+
width: var(--dt-ev-bar, 3px);
|
|
1385
1388
|
background: var(--ev-color);
|
|
1386
1389
|
}
|
|
1387
1390
|
|
|
1388
1391
|
.wg-ev:hover {
|
|
1389
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1392
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1390
1393
|
}
|
|
1391
1394
|
|
|
1392
1395
|
.wg-ev--drag-preview {
|
|
@@ -1396,7 +1399,7 @@ function onCellKeydown(e, ms) {
|
|
|
1396
1399
|
z-index: 8;
|
|
1397
1400
|
opacity: 0.95;
|
|
1398
1401
|
pointer-events: none;
|
|
1399
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1402
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1400
1403
|
box-shadow: inset 0 0 0 1px var(--ev-color);
|
|
1401
1404
|
cursor: grabbing;
|
|
1402
1405
|
}
|
|
@@ -1406,7 +1409,7 @@ function onCellKeydown(e, ms) {
|
|
|
1406
1409
|
}
|
|
1407
1410
|
|
|
1408
1411
|
.wg-ev--current {
|
|
1409
|
-
background: var(--dt-accent-dim, rgba(29, 79, 215, 0.12));
|
|
1412
|
+
background-color: var(--dt-accent-dim, rgba(29, 79, 215, 0.12));
|
|
1410
1413
|
}
|
|
1411
1414
|
|
|
1412
1415
|
/* Printed states: struck through, grey, or a dashed bar — never a tint */
|
|
@@ -1497,7 +1500,7 @@ function onCellKeydown(e, ms) {
|
|
|
1497
1500
|
|
|
1498
1501
|
.wg-ev--dragging {
|
|
1499
1502
|
cursor: grabbing;
|
|
1500
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1503
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1501
1504
|
box-shadow: inset 0 0 0 1px var(--ev-color);
|
|
1502
1505
|
}
|
|
1503
1506
|
</style>
|
|
@@ -23,6 +23,7 @@ import { HOUR_MS, sod, addDaysMs, diffDays } from "../../core/time.js";
|
|
|
23
23
|
import { startOfWeek as sowFn, isAllDay, segmentForDay } from "../../core/time.js";
|
|
24
24
|
import { fmtHParts, fmtTime, fmtDayNum, fmtDuration, fmtWeekRange, weekdayShort, weekdayLong } from "../../core/locale.js";
|
|
25
25
|
import { dateLong } from "../shared/format.js";
|
|
26
|
+
import { openingTop } from "../shared/opening-top.js";
|
|
26
27
|
let { mode = "week", mondayStart = true, locale, height = 520, events = [], style = "", focusDate, oneventclick, oneventcreate, onexternaldrop, selectedEventId = null, readOnly = false, visibleHours } = $props();
|
|
27
28
|
// ── Context ────────────────────────────────────────
|
|
28
29
|
const ctx = useCalendarContext();
|
|
@@ -302,9 +303,8 @@ const weekIsEmpty = $derived(!events.some((ev) => ev.start.getTime() < weekEndMs
|
|
|
302
303
|
// ── Auto-scroll (mount + week change) ──────────────
|
|
303
304
|
// Week with today → current time minus ~1h of context; otherwise the
|
|
304
305
|
// visibleHours start or 08:00, whichever is later. Lands on a whole hour,
|
|
305
|
-
// with room for its label (centred on the line) below the sticky top
|
|
306
|
-
//
|
|
307
|
-
// top, so the scroll starts at that event's hour instead.
|
|
306
|
+
// with room for its label (centred on the line) below the sticky top,
|
|
307
|
+
// moved so no event is sliced under it (openingTop).
|
|
308
308
|
// A server-rendered grid sits at midnight until this runs: it stays
|
|
309
309
|
// hidden until placed (.tw-body--unplaced), and shows anyway after 1.5s.
|
|
310
310
|
let placed = $state(false);
|
|
@@ -316,32 +316,15 @@ $effect(() => {
|
|
|
316
316
|
if (!el) return;
|
|
317
317
|
untrack(() => {
|
|
318
318
|
let targetHour;
|
|
319
|
+
let anchorHour;
|
|
319
320
|
if (weekHasToday) {
|
|
320
|
-
|
|
321
|
-
targetHour = Math.max(startHour,
|
|
321
|
+
anchorHour = Math.min(Math.max(nowFracHour, startHour), endHour);
|
|
322
|
+
targetHour = Math.max(startHour, anchorHour - 1);
|
|
322
323
|
} else {
|
|
323
|
-
targetHour = Math.max(startHour, Math.min(8, endHour - 1));
|
|
324
|
+
targetHour = anchorHour = Math.max(startHour, Math.min(8, endHour - 1));
|
|
324
325
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
// until no block crosses the line.
|
|
328
|
-
// ponytail: looks back at most 2 h, so a long block can't drag the view to dawn.
|
|
329
|
-
const floorY = (hour - 2 - startHour) * HOUR_H;
|
|
330
|
-
for (let moved = true; moved;) {
|
|
331
|
-
moved = false;
|
|
332
|
-
const line = (hour - startHour) * HOUR_H;
|
|
333
|
-
for (const day of layoutByDay.values()) {
|
|
334
|
-
for (const p of day) {
|
|
335
|
-
if (p.top < line && p.top + p.height > line && p.top >= floorY) {
|
|
336
|
-
hour = Math.floor(startHour + p.top / HOUR_H);
|
|
337
|
-
moved = true;
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
if (moved) break;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
el.scrollTop = Math.max(0, (hour - startHour) * HOUR_H - LABEL_ROOM);
|
|
326
|
+
const topH = el.querySelector(".tw-top")?.offsetHeight ?? 0;
|
|
327
|
+
el.scrollTop = openingTop([...layoutByDay.values()].flat(), (Math.floor(targetHour) - startHour) * HOUR_H, HOUR_H, LABEL_ROOM, (anchorHour - startHour) * HOUR_H, el.clientHeight - topH);
|
|
345
328
|
placed = true;
|
|
346
329
|
});
|
|
347
330
|
});
|
|
@@ -1245,7 +1228,7 @@ function onWindowKeydown(e) {
|
|
|
1245
1228
|
.tw-scroll::-webkit-scrollbar { width: 5px; height: 5px; }
|
|
1246
1229
|
.tw-scroll::-webkit-scrollbar-thumb {
|
|
1247
1230
|
background: var(--dt-scrollbar, rgba(15, 15, 15, 0.1));
|
|
1248
|
-
border-radius:
|
|
1231
|
+
border-radius: 0;
|
|
1249
1232
|
}
|
|
1250
1233
|
.tw-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
1251
1234
|
|
|
@@ -1673,6 +1656,9 @@ function onWindowKeydown(e) {
|
|
|
1673
1656
|
z-index: 6;
|
|
1674
1657
|
border-radius: 0;
|
|
1675
1658
|
background: transparent;
|
|
1659
|
+
/* eventFill: a wash in the event's colour; hover/current set background-color under it */
|
|
1660
|
+
--_fill: color-mix(in srgb, var(--ev-color) var(--dt-ev-fill, 0%), transparent);
|
|
1661
|
+
background-image: linear-gradient(var(--_fill), var(--_fill));
|
|
1676
1662
|
display: flex;
|
|
1677
1663
|
align-items: stretch;
|
|
1678
1664
|
overflow: hidden;
|
|
@@ -1685,7 +1671,7 @@ function onWindowKeydown(e) {
|
|
|
1685
1671
|
-webkit-tap-highlight-color: transparent;
|
|
1686
1672
|
}
|
|
1687
1673
|
.tw-ev:hover {
|
|
1688
|
-
background: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1674
|
+
background-color: var(--dt-hover, rgba(15, 15, 15, 0.04));
|
|
1689
1675
|
z-index: 8;
|
|
1690
1676
|
}
|
|
1691
1677
|
/* Short blocks keep duration-proportional height, but get a 44px
|
|
@@ -1705,12 +1691,13 @@ function onWindowKeydown(e) {
|
|
|
1705
1691
|
z-index: 9;
|
|
1706
1692
|
}
|
|
1707
1693
|
.tw-ev--current {
|
|
1708
|
-
background: var(--dt-accent-dim, rgba(29, 79, 215, 0.12));
|
|
1694
|
+
background-color: var(--dt-accent-dim, rgba(29, 79, 215, 0.12));
|
|
1709
1695
|
}
|
|
1710
1696
|
/* An event the now line crosses lays its column's ground (and its own
|
|
1711
1697
|
wash) over the paper, so the line passes beneath its text. */
|
|
1712
1698
|
.tw-ev.tw-ev--on-now {
|
|
1713
1699
|
background:
|
|
1700
|
+
linear-gradient(var(--_fill), var(--_fill)),
|
|
1714
1701
|
linear-gradient(var(--tw-ev-wash, transparent), var(--tw-ev-wash, transparent)),
|
|
1715
1702
|
linear-gradient(var(--tw-col-wash, transparent), var(--tw-col-wash, transparent)),
|
|
1716
1703
|
var(--dt-paper, none),
|
|
@@ -1754,7 +1741,7 @@ function onWindowKeydown(e) {
|
|
|
1754
1741
|
.tw-ev--readonly { cursor: default; }
|
|
1755
1742
|
|
|
1756
1743
|
.tw-ev-stripe {
|
|
1757
|
-
width: 3px;
|
|
1744
|
+
width: var(--dt-ev-bar, 3px);
|
|
1758
1745
|
background: var(--ev-color);
|
|
1759
1746
|
flex-shrink: 0;
|
|
1760
1747
|
}
|
|
@@ -1846,8 +1833,8 @@ function onWindowKeydown(e) {
|
|
|
1846
1833
|
position: absolute;
|
|
1847
1834
|
top: 4px;
|
|
1848
1835
|
right: 4px;
|
|
1849
|
-
width:
|
|
1850
|
-
height:
|
|
1836
|
+
width: 7px;
|
|
1837
|
+
height: 7px;
|
|
1851
1838
|
border-radius: 50%;
|
|
1852
1839
|
background: var(--ev-color, var(--dt-accent));
|
|
1853
1840
|
animation: tw-pulse 2s ease-in-out infinite;
|
|
@@ -1895,7 +1882,7 @@ function onWindowKeydown(e) {
|
|
|
1895
1882
|
transform: translateX(-50%);
|
|
1896
1883
|
width: 20px;
|
|
1897
1884
|
height: 3px;
|
|
1898
|
-
border-radius:
|
|
1885
|
+
border-radius: 0;
|
|
1899
1886
|
background: var(--ev-color);
|
|
1900
1887
|
opacity: 0;
|
|
1901
1888
|
transition: opacity 120ms;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a time grid opens: the y (grid px, 0 = first visible hour) to put at
|
|
3
|
+
* the top of the scroller so no event is sliced there.
|
|
4
|
+
*
|
|
5
|
+
* Starts at the hour line `line`, moves up an hour at a time while an event
|
|
6
|
+
* crosses it (looking back at most 2 h), then leaves `room` above for the
|
|
7
|
+
* hour label. If an event still crosses the top, the top goes to that event's
|
|
8
|
+
* start while `anchor` (the now line) stays on screen, else to the first hour
|
|
9
|
+
* after it ends, else the room is dropped; only a block too long for all of
|
|
10
|
+
* those is cut.
|
|
11
|
+
*/
|
|
12
|
+
export declare function openingTop(blocks: readonly {
|
|
13
|
+
top: number;
|
|
14
|
+
height: number;
|
|
15
|
+
}[], line: number, hourH: number, room: number, anchor: number, viewH: number): number;
|