@nomideusz/svelte-calendar 0.6.0 → 0.6.4
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 +183 -5
- package/dist/adapters/composite.d.ts +15 -0
- package/dist/adapters/composite.js +65 -0
- package/dist/adapters/index.d.ts +5 -1
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/mapped.d.ts +170 -0
- package/dist/adapters/mapped.js +343 -0
- package/dist/adapters/recurring.d.ts +11 -0
- package/dist/adapters/recurring.js +9 -13
- package/dist/adapters/types.d.ts +11 -2
- package/dist/calendar/Calendar.svelte +847 -767
- package/dist/calendar/Calendar.svelte.d.ts +14 -2
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.js +2 -0
- package/dist/core/measure.d.ts +104 -0
- package/dist/core/measure.js +195 -0
- package/dist/core/time.js +2 -2
- package/dist/core/types.d.ts +32 -0
- package/dist/engine/event-store.svelte.js +22 -7
- package/dist/headless/create-agenda.svelte.d.ts +67 -0
- package/dist/headless/create-agenda.svelte.js +139 -0
- package/dist/headless/create-calendar.svelte.d.ts +2 -0
- package/dist/headless/create-calendar.svelte.js +380 -0
- package/dist/headless/index.d.ts +4 -0
- package/dist/headless/index.js +2 -0
- package/dist/headless/types.d.ts +219 -0
- package/dist/headless/types.js +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +4 -2
- package/dist/primitives/DayHeader.svelte +103 -103
- package/dist/primitives/EmptySlot.svelte +105 -105
- package/dist/primitives/EventBlock.svelte +397 -312
- package/dist/primitives/NowIndicator.svelte +178 -178
- package/dist/primitives/TimeGutter.svelte +104 -104
- package/dist/theme/presets.js +36 -36
- package/dist/views/agenda/AgendaDay.svelte +1167 -1055
- package/dist/views/agenda/AgendaWeek.svelte +987 -924
- package/dist/views/mobile/Mobile.svelte +17 -17
- package/dist/views/mobile/MobileDay.svelte +729 -665
- package/dist/views/mobile/MobileWeek.svelte +476 -456
- package/dist/views/planner/PlannerDay.svelte +1184 -1091
- package/dist/views/planner/PlannerWeek.svelte +924 -945
- package/dist/views/shared/context.svelte.d.ts +47 -0
- package/dist/views/shared/context.svelte.js +47 -0
- package/dist/views/shared/format.d.ts +19 -0
- package/dist/views/shared/format.js +46 -0
- package/dist/views/shared/index.d.ts +4 -0
- package/dist/views/shared/index.js +2 -0
- package/dist/widget/CalendarWidget.svelte +142 -142
- package/package.json +76 -71
package/README.md
CHANGED
|
@@ -160,6 +160,37 @@ Custom day headers and hover previews:
|
|
|
160
160
|
</Calendar>
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
+
Replace the built-in navigation or the entire header chrome with your own controls:
|
|
164
|
+
|
|
165
|
+
```svelte
|
|
166
|
+
<Calendar {adapter}>
|
|
167
|
+
{#snippet navigation({ prev, next, goToday, isViewOnToday, mode })}
|
|
168
|
+
<button onclick={prev}>‹</button>
|
|
169
|
+
{#if !isViewOnToday}<button onclick={goToday}>Today</button>{/if}
|
|
170
|
+
<button onclick={next}>›</button>
|
|
171
|
+
{/snippet}
|
|
172
|
+
</Calendar>
|
|
173
|
+
|
|
174
|
+
<Calendar {adapter}>
|
|
175
|
+
{#snippet header({ dateLabel, mode, switchMode, prev, next, goToday })}
|
|
176
|
+
<nav class="my-toolbar">
|
|
177
|
+
<h2>{dateLabel}</h2>
|
|
178
|
+
<button class:active={mode === 'day'} onclick={() => switchMode('day')}>Day</button>
|
|
179
|
+
<button class:active={mode === 'week'} onclick={() => switchMode('week')}>Week</button>
|
|
180
|
+
<button onclick={prev}>←</button>
|
|
181
|
+
<button onclick={goToday}>Today</button>
|
|
182
|
+
<button onclick={next}>→</button>
|
|
183
|
+
</nav>
|
|
184
|
+
{/snippet}
|
|
185
|
+
</Calendar>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Let agenda content determine height instead of forcing a fixed box — useful when embedding inside a scrolling page:
|
|
189
|
+
|
|
190
|
+
```svelte
|
|
191
|
+
<Calendar {adapter} view="week-agenda" height="auto" compact />
|
|
192
|
+
```
|
|
193
|
+
|
|
163
194
|
## Themes
|
|
164
195
|
|
|
165
196
|
Three built-in presets:
|
|
@@ -288,8 +319,14 @@ const custom = `${neutral}; --dt-accent: #e11d48;`;
|
|
|
288
319
|
| `subtitle` | `string?` | Secondary text below the title |
|
|
289
320
|
| `tags` | `string[]?` | Small accent-colored pills |
|
|
290
321
|
| `allDay` | `boolean?` | Render as an all-day event |
|
|
322
|
+
| `location` | `string?` | Room, venue, or address (universal across domains) |
|
|
323
|
+
| `status` | `EventStatus?` | `'confirmed'` (default), `'cancelled'`, `'tentative'`, `'full'`, `'limited'` |
|
|
324
|
+
| `externalId` | `string?` | ID from an upstream system (booking platform, CRM, LMS) |
|
|
325
|
+
| `resourceId` | `string?` | Resource this event belongs to (room, instructor, court) for multi-resource views |
|
|
291
326
|
| `data` | `Record?` | Arbitrary payload for your app |
|
|
292
327
|
|
|
328
|
+
Cancelled events render with a strikethrough on the grid but remain visible so the slot isn't confused for free time.
|
|
329
|
+
|
|
293
330
|
### Auto-Coloring
|
|
294
331
|
|
|
295
332
|
Omit `color` and events are auto-assigned a vivid palette color, grouped by `category` (or `title`):
|
|
@@ -400,6 +437,74 @@ const adapter = createRestAdapter({
|
|
|
400
437
|
|
|
401
438
|
The adapter calls `GET /events?start=...&end=...`, `POST /events`, `PATCH /events/:id`, and `DELETE /events/:id`.
|
|
402
439
|
|
|
440
|
+
## Mapped Adapter
|
|
441
|
+
|
|
442
|
+
Wrap any static array of external records (yoga classes, gym 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, color assignment, tag extraction, and status coercion:
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
import { createMappedAdapter } from '@nomideusz/svelte-calendar';
|
|
446
|
+
|
|
447
|
+
const adapter = createMappedAdapter(rawClasses, {
|
|
448
|
+
fields: {
|
|
449
|
+
title: 'class_name',
|
|
450
|
+
start: 'starts_at_iso',
|
|
451
|
+
end: 'ends_at_iso',
|
|
452
|
+
subtitle: 'teacher',
|
|
453
|
+
location: 'room',
|
|
454
|
+
color: 'color',
|
|
455
|
+
externalId: 'reference_id',
|
|
456
|
+
status: 'is_cancelled', // boolean → 'cancelled' / 'confirmed'
|
|
457
|
+
tags: ['is_free', 'is_bookable_online'],
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
`start`/`end` accept ISO strings, `Date` objects, or Unix timestamps. When source records split date and time (`date: "2026-03-03"`, `startTime: "07:00"`), use `date` + `startTime` / `endTime` instead.
|
|
463
|
+
|
|
464
|
+
For full control, pass a `mapEvent` transform and skip `fields` entirely:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
const adapter = createMappedAdapter(rawData, {
|
|
468
|
+
mapEvent: (raw) => ({
|
|
469
|
+
id: raw.uid,
|
|
470
|
+
title: raw.procedure_name,
|
|
471
|
+
start: new Date(raw.scheduled_at),
|
|
472
|
+
end: new Date(raw.scheduled_end),
|
|
473
|
+
location: raw.office,
|
|
474
|
+
resourceId: raw.doctor_id,
|
|
475
|
+
}),
|
|
476
|
+
});
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Mapped adapters are read-only by default. Set `readOnly: false` and supply `onMutate: { onCreate, onUpdate, onDelete }` handlers to enable writes.
|
|
480
|
+
|
|
481
|
+
## Composite Adapter
|
|
482
|
+
|
|
483
|
+
Merge multiple adapters into one — e.g. a recurring weekly schedule combined with one-off events:
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
import {
|
|
487
|
+
Calendar,
|
|
488
|
+
createMemoryAdapter,
|
|
489
|
+
createRecurringAdapter,
|
|
490
|
+
createCompositeAdapter,
|
|
491
|
+
} from '@nomideusz/svelte-calendar';
|
|
492
|
+
|
|
493
|
+
const memory = createMemoryAdapter(oneOffEvents);
|
|
494
|
+
const recurring = createRecurringAdapter(weeklySchedule);
|
|
495
|
+
|
|
496
|
+
const adapter = createCompositeAdapter([memory, recurring]);
|
|
497
|
+
// Reads from both; writes go to memory (the primary — first adapter by default)
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
Change which adapter handles mutations with `primaryIndex`:
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
const adapter = createCompositeAdapter([recurring, memory], { primaryIndex: 1 });
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Updates and deletes fall through each adapter in order — handy when the recurring adapter generates occurrences that the primary memory adapter doesn't know about.
|
|
507
|
+
|
|
403
508
|
## Custom Adapter
|
|
404
509
|
|
|
405
510
|
Implement the `CalendarAdapter` interface to connect any data source:
|
|
@@ -415,6 +520,61 @@ const adapter: CalendarAdapter = {
|
|
|
415
520
|
};
|
|
416
521
|
```
|
|
417
522
|
|
|
523
|
+
## Headless API
|
|
524
|
+
|
|
525
|
+
For full control over rendering, skip the `<Calendar>` component and drive everything from reactive state. `createCalendar()` returns computed layouts, navigation actions, drag helpers, and raw engines — zero DOM, bring your own UI:
|
|
526
|
+
|
|
527
|
+
```svelte
|
|
528
|
+
<script lang="ts">
|
|
529
|
+
import { createCalendar, createMemoryAdapter } from '@nomideusz/svelte-calendar';
|
|
530
|
+
|
|
531
|
+
const adapter = createMemoryAdapter([/* ... */]);
|
|
532
|
+
const cal = createCalendar({ adapter, view: 'week-planner' });
|
|
533
|
+
</script>
|
|
534
|
+
|
|
535
|
+
<header>
|
|
536
|
+
<button onclick={cal.prev}>←</button>
|
|
537
|
+
<span>{cal.headerContext.dateLabel}</span>
|
|
538
|
+
<button onclick={cal.next}>→</button>
|
|
539
|
+
<button onclick={cal.goToday}>Today</button>
|
|
540
|
+
</header>
|
|
541
|
+
|
|
542
|
+
{#each cal.weeks as week}
|
|
543
|
+
<div class="week-row">
|
|
544
|
+
{#each week.days as day}
|
|
545
|
+
<div class:today={day.isToday} class:past={day.isPast}>
|
|
546
|
+
<h3>{day.dayNum}</h3>
|
|
547
|
+
{#each day.events as ev}
|
|
548
|
+
<div style:background={ev.color}>{ev.title}</div>
|
|
549
|
+
{/each}
|
|
550
|
+
</div>
|
|
551
|
+
{/each}
|
|
552
|
+
</div>
|
|
553
|
+
{/each}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
`cal.days` gives flat `HeadlessDay[]` with events attached; `cal.weeks` groups them into periods. `cal.todayQueue` returns `{ past, current, upcoming }` for today — updates every second via the built-in clock. `cal.hours` yields the visible hour numbers (cropped by `visibleHours`). Drag support is fully wired: `beginDragMove`, `beginDragCreate`, `updateDrag`, `commitDrag`, `cancelDrag`, plus `isDragging` / `dragPayload` / `dragMode` signals. Raw engines (`store`, `viewState`, `selection`, `dragState`, `clock`) are exposed for advanced cases.
|
|
557
|
+
|
|
558
|
+
For a simpler day-only list view, use `createAgenda()`:
|
|
559
|
+
|
|
560
|
+
```svelte
|
|
561
|
+
<script lang="ts">
|
|
562
|
+
import { createAgenda, createMemoryAdapter } from '@nomideusz/svelte-calendar';
|
|
563
|
+
|
|
564
|
+
const adapter = createMemoryAdapter([/* ... */]);
|
|
565
|
+
const agenda = createAgenda({ adapter });
|
|
566
|
+
</script>
|
|
567
|
+
|
|
568
|
+
<h2>{agenda.dateLabel}</h2>
|
|
569
|
+
{#each agenda.upcoming as ev}
|
|
570
|
+
<div>
|
|
571
|
+
<time>{agenda.fmtTime(ev.start)}</time>
|
|
572
|
+
<span>{ev.title}</span>
|
|
573
|
+
<small>{agenda.eta(ev)}</small>
|
|
574
|
+
</div>
|
|
575
|
+
{/each}
|
|
576
|
+
```
|
|
577
|
+
|
|
418
578
|
## Localization (i18n)
|
|
419
579
|
|
|
420
580
|
The `locale` prop controls date/time formatting (BCP 47):
|
|
@@ -489,7 +649,7 @@ const now = nowInZone('Asia/Tokyo');
|
|
|
489
649
|
|
|
490
650
|
## Embeddable Widget
|
|
491
651
|
|
|
492
|
-
Drop into any HTML page — no build tools needed:
|
|
652
|
+
Drop into any HTML page — no build tools needed. Registers a `<day-calendar>` custom element:
|
|
493
653
|
|
|
494
654
|
```html
|
|
495
655
|
<script src="https://cdn.jsdelivr.net/npm/@nomideusz/svelte-calendar/widget/widget.js"></script>
|
|
@@ -497,10 +657,25 @@ Drop into any HTML page — no build tools needed:
|
|
|
497
657
|
<day-calendar
|
|
498
658
|
api="https://myschool.com/api/events"
|
|
499
659
|
theme="neutral"
|
|
660
|
+
view="week-planner"
|
|
500
661
|
height="600"
|
|
662
|
+
locale="en-US"
|
|
663
|
+
mondaystart="true"
|
|
501
664
|
></day-calendar>
|
|
502
665
|
```
|
|
503
666
|
|
|
667
|
+
| Attribute | Description |
|
|
668
|
+
|-----------|-------------|
|
|
669
|
+
| `api` | REST endpoint — fetched as `GET {api}?start=...&end=...` |
|
|
670
|
+
| `events` | Inline JSON array of events (alternative to `api`) |
|
|
671
|
+
| `view` | Initial view ID (`week-planner`, `day-agenda`, …) |
|
|
672
|
+
| `theme` | `auto`, `neutral`, or `midnight` |
|
|
673
|
+
| `height` | Calendar height in px |
|
|
674
|
+
| `locale` | BCP 47 locale tag |
|
|
675
|
+
| `dir` | `ltr` or `rtl` |
|
|
676
|
+
| `mondaystart` | `"true"` / `"false"` — week-start day |
|
|
677
|
+
| `headers` | JSON string of extra headers to send with `api` requests |
|
|
678
|
+
|
|
504
679
|
## All Props
|
|
505
680
|
|
|
506
681
|
<details>
|
|
@@ -508,13 +683,13 @@ Drop into any HTML page — no build tools needed:
|
|
|
508
683
|
|
|
509
684
|
| Prop | Type | Default | Description |
|
|
510
685
|
|------|------|---------|-------------|
|
|
511
|
-
| `adapter` | `CalendarAdapter` | *required* | Data layer (memory, recurring, REST, or custom) |
|
|
512
|
-
| `views` | `CalendarView[]` |
|
|
686
|
+
| `adapter` | `CalendarAdapter` | *required* | Data layer (memory, recurring, mapped, composite, REST, or custom) |
|
|
687
|
+
| `views` | `CalendarView[]` | 6 built-in | Registered view components (4 desktop + 2 mobile variants) |
|
|
513
688
|
| `view` | `string` | first view | Active view ID |
|
|
514
689
|
| `theme` | `string` | `auto` | CSS theme string (`--dt-*` custom properties). `auto` probes the host page and generates matching tokens. |
|
|
515
690
|
| `autoTheme` | `AutoThemeOptions \| false` | `{}` | Fine-tune auto-detection: `{ mode, accent, font }`. Set `false` to disable probing. |
|
|
516
|
-
| `mobile` | `'auto' \| boolean` | `'auto'` | Mobile mode. `'auto'` detects via
|
|
517
|
-
| `height` | `number` | `600` |
|
|
691
|
+
| `mobile` | `'auto' \| boolean` | `'auto'` | Mobile mode. `'auto'` detects via container width (<768px). Remaps Planner→Mobile views. |
|
|
692
|
+
| `height` | `number \| 'auto'` | `600` | Height in pixels, or `'auto'` to let content grow naturally (ideal for Agenda views) |
|
|
518
693
|
| `borderRadius` | `number` | `12` | Border radius in pixels. Set to `0` for no rounding. |
|
|
519
694
|
| `locale` | `string` | `'en-US'` | BCP 47 locale tag |
|
|
520
695
|
| `dir` | `'ltr' \| 'rtl' \| 'auto'` | — | Text direction |
|
|
@@ -534,7 +709,10 @@ Drop into any HTML page — no build tools needed:
|
|
|
534
709
|
| `disabledDates` | `Date[]` | — | Dates that are fully disabled (dimmed, no interaction) |
|
|
535
710
|
| `minDuration` | `number` | — | Minimum event duration in minutes (enforced on create & resize) |
|
|
536
711
|
| `maxDuration` | `number` | — | Maximum event duration in minutes (enforced on create & resize) |
|
|
712
|
+
| `compact` | `boolean` | `false` | Minimal text-row rendering in Agenda views (dot + time + title) |
|
|
537
713
|
| `dayHeader` | `Snippet<[{ date, isToday, dayName }]>` | — | Custom day header snippet for planner/agenda views |
|
|
714
|
+
| `header` | `Snippet<[HeaderContext]>` | — | Replace entire header chrome (date label + mode pills + nav) |
|
|
715
|
+
| `navigation` | `Snippet<[NavigationContext]>` | — | Replace just the prev/next/today controls |
|
|
538
716
|
| `oneventclick` | `(event) => void` | — | Event clicked |
|
|
539
717
|
| `oneventcreate` | `(range) => void` | — | New time range selected |
|
|
540
718
|
| `oneventmove` | `(event, start, end) => void` | — | Event dragged to new time |
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CalendarAdapter } from './types.js';
|
|
2
|
+
export interface CompositeAdapterOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Index of the adapter that handles mutations (create/update/delete).
|
|
5
|
+
* Defaults to `0` (the first adapter).
|
|
6
|
+
*/
|
|
7
|
+
primaryIndex?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create a CalendarAdapter that merges events from multiple child adapters.
|
|
11
|
+
*
|
|
12
|
+
* @param adapters Array of child adapters to merge.
|
|
13
|
+
* @param options Optional configuration.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createCompositeAdapter(adapters: CalendarAdapter[], options?: CompositeAdapterOptions): CalendarAdapter;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a CalendarAdapter that merges events from multiple child adapters.
|
|
3
|
+
*
|
|
4
|
+
* @param adapters Array of child adapters to merge.
|
|
5
|
+
* @param options Optional configuration.
|
|
6
|
+
*/
|
|
7
|
+
export function createCompositeAdapter(adapters, options = {}) {
|
|
8
|
+
if (adapters.length === 0) {
|
|
9
|
+
throw new Error('createCompositeAdapter requires at least one adapter');
|
|
10
|
+
}
|
|
11
|
+
const { primaryIndex = 0 } = options;
|
|
12
|
+
if (primaryIndex < 0 || primaryIndex >= adapters.length) {
|
|
13
|
+
throw new Error(`primaryIndex ${primaryIndex} is out of range [0, ${adapters.length - 1}]`);
|
|
14
|
+
}
|
|
15
|
+
const primary = adapters[primaryIndex];
|
|
16
|
+
return {
|
|
17
|
+
async fetchEvents(range) {
|
|
18
|
+
const results = await Promise.all(adapters.map((a) => a.fetchEvents(range)));
|
|
19
|
+
// Flatten and deduplicate by id (first occurrence wins)
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
const merged = [];
|
|
22
|
+
for (const batch of results) {
|
|
23
|
+
for (const ev of batch) {
|
|
24
|
+
if (!seen.has(ev.id)) {
|
|
25
|
+
seen.add(ev.id);
|
|
26
|
+
merged.push(ev);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return merged;
|
|
31
|
+
},
|
|
32
|
+
// Create always goes to primary
|
|
33
|
+
...(primary.createEvent ? { createEvent: (event) => primary.createEvent(event) } : {}),
|
|
34
|
+
// Update/delete: try each adapter that supports the operation.
|
|
35
|
+
// This handles the case where a recurring adapter generates events
|
|
36
|
+
// that the primary (memory) adapter doesn't know about.
|
|
37
|
+
async updateEvent(id, patch) {
|
|
38
|
+
for (const adapter of adapters) {
|
|
39
|
+
if (!adapter.updateEvent)
|
|
40
|
+
continue;
|
|
41
|
+
try {
|
|
42
|
+
return await adapter.updateEvent(id, patch);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Event not in this adapter, try next
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`Event not found in any adapter: ${id}`);
|
|
49
|
+
},
|
|
50
|
+
async deleteEvent(id) {
|
|
51
|
+
for (const adapter of adapters) {
|
|
52
|
+
if (!adapter.deleteEvent)
|
|
53
|
+
continue;
|
|
54
|
+
try {
|
|
55
|
+
await adapter.deleteEvent(id);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Event not in this adapter, try next
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`Event not found in any adapter: ${id}`);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
export type { CalendarAdapter, DateRange } from './types.js';
|
|
1
|
+
export type { CalendarAdapter, WritableCalendarAdapter, DateRange } from './types.js';
|
|
2
2
|
export { createMemoryAdapter } from './memory.js';
|
|
3
3
|
export type { MemoryAdapterOptions } from './memory.js';
|
|
4
4
|
export { createRestAdapter } from './rest.js';
|
|
5
5
|
export type { RestAdapterOptions } from './rest.js';
|
|
6
6
|
export { createRecurringAdapter } from './recurring.js';
|
|
7
7
|
export type { RecurringEvent, RecurringAdapterOptions } from './recurring.js';
|
|
8
|
+
export { createMappedAdapter } from './mapped.js';
|
|
9
|
+
export type { FieldMapping, MappedAdapterOptions, MutationHandler } from './mapped.js';
|
|
10
|
+
export { createCompositeAdapter } from './composite.js';
|
|
11
|
+
export type { CompositeAdapterOptions } from './composite.js';
|
package/dist/adapters/index.js
CHANGED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapped adapter — universal adapter factory for external schedule data.
|
|
3
|
+
*
|
|
4
|
+
* Takes any array of external records (yoga classes, gym schedules,
|
|
5
|
+
* medical appointments, school timetables, etc.) and a mapping
|
|
6
|
+
* configuration that describes how to extract `TimelineEvent` fields
|
|
7
|
+
* from each record.
|
|
8
|
+
*
|
|
9
|
+
* Two usage modes:
|
|
10
|
+
* 1. **Field mapping** — declarative config mapping field names:
|
|
11
|
+
* createMappedAdapter(rawEvents, {
|
|
12
|
+
* fields: { title: 'class_name', start: 'starts_at_iso', ... }
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* 2. **Custom mapper** — full control via a transform function:
|
|
16
|
+
* createMappedAdapter(rawEvents, {
|
|
17
|
+
* mapEvent: (raw, i) => ({ id: raw.ref, title: raw.name, ... })
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* The adapter is read-only by default (no create/update/delete).
|
|
21
|
+
* Pass `readOnly: false` and supply mutation callbacks to enable writes.
|
|
22
|
+
*/
|
|
23
|
+
import type { TimelineEvent } from '../core/types.js';
|
|
24
|
+
import type { CalendarAdapter } from './types.js';
|
|
25
|
+
/**
|
|
26
|
+
* Declarative field mapping from external record keys to TimelineEvent fields.
|
|
27
|
+
*
|
|
28
|
+
* Each value is a key name (string) of the source object whose value
|
|
29
|
+
* will be used for the corresponding TimelineEvent field.
|
|
30
|
+
*
|
|
31
|
+
* For `start` and `end`, the source value can be:
|
|
32
|
+
* - an ISO 8601 string ("2026-03-03T07:00:00+01:00")
|
|
33
|
+
* - a Date object
|
|
34
|
+
* - a Unix timestamp (number, ms)
|
|
35
|
+
* - a pair of date + time strings when using `startDate`+`startTime`
|
|
36
|
+
*/
|
|
37
|
+
export interface FieldMapping {
|
|
38
|
+
/** Source key for `id`. Falls back to `reference_id`, `externalId`, or auto-generated. */
|
|
39
|
+
id?: string;
|
|
40
|
+
/** Source key for `title` (required unless `mapEvent` is used) */
|
|
41
|
+
title?: string;
|
|
42
|
+
/** Source key for a full start timestamp (ISO string, Date, or ms) */
|
|
43
|
+
start?: string;
|
|
44
|
+
/** Source key for a full end timestamp */
|
|
45
|
+
end?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Source key for a date-only string (e.g. "2026-03-03").
|
|
48
|
+
* Combined with `startTime` / `endTime` when `start`/`end` are not available.
|
|
49
|
+
*/
|
|
50
|
+
date?: string;
|
|
51
|
+
/** Source key for start time string (e.g. "07:00") — combined with `date` */
|
|
52
|
+
startTime?: string;
|
|
53
|
+
/** Source key for end time string (e.g. "08:15") — combined with `date` */
|
|
54
|
+
endTime?: string;
|
|
55
|
+
/** Source key for `color` */
|
|
56
|
+
color?: string;
|
|
57
|
+
/** Source key for `subtitle` */
|
|
58
|
+
subtitle?: string;
|
|
59
|
+
/** Source key for `location` */
|
|
60
|
+
location?: string;
|
|
61
|
+
/** Source key for `category` */
|
|
62
|
+
category?: string;
|
|
63
|
+
/** Source key for `resourceId` */
|
|
64
|
+
resourceId?: string;
|
|
65
|
+
/** Source key for `externalId` */
|
|
66
|
+
externalId?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Source key for status. The source value is coerced:
|
|
69
|
+
* - boolean `true`/`false` on an `is_cancelled`-type field → 'cancelled'/'confirmed'
|
|
70
|
+
* - string 'cancelled'/'tentative'/'confirmed' used directly
|
|
71
|
+
*/
|
|
72
|
+
status?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Source keys that should be collected as tags.
|
|
75
|
+
* Each key's value is included as a tag if truthy/non-empty.
|
|
76
|
+
* String values are used directly; booleans use the key name.
|
|
77
|
+
*/
|
|
78
|
+
tags?: string[];
|
|
79
|
+
}
|
|
80
|
+
export interface MappedAdapterOptions<T = Record<string, unknown>> {
|
|
81
|
+
/**
|
|
82
|
+
* Declarative field mapping — quick way to wire external fields
|
|
83
|
+
* to TimelineEvent properties.
|
|
84
|
+
*/
|
|
85
|
+
fields?: FieldMapping;
|
|
86
|
+
/**
|
|
87
|
+
* Custom mapper function — full control over the transformation.
|
|
88
|
+
* When provided, `fields` is ignored.
|
|
89
|
+
*/
|
|
90
|
+
mapEvent?: (raw: T, index: number) => TimelineEvent;
|
|
91
|
+
/**
|
|
92
|
+
* Fields to always include in `data` (the catch-all payload).
|
|
93
|
+
* Pass `'*'` to include all unmapped source fields.
|
|
94
|
+
* @default '*'
|
|
95
|
+
*/
|
|
96
|
+
includeData?: string[] | '*';
|
|
97
|
+
/**
|
|
98
|
+
* When true (default), ignore source colors and auto-assign from
|
|
99
|
+
* the palette grouped by category / title. This keeps the calendar
|
|
100
|
+
* visually consistent regardless of the upstream source.
|
|
101
|
+
* Set to `false` to preserve the original colors from the data.
|
|
102
|
+
* @default true
|
|
103
|
+
*/
|
|
104
|
+
autoColor?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Color palette used for auto-coloring.
|
|
107
|
+
* Defaults to VIVID_PALETTE.
|
|
108
|
+
*/
|
|
109
|
+
palette?: string[];
|
|
110
|
+
/**
|
|
111
|
+
* When true (default), create/update/delete throw an error.
|
|
112
|
+
* Set to false and provide `onMutate` to enable writes.
|
|
113
|
+
*/
|
|
114
|
+
readOnly?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Optional mutation handler for write operations.
|
|
117
|
+
* Only used when `readOnly` is false.
|
|
118
|
+
*/
|
|
119
|
+
onMutate?: MutationHandler<T>;
|
|
120
|
+
}
|
|
121
|
+
export interface MutationHandler<T = Record<string, unknown>> {
|
|
122
|
+
onCreate?: (event: Omit<TimelineEvent, 'id'>) => Promise<T>;
|
|
123
|
+
onUpdate?: (id: string, patch: Partial<TimelineEvent>) => Promise<T>;
|
|
124
|
+
onDelete?: (id: string) => Promise<void>;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Create a CalendarAdapter from an array of external records + mapping config.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* // Yoga school schedule
|
|
132
|
+
* const adapter = createMappedAdapter(yogaSchedule.events, {
|
|
133
|
+
* fields: {
|
|
134
|
+
* title: 'class_name',
|
|
135
|
+
* start: 'starts_at_iso',
|
|
136
|
+
* end: 'ends_at_iso',
|
|
137
|
+
* subtitle: 'teacher',
|
|
138
|
+
* location: 'room',
|
|
139
|
+
* color: 'color',
|
|
140
|
+
* externalId: 'reference_id',
|
|
141
|
+
* status: 'is_cancelled',
|
|
142
|
+
* tags: ['is_free', 'is_bookable_online'],
|
|
143
|
+
* },
|
|
144
|
+
* });
|
|
145
|
+
*
|
|
146
|
+
* // Medical appointments
|
|
147
|
+
* const adapter = createMappedAdapter(appointments, {
|
|
148
|
+
* fields: {
|
|
149
|
+
* title: 'procedure_name',
|
|
150
|
+
* start: 'scheduled_at',
|
|
151
|
+
* end: 'scheduled_end',
|
|
152
|
+
* subtitle: 'doctor_name',
|
|
153
|
+
* location: 'office',
|
|
154
|
+
* resourceId: 'doctor_id',
|
|
155
|
+
* },
|
|
156
|
+
* });
|
|
157
|
+
*
|
|
158
|
+
* // Full custom mapper
|
|
159
|
+
* const adapter = createMappedAdapter(rawData, {
|
|
160
|
+
* mapEvent: (raw) => ({
|
|
161
|
+
* id: raw.uid,
|
|
162
|
+
* title: `${raw.first_name} ${raw.last_name}`,
|
|
163
|
+
* start: new Date(raw.timestamp),
|
|
164
|
+
* end: new Date(raw.timestamp + raw.duration_ms),
|
|
165
|
+
* location: raw.room,
|
|
166
|
+
* }),
|
|
167
|
+
* });
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export declare function createMappedAdapter<T extends Record<string, unknown> = Record<string, unknown>>(sourceData: T[], options?: MappedAdapterOptions<T>): CalendarAdapter;
|