@nomideusz/svelte-calendar 0.3.0 → 0.3.1

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 CHANGED
@@ -169,7 +169,7 @@ The `visibleHours` prop is a `[startHour, endHour)` tuple. It applies to the Wee
169
169
 
170
170
  ## Subtitle & Tags on Events
171
171
 
172
- `TimelineEvent` now supports `subtitle` and `tags` fields — rendered automatically by `EventBlock`:
172
+ `TimelineEvent` supports `subtitle` and `tags` fields — rendered automatically in **all views**:
173
173
 
174
174
  ```ts
175
175
  const events: TimelineEvent[] = [
@@ -185,8 +185,9 @@ const events: TimelineEvent[] = [
185
185
  ];
186
186
  ```
187
187
 
188
- - **subtitle** — displayed as secondary text below the title in card and row variants
189
- - **tags** — rendered as small accent-colored pills
188
+ - **subtitle** — secondary text below the title (all views: WeekGrid, DayGrid, DayTimeline, Agenda, EventBlock)
189
+ - **tags** — accent-colored pills after the title (all views)
190
+ - In space-constrained views (DayGrid, DayTimeline), subtitle/tags appear only when the event block is tall/wide enough
190
191
 
191
192
  ## Color Map & Auto-Coloring
192
193
 
@@ -201,10 +202,36 @@ const adapter = createMemoryAdapter(events, {
201
202
  },
202
203
  });
203
204
 
204
- // Or auto-assign from a built-in 15-color palette
205
+ // Auto-assign from a built-in 15-color vivid palette
205
206
  const adapter = createMemoryAdapter(events, { autoColor: true });
206
207
  ```
207
208
 
209
+ ### Theme-Aware Auto-Coloring
210
+
211
+ Pass the theme's accent hex to `autoColor` and the palette is generated to harmonize with your theme — colors rotate via golden-angle hue spacing from the accent, with lightness adjusted for dark/light backgrounds:
212
+
213
+ ```ts
214
+ // Harmonious palette seeded from indigo accent
215
+ const adapter = createMemoryAdapter(events, { autoColor: '#6366f1' });
216
+
217
+ // Works with the recurring adapter too
218
+ const adapter = createRecurringAdapter(schedule, { autoColor: '#ef4444' });
219
+ ```
220
+
221
+ | `autoColor` value | Behaviour |
222
+ |---|---|
223
+ | `true` | Original 15-color vivid palette (fixed, theme-independent) |
224
+ | `'#ef4444'` | Golden-angle hue rotation from that accent; lightness adapted to dark/light |
225
+
226
+ You can also use the palette generator directly:
227
+
228
+ ```ts
229
+ import { generatePalette } from '@nomideusz/svelte-calendar';
230
+
231
+ generatePalette('#6366f1', 8); // 8 theme-harmonious hex colors
232
+ generatePalette(); // default vivid 15-color palette
233
+ ```
234
+
208
235
  Both `createMemoryAdapter` and `createRecurringAdapter` accept `colorMap` and `autoColor` options. Events with an explicit `color` field always take priority.
209
236
 
210
237
  ## Settings Panel
@@ -421,8 +448,8 @@ import {
421
448
 
422
449
  | Adapter | Use |
423
450
  |---------|-----|
424
- | `createMemoryAdapter(events, options?)` | In-memory — great for demos and prototyping. Supports `colorMap` and `autoColor`. |
425
- | `createRecurringAdapter(schedule, options?)` | Weekly recurring schedules — auto-projects onto viewed weeks. Read-only. |
451
+ | `createMemoryAdapter(events, options?)` | In-memory — great for demos and prototyping. Supports `colorMap` and `autoColor` (including theme-aware). |
452
+ | `createRecurringAdapter(schedule, options?)` | Weekly recurring schedules — auto-projects onto viewed weeks. Read-only. Supports `colorMap` and `autoColor`. |
426
453
  | `createRestAdapter(options)` | Fetch from a REST API with configurable endpoints. |
427
454
 
428
455
  ## Standalone Views
@@ -14,7 +14,12 @@ import type { CalendarAdapter } from './types.js';
14
14
  export interface MemoryAdapterOptions {
15
15
  /** Map of category/title to color */
16
16
  colorMap?: Record<string, string>;
17
- /** Auto-assign colors to events by category or title */
18
- autoColor?: boolean;
17
+ /**
18
+ * Auto-assign colors to events by category or title.
19
+ * true → use the default vivid palette
20
+ * string → hex accent color (e.g. '#6366f1') to generate a
21
+ * theme-harmonious palette via golden-angle hue rotation
22
+ */
23
+ autoColor?: boolean | string;
19
24
  }
20
25
  export declare function createMemoryAdapter(initial?: TimelineEvent[], options?: MemoryAdapterOptions): CalendarAdapter;
@@ -1,16 +1,19 @@
1
+ import { generatePalette, VIVID_PALETTE } from '../core/palette.js';
1
2
  let counter = 0;
2
3
  function uid() {
3
4
  return `mem-${Date.now()}-${++counter}`;
4
5
  }
5
6
  /** Default palette for auto-coloring */
6
- const AUTO_COLORS = [
7
- '#ef4444', '#f97316', '#eab308', '#22c55e', '#14b8a6',
8
- '#3b82f6', '#6366f1', '#a855f7', '#ec4899', '#f43f5e',
9
- '#06b6d4', '#84cc16', '#d946ef', '#0ea5e9', '#10b981',
10
- ];
7
+ const AUTO_COLORS = VIVID_PALETTE;
11
8
  export function createMemoryAdapter(initial = [], options = {}) {
12
9
  const { colorMap, autoColor } = options;
13
10
  const events = [...initial];
11
+ // Resolve palette: vivid default or theme-aware
12
+ const palette = autoColor
13
+ ? typeof autoColor === 'string'
14
+ ? generatePalette(autoColor)
15
+ : AUTO_COLORS
16
+ : AUTO_COLORS;
14
17
  // Build auto-color assignments
15
18
  const colorAssignments = new Map();
16
19
  let colorIndex = 0;
@@ -24,7 +27,7 @@ export function createMemoryAdapter(initial = [], options = {}) {
24
27
  return colorMap[key];
25
28
  if (autoColor) {
26
29
  if (!colorAssignments.has(key)) {
27
- colorAssignments.set(key, AUTO_COLORS[colorIndex % AUTO_COLORS.length]);
30
+ colorAssignments.set(key, palette[colorIndex % palette.length]);
28
31
  colorIndex++;
29
32
  }
30
33
  return colorAssignments.get(key);
@@ -27,8 +27,13 @@ export interface RecurringAdapterOptions {
27
27
  mondayStart?: boolean;
28
28
  /** Map of category/title to color */
29
29
  colorMap?: Record<string, string>;
30
- /** Auto-assign colors to events by category or title */
31
- autoColor?: boolean;
30
+ /**
31
+ * Auto-assign colors to events by category or title.
32
+ * true → use the default vivid palette
33
+ * string → hex accent color (e.g. '#6366f1') to generate a
34
+ * theme-harmonious palette via golden-angle hue rotation
35
+ */
36
+ autoColor?: boolean | string;
32
37
  }
33
38
  /**
34
39
  * Create a CalendarAdapter that projects recurring weekly events
@@ -1,5 +1,6 @@
1
1
  import { startOfWeek } from '../core/time.js';
2
2
  import { DAY_MS } from '../core/time.js';
3
+ import { generatePalette, VIVID_PALETTE } from '../core/palette.js';
3
4
  /** Parse "HH:MM" into [hours, minutes] */
4
5
  function parseTime(time) {
5
6
  const [h, m] = time.split(':').map(Number);
@@ -54,11 +55,7 @@ function getOverlappingWeeks(range, mondayStart) {
54
55
  return weeks;
55
56
  }
56
57
  /** Default palette for auto-coloring */
57
- const AUTO_COLORS = [
58
- '#ef4444', '#f97316', '#eab308', '#22c55e', '#14b8a6',
59
- '#3b82f6', '#6366f1', '#a855f7', '#ec4899', '#f43f5e',
60
- '#06b6d4', '#84cc16', '#d946ef', '#0ea5e9', '#10b981',
61
- ];
58
+ const AUTO_COLORS = VIVID_PALETTE;
62
59
  /**
63
60
  * Create a CalendarAdapter that projects recurring weekly events
64
61
  * onto concrete dates for whatever range the calendar requests.
@@ -67,6 +64,12 @@ const AUTO_COLORS = [
67
64
  */
68
65
  export function createRecurringAdapter(schedule, options = {}) {
69
66
  const { mondayStart = true, colorMap, autoColor } = options;
67
+ // Resolve palette: vivid default or theme-aware
68
+ const palette = autoColor
69
+ ? typeof autoColor === 'string'
70
+ ? generatePalette(autoColor)
71
+ : AUTO_COLORS
72
+ : AUTO_COLORS;
70
73
  // Build auto-color assignments
71
74
  const colorAssignments = new Map();
72
75
  if (autoColor || colorMap) {
@@ -77,7 +80,7 @@ export function createRecurringAdapter(schedule, options = {}) {
77
80
  colorAssignments.set(key, colorMap[key]);
78
81
  }
79
82
  else if (autoColor && !colorAssignments.has(key)) {
80
- colorAssignments.set(key, AUTO_COLORS[colorIndex % AUTO_COLORS.length]);
83
+ colorAssignments.set(key, palette[colorIndex % palette.length]);
81
84
  colorIndex++;
82
85
  }
83
86
  }
@@ -5,3 +5,4 @@ export { setDefaultLocale, getDefaultLocale, is24HourLocale, fmtH, weekdayShort,
5
5
  export { toZonedTime, fromZonedTime, nowInZone, formatInTimeZone, } from './timezone.js';
6
6
  export type { TimelineEvent, WeekTimelineProps, DayTimelineProps, } from './types.js';
7
7
  export { timeToX } from './types.js';
8
+ export { generatePalette, VIVID_PALETTE } from './palette.js';
@@ -8,3 +8,5 @@ export { setDefaultLocale, getDefaultLocale, is24HourLocale, fmtH, weekdayShort,
8
8
  // Timezone utilities
9
9
  export { toZonedTime, fromZonedTime, nowInZone, formatInTimeZone, } from './timezone.js';
10
10
  export { timeToX } from './types.js';
11
+ // Palette
12
+ export { generatePalette, VIVID_PALETTE } from './palette.js';
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Smart auto-color palette generator.
3
+ *
4
+ * Given a base accent hex (e.g. from `--dt-accent`), generates a
5
+ * palette of perceptually distinct colors that harmonize with the theme.
6
+ *
7
+ * Usage:
8
+ * generatePalette('#ef4444', 8) // 8 theme-harmonious colors
9
+ * generatePalette(undefined, 8) // falls back to the vivid default
10
+ */
11
+ export declare const VIVID_PALETTE: string[];
12
+ /**
13
+ * Generate `count` visually distinct and theme-harmonious colors
14
+ * by rotating hue evenly from the base accent, keeping saturation
15
+ * and lightness within a pleasant range.
16
+ *
17
+ * Dark themes (l < 0.5): bump lightness to 0.55–0.65 so colors pop on dark bg.
18
+ * Light themes (l ≥ 0.5): pull lightness to 0.38–0.48 so colors read on light bg.
19
+ *
20
+ * @param accent Hex color string (e.g. '#ef4444'). If undefined, returns VIVID_PALETTE.
21
+ * @param count Number of colors to generate (default: 15).
22
+ */
23
+ export declare function generatePalette(accent?: string, count?: number): string[];
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Smart auto-color palette generator.
3
+ *
4
+ * Given a base accent hex (e.g. from `--dt-accent`), generates a
5
+ * palette of perceptually distinct colors that harmonize with the theme.
6
+ *
7
+ * Usage:
8
+ * generatePalette('#ef4444', 8) // 8 theme-harmonious colors
9
+ * generatePalette(undefined, 8) // falls back to the vivid default
10
+ */
11
+ // ── Hardcoded vivid fallback (original behavior) ────────
12
+ export const VIVID_PALETTE = [
13
+ '#ef4444', '#f97316', '#eab308', '#22c55e', '#14b8a6',
14
+ '#3b82f6', '#6366f1', '#a855f7', '#ec4899', '#f43f5e',
15
+ '#06b6d4', '#84cc16', '#d946ef', '#0ea5e9', '#10b981',
16
+ ];
17
+ // ── Color math (hex ↔ HSL) ──────────────────────────────
18
+ function hexToRgb(hex) {
19
+ const h = hex.replace('#', '');
20
+ const n = h.length === 3
21
+ ? parseInt(h[0] + h[0] + h[1] + h[1] + h[2] + h[2], 16)
22
+ : parseInt(h, 16);
23
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
24
+ }
25
+ function rgbToHsl(r, g, b) {
26
+ r /= 255;
27
+ g /= 255;
28
+ b /= 255;
29
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
30
+ const l = (max + min) / 2;
31
+ if (max === min)
32
+ return [0, 0, l];
33
+ const d = max - min;
34
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
35
+ let h = 0;
36
+ if (max === r)
37
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
38
+ else if (max === g)
39
+ h = ((b - r) / d + 2) / 6;
40
+ else
41
+ h = ((r - g) / d + 4) / 6;
42
+ return [h, s, l];
43
+ }
44
+ function hslToHex(h, s, l) {
45
+ h = ((h % 1) + 1) % 1; // normalize to [0, 1)
46
+ const hue2rgb = (p, q, t) => {
47
+ if (t < 0)
48
+ t += 1;
49
+ if (t > 1)
50
+ t -= 1;
51
+ if (t < 1 / 6)
52
+ return p + (q - p) * 6 * t;
53
+ if (t < 1 / 2)
54
+ return q;
55
+ if (t < 2 / 3)
56
+ return p + (q - p) * (2 / 3 - t) * 6;
57
+ return p;
58
+ };
59
+ let r, g, b;
60
+ if (s === 0) {
61
+ r = g = b = l;
62
+ }
63
+ else {
64
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
65
+ const p = 2 * l - q;
66
+ r = hue2rgb(p, q, h + 1 / 3);
67
+ g = hue2rgb(p, q, h);
68
+ b = hue2rgb(p, q, h - 1 / 3);
69
+ }
70
+ const toHex = (v) => Math.round(v * 255).toString(16).padStart(2, '0');
71
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
72
+ }
73
+ // ── Palette generation ──────────────────────────────────
74
+ /**
75
+ * Generate `count` visually distinct and theme-harmonious colors
76
+ * by rotating hue evenly from the base accent, keeping saturation
77
+ * and lightness within a pleasant range.
78
+ *
79
+ * Dark themes (l < 0.5): bump lightness to 0.55–0.65 so colors pop on dark bg.
80
+ * Light themes (l ≥ 0.5): pull lightness to 0.38–0.48 so colors read on light bg.
81
+ *
82
+ * @param accent Hex color string (e.g. '#ef4444'). If undefined, returns VIVID_PALETTE.
83
+ * @param count Number of colors to generate (default: 15).
84
+ */
85
+ export function generatePalette(accent, count = 15) {
86
+ if (!accent)
87
+ return VIVID_PALETTE.slice(0, count);
88
+ const [r, g, b] = hexToRgb(accent);
89
+ const [baseH, baseS, baseL] = rgbToHsl(r, g, b);
90
+ // Determine a good saturation range
91
+ const sat = Math.max(0.45, Math.min(0.8, baseS));
92
+ // Light vs dark theme: adjust lightness for contrast
93
+ const isDark = baseL < 0.5;
94
+ const lCenter = isDark ? 0.6 : 0.43;
95
+ const lRange = 0.05;
96
+ const colors = [];
97
+ for (let i = 0; i < count; i++) {
98
+ // Golden-angle hue rotation for maximum perceptual spread
99
+ const hue = baseH + (i * 0.618033988749895);
100
+ // Slight lightness oscillation for differentiation
101
+ const lOff = ((i % 3) - 1) * lRange;
102
+ // Slight saturation variation
103
+ const sOff = ((i % 2) === 0 ? 0.04 : -0.04);
104
+ const s = Math.max(0.35, Math.min(0.85, sat + sOff));
105
+ const l = Math.max(0.3, Math.min(0.7, lCenter + lOff));
106
+ colors.push(hslToHex(hue, s, l));
107
+ }
108
+ return colors;
109
+ }
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export { createEventStore, createViewState, createSelection, createDragState, }
7
7
  export type { EventStore, ViewState, ViewStateOptions, CalendarViewId, BuiltInViewId, ViewGranularity, ViewDateRange, Selection, DragState, DragMode, DragPayload, } from './engine/index.js';
8
8
  export { createMemoryAdapter, createRestAdapter, createRecurringAdapter } from './adapters/index.js';
9
9
  export type { CalendarAdapter, DateRange, RestAdapterOptions, RecurringEvent, RecurringAdapterOptions, MemoryAdapterOptions, } from './adapters/index.js';
10
- export { createClock, DAY_MS, HOUR_MS, HOURS, sod, startOfWeek, addDaysMs, diffDays, pad, fractionalHour, fmtHM, fmtS, dayNum, dayOfWeek, fmtH, weekdayShort, weekdayLong, monthShort, monthLong, dateShort, dateWithWeekday, fmtDay, fmtWeekRange, setDefaultLocale, getDefaultLocale, is24HourLocale, timeToX, toZonedTime, fromZonedTime, nowInZone, formatInTimeZone, } from './core/index.js';
10
+ export { createClock, DAY_MS, HOUR_MS, HOURS, sod, startOfWeek, addDaysMs, diffDays, pad, fractionalHour, fmtHM, fmtS, dayNum, dayOfWeek, fmtH, weekdayShort, weekdayLong, monthShort, monthLong, dateShort, dateWithWeekday, fmtDay, fmtWeekRange, setDefaultLocale, getDefaultLocale, is24HourLocale, timeToX, toZonedTime, fromZonedTime, nowInZone, formatInTimeZone, generatePalette, VIVID_PALETTE, } from './core/index.js';
11
11
  export type { Clock, TimelineEvent, WeekTimelineProps, DayTimelineProps, } from './core/index.js';
12
12
  export { midnight, parchment, indigo, neutral, bare, presets } from './theme/index.js';
13
13
  export type { PresetName } from './theme/index.js';
package/dist/index.js CHANGED
@@ -9,6 +9,6 @@ export { createEventStore, createViewState, createSelection, createDragState, }
9
9
  // ─── Adapters ───────────────────────────────────────────
10
10
  export { createMemoryAdapter, createRestAdapter, createRecurringAdapter } from './adapters/index.js';
11
11
  // ─── Core: clock, time, locale, types ───────────────────
12
- export { createClock, DAY_MS, HOUR_MS, HOURS, sod, startOfWeek, addDaysMs, diffDays, pad, fractionalHour, fmtHM, fmtS, dayNum, dayOfWeek, fmtH, weekdayShort, weekdayLong, monthShort, monthLong, dateShort, dateWithWeekday, fmtDay, fmtWeekRange, setDefaultLocale, getDefaultLocale, is24HourLocale, timeToX, toZonedTime, fromZonedTime, nowInZone, formatInTimeZone, } from './core/index.js';
12
+ export { createClock, DAY_MS, HOUR_MS, HOURS, sod, startOfWeek, addDaysMs, diffDays, pad, fractionalHour, fmtHM, fmtS, dayNum, dayOfWeek, fmtH, weekdayShort, weekdayLong, monthShort, monthLong, dateShort, dateWithWeekday, fmtDay, fmtWeekRange, setDefaultLocale, getDefaultLocale, is24HourLocale, timeToX, toZonedTime, fromZonedTime, nowInZone, formatInTimeZone, generatePalette, VIVID_PALETTE, } from './core/index.js';
13
13
  // ─── Themes ─────────────────────────────────────────────
14
14
  export { midnight, parchment, indigo, neutral, bare, presets } from './theme/index.js';
@@ -287,6 +287,9 @@
287
287
  <div class="ag-card-stripe"></div>
288
288
  <div class="ag-card-body">
289
289
  <span class="ag-card-title">{ev.title}</span>
290
+ {#if ev.subtitle}
291
+ <span class="ag-card-sub">{ev.subtitle}</span>
292
+ {/if}
290
293
  <span class="ag-card-meta">
291
294
  {#if isNow}
292
295
  until {fmtTime(ev.end)}
@@ -295,6 +298,13 @@
295
298
  {/if}
296
299
  <span class="ag-card-dur">{duration(ev)}</span>
297
300
  </span>
301
+ {#if ev.tags?.length}
302
+ <div class="ag-card-tags">
303
+ {#each ev.tags as tag}
304
+ <span class="ag-card-tag">{tag}</span>
305
+ {/each}
306
+ </div>
307
+ {/if}
298
308
  {#if isNow}
299
309
  <div class="ag-card-progress">
300
310
  <div class="ag-card-progress-fill" style:width="{progress(ev) * 100}%"></div>
@@ -411,10 +421,20 @@
411
421
  <span class="ag-q-card-title">{ev.title}</span>
412
422
  <span class="ag-q-card-eta">{timeUntilEv(ev)}</span>
413
423
  </div>
424
+ {#if ev.subtitle}
425
+ <span class="ag-q-card-sub">{ev.subtitle}</span>
426
+ {/if}
414
427
  <div class="ag-q-card-meta">
415
428
  {fmtTime(ev.start)} – {fmtTime(ev.end)}
416
429
  <span class="ag-q-card-dur">{duration(ev)}</span>
417
430
  </div>
431
+ {#if ev.tags?.length}
432
+ <div class="ag-q-card-tags">
433
+ {#each ev.tags as tag}
434
+ <span class="ag-q-card-tag">{tag}</span>
435
+ {/each}
436
+ </div>
437
+ {/if}
418
438
  </div>
419
439
  </div>
420
440
  {/each}
@@ -494,10 +514,20 @@
494
514
  <span class="ag-plan-order">{i + 1}</span>
495
515
  <span class="ag-plan-title">{ev.title}</span>
496
516
  </div>
517
+ {#if ev.subtitle}
518
+ <span class="ag-plan-sub">{ev.subtitle}</span>
519
+ {/if}
497
520
  <div class="ag-plan-meta">
498
521
  {fmtTime(ev.start)} – {fmtTime(ev.end)}
499
522
  <span class="ag-plan-dur">{duration(ev)}</span>
500
523
  </div>
524
+ {#if ev.tags?.length}
525
+ <div class="ag-plan-tags">
526
+ {#each ev.tags as tag}
527
+ <span class="ag-plan-tag">{tag}</span>
528
+ {/each}
529
+ </div>
530
+ {/if}
501
531
  </div>
502
532
  </div>
503
533
  {/each}
@@ -584,6 +614,14 @@
584
614
  <span class="ag-compact-dot"></span>
585
615
  <span class="ag-compact-time">{fmtTime(ev.start)}</span>
586
616
  <span class="ag-compact-title">{ev.title}</span>
617
+ {#if ev.subtitle}
618
+ <span class="ag-compact-sub">{ev.subtitle}</span>
619
+ {/if}
620
+ {#if ev.tags?.length}
621
+ {#each ev.tags as tag}
622
+ <span class="ag-compact-tag">{tag}</span>
623
+ {/each}
624
+ {/if}
587
625
  <span class="ag-compact-dur">{duration(ev)}</span>
588
626
  </div>
589
627
  {/each}
@@ -740,6 +778,24 @@
740
778
  margin-left: 6px;
741
779
  color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
742
780
  }
781
+ .ag-card-sub {
782
+ font-size: 11px;
783
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
784
+ line-height: 1;
785
+ }
786
+ .ag-card-tags {
787
+ display: flex;
788
+ gap: 4px;
789
+ flex-wrap: wrap;
790
+ }
791
+ .ag-card-tag {
792
+ font: 500 9px / 1 var(--dt-sans, system-ui, sans-serif);
793
+ color: var(--ev-color, var(--dt-accent));
794
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
795
+ padding: 2px 5px;
796
+ border-radius: 3px;
797
+ white-space: nowrap;
798
+ }
743
799
  .ag-card-progress {
744
800
  height: 3px;
745
801
  background: var(--dt-border, rgba(255, 255, 255, 0.06));
@@ -998,6 +1054,25 @@
998
1054
  margin-left: 6px;
999
1055
  color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1000
1056
  }
1057
+ .ag-q-card-sub {
1058
+ font-size: 11px;
1059
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
1060
+ line-height: 1;
1061
+ }
1062
+ .ag-q-card-tags {
1063
+ display: flex;
1064
+ gap: 4px;
1065
+ flex-wrap: wrap;
1066
+ margin-top: 2px;
1067
+ }
1068
+ .ag-q-card-tag {
1069
+ font: 500 9px / 1 var(--dt-sans, system-ui, sans-serif);
1070
+ color: var(--ev-color, var(--dt-accent));
1071
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
1072
+ padding: 2px 5px;
1073
+ border-radius: 3px;
1074
+ white-space: nowrap;
1075
+ }
1001
1076
 
1002
1077
  /* ── PAST: minimal right gutter ── */
1003
1078
  .ag-q-done {
@@ -1214,6 +1289,27 @@
1214
1289
  margin-left: 6px;
1215
1290
  color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1216
1291
  }
1292
+ .ag-plan-sub {
1293
+ font-size: 11px;
1294
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
1295
+ line-height: 1;
1296
+ padding-left: 22px;
1297
+ }
1298
+ .ag-plan-tags {
1299
+ display: flex;
1300
+ gap: 4px;
1301
+ flex-wrap: wrap;
1302
+ padding-left: 22px;
1303
+ margin-top: 2px;
1304
+ }
1305
+ .ag-plan-tag {
1306
+ font: 500 9px / 1 var(--dt-sans, system-ui, sans-serif);
1307
+ color: var(--ev-color, var(--dt-accent));
1308
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
1309
+ padding: 2px 5px;
1310
+ border-radius: 3px;
1311
+ white-space: nowrap;
1312
+ }
1217
1313
 
1218
1314
  /* Header badges for past/future days */
1219
1315
  .ag-badge {
@@ -1396,6 +1492,20 @@
1396
1492
  color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1397
1493
  flex-shrink: 0;
1398
1494
  }
1495
+ .ag-compact-sub {
1496
+ font-size: 10px;
1497
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
1498
+ flex-shrink: 0;
1499
+ }
1500
+ .ag-compact-tag {
1501
+ font: 500 8px / 1 var(--dt-sans, system-ui, sans-serif);
1502
+ color: var(--ev-color, var(--dt-accent));
1503
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 12%, transparent);
1504
+ padding: 1px 4px;
1505
+ border-radius: 3px;
1506
+ white-space: nowrap;
1507
+ flex-shrink: 0;
1508
+ }
1399
1509
  .ag-compact-more {
1400
1510
  font-size: 11px;
1401
1511
  color: var(--dt-text-3);
@@ -656,6 +656,16 @@
656
656
  </span>
657
657
  {/if}
658
658
  <span class="fs-ev-title">{p.ev.title}</span>
659
+ {#if p.ev.subtitle && p.heightPx > 48}
660
+ <span class="fs-ev-sub">{p.ev.subtitle}</span>
661
+ {/if}
662
+ {#if p.ev.tags?.length && p.heightPx > 72}
663
+ <span class="fs-ev-tags">
664
+ {#each p.ev.tags as tag}
665
+ <span class="fs-ev-tag">{tag}</span>
666
+ {/each}
667
+ </span>
668
+ {/if}
659
669
  </div>
660
670
  </div>
661
671
  {/each}
@@ -1055,6 +1065,29 @@
1055
1065
  white-space: nowrap;
1056
1066
  }
1057
1067
 
1068
+ .fs-ev-sub {
1069
+ font: 400 11px / 1 var(--dt-sans);
1070
+ color: var(--dt-text-2);
1071
+ opacity: 0.6;
1072
+ white-space: nowrap;
1073
+ overflow: hidden;
1074
+ text-overflow: ellipsis;
1075
+ }
1076
+
1077
+ .fs-ev-tags {
1078
+ display: flex;
1079
+ gap: 4px;
1080
+ }
1081
+
1082
+ .fs-ev-tag {
1083
+ font: 500 8px / 1 var(--dt-sans);
1084
+ color: var(--ev-color, var(--dt-accent));
1085
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 18%, transparent);
1086
+ padding: 1px 4px;
1087
+ border-radius: 3px;
1088
+ white-space: nowrap;
1089
+ }
1090
+
1058
1091
  /* ─── Focus-visible (accessibility) ──────────── */
1059
1092
  .fs-event:focus-visible,
1060
1093
  .fs-night:focus-visible {
@@ -269,6 +269,14 @@
269
269
  onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); oneventclick?.(p.ev); } }}
270
270
  >
271
271
  <span class="dt-ev-title">{p.ev.title}</span>
272
+ {#if p.ev.subtitle}
273
+ <span class="dt-ev-sub">{p.ev.subtitle}</span>
274
+ {/if}
275
+ {#if p.ev.tags?.length}
276
+ {#each p.ev.tags as tag}
277
+ <span class="dt-ev-tag">{tag}</span>
278
+ {/each}
279
+ {/if}
272
280
  </div>
273
281
  {/each}
274
282
  </div>
@@ -476,6 +484,7 @@
476
484
  padding: 0 8px;
477
485
  display: flex;
478
486
  align-items: center;
487
+ gap: 4px;
479
488
  cursor: pointer;
480
489
  background: color-mix(in srgb, var(--ev-color) 22%, transparent);
481
490
  border-left: 3px solid var(--ev-color);
@@ -502,4 +511,22 @@
502
511
  overflow: hidden;
503
512
  text-overflow: ellipsis;
504
513
  }
514
+
515
+ .dt-ev-sub {
516
+ font: 400 9px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
517
+ color: var(--dt-text-2, rgba(148, 163, 184, 0.6));
518
+ white-space: nowrap;
519
+ overflow: hidden;
520
+ text-overflow: ellipsis;
521
+ }
522
+
523
+ .dt-ev-tag {
524
+ font: 500 7px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
525
+ color: var(--ev-color, var(--dt-accent));
526
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 20%, transparent);
527
+ padding: 1px 3px;
528
+ border-radius: 2px;
529
+ white-space: nowrap;
530
+ flex-shrink: 0;
531
+ }
505
532
  </style>
@@ -303,8 +303,16 @@
303
303
  onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); oneventclick?.(ev); } }}
304
304
  >
305
305
  <span class="wg-ev-time">{fmtAmPm(ev.start)}</span>
306
- <span class="wg-ev-title">{ev.title}</span>
307
- </div>
306
+ <span class="wg-ev-title">{ev.title}</span> {#if ev.subtitle}
307
+ <span class="wg-ev-sub">{ev.subtitle}</span>
308
+ {/if}
309
+ {#if ev.tags?.length}
310
+ <span class="wg-ev-tags">
311
+ {#each ev.tags as tag}
312
+ <span class="wg-ev-tag">{tag}</span>
313
+ {/each}
314
+ </span>
315
+ {/if} </div>
308
316
  {/each}
309
317
  {#if day.events.length > MAX_EVENTS_SHOWN}
310
318
  <div class="wg-ev-more">+{day.events.length - MAX_EVENTS_SHOWN} more</div>
@@ -480,7 +488,8 @@
480
488
  .wg-ev {
481
489
  display: flex;
482
490
  align-items: center;
483
- gap: 5px;
491
+ flex-wrap: wrap;
492
+ gap: 3px 5px;
484
493
  padding: 3px 6px;
485
494
  border-radius: 4px;
486
495
  background: color-mix(in srgb, var(--ev-color) 12%, transparent);
@@ -516,6 +525,29 @@
516
525
  text-overflow: ellipsis;
517
526
  }
518
527
 
528
+ .wg-ev-sub {
529
+ font: 400 10px / 1 var(--dt-sans, system-ui, sans-serif);
530
+ color: var(--dt-text-3, rgba(0, 0, 0, 0.4));
531
+ white-space: nowrap;
532
+ overflow: hidden;
533
+ text-overflow: ellipsis;
534
+ }
535
+
536
+ .wg-ev-tags {
537
+ display: flex;
538
+ gap: 3px;
539
+ flex-shrink: 0;
540
+ }
541
+
542
+ .wg-ev-tag {
543
+ font: 500 8px / 1 var(--dt-sans, system-ui, sans-serif);
544
+ color: var(--ev-color, var(--dt-accent));
545
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
546
+ padding: 1px 4px;
547
+ border-radius: 3px;
548
+ white-space: nowrap;
549
+ }
550
+
519
551
  .wg-ev-more {
520
552
  font: 500 10px / 1 var(--dt-sans, system-ui, sans-serif);
521
553
  color: var(--dt-text-3, rgba(0, 0, 0, 0.35));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomideusz/svelte-calendar",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "A themeable, pluggable Svelte 5 calendar with Day and Week views — Grid, Timeline, Agenda, Heatmap.",
5
5
  "type": "module",
6
6
  "license": "MIT",