@nomideusz/svelte-calendar 0.7.5 → 0.8.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 +19 -4
- package/dist/calendar/Calendar.svelte +364 -497
- package/dist/calendar/Calendar.svelte.d.ts +6 -2
- package/dist/core/clock.svelte.js +12 -5
- package/dist/core/locale.d.ts +4 -0
- package/dist/core/locale.js +4 -0
- package/dist/engine/view-state.svelte.d.ts +2 -2
- package/dist/engine/view-state.svelte.js +20 -0
- package/dist/headless/create-calendar.svelte.js +18 -2
- package/dist/headless/types.d.ts +6 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/primitives/DayHeader.svelte +9 -25
- package/dist/primitives/EmptySlot.svelte +15 -31
- package/dist/primitives/EventBlock.svelte +33 -61
- package/dist/primitives/NowIndicator.svelte +16 -42
- package/dist/primitives/TimeGutter.svelte +9 -23
- package/dist/views/agenda/Agenda.svelte +3 -10
- package/dist/views/agenda/AgendaDay.svelte +195 -167
- package/dist/views/agenda/AgendaWeek.svelte +141 -195
- package/dist/views/mobile/Mobile.svelte +3 -10
- package/dist/views/mobile/MobileDay.svelte +422 -266
- package/dist/views/mobile/MobileWeek.svelte +131 -186
- package/dist/views/month/MonthGrid.svelte +334 -0
- package/dist/views/month/MonthGrid.svelte.d.ts +15 -0
- package/dist/views/planner/Planner.svelte +3 -10
- package/dist/views/planner/PlannerDay.svelte +636 -546
- package/dist/views/planner/PlannerWeek.svelte +299 -412
- package/dist/views/shared/EventContent.svelte +16 -0
- package/dist/views/shared/EventContent.svelte.d.ts +9 -0
- package/dist/views/shared/context.svelte.d.ts +1 -0
- package/dist/views/shared/context.svelte.js +1 -0
- package/dist/widget/CalendarWidget.svelte +79 -116
- package/package.json +3 -2
- package/widget/svelte-calendar.css +321 -10
- package/widget/widget.js +1944 -1031
|
@@ -7,8 +7,8 @@ import type { AutoThemeOptions } from '../theme/auto.js';
|
|
|
7
7
|
export interface CalendarView {
|
|
8
8
|
id: CalendarViewId;
|
|
9
9
|
label: string;
|
|
10
|
-
/** day or
|
|
11
|
-
mode: 'day' | 'week';
|
|
10
|
+
/** day, week or month */
|
|
11
|
+
mode: 'day' | 'week' | 'month';
|
|
12
12
|
/** The Svelte component to render */
|
|
13
13
|
component: Component<Record<string, unknown>>;
|
|
14
14
|
/** Extra props to pass through (e.g. hourHeight, specialized settings) */
|
|
@@ -112,6 +112,10 @@ interface Props {
|
|
|
112
112
|
ondatechange?: (date: Date) => void;
|
|
113
113
|
/** Called when the pointer enters an event (hover). */
|
|
114
114
|
oneventhover?: (event: TimelineEvent) => void;
|
|
115
|
+
/** Called when a day cell is clicked (month grid; more views over time). */
|
|
116
|
+
ondayclick?: (date: Date) => void;
|
|
117
|
+
/** Surfaced instead of silent console output when loading or mutations fail. */
|
|
118
|
+
onerror?: (error: Error) => void;
|
|
115
119
|
}
|
|
116
120
|
declare const Calendar: Component<Props, {}, "">;
|
|
117
121
|
type Calendar = ReturnType<typeof Calendar>;
|
|
@@ -37,11 +37,18 @@ export function createClock() {
|
|
|
37
37
|
intervalId = null;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
// Auto-start and auto-cleanup
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
// Auto-start and auto-cleanup.
|
|
41
|
+
// Outside component initialisation (headless usage, tests) onMount throws —
|
|
42
|
+
// fall back to a static clock; callers may still call destroy().
|
|
43
|
+
try {
|
|
44
|
+
onMount(() => {
|
|
45
|
+
start();
|
|
46
|
+
return destroy;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// lifecycle_outside_component — no interval, clock stays at creation time
|
|
51
|
+
}
|
|
45
52
|
return {
|
|
46
53
|
get tick() { return tick; },
|
|
47
54
|
get today() { return today; },
|
package/dist/core/locale.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface CalendarLabels {
|
|
|
17
17
|
tomorrow: string;
|
|
18
18
|
day: string;
|
|
19
19
|
week: string;
|
|
20
|
+
month: string;
|
|
20
21
|
planner: string;
|
|
21
22
|
agenda: string;
|
|
22
23
|
now: string;
|
|
@@ -34,7 +35,9 @@ export interface CalendarLabels {
|
|
|
34
35
|
previousDay: string;
|
|
35
36
|
nextDay: string;
|
|
36
37
|
previousWeek: string;
|
|
38
|
+
previousMonth: string;
|
|
37
39
|
nextWeek: string;
|
|
40
|
+
nextMonth: string;
|
|
38
41
|
calendar: string;
|
|
39
42
|
viewMode: string;
|
|
40
43
|
dayNavigation: string;
|
|
@@ -56,6 +59,7 @@ export interface CalendarLabels {
|
|
|
56
59
|
nEvents: (n: number) => string;
|
|
57
60
|
/** e.g. "3 completed" */
|
|
58
61
|
nCompleted: (n: number) => string;
|
|
62
|
+
showLess: string;
|
|
59
63
|
/** e.g. "day 2 of 4" */
|
|
60
64
|
dayNOfTotal: (current: number, total: number) => string;
|
|
61
65
|
/** e.g. "75% complete" */
|
package/dist/core/locale.js
CHANGED
|
@@ -15,6 +15,7 @@ export const defaultLabels = {
|
|
|
15
15
|
tomorrow: 'Tomorrow',
|
|
16
16
|
day: 'Day',
|
|
17
17
|
week: 'Week',
|
|
18
|
+
month: 'Month',
|
|
18
19
|
planner: 'Planner',
|
|
19
20
|
agenda: 'Agenda',
|
|
20
21
|
now: 'now',
|
|
@@ -32,7 +33,9 @@ export const defaultLabels = {
|
|
|
32
33
|
previousDay: 'Previous day',
|
|
33
34
|
nextDay: 'Next day',
|
|
34
35
|
previousWeek: 'Previous week',
|
|
36
|
+
previousMonth: 'Previous month',
|
|
35
37
|
nextWeek: 'Next week',
|
|
38
|
+
nextMonth: 'Next month',
|
|
36
39
|
calendar: 'Calendar',
|
|
37
40
|
viewMode: 'View mode',
|
|
38
41
|
dayNavigation: 'Day navigation',
|
|
@@ -51,6 +54,7 @@ export const defaultLabels = {
|
|
|
51
54
|
nMore: (n) => `+${n} more`,
|
|
52
55
|
nEvents: (n) => `${n} event${n === 1 ? '' : 's'}`,
|
|
53
56
|
nCompleted: (n) => `${n} completed`,
|
|
57
|
+
showLess: 'Show less',
|
|
54
58
|
dayNOfTotal: (current, total) => `day ${current} of ${total}`,
|
|
55
59
|
percentComplete: (pct) => `${pct}% complete`,
|
|
56
60
|
};
|
|
@@ -4,13 +4,13 @@ export type { DateRange };
|
|
|
4
4
|
* Built-in view IDs. Custom view IDs are also supported — CalendarViewId
|
|
5
5
|
* is typed as `string` so consumers can register any ID.
|
|
6
6
|
*/
|
|
7
|
-
export type BuiltInViewId = 'day-planner' | 'day-agenda' | 'week-planner' | 'week-agenda';
|
|
7
|
+
export type BuiltInViewId = 'day-planner' | 'day-agenda' | 'week-planner' | 'week-agenda' | 'month-grid';
|
|
8
8
|
/**
|
|
9
9
|
* Any view identifier. Use built-in strings like 'day-planner' or your own
|
|
10
10
|
* custom IDs like 'day-kanban', 'week-resource', etc.
|
|
11
11
|
*/
|
|
12
12
|
export type CalendarViewId = string;
|
|
13
|
-
export type ViewMode = 'day' | 'week';
|
|
13
|
+
export type ViewMode = 'day' | 'week' | 'month';
|
|
14
14
|
export interface ViewStateOptions {
|
|
15
15
|
view?: CalendarViewId;
|
|
16
16
|
mondayStart?: boolean;
|
|
@@ -15,6 +15,8 @@ import { startOfWeek as calcStartOfWeek, addDaysMs, DAY_MS } from '../core/time.
|
|
|
15
15
|
function inferMode(view) {
|
|
16
16
|
if (view.startsWith('day'))
|
|
17
17
|
return 'day';
|
|
18
|
+
if (view.startsWith('month'))
|
|
19
|
+
return 'month';
|
|
18
20
|
return 'week';
|
|
19
21
|
}
|
|
20
22
|
function computeRange(focus, mode, mondayStart, dayCount = 7) {
|
|
@@ -24,6 +26,15 @@ function computeRange(focus, mode, mondayStart, dayCount = 7) {
|
|
|
24
26
|
const end = new Date(start.getTime() + DAY_MS);
|
|
25
27
|
return { start, end };
|
|
26
28
|
}
|
|
29
|
+
if (mode === 'month') {
|
|
30
|
+
// Week-aligned grid covering the focus month: 4–6 rows depending on
|
|
31
|
+
// where the month's edges fall.
|
|
32
|
+
const first = new Date(focus.getFullYear(), focus.getMonth(), 1);
|
|
33
|
+
const last = new Date(focus.getFullYear(), focus.getMonth() + 1, 0);
|
|
34
|
+
const gridStart = calcStartOfWeek(first.getTime(), mondayStart);
|
|
35
|
+
const gridEnd = addDaysMs(calcStartOfWeek(last.getTime(), mondayStart), 7);
|
|
36
|
+
return { start: new Date(gridStart), end: new Date(gridEnd) };
|
|
37
|
+
}
|
|
27
38
|
// week / custom period
|
|
28
39
|
if (dayCount === 7) {
|
|
29
40
|
const ws = calcStartOfWeek(focus.getTime(), mondayStart);
|
|
@@ -84,10 +95,19 @@ export function createViewState(options = {}) {
|
|
|
84
95
|
dayCount = n;
|
|
85
96
|
},
|
|
86
97
|
next() {
|
|
98
|
+
if (mode === 'month') {
|
|
99
|
+
// Anchor to the 1st so a Jan 31 focus can't skip February.
|
|
100
|
+
focusDate = new Date(focusDate.getFullYear(), focusDate.getMonth() + 1, 1);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
87
103
|
const days = mode === 'day' ? 1 : dayCount;
|
|
88
104
|
focusDate = new Date(addDaysMs(focusDate.getTime(), days));
|
|
89
105
|
},
|
|
90
106
|
prev() {
|
|
107
|
+
if (mode === 'month') {
|
|
108
|
+
focusDate = new Date(focusDate.getFullYear(), focusDate.getMonth() - 1, 1);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
91
111
|
const days = mode === 'day' ? -1 : -dayCount;
|
|
92
112
|
focusDate = new Date(addDaysMs(focusDate.getTime(), days));
|
|
93
113
|
},
|
|
@@ -182,7 +182,7 @@ export function createCalendar(options) {
|
|
|
182
182
|
return {
|
|
183
183
|
dateLabel,
|
|
184
184
|
mode,
|
|
185
|
-
modes: ['day', 'week'],
|
|
185
|
+
modes: ['day', 'week', 'month'],
|
|
186
186
|
switchMode: (m) => {
|
|
187
187
|
const currentView = viewState.view;
|
|
188
188
|
const currentLabel = currentView.replace(/^(day|week)-/, '');
|
|
@@ -220,6 +220,15 @@ export function createCalendar(options) {
|
|
|
220
220
|
let { start, end } = payload;
|
|
221
221
|
// Enforce min/max duration
|
|
222
222
|
if (mode === 'create' || mode === 'resize-start' || mode === 'resize-end') {
|
|
223
|
+
// Defensive floor: never accept a zero/negative duration, even when
|
|
224
|
+
// minDuration is unset (views clamp, but the engine must hold alone).
|
|
225
|
+
if (end.getTime() <= start.getTime()) {
|
|
226
|
+
const floorMs = Math.max(1, snapInterval) * 60_000;
|
|
227
|
+
if (mode === 'resize-start')
|
|
228
|
+
start = new Date(end.getTime() - floorMs);
|
|
229
|
+
else
|
|
230
|
+
end = new Date(start.getTime() + floorMs);
|
|
231
|
+
}
|
|
223
232
|
const durationMs = end.getTime() - start.getTime();
|
|
224
233
|
const durationMin = durationMs / 60_000;
|
|
225
234
|
if (minDuration && durationMin < minDuration) {
|
|
@@ -276,7 +285,14 @@ export function createCalendar(options) {
|
|
|
276
285
|
}
|
|
277
286
|
catch (e) {
|
|
278
287
|
const msg = e instanceof Error ? e.message : '';
|
|
279
|
-
|
|
288
|
+
// Read-only adapter: the host still gets the callback — it owns
|
|
289
|
+
// persistence and refetches.
|
|
290
|
+
if (msg.includes('read-only')) {
|
|
291
|
+
const ev = store.byId(payload.eventId);
|
|
292
|
+
if (ev)
|
|
293
|
+
oneventmove?.(ev, start, end);
|
|
294
|
+
}
|
|
295
|
+
else if (!msg.includes('not found')) {
|
|
280
296
|
console.warn('[calendar] drag commit failed:', e);
|
|
281
297
|
}
|
|
282
298
|
return null;
|
package/dist/headless/types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { CalendarAdapter } from '../adapters/types.js';
|
|
8
8
|
import type { TimelineEvent, BlockedSlot } from '../core/types.js';
|
|
9
9
|
import type { DaySegment } from '../core/time.js';
|
|
10
|
-
import type { CalendarViewId } from '../engine/view-state.svelte.js';
|
|
10
|
+
import type { CalendarViewId, ViewMode } from '../engine/view-state.svelte.js';
|
|
11
11
|
import type { EventStore } from '../engine/event-store.svelte.js';
|
|
12
12
|
import type { ViewState } from '../engine/view-state.svelte.js';
|
|
13
13
|
import type { Selection } from '../engine/selection.svelte.js';
|
|
@@ -96,11 +96,11 @@ export interface HeaderContext {
|
|
|
96
96
|
/** Formatted date label for current view */
|
|
97
97
|
dateLabel: string;
|
|
98
98
|
/** Current mode */
|
|
99
|
-
mode:
|
|
99
|
+
mode: ViewMode;
|
|
100
100
|
/** Available modes */
|
|
101
|
-
modes:
|
|
101
|
+
modes: ViewMode[];
|
|
102
102
|
/** Switch to a different mode */
|
|
103
|
-
switchMode: (mode:
|
|
103
|
+
switchMode: (mode: ViewMode) => void;
|
|
104
104
|
/** Go to previous period */
|
|
105
105
|
prev: () => void;
|
|
106
106
|
/** Go to next period */
|
|
@@ -118,7 +118,7 @@ export interface NavigationContext {
|
|
|
118
118
|
goToday: () => void;
|
|
119
119
|
isViewOnToday: boolean;
|
|
120
120
|
focusDate: Date;
|
|
121
|
-
mode:
|
|
121
|
+
mode: ViewMode;
|
|
122
122
|
}
|
|
123
123
|
export interface HeadlessCalendar {
|
|
124
124
|
/** All loaded events for the current range */
|
|
@@ -128,7 +128,7 @@ export interface HeadlessCalendar {
|
|
|
128
128
|
/** The currently focused date */
|
|
129
129
|
readonly focusDate: Date;
|
|
130
130
|
/** Current mode: 'day' or 'week' */
|
|
131
|
-
readonly mode:
|
|
131
|
+
readonly mode: ViewMode;
|
|
132
132
|
/** Current view ID */
|
|
133
133
|
readonly view: CalendarViewId;
|
|
134
134
|
/** Visible date range */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { NowIndicator, EventBlock, TimeGutter, DayHeader, EmptySlot, } from './primitives/index.js';
|
|
2
2
|
export { Calendar } from './calendar/index.js';
|
|
3
|
+
export { Planner, Agenda, Mobile } from './views/index.js';
|
|
4
|
+
export { default as MonthGrid } from './views/month/MonthGrid.svelte';
|
|
3
5
|
export type { CalendarView } from './calendar/index.js';
|
|
4
6
|
export { createEventStore, createViewState, createSelection, createDragState, } from './engine/index.js';
|
|
5
7
|
export type { EventStore, ViewState, ViewStateOptions, CalendarViewId, BuiltInViewId, ViewMode, Selection, DragState, DragMode, DragPayload, } from './engine/index.js';
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
export { NowIndicator, EventBlock, TimeGutter, DayHeader, EmptySlot, } from './primitives/index.js';
|
|
3
3
|
// ─── Calendar shell ─────────────────────────────────────
|
|
4
4
|
export { Calendar } from './calendar/index.js';
|
|
5
|
+
// Raw view components — compose your own shell around the engine if needed
|
|
6
|
+
export { Planner, Agenda, Mobile } from './views/index.js';
|
|
7
|
+
export { default as MonthGrid } from './views/month/MonthGrid.svelte';
|
|
5
8
|
// ─── Engine (reactive state) ────────────────────────────
|
|
6
9
|
export { createEventStore, createViewState, createSelection, createDragState, } from './engine/index.js';
|
|
7
10
|
// ─── Adapters ───────────────────────────────────────────
|
|
@@ -5,31 +5,15 @@
|
|
|
5
5
|
- Relative labels ("Today", "Tomorrow") for day views via fmtDay
|
|
6
6
|
- Short/long weekday + date number for week views
|
|
7
7
|
-->
|
|
8
|
-
<script lang="ts">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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());
|
|
8
|
+
<script lang="ts">import { weekdayShort, weekdayLong, monthShort, fmtDay } from "../core/locale.js";
|
|
9
|
+
let {
|
|
10
|
+
dayMs,
|
|
11
|
+
todayMs = Date.now(),
|
|
12
|
+
format = "short",
|
|
13
|
+
isToday = false,
|
|
14
|
+
isPast = false
|
|
15
|
+
} = $props();
|
|
16
|
+
const dayNum = $derived(new Date(dayMs).getDate());
|
|
33
17
|
</script>
|
|
34
18
|
|
|
35
19
|
<div
|
|
@@ -5,37 +5,21 @@
|
|
|
5
5
|
the time range when clicked. View components place these in gaps
|
|
6
6
|
between events.
|
|
7
7
|
-->
|
|
8
|
-
<script lang="ts">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
}
|
|
8
|
+
<script lang="ts">import { fmtTime, fmtDuration, getLabels } from "../core/locale.js";
|
|
9
|
+
const L = $derived(getLabels());
|
|
10
|
+
let {
|
|
11
|
+
start,
|
|
12
|
+
end,
|
|
13
|
+
onclick,
|
|
14
|
+
orientation = "vertical"
|
|
15
|
+
} = $props();
|
|
16
|
+
const dur = $derived(`${fmtDuration(start, end)} ${L.free}`);
|
|
17
|
+
function handleKeydown(e) {
|
|
18
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
onclick?.({ start, end });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
39
23
|
</script>
|
|
40
24
|
|
|
41
25
|
<div
|
|
@@ -8,67 +8,39 @@
|
|
|
8
8
|
|
|
9
9
|
Emits: onclick, ondragstart, onresize (for future interaction wiring)
|
|
10
10
|
-->
|
|
11
|
-
<script lang="ts">
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
showDuration = false,
|
|
45
|
-
editable = false,
|
|
46
|
-
onclick,
|
|
47
|
-
children,
|
|
48
|
-
}: Props = $props();
|
|
49
|
-
|
|
50
|
-
const accentColor = $derived(event.color || 'var(--dt-accent, #2563eb)');
|
|
51
|
-
const isCancelled = $derived(event.status === 'cancelled');
|
|
52
|
-
const isTentative = $derived(event.status === 'tentative');
|
|
53
|
-
const isFull = $derived(event.status === 'full');
|
|
54
|
-
const isLimited = $derived(event.status === 'limited');
|
|
55
|
-
|
|
56
|
-
const ariaLabel = $derived.by(() => {
|
|
57
|
-
const t = event.title;
|
|
58
|
-
const time = `${fmtTime(event.start)} to ${fmtTime(event.end)}`;
|
|
59
|
-
const dur = fmtDuration(event.start, event.end);
|
|
60
|
-
const loc = event.location ? `, ${event.location}` : '';
|
|
61
|
-
const statusStr = isCancelled ? ', cancelled' : isTentative ? ', tentative' : isFull ? ', full' : isLimited ? ', limited' : '';
|
|
62
|
-
const activeStr = active ? `, ${L.happeningNow}` : past ? `, ${L.past}` : '';
|
|
63
|
-
return `${t}${loc}, ${time}, ${dur}${statusStr}${activeStr}`;
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
function handleKeydown(e: KeyboardEvent) {
|
|
67
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
68
|
-
e.preventDefault();
|
|
69
|
-
onclick?.(event);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
11
|
+
<script lang="ts">import { fmtTime, fmtDuration, getLabels } from "../core/locale.js";
|
|
12
|
+
const L = $derived(getLabels());
|
|
13
|
+
let {
|
|
14
|
+
event,
|
|
15
|
+
variant = "chip",
|
|
16
|
+
active = false,
|
|
17
|
+
past = false,
|
|
18
|
+
showTime = false,
|
|
19
|
+
showDuration = false,
|
|
20
|
+
editable = false,
|
|
21
|
+
onclick,
|
|
22
|
+
children
|
|
23
|
+
} = $props();
|
|
24
|
+
const accentColor = $derived(event.color || "var(--dt-accent, #2563eb)");
|
|
25
|
+
const isCancelled = $derived(event.status === "cancelled");
|
|
26
|
+
const isTentative = $derived(event.status === "tentative");
|
|
27
|
+
const isFull = $derived(event.status === "full");
|
|
28
|
+
const isLimited = $derived(event.status === "limited");
|
|
29
|
+
const ariaLabel = $derived.by(() => {
|
|
30
|
+
const t = event.title;
|
|
31
|
+
const time = `${fmtTime(event.start)} to ${fmtTime(event.end)}`;
|
|
32
|
+
const dur = fmtDuration(event.start, event.end);
|
|
33
|
+
const loc = event.location ? `, ${event.location}` : "";
|
|
34
|
+
const statusStr = isCancelled ? ", cancelled" : isTentative ? ", tentative" : isFull ? ", full" : isLimited ? ", limited" : "";
|
|
35
|
+
const activeStr = active ? `, ${L.happeningNow}` : past ? `, ${L.past}` : "";
|
|
36
|
+
return `${t}${loc}, ${time}, ${dur}${statusStr}${activeStr}`;
|
|
37
|
+
});
|
|
38
|
+
function handleKeydown(e) {
|
|
39
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
onclick?.(event);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
72
44
|
</script>
|
|
73
45
|
|
|
74
46
|
{#snippet content()}
|
|
@@ -11,48 +11,22 @@
|
|
|
11
11
|
<NowIndicator mode="dot" position={nowPx} time={clock.hm} />
|
|
12
12
|
<NowIndicator mode="badge" />
|
|
13
13
|
-->
|
|
14
|
-
<script lang="ts">
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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}` : '');
|
|
14
|
+
<script lang="ts">import { getLabels } from "../core/locale.js";
|
|
15
|
+
const L = $derived(getLabels());
|
|
16
|
+
let {
|
|
17
|
+
mode = "line",
|
|
18
|
+
position = 0,
|
|
19
|
+
orientation = "vertical",
|
|
20
|
+
time = "",
|
|
21
|
+
seconds = "",
|
|
22
|
+
showLabel = true,
|
|
23
|
+
color,
|
|
24
|
+
children
|
|
25
|
+
} = $props();
|
|
26
|
+
const posStyle = $derived(
|
|
27
|
+
orientation === "vertical" ? `left: ${position}px` : `top: ${position}px`
|
|
28
|
+
);
|
|
29
|
+
const colorVar = $derived(color ? `--ni-color: ${color}` : "");
|
|
56
30
|
</script>
|
|
57
31
|
|
|
58
32
|
{#if mode === 'badge'}
|
|
@@ -5,29 +5,15 @@
|
|
|
5
5
|
"horizontal" — ticks along a horizontal track (for day timeline-style layouts)
|
|
6
6
|
"vertical" — label column along a vertical grid (for WeekTimeline style)
|
|
7
7
|
-->
|
|
8
|
-
<script lang="ts">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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();
|
|
8
|
+
<script lang="ts">import { HOURS } from "../core/time.js";
|
|
9
|
+
import { fmtH } from "../core/locale.js";
|
|
10
|
+
let {
|
|
11
|
+
orientation = "horizontal",
|
|
12
|
+
hourSize = 120,
|
|
13
|
+
halfHour = true,
|
|
14
|
+
hours = HOURS,
|
|
15
|
+
formatHour = fmtH
|
|
16
|
+
} = $props();
|
|
31
17
|
</script>
|
|
32
18
|
|
|
33
19
|
{#if orientation === 'horizontal'}
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
mode?: 'day' | 'week';
|
|
7
|
-
[key: string]: unknown;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
let { mode = 'day', ...rest }: Props = $props();
|
|
1
|
+
<script lang="ts">import AgendaDay from "./AgendaDay.svelte";
|
|
2
|
+
import AgendaWeek from "./AgendaWeek.svelte";
|
|
3
|
+
let { mode = "day", ...rest } = $props();
|
|
11
4
|
</script>
|
|
12
5
|
|
|
13
6
|
{#if mode === 'day'}
|