@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
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
import type { PresetName } from '../../theme/presets.js';
|
|
2
|
-
type
|
|
2
|
+
type BaseField = {
|
|
3
3
|
key: string;
|
|
4
4
|
label: string;
|
|
5
|
+
group?: string;
|
|
6
|
+
};
|
|
7
|
+
type RangeField = BaseField & {
|
|
5
8
|
type: 'range';
|
|
6
9
|
min: number;
|
|
7
10
|
max: number;
|
|
8
11
|
step: number;
|
|
12
|
+
enabledWhen?: string;
|
|
9
13
|
};
|
|
10
|
-
type ToggleField = {
|
|
11
|
-
key: string;
|
|
12
|
-
label: string;
|
|
14
|
+
type ToggleField = BaseField & {
|
|
13
15
|
type: 'toggle';
|
|
14
16
|
};
|
|
15
|
-
|
|
17
|
+
type SelectField = BaseField & {
|
|
18
|
+
type: 'select';
|
|
19
|
+
options: {
|
|
20
|
+
value: string;
|
|
21
|
+
label: string;
|
|
22
|
+
}[];
|
|
23
|
+
};
|
|
24
|
+
type SegmentField = BaseField & {
|
|
25
|
+
type: 'segment';
|
|
26
|
+
options: {
|
|
27
|
+
value: string;
|
|
28
|
+
label: string;
|
|
29
|
+
}[];
|
|
30
|
+
};
|
|
31
|
+
export type SettingsField = RangeField | ToggleField | SelectField | SegmentField;
|
|
16
32
|
interface Props {
|
|
17
33
|
fields: SettingsField[];
|
|
18
|
-
values: Record<string, number | boolean>;
|
|
34
|
+
values: Record<string, string | number | boolean>;
|
|
19
35
|
theme: PresetName;
|
|
20
36
|
}
|
|
21
37
|
declare const Settings: import("svelte").Component<Props, {}, "values" | "theme">;
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
interface Props {
|
|
21
21
|
weekOffset?: number;
|
|
22
22
|
mondayStart?: boolean;
|
|
23
|
+
locale?: string;
|
|
23
24
|
hourHeight?: number;
|
|
24
25
|
dayWidth?: number;
|
|
25
26
|
nowPosition?: number;
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
|
|
38
39
|
let {
|
|
39
40
|
mondayStart = true,
|
|
41
|
+
locale,
|
|
40
42
|
height = 520,
|
|
41
43
|
events = [],
|
|
42
44
|
style = '',
|
|
@@ -116,7 +118,7 @@
|
|
|
116
118
|
// Month label: show when first day of week is day 1-7
|
|
117
119
|
const startDate = new Date(weekStart);
|
|
118
120
|
const showMonth = startDate.getDate() <= 7;
|
|
119
|
-
const monthLabel = showMonth ? monthLong(weekStart).toUpperCase() : null;
|
|
121
|
+
const monthLabel = showMonth ? monthLong(weekStart, locale).toUpperCase() : null;
|
|
120
122
|
|
|
121
123
|
result.push({ weekStart, isCurrent, monthLabel, days });
|
|
122
124
|
}
|
|
@@ -126,11 +128,10 @@
|
|
|
126
128
|
|
|
127
129
|
// ─── Format helpers ─────────────────────────────────
|
|
128
130
|
function fmtAmPm(d: Date): string {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return m > 0 ? `${h}:${m.toString().padStart(2, '0')}${ampm}` : `${h}${ampm}`;
|
|
131
|
+
return d
|
|
132
|
+
.toLocaleTimeString(locale ?? 'en-US', { hour: 'numeric', minute: '2-digit', hour12: true })
|
|
133
|
+
.toUpperCase()
|
|
134
|
+
.replace(' ', '');
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
function fmtNowTime(tick: number): string {
|
|
@@ -274,13 +275,13 @@
|
|
|
274
275
|
class:wg-cell--weekend={day.isWeekend}
|
|
275
276
|
role="gridcell"
|
|
276
277
|
tabindex="0"
|
|
277
|
-
aria-label="{new Date(day.ms).toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric' })}{day.isToday ? ' (today)' : ''}, {day.events.length} events"
|
|
278
|
+
aria-label="{new Date(day.ms).toLocaleDateString(locale ?? 'en-US', { weekday: 'long', month: 'short', day: 'numeric' })}{day.isToday ? ' (today)' : ''}, {day.events.length} events"
|
|
278
279
|
onclick={(e) => handleDayCellClick(day.ms, e)}
|
|
279
280
|
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleDayCellClick(day.ms, e); } }}
|
|
280
281
|
>
|
|
281
282
|
<!-- Day label inside the cell -->
|
|
282
283
|
<div class="wg-cell-hd" class:wg-cell-hd--today={day.isToday}>
|
|
283
|
-
<span class="wg-day-wd">{weekdayShort(day.ms)}</span>
|
|
284
|
+
<span class="wg-day-wd">{weekdayShort(day.ms, locale)}</span>
|
|
284
285
|
<span class="wg-day-num" class:wg-day-num--today={day.isToday}>
|
|
285
286
|
{day.dayNum}
|
|
286
287
|
</span>
|
|
@@ -303,8 +304,16 @@
|
|
|
303
304
|
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); oneventclick?.(ev); } }}
|
|
304
305
|
>
|
|
305
306
|
<span class="wg-ev-time">{fmtAmPm(ev.start)}</span>
|
|
306
|
-
<span class="wg-ev-title">{ev.title}</span>
|
|
307
|
-
|
|
307
|
+
<span class="wg-ev-title">{ev.title}</span> {#if ev.subtitle}
|
|
308
|
+
<span class="wg-ev-sub">{ev.subtitle}</span>
|
|
309
|
+
{/if}
|
|
310
|
+
{#if ev.tags?.length}
|
|
311
|
+
<span class="wg-ev-tags">
|
|
312
|
+
{#each ev.tags as tag}
|
|
313
|
+
<span class="wg-ev-tag">{tag}</span>
|
|
314
|
+
{/each}
|
|
315
|
+
</span>
|
|
316
|
+
{/if} </div>
|
|
308
317
|
{/each}
|
|
309
318
|
{#if day.events.length > MAX_EVENTS_SHOWN}
|
|
310
319
|
<div class="wg-ev-more">+{day.events.length - MAX_EVENTS_SHOWN} more</div>
|
|
@@ -480,7 +489,8 @@
|
|
|
480
489
|
.wg-ev {
|
|
481
490
|
display: flex;
|
|
482
491
|
align-items: center;
|
|
483
|
-
|
|
492
|
+
flex-wrap: wrap;
|
|
493
|
+
gap: 3px 5px;
|
|
484
494
|
padding: 3px 6px;
|
|
485
495
|
border-radius: 4px;
|
|
486
496
|
background: color-mix(in srgb, var(--ev-color) 12%, transparent);
|
|
@@ -516,6 +526,29 @@
|
|
|
516
526
|
text-overflow: ellipsis;
|
|
517
527
|
}
|
|
518
528
|
|
|
529
|
+
.wg-ev-sub {
|
|
530
|
+
font: 400 10px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
531
|
+
color: var(--dt-text-3, rgba(0, 0, 0, 0.4));
|
|
532
|
+
white-space: nowrap;
|
|
533
|
+
overflow: hidden;
|
|
534
|
+
text-overflow: ellipsis;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.wg-ev-tags {
|
|
538
|
+
display: flex;
|
|
539
|
+
gap: 3px;
|
|
540
|
+
flex-shrink: 0;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.wg-ev-tag {
|
|
544
|
+
font: 500 8px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
545
|
+
color: var(--ev-color, var(--dt-accent));
|
|
546
|
+
background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
|
|
547
|
+
padding: 1px 4px;
|
|
548
|
+
border-radius: 3px;
|
|
549
|
+
white-space: nowrap;
|
|
550
|
+
}
|
|
551
|
+
|
|
519
552
|
.wg-ev-more {
|
|
520
553
|
font: 500 10px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
521
554
|
color: var(--dt-text-3, rgba(0, 0, 0, 0.35));
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
let {
|
|
22
22
|
weekOffset = 0,
|
|
23
23
|
mondayStart = true,
|
|
24
|
+
locale,
|
|
24
25
|
height = 400,
|
|
25
26
|
events = [],
|
|
26
27
|
style = '',
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
selectedEventId = null,
|
|
30
31
|
visibleHours,
|
|
31
32
|
...rest
|
|
32
|
-
}: WeekTimelineProps & { visibleHours?: [number, number]; [key: string]: unknown } = $props();
|
|
33
|
+
}: WeekTimelineProps & { locale?: string; visibleHours?: [number, number]; [key: string]: unknown } = $props();
|
|
33
34
|
|
|
34
35
|
const startHour = $derived(visibleHours?.[0] ?? 0);
|
|
35
36
|
const endHour = $derived(visibleHours?.[1] ?? 24);
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
: startOfWeek(clock.today, mondayStart) + weekOffset * 7 * DAY_MS,
|
|
44
45
|
);
|
|
45
46
|
const currentWeekVisible = $derived(clock.today >= weekStartMs && clock.today < weekStartMs + 7 * DAY_MS);
|
|
46
|
-
const weekLabel = $derived(fmtWeekRange(weekStartMs));
|
|
47
|
+
const weekLabel = $derived(fmtWeekRange(weekStartMs, locale));
|
|
47
48
|
|
|
48
49
|
let selectedCell = $state<{ day: number; hour: number } | null>(null);
|
|
49
50
|
|
|
@@ -96,7 +97,7 @@
|
|
|
96
97
|
|
|
97
98
|
out.push({
|
|
98
99
|
ms,
|
|
99
|
-
label: weekdayShort(ms),
|
|
100
|
+
label: weekdayShort(ms, locale),
|
|
100
101
|
num: dayNum(ms),
|
|
101
102
|
today: isToday,
|
|
102
103
|
past: ms < clock.today,
|
|
@@ -118,11 +119,11 @@
|
|
|
118
119
|
if (!selectedCell) return '';
|
|
119
120
|
const day = heatDays[selectedCell.day];
|
|
120
121
|
if (!day) return '';
|
|
121
|
-
return `${day.label} ${day.num}, ${fmtH(selectedCell.hour)}`;
|
|
122
|
+
return `${day.label} ${day.num}, ${fmtH(selectedCell.hour, locale)}`;
|
|
122
123
|
});
|
|
123
124
|
|
|
124
125
|
function fmtTime(d: Date): string {
|
|
125
|
-
return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }).toLowerCase();
|
|
126
|
+
return d.toLocaleTimeString(locale ?? 'en-US', { hour: 'numeric', minute: '2-digit', hour12: true }).toLowerCase();
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
function duration(ev: TimelineEvent): string {
|
|
@@ -188,7 +189,7 @@
|
|
|
188
189
|
{#each Array(visibleHourCount) as _, idx}
|
|
189
190
|
{@const h = startHour + idx}
|
|
190
191
|
{#if (h - startHour) % 3 === 0}
|
|
191
|
-
<span class="hm-hour-mark" style:left="calc({idx} * (100% / {visibleHourCount}))">{fmtH(h)}</span>
|
|
192
|
+
<span class="hm-hour-mark" style:left="calc({idx} * (100% / {visibleHourCount}))">{fmtH(h, locale)}</span>
|
|
192
193
|
{/if}
|
|
193
194
|
{/each}
|
|
194
195
|
</div>
|
|
@@ -209,7 +210,7 @@
|
|
|
209
210
|
class:hm-cell--selected={selectedCell?.day === di && selectedCell?.hour === hi}
|
|
210
211
|
style:background={heatColor(cell.intensity)}
|
|
211
212
|
onclick={() => selectCell(di, hi)}
|
|
212
|
-
title="{fmtH(cell.hour)}: {cell.events.length > 0 ? cell.events.map(e => e.title).join(', ') : 'free'}"
|
|
213
|
+
title="{fmtH(cell.hour, locale)}: {cell.events.length > 0 ? cell.events.map(e => e.title).join(', ') : 'free'}"
|
|
213
214
|
>
|
|
214
215
|
{#if cell.isNow}
|
|
215
216
|
<span class="hm-cell-now"></span>
|
package/dist/widget/widget.d.ts
CHANGED
package/dist/widget/widget.js
CHANGED
|
@@ -4,6 +4,47 @@
|
|
|
4
4
|
* This file is the entry point for the standalone widget bundle (widget.js).
|
|
5
5
|
* Import it via a <script> tag on any HTML page.
|
|
6
6
|
*/
|
|
7
|
-
import '
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { asClassComponent } from 'svelte/legacy';
|
|
8
|
+
import CalendarWidget from './CalendarWidget.svelte';
|
|
9
|
+
const CalendarWidgetClass = asClassComponent(CalendarWidget);
|
|
10
|
+
class DayCalendarElement extends HTMLElement {
|
|
11
|
+
instance = null;
|
|
12
|
+
static get observedAttributes() {
|
|
13
|
+
return ['api', 'events', 'theme', 'view', 'height', 'locale', 'dir', 'mondaystart', 'headers'];
|
|
14
|
+
}
|
|
15
|
+
connectedCallback() {
|
|
16
|
+
if (this.instance)
|
|
17
|
+
return;
|
|
18
|
+
this.instance = new CalendarWidgetClass({
|
|
19
|
+
target: this,
|
|
20
|
+
props: this.readProps(),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
disconnectedCallback() {
|
|
24
|
+
this.instance?.$destroy();
|
|
25
|
+
this.instance = null;
|
|
26
|
+
}
|
|
27
|
+
attributeChangedCallback(name, _oldValue, newValue) {
|
|
28
|
+
if (!this.instance)
|
|
29
|
+
return;
|
|
30
|
+
this.instance.$set({
|
|
31
|
+
[name]: newValue ?? undefined,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
readProps() {
|
|
35
|
+
return {
|
|
36
|
+
api: this.getAttribute('api') ?? undefined,
|
|
37
|
+
events: this.getAttribute('events') ?? undefined,
|
|
38
|
+
theme: this.getAttribute('theme') ?? undefined,
|
|
39
|
+
view: this.getAttribute('view') ?? undefined,
|
|
40
|
+
height: this.getAttribute('height') ?? undefined,
|
|
41
|
+
locale: this.getAttribute('locale') ?? undefined,
|
|
42
|
+
dir: this.getAttribute('dir') ?? undefined,
|
|
43
|
+
mondaystart: this.getAttribute('mondaystart') ?? undefined,
|
|
44
|
+
headers: this.getAttribute('headers') ?? undefined,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!customElements.get('day-calendar')) {
|
|
49
|
+
customElements.define('day-calendar', DayCalendarElement);
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,68 +1,66 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
}
|
|
2
|
+
"name": "@nomideusz/svelte-calendar",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "A themeable, pluggable Svelte 5 calendar with Day and Week views — Grid, Timeline, Agenda, Heatmap.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"svelte": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"svelte": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./widget": "./widget/widget.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"widget",
|
|
20
|
+
"!dist/**/*.test.*",
|
|
21
|
+
"!dist/**/*.spec.*"
|
|
22
|
+
],
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"svelte": "^5.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@sveltejs/adapter-auto": "^7.0.0",
|
|
28
|
+
"@sveltejs/kit": "^2.50.2",
|
|
29
|
+
"@sveltejs/package": "^2.5.7",
|
|
30
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
31
|
+
"svelte": "^5.51.0",
|
|
32
|
+
"svelte-check": "^4.3.6",
|
|
33
|
+
"typescript": "^5.9.3",
|
|
34
|
+
"vite": "^7.3.1",
|
|
35
|
+
"vitest": "^4.0.18"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"date-fns": "^4.1.0",
|
|
39
|
+
"date-fns-tz": "^3.2.0"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"svelte",
|
|
43
|
+
"calendar",
|
|
44
|
+
"scheduler",
|
|
45
|
+
"timeline",
|
|
46
|
+
"week",
|
|
47
|
+
"day",
|
|
48
|
+
"booking",
|
|
49
|
+
"svelte5"
|
|
50
|
+
],
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "https://github.com/nomideusz/svelte-calendar"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"dev": "vite dev",
|
|
57
|
+
"build": "vite build",
|
|
58
|
+
"package": "svelte-kit sync && svelte-package",
|
|
59
|
+
"build:widget": "vite build --config vite.config.widget.ts",
|
|
60
|
+
"preview": "vite preview",
|
|
61
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
62
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"test:watch": "vitest"
|
|
65
|
+
}
|
|
66
|
+
}
|