@nomideusz/svelte-calendar 0.6.0 → 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/dist/adapters/recurring.d.ts +7 -0
- package/dist/adapters/recurring.js +4 -3
- package/dist/calendar/Calendar.svelte +776 -767
- package/dist/calendar/Calendar.svelte.d.ts +2 -2
- 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/presets.js +36 -36
- package/dist/views/agenda/AgendaDay.svelte +1073 -1055
- package/dist/views/agenda/AgendaWeek.svelte +934 -924
- package/dist/views/mobile/Mobile.svelte +17 -17
- package/dist/views/mobile/MobileDay.svelte +702 -665
- package/dist/views/mobile/MobileWeek.svelte +461 -456
- package/dist/views/planner/PlannerDay.svelte +1129 -1091
- package/dist/views/planner/PlannerWeek.svelte +949 -945
- package/dist/widget/CalendarWidget.svelte +142 -142
- package/package.json +1 -1
|
@@ -34,8 +34,8 @@ interface Props {
|
|
|
34
34
|
autoTheme?: AutoThemeOptions | false;
|
|
35
35
|
/** Start week on Monday */
|
|
36
36
|
mondayStart?: boolean;
|
|
37
|
-
/** Total height */
|
|
38
|
-
height?: number;
|
|
37
|
+
/** Total height. Use `'auto'` to let content determine height (ideal for Agenda views). */
|
|
38
|
+
height?: number | 'auto';
|
|
39
39
|
/** Border radius in px (default: 12). Set to 0 for no rounding. */
|
|
40
40
|
borderRadius?: number;
|
|
41
41
|
/** Text direction: 'ltr' (default), 'rtl', or 'auto' */
|
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
DayHeader — shared day label primitive.
|
|
3
|
-
|
|
4
|
-
Handles both:
|
|
5
|
-
- Relative labels ("Today", "Tomorrow") for day views via fmtDay
|
|
6
|
-
- Short/long weekday + date number for week views
|
|
7
|
-
-->
|
|
8
|
-
<script lang="ts">
|
|
9
|
-
import { weekdayShort, weekdayLong, monthShort, fmtDay } from '../core/locale.js';
|
|
10
|
-
|
|
11
|
-
interface Props {
|
|
12
|
-
/** Timestamp (ms) of the day to label */
|
|
13
|
-
dayMs: number;
|
|
14
|
-
/** Timestamp (ms) of today (for relative labels) */
|
|
15
|
-
todayMs?: number;
|
|
16
|
-
/** Display format */
|
|
17
|
-
format?: 'relative' | 'short' | 'long';
|
|
18
|
-
/** Whether this day is "today" */
|
|
19
|
-
isToday?: boolean;
|
|
20
|
-
/** Whether this day is in the past */
|
|
21
|
-
isPast?: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let {
|
|
25
|
-
dayMs,
|
|
26
|
-
todayMs = Date.now(),
|
|
27
|
-
format = 'short',
|
|
28
|
-
isToday = false,
|
|
29
|
-
isPast = false,
|
|
30
|
-
}: Props = $props();
|
|
31
|
-
|
|
32
|
-
const dayNum = $derived(new Date(dayMs).getDate());
|
|
33
|
-
</script>
|
|
34
|
-
|
|
35
|
-
<div
|
|
36
|
-
class="dh"
|
|
37
|
-
class:dh-today={isToday}
|
|
38
|
-
class:dh-past={isPast}
|
|
39
|
-
>
|
|
40
|
-
{#if format === 'relative'}
|
|
41
|
-
<span class="dh-rel">{fmtDay(dayMs, todayMs)}</span>
|
|
42
|
-
{:else if format === 'long'}
|
|
43
|
-
<span class="dh-name dh-name-long">{weekdayLong(dayMs)}</span>
|
|
44
|
-
<span class="dh-date">{monthShort(dayMs)} {dayNum}</span>
|
|
45
|
-
{:else}
|
|
46
|
-
<span class="dh-name">{weekdayShort(dayMs)}</span>
|
|
47
|
-
<span class="dh-num" class:dh-num-today={isToday}>{dayNum}</span>
|
|
48
|
-
{/if}
|
|
49
|
-
</div>
|
|
50
|
-
|
|
51
|
-
<style>
|
|
52
|
-
.dh {
|
|
53
|
-
display: flex;
|
|
54
|
-
flex-direction: column;
|
|
55
|
-
align-items: center;
|
|
56
|
-
gap: 2px;
|
|
57
|
-
user-select: none;
|
|
58
|
-
}
|
|
59
|
-
.dh-past {
|
|
60
|
-
opacity: 0.45;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.dh-name {
|
|
64
|
-
font: 500 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
65
|
-
letter-spacing: 0.04em;
|
|
66
|
-
text-transform: uppercase;
|
|
67
|
-
color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
|
|
68
|
-
}
|
|
69
|
-
.dh-name-long {
|
|
70
|
-
font-size: 12px;
|
|
71
|
-
text-transform: none;
|
|
72
|
-
letter-spacing: 0.01em;
|
|
73
|
-
color: var(--dt-text, rgba(226, 232, 240, 0.85));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.dh-num {
|
|
77
|
-
font: 600 14px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
78
|
-
color: var(--dt-text, rgba(226, 232, 240, 0.85));
|
|
79
|
-
width: 28px;
|
|
80
|
-
height: 28px;
|
|
81
|
-
display: flex;
|
|
82
|
-
align-items: center;
|
|
83
|
-
justify-content: center;
|
|
84
|
-
border-radius: 50%;
|
|
85
|
-
}
|
|
86
|
-
.dh-num-today {
|
|
87
|
-
background: var(--dt-accent, #ef4444);
|
|
88
|
-
color: var(--dt-btn-text, #fff);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.dh-date {
|
|
92
|
-
font: 400 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
93
|
-
color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.dh-rel {
|
|
97
|
-
font: 500 12px / 1.3 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
98
|
-
color: var(--dt-text, rgba(226, 232, 240, 0.85));
|
|
99
|
-
}
|
|
100
|
-
.dh-today .dh-rel {
|
|
101
|
-
color: var(--dt-accent, #ef4444);
|
|
102
|
-
}
|
|
103
|
-
</style>
|
|
1
|
+
<!--
|
|
2
|
+
DayHeader — shared day label primitive.
|
|
3
|
+
|
|
4
|
+
Handles both:
|
|
5
|
+
- Relative labels ("Today", "Tomorrow") for day views via fmtDay
|
|
6
|
+
- Short/long weekday + date number for week views
|
|
7
|
+
-->
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import { weekdayShort, weekdayLong, monthShort, fmtDay } from '../core/locale.js';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
/** Timestamp (ms) of the day to label */
|
|
13
|
+
dayMs: number;
|
|
14
|
+
/** Timestamp (ms) of today (for relative labels) */
|
|
15
|
+
todayMs?: number;
|
|
16
|
+
/** Display format */
|
|
17
|
+
format?: 'relative' | 'short' | 'long';
|
|
18
|
+
/** Whether this day is "today" */
|
|
19
|
+
isToday?: boolean;
|
|
20
|
+
/** Whether this day is in the past */
|
|
21
|
+
isPast?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
dayMs,
|
|
26
|
+
todayMs = Date.now(),
|
|
27
|
+
format = 'short',
|
|
28
|
+
isToday = false,
|
|
29
|
+
isPast = false,
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
const dayNum = $derived(new Date(dayMs).getDate());
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<div
|
|
36
|
+
class="dh"
|
|
37
|
+
class:dh-today={isToday}
|
|
38
|
+
class:dh-past={isPast}
|
|
39
|
+
>
|
|
40
|
+
{#if format === 'relative'}
|
|
41
|
+
<span class="dh-rel">{fmtDay(dayMs, todayMs)}</span>
|
|
42
|
+
{:else if format === 'long'}
|
|
43
|
+
<span class="dh-name dh-name-long">{weekdayLong(dayMs)}</span>
|
|
44
|
+
<span class="dh-date">{monthShort(dayMs)} {dayNum}</span>
|
|
45
|
+
{:else}
|
|
46
|
+
<span class="dh-name">{weekdayShort(dayMs)}</span>
|
|
47
|
+
<span class="dh-num" class:dh-num-today={isToday}>{dayNum}</span>
|
|
48
|
+
{/if}
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<style>
|
|
52
|
+
.dh {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 2px;
|
|
57
|
+
user-select: none;
|
|
58
|
+
}
|
|
59
|
+
.dh-past {
|
|
60
|
+
opacity: 0.45;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.dh-name {
|
|
64
|
+
font: 500 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
65
|
+
letter-spacing: 0.04em;
|
|
66
|
+
text-transform: uppercase;
|
|
67
|
+
color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
|
|
68
|
+
}
|
|
69
|
+
.dh-name-long {
|
|
70
|
+
font-size: 12px;
|
|
71
|
+
text-transform: none;
|
|
72
|
+
letter-spacing: 0.01em;
|
|
73
|
+
color: var(--dt-text, rgba(226, 232, 240, 0.85));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.dh-num {
|
|
77
|
+
font: 600 14px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
78
|
+
color: var(--dt-text, rgba(226, 232, 240, 0.85));
|
|
79
|
+
width: 28px;
|
|
80
|
+
height: 28px;
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
border-radius: 50%;
|
|
85
|
+
}
|
|
86
|
+
.dh-num-today {
|
|
87
|
+
background: var(--dt-accent, #ef4444);
|
|
88
|
+
color: var(--dt-btn-text, #fff);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.dh-date {
|
|
92
|
+
font: 400 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
93
|
+
color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.dh-rel {
|
|
97
|
+
font: 500 12px / 1.3 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
98
|
+
color: var(--dt-text, rgba(226, 232, 240, 0.85));
|
|
99
|
+
}
|
|
100
|
+
.dh-today .dh-rel {
|
|
101
|
+
color: var(--dt-accent, #ef4444);
|
|
102
|
+
}
|
|
103
|
+
</style>
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
EmptySlot — clickable empty time slot for "click to create" interactions.
|
|
3
|
-
|
|
4
|
-
Renders a transparent hit-target that highlights on hover and emits
|
|
5
|
-
the time range when clicked. View components place these in gaps
|
|
6
|
-
between events.
|
|
7
|
-
-->
|
|
8
|
-
<script lang="ts">
|
|
9
|
-
import { fmtTime, fmtDuration, getLabels } from '../core/locale.js';
|
|
10
|
-
|
|
11
|
-
const L = $derived(getLabels());
|
|
12
|
-
|
|
13
|
-
interface Props {
|
|
14
|
-
/** Start time of the empty slot */
|
|
15
|
-
start: Date;
|
|
16
|
-
/** End time of the empty slot */
|
|
17
|
-
end: Date;
|
|
18
|
-
/** Click handler — create event in this range */
|
|
19
|
-
onclick?: (range: { start: Date; end: Date }) => void;
|
|
20
|
-
/** Orientation for cursor hint */
|
|
21
|
-
orientation?: 'horizontal' | 'vertical';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let {
|
|
25
|
-
start,
|
|
26
|
-
end,
|
|
27
|
-
onclick,
|
|
28
|
-
orientation = 'vertical',
|
|
29
|
-
}: Props = $props();
|
|
30
|
-
|
|
31
|
-
const dur = $derived(`${fmtDuration(start, end)} ${L.free}`);
|
|
32
|
-
|
|
33
|
-
function handleKeydown(e: KeyboardEvent) {
|
|
34
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
35
|
-
e.preventDefault();
|
|
36
|
-
onclick?.({ start, end });
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
</script>
|
|
40
|
-
|
|
41
|
-
<div
|
|
42
|
-
class="es"
|
|
43
|
-
class:es-v={orientation === 'vertical'}
|
|
44
|
-
class:es-h={orientation === 'horizontal'}
|
|
45
|
-
role="button"
|
|
46
|
-
tabindex="0"
|
|
47
|
-
aria-label="{L.createEvent}, {fmtTime(start)} to {fmtTime(end)}, {dur}"
|
|
48
|
-
onclick={() => onclick?.({ start, end })}
|
|
49
|
-
onkeydown={handleKeydown}
|
|
50
|
-
>
|
|
51
|
-
<div class="es-hint" aria-hidden="true">
|
|
52
|
-
<span class="es-plus">+</span>
|
|
53
|
-
<span class="es-range">{fmtTime(start)} – {fmtTime(end)}</span>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
<style>
|
|
58
|
-
.es {
|
|
59
|
-
position: relative;
|
|
60
|
-
cursor: pointer;
|
|
61
|
-
border: 1px dashed transparent;
|
|
62
|
-
border-radius: 6px;
|
|
63
|
-
transition: border-color 150ms, background 150ms;
|
|
64
|
-
min-height: 24px;
|
|
65
|
-
}
|
|
66
|
-
.es:hover {
|
|
67
|
-
border-color: var(--dt-accent-dim, rgba(239, 68, 68, 0.18));
|
|
68
|
-
background: var(--dt-accent-dim, rgba(239, 68, 68, 0.05));
|
|
69
|
-
}
|
|
70
|
-
.es:focus-visible {
|
|
71
|
-
outline: 2px solid var(--dt-accent, #ef4444);
|
|
72
|
-
outline-offset: 2px;
|
|
73
|
-
border-color: var(--dt-accent-dim, rgba(239, 68, 68, 0.18));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.es-hint {
|
|
77
|
-
position: absolute;
|
|
78
|
-
inset: 0;
|
|
79
|
-
display: flex;
|
|
80
|
-
align-items: center;
|
|
81
|
-
justify-content: center;
|
|
82
|
-
gap: 6px;
|
|
83
|
-
opacity: 0;
|
|
84
|
-
transition: opacity 150ms;
|
|
85
|
-
}
|
|
86
|
-
.es:hover .es-hint {
|
|
87
|
-
opacity: 1;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.es-plus {
|
|
91
|
-
font: 500 14px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
92
|
-
color: var(--dt-accent, #ef4444);
|
|
93
|
-
}
|
|
94
|
-
.es-range {
|
|
95
|
-
font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
|
|
96
|
-
color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.es-v {
|
|
100
|
-
width: 100%;
|
|
101
|
-
}
|
|
102
|
-
.es-h {
|
|
103
|
-
height: 100%;
|
|
104
|
-
}
|
|
105
|
-
</style>
|
|
1
|
+
<!--
|
|
2
|
+
EmptySlot — clickable empty time slot for "click to create" interactions.
|
|
3
|
+
|
|
4
|
+
Renders a transparent hit-target that highlights on hover and emits
|
|
5
|
+
the time range when clicked. View components place these in gaps
|
|
6
|
+
between events.
|
|
7
|
+
-->
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import { fmtTime, fmtDuration, getLabels } from '../core/locale.js';
|
|
10
|
+
|
|
11
|
+
const L = $derived(getLabels());
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
/** Start time of the empty slot */
|
|
15
|
+
start: Date;
|
|
16
|
+
/** End time of the empty slot */
|
|
17
|
+
end: Date;
|
|
18
|
+
/** Click handler — create event in this range */
|
|
19
|
+
onclick?: (range: { start: Date; end: Date }) => void;
|
|
20
|
+
/** Orientation for cursor hint */
|
|
21
|
+
orientation?: 'horizontal' | 'vertical';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
start,
|
|
26
|
+
end,
|
|
27
|
+
onclick,
|
|
28
|
+
orientation = 'vertical',
|
|
29
|
+
}: Props = $props();
|
|
30
|
+
|
|
31
|
+
const dur = $derived(`${fmtDuration(start, end)} ${L.free}`);
|
|
32
|
+
|
|
33
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
34
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
onclick?.({ start, end });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<div
|
|
42
|
+
class="es"
|
|
43
|
+
class:es-v={orientation === 'vertical'}
|
|
44
|
+
class:es-h={orientation === 'horizontal'}
|
|
45
|
+
role="button"
|
|
46
|
+
tabindex="0"
|
|
47
|
+
aria-label="{L.createEvent}, {fmtTime(start)} to {fmtTime(end)}, {dur}"
|
|
48
|
+
onclick={() => onclick?.({ start, end })}
|
|
49
|
+
onkeydown={handleKeydown}
|
|
50
|
+
>
|
|
51
|
+
<div class="es-hint" aria-hidden="true">
|
|
52
|
+
<span class="es-plus">+</span>
|
|
53
|
+
<span class="es-range">{fmtTime(start)} – {fmtTime(end)}</span>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<style>
|
|
58
|
+
.es {
|
|
59
|
+
position: relative;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
border: 1px dashed transparent;
|
|
62
|
+
border-radius: 6px;
|
|
63
|
+
transition: border-color 150ms, background 150ms;
|
|
64
|
+
min-height: 24px;
|
|
65
|
+
}
|
|
66
|
+
.es:hover {
|
|
67
|
+
border-color: var(--dt-accent-dim, rgba(239, 68, 68, 0.18));
|
|
68
|
+
background: var(--dt-accent-dim, rgba(239, 68, 68, 0.05));
|
|
69
|
+
}
|
|
70
|
+
.es:focus-visible {
|
|
71
|
+
outline: 2px solid var(--dt-accent, #ef4444);
|
|
72
|
+
outline-offset: 2px;
|
|
73
|
+
border-color: var(--dt-accent-dim, rgba(239, 68, 68, 0.18));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.es-hint {
|
|
77
|
+
position: absolute;
|
|
78
|
+
inset: 0;
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: center;
|
|
82
|
+
gap: 6px;
|
|
83
|
+
opacity: 0;
|
|
84
|
+
transition: opacity 150ms;
|
|
85
|
+
}
|
|
86
|
+
.es:hover .es-hint {
|
|
87
|
+
opacity: 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.es-plus {
|
|
91
|
+
font: 500 14px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
|
|
92
|
+
color: var(--dt-accent, #ef4444);
|
|
93
|
+
}
|
|
94
|
+
.es-range {
|
|
95
|
+
font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
|
|
96
|
+
color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.es-v {
|
|
100
|
+
width: 100%;
|
|
101
|
+
}
|
|
102
|
+
.es-h {
|
|
103
|
+
height: 100%;
|
|
104
|
+
}
|
|
105
|
+
</style>
|