@nomideusz/svelte-calendar 0.2.3 → 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 +234 -6
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/memory.d.ts +12 -1
- package/dist/adapters/memory.js +38 -4
- package/dist/adapters/recurring.d.ts +44 -0
- package/dist/adapters/recurring.js +120 -0
- package/dist/calendar/Calendar.svelte +20 -5
- package/dist/calendar/Calendar.svelte.d.ts +4 -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/core/types.d.ts +4 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -4
- package/dist/primitives/EventBlock.svelte +42 -0
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/theme/presets.d.ts +10 -15
- package/dist/theme/presets.js +5 -11
- package/dist/views/agenda/Agenda.svelte +110 -0
- package/dist/views/day/DayGrid.svelte +38 -0
- package/dist/views/day/DayGrid.svelte.d.ts +5 -0
- package/dist/views/day/DayTimeline.svelte +27 -0
- package/dist/views/index.d.ts +1 -0
- package/dist/views/index.js +1 -0
- package/dist/views/schedule/WeekSchedule.svelte +133 -0
- package/dist/views/schedule/WeekSchedule.svelte.d.ts +38 -0
- package/dist/views/schedule/index.d.ts +1 -0
- package/dist/views/schedule/index.js +2 -0
- package/dist/views/settings/Settings.svelte +1 -1
- package/dist/views/week/WeekGrid.svelte +42 -5
- package/dist/views/week/WeekGrid.svelte.d.ts +3 -0
- package/dist/views/week/WeekHeatmap.svelte +12 -5
- package/dist/views/week/WeekHeatmap.svelte.d.ts +5 -1
- package/dist/widget/CalendarWidget.svelte +138 -0
- package/dist/widget/CalendarWidget.svelte.d.ts +23 -0
- package/dist/widget/index.d.ts +1 -0
- package/dist/widget/index.js +1 -0
- package/dist/widget/widget.d.ts +7 -0
- package/dist/widget/widget.js +9 -0
- package/package.json +5 -2
- package/widget/widget.js +260 -0
package/dist/views/index.d.ts
CHANGED
package/dist/views/index.js
CHANGED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
WeekSchedule — zero-config weekly schedule display.
|
|
3
|
+
|
|
4
|
+
A convenience wrapper that pre-wires Calendar + WeekGrid + Agenda
|
|
5
|
+
so consumers can render a read-only weekly schedule with a single import.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
<WeekSchedule
|
|
9
|
+
events={events}
|
|
10
|
+
theme={neutral}
|
|
11
|
+
locale="pl-PL"
|
|
12
|
+
height={560}
|
|
13
|
+
readOnly
|
|
14
|
+
/>
|
|
15
|
+
|
|
16
|
+
Or with a recurring adapter:
|
|
17
|
+
<WeekSchedule
|
|
18
|
+
schedule={weeklyClasses}
|
|
19
|
+
theme={neutral}
|
|
20
|
+
readOnly
|
|
21
|
+
/>
|
|
22
|
+
-->
|
|
23
|
+
<script lang="ts">
|
|
24
|
+
import type { TimelineEvent } from '../../core/types.js';
|
|
25
|
+
import type { RecurringEvent } from '../../adapters/recurring.js';
|
|
26
|
+
import type { MemoryAdapterOptions } from '../../adapters/memory.js';
|
|
27
|
+
import { createMemoryAdapter } from '../../adapters/memory.js';
|
|
28
|
+
import { createRecurringAdapter, type RecurringAdapterOptions } from '../../adapters/recurring.js';
|
|
29
|
+
import Calendar from '../../calendar/Calendar.svelte';
|
|
30
|
+
import type { CalendarView } from '../../calendar/Calendar.svelte';
|
|
31
|
+
import { WeekGrid } from '../week/index.js';
|
|
32
|
+
import { Agenda } from '../agenda/index.js';
|
|
33
|
+
|
|
34
|
+
interface Props {
|
|
35
|
+
/** Concrete events (mutually exclusive with `schedule`) */
|
|
36
|
+
events?: TimelineEvent[];
|
|
37
|
+
/** Recurring weekly schedule (mutually exclusive with `events`) */
|
|
38
|
+
schedule?: RecurringEvent[];
|
|
39
|
+
/** CSS theme string */
|
|
40
|
+
theme?: string;
|
|
41
|
+
/** BCP 47 locale */
|
|
42
|
+
locale?: string;
|
|
43
|
+
/** Total height */
|
|
44
|
+
height?: number;
|
|
45
|
+
/** Start week on Monday */
|
|
46
|
+
mondayStart?: boolean;
|
|
47
|
+
/** Read-only mode (default: true for schedule display) */
|
|
48
|
+
readOnly?: boolean;
|
|
49
|
+
/** Visible hour range [startHour, endHour) */
|
|
50
|
+
visibleHours?: [number, number];
|
|
51
|
+
/** Show toolbar */
|
|
52
|
+
showToolbar?: boolean;
|
|
53
|
+
/** Show agenda view toggle */
|
|
54
|
+
showAgenda?: boolean;
|
|
55
|
+
/** Map of category/title to color */
|
|
56
|
+
colorMap?: Record<string, string>;
|
|
57
|
+
/** Auto-assign colors */
|
|
58
|
+
autoColor?: boolean;
|
|
59
|
+
/** Event click handler */
|
|
60
|
+
oneventclick?: (event: TimelineEvent) => void;
|
|
61
|
+
/** Event create handler (only works when readOnly is false) */
|
|
62
|
+
oneventcreate?: (range: { start: Date; end: Date }) => void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let {
|
|
66
|
+
events,
|
|
67
|
+
schedule,
|
|
68
|
+
theme = '',
|
|
69
|
+
locale,
|
|
70
|
+
height = 560,
|
|
71
|
+
mondayStart = true,
|
|
72
|
+
readOnly = true,
|
|
73
|
+
visibleHours,
|
|
74
|
+
showToolbar = true,
|
|
75
|
+
showAgenda = true,
|
|
76
|
+
colorMap,
|
|
77
|
+
autoColor,
|
|
78
|
+
oneventclick,
|
|
79
|
+
oneventcreate,
|
|
80
|
+
}: Props = $props();
|
|
81
|
+
|
|
82
|
+
// Auto-create the adapter from events or schedule
|
|
83
|
+
const adapter = $derived.by(() => {
|
|
84
|
+
if (schedule) {
|
|
85
|
+
return createRecurringAdapter(schedule, {
|
|
86
|
+
mondayStart,
|
|
87
|
+
colorMap,
|
|
88
|
+
autoColor,
|
|
89
|
+
} satisfies RecurringAdapterOptions);
|
|
90
|
+
}
|
|
91
|
+
return createMemoryAdapter(events ?? [], {
|
|
92
|
+
colorMap,
|
|
93
|
+
autoColor,
|
|
94
|
+
} satisfies MemoryAdapterOptions);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Pre-wired views
|
|
98
|
+
const views = $derived.by((): CalendarView[] => {
|
|
99
|
+
const v: CalendarView[] = [
|
|
100
|
+
{
|
|
101
|
+
id: 'week-grid',
|
|
102
|
+
label: 'Week',
|
|
103
|
+
granularity: 'week',
|
|
104
|
+
component: WeekGrid,
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
if (showAgenda) {
|
|
108
|
+
v.push({
|
|
109
|
+
id: 'agenda',
|
|
110
|
+
label: 'Agenda',
|
|
111
|
+
granularity: 'week',
|
|
112
|
+
component: Agenda,
|
|
113
|
+
props: { mode: 'week' },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return v;
|
|
117
|
+
});
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<Calendar
|
|
121
|
+
{adapter}
|
|
122
|
+
{views}
|
|
123
|
+
defaultView="week-grid"
|
|
124
|
+
{theme}
|
|
125
|
+
{locale}
|
|
126
|
+
{height}
|
|
127
|
+
{mondayStart}
|
|
128
|
+
{readOnly}
|
|
129
|
+
{visibleHours}
|
|
130
|
+
{showToolbar}
|
|
131
|
+
{oneventclick}
|
|
132
|
+
oneventcreate={readOnly ? undefined : oneventcreate}
|
|
133
|
+
/>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TimelineEvent } from '../../core/types.js';
|
|
2
|
+
import type { RecurringEvent } from '../../adapters/recurring.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Concrete events (mutually exclusive with `schedule`) */
|
|
5
|
+
events?: TimelineEvent[];
|
|
6
|
+
/** Recurring weekly schedule (mutually exclusive with `events`) */
|
|
7
|
+
schedule?: RecurringEvent[];
|
|
8
|
+
/** CSS theme string */
|
|
9
|
+
theme?: string;
|
|
10
|
+
/** BCP 47 locale */
|
|
11
|
+
locale?: string;
|
|
12
|
+
/** Total height */
|
|
13
|
+
height?: number;
|
|
14
|
+
/** Start week on Monday */
|
|
15
|
+
mondayStart?: boolean;
|
|
16
|
+
/** Read-only mode (default: true for schedule display) */
|
|
17
|
+
readOnly?: boolean;
|
|
18
|
+
/** Visible hour range [startHour, endHour) */
|
|
19
|
+
visibleHours?: [number, number];
|
|
20
|
+
/** Show toolbar */
|
|
21
|
+
showToolbar?: boolean;
|
|
22
|
+
/** Show agenda view toggle */
|
|
23
|
+
showAgenda?: boolean;
|
|
24
|
+
/** Map of category/title to color */
|
|
25
|
+
colorMap?: Record<string, string>;
|
|
26
|
+
/** Auto-assign colors */
|
|
27
|
+
autoColor?: boolean;
|
|
28
|
+
/** Event click handler */
|
|
29
|
+
oneventclick?: (event: TimelineEvent) => void;
|
|
30
|
+
/** Event create handler (only works when readOnly is false) */
|
|
31
|
+
oneventcreate?: (range: {
|
|
32
|
+
start: Date;
|
|
33
|
+
end: Date;
|
|
34
|
+
}) => void;
|
|
35
|
+
}
|
|
36
|
+
declare const WeekSchedule: import("svelte").Component<Props, {}, "">;
|
|
37
|
+
type WeekSchedule = ReturnType<typeof WeekSchedule>;
|
|
38
|
+
export default WeekSchedule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as WeekSchedule } from './WeekSchedule.svelte';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { slide } from 'svelte/transition';
|
|
3
3
|
import type { PresetName } from '../../theme/presets.js';
|
|
4
|
-
import { presets
|
|
4
|
+
import { presets } from '../../theme/presets.js';
|
|
5
5
|
|
|
6
6
|
/* ─── Field definition types ─────────────────────── */
|
|
7
7
|
type RangeField = {
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
oneventclick?: (event: TimelineEvent) => void;
|
|
31
31
|
oneventcreate?: (range: { start: Date; end: Date }) => void;
|
|
32
32
|
selectedEventId?: string | null;
|
|
33
|
+
readOnly?: boolean;
|
|
34
|
+
visibleHours?: [number, number];
|
|
35
|
+
[key: string]: unknown;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
let {
|
|
@@ -41,6 +44,8 @@
|
|
|
41
44
|
oneventclick,
|
|
42
45
|
oneventcreate,
|
|
43
46
|
selectedEventId = null,
|
|
47
|
+
readOnly = false,
|
|
48
|
+
...rest
|
|
44
49
|
}: Props = $props();
|
|
45
50
|
|
|
46
51
|
// ── Drag support (available when inside Calendar) ──
|
|
@@ -162,7 +167,7 @@
|
|
|
162
167
|
function handleDayCellClick(ms: number, e: Event) {
|
|
163
168
|
const target = e.target as HTMLElement;
|
|
164
169
|
if (target.closest('.wg-ev')) return;
|
|
165
|
-
if (!oneventcreate) return;
|
|
170
|
+
if (readOnly || !oneventcreate) return;
|
|
166
171
|
const start = new Date(ms + 9 * 3_600_000);
|
|
167
172
|
const end = new Date(ms + 10 * 3_600_000);
|
|
168
173
|
oneventcreate({ start, end });
|
|
@@ -182,7 +187,7 @@
|
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
function onEventPointerDown(e: PointerEvent, ev: TimelineEvent) {
|
|
185
|
-
if (e.button !== 0 || !drag) return;
|
|
190
|
+
if (e.button !== 0 || !drag || readOnly) return;
|
|
186
191
|
e.stopPropagation();
|
|
187
192
|
evDragStartX = e.clientX;
|
|
188
193
|
evDragStarted = false;
|
|
@@ -298,8 +303,16 @@
|
|
|
298
303
|
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); oneventclick?.(ev); } }}
|
|
299
304
|
>
|
|
300
305
|
<span class="wg-ev-time">{fmtAmPm(ev.start)}</span>
|
|
301
|
-
<span class="wg-ev-title">{ev.title}</span>
|
|
302
|
-
|
|
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>
|
|
303
316
|
{/each}
|
|
304
317
|
{#if day.events.length > MAX_EVENTS_SHOWN}
|
|
305
318
|
<div class="wg-ev-more">+{day.events.length - MAX_EVENTS_SHOWN} more</div>
|
|
@@ -475,7 +488,8 @@
|
|
|
475
488
|
.wg-ev {
|
|
476
489
|
display: flex;
|
|
477
490
|
align-items: center;
|
|
478
|
-
|
|
491
|
+
flex-wrap: wrap;
|
|
492
|
+
gap: 3px 5px;
|
|
479
493
|
padding: 3px 6px;
|
|
480
494
|
border-radius: 4px;
|
|
481
495
|
background: color-mix(in srgb, var(--ev-color) 12%, transparent);
|
|
@@ -511,6 +525,29 @@
|
|
|
511
525
|
text-overflow: ellipsis;
|
|
512
526
|
}
|
|
513
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
|
+
|
|
514
551
|
.wg-ev-more {
|
|
515
552
|
font: 500 10px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
516
553
|
color: var(--dt-text-3, rgba(0, 0, 0, 0.35));
|
|
@@ -15,6 +15,9 @@ interface Props {
|
|
|
15
15
|
end: Date;
|
|
16
16
|
}) => void;
|
|
17
17
|
selectedEventId?: string | null;
|
|
18
|
+
readOnly?: boolean;
|
|
19
|
+
visibleHours?: [number, number];
|
|
20
|
+
[key: string]: unknown;
|
|
18
21
|
}
|
|
19
22
|
declare const WeekGrid: import("svelte").Component<Props, {}, "">;
|
|
20
23
|
type WeekGrid = ReturnType<typeof WeekGrid>;
|
|
@@ -27,7 +27,13 @@
|
|
|
27
27
|
focusDate,
|
|
28
28
|
oneventclick,
|
|
29
29
|
selectedEventId = null,
|
|
30
|
-
|
|
30
|
+
visibleHours,
|
|
31
|
+
...rest
|
|
32
|
+
}: WeekTimelineProps & { visibleHours?: [number, number]; [key: string]: unknown } = $props();
|
|
33
|
+
|
|
34
|
+
const startHour = $derived(visibleHours?.[0] ?? 0);
|
|
35
|
+
const endHour = $derived(visibleHours?.[1] ?? 24);
|
|
36
|
+
const visibleHourCount = $derived(endHour - startHour);
|
|
31
37
|
|
|
32
38
|
const clock = createClock();
|
|
33
39
|
|
|
@@ -66,7 +72,7 @@
|
|
|
66
72
|
const isToday = ms === clock.today;
|
|
67
73
|
|
|
68
74
|
const cells: HeatCell[] = [];
|
|
69
|
-
for (let h =
|
|
75
|
+
for (let h = startHour; h < endHour; h++) {
|
|
70
76
|
const cellStart = ms + h * 3600000;
|
|
71
77
|
const cellEnd = cellStart + 3600000;
|
|
72
78
|
const overlaps = events.filter((ev) => ev.start.getTime() < cellEnd && ev.end.getTime() > cellStart);
|
|
@@ -179,9 +185,10 @@
|
|
|
179
185
|
<!-- Hour labels row -->
|
|
180
186
|
<div class="hm-hours-row">
|
|
181
187
|
<div class="hm-row-label"></div>
|
|
182
|
-
{#each Array(
|
|
183
|
-
{
|
|
184
|
-
|
|
188
|
+
{#each Array(visibleHourCount) as _, idx}
|
|
189
|
+
{@const h = startHour + idx}
|
|
190
|
+
{#if (h - startHour) % 3 === 0}
|
|
191
|
+
<span class="hm-hour-mark" style:left="calc({idx} * (100% / {visibleHourCount}))">{fmtH(h)}</span>
|
|
185
192
|
{/if}
|
|
186
193
|
{/each}
|
|
187
194
|
</div>
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { WeekTimelineProps } from '../../core/types.js';
|
|
2
|
-
|
|
2
|
+
type $$ComponentProps = WeekTimelineProps & {
|
|
3
|
+
visibleHours?: [number, number];
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
};
|
|
6
|
+
declare const WeekHeatmap: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
3
7
|
type WeekHeatmap = ReturnType<typeof WeekHeatmap>;
|
|
4
8
|
export default WeekHeatmap;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CalendarWidget — self-contained calendar for embedding via <day-calendar> custom element.
|
|
3
|
+
|
|
4
|
+
Accepts simple HTML attributes and wires up the full Calendar with sensible defaults.
|
|
5
|
+
Designed for non-Svelte sites (plain HTML, WordPress, Squarespace, etc.).
|
|
6
|
+
|
|
7
|
+
Usage as custom element:
|
|
8
|
+
<day-calendar
|
|
9
|
+
api="https://myschool.com/api/events"
|
|
10
|
+
theme="neutral"
|
|
11
|
+
view="week-grid"
|
|
12
|
+
height="600"
|
|
13
|
+
locale="en-US"
|
|
14
|
+
></day-calendar>
|
|
15
|
+
-->
|
|
16
|
+
<svelte:options customElement="day-calendar" />
|
|
17
|
+
|
|
18
|
+
<script lang="ts">
|
|
19
|
+
import Calendar from '../calendar/Calendar.svelte';
|
|
20
|
+
import type { CalendarView } from '../calendar/Calendar.svelte';
|
|
21
|
+
import { DayGrid } from '../views/day/index.js';
|
|
22
|
+
import { WeekGrid } from '../views/week/index.js';
|
|
23
|
+
import { Agenda } from '../views/agenda/index.js';
|
|
24
|
+
import { WeekHeatmap } from '../views/week/index.js';
|
|
25
|
+
import { DayTimeline } from '../views/day/index.js';
|
|
26
|
+
import { createRestAdapter } from '../adapters/rest.js';
|
|
27
|
+
import { createMemoryAdapter } from '../adapters/memory.js';
|
|
28
|
+
import { presets } from '../theme/presets.js';
|
|
29
|
+
import type { PresetName } from '../theme/presets.js';
|
|
30
|
+
import type { TimelineEvent } from '../core/types.js';
|
|
31
|
+
|
|
32
|
+
interface Props {
|
|
33
|
+
/** REST API base URL — if provided, fetches events from this endpoint */
|
|
34
|
+
api?: string;
|
|
35
|
+
/** JSON string of events for static/inline data (alternative to api) */
|
|
36
|
+
events?: string;
|
|
37
|
+
/** Theme preset name: midnight, parchment, indigo, neutral, bare */
|
|
38
|
+
theme?: string;
|
|
39
|
+
/** Default view ID */
|
|
40
|
+
view?: string;
|
|
41
|
+
/** Calendar height in pixels */
|
|
42
|
+
height?: string;
|
|
43
|
+
/** BCP 47 locale tag (e.g. 'en-US', 'pl-PL') */
|
|
44
|
+
locale?: string;
|
|
45
|
+
/** Text direction: ltr, rtl, auto */
|
|
46
|
+
dir?: string;
|
|
47
|
+
/** Start week on Monday (default: true) */
|
|
48
|
+
mondaystart?: string;
|
|
49
|
+
/** Custom HTTP headers as JSON string for REST adapter */
|
|
50
|
+
headers?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let {
|
|
54
|
+
api,
|
|
55
|
+
events,
|
|
56
|
+
theme = 'neutral',
|
|
57
|
+
view = 'week-grid',
|
|
58
|
+
height = '600',
|
|
59
|
+
locale,
|
|
60
|
+
dir,
|
|
61
|
+
mondaystart = 'true',
|
|
62
|
+
headers,
|
|
63
|
+
}: Props = $props();
|
|
64
|
+
|
|
65
|
+
// ── Parse attributes ──
|
|
66
|
+
const heightPx = $derived(parseInt(height, 10) || 600);
|
|
67
|
+
const isMondayStart = $derived(mondaystart !== 'false');
|
|
68
|
+
const themeStyle = $derived(
|
|
69
|
+
(presets as Record<string, string>)[theme] ?? presets.neutral
|
|
70
|
+
);
|
|
71
|
+
const dirValue = $derived(
|
|
72
|
+
(dir === 'rtl' || dir === 'ltr' || dir === 'auto') ? dir as 'ltr' | 'rtl' | 'auto' : undefined
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// ── Parse static events from JSON attribute ──
|
|
76
|
+
function parseEvents(json?: string): TimelineEvent[] {
|
|
77
|
+
if (!json) return [];
|
|
78
|
+
try {
|
|
79
|
+
const raw = JSON.parse(json) as Array<Record<string, unknown>>;
|
|
80
|
+
return raw.map((e) => ({
|
|
81
|
+
id: String(e.id ?? crypto.randomUUID()),
|
|
82
|
+
title: String(e.title ?? 'Untitled'),
|
|
83
|
+
start: new Date(e.start as string),
|
|
84
|
+
end: new Date(e.end as string),
|
|
85
|
+
color: e.color ? String(e.color) : undefined,
|
|
86
|
+
}));
|
|
87
|
+
} catch {
|
|
88
|
+
console.warn('[day-calendar] Failed to parse events JSON:', json);
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Create adapter ──
|
|
94
|
+
const adapter = $derived.by(() => {
|
|
95
|
+
if (api) {
|
|
96
|
+
const parsedHeaders = headers ? JSON.parse(headers) as Record<string, string> : undefined;
|
|
97
|
+
return createRestAdapter({
|
|
98
|
+
baseUrl: api,
|
|
99
|
+
headers: parsedHeaders,
|
|
100
|
+
mapEvents: (data: unknown) => {
|
|
101
|
+
const arr = Array.isArray(data) ? data : (data as Record<string, unknown>).events as unknown[] ?? [];
|
|
102
|
+
return arr.map((e: unknown) => {
|
|
103
|
+
const ev = e as Record<string, unknown>;
|
|
104
|
+
return {
|
|
105
|
+
id: String(ev.id ?? ''),
|
|
106
|
+
title: String(ev.title ?? 'Untitled'),
|
|
107
|
+
start: new Date(ev.start as string),
|
|
108
|
+
end: new Date(ev.end as string),
|
|
109
|
+
color: ev.color ? String(ev.color) : undefined,
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return createMemoryAdapter(parseEvents(events));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// ── Default views ──
|
|
119
|
+
const defaultViews: CalendarView[] = [
|
|
120
|
+
{ id: 'day-grid', label: 'Grid', granularity: 'day', component: DayGrid },
|
|
121
|
+
{ id: 'week-grid', label: 'Grid', granularity: 'week', component: WeekGrid },
|
|
122
|
+
{ id: 'day-timeline', label: 'Timeline', granularity: 'day', component: DayTimeline },
|
|
123
|
+
{ id: 'day-agenda', label: 'Agenda', granularity: 'day', component: Agenda, props: { mode: 'day' } },
|
|
124
|
+
{ id: 'week-agenda', label: 'Agenda', granularity: 'week', component: Agenda, props: { mode: 'week' } },
|
|
125
|
+
{ id: 'week-heatmap', label: 'Heatmap', granularity: 'week', component: WeekHeatmap },
|
|
126
|
+
];
|
|
127
|
+
</script>
|
|
128
|
+
|
|
129
|
+
<Calendar
|
|
130
|
+
{adapter}
|
|
131
|
+
views={defaultViews}
|
|
132
|
+
defaultView={view}
|
|
133
|
+
theme={themeStyle}
|
|
134
|
+
height={heightPx}
|
|
135
|
+
mondayStart={isMondayStart}
|
|
136
|
+
dir={dirValue}
|
|
137
|
+
{locale}
|
|
138
|
+
/>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** REST API base URL — if provided, fetches events from this endpoint */
|
|
3
|
+
api?: string;
|
|
4
|
+
/** JSON string of events for static/inline data (alternative to api) */
|
|
5
|
+
events?: string;
|
|
6
|
+
/** Theme preset name: midnight, parchment, indigo, neutral, bare */
|
|
7
|
+
theme?: string;
|
|
8
|
+
/** Default view ID */
|
|
9
|
+
view?: string;
|
|
10
|
+
/** Calendar height in pixels */
|
|
11
|
+
height?: string;
|
|
12
|
+
/** BCP 47 locale tag (e.g. 'en-US', 'pl-PL') */
|
|
13
|
+
locale?: string;
|
|
14
|
+
/** Text direction: ltr, rtl, auto */
|
|
15
|
+
dir?: string;
|
|
16
|
+
/** Start week on Monday (default: true) */
|
|
17
|
+
mondaystart?: string;
|
|
18
|
+
/** Custom HTTP headers as JSON string for REST adapter */
|
|
19
|
+
headers?: string;
|
|
20
|
+
}
|
|
21
|
+
declare const CalendarWidget: import("svelte").Component<Props, {}, "">;
|
|
22
|
+
type CalendarWidget = ReturnType<typeof CalendarWidget>;
|
|
23
|
+
export default CalendarWidget;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CalendarWidget } from './CalendarWidget.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CalendarWidget } from './CalendarWidget.svelte';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget entry point — registers <day-calendar> as a custom element.
|
|
3
|
+
*
|
|
4
|
+
* This file is the entry point for the standalone widget bundle (widget.js).
|
|
5
|
+
* Import it via a <script> tag on any HTML page.
|
|
6
|
+
*/
|
|
7
|
+
import './CalendarWidget.svelte';
|
|
8
|
+
// The Svelte custom element is auto-registered via <svelte:options customElement="day-calendar" />
|
|
9
|
+
// Nothing else needed — the component self-registers when this module is loaded.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nomideusz/svelte-calendar",
|
|
3
|
-
"version": "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",
|
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"svelte": "./dist/index.js",
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
|
-
}
|
|
14
|
+
},
|
|
15
|
+
"./widget": "./widget/widget.js"
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
17
18
|
"dist",
|
|
19
|
+
"widget",
|
|
18
20
|
"!dist/**/*.test.*",
|
|
19
21
|
"!dist/**/*.spec.*"
|
|
20
22
|
],
|
|
@@ -22,6 +24,7 @@
|
|
|
22
24
|
"dev": "vite dev",
|
|
23
25
|
"build": "vite build",
|
|
24
26
|
"package": "svelte-kit sync && svelte-package",
|
|
27
|
+
"build:widget": "vite build --config vite.config.widget.ts",
|
|
25
28
|
"prepublishOnly": "npm run package",
|
|
26
29
|
"preview": "vite preview",
|
|
27
30
|
"prepare": "svelte-kit sync || echo ''",
|