@nomideusz/svelte-calendar 0.20.1 → 1.0.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.
Files changed (69) hide show
  1. package/README.md +502 -267
  2. package/dist/adapters/composite.d.ts +12 -1
  3. package/dist/adapters/composite.js +109 -47
  4. package/dist/adapters/errors.d.ts +22 -0
  5. package/dist/adapters/errors.js +32 -0
  6. package/dist/adapters/index.d.ts +1 -0
  7. package/dist/adapters/index.js +1 -0
  8. package/dist/adapters/jmap.js +26 -17
  9. package/dist/adapters/mapped.d.ts +2 -2
  10. package/dist/adapters/mapped.js +18 -9
  11. package/dist/adapters/memory.d.ts +1 -1
  12. package/dist/adapters/memory.js +5 -3
  13. package/dist/adapters/recurring.d.ts +1 -1
  14. package/dist/adapters/recurring.js +43 -25
  15. package/dist/adapters/rest.d.ts +2 -2
  16. package/dist/adapters/rest.js +9 -4
  17. package/dist/calendar/Calendar.svelte +122 -78
  18. package/dist/calendar/Calendar.svelte.d.ts +69 -14
  19. package/dist/calendar/index.d.ts +2 -2
  20. package/dist/calendar/index.js +1 -1
  21. package/dist/core/clock.svelte.js +3 -2
  22. package/dist/core/locale.d.ts +11 -1
  23. package/dist/core/locale.js +16 -16
  24. package/dist/core/time.d.ts +9 -0
  25. package/dist/core/time.js +12 -5
  26. package/dist/core/timezone.d.ts +7 -3
  27. package/dist/core/timezone.js +10 -4
  28. package/dist/engine/event-store.svelte.js +94 -57
  29. package/dist/engine/view-state.svelte.d.ts +5 -1
  30. package/dist/engine/view-state.svelte.js +8 -6
  31. package/dist/headless/create-agenda.svelte.d.ts +8 -1
  32. package/dist/headless/create-agenda.svelte.js +36 -18
  33. package/dist/headless/create-calendar.svelte.js +57 -21
  34. package/dist/headless/create-range-agenda.svelte.d.ts +7 -0
  35. package/dist/headless/create-range-agenda.svelte.js +38 -17
  36. package/dist/headless/index.d.ts +1 -0
  37. package/dist/index.d.ts +10 -7
  38. package/dist/index.js +8 -5
  39. package/dist/primitives/DayHeader.svelte +3 -2
  40. package/dist/primitives/DayHeader.svelte.d.ts +1 -1
  41. package/dist/primitives/EmptySlot.svelte +5 -2
  42. package/dist/primitives/EventBlock.svelte +21 -14
  43. package/dist/primitives/FloatingPanel.svelte +110 -15
  44. package/dist/primitives/FloatingPanel.svelte.d.ts +7 -5
  45. package/dist/primitives/NowIndicator.svelte +11 -4
  46. package/dist/primitives/TimeGutter.svelte +10 -6
  47. package/dist/primitives/index.d.ts +1 -0
  48. package/dist/text-fit.d.ts +8 -2
  49. package/dist/views/agenda/AgendaDay.svelte +43 -35
  50. package/dist/views/agenda/AgendaWeek.svelte +75 -38
  51. package/dist/views/index.d.ts +2 -0
  52. package/dist/views/index.js +2 -0
  53. package/dist/views/mobile/MobileDay.svelte +99 -42
  54. package/dist/views/mobile/MobileWeek.svelte +31 -15
  55. package/dist/views/mobile/swipe.js +6 -1
  56. package/dist/views/month/MonthGrid.svelte +33 -21
  57. package/dist/views/month/MonthGrid.svelte.d.ts +1 -1
  58. package/dist/views/planner/PlannerScroll.svelte +438 -164
  59. package/dist/views/planner/PlannerWeek.svelte +150 -50
  60. package/dist/views/shared/context.svelte.d.ts +1 -1
  61. package/package.json +24 -9
  62. package/widget/widget.js +10000 -9520
  63. package/dist/assets/favicon.svg +0 -9
  64. package/dist/widget/CalendarWidget.svelte +0 -114
  65. package/dist/widget/CalendarWidget.svelte.d.ts +0 -37
  66. package/dist/widget/index.d.ts +0 -1
  67. package/dist/widget/index.js +0 -1
  68. package/dist/widget/widget.d.ts +0 -10
  69. package/dist/widget/widget.js +0 -96
@@ -1,14 +1,25 @@
1
1
  import type { CalendarAdapter } from './types.js';
2
2
  export interface CompositeAdapterOptions {
3
3
  /**
4
- * Index of the adapter that handles mutations (create/update/delete).
4
+ * Index of the adapter that handles `createEvent`.
5
5
  * Defaults to `0` (the first adapter).
6
6
  */
7
7
  primaryIndex?: number;
8
+ /**
9
+ * Called when one child's `fetchEvents` fails. With it, the others'
10
+ * events still load and the failure is yours to show; without it, one
11
+ * failing child fails the whole load (the calendar's `onerror`).
12
+ */
13
+ onFetchError?: (error: unknown, adapterIndex: number) => void;
8
14
  }
9
15
  /**
10
16
  * Create a CalendarAdapter that merges events from multiple child adapters.
11
17
  *
18
+ * Updates and deletes go to the child that returned the event. An event from
19
+ * a child that cannot write it is refused with a `read-only` error — the
20
+ * calendar then hands the move to the host (`oneventmove`) instead of
21
+ * dropping it.
22
+ *
12
23
  * @param adapters Array of child adapters to merge.
13
24
  * @param options Optional configuration.
14
25
  */
@@ -1,6 +1,13 @@
1
+ import { CalendarReadOnlyError, EventNotFoundError, isNotFoundError } from './errors.js';
2
+ import { overlapsRange } from '../core/time.js';
1
3
  /**
2
4
  * Create a CalendarAdapter that merges events from multiple child adapters.
3
5
  *
6
+ * Updates and deletes go to the child that returned the event. An event from
7
+ * a child that cannot write it is refused with a `read-only` error — the
8
+ * calendar then hands the move to the host (`oneventmove`) instead of
9
+ * dropping it.
10
+ *
4
11
  * @param adapters Array of child adapters to merge.
5
12
  * @param options Optional configuration.
6
13
  */
@@ -8,63 +15,118 @@ export function createCompositeAdapter(adapters, options = {}) {
8
15
  if (adapters.length === 0) {
9
16
  throw new Error('createCompositeAdapter requires at least one adapter');
10
17
  }
11
- const { primaryIndex = 0 } = options;
18
+ const { primaryIndex = 0, onFetchError } = options;
12
19
  if (primaryIndex < 0 || primaryIndex >= adapters.length) {
13
20
  throw new Error(`primaryIndex ${primaryIndex} is out of range [0, ${adapters.length - 1}]`);
14
21
  }
15
22
  const primary = adapters[primaryIndex];
23
+ /** Each child's last successful fetch, shown while it is failing. */
24
+ const lastGood = [];
25
+ /** Which child returned each event id — where its writes go. */
26
+ const owner = new Map();
27
+ // Flatten and deduplicate by id (first occurrence wins)
28
+ function mergeBatches(results) {
29
+ const seen = new Set();
30
+ const merged = [];
31
+ results.forEach((batch, i) => {
32
+ for (const ev of batch ?? []) {
33
+ if (seen.has(ev.id))
34
+ continue;
35
+ seen.add(ev.id);
36
+ owner.set(ev.id, adapters[i]);
37
+ merged.push(ev);
38
+ }
39
+ });
40
+ return merged;
41
+ }
42
+ /**
43
+ * Run a write on the child that owns `id`. An id no fetch has returned
44
+ * (created elsewhere, or a host calling the adapter directly) is offered
45
+ * to each writable child in turn; only a "not found" moves on.
46
+ */
47
+ async function routeWrite(id, op, run) {
48
+ const known = owner.get(id);
49
+ if (known) {
50
+ if (!known[op])
51
+ throw new CalendarReadOnlyError(`read-only: the adapter that holds ${id} cannot write it`);
52
+ return run(known);
53
+ }
54
+ let readOnlyChild = false;
55
+ for (const adapter of adapters) {
56
+ if (!adapter[op]) {
57
+ readOnlyChild = true;
58
+ continue;
59
+ }
60
+ try {
61
+ return await run(adapter);
62
+ }
63
+ catch (e) {
64
+ // A refusal or a real failure is an answer; only "not mine" asks on.
65
+ if (!isNotFoundError(e))
66
+ throw e;
67
+ }
68
+ }
69
+ if (readOnlyChild)
70
+ throw new CalendarReadOnlyError(`read-only: no writable adapter holds ${id}`);
71
+ throw new EventNotFoundError(id);
72
+ }
73
+ const canUpdate = adapters.some((a) => a.updateEvent);
74
+ const canDelete = adapters.some((a) => a.deleteEvent);
75
+ const allSync = adapters.every((a) => a.fetchEventsSync);
16
76
  return {
17
77
  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
- }
78
+ if (!onFetchError) {
79
+ return mergeBatches(await Promise.all(adapters.map((a) => a.fetchEvents(range))));
29
80
  }
30
- return merged;
81
+ const settled = await Promise.allSettled(adapters.map((a) => a.fetchEvents(range)));
82
+ return mergeBatches(settled.map((r, i) => {
83
+ if (r.status === 'fulfilled') {
84
+ lastGood[i] = r.value;
85
+ return r.value;
86
+ }
87
+ onFetchError(r.reason, i);
88
+ // The store treats a load as the whole truth for its range: keep
89
+ // what this child last gave, or its events would vanish.
90
+ return (lastGood[i] ?? []).filter((ev) => overlapsRange(ev, range.start, range.end));
91
+ }));
31
92
  },
93
+ // Answers at once only when every child does; otherwise the async path.
94
+ ...(allSync
95
+ ? {
96
+ fetchEventsSync(range) {
97
+ const results = [];
98
+ for (const a of adapters) {
99
+ const r = a.fetchEventsSync(range);
100
+ if (!r)
101
+ return undefined;
102
+ results.push(r);
103
+ }
104
+ return mergeBatches(results);
105
+ },
106
+ }
107
+ : {}),
32
108
  // 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 (e) {
45
- // A refusal is an answer: the adapter that owns this event
46
- // cannot write it, and asking the others would turn that
47
- // into a meaningless "not found" the host cannot act on.
48
- if (e instanceof Error && e.message.includes('read-only'))
49
- throw e;
50
- // Otherwise: not this adapter's event, try the next.
51
- }
109
+ ...(primary.createEvent
110
+ ? {
111
+ async createEvent(event) {
112
+ const created = await primary.createEvent(event);
113
+ owner.set(created.id, primary);
114
+ return created;
115
+ },
52
116
  }
53
- throw new Error(`Event not found in any adapter: ${id}`);
54
- },
55
- async deleteEvent(id) {
56
- for (const adapter of adapters) {
57
- if (!adapter.deleteEvent)
58
- continue;
59
- try {
60
- await adapter.deleteEvent(id);
61
- return;
62
- }
63
- catch {
64
- // Event not in this adapter, try next
65
- }
117
+ : {}),
118
+ ...(canUpdate
119
+ ? {
120
+ updateEvent: (id, patch) => routeWrite(id, 'updateEvent', (a) => a.updateEvent(id, patch)),
66
121
  }
67
- throw new Error(`Event not found in any adapter: ${id}`);
68
- },
122
+ : {}),
123
+ ...(canDelete
124
+ ? {
125
+ async deleteEvent(id) {
126
+ await routeWrite(id, 'deleteEvent', (a) => a.deleteEvent(id));
127
+ owner.delete(id);
128
+ },
129
+ }
130
+ : {}),
69
131
  };
70
132
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The two refusals the calendar tells apart when a write fails.
3
+ *
4
+ * Throw `CalendarReadOnlyError` from an adapter that holds an event but
5
+ * cannot store a change to it: the Calendar keeps the dragged block where it
6
+ * was dropped and hands the move to the host (`oneventmove`), which owns the
7
+ * write. Throw `EventNotFoundError` when an id is not yours: a composite
8
+ * adapter then asks its next child, and a drag of it ends quietly.
9
+ *
10
+ * Plain `Error`s whose message contains "read-only" / "not found" are read
11
+ * the same way, so adapters written before these classes keep working.
12
+ */
13
+ export declare class CalendarReadOnlyError extends Error {
14
+ constructor(message?: string);
15
+ }
16
+ export declare class EventNotFoundError extends Error {
17
+ constructor(id: string);
18
+ }
19
+ /** Whether an adapter refused a write it could not store. */
20
+ export declare function isReadOnlyError(e: unknown): boolean;
21
+ /** Whether an adapter answered "not my event". */
22
+ export declare function isNotFoundError(e: unknown): boolean;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The two refusals the calendar tells apart when a write fails.
3
+ *
4
+ * Throw `CalendarReadOnlyError` from an adapter that holds an event but
5
+ * cannot store a change to it: the Calendar keeps the dragged block where it
6
+ * was dropped and hands the move to the host (`oneventmove`), which owns the
7
+ * write. Throw `EventNotFoundError` when an id is not yours: a composite
8
+ * adapter then asks its next child, and a drag of it ends quietly.
9
+ *
10
+ * Plain `Error`s whose message contains "read-only" / "not found" are read
11
+ * the same way, so adapters written before these classes keep working.
12
+ */
13
+ export class CalendarReadOnlyError extends Error {
14
+ constructor(message = 'read-only: this adapter cannot store changes') {
15
+ super(message.includes('read-only') ? message : `read-only: ${message}`);
16
+ this.name = 'CalendarReadOnlyError';
17
+ }
18
+ }
19
+ export class EventNotFoundError extends Error {
20
+ constructor(id) {
21
+ super(`Event not found: ${id}`);
22
+ this.name = 'EventNotFoundError';
23
+ }
24
+ }
25
+ /** Whether an adapter refused a write it could not store. */
26
+ export function isReadOnlyError(e) {
27
+ return e instanceof CalendarReadOnlyError || (e instanceof Error && /read-only/i.test(e.message));
28
+ }
29
+ /** Whether an adapter answered "not my event". */
30
+ export function isNotFoundError(e) {
31
+ return e instanceof EventNotFoundError || (e instanceof Error && /not found/i.test(e.message));
32
+ }
@@ -12,3 +12,4 @@ export { withInitialEvents } from './seeded.js';
12
12
  export type { CompositeAdapterOptions } from './composite.js';
13
13
  export { createJmapAdapter } from './jmap.js';
14
14
  export type { JmapClient, JmapCalendarAdapterOptions } from './jmap.js';
15
+ export { CalendarReadOnlyError, EventNotFoundError, isReadOnlyError, isNotFoundError } from './errors.js';
@@ -5,3 +5,4 @@ export { createMappedAdapter } from './mapped.js';
5
5
  export { createCompositeAdapter } from './composite.js';
6
6
  export { withInitialEvents } from './seeded.js';
7
7
  export { createJmapAdapter } from './jmap.js';
8
+ export { CalendarReadOnlyError, EventNotFoundError, isReadOnlyError, isNotFoundError } from './errors.js';
@@ -1,37 +1,46 @@
1
+ import { addDaysMs } from '../core/time.js';
1
2
  export function createJmapAdapter(client, options) {
2
3
  const getAccountId = options.getAccountId;
3
4
  const timeZone = options.timeZone ?? 'Etc/UTC';
5
+ /**
6
+ * Parse an RFC 8601/JSCalendar duration into its calendar part (weeks and
7
+ * days — whole days on the wall clock, which a DST change makes 23 or 25
8
+ * hours long) and its exact part (hours, minutes, seconds).
9
+ */
4
10
  function parseIsoDuration(duration) {
5
- const match = /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/i.exec(duration);
11
+ const match = /^P(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/i.exec(duration);
6
12
  if (!match)
7
- return 0;
8
- const days = Number(match[1] ?? 0);
9
- const hours = Number(match[2] ?? 0);
10
- const minutes = Number(match[3] ?? 0);
11
- const seconds = Number(match[4] ?? 0);
12
- return (((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000;
13
+ return { days: 0, ms: 0 };
14
+ const weeks = Number(match[1] ?? 0);
15
+ const days = Number(match[2] ?? 0);
16
+ const hours = Number(match[3] ?? 0);
17
+ const minutes = Number(match[4] ?? 0);
18
+ const seconds = Number(match[5] ?? 0);
19
+ return { days: weeks * 7 + days, ms: ((hours * 60 + minutes) * 60 + seconds) * 1000 };
13
20
  }
14
21
  function parseEventStart(event) {
22
+ // An all-day event is floating: it sits on its date wherever the viewer
23
+ // is. utcStart would pin it to the server zone's midnight instead.
24
+ if (event.start && event.showWithoutTime) {
25
+ const [datePart] = event.start.split('T');
26
+ const [year, month, day] = datePart.split('-').map(Number);
27
+ return new Date(year, month - 1, day);
28
+ }
15
29
  if (event.utcStart)
16
30
  return new Date(event.utcStart);
17
- if (event.start) {
18
- if (event.showWithoutTime) {
19
- const [datePart] = event.start.split('T');
20
- const [year, month, day] = datePart.split('-').map(Number);
21
- return new Date(year, month - 1, day);
22
- }
31
+ if (event.start)
23
32
  return new Date(event.start);
24
- }
25
33
  return new Date();
26
34
  }
27
35
  function parseEventEnd(event, start) {
28
- if (event.utcEnd)
36
+ if (event.utcEnd && !event.showWithoutTime)
29
37
  return new Date(event.utcEnd);
30
38
  if (event.duration) {
31
- return new Date(start.getTime() + parseIsoDuration(event.duration));
39
+ const { days, ms } = parseIsoDuration(event.duration);
40
+ return new Date(addDaysMs(start.getTime(), days) + ms);
32
41
  }
33
42
  if (event.showWithoutTime) {
34
- return new Date(start.getFullYear(), start.getMonth(), start.getDate(), 23, 59, 59, 999);
43
+ return new Date(addDaysMs(start.getTime(), 1));
35
44
  }
36
45
  return new Date(start.getTime() + 60 * 60 * 1000);
37
46
  }
@@ -106,7 +106,7 @@ export interface MappedAdapterOptions<T = Record<string, unknown>> {
106
106
  * Color palette used for auto-coloring.
107
107
  * Defaults to VIVID_PALETTE.
108
108
  */
109
- palette?: string[];
109
+ palette?: readonly string[];
110
110
  /**
111
111
  * When true (default), create/update/delete throw an error.
112
112
  * Set to false and provide `onMutate` to enable writes.
@@ -167,4 +167,4 @@ export interface MutationHandler<T = Record<string, unknown>> {
167
167
  * });
168
168
  * ```
169
169
  */
170
- export declare function createMappedAdapter<T extends Record<string, unknown> = Record<string, unknown>>(sourceData: T[], options?: MappedAdapterOptions<T>): CalendarAdapter;
170
+ export declare function createMappedAdapter<T extends object = Record<string, unknown>>(sourceData: T[], options?: MappedAdapterOptions<T>): CalendarAdapter;
@@ -1,4 +1,6 @@
1
1
  import { VIVID_PALETTE } from '../core/palette.js';
2
+ import { overlapsRange } from '../core/time.js';
3
+ import { CalendarReadOnlyError, EventNotFoundError } from './errors.js';
2
4
  // ── Internal helpers ────────────────────────────────────
3
5
  let counter = 0;
4
6
  function uid() {
@@ -23,7 +25,10 @@ function toDate(value) {
23
25
  function combineDateAndTime(dateStr, timeStr) {
24
26
  // Handle various date formats
25
27
  const datePart = dateStr.includes('T') ? dateStr.split('T')[0] : dateStr;
26
- const d = new Date(`${datePart}T${timeStr}:00`);
28
+ // "7:00", "07:00" and "07:00:00" all mean the same wall-clock time.
29
+ const t = /^(\d{1,2}):(\d{2})(?::(\d{2}))?/.exec(timeStr.trim());
30
+ const time = t ? `${t[1].padStart(2, '0')}:${t[2]}:${t[3] ?? '00'}` : timeStr;
31
+ const d = new Date(`${datePart}T${time}`);
27
32
  if (isNaN(d.getTime())) {
28
33
  throw new Error(`Cannot combine date "${dateStr}" and time "${timeStr}"`);
29
34
  }
@@ -158,11 +163,11 @@ export function createMappedAdapter(sourceData, options = {}) {
158
163
  * Map a single raw record using the declarative field mapping.
159
164
  */
160
165
  function mapWithFields(raw, index) {
161
- const f = fields;
166
+ const f = fields ?? {};
162
167
  const r = raw;
163
168
  // ── ID ──
164
- const id = f.id
165
- ? String(r[f.id] ?? '')
169
+ const id = f.id && r[f.id] != null
170
+ ? String(r[f.id])
166
171
  : String(r['id'] ?? r['reference_id'] ?? r['externalId'] ?? r['_id'] ?? uid());
167
172
  // ── Title ──
168
173
  const titleKey = f.title ?? 'title';
@@ -287,15 +292,19 @@ export function createMappedAdapter(sourceData, options = {}) {
287
292
  }
288
293
  // ── Adapter interface ──
289
294
  function overlaps(ev, range) {
290
- return ev.start < range.end && ev.end > range.start;
295
+ return overlapsRange(ev, range.start, range.end);
291
296
  }
292
297
  return {
298
+ // The records are in memory: answer at once, so a server render holds them.
299
+ fetchEventsSync(range) {
300
+ return events.filter((ev) => overlaps(ev, range));
301
+ },
293
302
  async fetchEvents(range) {
294
303
  return events.filter((ev) => overlaps(ev, range));
295
304
  },
296
305
  async createEvent(data) {
297
306
  if (readOnly) {
298
- throw new Error('Mapped adapter is read-only. Set readOnly: false and provide onMutate to enable writes.');
307
+ throw new CalendarReadOnlyError('Mapped adapter is read-only. Set readOnly: false and provide onMutate to enable writes.');
299
308
  }
300
309
  const handler = options.onMutate?.onCreate;
301
310
  if (handler) {
@@ -312,11 +321,11 @@ export function createMappedAdapter(sourceData, options = {}) {
312
321
  },
313
322
  async updateEvent(id, patch) {
314
323
  if (readOnly) {
315
- throw new Error('Mapped adapter is read-only. Set readOnly: false and provide onMutate to enable writes.');
324
+ throw new CalendarReadOnlyError('Mapped adapter is read-only. Set readOnly: false and provide onMutate to enable writes.');
316
325
  }
317
326
  const idx = events.findIndex((e) => e.id === id);
318
327
  if (idx < 0)
319
- throw new Error(`Event not found: ${id}`);
328
+ throw new EventNotFoundError(id);
320
329
  const handler = options.onMutate?.onUpdate;
321
330
  if (handler) {
322
331
  const raw = await handler(id, patch);
@@ -330,7 +339,7 @@ export function createMappedAdapter(sourceData, options = {}) {
330
339
  },
331
340
  async deleteEvent(id) {
332
341
  if (readOnly) {
333
- throw new Error('Mapped adapter is read-only. Set readOnly: false and provide onMutate to enable writes.');
342
+ throw new CalendarReadOnlyError('Mapped adapter is read-only. Set readOnly: false and provide onMutate to enable writes.');
334
343
  }
335
344
  const handler = options.onMutate?.onDelete;
336
345
  if (handler)
@@ -18,6 +18,6 @@ export interface MemoryAdapterOptions {
18
18
  * Defaults to VIVID_PALETTE. Pass `generatePalette(accent)` to
19
19
  * make event colors adapt to your theme.
20
20
  */
21
- palette?: string[];
21
+ palette?: readonly string[];
22
22
  }
23
23
  export declare function createMemoryAdapter(initial?: TimelineEvent[], options?: MemoryAdapterOptions): CalendarAdapter;
@@ -1,4 +1,6 @@
1
1
  import { VIVID_PALETTE } from '../core/palette.js';
2
+ import { overlapsRange } from '../core/time.js';
3
+ import { EventNotFoundError } from './errors.js';
2
4
  let counter = 0;
3
5
  function uid() {
4
6
  return `mem-${Date.now()}-${++counter}`;
@@ -24,7 +26,7 @@ export function createMemoryAdapter(initial = [], options) {
24
26
  return color ? { ...ev, color } : ev;
25
27
  }
26
28
  function overlaps(ev, range) {
27
- return ev.start < range.end && ev.end > range.start;
29
+ return overlapsRange(ev, range.start, range.end);
28
30
  }
29
31
  const fetchEventsSync = (range) => events.filter((ev) => overlaps(ev, range)).map(withColor);
30
32
  return {
@@ -40,14 +42,14 @@ export function createMemoryAdapter(initial = [], options) {
40
42
  async updateEvent(id, patch) {
41
43
  const idx = events.findIndex((e) => e.id === id);
42
44
  if (idx < 0)
43
- throw new Error(`Event not found: ${id}`);
45
+ throw new EventNotFoundError(id);
44
46
  events[idx] = { ...events[idx], ...patch, id };
45
47
  return withColor(events[idx]);
46
48
  },
47
49
  async deleteEvent(id) {
48
50
  const idx = events.findIndex((e) => e.id === id);
49
51
  if (idx < 0)
50
- throw new Error(`Event not found: ${id}`);
52
+ throw new EventNotFoundError(id);
51
53
  events.splice(idx, 1);
52
54
  },
53
55
  };
@@ -79,7 +79,7 @@ export interface RecurringAdapterOptions {
79
79
  * or provide your own array of hex strings.
80
80
  * Defaults to the built-in vivid palette.
81
81
  */
82
- palette?: string[];
82
+ palette?: readonly string[];
83
83
  /**
84
84
  * Let projected occurrences be dragged (default: `false`).
85
85
  *