@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
@@ -8,7 +8,7 @@
8
8
  -->
9
9
  <script lang="ts">import { useCalendarContext } from "../shared/context.svelte.js";
10
10
  import { createClock } from "../../core/clock.svelte.js";
11
- import { DAY_MS, sod, isAllDay } from "../../core/time.js";
11
+ import { DAY_MS, sod, addDaysMs, isAllDay } from "../../core/time.js";
12
12
  import { fmtTime, weekdayShort } from "../../core/locale.js";
13
13
  let { events = [], style = "", height = null, locale, focusDate, oneventclick, selectedEventId = null } = $props();
14
14
  const ctx = useCalendarContext();
@@ -44,23 +44,41 @@ let expandedMs = $state(null);
44
44
  let focusMs = $state(null);
45
45
  let bodyEl = $state(null);
46
46
  const cellsInteractive = $derived(!!ondayclick);
47
+ /** A cell takes focus when it can act: drill down, or open its overflow. */
48
+ function isFocusable(cell) {
49
+ return !cell.isDisabled && (cellsInteractive || cell.overflow > 0);
50
+ }
51
+ /** Focusable cells by day, in grid order. */
52
+ const focusable = $derived.by(() => {
53
+ const map = new Map();
54
+ for (const row of weeks) for (const cell of row) if (isFocusable(cell)) map.set(cell.ms, cell);
55
+ return map;
56
+ });
57
+ // The roving anchor always lands on a focusable cell, so the grid keeps
58
+ // its one Tab stop even when the remembered day or today is disabled.
47
59
  const rovingMs = $derived.by(() => {
48
- if (!range) return null;
49
- const start = sod(range.start.getTime());
50
- const end = range.end.getTime();
51
- if (focusMs !== null && focusMs >= start && focusMs < end) return focusMs;
52
- if (todayMs >= start && todayMs < end) return todayMs;
53
- return start;
60
+ if (focusMs !== null && focusable.has(focusMs)) return focusMs;
61
+ if (focusable.has(todayMs)) return todayMs;
62
+ const first = focusable.keys().next();
63
+ return first.done ? null : first.value;
54
64
  });
55
65
  function moveFocus(fromMs, deltaDays) {
56
66
  if (!range) return;
57
- const target = fromMs + deltaDays * DAY_MS;
58
- if (target < sod(range.start.getTime()) || target >= range.end.getTime()) return;
67
+ const lo = sod(range.start.getTime());
68
+ const hi = range.end.getTime();
69
+ // Skip days that can't take focus, continuing in the same direction.
70
+ let target = addDaysMs(fromMs, deltaDays);
71
+ while (target >= lo && target < hi && !focusable.has(target)) target = addDaysMs(target, deltaDays);
72
+ if (target < lo || target >= hi) return;
59
73
  focusMs = target;
60
74
  const el = bodyEl?.querySelector(`[data-ms="${target}"]`);
61
75
  el?.focus();
62
76
  }
63
77
  function cellKeydown(e, cell) {
78
+ // Enter/Space on an event chip or "+N more" belong to that button (a
79
+ // chip opens its event, not the day); arrows still move between days.
80
+ const onCell = e.target === e.currentTarget;
81
+ if (!onCell && (e.key === "Enter" || e.key === " ")) return;
64
82
  switch (e.key) {
65
83
  case "ArrowRight":
66
84
  e.preventDefault();
@@ -108,7 +126,7 @@ function cellLabel(cell) {
108
126
  }
109
127
  function eventsForDay(ms) {
110
128
  const dayStart = ms;
111
- const dayEnd = ms + DAY_MS;
129
+ const dayEnd = addDaysMs(ms, 1);
112
130
  return events.filter((e) => e.start.getTime() < dayEnd && e.end.getTime() > dayStart).sort((a, b) => {
113
131
  const aAll = isAllDay(a) ? 0 : 1;
114
132
  const bAll = isAllDay(b) ? 0 : 1;
@@ -118,10 +136,10 @@ function eventsForDay(ms) {
118
136
  const weeks = $derived.by(() => {
119
137
  if (!range) return [];
120
138
  const rows = [];
121
- for (let ms = sod(range.start.getTime()); ms < range.end.getTime(); ms += 7 * DAY_MS) {
139
+ for (let ms = sod(range.start.getTime()); ms < range.end.getTime(); ms = addDaysMs(ms, 7)) {
122
140
  const row = [];
123
141
  for (let i = 0; i < 7; i++) {
124
- const cellMs = ms + i * DAY_MS;
142
+ const cellMs = addDaysMs(ms, i);
125
143
  const date = new Date(cellMs);
126
144
  const jsDay = date.getDay();
127
145
  const dayEvents = eventsForDay(cellMs);
@@ -183,13 +201,7 @@ function chipTime(e) {
183
201
  aria-label={cellLabel(cell)}
184
202
  aria-current={cell.isToday ? 'date' : undefined}
185
203
  data-ms={cell.ms}
186
- tabindex={cell.isDisabled
187
- ? undefined
188
- : cellsInteractive || cell.overflow > 0
189
- ? cell.ms === rovingMs
190
- ? 0
191
- : -1
192
- : undefined}
204
+ tabindex={isFocusable(cell) ? (cell.ms === rovingMs ? 0 : -1) : undefined}
193
205
  onclick={() => {
194
206
  if (!cell.isDisabled) ondayclick?.(cell.date);
195
207
  }}
@@ -208,7 +220,7 @@ function chipTime(e) {
208
220
  class:mg-chip--selected={ev.id === selectedEventId}
209
221
  onclick={(e) => {
210
222
  e.stopPropagation();
211
- oneventclick?.(ev);
223
+ oneventclick?.(ev, e.currentTarget.getBoundingClientRect());
212
224
  }}
213
225
  onmouseenter={() => oneventhover?.(ev)}
214
226
  >
@@ -225,7 +237,7 @@ function chipTime(e) {
225
237
  aria-label="{ev.title}{chipTime(ev) ? `, ${chipTime(ev)}` : ''}"
226
238
  onclick={(e) => {
227
239
  e.stopPropagation();
228
- oneventclick?.(ev);
240
+ oneventclick?.(ev, e.currentTarget.getBoundingClientRect());
229
241
  }}
230
242
  onmouseenter={() => oneventhover?.(ev)}
231
243
  >
@@ -5,7 +5,7 @@ interface Props {
5
5
  height?: number | null;
6
6
  locale?: string;
7
7
  focusDate?: Date;
8
- oneventclick?: (event: TimelineEvent) => void;
8
+ oneventclick?: (event: TimelineEvent, anchor?: DOMRect) => void;
9
9
  selectedEventId?: string | null;
10
10
  readOnly?: boolean;
11
11
  [key: string]: unknown;