@nomideusz/svelte-calendar 0.3.0 → 0.4.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 +568 -517
- package/dist/adapters/memory.d.ts +7 -2
- package/dist/adapters/memory.js +9 -6
- package/dist/adapters/recurring.d.ts +7 -2
- package/dist/adapters/recurring.js +9 -6
- package/dist/calendar/Calendar.svelte +21 -1
- package/dist/calendar/Calendar.svelte.d.ts +1 -0
- package/dist/calendar/Toolbar.svelte +5 -2
- package/dist/calendar/Toolbar.svelte.d.ts +2 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +2 -0
- package/dist/core/palette.d.ts +23 -0
- package/dist/core/palette.js +109 -0
- package/dist/engine/view-state.svelte.d.ts +1 -0
- package/dist/engine/view-state.svelte.js +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/views/agenda/Agenda.svelte +117 -5
- package/dist/views/agenda/Agenda.svelte.d.ts +1 -0
- package/dist/views/day/DayGrid.svelte +39 -3
- package/dist/views/day/DayGrid.svelte.d.ts +2 -0
- package/dist/views/day/DayTimeline.svelte +31 -3
- package/dist/views/day/DayTimeline.svelte.d.ts +5 -1
- package/dist/views/schedule/WeekSchedule.svelte +4 -0
- package/dist/views/schedule/WeekSchedule.svelte.d.ts +2 -0
- package/dist/views/settings/Settings.svelte +451 -173
- package/dist/views/settings/Settings.svelte.d.ts +22 -6
- package/dist/views/week/WeekGrid.svelte +44 -11
- package/dist/views/week/WeekGrid.svelte.d.ts +1 -0
- package/dist/views/week/WeekHeatmap.svelte +8 -7
- package/dist/views/week/WeekHeatmap.svelte.d.ts +1 -0
- package/dist/widget/CalendarWidget.svelte +0 -2
- package/dist/widget/widget.d.ts +1 -7
- package/dist/widget/widget.js +44 -3
- package/package.json +65 -67
- package/widget/widget.js +260 -260
|
@@ -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
|
-
/**
|
|
18
|
-
|
|
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;
|
package/dist/adapters/memory.js
CHANGED
|
@@ -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,
|
|
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
|
-
/**
|
|
31
|
-
|
|
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,
|
|
83
|
+
colorAssignments.set(key, palette[colorIndex % palette.length]);
|
|
81
84
|
colorIndex++;
|
|
82
85
|
}
|
|
83
86
|
}
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
oneventclick?: (event: TimelineEvent) => void;
|
|
73
73
|
oneventcreate?: (range: { start: Date; end: Date }) => void;
|
|
74
74
|
oneventmove?: (event: TimelineEvent, newStart: Date, newEnd: Date) => void;
|
|
75
|
+
onviewchange?: (viewId: CalendarViewId) => void;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
let {
|
|
@@ -90,6 +91,7 @@
|
|
|
90
91
|
oneventclick,
|
|
91
92
|
oneventcreate,
|
|
92
93
|
oneventmove,
|
|
94
|
+
onviewchange,
|
|
93
95
|
}: Props = $props();
|
|
94
96
|
|
|
95
97
|
// In readOnly mode, suppress mutation callbacks
|
|
@@ -153,6 +155,23 @@
|
|
|
153
155
|
store.load({ start, end });
|
|
154
156
|
});
|
|
155
157
|
|
|
158
|
+
// Keep active view in sync when external defaultView changes after mount.
|
|
159
|
+
$effect(() => {
|
|
160
|
+
viewState.setView(defaultView);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Keep view state's week-start rule in sync with incoming prop changes.
|
|
164
|
+
$effect(() => {
|
|
165
|
+
if (viewState.mondayStart !== mondayStart) {
|
|
166
|
+
viewState.setMondayStart(mondayStart);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Notify host when active view changes (e.g. via toolbar concept/granularity toggles).
|
|
171
|
+
$effect(() => {
|
|
172
|
+
onviewchange?.(viewState.view);
|
|
173
|
+
});
|
|
174
|
+
|
|
156
175
|
// ── Resolve active view ──
|
|
157
176
|
const activeView = $derived(views.find((v) => v.id === viewState.view) ?? views[0]);
|
|
158
177
|
|
|
@@ -170,7 +189,7 @@
|
|
|
170
189
|
lang={locale}
|
|
171
190
|
>
|
|
172
191
|
{#if showToolbar}
|
|
173
|
-
<Toolbar {viewState} views={toolbarViews} {links} />
|
|
192
|
+
<Toolbar {viewState} views={toolbarViews} {links} {locale} />
|
|
174
193
|
{/if}
|
|
175
194
|
|
|
176
195
|
<div class="cal-body">
|
|
@@ -181,6 +200,7 @@
|
|
|
181
200
|
style={theme}
|
|
182
201
|
height={height - (showToolbar ? 48 : 0)}
|
|
183
202
|
mondayStart={viewState.mondayStart}
|
|
203
|
+
{locale}
|
|
184
204
|
focusDate={viewState.focusDate}
|
|
185
205
|
oneventclick={oneventclick}
|
|
186
206
|
oneventcreate={effectiveCreate}
|
|
@@ -47,6 +47,7 @@ interface Props {
|
|
|
47
47
|
end: Date;
|
|
48
48
|
}) => void;
|
|
49
49
|
oneventmove?: (event: TimelineEvent, newStart: Date, newEnd: Date) => void;
|
|
50
|
+
onviewchange?: (viewId: CalendarViewId) => void;
|
|
50
51
|
}
|
|
51
52
|
declare const Calendar: Component<Props, {}, "">;
|
|
52
53
|
type Calendar = ReturnType<typeof Calendar>;
|
|
@@ -19,20 +19,23 @@
|
|
|
19
19
|
views?: ViewOption[];
|
|
20
20
|
/** Links to show in the toolbar */
|
|
21
21
|
links?: { href: string; label: string }[];
|
|
22
|
+
/** Locale used for labels */
|
|
23
|
+
locale?: string;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
let {
|
|
25
27
|
viewState,
|
|
26
28
|
views = [],
|
|
27
29
|
links = [],
|
|
30
|
+
locale,
|
|
28
31
|
}: Props = $props();
|
|
29
32
|
|
|
30
33
|
const dateLabel = $derived(() => {
|
|
31
34
|
if (viewState.granularity === 'day') {
|
|
32
|
-
return `${weekdayLong(viewState.focusDate.getTime())}, ${fmtDay(viewState.focusDate.getTime(), Date.now())}`;
|
|
35
|
+
return `${weekdayLong(viewState.focusDate.getTime(), locale)}, ${fmtDay(viewState.focusDate.getTime(), Date.now(), undefined, locale)}`;
|
|
33
36
|
}
|
|
34
37
|
const ws = viewState.range.start.getTime();
|
|
35
|
-
return fmtWeekRange(ws);
|
|
38
|
+
return fmtWeekRange(ws, locale);
|
|
36
39
|
});
|
|
37
40
|
|
|
38
41
|
// Which granularities are available?
|
package/dist/core/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/core/index.js
CHANGED
|
@@ -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
|
+
}
|
|
@@ -28,6 +28,7 @@ export interface ViewState {
|
|
|
28
28
|
/** IANA timezone, or undefined for local */
|
|
29
29
|
readonly timezone: string | undefined;
|
|
30
30
|
setView(id: CalendarViewId): void;
|
|
31
|
+
setMondayStart(value: boolean): void;
|
|
31
32
|
setFocusDate(date: Date): void;
|
|
32
33
|
next(): void;
|
|
33
34
|
prev(): void;
|
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';
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
/** 'day' = single-day timeline rail, 'week' = rolling 7-day view */
|
|
26
26
|
mode?: 'day' | 'week';
|
|
27
27
|
mondayStart?: boolean;
|
|
28
|
+
locale?: string;
|
|
28
29
|
height?: number;
|
|
29
30
|
events?: TimelineEvent[];
|
|
30
31
|
style?: string;
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
let {
|
|
39
40
|
mode = 'day',
|
|
40
41
|
mondayStart = true,
|
|
42
|
+
locale,
|
|
41
43
|
height = 520,
|
|
42
44
|
events = [],
|
|
43
45
|
style = '',
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
// ── Format helpers ──────────────────────────────────
|
|
52
54
|
function fmtTime(d: Date): string {
|
|
53
55
|
return d
|
|
54
|
-
.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true })
|
|
56
|
+
.toLocaleTimeString(locale ?? 'en-US', { hour: 'numeric', minute: '2-digit', hour12: true })
|
|
55
57
|
.toLowerCase();
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -220,7 +222,7 @@
|
|
|
220
222
|
? startOfWeek(sod(focusDate.getTime()), mondayStart)
|
|
221
223
|
: startOfWeek(clock.today, mondayStart),
|
|
222
224
|
);
|
|
223
|
-
const weekLabel = $derived(fmtWeekRange(weekStartMs));
|
|
225
|
+
const weekLabel = $derived(fmtWeekRange(weekStartMs, locale));
|
|
224
226
|
|
|
225
227
|
const weekDays = $derived.by((): DayGroup[] => {
|
|
226
228
|
if (mode !== 'week') return [];
|
|
@@ -255,8 +257,8 @@
|
|
|
255
257
|
|
|
256
258
|
out.push({
|
|
257
259
|
ms,
|
|
258
|
-
dayName: tier === 'today' || tier === 'tomorrow' ? weekdayLong(ms) : weekdayShort(ms),
|
|
259
|
-
dateLabel: `${monthShort(ms)} ${dayNum(ms)}`,
|
|
260
|
+
dayName: tier === 'today' || tier === 'tomorrow' ? weekdayLong(ms, locale) : weekdayShort(ms, locale),
|
|
261
|
+
dateLabel: `${monthShort(ms, locale)} ${dayNum(ms)}`,
|
|
260
262
|
tier,
|
|
261
263
|
events: dayEvts,
|
|
262
264
|
pastEvents,
|
|
@@ -287,6 +289,9 @@
|
|
|
287
289
|
<div class="ag-card-stripe"></div>
|
|
288
290
|
<div class="ag-card-body">
|
|
289
291
|
<span class="ag-card-title">{ev.title}</span>
|
|
292
|
+
{#if ev.subtitle}
|
|
293
|
+
<span class="ag-card-sub">{ev.subtitle}</span>
|
|
294
|
+
{/if}
|
|
290
295
|
<span class="ag-card-meta">
|
|
291
296
|
{#if isNow}
|
|
292
297
|
until {fmtTime(ev.end)}
|
|
@@ -295,6 +300,13 @@
|
|
|
295
300
|
{/if}
|
|
296
301
|
<span class="ag-card-dur">{duration(ev)}</span>
|
|
297
302
|
</span>
|
|
303
|
+
{#if ev.tags?.length}
|
|
304
|
+
<div class="ag-card-tags">
|
|
305
|
+
{#each ev.tags as tag}
|
|
306
|
+
<span class="ag-card-tag">{tag}</span>
|
|
307
|
+
{/each}
|
|
308
|
+
</div>
|
|
309
|
+
{/if}
|
|
298
310
|
{#if isNow}
|
|
299
311
|
<div class="ag-card-progress">
|
|
300
312
|
<div class="ag-card-progress-fill" style:width="{progress(ev) * 100}%"></div>
|
|
@@ -315,7 +327,7 @@
|
|
|
315
327
|
<header class="ag-header">
|
|
316
328
|
{#if mode === 'day'}
|
|
317
329
|
<div class="ag-header-left">
|
|
318
|
-
<span class="ag-title">{fmtDay(dayMs, clock.today, { short: false })}</span>
|
|
330
|
+
<span class="ag-title">{fmtDay(dayMs, clock.today, { short: false }, locale)}</span>
|
|
319
331
|
{#if isToday}
|
|
320
332
|
<span class="ag-clock">{clock.hm}</span>
|
|
321
333
|
{:else if isPastDay}
|
|
@@ -411,10 +423,20 @@
|
|
|
411
423
|
<span class="ag-q-card-title">{ev.title}</span>
|
|
412
424
|
<span class="ag-q-card-eta">{timeUntilEv(ev)}</span>
|
|
413
425
|
</div>
|
|
426
|
+
{#if ev.subtitle}
|
|
427
|
+
<span class="ag-q-card-sub">{ev.subtitle}</span>
|
|
428
|
+
{/if}
|
|
414
429
|
<div class="ag-q-card-meta">
|
|
415
430
|
{fmtTime(ev.start)} – {fmtTime(ev.end)}
|
|
416
431
|
<span class="ag-q-card-dur">{duration(ev)}</span>
|
|
417
432
|
</div>
|
|
433
|
+
{#if ev.tags?.length}
|
|
434
|
+
<div class="ag-q-card-tags">
|
|
435
|
+
{#each ev.tags as tag}
|
|
436
|
+
<span class="ag-q-card-tag">{tag}</span>
|
|
437
|
+
{/each}
|
|
438
|
+
</div>
|
|
439
|
+
{/if}
|
|
418
440
|
</div>
|
|
419
441
|
</div>
|
|
420
442
|
{/each}
|
|
@@ -494,10 +516,20 @@
|
|
|
494
516
|
<span class="ag-plan-order">{i + 1}</span>
|
|
495
517
|
<span class="ag-plan-title">{ev.title}</span>
|
|
496
518
|
</div>
|
|
519
|
+
{#if ev.subtitle}
|
|
520
|
+
<span class="ag-plan-sub">{ev.subtitle}</span>
|
|
521
|
+
{/if}
|
|
497
522
|
<div class="ag-plan-meta">
|
|
498
523
|
{fmtTime(ev.start)} – {fmtTime(ev.end)}
|
|
499
524
|
<span class="ag-plan-dur">{duration(ev)}</span>
|
|
500
525
|
</div>
|
|
526
|
+
{#if ev.tags?.length}
|
|
527
|
+
<div class="ag-plan-tags">
|
|
528
|
+
{#each ev.tags as tag}
|
|
529
|
+
<span class="ag-plan-tag">{tag}</span>
|
|
530
|
+
{/each}
|
|
531
|
+
</div>
|
|
532
|
+
{/if}
|
|
501
533
|
</div>
|
|
502
534
|
</div>
|
|
503
535
|
{/each}
|
|
@@ -584,6 +616,14 @@
|
|
|
584
616
|
<span class="ag-compact-dot"></span>
|
|
585
617
|
<span class="ag-compact-time">{fmtTime(ev.start)}</span>
|
|
586
618
|
<span class="ag-compact-title">{ev.title}</span>
|
|
619
|
+
{#if ev.subtitle}
|
|
620
|
+
<span class="ag-compact-sub">{ev.subtitle}</span>
|
|
621
|
+
{/if}
|
|
622
|
+
{#if ev.tags?.length}
|
|
623
|
+
{#each ev.tags as tag}
|
|
624
|
+
<span class="ag-compact-tag">{tag}</span>
|
|
625
|
+
{/each}
|
|
626
|
+
{/if}
|
|
587
627
|
<span class="ag-compact-dur">{duration(ev)}</span>
|
|
588
628
|
</div>
|
|
589
629
|
{/each}
|
|
@@ -740,6 +780,24 @@
|
|
|
740
780
|
margin-left: 6px;
|
|
741
781
|
color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
|
|
742
782
|
}
|
|
783
|
+
.ag-card-sub {
|
|
784
|
+
font-size: 11px;
|
|
785
|
+
color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
|
|
786
|
+
line-height: 1;
|
|
787
|
+
}
|
|
788
|
+
.ag-card-tags {
|
|
789
|
+
display: flex;
|
|
790
|
+
gap: 4px;
|
|
791
|
+
flex-wrap: wrap;
|
|
792
|
+
}
|
|
793
|
+
.ag-card-tag {
|
|
794
|
+
font: 500 9px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
795
|
+
color: var(--ev-color, var(--dt-accent));
|
|
796
|
+
background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
|
|
797
|
+
padding: 2px 5px;
|
|
798
|
+
border-radius: 3px;
|
|
799
|
+
white-space: nowrap;
|
|
800
|
+
}
|
|
743
801
|
.ag-card-progress {
|
|
744
802
|
height: 3px;
|
|
745
803
|
background: var(--dt-border, rgba(255, 255, 255, 0.06));
|
|
@@ -998,6 +1056,25 @@
|
|
|
998
1056
|
margin-left: 6px;
|
|
999
1057
|
color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
|
|
1000
1058
|
}
|
|
1059
|
+
.ag-q-card-sub {
|
|
1060
|
+
font-size: 11px;
|
|
1061
|
+
color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
|
|
1062
|
+
line-height: 1;
|
|
1063
|
+
}
|
|
1064
|
+
.ag-q-card-tags {
|
|
1065
|
+
display: flex;
|
|
1066
|
+
gap: 4px;
|
|
1067
|
+
flex-wrap: wrap;
|
|
1068
|
+
margin-top: 2px;
|
|
1069
|
+
}
|
|
1070
|
+
.ag-q-card-tag {
|
|
1071
|
+
font: 500 9px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
1072
|
+
color: var(--ev-color, var(--dt-accent));
|
|
1073
|
+
background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
|
|
1074
|
+
padding: 2px 5px;
|
|
1075
|
+
border-radius: 3px;
|
|
1076
|
+
white-space: nowrap;
|
|
1077
|
+
}
|
|
1001
1078
|
|
|
1002
1079
|
/* ── PAST: minimal right gutter ── */
|
|
1003
1080
|
.ag-q-done {
|
|
@@ -1214,6 +1291,27 @@
|
|
|
1214
1291
|
margin-left: 6px;
|
|
1215
1292
|
color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
|
|
1216
1293
|
}
|
|
1294
|
+
.ag-plan-sub {
|
|
1295
|
+
font-size: 11px;
|
|
1296
|
+
color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
|
|
1297
|
+
line-height: 1;
|
|
1298
|
+
padding-left: 22px;
|
|
1299
|
+
}
|
|
1300
|
+
.ag-plan-tags {
|
|
1301
|
+
display: flex;
|
|
1302
|
+
gap: 4px;
|
|
1303
|
+
flex-wrap: wrap;
|
|
1304
|
+
padding-left: 22px;
|
|
1305
|
+
margin-top: 2px;
|
|
1306
|
+
}
|
|
1307
|
+
.ag-plan-tag {
|
|
1308
|
+
font: 500 9px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
1309
|
+
color: var(--ev-color, var(--dt-accent));
|
|
1310
|
+
background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
|
|
1311
|
+
padding: 2px 5px;
|
|
1312
|
+
border-radius: 3px;
|
|
1313
|
+
white-space: nowrap;
|
|
1314
|
+
}
|
|
1217
1315
|
|
|
1218
1316
|
/* Header badges for past/future days */
|
|
1219
1317
|
.ag-badge {
|
|
@@ -1396,6 +1494,20 @@
|
|
|
1396
1494
|
color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
|
|
1397
1495
|
flex-shrink: 0;
|
|
1398
1496
|
}
|
|
1497
|
+
.ag-compact-sub {
|
|
1498
|
+
font-size: 10px;
|
|
1499
|
+
color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
|
|
1500
|
+
flex-shrink: 0;
|
|
1501
|
+
}
|
|
1502
|
+
.ag-compact-tag {
|
|
1503
|
+
font: 500 8px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
1504
|
+
color: var(--ev-color, var(--dt-accent));
|
|
1505
|
+
background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 12%, transparent);
|
|
1506
|
+
padding: 1px 4px;
|
|
1507
|
+
border-radius: 3px;
|
|
1508
|
+
white-space: nowrap;
|
|
1509
|
+
flex-shrink: 0;
|
|
1510
|
+
}
|
|
1399
1511
|
.ag-compact-more {
|
|
1400
1512
|
font-size: 11px;
|
|
1401
1513
|
color: var(--dt-text-3);
|