@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
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { VIVID_PALETTE } from '../core/palette.js';
|
|
2
|
+
// ── Internal helpers ────────────────────────────────────
|
|
3
|
+
let counter = 0;
|
|
4
|
+
function uid() {
|
|
5
|
+
return `mapped-${Date.now()}-${++counter}`;
|
|
6
|
+
}
|
|
7
|
+
/** Parse a date/time value into a Date object */
|
|
8
|
+
function toDate(value) {
|
|
9
|
+
if (value instanceof Date)
|
|
10
|
+
return value;
|
|
11
|
+
if (typeof value === 'number')
|
|
12
|
+
return new Date(value);
|
|
13
|
+
if (typeof value === 'string') {
|
|
14
|
+
const d = new Date(value);
|
|
15
|
+
if (isNaN(d.getTime())) {
|
|
16
|
+
throw new Error(`Cannot parse date: "${value}"`);
|
|
17
|
+
}
|
|
18
|
+
return d;
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`Cannot convert to Date: ${typeof value}`);
|
|
21
|
+
}
|
|
22
|
+
/** Combine "YYYY-MM-DD" date and "HH:MM" time into a Date */
|
|
23
|
+
function combineDateAndTime(dateStr, timeStr) {
|
|
24
|
+
// Handle various date formats
|
|
25
|
+
const datePart = dateStr.includes('T') ? dateStr.split('T')[0] : dateStr;
|
|
26
|
+
const d = new Date(`${datePart}T${timeStr}:00`);
|
|
27
|
+
if (isNaN(d.getTime())) {
|
|
28
|
+
throw new Error(`Cannot combine date "${dateStr}" and time "${timeStr}"`);
|
|
29
|
+
}
|
|
30
|
+
return d;
|
|
31
|
+
}
|
|
32
|
+
/** Coerce a source value into an EventStatus */
|
|
33
|
+
function coerceStatus(value, fieldName) {
|
|
34
|
+
if (typeof value === 'string') {
|
|
35
|
+
const lower = value.toLowerCase();
|
|
36
|
+
if (lower === 'cancelled' || lower === 'canceled')
|
|
37
|
+
return 'cancelled';
|
|
38
|
+
if (lower === 'tentative')
|
|
39
|
+
return 'tentative';
|
|
40
|
+
if (lower === 'full')
|
|
41
|
+
return 'full';
|
|
42
|
+
if (lower === 'limited')
|
|
43
|
+
return 'limited';
|
|
44
|
+
return 'confirmed';
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === 'boolean') {
|
|
47
|
+
// E.g. is_cancelled: true → 'cancelled'
|
|
48
|
+
const lowerField = fieldName.toLowerCase();
|
|
49
|
+
if (lowerField.includes('cancel'))
|
|
50
|
+
return value ? 'cancelled' : 'confirmed';
|
|
51
|
+
if (lowerField.includes('tentative'))
|
|
52
|
+
return value ? 'tentative' : 'confirmed';
|
|
53
|
+
return value ? 'confirmed' : 'tentative';
|
|
54
|
+
}
|
|
55
|
+
return 'confirmed';
|
|
56
|
+
}
|
|
57
|
+
/** Parse an RGB string like "rgb(62, 101, 255)" to a hex color */
|
|
58
|
+
function normalizeColor(value) {
|
|
59
|
+
if (!value)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (typeof value !== 'string')
|
|
62
|
+
return undefined;
|
|
63
|
+
const str = value.trim();
|
|
64
|
+
// Already hex
|
|
65
|
+
if (str.startsWith('#'))
|
|
66
|
+
return str;
|
|
67
|
+
// rgb(r, g, b) format
|
|
68
|
+
const rgbMatch = str.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
|
|
69
|
+
if (rgbMatch) {
|
|
70
|
+
const r = parseInt(rgbMatch[1], 10);
|
|
71
|
+
const g = parseInt(rgbMatch[2], 10);
|
|
72
|
+
const b = parseInt(rgbMatch[3], 10);
|
|
73
|
+
return `#${((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1)}`;
|
|
74
|
+
}
|
|
75
|
+
// CSS named colors and other formats — pass through
|
|
76
|
+
return str;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Collect unmapped fields into the `data` payload.
|
|
80
|
+
*/
|
|
81
|
+
function collectData(raw, mappedKeys, include) {
|
|
82
|
+
const data = {};
|
|
83
|
+
const keys = include === '*' ? Object.keys(raw) : include;
|
|
84
|
+
for (const key of keys) {
|
|
85
|
+
if (!mappedKeys.has(key) && raw[key] !== undefined) {
|
|
86
|
+
data[key] = raw[key];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return data;
|
|
90
|
+
}
|
|
91
|
+
// ── Factory ─────────────────────────────────────────────
|
|
92
|
+
/**
|
|
93
|
+
* Create a CalendarAdapter from an array of external records + mapping config.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* // Yoga school schedule
|
|
98
|
+
* const adapter = createMappedAdapter(yogaSchedule.events, {
|
|
99
|
+
* fields: {
|
|
100
|
+
* title: 'class_name',
|
|
101
|
+
* start: 'starts_at_iso',
|
|
102
|
+
* end: 'ends_at_iso',
|
|
103
|
+
* subtitle: 'teacher',
|
|
104
|
+
* location: 'room',
|
|
105
|
+
* color: 'color',
|
|
106
|
+
* externalId: 'reference_id',
|
|
107
|
+
* status: 'is_cancelled',
|
|
108
|
+
* tags: ['is_free', 'is_bookable_online'],
|
|
109
|
+
* },
|
|
110
|
+
* });
|
|
111
|
+
*
|
|
112
|
+
* // Medical appointments
|
|
113
|
+
* const adapter = createMappedAdapter(appointments, {
|
|
114
|
+
* fields: {
|
|
115
|
+
* title: 'procedure_name',
|
|
116
|
+
* start: 'scheduled_at',
|
|
117
|
+
* end: 'scheduled_end',
|
|
118
|
+
* subtitle: 'doctor_name',
|
|
119
|
+
* location: 'office',
|
|
120
|
+
* resourceId: 'doctor_id',
|
|
121
|
+
* },
|
|
122
|
+
* });
|
|
123
|
+
*
|
|
124
|
+
* // Full custom mapper
|
|
125
|
+
* const adapter = createMappedAdapter(rawData, {
|
|
126
|
+
* mapEvent: (raw) => ({
|
|
127
|
+
* id: raw.uid,
|
|
128
|
+
* title: `${raw.first_name} ${raw.last_name}`,
|
|
129
|
+
* start: new Date(raw.timestamp),
|
|
130
|
+
* end: new Date(raw.timestamp + raw.duration_ms),
|
|
131
|
+
* location: raw.room,
|
|
132
|
+
* }),
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function createMappedAdapter(sourceData, options = {}) {
|
|
137
|
+
const { fields, mapEvent: customMapper, includeData = '*', autoColor = true, palette = VIVID_PALETTE, readOnly = true, } = options;
|
|
138
|
+
// Build the internal event list
|
|
139
|
+
const events = [];
|
|
140
|
+
const colorAssignments = new Map();
|
|
141
|
+
let colorIndex = 0;
|
|
142
|
+
function resolveColor(ev) {
|
|
143
|
+
// When autoColor is on, always assign from palette (ignore source color)
|
|
144
|
+
if (!autoColor && ev.color)
|
|
145
|
+
return ev.color;
|
|
146
|
+
const key = ev.category ?? ev.title;
|
|
147
|
+
if (!colorAssignments.has(key)) {
|
|
148
|
+
colorAssignments.set(key, palette[colorIndex % palette.length]);
|
|
149
|
+
colorIndex++;
|
|
150
|
+
}
|
|
151
|
+
return colorAssignments.get(key);
|
|
152
|
+
}
|
|
153
|
+
function withColor(ev) {
|
|
154
|
+
const color = resolveColor(ev);
|
|
155
|
+
return color ? { ...ev, color } : ev;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Map a single raw record using the declarative field mapping.
|
|
159
|
+
*/
|
|
160
|
+
function mapWithFields(raw, index) {
|
|
161
|
+
const f = fields;
|
|
162
|
+
const r = raw;
|
|
163
|
+
// ── ID ──
|
|
164
|
+
const id = f.id
|
|
165
|
+
? String(r[f.id] ?? '')
|
|
166
|
+
: String(r['id'] ?? r['reference_id'] ?? r['externalId'] ?? r['_id'] ?? uid());
|
|
167
|
+
// ── Title ──
|
|
168
|
+
const titleKey = f.title ?? 'title';
|
|
169
|
+
const title = String(r[titleKey] ?? r['name'] ?? r['class_name'] ?? `Event ${index + 1}`);
|
|
170
|
+
// ── Start / End ──
|
|
171
|
+
let start;
|
|
172
|
+
let end;
|
|
173
|
+
if (f.start && r[f.start] !== undefined) {
|
|
174
|
+
start = toDate(r[f.start]);
|
|
175
|
+
}
|
|
176
|
+
else if (f.date && f.startTime && r[f.date] && r[f.startTime]) {
|
|
177
|
+
start = combineDateAndTime(String(r[f.date]), String(r[f.startTime]));
|
|
178
|
+
}
|
|
179
|
+
else if (r['start'] !== undefined) {
|
|
180
|
+
start = toDate(r['start']);
|
|
181
|
+
}
|
|
182
|
+
else if (r['starts_at_iso'] !== undefined) {
|
|
183
|
+
start = toDate(r['starts_at_iso']);
|
|
184
|
+
}
|
|
185
|
+
else if (r['start_time'] !== undefined && r['date'] !== undefined) {
|
|
186
|
+
start = combineDateAndTime(String(r['date']), String(r['start_time']));
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
throw new Error(`Cannot determine start time for record ${index}: no matching field found`);
|
|
190
|
+
}
|
|
191
|
+
if (f.end && r[f.end] !== undefined) {
|
|
192
|
+
end = toDate(r[f.end]);
|
|
193
|
+
}
|
|
194
|
+
else if (f.date && f.endTime && r[f.date] && r[f.endTime]) {
|
|
195
|
+
end = combineDateAndTime(String(r[f.date]), String(r[f.endTime]));
|
|
196
|
+
}
|
|
197
|
+
else if (r['end'] !== undefined) {
|
|
198
|
+
end = toDate(r['end']);
|
|
199
|
+
}
|
|
200
|
+
else if (r['ends_at_iso'] !== undefined) {
|
|
201
|
+
end = toDate(r['ends_at_iso']);
|
|
202
|
+
}
|
|
203
|
+
else if (r['end_time'] !== undefined && r['date'] !== undefined) {
|
|
204
|
+
end = combineDateAndTime(String(r['date']), String(r['end_time']));
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// Default: 1 hour after start
|
|
208
|
+
end = new Date(start.getTime() + 60 * 60 * 1000);
|
|
209
|
+
}
|
|
210
|
+
// ── Optional fields ──
|
|
211
|
+
const color = normalizeColor(r[f.color ?? 'color']);
|
|
212
|
+
const subtitle = f.subtitle ? String(r[f.subtitle] ?? '') || undefined : undefined;
|
|
213
|
+
const location = f.location
|
|
214
|
+
? String(r[f.location] ?? '') || undefined
|
|
215
|
+
: r['room'] ? String(r['room']) : r['location'] ? String(r['location']) : undefined;
|
|
216
|
+
const category = f.category ? String(r[f.category] ?? '') || undefined : undefined;
|
|
217
|
+
const resourceId = f.resourceId ? String(r[f.resourceId] ?? '') || undefined : undefined;
|
|
218
|
+
const externalId = f.externalId
|
|
219
|
+
? String(r[f.externalId] ?? '') || undefined
|
|
220
|
+
: r['reference_id'] ? String(r['reference_id']) : undefined;
|
|
221
|
+
// ── Status ──
|
|
222
|
+
let status;
|
|
223
|
+
if (f.status && r[f.status] !== undefined) {
|
|
224
|
+
status = coerceStatus(r[f.status], f.status);
|
|
225
|
+
}
|
|
226
|
+
else if (r['is_cancelled'] !== undefined) {
|
|
227
|
+
status = coerceStatus(r['is_cancelled'], 'is_cancelled');
|
|
228
|
+
}
|
|
229
|
+
// ── Tags ──
|
|
230
|
+
let tags;
|
|
231
|
+
if (f.tags && f.tags.length > 0) {
|
|
232
|
+
tags = [];
|
|
233
|
+
for (const tagKey of f.tags) {
|
|
234
|
+
const val = r[tagKey];
|
|
235
|
+
if (val === true) {
|
|
236
|
+
// Convert key name to human-readable tag
|
|
237
|
+
tags.push(tagKey.replace(/^is_/, '').replace(/_/g, ' '));
|
|
238
|
+
}
|
|
239
|
+
else if (typeof val === 'string' && val) {
|
|
240
|
+
tags.push(val);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (tags.length === 0)
|
|
244
|
+
tags = undefined;
|
|
245
|
+
}
|
|
246
|
+
// ── Data (unmapped fields) ──
|
|
247
|
+
const mappedKeys = new Set();
|
|
248
|
+
for (const v of Object.values(f)) {
|
|
249
|
+
if (typeof v === 'string')
|
|
250
|
+
mappedKeys.add(v);
|
|
251
|
+
if (Array.isArray(v))
|
|
252
|
+
v.forEach((k) => mappedKeys.add(k));
|
|
253
|
+
}
|
|
254
|
+
// Also mark auto-detected keys as mapped
|
|
255
|
+
for (const k of ['id', 'title', 'name', 'class_name', 'start', 'end',
|
|
256
|
+
'starts_at_iso', 'ends_at_iso', 'start_time', 'end_time', 'date',
|
|
257
|
+
'color', 'room', 'location', 'reference_id', 'is_cancelled']) {
|
|
258
|
+
mappedKeys.add(k);
|
|
259
|
+
}
|
|
260
|
+
const data = collectData(r, mappedKeys, includeData);
|
|
261
|
+
const ev = { id, title, start, end };
|
|
262
|
+
if (color)
|
|
263
|
+
ev.color = color;
|
|
264
|
+
if (subtitle)
|
|
265
|
+
ev.subtitle = subtitle;
|
|
266
|
+
if (location)
|
|
267
|
+
ev.location = location;
|
|
268
|
+
if (category)
|
|
269
|
+
ev.category = category;
|
|
270
|
+
if (resourceId)
|
|
271
|
+
ev.resourceId = resourceId;
|
|
272
|
+
if (externalId)
|
|
273
|
+
ev.externalId = externalId;
|
|
274
|
+
if (status && status !== 'confirmed')
|
|
275
|
+
ev.status = status;
|
|
276
|
+
if (tags)
|
|
277
|
+
ev.tags = tags;
|
|
278
|
+
if (Object.keys(data).length > 0)
|
|
279
|
+
ev.data = data;
|
|
280
|
+
return ev;
|
|
281
|
+
}
|
|
282
|
+
// ── Build events array ──
|
|
283
|
+
for (let i = 0; i < sourceData.length; i++) {
|
|
284
|
+
const mapper = customMapper ?? mapWithFields;
|
|
285
|
+
const ev = withColor(mapper(sourceData[i], i));
|
|
286
|
+
events.push(ev);
|
|
287
|
+
}
|
|
288
|
+
// ── Adapter interface ──
|
|
289
|
+
function overlaps(ev, range) {
|
|
290
|
+
return ev.start < range.end && ev.end > range.start;
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
async fetchEvents(range) {
|
|
294
|
+
return events.filter((ev) => overlaps(ev, range));
|
|
295
|
+
},
|
|
296
|
+
async createEvent(data) {
|
|
297
|
+
if (readOnly) {
|
|
298
|
+
throw new Error('Mapped adapter is read-only. Set readOnly: false and provide onMutate to enable writes.');
|
|
299
|
+
}
|
|
300
|
+
const handler = options.onMutate?.onCreate;
|
|
301
|
+
if (handler) {
|
|
302
|
+
const raw = await handler(data);
|
|
303
|
+
const mapper = customMapper ?? mapWithFields;
|
|
304
|
+
const ev = withColor(mapper(raw, events.length));
|
|
305
|
+
events.push(ev);
|
|
306
|
+
return ev;
|
|
307
|
+
}
|
|
308
|
+
// Fallback: create locally
|
|
309
|
+
const ev = { ...data, id: uid() };
|
|
310
|
+
events.push(withColor(ev));
|
|
311
|
+
return ev;
|
|
312
|
+
},
|
|
313
|
+
async updateEvent(id, patch) {
|
|
314
|
+
if (readOnly) {
|
|
315
|
+
throw new Error('Mapped adapter is read-only. Set readOnly: false and provide onMutate to enable writes.');
|
|
316
|
+
}
|
|
317
|
+
const idx = events.findIndex((e) => e.id === id);
|
|
318
|
+
if (idx < 0)
|
|
319
|
+
throw new Error(`Event not found: ${id}`);
|
|
320
|
+
const handler = options.onMutate?.onUpdate;
|
|
321
|
+
if (handler) {
|
|
322
|
+
const raw = await handler(id, patch);
|
|
323
|
+
const mapper = customMapper ?? mapWithFields;
|
|
324
|
+
const ev = withColor(mapper(raw, idx));
|
|
325
|
+
events[idx] = ev;
|
|
326
|
+
return ev;
|
|
327
|
+
}
|
|
328
|
+
events[idx] = { ...events[idx], ...patch, id };
|
|
329
|
+
return events[idx];
|
|
330
|
+
},
|
|
331
|
+
async deleteEvent(id) {
|
|
332
|
+
if (readOnly) {
|
|
333
|
+
throw new Error('Mapped adapter is read-only. Set readOnly: false and provide onMutate to enable writes.');
|
|
334
|
+
}
|
|
335
|
+
const handler = options.onMutate?.onDelete;
|
|
336
|
+
if (handler)
|
|
337
|
+
await handler(id);
|
|
338
|
+
const idx = events.findIndex((e) => e.id === id);
|
|
339
|
+
if (idx >= 0)
|
|
340
|
+
events.splice(idx, 1);
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
@@ -57,12 +57,23 @@ export interface RecurringEvent {
|
|
|
57
57
|
tags?: string[];
|
|
58
58
|
/** Category for grouping */
|
|
59
59
|
category?: string;
|
|
60
|
+
/** Location or room name */
|
|
61
|
+
location?: string;
|
|
62
|
+
/** Resource ID for multi-resource views */
|
|
63
|
+
resourceId?: string;
|
|
60
64
|
/** Arbitrary payload */
|
|
61
65
|
data?: Record<string, unknown>;
|
|
62
66
|
}
|
|
63
67
|
export interface RecurringAdapterOptions {
|
|
64
68
|
/** Start weeks on Monday (default: true) */
|
|
65
69
|
mondayStart?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Custom color palette for auto-coloring events.
|
|
72
|
+
* Pass `generatePalette('#yourAccent')` to get theme-harmonious colors,
|
|
73
|
+
* or provide your own array of hex strings.
|
|
74
|
+
* Defaults to the built-in vivid palette.
|
|
75
|
+
*/
|
|
76
|
+
palette?: string[];
|
|
66
77
|
}
|
|
67
78
|
/**
|
|
68
79
|
* Create a CalendarAdapter that projects recurring events onto concrete
|
|
@@ -68,7 +68,9 @@ function createConcreteEvent(rec, date) {
|
|
|
68
68
|
category: rec.category,
|
|
69
69
|
subtitle: rec.subtitle,
|
|
70
70
|
tags: rec.tags,
|
|
71
|
-
|
|
71
|
+
location: rec.location,
|
|
72
|
+
resourceId: rec.resourceId,
|
|
73
|
+
data: { ...rec.data, recurringId: rec.id, readOnly: true },
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
// ── Count → until resolution ────────────────────────────
|
|
@@ -230,8 +232,9 @@ const PALETTE = VIVID_PALETTE;
|
|
|
230
232
|
* Read-only by default — create/update/delete throw.
|
|
231
233
|
*/
|
|
232
234
|
export function createRecurringAdapter(schedule, options = {}) {
|
|
233
|
-
const { mondayStart = true } = options;
|
|
234
|
-
|
|
235
|
+
const { mondayStart = true, palette } = options;
|
|
236
|
+
const colors = palette?.length ? palette : PALETTE;
|
|
237
|
+
// Auto-color: assign from palette by category/title
|
|
235
238
|
const colorAssignments = new Map();
|
|
236
239
|
let colorIndex = 0;
|
|
237
240
|
for (const rec of schedule) {
|
|
@@ -239,7 +242,7 @@ export function createRecurringAdapter(schedule, options = {}) {
|
|
|
239
242
|
continue;
|
|
240
243
|
const key = rec.category ?? rec.title;
|
|
241
244
|
if (!colorAssignments.has(key)) {
|
|
242
|
-
colorAssignments.set(key,
|
|
245
|
+
colorAssignments.set(key, colors[colorIndex % colors.length]);
|
|
243
246
|
colorIndex++;
|
|
244
247
|
}
|
|
245
248
|
}
|
|
@@ -282,14 +285,7 @@ export function createRecurringAdapter(schedule, options = {}) {
|
|
|
282
285
|
}
|
|
283
286
|
return events;
|
|
284
287
|
},
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
},
|
|
288
|
-
async updateEvent() {
|
|
289
|
-
throw new Error('createRecurringAdapter is read-only. Use a memory or REST adapter for mutations.');
|
|
290
|
-
},
|
|
291
|
-
async deleteEvent() {
|
|
292
|
-
throw new Error('createRecurringAdapter is read-only. Use a memory or REST adapter for mutations.');
|
|
293
|
-
},
|
|
288
|
+
// Read-only adapter: CRUD methods intentionally omitted.
|
|
289
|
+
// Use createMemoryAdapter or createRestAdapter for mutations.
|
|
294
290
|
};
|
|
295
291
|
}
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -15,9 +15,18 @@ export interface CalendarAdapter {
|
|
|
15
15
|
/** Fetch events that overlap the given date range */
|
|
16
16
|
fetchEvents(range: DateRange): Promise<TimelineEvent[]>;
|
|
17
17
|
/** Create a new event, return it with a server-assigned ID */
|
|
18
|
-
createEvent(event: Omit<TimelineEvent, 'id'>): Promise<TimelineEvent>;
|
|
18
|
+
createEvent?(event: Omit<TimelineEvent, 'id'>): Promise<TimelineEvent>;
|
|
19
19
|
/** Update an event, return the full updated event */
|
|
20
|
-
updateEvent(id: string, patch: Partial<TimelineEvent>): Promise<TimelineEvent>;
|
|
20
|
+
updateEvent?(id: string, patch: Partial<TimelineEvent>): Promise<TimelineEvent>;
|
|
21
21
|
/** Delete an event by ID */
|
|
22
|
+
deleteEvent?(id: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A CalendarAdapter that supports full CRUD operations.
|
|
26
|
+
* Use this type when you need to guarantee write support.
|
|
27
|
+
*/
|
|
28
|
+
export interface WritableCalendarAdapter extends CalendarAdapter {
|
|
29
|
+
createEvent(event: Omit<TimelineEvent, 'id'>): Promise<TimelineEvent>;
|
|
30
|
+
updateEvent(id: string, patch: Partial<TimelineEvent>): Promise<TimelineEvent>;
|
|
22
31
|
deleteEvent(id: string): Promise<void>;
|
|
23
32
|
}
|