@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.
@@ -1,312 +1,312 @@
1
- <!--
2
- EventBlock — shared event rendering primitive.
3
-
4
- Three display variants:
5
- "chip" — compact colored chip (dot + title)
6
- "card" — full card (title, time, duration, color stripe)
7
- "row" — full-width row (time, title, duration — agenda style)
8
-
9
- Emits: onclick, ondragstart, onresize (for future interaction wiring)
10
- -->
11
- <script lang="ts">
12
- import type { TimelineEvent } from '../core/types.js';
13
- import { fmtTime, fmtDuration, getLabels } from '../core/locale.js';
14
- import type { Snippet } from 'svelte';
15
-
16
- const L = $derived(getLabels());
17
-
18
- interface Props {
19
- event: TimelineEvent;
20
- /** Display variant */
21
- variant?: 'chip' | 'card' | 'row';
22
- /** Is this event currently in-progress? */
23
- active?: boolean;
24
- /** Is this event in the past? */
25
- past?: boolean;
26
- /** Show the time range */
27
- showTime?: boolean;
28
- /** Show the duration */
29
- showDuration?: boolean;
30
- /** Editable (shows resize handles in future) */
31
- editable?: boolean;
32
- /** Click handler */
33
- onclick?: (event: TimelineEvent) => void;
34
- /** Custom content slot */
35
- children?: Snippet<[TimelineEvent]>;
36
- }
37
-
38
- let {
39
- event,
40
- variant = 'chip',
41
- active = false,
42
- past = false,
43
- showTime = false,
44
- showDuration = false,
45
- editable = false,
46
- onclick,
47
- children,
48
- }: Props = $props();
49
-
50
- const accentColor = $derived(event.color || 'var(--dt-accent, #ef4444)');
51
-
52
- const ariaLabel = $derived.by(() => {
53
- const t = event.title;
54
- const time = `${fmtTime(event.start)} to ${fmtTime(event.end)}`;
55
- const dur = fmtDuration(event.start, event.end);
56
- const status = active ? `, ${L.happeningNow}` : past ? `, ${L.past}` : '';
57
- return `${t}, ${time}, ${dur}${status}`;
58
- });
59
-
60
- function handleKeydown(e: KeyboardEvent) {
61
- if (e.key === 'Enter' || e.key === ' ') {
62
- e.preventDefault();
63
- onclick?.(event);
64
- }
65
- }
66
- </script>
67
-
68
- {#snippet content()}
69
- {#if children}
70
- {@render children(event)}
71
- {:else if variant === 'chip'}
72
- <span class="eb-dot"></span>
73
- <span class="eb-title">{event.title}</span>
74
- {#if active}<span class="eb-live">●</span>{/if}
75
- {:else if variant === 'card'}
76
- <div class="eb-stripe"></div>
77
- <div class="eb-body">
78
- <span class="eb-title">{event.title}</span>
79
- {#if event.subtitle}
80
- <span class="eb-subtitle">{event.subtitle}</span>
81
- {/if}
82
- {#if showTime}
83
- <span class="eb-time">{fmtTime(event.start)} – {fmtTime(event.end)}</span>
84
- {/if}
85
- {#if showDuration}
86
- <span class="eb-dur">{fmtDuration(event.start, event.end)}</span>
87
- {/if}
88
- {#if event.tags && event.tags.length > 0}
89
- <div class="eb-tags">
90
- {#each event.tags as tag}
91
- <span class="eb-tag">{tag}</span>
92
- {/each}
93
- </div>
94
- {/if}
95
- {#if active}<span class="eb-live-badge">{L.now}</span>{/if}
96
- </div>
97
- {:else}
98
- <!-- row -->
99
- <span class="eb-stripe"></span>
100
- <span class="eb-time">{fmtTime(event.start)} – {fmtTime(event.end)}</span>
101
- <span class="eb-title">{event.title}</span>
102
- {#if event.subtitle}
103
- <span class="eb-subtitle">{event.subtitle}</span>
104
- {/if}
105
- {#if showDuration}
106
- <span class="eb-dur">{fmtDuration(event.start, event.end)}</span>
107
- {/if}
108
- {#if event.tags && event.tags.length > 0}
109
- <span class="eb-tags">
110
- {#each event.tags as tag}
111
- <span class="eb-tag">{tag}</span>
112
- {/each}
113
- </span>
114
- {/if}
115
- {#if active}<span class="eb-live-badge">{L.now}</span>{/if}
116
- {/if}
117
- {/snippet}
118
-
119
- {#if onclick}
120
- <div
121
- class="eb eb-{variant}"
122
- class:eb-active={active}
123
- class:eb-past={past}
124
- class:eb-editable={editable}
125
- style="--eb-color: {accentColor}"
126
- role="button"
127
- tabindex="0"
128
- aria-label={ariaLabel}
129
- aria-current={active ? 'true' : undefined}
130
- onclick={() => onclick?.(event)}
131
- onkeydown={handleKeydown}
132
- >
133
- {@render content()}
134
- </div>
135
- {:else}
136
- <div
137
- class="eb eb-{variant}"
138
- class:eb-active={active}
139
- class:eb-past={past}
140
- class:eb-editable={editable}
141
- style="--eb-color: {accentColor}"
142
- role="article"
143
- aria-label={ariaLabel}
144
- aria-current={active ? 'true' : undefined}
145
- >
146
- {@render content()}
147
- </div>
148
- {/if}
149
-
150
- <style>
151
- .eb {
152
- --_color: var(--eb-color, var(--dt-accent, #ef4444));
153
- cursor: default;
154
- transition: opacity 120ms, transform 80ms;
155
- }
156
- .eb-editable {
157
- cursor: pointer;
158
- }
159
- .eb-editable:hover {
160
- transform: translateY(-1px);
161
- }
162
- .eb:focus-visible {
163
- outline: 2px solid var(--dt-accent, #ef4444);
164
- outline-offset: 2px;
165
- border-radius: 4px;
166
- }
167
- .eb-past {
168
- opacity: 0.5;
169
- }
170
-
171
- /* ── Chip ── */
172
- .eb-chip {
173
- display: inline-flex;
174
- align-items: center;
175
- gap: 5px;
176
- font: 400 11px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
177
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
178
- white-space: nowrap;
179
- overflow: hidden;
180
- text-overflow: ellipsis;
181
- }
182
- .eb-chip .eb-dot {
183
- width: 6px;
184
- height: 6px;
185
- border-radius: 50%;
186
- background: var(--_color);
187
- flex-shrink: 0;
188
- }
189
- .eb-chip .eb-title {
190
- overflow: hidden;
191
- text-overflow: ellipsis;
192
- }
193
- .eb-live {
194
- color: var(--_color);
195
- font-size: 8px;
196
- animation: eb-blink 1.5s ease-in-out infinite;
197
- }
198
-
199
- /* ── Card ── */
200
- .eb-card {
201
- display: flex;
202
- border-radius: 8px;
203
- background: var(--dt-surface, #10141c);
204
- border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
205
- overflow: hidden;
206
- }
207
- .eb-card .eb-stripe {
208
- width: 4px;
209
- flex-shrink: 0;
210
- background: var(--_color);
211
- }
212
- .eb-card .eb-body {
213
- display: flex;
214
- flex-direction: column;
215
- gap: 3px;
216
- padding: 8px 10px;
217
- min-width: 0;
218
- }
219
- .eb-card .eb-title {
220
- font: 500 12px / 1.3 var(--dt-sans, 'Outfit', system-ui, sans-serif);
221
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
222
- overflow: hidden;
223
- text-overflow: ellipsis;
224
- white-space: nowrap;
225
- }
226
- .eb-card .eb-time {
227
- font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
228
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
229
- }
230
- .eb-card .eb-dur {
231
- font: 300 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
232
- color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
233
- }
234
-
235
- /* ── Row ── */
236
- .eb-row {
237
- display: flex;
238
- align-items: center;
239
- gap: 10px;
240
- padding: 6px 12px;
241
- border-bottom: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
242
- }
243
- .eb-row .eb-stripe {
244
- width: 3px;
245
- height: 100%;
246
- min-height: 20px;
247
- border-radius: 2px;
248
- background: var(--_color);
249
- flex-shrink: 0;
250
- }
251
- .eb-row .eb-time {
252
- font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
253
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
254
- flex-shrink: 0;
255
- width: 90px;
256
- }
257
- .eb-row .eb-title {
258
- font: 400 12px / 1.3 var(--dt-sans, 'Outfit', system-ui, sans-serif);
259
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
260
- flex: 1;
261
- overflow: hidden;
262
- text-overflow: ellipsis;
263
- white-space: nowrap;
264
- }
265
- .eb-row .eb-dur {
266
- font: 300 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
267
- color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
268
- flex-shrink: 0;
269
- }
270
-
271
- /* ── Shared ── */
272
- .eb-subtitle {
273
- font: 400 10px / 1.2 var(--dt-sans, 'Outfit', system-ui, sans-serif);
274
- color: var(--dt-text-2, rgba(148, 163, 184, 0.65));
275
- overflow: hidden;
276
- text-overflow: ellipsis;
277
- white-space: nowrap;
278
- }
279
- .eb-tags {
280
- display: inline-flex;
281
- flex-wrap: wrap;
282
- gap: 3px;
283
- }
284
- .eb-tag {
285
- display: inline-block;
286
- font: 500 8px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
287
- letter-spacing: 0.04em;
288
- text-transform: uppercase;
289
- padding: 2px 5px;
290
- border-radius: 3px;
291
- background: color-mix(in srgb, var(--_color) 18%, transparent);
292
- color: var(--_color);
293
- }
294
- .eb-live-badge {
295
- display: inline-flex;
296
- align-items: center;
297
- font: 700 8px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
298
- letter-spacing: 0.08em;
299
- text-transform: uppercase;
300
- color: var(--dt-btn-text, #fff);
301
- background: var(--_color);
302
- padding: 2px 6px;
303
- border-radius: 3px;
304
- width: fit-content;
305
- animation: eb-blink 2s ease-in-out infinite;
306
- }
307
-
308
- @keyframes eb-blink {
309
- 0%, 100% { opacity: 1; }
310
- 50% { opacity: 0.6; }
311
- }
312
- </style>
1
+ <!--
2
+ EventBlock — shared event rendering primitive.
3
+
4
+ Three display variants:
5
+ "chip" — compact colored chip (dot + title)
6
+ "card" — full card (title, time, duration, color stripe)
7
+ "row" — full-width row (time, title, duration — agenda style)
8
+
9
+ Emits: onclick, ondragstart, onresize (for future interaction wiring)
10
+ -->
11
+ <script lang="ts">
12
+ import type { TimelineEvent } from '../core/types.js';
13
+ import { fmtTime, fmtDuration, getLabels } from '../core/locale.js';
14
+ import type { Snippet } from 'svelte';
15
+
16
+ const L = $derived(getLabels());
17
+
18
+ interface Props {
19
+ event: TimelineEvent;
20
+ /** Display variant */
21
+ variant?: 'chip' | 'card' | 'row';
22
+ /** Is this event currently in-progress? */
23
+ active?: boolean;
24
+ /** Is this event in the past? */
25
+ past?: boolean;
26
+ /** Show the time range */
27
+ showTime?: boolean;
28
+ /** Show the duration */
29
+ showDuration?: boolean;
30
+ /** Editable (shows resize handles in future) */
31
+ editable?: boolean;
32
+ /** Click handler */
33
+ onclick?: (event: TimelineEvent) => void;
34
+ /** Custom content slot */
35
+ children?: Snippet<[TimelineEvent]>;
36
+ }
37
+
38
+ let {
39
+ event,
40
+ variant = 'chip',
41
+ active = false,
42
+ past = false,
43
+ showTime = false,
44
+ showDuration = false,
45
+ editable = false,
46
+ onclick,
47
+ children,
48
+ }: Props = $props();
49
+
50
+ const accentColor = $derived(event.color || 'var(--dt-accent, #ef4444)');
51
+
52
+ const ariaLabel = $derived.by(() => {
53
+ const t = event.title;
54
+ const time = `${fmtTime(event.start)} to ${fmtTime(event.end)}`;
55
+ const dur = fmtDuration(event.start, event.end);
56
+ const status = active ? `, ${L.happeningNow}` : past ? `, ${L.past}` : '';
57
+ return `${t}, ${time}, ${dur}${status}`;
58
+ });
59
+
60
+ function handleKeydown(e: KeyboardEvent) {
61
+ if (e.key === 'Enter' || e.key === ' ') {
62
+ e.preventDefault();
63
+ onclick?.(event);
64
+ }
65
+ }
66
+ </script>
67
+
68
+ {#snippet content()}
69
+ {#if children}
70
+ {@render children(event)}
71
+ {:else if variant === 'chip'}
72
+ <span class="eb-dot"></span>
73
+ <span class="eb-title">{event.title}</span>
74
+ {#if active}<span class="eb-live">●</span>{/if}
75
+ {:else if variant === 'card'}
76
+ <div class="eb-stripe"></div>
77
+ <div class="eb-body">
78
+ <span class="eb-title">{event.title}</span>
79
+ {#if event.subtitle}
80
+ <span class="eb-subtitle">{event.subtitle}</span>
81
+ {/if}
82
+ {#if showTime}
83
+ <span class="eb-time">{fmtTime(event.start)} – {fmtTime(event.end)}</span>
84
+ {/if}
85
+ {#if showDuration}
86
+ <span class="eb-dur">{fmtDuration(event.start, event.end)}</span>
87
+ {/if}
88
+ {#if event.tags && event.tags.length > 0}
89
+ <div class="eb-tags">
90
+ {#each event.tags as tag}
91
+ <span class="eb-tag">{tag}</span>
92
+ {/each}
93
+ </div>
94
+ {/if}
95
+ {#if active}<span class="eb-live-badge">{L.now}</span>{/if}
96
+ </div>
97
+ {:else}
98
+ <!-- row -->
99
+ <span class="eb-stripe"></span>
100
+ <span class="eb-time">{fmtTime(event.start)} – {fmtTime(event.end)}</span>
101
+ <span class="eb-title">{event.title}</span>
102
+ {#if event.subtitle}
103
+ <span class="eb-subtitle">{event.subtitle}</span>
104
+ {/if}
105
+ {#if showDuration}
106
+ <span class="eb-dur">{fmtDuration(event.start, event.end)}</span>
107
+ {/if}
108
+ {#if event.tags && event.tags.length > 0}
109
+ <span class="eb-tags">
110
+ {#each event.tags as tag}
111
+ <span class="eb-tag">{tag}</span>
112
+ {/each}
113
+ </span>
114
+ {/if}
115
+ {#if active}<span class="eb-live-badge">{L.now}</span>{/if}
116
+ {/if}
117
+ {/snippet}
118
+
119
+ {#if onclick}
120
+ <div
121
+ class="eb eb-{variant}"
122
+ class:eb-active={active}
123
+ class:eb-past={past}
124
+ class:eb-editable={editable}
125
+ style="--eb-color: {accentColor}"
126
+ role="button"
127
+ tabindex="0"
128
+ aria-label={ariaLabel}
129
+ aria-current={active ? 'true' : undefined}
130
+ onclick={() => onclick?.(event)}
131
+ onkeydown={handleKeydown}
132
+ >
133
+ {@render content()}
134
+ </div>
135
+ {:else}
136
+ <div
137
+ class="eb eb-{variant}"
138
+ class:eb-active={active}
139
+ class:eb-past={past}
140
+ class:eb-editable={editable}
141
+ style="--eb-color: {accentColor}"
142
+ role="article"
143
+ aria-label={ariaLabel}
144
+ aria-current={active ? 'true' : undefined}
145
+ >
146
+ {@render content()}
147
+ </div>
148
+ {/if}
149
+
150
+ <style>
151
+ .eb {
152
+ --_color: var(--eb-color, var(--dt-accent, #ef4444));
153
+ cursor: default;
154
+ transition: opacity 120ms, transform 80ms;
155
+ }
156
+ .eb-editable {
157
+ cursor: pointer;
158
+ }
159
+ .eb-editable:hover {
160
+ transform: translateY(-1px);
161
+ }
162
+ .eb:focus-visible {
163
+ outline: 2px solid var(--dt-accent, #ef4444);
164
+ outline-offset: 2px;
165
+ border-radius: 4px;
166
+ }
167
+ .eb-past {
168
+ opacity: 0.5;
169
+ }
170
+
171
+ /* ── Chip ── */
172
+ .eb-chip {
173
+ display: inline-flex;
174
+ align-items: center;
175
+ gap: 5px;
176
+ font: 400 11px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
177
+ color: var(--dt-text, rgba(226, 232, 240, 0.85));
178
+ white-space: nowrap;
179
+ overflow: hidden;
180
+ text-overflow: ellipsis;
181
+ }
182
+ .eb-chip .eb-dot {
183
+ width: 6px;
184
+ height: 6px;
185
+ border-radius: 50%;
186
+ background: var(--_color);
187
+ flex-shrink: 0;
188
+ }
189
+ .eb-chip .eb-title {
190
+ overflow: hidden;
191
+ text-overflow: ellipsis;
192
+ }
193
+ .eb-live {
194
+ color: var(--_color);
195
+ font-size: 8px;
196
+ animation: eb-blink 1.5s ease-in-out infinite;
197
+ }
198
+
199
+ /* ── Card ── */
200
+ .eb-card {
201
+ display: flex;
202
+ border-radius: 8px;
203
+ background: var(--dt-surface, #10141c);
204
+ border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
205
+ overflow: hidden;
206
+ }
207
+ .eb-card .eb-stripe {
208
+ width: 4px;
209
+ flex-shrink: 0;
210
+ background: var(--_color);
211
+ }
212
+ .eb-card .eb-body {
213
+ display: flex;
214
+ flex-direction: column;
215
+ gap: 3px;
216
+ padding: 8px 10px;
217
+ min-width: 0;
218
+ }
219
+ .eb-card .eb-title {
220
+ font: 500 12px / 1.3 var(--dt-sans, 'Outfit', system-ui, sans-serif);
221
+ color: var(--dt-text, rgba(226, 232, 240, 0.85));
222
+ overflow: hidden;
223
+ text-overflow: ellipsis;
224
+ white-space: nowrap;
225
+ }
226
+ .eb-card .eb-time {
227
+ font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
228
+ color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
229
+ }
230
+ .eb-card .eb-dur {
231
+ font: 300 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
232
+ color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
233
+ }
234
+
235
+ /* ── Row ── */
236
+ .eb-row {
237
+ display: flex;
238
+ align-items: center;
239
+ gap: 10px;
240
+ padding: 6px 12px;
241
+ border-bottom: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
242
+ }
243
+ .eb-row .eb-stripe {
244
+ width: 3px;
245
+ height: 100%;
246
+ min-height: 20px;
247
+ border-radius: 2px;
248
+ background: var(--_color);
249
+ flex-shrink: 0;
250
+ }
251
+ .eb-row .eb-time {
252
+ font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
253
+ color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
254
+ flex-shrink: 0;
255
+ width: 90px;
256
+ }
257
+ .eb-row .eb-title {
258
+ font: 400 12px / 1.3 var(--dt-sans, 'Outfit', system-ui, sans-serif);
259
+ color: var(--dt-text, rgba(226, 232, 240, 0.85));
260
+ flex: 1;
261
+ overflow: hidden;
262
+ text-overflow: ellipsis;
263
+ white-space: nowrap;
264
+ }
265
+ .eb-row .eb-dur {
266
+ font: 300 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
267
+ color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
268
+ flex-shrink: 0;
269
+ }
270
+
271
+ /* ── Shared ── */
272
+ .eb-subtitle {
273
+ font: 400 10px / 1.2 var(--dt-sans, 'Outfit', system-ui, sans-serif);
274
+ color: var(--dt-text-2, rgba(148, 163, 184, 0.65));
275
+ overflow: hidden;
276
+ text-overflow: ellipsis;
277
+ white-space: nowrap;
278
+ }
279
+ .eb-tags {
280
+ display: inline-flex;
281
+ flex-wrap: wrap;
282
+ gap: 3px;
283
+ }
284
+ .eb-tag {
285
+ display: inline-block;
286
+ font: 500 8px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
287
+ letter-spacing: 0.04em;
288
+ text-transform: uppercase;
289
+ padding: 2px 5px;
290
+ border-radius: 3px;
291
+ background: color-mix(in srgb, var(--_color) 18%, transparent);
292
+ color: var(--_color);
293
+ }
294
+ .eb-live-badge {
295
+ display: inline-flex;
296
+ align-items: center;
297
+ font: 700 8px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
298
+ letter-spacing: 0.08em;
299
+ text-transform: uppercase;
300
+ color: var(--dt-btn-text, #fff);
301
+ background: var(--_color);
302
+ padding: 2px 6px;
303
+ border-radius: 3px;
304
+ width: fit-content;
305
+ animation: eb-blink 2s ease-in-out infinite;
306
+ }
307
+
308
+ @keyframes eb-blink {
309
+ 0%, 100% { opacity: 1; }
310
+ 50% { opacity: 0.6; }
311
+ }
312
+ </style>