@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.
Files changed (36) hide show
  1. package/README.md +19 -4
  2. package/dist/calendar/Calendar.svelte +364 -497
  3. package/dist/calendar/Calendar.svelte.d.ts +6 -2
  4. package/dist/core/clock.svelte.js +12 -5
  5. package/dist/core/locale.d.ts +4 -0
  6. package/dist/core/locale.js +4 -0
  7. package/dist/engine/view-state.svelte.d.ts +2 -2
  8. package/dist/engine/view-state.svelte.js +20 -0
  9. package/dist/headless/create-calendar.svelte.js +18 -2
  10. package/dist/headless/types.d.ts +6 -6
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +3 -0
  13. package/dist/primitives/DayHeader.svelte +9 -25
  14. package/dist/primitives/EmptySlot.svelte +15 -31
  15. package/dist/primitives/EventBlock.svelte +33 -61
  16. package/dist/primitives/NowIndicator.svelte +16 -42
  17. package/dist/primitives/TimeGutter.svelte +9 -23
  18. package/dist/views/agenda/Agenda.svelte +3 -10
  19. package/dist/views/agenda/AgendaDay.svelte +195 -167
  20. package/dist/views/agenda/AgendaWeek.svelte +141 -195
  21. package/dist/views/mobile/Mobile.svelte +3 -10
  22. package/dist/views/mobile/MobileDay.svelte +422 -266
  23. package/dist/views/mobile/MobileWeek.svelte +131 -186
  24. package/dist/views/month/MonthGrid.svelte +334 -0
  25. package/dist/views/month/MonthGrid.svelte.d.ts +15 -0
  26. package/dist/views/planner/Planner.svelte +3 -10
  27. package/dist/views/planner/PlannerDay.svelte +636 -546
  28. package/dist/views/planner/PlannerWeek.svelte +299 -412
  29. package/dist/views/shared/EventContent.svelte +16 -0
  30. package/dist/views/shared/EventContent.svelte.d.ts +9 -0
  31. package/dist/views/shared/context.svelte.d.ts +1 -0
  32. package/dist/views/shared/context.svelte.js +1 -0
  33. package/dist/widget/CalendarWidget.svelte +79 -116
  34. package/package.json +3 -2
  35. package/widget/svelte-calendar.css +321 -10
  36. 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 week */
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
- onMount(() => {
42
- start();
43
- return destroy;
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; },
@@ -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" */
@@ -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
- if (!msg.includes('read-only') && !msg.includes('not found')) {
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;
@@ -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: 'day' | 'week';
99
+ mode: ViewMode;
100
100
  /** Available modes */
101
- modes: ('day' | 'week')[];
101
+ modes: ViewMode[];
102
102
  /** Switch to a different mode */
103
- switchMode: (mode: 'day' | 'week') => void;
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: 'day' | 'week';
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: 'day' | 'week';
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
- import { weekdayShort, weekdayLong, monthShort, fmtDay } from '../core/locale.js';
10
-
11
- interface Props {
12
- /** Timestamp (ms) of the day to label */
13
- dayMs: number;
14
- /** Timestamp (ms) of today (for relative labels) */
15
- todayMs?: number;
16
- /** Display format */
17
- format?: 'relative' | 'short' | 'long';
18
- /** Whether this day is "today" */
19
- isToday?: boolean;
20
- /** Whether this day is in the past */
21
- isPast?: boolean;
22
- }
23
-
24
- let {
25
- dayMs,
26
- todayMs = Date.now(),
27
- format = 'short',
28
- isToday = false,
29
- isPast = false,
30
- }: Props = $props();
31
-
32
- const dayNum = $derived(new Date(dayMs).getDate());
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
- import { fmtTime, fmtDuration, getLabels } from '../core/locale.js';
10
-
11
- const L = $derived(getLabels());
12
-
13
- interface Props {
14
- /** Start time of the empty slot */
15
- start: Date;
16
- /** End time of the empty slot */
17
- end: Date;
18
- /** Click handler create event in this range */
19
- onclick?: (range: { start: Date; end: Date }) => void;
20
- /** Orientation for cursor hint */
21
- orientation?: 'horizontal' | 'vertical';
22
- }
23
-
24
- let {
25
- start,
26
- end,
27
- onclick,
28
- orientation = 'vertical',
29
- }: Props = $props();
30
-
31
- const dur = $derived(`${fmtDuration(start, end)} ${L.free}`);
32
-
33
- function handleKeydown(e: KeyboardEvent) {
34
- if (e.key === 'Enter' || e.key === ' ') {
35
- e.preventDefault();
36
- onclick?.({ start, end });
37
- }
38
- }
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
- 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, #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
- import type { Snippet } from 'svelte';
16
- import { getLabels } from '../core/locale.js';
17
-
18
- const L = $derived(getLabels());
19
-
20
- interface Props {
21
- /** Visual mode */
22
- mode?: 'line' | 'dot' | 'badge';
23
- /** Pixel offset for positioning (line/dot modes) */
24
- position?: number;
25
- /** Orientation of the line/dot */
26
- orientation?: 'vertical' | 'horizontal';
27
- /** Formatted time string (e.g. "14:30") */
28
- time?: string;
29
- /** Formatted seconds string (e.g. ":05") */
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
- import { HOURS } from '../core/time.js';
10
- import { fmtH } from '../core/locale.js';
11
-
12
- interface Props {
13
- orientation?: 'horizontal' | 'vertical';
14
- /** Pixel size per hour (width for horizontal, height for vertical) */
15
- hourSize?: number;
16
- /** Show half-hour ticks (horizontal only) */
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
- import AgendaDay from './AgendaDay.svelte';
3
- import AgendaWeek from './AgendaWeek.svelte';
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'}