@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,178 +1,178 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
NowIndicator — shared "now" marker component.
|
|
3
|
-
|
|
4
|
-
Three visual modes:
|
|
5
|
-
"line" — vertical/horizontal line with time label (default)
|
|
6
|
-
"dot" — circular dot with time label
|
|
7
|
-
"badge" — inline text badge ("now")
|
|
8
|
-
|
|
9
|
-
Usage:
|
|
10
|
-
<NowIndicator position={nowPx} time={clock.hm} seconds={clock.s} />
|
|
11
|
-
<NowIndicator mode="dot" position={nowPx} time={clock.hm} />
|
|
12
|
-
<NowIndicator mode="badge" />
|
|
13
|
-
-->
|
|
14
|
-
<script lang="ts">
|
|
15
|
-
import type { Snippet } from 'svelte';
|
|
16
|
-
import { getLabels } from '../core/locale.js';
|
|
17
|
-
|
|
18
|
-
const L = $derived(getLabels());
|
|
19
|
-
|
|
20
|
-
interface Props {
|
|
21
|
-
/** Visual mode */
|
|
22
|
-
mode?: 'line' | 'dot' | 'badge';
|
|
23
|
-
/** Pixel offset for positioning (line/dot modes) */
|
|
24
|
-
position?: number;
|
|
25
|
-
/** Orientation of the line/dot */
|
|
26
|
-
orientation?: 'vertical' | 'horizontal';
|
|
27
|
-
/** Formatted time string (e.g. "14:30") */
|
|
28
|
-
time?: string;
|
|
29
|
-
/** Formatted seconds string (e.g. ":05") */
|
|
30
|
-
seconds?: string;
|
|
31
|
-
/** Show the time label */
|
|
32
|
-
showLabel?: boolean;
|
|
33
|
-
/** Accent color override */
|
|
34
|
-
color?: string;
|
|
35
|
-
/** Custom content slot */
|
|
36
|
-
children?: Snippet;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let {
|
|
40
|
-
mode = 'line',
|
|
41
|
-
position = 0,
|
|
42
|
-
orientation = 'vertical',
|
|
43
|
-
time = '',
|
|
44
|
-
seconds = '',
|
|
45
|
-
showLabel = true,
|
|
46
|
-
color,
|
|
47
|
-
children,
|
|
48
|
-
}: Props = $props();
|
|
49
|
-
|
|
50
|
-
const posStyle = $derived(
|
|
51
|
-
orientation === 'vertical'
|
|
52
|
-
? `left: ${position}px`
|
|
53
|
-
: `top: ${position}px`,
|
|
54
|
-
);
|
|
55
|
-
const colorVar = $derived(color ? `--ni-color: ${color}` : '');
|
|
56
|
-
</script>
|
|
57
|
-
|
|
58
|
-
{#if mode === 'badge'}
|
|
59
|
-
<span class="ni-badge" style={colorVar} role="status" aria-live="polite" aria-label="{L.currentTime}: {time}">
|
|
60
|
-
{#if children}
|
|
61
|
-
{@render children()}
|
|
62
|
-
{:else}
|
|
63
|
-
{L.now}
|
|
64
|
-
{/if}
|
|
65
|
-
</span>
|
|
66
|
-
{:else if mode === 'dot'}
|
|
67
|
-
<div class="ni ni-dot {orientation}" style="{posStyle}; {colorVar}" role="status" aria-label="{L.currentTime}: {time}">
|
|
68
|
-
<div class="ni-dot-circle"></div>
|
|
69
|
-
{#if showLabel && time}
|
|
70
|
-
<div class="ni-label">
|
|
71
|
-
<span class="ni-time">{time}</span>
|
|
72
|
-
{#if seconds}<span class="ni-sec">{seconds}</span>{/if}
|
|
73
|
-
</div>
|
|
74
|
-
{/if}
|
|
75
|
-
</div>
|
|
76
|
-
{:else}
|
|
77
|
-
<div class="ni ni-line {orientation}" style="{posStyle}; {colorVar}" role="status" aria-label="{L.currentTime}: {time}">
|
|
78
|
-
<div class="ni-line-bar"></div>
|
|
79
|
-
{#if showLabel && time}
|
|
80
|
-
<div class="ni-label">
|
|
81
|
-
<span class="ni-time">{time}</span>
|
|
82
|
-
{#if seconds}<span class="ni-sec">{seconds}</span>{/if}
|
|
83
|
-
</div>
|
|
84
|
-
{/if}
|
|
85
|
-
</div>
|
|
86
|
-
{/if}
|
|
87
|
-
|
|
88
|
-
<style>
|
|
89
|
-
.ni {
|
|
90
|
-
position: absolute;
|
|
91
|
-
z-index: 10;
|
|
92
|
-
pointer-events: none;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/* ── Line mode ── */
|
|
96
|
-
.ni-line.vertical {
|
|
97
|
-
top: 0;
|
|
98
|
-
bottom: 0;
|
|
99
|
-
width: 0;
|
|
100
|
-
}
|
|
101
|
-
.ni-line.horizontal {
|
|
102
|
-
left: 0;
|
|
103
|
-
right: 0;
|
|
104
|
-
height: 0;
|
|
105
|
-
}
|
|
106
|
-
.ni-line-bar {
|
|
107
|
-
background: var(--ni-color, var(--dt-accent, #ef4444));
|
|
108
|
-
}
|
|
109
|
-
.ni-line.vertical .ni-line-bar {
|
|
110
|
-
width: 2px;
|
|
111
|
-
height: 100%;
|
|
112
|
-
margin-left: -1px;
|
|
113
|
-
}
|
|
114
|
-
.ni-line.horizontal .ni-line-bar {
|
|
115
|
-
height: 2px;
|
|
116
|
-
width: 100%;
|
|
117
|
-
margin-top: -1px;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/* ── Dot mode ── */
|
|
121
|
-
.ni-dot-circle {
|
|
122
|
-
width: 10px;
|
|
123
|
-
height: 10px;
|
|
124
|
-
border-radius: 50%;
|
|
125
|
-
background: var(--ni-color, var(--dt-accent, #ef4444));
|
|
126
|
-
box-shadow: 0 0 6px var(--ni-color, var(--dt-glow, rgba(239, 68, 68, 0.35)));
|
|
127
|
-
}
|
|
128
|
-
.ni-dot.vertical .ni-dot-circle {
|
|
129
|
-
margin-left: -5px;
|
|
130
|
-
}
|
|
131
|
-
.ni-dot.horizontal .ni-dot-circle {
|
|
132
|
-
margin-top: -5px;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/* ── Label ── */
|
|
136
|
-
.ni-label {
|
|
137
|
-
position: absolute;
|
|
138
|
-
white-space: nowrap;
|
|
139
|
-
font: 600 10px / 1 var(--dt-mono, 'SF Mono', 'Fira Code', monospace);
|
|
140
|
-
color: var(--dt-btn-text, #fff);
|
|
141
|
-
background: var(--ni-color, var(--dt-accent, #ef4444));
|
|
142
|
-
padding: 3px 6px;
|
|
143
|
-
border-radius: 4px;
|
|
144
|
-
}
|
|
145
|
-
.ni-line.vertical .ni-label,
|
|
146
|
-
.ni-dot.vertical .ni-label {
|
|
147
|
-
top: 0;
|
|
148
|
-
transform: translateX(-50%);
|
|
149
|
-
}
|
|
150
|
-
.ni-line.horizontal .ni-label,
|
|
151
|
-
.ni-dot.horizontal .ni-label {
|
|
152
|
-
left: 0;
|
|
153
|
-
transform: translateY(-100%) translateY(-4px);
|
|
154
|
-
}
|
|
155
|
-
.ni-sec {
|
|
156
|
-
opacity: 0.6;
|
|
157
|
-
font-weight: 400;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/* ── Badge mode ── */
|
|
161
|
-
.ni-badge {
|
|
162
|
-
display: inline-flex;
|
|
163
|
-
align-items: center;
|
|
164
|
-
font: 700 9px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
165
|
-
letter-spacing: 0.08em;
|
|
166
|
-
text-transform: uppercase;
|
|
167
|
-
color: var(--dt-btn-text, #fff);
|
|
168
|
-
background: var(--ni-color, var(--dt-accent, #ef4444));
|
|
169
|
-
padding: 2px 7px;
|
|
170
|
-
border-radius: 3px;
|
|
171
|
-
animation: ni-pulse 2s ease-in-out infinite;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
@keyframes ni-pulse {
|
|
175
|
-
0%, 100% { opacity: 1; }
|
|
176
|
-
50% { opacity: 0.7; }
|
|
177
|
-
}
|
|
178
|
-
</style>
|
|
1
|
+
<!--
|
|
2
|
+
NowIndicator — shared "now" marker component.
|
|
3
|
+
|
|
4
|
+
Three visual modes:
|
|
5
|
+
"line" — vertical/horizontal line with time label (default)
|
|
6
|
+
"dot" — circular dot with time label
|
|
7
|
+
"badge" — inline text badge ("now")
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
<NowIndicator position={nowPx} time={clock.hm} seconds={clock.s} />
|
|
11
|
+
<NowIndicator mode="dot" position={nowPx} time={clock.hm} />
|
|
12
|
+
<NowIndicator mode="badge" />
|
|
13
|
+
-->
|
|
14
|
+
<script lang="ts">
|
|
15
|
+
import type { Snippet } from 'svelte';
|
|
16
|
+
import { getLabels } from '../core/locale.js';
|
|
17
|
+
|
|
18
|
+
const L = $derived(getLabels());
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
/** Visual mode */
|
|
22
|
+
mode?: 'line' | 'dot' | 'badge';
|
|
23
|
+
/** Pixel offset for positioning (line/dot modes) */
|
|
24
|
+
position?: number;
|
|
25
|
+
/** Orientation of the line/dot */
|
|
26
|
+
orientation?: 'vertical' | 'horizontal';
|
|
27
|
+
/** Formatted time string (e.g. "14:30") */
|
|
28
|
+
time?: string;
|
|
29
|
+
/** Formatted seconds string (e.g. ":05") */
|
|
30
|
+
seconds?: string;
|
|
31
|
+
/** Show the time label */
|
|
32
|
+
showLabel?: boolean;
|
|
33
|
+
/** Accent color override */
|
|
34
|
+
color?: string;
|
|
35
|
+
/** Custom content slot */
|
|
36
|
+
children?: Snippet;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let {
|
|
40
|
+
mode = 'line',
|
|
41
|
+
position = 0,
|
|
42
|
+
orientation = 'vertical',
|
|
43
|
+
time = '',
|
|
44
|
+
seconds = '',
|
|
45
|
+
showLabel = true,
|
|
46
|
+
color,
|
|
47
|
+
children,
|
|
48
|
+
}: Props = $props();
|
|
49
|
+
|
|
50
|
+
const posStyle = $derived(
|
|
51
|
+
orientation === 'vertical'
|
|
52
|
+
? `left: ${position}px`
|
|
53
|
+
: `top: ${position}px`,
|
|
54
|
+
);
|
|
55
|
+
const colorVar = $derived(color ? `--ni-color: ${color}` : '');
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
{#if mode === 'badge'}
|
|
59
|
+
<span class="ni-badge" style={colorVar} role="status" aria-live="polite" aria-label="{L.currentTime}: {time}">
|
|
60
|
+
{#if children}
|
|
61
|
+
{@render children()}
|
|
62
|
+
{:else}
|
|
63
|
+
{L.now}
|
|
64
|
+
{/if}
|
|
65
|
+
</span>
|
|
66
|
+
{:else if mode === 'dot'}
|
|
67
|
+
<div class="ni ni-dot {orientation}" style="{posStyle}; {colorVar}" role="status" aria-label="{L.currentTime}: {time}">
|
|
68
|
+
<div class="ni-dot-circle"></div>
|
|
69
|
+
{#if showLabel && time}
|
|
70
|
+
<div class="ni-label">
|
|
71
|
+
<span class="ni-time">{time}</span>
|
|
72
|
+
{#if seconds}<span class="ni-sec">{seconds}</span>{/if}
|
|
73
|
+
</div>
|
|
74
|
+
{/if}
|
|
75
|
+
</div>
|
|
76
|
+
{:else}
|
|
77
|
+
<div class="ni ni-line {orientation}" style="{posStyle}; {colorVar}" role="status" aria-label="{L.currentTime}: {time}">
|
|
78
|
+
<div class="ni-line-bar"></div>
|
|
79
|
+
{#if showLabel && time}
|
|
80
|
+
<div class="ni-label">
|
|
81
|
+
<span class="ni-time">{time}</span>
|
|
82
|
+
{#if seconds}<span class="ni-sec">{seconds}</span>{/if}
|
|
83
|
+
</div>
|
|
84
|
+
{/if}
|
|
85
|
+
</div>
|
|
86
|
+
{/if}
|
|
87
|
+
|
|
88
|
+
<style>
|
|
89
|
+
.ni {
|
|
90
|
+
position: absolute;
|
|
91
|
+
z-index: 10;
|
|
92
|
+
pointer-events: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ── Line mode ── */
|
|
96
|
+
.ni-line.vertical {
|
|
97
|
+
top: 0;
|
|
98
|
+
bottom: 0;
|
|
99
|
+
width: 0;
|
|
100
|
+
}
|
|
101
|
+
.ni-line.horizontal {
|
|
102
|
+
left: 0;
|
|
103
|
+
right: 0;
|
|
104
|
+
height: 0;
|
|
105
|
+
}
|
|
106
|
+
.ni-line-bar {
|
|
107
|
+
background: var(--ni-color, var(--dt-accent, #ef4444));
|
|
108
|
+
}
|
|
109
|
+
.ni-line.vertical .ni-line-bar {
|
|
110
|
+
width: 2px;
|
|
111
|
+
height: 100%;
|
|
112
|
+
margin-left: -1px;
|
|
113
|
+
}
|
|
114
|
+
.ni-line.horizontal .ni-line-bar {
|
|
115
|
+
height: 2px;
|
|
116
|
+
width: 100%;
|
|
117
|
+
margin-top: -1px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ── Dot mode ── */
|
|
121
|
+
.ni-dot-circle {
|
|
122
|
+
width: 10px;
|
|
123
|
+
height: 10px;
|
|
124
|
+
border-radius: 50%;
|
|
125
|
+
background: var(--ni-color, var(--dt-accent, #ef4444));
|
|
126
|
+
box-shadow: 0 0 6px var(--ni-color, var(--dt-glow, rgba(239, 68, 68, 0.35)));
|
|
127
|
+
}
|
|
128
|
+
.ni-dot.vertical .ni-dot-circle {
|
|
129
|
+
margin-left: -5px;
|
|
130
|
+
}
|
|
131
|
+
.ni-dot.horizontal .ni-dot-circle {
|
|
132
|
+
margin-top: -5px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* ── Label ── */
|
|
136
|
+
.ni-label {
|
|
137
|
+
position: absolute;
|
|
138
|
+
white-space: nowrap;
|
|
139
|
+
font: 600 10px / 1 var(--dt-mono, 'SF Mono', 'Fira Code', monospace);
|
|
140
|
+
color: var(--dt-btn-text, #fff);
|
|
141
|
+
background: var(--ni-color, var(--dt-accent, #ef4444));
|
|
142
|
+
padding: 3px 6px;
|
|
143
|
+
border-radius: 4px;
|
|
144
|
+
}
|
|
145
|
+
.ni-line.vertical .ni-label,
|
|
146
|
+
.ni-dot.vertical .ni-label {
|
|
147
|
+
top: 0;
|
|
148
|
+
transform: translateX(-50%);
|
|
149
|
+
}
|
|
150
|
+
.ni-line.horizontal .ni-label,
|
|
151
|
+
.ni-dot.horizontal .ni-label {
|
|
152
|
+
left: 0;
|
|
153
|
+
transform: translateY(-100%) translateY(-4px);
|
|
154
|
+
}
|
|
155
|
+
.ni-sec {
|
|
156
|
+
opacity: 0.6;
|
|
157
|
+
font-weight: 400;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* ── Badge mode ── */
|
|
161
|
+
.ni-badge {
|
|
162
|
+
display: inline-flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
font: 700 9px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
165
|
+
letter-spacing: 0.08em;
|
|
166
|
+
text-transform: uppercase;
|
|
167
|
+
color: var(--dt-btn-text, #fff);
|
|
168
|
+
background: var(--ni-color, var(--dt-accent, #ef4444));
|
|
169
|
+
padding: 2px 7px;
|
|
170
|
+
border-radius: 3px;
|
|
171
|
+
animation: ni-pulse 2s ease-in-out infinite;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@keyframes ni-pulse {
|
|
175
|
+
0%, 100% { opacity: 1; }
|
|
176
|
+
50% { opacity: 0.7; }
|
|
177
|
+
}
|
|
178
|
+
</style>
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
TimeGutter — shared hour labels / tick marks.
|
|
3
|
-
|
|
4
|
-
Two orientations:
|
|
1
|
+
<!--
|
|
2
|
+
TimeGutter — shared hour labels / tick marks.
|
|
3
|
+
|
|
4
|
+
Two orientations:
|
|
5
5
|
"horizontal" — ticks along a horizontal track (for day timeline-style layouts)
|
|
6
|
-
"vertical" — label column along a vertical grid (for WeekTimeline style)
|
|
7
|
-
-->
|
|
8
|
-
<script lang="ts">
|
|
9
|
-
import { HOURS } from '../core/time.js';
|
|
10
|
-
import { fmtH } from '../core/locale.js';
|
|
11
|
-
|
|
12
|
-
interface Props {
|
|
13
|
-
orientation?: 'horizontal' | 'vertical';
|
|
14
|
-
/** Pixel size per hour (width for horizontal, height for vertical) */
|
|
15
|
-
hourSize?: number;
|
|
16
|
-
/** Show half-hour ticks (horizontal only) */
|
|
17
|
-
halfHour?: boolean;
|
|
18
|
-
/** Hours to display (default 0-23) */
|
|
19
|
-
hours?: readonly number[];
|
|
20
|
-
/** Custom hour formatter */
|
|
21
|
-
formatHour?: (h: number) => string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let {
|
|
25
|
-
orientation = 'horizontal',
|
|
26
|
-
hourSize = 120,
|
|
27
|
-
halfHour = true,
|
|
28
|
-
hours = HOURS,
|
|
29
|
-
formatHour = fmtH,
|
|
30
|
-
}: Props = $props();
|
|
31
|
-
</script>
|
|
32
|
-
|
|
33
|
-
{#if orientation === 'horizontal'}
|
|
34
|
-
<div class="tg tg-h" style="--tg-hour: {hourSize}px">
|
|
35
|
-
{#each hours as h}
|
|
36
|
-
<div class="tg-tick" style="left: {h * hourSize}px">
|
|
37
|
-
<span class="tg-label">{formatHour(h)}</span>
|
|
38
|
-
</div>
|
|
39
|
-
{#if halfHour}
|
|
40
|
-
<div class="tg-tick tg-tick-half" style="left: {(h + 0.5) * hourSize}px"></div>
|
|
41
|
-
{/if}
|
|
42
|
-
{/each}
|
|
43
|
-
</div>
|
|
44
|
-
{:else}
|
|
45
|
-
<div class="tg tg-v" style="--tg-hour: {hourSize}px">
|
|
46
|
-
{#each hours as h}
|
|
47
|
-
<div class="tg-row" style="top: {h * hourSize}px; height: {hourSize}px">
|
|
48
|
-
<span class="tg-label">{formatHour(h)}</span>
|
|
49
|
-
</div>
|
|
50
|
-
{/each}
|
|
51
|
-
</div>
|
|
52
|
-
{/if}
|
|
53
|
-
|
|
54
|
-
<style>
|
|
55
|
-
.tg {
|
|
56
|
-
position: relative;
|
|
57
|
-
pointer-events: none;
|
|
58
|
-
user-select: none;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/* ── Horizontal ── */
|
|
62
|
-
.tg-h {
|
|
63
|
-
height: 100%;
|
|
64
|
-
width: calc(24 * var(--tg-hour));
|
|
65
|
-
}
|
|
66
|
-
.tg-tick {
|
|
67
|
-
position: absolute;
|
|
68
|
-
top: 0;
|
|
69
|
-
bottom: 0;
|
|
70
|
-
width: 0;
|
|
71
|
-
border-left: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
|
|
72
|
-
}
|
|
73
|
-
.tg-tick-half {
|
|
74
|
-
border-left-style: dashed;
|
|
75
|
-
opacity: 0.4;
|
|
76
|
-
}
|
|
77
|
-
.tg-h .tg-label {
|
|
78
|
-
position: absolute;
|
|
79
|
-
top: 4px;
|
|
80
|
-
left: 6px;
|
|
81
|
-
font: 400 9px / 1 var(--dt-mono, 'SF Mono', 'Fira Code', monospace);
|
|
82
|
-
color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
|
|
83
|
-
white-space: nowrap;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/* ── Vertical ── */
|
|
87
|
-
.tg-v {
|
|
88
|
-
width: 48px;
|
|
89
|
-
flex-shrink: 0;
|
|
90
|
-
}
|
|
91
|
-
.tg-row {
|
|
92
|
-
position: absolute;
|
|
93
|
-
left: 0;
|
|
94
|
-
right: 0;
|
|
95
|
-
display: flex;
|
|
96
|
-
align-items: flex-start;
|
|
97
|
-
justify-content: flex-end;
|
|
98
|
-
padding: 2px 8px 0 0;
|
|
99
|
-
border-top: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
|
|
100
|
-
}
|
|
101
|
-
.tg-v .tg-label {
|
|
102
|
-
font: 400 9px / 1 var(--dt-mono, 'SF Mono', 'Fira Code', monospace);
|
|
103
|
-
color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
|
|
104
|
-
}
|
|
105
|
-
</style>
|
|
6
|
+
"vertical" — label column along a vertical grid (for WeekTimeline style)
|
|
7
|
+
-->
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import { HOURS } from '../core/time.js';
|
|
10
|
+
import { fmtH } from '../core/locale.js';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
orientation?: 'horizontal' | 'vertical';
|
|
14
|
+
/** Pixel size per hour (width for horizontal, height for vertical) */
|
|
15
|
+
hourSize?: number;
|
|
16
|
+
/** Show half-hour ticks (horizontal only) */
|
|
17
|
+
halfHour?: boolean;
|
|
18
|
+
/** Hours to display (default 0-23) */
|
|
19
|
+
hours?: readonly number[];
|
|
20
|
+
/** Custom hour formatter */
|
|
21
|
+
formatHour?: (h: number) => string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
orientation = 'horizontal',
|
|
26
|
+
hourSize = 120,
|
|
27
|
+
halfHour = true,
|
|
28
|
+
hours = HOURS,
|
|
29
|
+
formatHour = fmtH,
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
{#if orientation === 'horizontal'}
|
|
34
|
+
<div class="tg tg-h" style="--tg-hour: {hourSize}px">
|
|
35
|
+
{#each hours as h}
|
|
36
|
+
<div class="tg-tick" style="left: {h * hourSize}px">
|
|
37
|
+
<span class="tg-label">{formatHour(h)}</span>
|
|
38
|
+
</div>
|
|
39
|
+
{#if halfHour}
|
|
40
|
+
<div class="tg-tick tg-tick-half" style="left: {(h + 0.5) * hourSize}px"></div>
|
|
41
|
+
{/if}
|
|
42
|
+
{/each}
|
|
43
|
+
</div>
|
|
44
|
+
{:else}
|
|
45
|
+
<div class="tg tg-v" style="--tg-hour: {hourSize}px">
|
|
46
|
+
{#each hours as h}
|
|
47
|
+
<div class="tg-row" style="top: {h * hourSize}px; height: {hourSize}px">
|
|
48
|
+
<span class="tg-label">{formatHour(h)}</span>
|
|
49
|
+
</div>
|
|
50
|
+
{/each}
|
|
51
|
+
</div>
|
|
52
|
+
{/if}
|
|
53
|
+
|
|
54
|
+
<style>
|
|
55
|
+
.tg {
|
|
56
|
+
position: relative;
|
|
57
|
+
pointer-events: none;
|
|
58
|
+
user-select: none;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ── Horizontal ── */
|
|
62
|
+
.tg-h {
|
|
63
|
+
height: 100%;
|
|
64
|
+
width: calc(24 * var(--tg-hour));
|
|
65
|
+
}
|
|
66
|
+
.tg-tick {
|
|
67
|
+
position: absolute;
|
|
68
|
+
top: 0;
|
|
69
|
+
bottom: 0;
|
|
70
|
+
width: 0;
|
|
71
|
+
border-left: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
|
|
72
|
+
}
|
|
73
|
+
.tg-tick-half {
|
|
74
|
+
border-left-style: dashed;
|
|
75
|
+
opacity: 0.4;
|
|
76
|
+
}
|
|
77
|
+
.tg-h .tg-label {
|
|
78
|
+
position: absolute;
|
|
79
|
+
top: 4px;
|
|
80
|
+
left: 6px;
|
|
81
|
+
font: 400 9px / 1 var(--dt-mono, 'SF Mono', 'Fira Code', monospace);
|
|
82
|
+
color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
|
|
83
|
+
white-space: nowrap;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ── Vertical ── */
|
|
87
|
+
.tg-v {
|
|
88
|
+
width: 48px;
|
|
89
|
+
flex-shrink: 0;
|
|
90
|
+
}
|
|
91
|
+
.tg-row {
|
|
92
|
+
position: absolute;
|
|
93
|
+
left: 0;
|
|
94
|
+
right: 0;
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: flex-start;
|
|
97
|
+
justify-content: flex-end;
|
|
98
|
+
padding: 2px 8px 0 0;
|
|
99
|
+
border-top: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
|
|
100
|
+
}
|
|
101
|
+
.tg-v .tg-label {
|
|
102
|
+
font: 400 9px / 1 var(--dt-mono, 'SF Mono', 'Fira Code', monospace);
|
|
103
|
+
color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
|
|
104
|
+
}
|
|
105
|
+
</style>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Auto Theme — probes the host page and generates --dt-* CSS tokens
|
|
3
|
+
* that blend the calendar into any design system.
|
|
4
|
+
*
|
|
5
|
+
* How it works:
|
|
6
|
+
* 1. Reads computed styles from the element's ancestors (body, parent)
|
|
7
|
+
* 2. Detects light/dark mode from background luminance
|
|
8
|
+
* 3. Extracts fonts, text colors, accent/brand colors
|
|
9
|
+
* 4. Generates a full --dt-* CSS variable string
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const vars = probeHostTheme(calendarElement);
|
|
13
|
+
* // → "--dt-bg: #fff; --dt-text: rgba(0,0,0,0.87); ..."
|
|
14
|
+
*
|
|
15
|
+
* The Calendar component calls this automatically when theme is `auto` (empty string).
|
|
16
|
+
*/
|
|
17
|
+
/** Options for fine-tuning auto-detection behavior. */
|
|
18
|
+
export interface AutoThemeOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Force light or dark mode instead of auto-detecting.
|
|
21
|
+
* Useful when the host page has a known color scheme.
|
|
22
|
+
*/
|
|
23
|
+
mode?: 'light' | 'dark' | 'auto';
|
|
24
|
+
/**
|
|
25
|
+
* Override the accent color (hex). Skips accent probing.
|
|
26
|
+
* Example: '#2563eb'
|
|
27
|
+
*/
|
|
28
|
+
accent?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Override the font stack. Skips font probing.
|
|
31
|
+
* Example: '"Inter", system-ui, sans-serif'
|
|
32
|
+
*/
|
|
33
|
+
font?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Probe the host page surrounding `el` and generate a complete --dt-* CSS string.
|
|
37
|
+
*
|
|
38
|
+
* @param el The calendar's root element (or any element in the host page).
|
|
39
|
+
* @param options Optional overrides for mode, accent, font.
|
|
40
|
+
* @returns A CSS inline-style string of --dt-* custom properties.
|
|
41
|
+
*/
|
|
42
|
+
export declare function probeHostTheme(el: HTMLElement, options?: AutoThemeOptions): string;
|
|
43
|
+
/**
|
|
44
|
+
* Observe changes to the host page that might affect theming
|
|
45
|
+
* (color-scheme toggle, class changes on <html>/<body>, style attribute changes).
|
|
46
|
+
*
|
|
47
|
+
* Returns a cleanup function to stop observing.
|
|
48
|
+
*
|
|
49
|
+
* @param el The calendar's root element.
|
|
50
|
+
* @param callback Called with the new CSS string whenever the host theme changes.
|
|
51
|
+
* @param options Passthrough to probeHostTheme.
|
|
52
|
+
*/
|
|
53
|
+
export declare function observeHostTheme(el: HTMLElement, callback: (vars: string) => void, options?: AutoThemeOptions): () => void;
|