@nomideusz/svelte-calendar 1.3.0 → 1.4.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm](https://badgen.net/npm/v/@nomideusz/svelte-calendar)](https://www.npmjs.com/package/@nomideusz/svelte-calendar) [![license](https://badgen.net/badge/license/MIT/blue)](https://github.com/nomideusz/svelte-calendar/blob/main/LICENSE)
4
4
 
5
- A themeable **Svelte 5** calendar: day and week **Planner** time grids, a multi-week **Scroll** view, **Agenda** lists, a **Month** grid and touch-first **Mobile** layouts — with drag-and-drop, timezones, a headless API, an embeddable web component, and a smart auto-theme that adapts to any page.
5
+ A themeable **Svelte 5** calendar: day and week **Planner** time grids, a multi-week **Scroll** view, **Agenda** lists, a **Month** grid and touch-first **Mobile** layouts, with drag to move, resize and create events, timezones, a headless API, an embeddable web component, and a smart auto-theme that adapts to any page.
6
6
 
7
7
  **[Live demo → svelte-calendar.xyz](https://svelte-calendar.xyz/)**
8
8
 
@@ -14,7 +14,7 @@ pnpm add @nomideusz/svelte-calendar
14
14
 
15
15
  Requirements:
16
16
 
17
- - **Svelte `^5.29.0`** (peer dependency — the package uses attachments, `{@attach}`).
17
+ - **Svelte `^5.29.0`** (peer dependency: the package uses attachments, `{@attach}`).
18
18
  - Runtime dependencies (installed for you): `date-fns`, `date-fns-tz`, `@chenglou/pretext`.
19
19
  - Node ≥ 22 is only needed to develop the package itself, not to use it.
20
20
 
@@ -28,30 +28,43 @@ Requirements:
28
28
  const today = new Date();
29
29
  const at = (hour) => new Date(today.getFullYear(), today.getMonth(), today.getDate(), hour);
30
30
 
31
- const adapter = createMemoryAdapter([
31
+ let events = $state([
32
32
  { id: '1', title: 'Team Sync', start: at(9), end: at(10) },
33
33
  { id: '2', title: 'Lunch', start: at(12), end: at(13) },
34
34
  ]);
35
+ // A new adapter whenever the events change; the calendar reloads without blanking
36
+ const adapter = $derived(createMemoryAdapter(events));
37
+
38
+ // Drag on empty time (or press Enter on a day column) to create
39
+ function create({ start, end }) {
40
+ events = [...events, { id: crypto.randomUUID(), title: 'New event', start, end }];
41
+ }
42
+ // Drag an event to move it, or its edge to resize it
43
+ function move(event, start, end) {
44
+ events = events.map((e) => (e.id === event.id ? { ...e, start, end } : e));
45
+ }
35
46
  </script>
36
47
 
37
- <Calendar {adapter} view="week-planner" />
48
+ <Calendar {adapter} view="week-planner" oneventcreate={create} oneventmove={move} />
38
49
  ```
39
50
 
40
- That's it — 8 built-in views, auto-coloring, drag-and-drop with live previews, all out of the box. The default `auto` theme probes your page's background, accent color and fonts, so the calendar adapts to any design.
51
+ That's a working, editable calendar. The calendar stores nothing on create: without `oneventcreate`, dragging on empty time does nothing. Moves and resizes write to the adapter by themselves, and `oneventmove` keeps your own data in step, so a move survives the next new adapter. The default `auto` theme probes your page's background, accent color and fonts, so the calendar adapts to any design. Events without a `color` draw in ink; to color them automatically, give the adapter a [`palette`](#event-colors).
41
52
 
42
53
  ## Views
43
54
 
44
- The default registry (`defaultViews`) holds eight views:
55
+ Without `view` the calendar opens on `day-planner`, the first registered view. The Quick Start, the demo, the widget and `createCalendar` open on `week-planner`, so pass `view="week-planner"` for the week grid.
56
+
57
+ The default registry (`defaultViews`) holds eight views. Six are the ones you choose with `view`; the two `*-mobile` views are what the others swap to in a narrow container (see [Mobile](#mobile)), so the demo's view list shows six.
45
58
 
46
59
  | View ID | Mode | What it is |
47
60
  |---------|------|------------|
48
- | `day-planner` | day | Vertical time grid, one column. **The default view** (first registered). |
61
+ | `day-planner` | day | Vertical time grid, one column. The default when `view` is unset. |
49
62
  | `week-planner` | week | The same time grid, one column per day (`days`, default 7). |
50
63
  | `week-scroll` | week | Weeks stacked in one infinite vertical scroller (Hey-Calendar style): each chip sits at the height of its start time, month names run down the side. Drag to move, drop onto a day; it scrolls under a drag. |
51
64
  | `day-agenda` | day | Live list for one day: now / up next / done. |
52
65
  | `week-agenda` | week | List of the week's days (`columns` turns it into a timetable grid). |
53
- | `day-mobile` | day | Touch-first time grid with swipe navigation. |
54
- | `week-mobile` | week | Touch-first list of the week's days. |
66
+ | `day-mobile` | day | Touch-first time grid with swipe navigation. Used automatically in a narrow container. |
67
+ | `week-mobile` | week | Touch-first list of the week's days. Used automatically in a narrow container. |
55
68
  | `month-grid` | month | Week-aligned month grid, chips per day with a "+N more" overflow. |
56
69
 
57
70
  ```svelte
@@ -68,10 +81,10 @@ Users switch with the built-in pills: **Day / Week / Month** for the mode, plus
68
81
 
69
82
  Planner views are designed for direct manipulation:
70
83
 
71
- - **Move** — drag an event to another day or time; a ghost previews the target before the move commits.
72
- - **Resize** — drag an event's top/bottom grip to change its duration; `minDuration`/`maxDuration` clamp the result.
73
- - **Create** — press on empty canvas and sweep to draw a range, or click for a default-length slot. Both go through the same validation (blocked slots, disabled dates, `minDuration`/`maxDuration`) and then fire `oneventcreate`.
74
- - **External drop** — an HTML5 drag from outside the calendar (a class chip, a template) dropped on the planner or `week-scroll` fires `onexternaldrop({ start, dataTransfer })`.
84
+ - **Move**: drag an event to another day or time; a ghost previews the target before the move commits.
85
+ - **Resize**: drag an event's top/bottom grip to change its duration; `minDuration`/`maxDuration` clamp the result.
86
+ - **Create**: press on empty canvas and sweep to draw a range, or click for a default-length slot. Both go through the same validation (blocked slots, disabled dates, `minDuration`/`maxDuration`) and then fire `oneventcreate`.
87
+ - **External drop**: an HTML5 drag from outside the calendar (a class chip, a template) dropped on the planner or `week-scroll` fires `onexternaldrop({ start, dataTransfer })`.
75
88
 
76
89
  `week-scroll` supports move, click-to-create and external drops. In `week-agenda` with `columns` and an `oneventmove` handler, a card can be dragged to another day (it keeps its time of day); that move goes straight to `oneventmove` without writing the adapter.
77
90
 
@@ -81,8 +94,8 @@ The month grid shows up to three chips per day. Clicking a day or its "+N more"
81
94
 
82
95
  When focus is inside the calendar:
83
96
 
84
- - `t` — go to today.
85
- - `←` / `→` — previous / next period (mirrored in RTL). Ignored while typing in a field or inside a pill group, where arrows move between pills.
97
+ - `t`: go to today.
98
+ - `←` / `→`: previous / next period (mirrored in RTL). Ignored while typing in a field or inside a pill group, where arrows move between pills.
86
99
  - In `month-grid`: arrow keys move between days, `Enter` / `Space` opens the focused day (`ondayclick`).
87
100
  - In `week-planner` / `day-planner`: the day columns are one tab stop; `←` / `→` move between days, `Enter` / `Space` creates an event (`oneventcreate`) at the next slot today, or 09:00. On a focused event, `Alt+↑` / `Alt+↓` move it one `snapInterval`, `Alt+←` / `Alt+→` move it a day, and `Alt+Shift+↑` / `Alt+Shift+↓` move its end; each goes through the same validation as a drag and fires `oneventmove`.
88
101
 
@@ -106,13 +119,13 @@ The `views` prop replaces the registry. Spread `defaultViews` to add your own vi
106
119
 
107
120
  A `CalendarView` is `{ id, label, mode: 'day' | 'week' | 'month', component, props? }`. `label` names the view-type pill; views sharing a label across modes are what the Day/Week/Month pills switch between.
108
121
 
109
- The component receives `CalendarViewProps` (plus your `props`) — declare only the ones it uses:
122
+ The component receives `CalendarViewProps` (plus your `props`); declare only the ones it uses:
110
123
 
111
124
  | Prop | Type | Notes |
112
125
  |------|------|-------|
113
126
  | `events` | `TimelineEvent[]` | Everything loaded for the current range |
114
127
  | `style` | `string` | The active `--dt-*` theme string |
115
- | `height` | `number \| null` | Currently always `null` — size to the container |
128
+ | `height` | `number \| null` | Always `null`: the view fills its container, sized by the Calendar's own [`height`](#display) (default `600`) |
116
129
  | `mode` | `'day' \| 'week' \| 'month'` | |
117
130
  | `mondayStart` | `boolean` | |
118
131
  | `locale` | `string \| undefined` | |
@@ -143,14 +156,14 @@ The component receives `CalendarViewProps` (plus your `props`) — declare only
143
156
 
144
157
  `useCalendarContext()` (type `CalendarContext`) gives a view the running Calendar's engines and config: `viewState`, `drag`, `commitDrag`, `labels`, `readOnly`, `snapInterval`, `minColumnWidth`, `blockedSlots`, `disabledDates` / `disabledSet`, `minDuration` / `maxDuration`, `hideDays`, `equalDays`, `showDates`, `compact`, `columns`, `isMobile`, `autoHeight`, `timezone`, `oneventmove`, `oneventhover`, `ondayclick`, `eventSnippet`, `emptySnippet`, `dayHeaderSnippet` and `loadRange` (a view can widen the range the store loads). Call it at component init, inside a `<Calendar>`.
145
158
 
146
- The raw view components — `Planner`, `PlannerScroll`, `Agenda`, `Mobile`, `MonthGrid` — are exported so you can register them under your own ids. Building blocks for custom views are exported as primitives: `EventBlock` (an event card), `TimeGutter` (hour labels), `DayHeader`, `NowIndicator`, `EmptySlot` (click-to-create target) and `FloatingPanel` (see [Event details panel](#event-details-panel)).
159
+ The raw view components (`Planner`, `PlannerScroll`, `Agenda`, `Mobile`, `MonthGrid`) are exported so you can register them under your own ids. Building blocks for custom views are exported as primitives: `EventBlock` (an event card), `TimeGutter` (hour labels), `DayHeader`, `NowIndicator`, `EmptySlot` (click-to-create target) and `FloatingPanel` (see [Event details panel](#event-details-panel)).
147
160
 
148
161
  ## Mobile
149
162
 
150
163
  When the calendar's **container** is narrower than 768px, it swaps to touch-first Mobile views with swipe navigation and a compact header:
151
164
 
152
165
  ```svelte
153
- <!-- Auto-detect (default) — switches at a 768px container width -->
166
+ <!-- Auto-detect (default): switches at a 768px container width -->
154
167
  <Calendar {adapter} view="week-planner" />
155
168
 
156
169
  <!-- Force mobile layout -->
@@ -160,10 +173,10 @@ When the calendar's **container** is narrower than 768px, it swaps to touch-firs
160
173
  <Calendar {adapter} mobile={false} />
161
174
  ```
162
175
 
163
- - **MobileDay** — vertical time grid with hour labels, swipe left/right to change days, all-day chips at the top, tap-to-create, stable columns for overlapping events.
164
- - **MobileWeek** — vertical day list with relative labels (Today, Tomorrow, …) and accessible event buttons in each row.
176
+ - **MobileDay**: vertical time grid with hour labels, swipe left/right to change days, all-day chips at the top, tap-to-create, stable columns for overlapping events.
177
+ - **MobileWeek**: vertical day list with relative labels (Today, Tomorrow, …) and accessible event buttons in each row.
165
178
 
166
- In mobile mode every view except `*-agenda` and `*-mobile` is swapped for the `{mode}-mobile` entry of the same mode when one is registered — this includes custom views. Agenda views keep their list layout; `month-grid` has no mobile entry and stays. The mobile header shows only the Day/Week/Month pills.
179
+ In mobile mode every view except `*-agenda` and `*-mobile` is swapped for the `{mode}-mobile` entry of the same mode when one is registered, custom views included. Agenda views keep their list layout; `month-grid` has no mobile entry and stays. The mobile header shows only the Day/Week/Month pills.
167
180
 
168
181
  With `'auto'`, the first render (and the server render) is the desktop layout; the container is measured on mount, so SSR hydrates cleanly.
169
182
 
@@ -185,9 +198,9 @@ With `'auto'`, the first render (and the server render) is the desktop layout; t
185
198
 
186
199
  | Callback | When it fires |
187
200
  |----------|---------------|
188
- | `oneventclick(event, anchor?)` | An event is clicked. The event is also selected (highlighted across views). `anchor` is the clicked block's viewport rect where the view has one (planner, `week-scroll`, `month-grid`) — for positioning a `FloatingPanel`. |
201
+ | `oneventclick(event, anchor?)` | An event is clicked. The event is also selected (highlighted across views). `anchor` is the clicked block's viewport rect where the view has one (planner, `week-scroll`, `month-grid`), for positioning a `FloatingPanel`. |
189
202
  | `oneventcreate({ start, end })` | A drag-create or click-to-create passed validation. The calendar does **not** store anything: add the event to your data and hand the Calendar the updated adapter. |
190
- | `oneventmove(event, start, end)` | A drag or resize committed — **after** the adapter stored it, or when the adapter refused with a read-only error (the host owns persistence then; the block stays where it was dropped). Other adapter failures revert the block and go to `onerror`. (The `week-agenda` columns day-drag is the exception: it only calls `oneventmove`.) |
203
+ | `oneventmove(event, start, end)` | A drag or resize committed: **after** the adapter stored it, or when the adapter refused with a read-only error (the host owns persistence then; the block stays where it was dropped). Other adapter failures revert the block and go to `onerror`. (The `week-agenda` columns day-drag is the exception: it only calls `oneventmove`.) |
191
204
  | `onexternaldrop({ start, dataTransfer })` | An outside HTML5 drag was dropped on the grid; `start` is the pointer's time, snapped. |
192
205
  | `onviewchange(viewId)` | Once on mount, then on every view change. |
193
206
  | `ondatechange(date)` | Once on mount, then whenever the focused date changes (navigation, scrolling, a tapped day). |
@@ -197,7 +210,7 @@ With `'auto'`, the first render (and the server render) is the desktop layout; t
197
210
 
198
211
  `oneventcreate`, `oneventmove` and `onexternaldrop` receive real instants, also with [`timezone`](#timezone-support). None of the write callbacks fire when `readOnly` is set.
199
212
 
200
- To show a newly created or moved event, update your data and give the Calendar a new adapter — the store reloads on a new adapter identity without blanking the grid:
213
+ To show a newly created or moved event, update your data and give the Calendar a new adapter. The store reloads on a new adapter identity without blanking the grid:
201
214
 
202
215
  ```svelte
203
216
  <script lang="ts">
@@ -246,7 +259,7 @@ To lock single events, set `data.readOnly: true` on them: views refuse to move o
246
259
  | Prop | Type | Default | Description |
247
260
  |------|------|---------|-------------|
248
261
  | `title` | `string` | `''` | Header text and dialog label |
249
- | `anchor` | `FloatingPanelAnchor` | `{ x: 24, y: 24 }` | `{ x, y, width?, height? }` — a `DOMRect` works as is |
262
+ | `anchor` | `FloatingPanelAnchor` | `{ x: 24, y: 24 }` | `{ x, y, width?, height? }`; a `DOMRect` works as is |
250
263
  | `width` | `number` | `440` | Panel width in px |
251
264
  | `theme` | `string` | `''` | A `--dt-*` theme string, as the Calendar takes |
252
265
  | `closeLabel` | `string` | `'Close'` | Close button label |
@@ -320,7 +333,7 @@ Disable specific dates:
320
333
  />
321
334
  ```
322
335
 
323
- Disabled dates refuse creating or moving events into those days. Existing events stay visible and clickable — useful for holidays or fully booked days.
336
+ Disabled dates refuse creating or moving events into those days, which suits holidays or fully booked days. In the planner, agenda and mobile views their existing events stay visible and clickable; `month-grid` dims a disabled day, and its chips ignore clicks.
324
337
 
325
338
  Custom day headers and hover previews:
326
339
 
@@ -358,7 +371,7 @@ Replace the built-in navigation or the entire header chrome with your own contro
358
371
  </Calendar>
359
372
  ```
360
373
 
361
- Let agenda content determine height instead of forcing a fixed box — useful inside a scrolling page:
374
+ Let agenda content determine height instead of forcing a fixed box, for a calendar inside a scrolling page:
362
375
 
363
376
  ```svelte
364
377
  <Calendar {adapter} view="week-agenda" height="auto" compact />
@@ -370,7 +383,7 @@ Three built-in presets:
370
383
 
371
384
  | Preset | Description |
372
385
  |--------|-------------|
373
- | `auto` | **Default.** Probes the host page at mount — background, accent color, fonts, light/dark mode — and writes matching `--dt-*` tokens. Re-probes when the host theme changes. |
386
+ | `auto` | **Default.** Probes the host page at mount (background, accent color, fonts, light/dark mode) and writes matching `--dt-*` tokens. Re-probes when the host theme changes. |
374
387
  | `neutral` | Explicit light theme. White bg, blue accent. Use when embedding standalone. |
375
388
  | `midnight` | Explicit dark theme. Charcoal bg, red accent. |
376
389
 
@@ -390,11 +403,11 @@ Three built-in presets:
390
403
 
391
404
  The default `auto` preset probes the page around the calendar and generates a theme that blends in. It detects:
392
405
 
393
- - **Background color** — from common CSS variables on `:root` (`--bg`, `--background`, `--color-bg`, `--bs-body-bg`, …), inline styles, or computed styles
394
- - **Light/dark mode** — from background luminance
395
- - **Accent/brand color** — from common CSS variables (`--accent`, `--primary`, `--brand`, `--bs-primary`, …), link colors, or button colors
396
- - **Text color** — validated for contrast against the background
397
- - **Fonts** — the host's computed font stack; the mono stack from `--font-mono` and similar variables
406
+ - **Background color**: from common CSS variables on `:root` (`--bg`, `--background`, `--color-bg`, `--bs-body-bg`, …), inline styles, or computed styles
407
+ - **Light/dark mode**: from background luminance
408
+ - **Accent/brand color**: from common CSS variables (`--accent`, `--primary`, `--brand`, `--bs-primary`, …), link colors, or button colors
409
+ - **Text color**: validated for contrast against the background
410
+ - **Fonts**: the host's computed font stack; the mono stack from `--font-mono` and similar variables
398
411
 
399
412
  It watches for changes (a dark-mode toggle on `<html>`/`<body>`, the system color scheme) and updates automatically.
400
413
 
@@ -420,7 +433,7 @@ Fine-tune auto-detection with the `autoTheme` prop (`AutoThemeOptions`):
420
433
 
421
434
  The calendar writes its theme inline on its own root element, so it **wins over** `--dt-*` values set on an ancestor or `:root`. With the default `auto` theme it writes a full probed set. To use your own values:
422
435
 
423
- - **Change a few tokens** — append them to a preset (later declarations win):
436
+ - **Change a few tokens**: append them to a preset (later declarations win):
424
437
 
425
438
  ```ts
426
439
  import { neutral } from '@nomideusz/svelte-calendar';
@@ -432,8 +445,8 @@ The calendar writes its theme inline on its own root element, so it **wins over*
432
445
  <Calendar {adapter} theme={custom} />
433
446
  ```
434
447
 
435
- - **Write your own theme string** — any `--dt-*` declarations; tokens you leave out are inherited from ancestors or fall back to neutral defaults.
436
- - **Inherit from the page** — pass `autoTheme={false}` (keeping the default `auto` theme). The calendar then writes no tokens and reads whatever `--dt-*` its ancestors define:
448
+ - **Write your own theme string**: any `--dt-*` declarations; tokens you leave out are inherited from ancestors or fall back to neutral defaults.
449
+ - **Inherit from the page**: pass `autoTheme={false}` (keeping the default `auto` theme). The calendar then writes no tokens and reads whatever `--dt-*` its ancestors define:
437
450
 
438
451
  ```svelte
439
452
  <div style="--dt-accent: #e11d48; --dt-bg: #1a1a2e; --dt-text: rgba(255,255,255,0.87);">
@@ -457,6 +470,7 @@ Component fallbacks use system fonts and a neutral blue accent, so the package s
457
470
  | `--dt-text-3` | Tertiary text |
458
471
  | `--dt-accent` | Accent color |
459
472
  | `--dt-accent-dim` | Accent at ~12% opacity (selection, highlights) |
473
+ | `--dt-event` | Bar color of an event without its own `color` (the presets set it to the text color, so such events draw in ink) |
460
474
  | `--dt-accent-fg` | Text on the planner's today-number badge (default `#fff`; not set by the presets) |
461
475
  | `--dt-btn-text` | Text on accent-filled buttons and badges |
462
476
  | `--dt-glow` | Accent glow / focus ring |
@@ -481,8 +495,8 @@ Component fallbacks use system fonts and a neutral blue accent, so the package s
481
495
  | `id` | `string` | Unique identifier |
482
496
  | `title` | `string` | Event title |
483
497
  | `start` / `end` | `Date` | Time range |
484
- | `color` | `string?` | Accent color (auto-assigned if omitted) |
485
- | `category` | `string?` | Grouping key — events with the same category share a color |
498
+ | `color` | `string?` | The event's bar color. Without it the event draws in `--dt-event` (ink), or takes a color from the adapter's `palette` |
499
+ | `category` | `string?` | Grouping key: with a `palette`, events of the same category share a color |
486
500
  | `subtitle` | `string?` | Secondary text below the title |
487
501
  | `tags` | `string[]?` | Small accent-colored pills |
488
502
  | `allDay` | `boolean?` | Render as an all-day event |
@@ -494,19 +508,25 @@ Component fallbacks use system fonts and a neutral blue accent, so the package s
494
508
 
495
509
  Cancelled events render with a strikethrough but stay visible, so the slot isn't mistaken for free time.
496
510
 
497
- ### Auto-Coloring
511
+ ### Event colors
512
+
513
+ An event with a `color` draws its bar in that color. An event without one draws in ink: `--dt-event`, which the presets set to the text color. Nothing is colored for you unless you ask.
498
514
 
499
- Omit `color` and the built-in adapters assign a vivid palette color, grouped by `category` (or `title`):
515
+ To color events automatically, pass a `palette` to the memory, recurring or mapped adapter. Events without a `color` then take a palette color, grouped by `category` (or `title`):
500
516
 
501
517
  ```ts
518
+ import { createMemoryAdapter, VIVID_PALETTE } from '@nomideusz/svelte-calendar';
519
+
502
520
  const events = [
503
521
  { id: '1', title: 'Yoga', category: 'wellness', start, end },
504
522
  { id: '2', title: 'Pilates', category: 'wellness', start, end }, // same color
505
523
  { id: '3', title: 'Standup', start, end }, // different color
506
524
  ];
525
+
526
+ const adapter = createMemoryAdapter(events, { palette: VIVID_PALETTE });
507
527
  ```
508
528
 
509
- Generate a theme-harmonious palette from any accent color:
529
+ Or generate a theme-harmonious palette from any accent color:
510
530
 
511
531
  ```ts
512
532
  import { createMemoryAdapter, generatePalette } from '@nomideusz/svelte-calendar';
@@ -515,7 +535,7 @@ const palette = generatePalette('#e11d48'); // (accent?, count = 15)
515
535
  const adapter = createMemoryAdapter(events, { palette });
516
536
  ```
517
537
 
518
- Related exports: `VIVID_PALETTE` (the default palette) and `extractAccent(themeString)` (the accent color of a `--dt-*` theme string).
538
+ Related exports: `VIVID_PALETTE` (the vivid palette, the default before 1.4) and `extractAccent(themeString)` (the accent color of a `--dt-*` theme string).
519
539
 
520
540
  ### Multi-day & All-day
521
541
 
@@ -530,7 +550,7 @@ const events = [
530
550
 
531
551
  ### Custom Event Rendering
532
552
 
533
- The `event` snippet replaces the event *content* in every view (the interactive shell — click, keyboard, drag, selection — stays intact). The `empty` snippet replaces the empty state of the day agenda, the day on mobile, and an empty planner week:
553
+ The `event` snippet replaces the event *content* in every view (the interactive shell for click, keyboard, drag and selection stays intact). The `empty` snippet replaces the empty state of the day agenda, the day on mobile, and an empty planner week:
534
554
 
535
555
  ```svelte
536
556
  <Calendar {adapter}>
@@ -541,11 +561,25 @@ The `event` snippet replaces the event *content* in every view (the interactive
541
561
  </div>
542
562
  {/snippet}
543
563
  {#snippet empty()}
544
- <p>Nothing booked — drag on the grid to add a class.</p>
564
+ <p>Nothing booked. Drag on the grid to add a class.</p>
545
565
  {/snippet}
546
566
  </Calendar>
547
567
  ```
548
568
 
569
+ ## Adapters
570
+
571
+ The calendar reads its events through an adapter. Which one:
572
+
573
+ | Adapter | Use it when |
574
+ |---------|-------------|
575
+ | `createMemoryAdapter(events)` | The events are already in the page (a fixed list, your own state). Writes stay in memory. See [Quick Start](#quick-start). |
576
+ | [`createRecurringAdapter(rules)`](#recurring-schedules) | The schedule repeats by rule: class timetables, office hours. Read-only. |
577
+ | [`createRestAdapter(options)`](#rest-adapter) | The events live behind your own HTTP API, which also stores creates and moves. |
578
+ | [`createJmapAdapter(client, options)`](#jmap-adapter) | The events are in a JMAP Calendars server (Fastmail, Stalwart, Cyrus). Read-only. |
579
+ | [`createMappedAdapter(records, options)`](#mapped-adapter) | You have records from another system with their own field names: map the fields instead of writing an adapter. |
580
+ | [`createCompositeAdapter(adapters)`](#composite-adapter) | One calendar shows several sources, e.g. a recurring timetable plus one-off events. |
581
+ | [Custom `CalendarAdapter`](#custom-adapter) | None of the above fits: implement `fetchEvents`, plus the writes you need. |
582
+
549
583
  ## Recurring Schedules
550
584
 
551
585
  For fixed repeating events (class timetables, office hours):
@@ -584,13 +618,13 @@ For fixed repeating events (class timetables, office hours):
584
618
  | `until` | `string` | — | Last possible occurrence `"YYYY-MM-DD"` |
585
619
  | `count` | `number` | — | Max occurrences from `startDate` (requires it). With `until`, the stricter wins. |
586
620
  | `excludeDates` | `string[]` | — | `"YYYY-MM-DD"` dates this rule skips |
587
- | `color` | `string` | auto | Accent color |
621
+ | `color` | `string` | — | Bar color; else from `palette`, else ink (`--dt-event`) |
588
622
  | `subtitle` / `tags` / `category` / `location` / `resourceId` | | — | Copied to every occurrence |
589
623
  | `data` | `Record<string, unknown>` | — | Copied to every occurrence, plus `recurringId` |
590
624
 
591
625
  </details>
592
626
 
593
- Options (`RecurringAdapterOptions`): `mondayStart` (default `true`), `palette`, `movable` (default `false`).
627
+ Options (`RecurringAdapterOptions`): `mondayStart` (default `true`), `palette` (colors rules without a `color`; off by default), `movable` (default `false`).
594
628
 
595
629
  Occurrences get the id `` `${rule.id}--YYYYMMDD` `` and `data.recurringId`. The adapter projects and stores nothing, so by default every occurrence carries `data.readOnly` and can't be dragged (it can still be clicked). With `movable: true` the views allow the drag and the adapter refuses the write with a read-only error, so `oneventmove` fires and the host stores the move: add the date to the rule's `excludeDates` and keep the moved occurrence with your one-off events (for example the memory side of a [composite adapter](#composite-adapter)).
596
630
 
@@ -646,7 +680,7 @@ Read-only. Expands recurrences server-side and handles all-day events, ISO-durat
646
680
 
647
681
  ## Mapped Adapter
648
682
 
649
- 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:
683
+ 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:
650
684
 
651
685
  ```ts
652
686
  import { createMappedAdapter } from '@nomideusz/svelte-calendar';
@@ -667,7 +701,7 @@ const adapter = createMappedAdapter(rawClasses, {
667
701
 
668
702
  - Works without options too: it looks for common keys (`id`, `title`/`name`, `start`/`end`, `starts_at_iso`/`ends_at_iso`, `date` + `start_time`/`end_time`, `room`/`location`, `is_cancelled`). A missing end defaults to one hour after the start.
669
703
  - `start`/`end` accept ISO strings, `Date`s or millisecond timestamps. When records split date and time (`date: "2026-03-03"`, `startTime: "07:00"`), map `date` + `startTime` / `endTime`; times may be `7:00`, `07:00` or `07:00:00`.
670
- - `autoColor` defaults to **`true`**: source colors are ignored and events are colored from `palette` by category/title. Pass `autoColor: false` to keep the colors from your data.
704
+ - Source colors are kept. Records without one draw in ink, unless you pass a `palette` to fill them by category/title. `autoColor: true` ignores the source colors and colors every event from `palette` (default `VIVID_PALETTE`).
671
705
  - `includeData` (default `'*'`) copies unmapped source fields into `event.data`; pass a list of keys to limit it.
672
706
 
673
707
  For full control, pass `mapEvent(raw, index)` and skip `fields`:
@@ -689,7 +723,7 @@ Mapped adapters are read-only by default (writes throw a read-only error, so dra
689
723
 
690
724
  ## Composite Adapter
691
725
 
692
- Merge several adapters into one — e.g. a recurring weekly schedule plus one-off events:
726
+ Merge several adapters into one, e.g. a recurring weekly schedule plus one-off events:
693
727
 
694
728
  ```ts
695
729
  import { createMemoryAdapter, createRecurringAdapter, createCompositeAdapter } from '@nomideusz/svelte-calendar';
@@ -701,9 +735,9 @@ const adapter = createCompositeAdapter([memory, recurring]);
701
735
  ```
702
736
 
703
737
  - **Reads** query every child in parallel and merge the results (the first event with a given id wins).
704
- - **Creates** go to the primary child — the first by default, or `primaryIndex`.
738
+ - **Creates** go to the primary child: the first by default, or `primaryIndex`.
705
739
  - **Updates and deletes** go to the child that returned the event. If that child can't write, the composite refuses with a read-only error (so `oneventmove` reaches the host). An id no fetch returned is offered to each writable child in turn.
706
- - **`onFetchError(error, adapterIndex)`** — with it, a failing child is reported to you and the others still load; without it, one failing child fails the whole load (surfacing through `onerror`).
740
+ - **`onFetchError(error, adapterIndex)`**: with it, a failing child is reported to you and the others still load; without it, one failing child fails the whole load (surfacing through `onerror`).
707
741
 
708
742
  ```ts
709
743
  const adapter = createCompositeAdapter([recurring, memory], {
@@ -724,7 +758,7 @@ const adapter: CalendarAdapter = {
724
758
  fetchEvents: async (range: DateRange) => { /* return TimelineEvent[] */ },
725
759
  // Optional: the same answer synchronously, when the data is already in memory
726
760
  fetchEventsSync: (range) => { /* return TimelineEvent[], or undefined to go async */ },
727
- // Optional writes — omit them for a read-only source
761
+ // Optional writes; omit them for a read-only source
728
762
  createEvent: async (event) => { /* return the created TimelineEvent with its id */ },
729
763
  updateEvent: async (id, patch) => { /* return the updated TimelineEvent */ },
730
764
  deleteEvent: async (id) => { /* void */ },
@@ -733,7 +767,7 @@ const adapter: CalendarAdapter = {
733
767
 
734
768
  `WritableCalendarAdapter` is the same interface with all three write methods required.
735
769
 
736
- **`fetchEventsSync`** lets the first render — including the server render — hold the events instead of a loading state. The memory, recurring and seeded adapters implement it. To seed an async adapter with events you already loaded on the server, wrap it:
770
+ **`fetchEventsSync`** lets the first render (the server render too) hold the events instead of a loading state. The memory, recurring and seeded adapters implement it. To seed an async adapter with events you already loaded on the server, wrap it:
737
771
 
738
772
  ```ts
739
773
  import { createRestAdapter, withInitialEvents } from '@nomideusz/svelte-calendar';
@@ -750,7 +784,7 @@ The Calendar tells two refusals apart when `updateEvent` throws:
750
784
 
751
785
  | Throw | Meaning | What happens |
752
786
  |-------|---------|--------------|
753
- | `new CalendarReadOnlyError(message?)` | "I hold this event but can't store the change." | The block stays where it was dropped and `oneventmove` fires — the host stores the move and refreshes. |
787
+ | `new CalendarReadOnlyError(message?)` | "I hold this event but can't store the change." | The block stays where it was dropped and `oneventmove` fires; the host stores the move and refreshes. |
754
788
  | `new EventNotFoundError(id)` | "Not my event." | A composite asks its next child; a drag ends quietly. |
755
789
  | any other error | A real failure | The block reverts and the error goes to `onerror` (or `console.warn`). |
756
790
 
@@ -758,7 +792,7 @@ An adapter without `updateEvent` counts as read-only. Plain `Error`s whose messa
758
792
 
759
793
  ## Headless API
760
794
 
761
- For full control over rendering, skip `<Calendar>` and drive everything from reactive state. `createCalendar()` returns computed layouts, navigation actions, drag helpers and the raw engines — zero DOM, bring your own UI. Call it during component initialisation.
795
+ For full control over rendering, skip `<Calendar>` and drive everything from reactive state. `createCalendar()` returns computed layouts, navigation actions, drag helpers and the raw engines. Zero DOM: bring your own UI. Call it during component initialisation.
762
796
 
763
797
  ```svelte
764
798
  <script lang="ts">
@@ -793,7 +827,7 @@ Options (`HeadlessCalendarOptions`) mirror the Calendar props: `adapter`, `view`
793
827
 
794
828
  `cal.days` gives flat `HeadlessDay[]` with events attached; `cal.weeks` groups them into periods (`HeadlessWeek`). `cal.todayQueue` returns `{ past, current, upcoming }` for today (`TodayQueue`), updated every second. `cal.hours` yields the visible hour numbers. Drag is fully wired: `beginDragMove`, `beginDragCreate`, `updateDrag`, `commitDrag` (validates like the Calendar), `cancelDrag`, plus `isDragging` / `dragPayload` / `dragMode`. `headerContext` / `navigationContext` match the Calendar's `header` / `navigation` snippet contexts. Raw engines (`store`, `viewState`, `selection`, `dragState`, `clock`) are exposed for advanced cases.
795
829
 
796
- The engine factories are exported individually — `createEventStore(adapter)`, `createViewState(options)`, `createSelection()`, `createDragState()`, `createClock(timezone?)` — if you want to compose your own calendar loop (types: `EventStore`, `ViewState`, `ViewStateOptions`, `CalendarSelection`, `DragState`, `DragMode`, `DragPayload`, `CalendarViewId`, `BuiltInViewId`, `ViewMode`).
830
+ The engine factories are exported individually (`createEventStore(adapter)`, `createViewState(options)`, `createSelection()`, `createDragState()`, `createClock(timezone?)`) if you want to compose your own calendar loop (types: `EventStore`, `ViewState`, `ViewStateOptions`, `CalendarSelection`, `DragState`, `DragMode`, `DragPayload`, `CalendarViewId`, `BuiltInViewId`, `ViewMode`).
797
831
 
798
832
  ### createAgenda
799
833
 
@@ -821,7 +855,7 @@ It returns `dayEvents`, `allDay`, `past`, `current`, `upcoming`, `upcomingSlots`
821
855
 
822
856
  ### createRangeAgenda
823
857
 
824
- A window of days with events grouped per day — no clock, drag or selection. Built for read-only schedule surfaces (public timetables, embeds):
858
+ A window of days with events grouped per day; no clock, drag or selection. Built for read-only schedule surfaces (public timetables, embeds):
825
859
 
826
860
  ```svelte
827
861
  <script lang="ts">
@@ -845,7 +879,7 @@ Options (`RangeAgendaOptions`): `adapter` (or a getter), `days` (default 7), `in
845
879
 
846
880
  ## Localization (i18n)
847
881
 
848
- The `locale` prop controls date/time formatting (BCP 47). Without it, the global default locale is used — `'en-US'` unless you call `setDefaultLocale()`:
882
+ The `locale` prop controls date/time formatting (BCP 47). Without it, the global default locale is used: `'en-US'` unless you call `setDefaultLocale()`:
849
883
 
850
884
  ```svelte
851
885
  <Calendar {adapter} locale="de-DE" />
@@ -853,7 +887,7 @@ The `locale` prop controls date/time formatting (BCP 47). Without it, the global
853
887
 
854
888
  `setDefaultLocale('de-DE')` sets it for every calendar and formatter; `getDefaultLocale()` reads it back; `is24HourLocale(tag?)` tells you how times will format. An RTL locale (`ar-SA`, `he-IL`, …) sets `dir="rtl"` where the browser reports the locale's text direction (`Intl.Locale` text info); pass `dir` to be explicit.
855
889
 
856
- Override UI labels per calendar with the `labels` prop — it's reactive (swap it to switch language live) and instance-scoped (two calendars on one page can carry different languages):
890
+ Override UI labels per calendar with the `labels` prop. It's reactive (swap it to switch language live) and instance-scoped (two calendars on one page can carry different languages):
857
891
 
858
892
  ```svelte
859
893
  <Calendar {adapter} locale="de-DE" labels={{
@@ -871,7 +905,7 @@ import { setLabels } from '@nomideusz/svelte-calendar';
871
905
  setLabels({ today: 'Heute', day: 'Tag', week: 'Woche' });
872
906
  ```
873
907
 
874
- Both take a `Partial<CalendarLabels>` — that is the supported input: pass the keys you translate, and keys added in later minor releases fall back to English. The `labels` prop merges over the global set. `resetLabels()` restores English (`defaultLabels` exports it; `getLabels()` reads the active set). Global `setLabels()` calls after mount don't re-render mounted calendars — use the `labels` prop to switch language live. Standalone primitives (`DayHeader`, `EventBlock`, …) and the format helpers read the global set.
908
+ Both take a `Partial<CalendarLabels>`, and that is the supported input: pass the keys you translate, and keys added in later minor releases fall back to English. The `labels` prop merges over the global set. `resetLabels()` restores English (`defaultLabels` exports it; `getLabels()` reads the active set). Global `setLabels()` calls after mount don't re-render mounted calendars; use the `labels` prop to switch language live. Standalone primitives (`DayHeader`, `EventBlock`, …) and the format helpers read the global set.
875
909
 
876
910
  <details>
877
911
  <summary>All label keys</summary>
@@ -911,7 +945,7 @@ Both take a `Partial<CalendarLabels>` — that is the supported input: pass the
911
945
 
912
946
  ## Timezone Support
913
947
 
914
- Render the whole calendar in any IANA timezone — events, the now-indicator and day boundaries all shift:
948
+ Render the whole calendar in any IANA timezone: events, the now-indicator and day boundaries all shift:
915
949
 
916
950
  ```svelte
917
951
  <Calendar {adapter} timezone="Europe/Warsaw" />
@@ -919,7 +953,7 @@ Render the whole calendar in any IANA timezone — events, the now-indicator and
919
953
 
920
954
  - `initialDate` and `currentDate` are instants, read in that zone.
921
955
  - `oneventcreate`, `oneventmove` and `onexternaldrop` receive real instants.
922
- - `ondatechange` and `ondayclick` receive instants too — the start of that day in the zone — so feeding one back is stable: `currentDate={date} ondatechange={(d) => (date = d)}`. To read the date, format it in the zone:
956
+ - `ondatechange` and `ondayclick` receive instants too (the start of that day in the zone), so feeding one back is stable: `currentDate={date} ondatechange={(d) => (date = d)}`. To read the date, format it in the zone:
923
957
 
924
958
  ```ts
925
959
  import { formatInTimeZone } from '@nomideusz/svelte-calendar';
@@ -970,20 +1004,20 @@ Small helpers used by the built-in views, exported for custom rendering. Formatt
970
1004
  | `weekdayShort` / `weekdayLong` / `monthShort` / `monthLong` | Name parts of a timestamp |
971
1005
  | `sod(ms)` | Start of that day (local midnight) |
972
1006
  | `startOfWeek(ms, mondayStart = true)` | Start-of-week timestamp |
973
- | `addDaysMs(ms, n)` / `diffDays(a, b)` | Add / count **calendar** days — DST-safe (a DST day is 23 or 25 hours, so don't add `n * 86_400_000`) |
1007
+ | `addDaysMs(ms, n)` / `diffDays(a, b)` | Add / count **calendar** days, DST-safe (a DST day is 23 or 25 hours, so don't add `n * 86_400_000`) |
974
1008
  | `isAllDay(ev)` / `isMultiDay(ev)` | Event classification |
975
1009
  | `segmentForDay(ev, dayMs)` | The slice of a multi-day event on one day (`DaySegment`), or `null` |
976
1010
  | `createClock(timezone?)` | Reactive clock: `tick` (ms, every second), `today`, `hm`, `s`, `fractionalHour`, `destroy()` |
977
- | `textWidth(text, font)` / `textHeight(text, font, width, lineHeight)` / `lineCount(text, font, width, options?)` / `fits(text, font, width, lines = 1)` | Text measurement via [pretext](https://github.com/chenglou/pretext) — before render, no layout thrash. `options` is `TextLayoutOptions` (`whiteSpace`, `wordBreak`, `letterSpacing`). Browser-only. |
1011
+ | `textWidth(text, font)` / `textHeight(text, font, width, lineHeight)` / `lineCount(text, font, width, options?)` / `fits(text, font, width, lines = 1)` | Text measurement via [pretext](https://github.com/chenglou/pretext), before render, no layout thrash. `options` is `TextLayoutOptions` (`whiteSpace`, `wordBreak`, `letterSpacing`). Browser-only. |
978
1012
  | `pickFit(candidates, font, width, lines = 1)` | The longest candidate that fits |
979
1013
  | `fitLabel(candidates, lines = 1)` | Attachment: `<span {@attach fitLabel([ev.title, ev.short])}>` keeps the longest label that fits its box, re-fitting on resize |
980
1014
  | `fontOf(el)` | An element's computed font as a canvas font string |
981
1015
  | `fitParts(parts, budget, measure?)` | Which parts of a dense chip fit, along either axis → `Record<key, boolean>`. Each `ChipPart` is `{ key, text, font }` to measure or `{ key, size }` to state its cost, plus `priority` (`0` is an anchor and never drops; higher numbers drop first) and `extra` (a gap or icon). The first optional part that doesn't fit ends the list, so a chip never keeps a later detail after dropping an earlier one. A budget of `0` (server-rendered, not yet measured) leaves only the anchors. |
982
- | `typeset(text)` / `breakLines(text, font, width)` | Knuth-Plass paragraph breaking over the text's own spaces and soft hyphens. `<p {@attach typeset(text)}>` sets justified lines, re-done on resize; if the browser disagrees with the measure, the plain text goes back. Progressive — SSR text stays. |
1016
+ | `typeset(text)` / `breakLines(text, font, width)` | Knuth-Plass paragraph breaking over the text's own spaces and soft hyphens. `<p {@attach typeset(text)}>` sets justified lines, re-done on resize; if the browser disagrees with the measure, the plain text goes back. Progressive: SSR text stays. |
983
1017
 
984
1018
  ## Embeddable Widget
985
1019
 
986
- Drop into any HTML page — no build tools needed. Registers a `<day-calendar>` custom element:
1020
+ Drop into any HTML page, no build tools needed. Registers a `<day-calendar>` custom element:
987
1021
 
988
1022
  ```html
989
1023
  <script src="https://cdn.jsdelivr.net/npm/@nomideusz/svelte-calendar/widget/widget.js"></script>
@@ -1024,47 +1058,78 @@ Each event in `api` responses and `events` needs `start` and `end` as date strin
1024
1058
 
1025
1059
  ## All Props
1026
1060
 
1027
- <details>
1028
- <summary>Full Calendar props reference (<code>CalendarProps</code>)</summary>
1061
+ Every `<Calendar>` prop (`CalendarProps`), grouped by what it controls.
1062
+
1063
+ ### Data and editing
1029
1064
 
1030
1065
  | Prop | Type | Default | Description |
1031
1066
  |------|------|---------|-------------|
1032
1067
  | `adapter` | `CalendarAdapter` | *required* | Data source (memory, recurring, mapped, composite, REST, JMAP, or custom) |
1033
- | `views` | `readonly CalendarView[]` | `defaultViews` | View registry (the 8 built-ins). Spread `defaultViews` to add your own. |
1034
- | `view` | `CalendarViewId` | first registered (`'day-planner'`) | Active view ID. Reactive. |
1035
- | `theme` | `string` | `auto` | CSS theme string of `--dt-*` properties. `auto` probes the host page. |
1036
- | `autoTheme` | `AutoThemeOptions \| false` | `undefined` (probe) | Fine-tune auto-detection: `{ mode, accent, font }`. `false` disables probing. |
1037
- | `mobile` | `'auto' \| boolean` | `'auto'` | `'auto'` switches to mobile views below a 768px container width |
1038
- | `height` | `number \| 'auto'` | `600` | Height in px, or `'auto'` to grow with content (ideal for Agenda views) |
1039
- | `borderRadius` | `number` | `12` | Border radius in px. `0` for none. |
1040
- | `locale` | `string` | global default (`'en-US'`) | BCP 47 locale tag; see `setDefaultLocale` |
1041
- | `labels` | `Partial<CalendarLabels>` | — | Per-instance UI labels, merged over the global set. Reactive. |
1042
- | `dir` | `'ltr' \| 'rtl' \| 'auto'` | from `locale` | Text direction |
1043
- | `mondayStart` | `boolean` | `true` | Start weeks on Monday |
1044
1068
  | `readOnly` | `boolean` | `false` | Disable drag, resize, click-to-create and external drops |
1045
- | `visibleHours` | `[number, number]` | — | Crop the grid to `[startHour, endHour)` |
1069
+ | `snapInterval` | `number` | `15` | Drag snap in minutes |
1070
+ | `minDuration` | `number` | — | Minimum duration in minutes; clamps creates and resizes |
1071
+ | `maxDuration` | `number` | — | Maximum duration in minutes; clamps creates and resizes |
1072
+ | `blockedSlots` | `BlockedSlot[]` | — | `{ day?, start, end, label? }` hour ranges that can't be booked (hatched in planner views) |
1073
+ | `disabledDates` | `Date[]` | — | Dates that refuse creates/moves; existing events stay visible and clickable (dimmed and inert in `month-grid`) |
1074
+
1075
+ ### View and navigation
1076
+
1077
+ | Prop | Type | Default | Description |
1078
+ |------|------|---------|-------------|
1079
+ | `view` | `CalendarViewId` | first registered (`'day-planner'`) | Active view ID. Reactive. |
1080
+ | `views` | `readonly CalendarView[]` | `defaultViews` | View registry (the eight built-ins). Spread `defaultViews` to add your own. |
1046
1081
  | `initialDate` | `Date` | today | Date to focus at mount (an instant; read in `timezone`) |
1047
1082
  | `currentDate` | `Date` | — | Controlled focus date (an instant; read in `timezone`) |
1048
- | `timezone` | `string` | viewer's zone | IANA zone to render in. Read at mount. |
1049
- | `snapInterval` | `number` | `15` | Drag snap in minutes |
1050
- | `minColumnWidth` | `number` | `110` | Minimum day-column width (px) in planner views; below the total the grid scrolls horizontally |
1051
1083
  | `days` | `number` | `7` | Days shown in week mode (e.g. `3` for a rolling 3-day view) |
1052
- | `hideDays` | `number[]` | — | ISO weekdays to hide (1=Mon … 7=Sun), e.g. `[6, 7]` |
1084
+ | `mobile` | `'auto' \| boolean` | `'auto'` | `'auto'` switches to mobile views below a 768px container width |
1053
1085
  | `showModePills` | `boolean` | `true` | Show the Day/Week/Month pills and the view-type pills |
1054
1086
  | `showNavigation` | `boolean` | `true` | Show prev/next/today |
1087
+
1088
+ ### Display
1089
+
1090
+ | Prop | Type | Default | Description |
1091
+ |------|------|---------|-------------|
1092
+ | `height` | `number \| 'auto'` | `600` | Height in px, or `'auto'` to grow with content (ideal for Agenda views) |
1093
+ | `visibleHours` | `[number, number]` | — | Crop the grid to `[startHour, endHour)` |
1094
+ | `hideDays` | `number[]` | — | ISO weekdays to hide (1=Mon … 7=Sun), e.g. `[6, 7]` |
1095
+ | `mondayStart` | `boolean` | `true` | Start weeks on Monday |
1055
1096
  | `showDates` | `boolean` | `true` | Show date numbers and the date label. `false` = day names only (templates, recurring schedules) |
1056
1097
  | `equalDays` | `boolean` | `false` | Treat all days equally (no past-day dimming/collapsing) |
1057
- | `blockedSlots` | `BlockedSlot[]` | — | `{ day?, start, end, label? }` hour ranges that can't be booked (hatched in planner views) |
1058
- | `disabledDates` | `Date[]` | — | Dates that refuse creates/moves; existing events stay visible and clickable |
1059
- | `minDuration` | `number` | — | Minimum duration in minutes; clamps creates and resizes |
1060
- | `maxDuration` | `number` | — | Maximum duration in minutes; clamps creates and resizes |
1061
1098
  | `compact` | `boolean` | `false` | Minimal text rows in Agenda views (dot + time + title) |
1062
1099
  | `columns` | `boolean` | `false` | Timetable layout: `week-agenda` days side by side on desktop. Pairs with `equalDays`; overrides `compact` |
1100
+ | `minColumnWidth` | `number` | `110` | Minimum day-column width (px) in planner views; below the total the grid scrolls horizontally |
1101
+
1102
+ ### Theming
1103
+
1104
+ | Prop | Type | Default | Description |
1105
+ |------|------|---------|-------------|
1106
+ | `theme` | `string` | `auto` | CSS theme string of `--dt-*` properties. `auto` probes the host page. |
1107
+ | `autoTheme` | `AutoThemeOptions \| false` | `undefined` (probe) | Fine-tune auto-detection: `{ mode, accent, font }`. `false` disables probing. |
1108
+ | `borderRadius` | `number` | `0` | Corner radius in px (square by default) |
1109
+
1110
+ ### Locale and timezone
1111
+
1112
+ | Prop | Type | Default | Description |
1113
+ |------|------|---------|-------------|
1114
+ | `locale` | `string` | global default (`'en-US'`) | BCP 47 locale tag; see `setDefaultLocale` |
1115
+ | `labels` | `Partial<CalendarLabels>` | — | Per-instance UI labels, merged over the global set. Reactive. |
1116
+ | `dir` | `'ltr' \| 'rtl' \| 'auto'` | from `locale` | Text direction |
1117
+ | `timezone` | `string` | viewer's zone | IANA zone to render in. Read at mount. |
1118
+
1119
+ ### Snippets
1120
+
1121
+ | Prop | Type | Default | Description |
1122
+ |------|------|---------|-------------|
1063
1123
  | `event` | `Snippet<[TimelineEvent]>` | — | Custom event content |
1064
1124
  | `empty` | `Snippet` | — | Empty state for the day agenda, mobile day and an empty planner week |
1065
1125
  | `dayHeader` | `Snippet<[{ date, isToday, dayName }]>` | — | Custom day header |
1066
1126
  | `header` | `Snippet<[HeaderContext]>` | — | Replace the whole header (`dateLabel`, `mode`, `modes`, `switchMode`, `prev`, `next`, `goToday`, `isViewOnToday`, `focusDate`) |
1067
1127
  | `navigation` | `Snippet<[NavigationContext]>` | — | Replace prev/next/today (`prev`, `next`, `goToday`, `isViewOnToday`, `focusDate`, `mode`) |
1128
+
1129
+ ### Callbacks
1130
+
1131
+ | Prop | Type | Default | Description |
1132
+ |------|------|---------|-------------|
1068
1133
  | `oneventclick` | `(event, anchor?: DOMRect) => void` | — | Event clicked (and selected) |
1069
1134
  | `oneventcreate` | `(range: { start, end }) => void` | — | A validated new range (real instants) |
1070
1135
  | `oneventmove` | `(event, start, end) => void` | — | Drag/resize stored, or refused read-only (real instants) |
@@ -1075,7 +1140,15 @@ Each event in `api` responses and `events` needs `start` and `end` as date strin
1075
1140
  | `oneventhover` | `(event) => void` | — | Pointer enters an event |
1076
1141
  | `onerror` | `(error: Error) => void` | — | Load and commit failures |
1077
1142
 
1078
- </details>
1143
+ ## Troubleshooting
1144
+
1145
+ - **Dragging on empty time does nothing.** Creating needs an `oneventcreate` handler (and no `readOnly`); see the [Quick Start](#quick-start).
1146
+ - **A created event doesn't appear.** The calendar stores nothing on create: add the event to your data and hand the Calendar a new adapter (`$derived(createMemoryAdapter(events))`).
1147
+ - **Moved events jump back after the next change.** A new adapter is built from your array, so write each move into it in `oneventmove`.
1148
+ - **The `auto` theme picked the wrong colours.** It probes the host page around the calendar, and probes again when `<html>`/`<body>` change class, style or `data-theme`, when the system scheme flips and when a background transition ends. Steer it with [`autoTheme`](#smart-auto-theme), or set `theme` to a preset or your own tokens to opt out.
1149
+ - **Events are all one colour.** Since 1.4 events without a `color` draw in ink; give the adapter a [`palette`](#event-colors).
1150
+ - **The calendar stops at 600px.** That's the default `height`; pass a number, or `'auto'` to grow with the content.
1151
+ - **The phone layout shows on a wide page (or the desktop one in a narrow column).** The switch reads the calendar's container, not the window; force it with `mobile={false}` or `mobile={true}`.
1079
1152
 
1080
1153
  ## Versioning
1081
1154
 
@@ -1084,7 +1157,7 @@ The package follows [semver](https://semver.org/) from 1.0:
1084
1157
  - The public API is exactly what the `.` and `./widget` entry points export. Deep imports into `dist/` are not supported.
1085
1158
  - `TimelineEvent`, `CalendarAdapter` and `DateRange` are stable shapes: fields may be added in minor releases, never renamed, removed or given new meaning outside a major.
1086
1159
  - The `--dt-*` theme tokens are part of the contract, as are the `<day-calendar>` attributes.
1087
- - `CalendarLabels` gains keys in minor releases — pass a `Partial` (see [Localization](#localization-i18n)).
1160
+ - `CalendarLabels` gains keys in minor releases, so pass a `Partial` (see [Localization](#localization-i18n)).
1088
1161
 
1089
1162
  ## Development
1090
1163