@nomideusz/svelte-calendar 1.2.0 → 1.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 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-and-drop, 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
 
@@ -34,24 +34,24 @@ Requirements:
34
34
  ]);
35
35
  </script>
36
36
 
37
- <Calendar {adapter} />
37
+ <Calendar {adapter} view="week-planner" />
38
38
  ```
39
39
 
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.
40
+ That's it: eight built-in views and drag-and-drop with live previews, out of the box. 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
41
 
42
42
  ## Views
43
43
 
44
- The default registry (`defaultViews`) holds eight views:
44
+ 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
45
 
46
46
  | View ID | Mode | What it is |
47
47
  |---------|------|------------|
48
- | `day-planner` | day | Vertical time grid, one column. **The default view** (first registered). |
48
+ | `day-planner` | day | Vertical time grid, one column. Opens when `view` is unset (the first registered view). The examples here, the demo, the widget and `createCalendar` open on `week-planner`. |
49
49
  | `week-planner` | week | The same time grid, one column per day (`days`, default 7). |
50
50
  | `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
51
  | `day-agenda` | day | Live list for one day: now / up next / done. |
52
52
  | `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. |
53
+ | `day-mobile` | day | Touch-first time grid with swipe navigation. Used automatically in a narrow container. |
54
+ | `week-mobile` | week | Touch-first list of the week's days. Used automatically in a narrow container. |
55
55
  | `month-grid` | month | Week-aligned month grid, chips per day with a "+N more" overflow. |
56
56
 
57
57
  ```svelte
@@ -68,10 +68,10 @@ Users switch with the built-in pills: **Day / Week / Month** for the mode, plus
68
68
 
69
69
  Planner views are designed for direct manipulation:
70
70
 
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 })`.
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 })`.
75
75
 
76
76
  `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
77
 
@@ -81,8 +81,8 @@ The month grid shows up to three chips per day. Clicking a day or its "+N more"
81
81
 
82
82
  When focus is inside the calendar:
83
83
 
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.
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.
86
86
  - In `month-grid`: arrow keys move between days, `Enter` / `Space` opens the focused day (`ondayclick`).
87
87
  - 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
88
 
@@ -106,13 +106,13 @@ The `views` prop replaces the registry. Spread `defaultViews` to add your own vi
106
106
 
107
107
  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
108
 
109
- The component receives `CalendarViewProps` (plus your `props`) — declare only the ones it uses:
109
+ The component receives `CalendarViewProps` (plus your `props`); declare only the ones it uses:
110
110
 
111
111
  | Prop | Type | Notes |
112
112
  |------|------|-------|
113
113
  | `events` | `TimelineEvent[]` | Everything loaded for the current range |
114
114
  | `style` | `string` | The active `--dt-*` theme string |
115
- | `height` | `number \| null` | Currently always `null` — size to the container |
115
+ | `height` | `number \| null` | Currently always `null`: size to the container |
116
116
  | `mode` | `'day' \| 'week' \| 'month'` | |
117
117
  | `mondayStart` | `boolean` | |
118
118
  | `locale` | `string \| undefined` | |
@@ -143,15 +143,15 @@ The component receives `CalendarViewProps` (plus your `props`) — declare only
143
143
 
144
144
  `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
145
 
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)).
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)).
147
147
 
148
148
  ## Mobile
149
149
 
150
150
  When the calendar's **container** is narrower than 768px, it swaps to touch-first Mobile views with swipe navigation and a compact header:
151
151
 
152
152
  ```svelte
153
- <!-- Auto-detect (default) — switches at a 768px container width -->
154
- <Calendar {adapter} />
153
+ <!-- Auto-detect (default): switches at a 768px container width -->
154
+ <Calendar {adapter} view="week-planner" />
155
155
 
156
156
  <!-- Force mobile layout -->
157
157
  <Calendar {adapter} mobile={true} />
@@ -160,10 +160,10 @@ When the calendar's **container** is narrower than 768px, it swaps to touch-firs
160
160
  <Calendar {adapter} mobile={false} />
161
161
  ```
162
162
 
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.
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.
165
165
 
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.
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, 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
167
 
168
168
  With `'auto'`, the first render (and the server render) is the desktop layout; the container is measured on mount, so SSR hydrates cleanly.
169
169
 
@@ -185,9 +185,9 @@ With `'auto'`, the first render (and the server render) is the desktop layout; t
185
185
 
186
186
  | Callback | When it fires |
187
187
  |----------|---------------|
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`. |
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`. |
189
189
  | `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`.) |
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`.) |
191
191
  | `onexternaldrop({ start, dataTransfer })` | An outside HTML5 drag was dropped on the grid; `start` is the pointer's time, snapped. |
192
192
  | `onviewchange(viewId)` | Once on mount, then on every view change. |
193
193
  | `ondatechange(date)` | Once on mount, then whenever the focused date changes (navigation, scrolling, a tapped day). |
@@ -197,7 +197,7 @@ With `'auto'`, the first render (and the server render) is the desktop layout; t
197
197
 
198
198
  `oneventcreate`, `oneventmove` and `onexternaldrop` receive real instants, also with [`timezone`](#timezone-support). None of the write callbacks fire when `readOnly` is set.
199
199
 
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:
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:
201
201
 
202
202
  ```svelte
203
203
  <script lang="ts">
@@ -246,7 +246,7 @@ To lock single events, set `data.readOnly: true` on them: views refuse to move o
246
246
  | Prop | Type | Default | Description |
247
247
  |------|------|---------|-------------|
248
248
  | `title` | `string` | `''` | Header text and dialog label |
249
- | `anchor` | `FloatingPanelAnchor` | `{ x: 24, y: 24 }` | `{ x, y, width?, height? }` — a `DOMRect` works as is |
249
+ | `anchor` | `FloatingPanelAnchor` | `{ x: 24, y: 24 }` | `{ x, y, width?, height? }`; a `DOMRect` works as is |
250
250
  | `width` | `number` | `440` | Panel width in px |
251
251
  | `theme` | `string` | `''` | A `--dt-*` theme string, as the Calendar takes |
252
252
  | `closeLabel` | `string` | `'Close'` | Close button label |
@@ -320,7 +320,7 @@ Disable specific dates:
320
320
  />
321
321
  ```
322
322
 
323
- Disabled dates refuse creating or moving events into those days. Existing events stay visible and clickable — useful for holidays or fully booked days.
323
+ Disabled dates refuse creating or moving events into those days. Existing events stay visible and clickable, which suits holidays or fully booked days.
324
324
 
325
325
  Custom day headers and hover previews:
326
326
 
@@ -358,7 +358,7 @@ Replace the built-in navigation or the entire header chrome with your own contro
358
358
  </Calendar>
359
359
  ```
360
360
 
361
- Let agenda content determine height instead of forcing a fixed box — useful inside a scrolling page:
361
+ Let agenda content determine height instead of forcing a fixed box, for a calendar inside a scrolling page:
362
362
 
363
363
  ```svelte
364
364
  <Calendar {adapter} view="week-agenda" height="auto" compact />
@@ -370,7 +370,7 @@ Three built-in presets:
370
370
 
371
371
  | Preset | Description |
372
372
  |--------|-------------|
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. |
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. |
374
374
  | `neutral` | Explicit light theme. White bg, blue accent. Use when embedding standalone. |
375
375
  | `midnight` | Explicit dark theme. Charcoal bg, red accent. |
376
376
 
@@ -390,11 +390,11 @@ Three built-in presets:
390
390
 
391
391
  The default `auto` preset probes the page around the calendar and generates a theme that blends in. It detects:
392
392
 
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
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
398
398
 
399
399
  It watches for changes (a dark-mode toggle on `<html>`/`<body>`, the system color scheme) and updates automatically.
400
400
 
@@ -420,7 +420,7 @@ Fine-tune auto-detection with the `autoTheme` prop (`AutoThemeOptions`):
420
420
 
421
421
  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
422
 
423
- - **Change a few tokens** — append them to a preset (later declarations win):
423
+ - **Change a few tokens**: append them to a preset (later declarations win):
424
424
 
425
425
  ```ts
426
426
  import { neutral } from '@nomideusz/svelte-calendar';
@@ -432,8 +432,8 @@ The calendar writes its theme inline on its own root element, so it **wins over*
432
432
  <Calendar {adapter} theme={custom} />
433
433
  ```
434
434
 
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:
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:
437
437
 
438
438
  ```svelte
439
439
  <div style="--dt-accent: #e11d48; --dt-bg: #1a1a2e; --dt-text: rgba(255,255,255,0.87);">
@@ -457,6 +457,7 @@ Component fallbacks use system fonts and a neutral blue accent, so the package s
457
457
  | `--dt-text-3` | Tertiary text |
458
458
  | `--dt-accent` | Accent color |
459
459
  | `--dt-accent-dim` | Accent at ~12% opacity (selection, highlights) |
460
+ | `--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
461
  | `--dt-accent-fg` | Text on the planner's today-number badge (default `#fff`; not set by the presets) |
461
462
  | `--dt-btn-text` | Text on accent-filled buttons and badges |
462
463
  | `--dt-glow` | Accent glow / focus ring |
@@ -481,8 +482,8 @@ Component fallbacks use system fonts and a neutral blue accent, so the package s
481
482
  | `id` | `string` | Unique identifier |
482
483
  | `title` | `string` | Event title |
483
484
  | `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 |
485
+ | `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` |
486
+ | `category` | `string?` | Grouping key: with a `palette`, events of the same category share a color |
486
487
  | `subtitle` | `string?` | Secondary text below the title |
487
488
  | `tags` | `string[]?` | Small accent-colored pills |
488
489
  | `allDay` | `boolean?` | Render as an all-day event |
@@ -494,19 +495,25 @@ Component fallbacks use system fonts and a neutral blue accent, so the package s
494
495
 
495
496
  Cancelled events render with a strikethrough but stay visible, so the slot isn't mistaken for free time.
496
497
 
497
- ### Auto-Coloring
498
+ ### Event colors
499
+
500
+ 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
501
 
499
- Omit `color` and the built-in adapters assign a vivid palette color, grouped by `category` (or `title`):
502
+ 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
503
 
501
504
  ```ts
505
+ import { createMemoryAdapter, VIVID_PALETTE } from '@nomideusz/svelte-calendar';
506
+
502
507
  const events = [
503
508
  { id: '1', title: 'Yoga', category: 'wellness', start, end },
504
509
  { id: '2', title: 'Pilates', category: 'wellness', start, end }, // same color
505
510
  { id: '3', title: 'Standup', start, end }, // different color
506
511
  ];
512
+
513
+ const adapter = createMemoryAdapter(events, { palette: VIVID_PALETTE });
507
514
  ```
508
515
 
509
- Generate a theme-harmonious palette from any accent color:
516
+ Or generate a theme-harmonious palette from any accent color:
510
517
 
511
518
  ```ts
512
519
  import { createMemoryAdapter, generatePalette } from '@nomideusz/svelte-calendar';
@@ -515,7 +522,7 @@ const palette = generatePalette('#e11d48'); // (accent?, count = 15)
515
522
  const adapter = createMemoryAdapter(events, { palette });
516
523
  ```
517
524
 
518
- Related exports: `VIVID_PALETTE` (the default palette) and `extractAccent(themeString)` (the accent color of a `--dt-*` theme string).
525
+ Related exports: `VIVID_PALETTE` (the vivid palette, the default before 1.4) and `extractAccent(themeString)` (the accent color of a `--dt-*` theme string).
519
526
 
520
527
  ### Multi-day & All-day
521
528
 
@@ -530,7 +537,7 @@ const events = [
530
537
 
531
538
  ### Custom Event Rendering
532
539
 
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:
540
+ 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
541
 
535
542
  ```svelte
536
543
  <Calendar {adapter}>
@@ -541,11 +548,25 @@ The `event` snippet replaces the event *content* in every view (the interactive
541
548
  </div>
542
549
  {/snippet}
543
550
  {#snippet empty()}
544
- <p>Nothing booked — drag on the grid to add a class.</p>
551
+ <p>Nothing booked. Drag on the grid to add a class.</p>
545
552
  {/snippet}
546
553
  </Calendar>
547
554
  ```
548
555
 
556
+ ## Adapters
557
+
558
+ The calendar reads its events through an adapter. Which one:
559
+
560
+ | Adapter | Use it when |
561
+ |---------|-------------|
562
+ | `createMemoryAdapter(events)` | The events are already in the page (a fixed list, your own state). Writes stay in memory. See [Quick Start](#quick-start). |
563
+ | [`createRecurringAdapter(rules)`](#recurring-schedules) | The schedule repeats by rule: class timetables, office hours. Read-only. |
564
+ | [`createRestAdapter(options)`](#rest-adapter) | The events live behind your own HTTP API, which also stores creates and moves. |
565
+ | [`createJmapAdapter(client, options)`](#jmap-adapter) | The events are in a JMAP Calendars server (Fastmail, Stalwart, Cyrus). Read-only. |
566
+ | [`createMappedAdapter(records, options)`](#mapped-adapter) | You have records from another system with their own field names: map the fields instead of writing an adapter. |
567
+ | [`createCompositeAdapter(adapters)`](#composite-adapter) | One calendar shows several sources, e.g. a recurring timetable plus one-off events. |
568
+ | [Custom `CalendarAdapter`](#custom-adapter) | None of the above fits: implement `fetchEvents`, plus the writes you need. |
569
+
549
570
  ## Recurring Schedules
550
571
 
551
572
  For fixed repeating events (class timetables, office hours):
@@ -564,7 +585,7 @@ For fixed repeating events (class timetables, office hours):
564
585
  ]);
565
586
  </script>
566
587
 
567
- <Calendar {adapter} />
588
+ <Calendar {adapter} view="week-planner" />
568
589
  ```
569
590
 
570
591
  <details>
@@ -584,13 +605,13 @@ For fixed repeating events (class timetables, office hours):
584
605
  | `until` | `string` | — | Last possible occurrence `"YYYY-MM-DD"` |
585
606
  | `count` | `number` | — | Max occurrences from `startDate` (requires it). With `until`, the stricter wins. |
586
607
  | `excludeDates` | `string[]` | — | `"YYYY-MM-DD"` dates this rule skips |
587
- | `color` | `string` | auto | Accent color |
608
+ | `color` | `string` | — | Bar color; else from `palette`, else ink (`--dt-event`) |
588
609
  | `subtitle` / `tags` / `category` / `location` / `resourceId` | | — | Copied to every occurrence |
589
610
  | `data` | `Record<string, unknown>` | — | Copied to every occurrence, plus `recurringId` |
590
611
 
591
612
  </details>
592
613
 
593
- Options (`RecurringAdapterOptions`): `mondayStart` (default `true`), `palette`, `movable` (default `false`).
614
+ Options (`RecurringAdapterOptions`): `mondayStart` (default `true`), `palette` (colors rules without a `color`; off by default), `movable` (default `false`).
594
615
 
595
616
  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
617
 
@@ -646,7 +667,7 @@ Read-only. Expands recurrences server-side and handles all-day events, ISO-durat
646
667
 
647
668
  ## Mapped Adapter
648
669
 
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:
670
+ 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
671
 
651
672
  ```ts
652
673
  import { createMappedAdapter } from '@nomideusz/svelte-calendar';
@@ -667,7 +688,7 @@ const adapter = createMappedAdapter(rawClasses, {
667
688
 
668
689
  - 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
690
  - `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.
691
+ - 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
692
  - `includeData` (default `'*'`) copies unmapped source fields into `event.data`; pass a list of keys to limit it.
672
693
 
673
694
  For full control, pass `mapEvent(raw, index)` and skip `fields`:
@@ -689,7 +710,7 @@ Mapped adapters are read-only by default (writes throw a read-only error, so dra
689
710
 
690
711
  ## Composite Adapter
691
712
 
692
- Merge several adapters into one — e.g. a recurring weekly schedule plus one-off events:
713
+ Merge several adapters into one, e.g. a recurring weekly schedule plus one-off events:
693
714
 
694
715
  ```ts
695
716
  import { createMemoryAdapter, createRecurringAdapter, createCompositeAdapter } from '@nomideusz/svelte-calendar';
@@ -701,9 +722,9 @@ const adapter = createCompositeAdapter([memory, recurring]);
701
722
  ```
702
723
 
703
724
  - **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`.
725
+ - **Creates** go to the primary child: the first by default, or `primaryIndex`.
705
726
  - **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`).
727
+ - **`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
728
 
708
729
  ```ts
709
730
  const adapter = createCompositeAdapter([recurring, memory], {
@@ -724,7 +745,7 @@ const adapter: CalendarAdapter = {
724
745
  fetchEvents: async (range: DateRange) => { /* return TimelineEvent[] */ },
725
746
  // Optional: the same answer synchronously, when the data is already in memory
726
747
  fetchEventsSync: (range) => { /* return TimelineEvent[], or undefined to go async */ },
727
- // Optional writes — omit them for a read-only source
748
+ // Optional writes; omit them for a read-only source
728
749
  createEvent: async (event) => { /* return the created TimelineEvent with its id */ },
729
750
  updateEvent: async (id, patch) => { /* return the updated TimelineEvent */ },
730
751
  deleteEvent: async (id) => { /* void */ },
@@ -733,7 +754,7 @@ const adapter: CalendarAdapter = {
733
754
 
734
755
  `WritableCalendarAdapter` is the same interface with all three write methods required.
735
756
 
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:
757
+ **`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
758
 
738
759
  ```ts
739
760
  import { createRestAdapter, withInitialEvents } from '@nomideusz/svelte-calendar';
@@ -750,7 +771,7 @@ The Calendar tells two refusals apart when `updateEvent` throws:
750
771
 
751
772
  | Throw | Meaning | What happens |
752
773
  |-------|---------|--------------|
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. |
774
+ | `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
775
  | `new EventNotFoundError(id)` | "Not my event." | A composite asks its next child; a drag ends quietly. |
755
776
  | any other error | A real failure | The block reverts and the error goes to `onerror` (or `console.warn`). |
756
777
 
@@ -758,7 +779,7 @@ An adapter without `updateEvent` counts as read-only. Plain `Error`s whose messa
758
779
 
759
780
  ## Headless API
760
781
 
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.
782
+ 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
783
 
763
784
  ```svelte
764
785
  <script lang="ts">
@@ -793,7 +814,7 @@ Options (`HeadlessCalendarOptions`) mirror the Calendar props: `adapter`, `view`
793
814
 
794
815
  `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
816
 
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`).
817
+ 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
818
 
798
819
  ### createAgenda
799
820
 
@@ -821,7 +842,7 @@ It returns `dayEvents`, `allDay`, `past`, `current`, `upcoming`, `upcomingSlots`
821
842
 
822
843
  ### createRangeAgenda
823
844
 
824
- A window of days with events grouped per day — no clock, drag or selection. Built for read-only schedule surfaces (public timetables, embeds):
845
+ A window of days with events grouped per day; no clock, drag or selection. Built for read-only schedule surfaces (public timetables, embeds):
825
846
 
826
847
  ```svelte
827
848
  <script lang="ts">
@@ -845,7 +866,7 @@ Options (`RangeAgendaOptions`): `adapter` (or a getter), `days` (default 7), `in
845
866
 
846
867
  ## Localization (i18n)
847
868
 
848
- The `locale` prop controls date/time formatting (BCP 47). Without it, the global default locale is used — `'en-US'` unless you call `setDefaultLocale()`:
869
+ The `locale` prop controls date/time formatting (BCP 47). Without it, the global default locale is used: `'en-US'` unless you call `setDefaultLocale()`:
849
870
 
850
871
  ```svelte
851
872
  <Calendar {adapter} locale="de-DE" />
@@ -853,7 +874,7 @@ The `locale` prop controls date/time formatting (BCP 47). Without it, the global
853
874
 
854
875
  `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
876
 
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):
877
+ 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
878
 
858
879
  ```svelte
859
880
  <Calendar {adapter} locale="de-DE" labels={{
@@ -871,7 +892,7 @@ import { setLabels } from '@nomideusz/svelte-calendar';
871
892
  setLabels({ today: 'Heute', day: 'Tag', week: 'Woche' });
872
893
  ```
873
894
 
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.
895
+ 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
896
 
876
897
  <details>
877
898
  <summary>All label keys</summary>
@@ -911,7 +932,7 @@ Both take a `Partial<CalendarLabels>` — that is the supported input: pass the
911
932
 
912
933
  ## Timezone Support
913
934
 
914
- Render the whole calendar in any IANA timezone — events, the now-indicator and day boundaries all shift:
935
+ Render the whole calendar in any IANA timezone: events, the now-indicator and day boundaries all shift:
915
936
 
916
937
  ```svelte
917
938
  <Calendar {adapter} timezone="Europe/Warsaw" />
@@ -919,7 +940,7 @@ Render the whole calendar in any IANA timezone — events, the now-indicator and
919
940
 
920
941
  - `initialDate` and `currentDate` are instants, read in that zone.
921
942
  - `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:
943
+ - `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
944
 
924
945
  ```ts
925
946
  import { formatInTimeZone } from '@nomideusz/svelte-calendar';
@@ -970,20 +991,20 @@ Small helpers used by the built-in views, exported for custom rendering. Formatt
970
991
  | `weekdayShort` / `weekdayLong` / `monthShort` / `monthLong` | Name parts of a timestamp |
971
992
  | `sod(ms)` | Start of that day (local midnight) |
972
993
  | `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`) |
994
+ | `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
995
  | `isAllDay(ev)` / `isMultiDay(ev)` | Event classification |
975
996
  | `segmentForDay(ev, dayMs)` | The slice of a multi-day event on one day (`DaySegment`), or `null` |
976
997
  | `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. |
998
+ | `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
999
  | `pickFit(candidates, font, width, lines = 1)` | The longest candidate that fits |
979
1000
  | `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
1001
  | `fontOf(el)` | An element's computed font as a canvas font string |
981
1002
  | `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. |
1003
+ | `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
1004
 
984
1005
  ## Embeddable Widget
985
1006
 
986
- Drop into any HTML page — no build tools needed. Registers a `<day-calendar>` custom element:
1007
+ Drop into any HTML page, no build tools needed. Registers a `<day-calendar>` custom element:
987
1008
 
988
1009
  ```html
989
1010
  <script src="https://cdn.jsdelivr.net/npm/@nomideusz/svelte-calendar/widget/widget.js"></script>
@@ -1024,47 +1045,78 @@ Each event in `api` responses and `events` needs `start` and `end` as date strin
1024
1045
 
1025
1046
  ## All Props
1026
1047
 
1027
- <details>
1028
- <summary>Full Calendar props reference (<code>CalendarProps</code>)</summary>
1048
+ Every `<Calendar>` prop (`CalendarProps`), grouped by what it controls.
1049
+
1050
+ ### Data and editing
1029
1051
 
1030
1052
  | Prop | Type | Default | Description |
1031
1053
  |------|------|---------|-------------|
1032
1054
  | `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
1055
  | `readOnly` | `boolean` | `false` | Disable drag, resize, click-to-create and external drops |
1045
- | `visibleHours` | `[number, number]` | — | Crop the grid to `[startHour, endHour)` |
1056
+ | `snapInterval` | `number` | `15` | Drag snap in minutes |
1057
+ | `minDuration` | `number` | — | Minimum duration in minutes; clamps creates and resizes |
1058
+ | `maxDuration` | `number` | — | Maximum duration in minutes; clamps creates and resizes |
1059
+ | `blockedSlots` | `BlockedSlot[]` | — | `{ day?, start, end, label? }` hour ranges that can't be booked (hatched in planner views) |
1060
+ | `disabledDates` | `Date[]` | — | Dates that refuse creates/moves; existing events stay visible and clickable |
1061
+
1062
+ ### View and navigation
1063
+
1064
+ | Prop | Type | Default | Description |
1065
+ |------|------|---------|-------------|
1066
+ | `view` | `CalendarViewId` | first registered (`'day-planner'`) | Active view ID. Reactive. |
1067
+ | `views` | `readonly CalendarView[]` | `defaultViews` | View registry (the eight built-ins). Spread `defaultViews` to add your own. |
1046
1068
  | `initialDate` | `Date` | today | Date to focus at mount (an instant; read in `timezone`) |
1047
1069
  | `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
1070
  | `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]` |
1071
+ | `mobile` | `'auto' \| boolean` | `'auto'` | `'auto'` switches to mobile views below a 768px container width |
1053
1072
  | `showModePills` | `boolean` | `true` | Show the Day/Week/Month pills and the view-type pills |
1054
1073
  | `showNavigation` | `boolean` | `true` | Show prev/next/today |
1074
+
1075
+ ### Display
1076
+
1077
+ | Prop | Type | Default | Description |
1078
+ |------|------|---------|-------------|
1079
+ | `height` | `number \| 'auto'` | `600` | Height in px, or `'auto'` to grow with content (ideal for Agenda views) |
1080
+ | `visibleHours` | `[number, number]` | — | Crop the grid to `[startHour, endHour)` |
1081
+ | `hideDays` | `number[]` | — | ISO weekdays to hide (1=Mon … 7=Sun), e.g. `[6, 7]` |
1082
+ | `mondayStart` | `boolean` | `true` | Start weeks on Monday |
1055
1083
  | `showDates` | `boolean` | `true` | Show date numbers and the date label. `false` = day names only (templates, recurring schedules) |
1056
1084
  | `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
1085
  | `compact` | `boolean` | `false` | Minimal text rows in Agenda views (dot + time + title) |
1062
1086
  | `columns` | `boolean` | `false` | Timetable layout: `week-agenda` days side by side on desktop. Pairs with `equalDays`; overrides `compact` |
1087
+ | `minColumnWidth` | `number` | `110` | Minimum day-column width (px) in planner views; below the total the grid scrolls horizontally |
1088
+
1089
+ ### Theming
1090
+
1091
+ | Prop | Type | Default | Description |
1092
+ |------|------|---------|-------------|
1093
+ | `theme` | `string` | `auto` | CSS theme string of `--dt-*` properties. `auto` probes the host page. |
1094
+ | `autoTheme` | `AutoThemeOptions \| false` | `undefined` (probe) | Fine-tune auto-detection: `{ mode, accent, font }`. `false` disables probing. |
1095
+ | `borderRadius` | `number` | `0` | Corner radius in px (square by default) |
1096
+
1097
+ ### Locale and timezone
1098
+
1099
+ | Prop | Type | Default | Description |
1100
+ |------|------|---------|-------------|
1101
+ | `locale` | `string` | global default (`'en-US'`) | BCP 47 locale tag; see `setDefaultLocale` |
1102
+ | `labels` | `Partial<CalendarLabels>` | — | Per-instance UI labels, merged over the global set. Reactive. |
1103
+ | `dir` | `'ltr' \| 'rtl' \| 'auto'` | from `locale` | Text direction |
1104
+ | `timezone` | `string` | viewer's zone | IANA zone to render in. Read at mount. |
1105
+
1106
+ ### Snippets
1107
+
1108
+ | Prop | Type | Default | Description |
1109
+ |------|------|---------|-------------|
1063
1110
  | `event` | `Snippet<[TimelineEvent]>` | — | Custom event content |
1064
1111
  | `empty` | `Snippet` | — | Empty state for the day agenda, mobile day and an empty planner week |
1065
1112
  | `dayHeader` | `Snippet<[{ date, isToday, dayName }]>` | — | Custom day header |
1066
1113
  | `header` | `Snippet<[HeaderContext]>` | — | Replace the whole header (`dateLabel`, `mode`, `modes`, `switchMode`, `prev`, `next`, `goToday`, `isViewOnToday`, `focusDate`) |
1067
1114
  | `navigation` | `Snippet<[NavigationContext]>` | — | Replace prev/next/today (`prev`, `next`, `goToday`, `isViewOnToday`, `focusDate`, `mode`) |
1115
+
1116
+ ### Callbacks
1117
+
1118
+ | Prop | Type | Default | Description |
1119
+ |------|------|---------|-------------|
1068
1120
  | `oneventclick` | `(event, anchor?: DOMRect) => void` | — | Event clicked (and selected) |
1069
1121
  | `oneventcreate` | `(range: { start, end }) => void` | — | A validated new range (real instants) |
1070
1122
  | `oneventmove` | `(event, start, end) => void` | — | Drag/resize stored, or refused read-only (real instants) |
@@ -1075,8 +1127,6 @@ Each event in `api` responses and `events` needs `start` and `end` as date strin
1075
1127
  | `oneventhover` | `(event) => void` | — | Pointer enters an event |
1076
1128
  | `onerror` | `(error: Error) => void` | — | Load and commit failures |
1077
1129
 
1078
- </details>
1079
-
1080
1130
  ## Versioning
1081
1131
 
1082
1132
  The package follows [semver](https://semver.org/) from 1.0:
@@ -1084,7 +1134,7 @@ The package follows [semver](https://semver.org/) from 1.0:
1084
1134
  - The public API is exactly what the `.` and `./widget` entry points export. Deep imports into `dist/` are not supported.
1085
1135
  - `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
1136
  - 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)).
1137
+ - `CalendarLabels` gains keys in minor releases, so pass a `Partial` (see [Localization](#localization-i18n)).
1088
1138
 
1089
1139
  ## Development
1090
1140