@nomideusz/svelte-calendar 1.7.0 → 1.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 +62 -0
- package/dist/adapters/ics.d.ts +60 -0
- package/dist/adapters/ics.js +682 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.js +1 -0
- package/dist/calendar/Calendar.svelte +4 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/text-fit.js +14 -4
- package/package.json +1 -1
- package/widget/widget.js +1 -1
package/README.md
CHANGED
|
@@ -585,6 +585,7 @@ The calendar reads its events through an adapter. Which one:
|
|
|
585
585
|
| `createMemoryAdapter(events)` | The events are already in the page (a fixed list, your own state). Writes stay in memory. See [Quick Start](#quick-start). |
|
|
586
586
|
| [`createRecurringAdapter(rules)`](#recurring-schedules) | The schedule repeats by rule: class timetables, office hours. Read-only. |
|
|
587
587
|
| [`createRestAdapter(options)`](#rest-adapter) | The events live behind your own HTTP API, which also stores creates and moves. |
|
|
588
|
+
| [`createIcsAdapter(source)`](#icalendar-ics) | The events come from Google Calendar, Apple Calendar, Outlook or any other calendar app: an imported `.ics` file or a subscription URL. Read-only. |
|
|
588
589
|
| [`createJmapAdapter(client, options)`](#jmap-adapter) | The events are in a JMAP Calendars server (Fastmail, Stalwart, Cyrus). Read-only. |
|
|
589
590
|
| [`createMappedAdapter(records, options)`](#mapped-adapter) | You have records from another system with their own field names: map the fields instead of writing an adapter. |
|
|
590
591
|
| [`createCompositeAdapter(adapters)`](#composite-adapter) | One calendar shows several sources, e.g. a recurring timetable plus one-off events. |
|
|
@@ -640,6 +641,67 @@ const adapter = createJmapAdapter(client, {
|
|
|
640
641
|
|
|
641
642
|
Read-only. Expands recurrences server-side and handles all-day events, ISO-duration ends and per-calendar colors (pass `calendars: [{ id, name, color }]`, or a function returning them).
|
|
642
643
|
|
|
644
|
+
### iCalendar (.ics)
|
|
645
|
+
|
|
646
|
+
Every calendar app speaks iCalendar: Google Calendar, Apple Calendar / iCloud, Outlook / Microsoft 365, Thunderbird, Proton, Fastmail, Nextcloud. One adapter reads all of them, from a file or a feed:
|
|
647
|
+
|
|
648
|
+
```svelte
|
|
649
|
+
<script>
|
|
650
|
+
import { Calendar, createIcsAdapter } from '@nomideusz/svelte-calendar';
|
|
651
|
+
|
|
652
|
+
let adapter = $state(createIcsAdapter(''));
|
|
653
|
+
async function importFile(e) {
|
|
654
|
+
const file = e.currentTarget.files?.[0];
|
|
655
|
+
if (file) adapter = createIcsAdapter(await file.text());
|
|
656
|
+
}
|
|
657
|
+
</script>
|
|
658
|
+
|
|
659
|
+
<input type="file" accept=".ics,.ical,.ifb,text/calendar" onchange={importFile} />
|
|
660
|
+
<Calendar {adapter} />
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
A subscription URL loads on the first fetch (`webcal://` reads as `https://`); `refreshInterval` refetches once the copy is older than that many ms:
|
|
664
|
+
|
|
665
|
+
```ts
|
|
666
|
+
// Google: Settings → your calendar → "Secret address in iCal format"
|
|
667
|
+
// Outlook: Settings → Calendar → Shared calendars → Publish → ICS link
|
|
668
|
+
// iCloud: share the calendar as "Public Calendar" → webcal:// link
|
|
669
|
+
const adapter = createIcsAdapter({ url: '/api/calendar-feed', refreshInterval: 15 * 60_000 });
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
Google, Outlook and iCloud send no CORS headers, so a browser can't fetch their feeds directly: serve them through a route of your own (the feed URL is a secret, so keep it on the server anyway):
|
|
673
|
+
|
|
674
|
+
```ts
|
|
675
|
+
// src/routes/api/calendar-feed/+server.ts
|
|
676
|
+
export const GET = async ({ fetch }) => new Response(await (await fetch(FEED_URL)).text(), {
|
|
677
|
+
headers: { 'content-type': 'text/calendar; charset=utf-8' },
|
|
678
|
+
});
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
- Recurrence is expanded per viewed range: `RRULE` (daily, weekly, monthly, yearly; `INTERVAL`, `COUNT`, `UNTIL`, `BYDAY` with ordinals like `-1FR`, `BYMONTHDAY`, `BYMONTH`, `BYSETPOS`, `WKST`), `RDATE`, `EXDATE`, and moved or edited occurrences (`RECURRENCE-ID`).
|
|
682
|
+
- Times keep their zone: UTC, IANA `TZID`s, and the `VTIMEZONE` blocks Outlook writes under Windows zone names. Floating times read in the calendar's `X-WR-TIMEZONE`, else the browser's. All-day dates are local midnights, end exclusive.
|
|
683
|
+
- `SUMMARY` → `title`, `LOCATION` → `location`, `STATUS` → `status`, the first of `CATEGORIES` → `category`, `COLOR` → `color`, `UID` → `externalId`, `DESCRIPTION` / `URL` → `data.description` / `data.url`. Events without a colour take `options.color`, else the calendar's own (`X-APPLE-CALENDAR-COLOR`).
|
|
684
|
+
- Read-only: each occurrence carries `data.readOnly`, and a write throws `CalendarReadOnlyError`, so in a composite a drag reaches `oneventmove`.
|
|
685
|
+
- The source may also be a function returning the text: `createIcsAdapter(() => loadIt())`.
|
|
686
|
+
|
|
687
|
+
To use the events outside an adapter (copy an import into your own store), parse once and expand any range:
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
import { parseIcs } from '@nomideusz/svelte-calendar';
|
|
691
|
+
|
|
692
|
+
const cal = parseIcs(text); // { name, color, events(range) }
|
|
693
|
+
const imported = cal.events({ start, end });
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
**Export.** `toIcs(events, { name })` writes an `.ics` file that Google, Apple and Outlook import, or subscribe to when you serve it from a URL. For a single event, `googleCalendarUrl(event)` and `outlookCalendarUrl(event, { work })` (personal outlook.live.com, or Microsoft 365) open the app's "add event" page:
|
|
697
|
+
|
|
698
|
+
```ts
|
|
699
|
+
import { toIcs, googleCalendarUrl, outlookCalendarUrl } from '@nomideusz/svelte-calendar';
|
|
700
|
+
|
|
701
|
+
const file = new Blob([toIcs(events, { name: 'Studio' })], { type: 'text/calendar' });
|
|
702
|
+
const href = URL.createObjectURL(file); // <a {href} download="studio.ics">
|
|
703
|
+
```
|
|
704
|
+
|
|
643
705
|
### Mapped Adapter
|
|
644
706
|
|
|
645
707
|
Wrap any static array of external records (class schedules, appointments, timetables) without writing a custom adapter. Supply a declarative field mapping (or a `mapEvent` function for full control) and the adapter handles parsing, colors, tags and status:
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { TimelineEvent } from '../core/types.js';
|
|
2
|
+
import type { CalendarAdapter, DateRange } from './types.js';
|
|
3
|
+
export interface IcsCalendar {
|
|
4
|
+
/** The calendar's name (X-WR-CALNAME / NAME), when the file has one */
|
|
5
|
+
name?: string;
|
|
6
|
+
/** The calendar's colour (X-APPLE-CALENDAR-COLOR / COLOR), when the file has one */
|
|
7
|
+
color?: string;
|
|
8
|
+
/** Every event overlapping `range`, recurrences expanded */
|
|
9
|
+
events(range: DateRange): TimelineEvent[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Parse an iCalendar text (.ics, .ical, .ifb; a .vcs reads as far as its
|
|
13
|
+
* events go). Recurring events are expanded per range by `events(range)`.
|
|
14
|
+
* A malformed line or event is skipped, never thrown.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseIcs(input: string): IcsCalendar;
|
|
17
|
+
export type IcsSource = string | {
|
|
18
|
+
/** An https:// or webcal:// feed URL (Google's "secret address in iCal format", Outlook's published ICS, an iCloud public calendar) */
|
|
19
|
+
url: string;
|
|
20
|
+
/** Your own fetch — e.g. one that adds headers or goes through your server */
|
|
21
|
+
fetch?: typeof fetch;
|
|
22
|
+
/** Refetch on the next load once the copy is older than this many ms (default: never) */
|
|
23
|
+
refreshInterval?: number;
|
|
24
|
+
} | (() => Promise<string>);
|
|
25
|
+
export interface IcsAdapterOptions {
|
|
26
|
+
/** Colour for events that name none (default: the calendar's own colour, else ink) */
|
|
27
|
+
color?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A read-only CalendarAdapter over iCalendar data: a file's text, a feed URL,
|
|
31
|
+
* or a function that loads the text. Each occurrence carries `data.readOnly`,
|
|
32
|
+
* and a write throws CalendarReadOnlyError, so a composite hands a drag to
|
|
33
|
+
* `oneventmove`.
|
|
34
|
+
*
|
|
35
|
+
* Feeds from Google, Outlook and iCloud send no CORS headers: in a browser,
|
|
36
|
+
* fetch them through your own server route.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createIcsAdapter(source: IcsSource, options?: IcsAdapterOptions): CalendarAdapter;
|
|
39
|
+
export interface ToIcsOptions {
|
|
40
|
+
/** Calendar name (X-WR-CALNAME), shown by Google, Apple and Outlook */
|
|
41
|
+
name?: string;
|
|
42
|
+
/** PRODID (default: -//nomideusz//svelte-calendar//EN) */
|
|
43
|
+
prodId?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Write events as an iCalendar file that Google Calendar, Apple Calendar and
|
|
47
|
+
* Outlook import (or subscribe to, served from a URL). Times go out in UTC;
|
|
48
|
+
* all-day events as dates. UID is `externalId ?? id`. A string
|
|
49
|
+
* `data.description` / `data.url` becomes DESCRIPTION / URL.
|
|
50
|
+
*/
|
|
51
|
+
export declare function toIcs(events: TimelineEvent[], options?: ToIcsOptions): string;
|
|
52
|
+
/** An "add to Google Calendar" link for one event. */
|
|
53
|
+
export declare function googleCalendarUrl(ev: TimelineEvent): string;
|
|
54
|
+
/**
|
|
55
|
+
* An "add to Outlook" link for one event: outlook.live.com (personal), or
|
|
56
|
+
* `work: true` for outlook.office.com (Microsoft 365).
|
|
57
|
+
*/
|
|
58
|
+
export declare function outlookCalendarUrl(ev: TimelineEvent, options?: {
|
|
59
|
+
work?: boolean;
|
|
60
|
+
}): string;
|