@rapidmx/web-client 0.14.1 → 0.15.1

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 (171) hide show
  1. package/apps/admin/domains/[uid].tsx +3 -2
  2. package/apps/shared/appearance/AppearanceForm.tsx +9 -1
  3. package/apps/shared/calendar/bookingPlugin.ts +100 -0
  4. package/apps/shared/calendar/calendarLiveUpdates.ts +27 -0
  5. package/apps/shared/components/admin/layout/AdminShell.tsx +37 -5
  6. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +3 -1
  7. package/apps/shared/components/admin/settings/PluginsManager.tsx +1545 -1491
  8. package/apps/shared/components/calendar/AppointmentQuickForm.tsx +327 -0
  9. package/apps/shared/components/calendar/DescriptionEditor.tsx +274 -0
  10. package/apps/shared/components/calendar/EventDescriptionView.tsx +70 -0
  11. package/apps/shared/components/calendar/EventDetails.tsx +401 -0
  12. package/apps/shared/components/calendar/EventEditor.tsx +640 -0
  13. package/apps/shared/components/calendar/EventExpandedForm.tsx +210 -0
  14. package/apps/shared/components/calendar/EventFormParts.tsx +622 -0
  15. package/apps/shared/components/calendar/EventModal.tsx +126 -866
  16. package/apps/shared/components/calendar/EventQuickForm.tsx +116 -0
  17. package/apps/shared/components/calendar/EventShell.tsx +202 -0
  18. package/apps/shared/components/calendar/FindATime.tsx +379 -0
  19. package/apps/shared/components/calendar/LazyDescriptionEditor.tsx +70 -0
  20. package/apps/shared/components/calendar/MonthView.tsx +15 -3
  21. package/apps/shared/components/calendar/QuickCreateFaces.tsx +87 -0
  22. package/apps/shared/components/calendar/QuickCreateTabs.tsx +81 -0
  23. package/apps/shared/components/calendar/QuickFaceFrame.tsx +81 -0
  24. package/apps/shared/components/calendar/RecurrenceEditor.tsx +10 -5
  25. package/apps/shared/components/calendar/RequestChangeForm.tsx +271 -0
  26. package/apps/shared/components/calendar/SplitDayView.tsx +6 -5
  27. package/apps/shared/components/calendar/TaskQuickForm.tsx +289 -0
  28. package/apps/shared/components/calendar/TimeGridView.tsx +8 -6
  29. package/apps/shared/components/calendar/eventDialogFields.ts +86 -0
  30. package/apps/shared/components/calendar/eventForm.ts +108 -0
  31. package/apps/shared/components/calendar/eventFormat.ts +183 -0
  32. package/apps/shared/components/calendar/findTimeGrid.ts +90 -0
  33. package/apps/shared/components/calendar/swipeNavigation.ts +14 -0
  34. package/apps/shared/components/layout/UserMenu.tsx +80 -28
  35. package/apps/shared/components/mail/ConversationList.tsx +100 -48
  36. package/apps/shared/components/mail/ConversationThreadPane.tsx +490 -486
  37. package/apps/shared/components/mail/InviteCard.tsx +387 -0
  38. package/apps/shared/components/mail/MessageDetailPane.tsx +24 -2
  39. package/apps/shared/components/mail/SwipeRow.tsx +120 -0
  40. package/apps/shared/components/mail/compose/ComposeWindow.tsx +7 -3
  41. package/apps/shared/components/mail/invite/InviteRowChip.tsx +104 -0
  42. package/apps/shared/components/mail/invite/InviteRsvpPopover.tsx +271 -0
  43. package/apps/shared/components/mail/invite/InviteTimeline.tsx +200 -0
  44. package/apps/shared/components/mail/invite/ProposeTimeForm.tsx +109 -0
  45. package/apps/shared/components/mail/invite/inviteFormat.ts +110 -0
  46. package/apps/shared/components/mail/invite/inviteStore.ts +114 -0
  47. package/apps/shared/components/mail/layout/MailShell.tsx +522 -490
  48. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
  49. package/apps/shared/gestures/useEnterSlide.ts +54 -0
  50. package/apps/shared/gestures/useSwipe.ts +114 -0
  51. package/apps/shared/gestures/useSwipeSlide.ts +113 -0
  52. package/apps/shared/gestures/useWheelPaging.ts +107 -0
  53. package/apps/shared/mail/newMailNotifications.ts +171 -183
  54. package/apps/shared/mail/useNewMailNotifications.ts +178 -178
  55. package/apps/shared/notifications/preferences.ts +36 -0
  56. package/apps/shared/notifications/store.ts +560 -551
  57. package/apps/www/_routes.ts +1 -0
  58. package/apps/www/calendar/index.tsx +139 -37
  59. package/apps/www/index.tsx +273 -126
  60. package/apps/www/messages/[uid].tsx +31 -4
  61. package/apps/www/settings/profile/index.tsx +248 -0
  62. package/dist/apps/admin/domains/[uid].js +4 -2
  63. package/dist/apps/shared/appearance/AppearanceForm.js +9 -1
  64. package/dist/apps/shared/calendar/bookingPlugin.d.ts +50 -0
  65. package/dist/apps/shared/calendar/bookingPlugin.js +57 -0
  66. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -0
  67. package/dist/apps/shared/calendar/calendarLiveUpdates.js +23 -0
  68. package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
  69. package/dist/apps/shared/components/admin/layout/AdminShell.js +7 -3
  70. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +3 -1
  71. package/dist/apps/shared/components/admin/settings/PluginsManager.js +40 -6
  72. package/dist/apps/shared/components/calendar/AppointmentQuickForm.d.ts +47 -0
  73. package/dist/apps/shared/components/calendar/AppointmentQuickForm.js +135 -0
  74. package/dist/apps/shared/components/calendar/DescriptionEditor.d.ts +26 -0
  75. package/dist/apps/shared/components/calendar/DescriptionEditor.js +192 -0
  76. package/dist/apps/shared/components/calendar/EventDescriptionView.d.ts +14 -0
  77. package/dist/apps/shared/components/calendar/EventDescriptionView.js +39 -0
  78. package/dist/apps/shared/components/calendar/EventDetails.d.ts +33 -0
  79. package/dist/apps/shared/components/calendar/EventDetails.js +137 -0
  80. package/dist/apps/shared/components/calendar/EventEditor.d.ts +59 -0
  81. package/dist/apps/shared/components/calendar/EventEditor.js +549 -0
  82. package/dist/apps/shared/components/calendar/EventExpandedForm.d.ts +14 -0
  83. package/dist/apps/shared/components/calendar/EventExpandedForm.js +44 -0
  84. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +69 -0
  85. package/dist/apps/shared/components/calendar/EventFormParts.js +202 -0
  86. package/dist/apps/shared/components/calendar/EventModal.d.ts +26 -13
  87. package/dist/apps/shared/components/calendar/EventModal.js +57 -459
  88. package/dist/apps/shared/components/calendar/EventQuickForm.d.ts +18 -0
  89. package/dist/apps/shared/components/calendar/EventQuickForm.js +25 -0
  90. package/dist/apps/shared/components/calendar/EventShell.d.ts +64 -0
  91. package/dist/apps/shared/components/calendar/EventShell.js +134 -0
  92. package/dist/apps/shared/components/calendar/FindATime.d.ts +18 -0
  93. package/dist/apps/shared/components/calendar/FindATime.js +190 -0
  94. package/dist/apps/shared/components/calendar/LazyDescriptionEditor.d.ts +8 -0
  95. package/dist/apps/shared/components/calendar/LazyDescriptionEditor.js +44 -0
  96. package/dist/apps/shared/components/calendar/MonthView.d.ts +5 -1
  97. package/dist/apps/shared/components/calendar/MonthView.js +11 -5
  98. package/dist/apps/shared/components/calendar/QuickCreateFaces.d.ts +30 -0
  99. package/dist/apps/shared/components/calendar/QuickCreateFaces.js +44 -0
  100. package/dist/apps/shared/components/calendar/QuickCreateTabs.d.ts +24 -0
  101. package/dist/apps/shared/components/calendar/QuickCreateTabs.js +46 -0
  102. package/dist/apps/shared/components/calendar/QuickFaceFrame.d.ts +25 -0
  103. package/dist/apps/shared/components/calendar/QuickFaceFrame.js +19 -0
  104. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
  105. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +2 -2
  106. package/dist/apps/shared/components/calendar/RequestChangeForm.d.ts +36 -0
  107. package/dist/apps/shared/components/calendar/RequestChangeForm.js +138 -0
  108. package/dist/apps/shared/components/calendar/SplitDayView.d.ts +3 -2
  109. package/dist/apps/shared/components/calendar/SplitDayView.js +3 -2
  110. package/dist/apps/shared/components/calendar/TaskQuickForm.d.ts +40 -0
  111. package/dist/apps/shared/components/calendar/TaskQuickForm.js +101 -0
  112. package/dist/apps/shared/components/calendar/TimeGridView.d.ts +4 -2
  113. package/dist/apps/shared/components/calendar/TimeGridView.js +3 -2
  114. package/dist/apps/shared/components/calendar/eventDialogFields.d.ts +28 -0
  115. package/dist/apps/shared/components/calendar/eventDialogFields.js +59 -0
  116. package/dist/apps/shared/components/calendar/eventForm.d.ts +98 -0
  117. package/dist/apps/shared/components/calendar/eventForm.js +1 -0
  118. package/dist/apps/shared/components/calendar/eventFormat.d.ts +67 -0
  119. package/dist/apps/shared/components/calendar/eventFormat.js +163 -0
  120. package/dist/apps/shared/components/calendar/findTimeGrid.d.ts +30 -0
  121. package/dist/apps/shared/components/calendar/findTimeGrid.js +83 -0
  122. package/dist/apps/shared/components/calendar/swipeNavigation.d.ts +9 -0
  123. package/dist/apps/shared/components/calendar/swipeNavigation.js +8 -0
  124. package/dist/apps/shared/components/layout/UserMenu.d.ts +10 -8
  125. package/dist/apps/shared/components/layout/UserMenu.js +70 -23
  126. package/dist/apps/shared/components/mail/ConversationList.d.ts +12 -1
  127. package/dist/apps/shared/components/mail/ConversationList.js +21 -4
  128. package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +4 -1
  129. package/dist/apps/shared/components/mail/InviteCard.d.ts +20 -0
  130. package/dist/apps/shared/components/mail/InviteCard.js +150 -0
  131. package/dist/apps/shared/components/mail/MessageDetailPane.js +13 -2
  132. package/dist/apps/shared/components/mail/SwipeRow.d.ts +22 -0
  133. package/dist/apps/shared/components/mail/SwipeRow.js +71 -0
  134. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +8 -3
  135. package/dist/apps/shared/components/mail/invite/InviteRowChip.d.ts +23 -0
  136. package/dist/apps/shared/components/mail/invite/InviteRowChip.js +46 -0
  137. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.d.ts +20 -0
  138. package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.js +141 -0
  139. package/dist/apps/shared/components/mail/invite/InviteTimeline.d.ts +27 -0
  140. package/dist/apps/shared/components/mail/invite/InviteTimeline.js +120 -0
  141. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.d.ts +17 -0
  142. package/dist/apps/shared/components/mail/invite/ProposeTimeForm.js +45 -0
  143. package/dist/apps/shared/components/mail/invite/inviteFormat.d.ts +28 -0
  144. package/dist/apps/shared/components/mail/invite/inviteFormat.js +94 -0
  145. package/dist/apps/shared/components/mail/invite/inviteStore.d.ts +28 -0
  146. package/dist/apps/shared/components/mail/invite/inviteStore.js +95 -0
  147. package/dist/apps/shared/components/mail/layout/MailShell.d.ts +5 -0
  148. package/dist/apps/shared/components/mail/layout/MailShell.js +14 -3
  149. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
  150. package/dist/apps/shared/gestures/useEnterSlide.d.ts +14 -0
  151. package/dist/apps/shared/gestures/useEnterSlide.js +47 -0
  152. package/dist/apps/shared/gestures/useSwipe.d.ts +45 -0
  153. package/dist/apps/shared/gestures/useSwipe.js +90 -0
  154. package/dist/apps/shared/gestures/useSwipeSlide.d.ts +40 -0
  155. package/dist/apps/shared/gestures/useSwipeSlide.js +95 -0
  156. package/dist/apps/shared/gestures/useWheelPaging.d.ts +32 -0
  157. package/dist/apps/shared/gestures/useWheelPaging.js +85 -0
  158. package/dist/apps/shared/mail/newMailNotifications.d.ts +3 -8
  159. package/dist/apps/shared/mail/newMailNotifications.js +3 -12
  160. package/dist/apps/shared/mail/useNewMailNotifications.js +3 -2
  161. package/dist/apps/shared/notifications/preferences.d.ts +13 -0
  162. package/dist/apps/shared/notifications/preferences.js +36 -0
  163. package/dist/apps/shared/notifications/store.d.ts +1 -0
  164. package/dist/apps/shared/notifications/store.js +7 -23
  165. package/dist/apps/www/_routes.js +1 -0
  166. package/dist/apps/www/calendar/index.js +76 -6
  167. package/dist/apps/www/index.js +140 -39
  168. package/dist/apps/www/messages/[uid].js +13 -5
  169. package/dist/apps/www/settings/profile/index.d.ts +4 -0
  170. package/dist/apps/www/settings/profile/index.js +124 -0
  171. package/package.json +2 -2
@@ -28,6 +28,7 @@ export const wwwRoutes: readonly RouteDefinition[] = [
28
28
  { path: "/settings/filters/:uid", active: "settings", load: () => import("./settings/filters/[uid].js") },
29
29
  { path: "/settings/labels", active: "settings", load: () => import("./settings/labels/index.js") },
30
30
  { path: "/settings/privacy", active: "settings", load: () => import("./settings/privacy/index.js") },
31
+ { path: "/settings/profile", active: "settings", load: () => import("./settings/profile/index.js") },
31
32
  { path: "/settings/read-receipts", active: "settings", load: () => import("./settings/read-receipts/index.js") },
32
33
  { path: "/settings/sharing", active: "settings", load: () => import("./settings/sharing/index.js") },
33
34
  { path: "/settings/signatures", active: "settings", load: () => import("./settings/signatures/index.js") },
@@ -3,7 +3,7 @@
3
3
  // SPDX-License-Identifier: MPL-2.0
4
4
  ///////////////////////////////////////////////////////////////////////////////
5
5
  import { routedPage } from "../_routedPage.js";
6
- import React, { useEffect, useMemo, useRef, useState } from "react";
6
+ import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
7
7
  import { DndContext, DragEndEvent, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core";
8
8
  import { HiOutlineBars3 } from "react-icons/hi2";
9
9
  import {
@@ -21,7 +21,9 @@ import {
21
21
  startOfWeek,
22
22
  } from "date-fns";
23
23
  import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
24
- import { CalendarEvent, listCalendarEvents } from "@rapidmx/react-shared/calendar/calendarApi.js";
24
+ import useIsMobile from "@rapidmx/react-shared/util/useIsMobile.js";
25
+ import { CalendarEvent, getCalendarEvent, listCalendarEvents } from "@rapidmx/react-shared/calendar/calendarApi.js";
26
+ import { getPushClient } from "@rapidmx/react-shared/mail/pushClient.js";
25
27
  import { moveOccurrence, resizeOccurrenceEnd } from "@rapidmx/react-shared/calendar/calendarMutations.js";
26
28
  import { resolveDragAction } from "@rapidmx/react-shared/calendar/calendarDragIds.js";
27
29
  import { createFolder } from "@rapidmx/react-shared/mail/mailApi.js";
@@ -31,21 +33,28 @@ import CalendarShell, { CalendarShellProps, useCalendarShell } from "../../share
31
33
  import CalendarListSidebar from "../../shared/components/calendar/CalendarListSidebar.js";
32
34
  import { useWritableMailboxes } from "../../shared/components/mail/writableMailboxes.js";
33
35
  import EventModal from "../../shared/components/calendar/EventModal.js";
36
+ import { EventAnchor, anchorOf } from "../../shared/components/calendar/EventShell.js";
34
37
  import MiniDatePicker from "@rapidmx/react-shared/components/pickers/MiniDatePicker.js";
35
38
  import MonthView from "../../shared/components/calendar/MonthView.js";
36
39
  import SplitDayView from "../../shared/components/calendar/SplitDayView.js";
37
40
  import TimeGridView from "../../shared/components/calendar/TimeGridView.js";
38
41
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
39
42
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
43
+ import { SWIPE_PERIOD_SHIFT } from "../../shared/components/calendar/swipeNavigation.js";
44
+ import { useSwipeSlide } from "../../shared/gestures/useSwipeSlide.js";
45
+ import { useEnterSlide } from "../../shared/gestures/useEnterSlide.js";
46
+ import { useWheelPaging, type WheelEdge } from "../../shared/gestures/useWheelPaging.js";
40
47
  import { SHORTCUTS, ShortcutDef } from "../../shared/keyboard/keymap.js";
41
48
  import { useShortcut } from "../../shared/keyboard/useShortcut.js";
42
49
  import { useShortcutProps } from "../../shared/keyboard/useShortcutProps.js";
43
50
  import { notifyApiError } from "../../shared/notifications/apiErrors.js";
51
+ import { bookingSettingsHref } from "../../shared/calendar/bookingPlugin.js";
52
+ import { redactedEventUidOf } from "../../shared/calendar/calendarLiveUpdates.js";
44
53
 
45
54
  function CalendarPage(props: CalendarShellProps) {
46
55
  return (
47
56
  <CalendarShell {...props}>
48
- <CalendarContent userUid={props.userUid} />
57
+ <CalendarContent userUid={props.userUid} bookingHref={bookingSettingsHref(props.pluginNav)} />
49
58
  </CalendarShell>
50
59
  );
51
60
  }
@@ -90,9 +99,13 @@ interface ModalState {
90
99
  /** Overrides the default calendar a new event is created into (e.g. the column clicked in Split
91
100
  * view) — ignored when editing an existing occurrence, which always keeps its own `folderUid`. */
92
101
  targetFolderUid?: string;
102
+ /** What was clicked to start a new event, for the quick-create popover to open beside (none: centered near the top). */
103
+ anchor?: EventAnchor;
104
+ /** A new event that starts as an all-day one (a day of the month view was clicked). */
105
+ initialAllDay?: boolean;
93
106
  }
94
107
 
95
- function CalendarContent({ userUid }: { userUid?: string }) {
108
+ function CalendarContent({ userUid, bookingHref }: { userUid?: string; bookingHref?: string }) {
96
109
  const { mailboxUid, folderUid, calendarFolders, mailboxCalendars, mailboxes, reloadFolders, colorFor } = useCalendarShell();
97
110
  // `PointerSensor` alone activates a drag on the very first touch-move, indistinguishable from a
98
111
  // scroll gesture on a touch device. `MouseSensor` (a small `distance` — desktop drags still start
@@ -111,6 +124,8 @@ function CalendarContent({ userUid }: { userUid?: string }) {
111
124
  const [loading, setLoading] = useState(true);
112
125
  const [error, setError] = useState<string | null>(null);
113
126
  const [modal, setModal] = useState<ModalState | null>(null);
127
+ // True from the moment a touch drag of an event (or its resize handle) activates until it ends or is cancelled.
128
+ const [dragging, setDragging] = useState(false);
114
129
  // `null` means "not yet customized by the user" — every calendar is treated as checked without
115
130
  // needing to be seeded from `calendarFolders` the moment it loads (see `checkedFolderUids` below).
116
131
  // Once the user toggles anything, this becomes a real (possibly empty) set of exactly what's checked.
@@ -235,6 +250,39 @@ function CalendarContent({ userUid }: { userUid?: string }) {
235
250
  const checkedFolderUidsKey = Array.from(checkedFolderUids).sort().join(",");
236
251
  useEffect(reload, [calendarFolderUidsKey, checkedFolderUidsKey]);
237
252
 
253
+ // A private or confidential event that somebody creates or changes is announced on the push connection as its busy block, whatever the reader may
254
+ // see (see `redactedEventUidOf()`): that payload is never stored here. The event is fetched again by its uid, which comes back as much as this
255
+ // reader may see - all of it for the owner - and replaces (or joins) what the calendar holds; one that is gone (a 404) leaves it. A failed fetch
256
+ // changes nothing: the next load shows the event as it is.
257
+ const checkedFolderUidsRef = useRef(checkedFolderUids);
258
+ checkedFolderUidsRef.current = checkedFolderUids;
259
+ useEffect(
260
+ () =>
261
+ getPushClient().onEvent((event) => {
262
+ const uid = redactedEventUidOf(event);
263
+ if (!uid) {
264
+ return;
265
+ }
266
+ void getCalendarEvent(uid).then(
267
+ (fresh) => {
268
+ if (checkedFolderUidsRef.current.has(fresh.folderUid)) {
269
+ setEvents((previous) =>
270
+ previous.some((existing) => existing.uid === fresh.uid)
271
+ ? previous.map((existing) => (existing.uid === fresh.uid ? fresh : existing))
272
+ : [...previous, fresh],
273
+ );
274
+ }
275
+ },
276
+ (err: unknown) => {
277
+ if (err instanceof ApiRequestError && err.status === 404) {
278
+ setEvents((previous) => previous.filter((existing) => existing.uid !== uid));
279
+ }
280
+ },
281
+ );
282
+ }),
283
+ [],
284
+ );
285
+
238
286
  const occurrences = useMemo(
239
287
  () => expandAllOccurrences(events, rangeStart, rangeEnd),
240
288
  [events, rangeStart, rangeEnd],
@@ -258,6 +306,41 @@ function CalendarContent({ userUid }: { userUid?: string }) {
258
306
  );
259
307
  }
260
308
 
309
+ // The phone layout's swipe between periods. Only the view area carries it (not the toolbar), it calls the same `shiftView()`
310
+ // the Previous/Next buttons and shortcuts do, and it is off while an event is being dragged or the editor is open. The views
311
+ // never scroll sideways (their columns share the width), so there is no horizontal scroller for the gesture to fight with.
312
+ // The view follows the finger and, once the swipe commits, slides out while the next period slides in (see `useSwipeSlide()`).
313
+ const isMobile = useIsMobile();
314
+ const swipe = useSwipeSlide({
315
+ enabled: isMobile && !dragging && !modal,
316
+ onShift: (direction) => shiftView(SWIPE_PERIOD_SHIFT[direction]),
317
+ });
318
+
319
+ // The desktop's mouse wheel steps through the months without end; in the views that scroll through the hours of a day it scrolls
320
+ // them as usual and, pushed past the last (or first) hour, carries on into the next (or previous) day, week or work week. Each
321
+ // step is the same `shiftView()` and enters with a short vertical slide (`useEnterSlide()`), which never waits for the last one.
322
+ const wheelSlide = useEnterSlide();
323
+ /** Where the scroller of the page a wheel step arrived at starts: its top after going on, its bottom after going back. */
324
+ const arrivalEdge = useRef<WheelEdge | null>(null);
325
+ const scrollerOf = (): HTMLElement | null => swipe.ref.current?.querySelector<HTMLElement>("[data-calendar-scroller]") ?? null;
326
+ useWheelPaging(swipe.ref, {
327
+ enabled: !isMobile && !dragging && !modal,
328
+ getScroller: () => (view === "month" ? null : scrollerOf()),
329
+ onStep: (direction, edge) => {
330
+ arrivalEdge.current = edge;
331
+ shiftView(direction);
332
+ wheelSlide.enter(direction);
333
+ },
334
+ });
335
+ useLayoutEffect(() => {
336
+ const edge = arrivalEdge.current;
337
+ arrivalEdge.current = null;
338
+ const scroller = edge ? scrollerOf() : null;
339
+ if (scroller) {
340
+ scroller.scrollTop = edge === "top" ? 0 : scroller.scrollHeight;
341
+ }
342
+ }, [viewDate]);
343
+
261
344
  function goToday() {
262
345
  setViewDate(startOfDay(new Date()));
263
346
  }
@@ -267,7 +350,7 @@ function CalendarContent({ userUid }: { userUid?: string }) {
267
350
  setViewDate(startOfDay(date));
268
351
  }
269
352
 
270
- function openNewEvent(start?: Date, end?: Date, targetFolderUid?: string) {
353
+ function openNewEvent(start?: Date, end?: Date, targetFolderUid?: string, anchor?: EventAnchor, initialAllDay?: boolean) {
271
354
  if (!mailboxUid || !folderUid) {
272
355
  return;
273
356
  }
@@ -276,6 +359,8 @@ function CalendarContent({ userUid }: { userUid?: string }) {
276
359
  initialStart: start ?? new Date(),
277
360
  initialEnd: end ?? new Date(Date.now() + 30 * 60_000),
278
361
  targetFolderUid,
362
+ anchor,
363
+ initialAllDay,
279
364
  });
280
365
  }
281
366
 
@@ -312,6 +397,7 @@ function CalendarContent({ userUid }: { userUid?: string }) {
312
397
  }
313
398
 
314
399
  async function handleDragEnd(event: DragEndEvent) {
400
+ setDragging(false);
315
401
  const overId = event.over ? String(event.over.id) : undefined;
316
402
  const action = resolveDragAction(String(event.active.id), overId, occurrences);
317
403
  if (!action) {
@@ -380,7 +466,7 @@ function CalendarContent({ userUid }: { userUid?: string }) {
380
466
  >
381
467
  <HiOutlineBars3 size={20} aria-hidden="true" />
382
468
  </button>
383
- <Button type="button" onClick={() => openNewEvent()} className="!w-auto shrink-0" {...newEventHint}>
469
+ <Button type="button" onClick={(e) => openNewEvent(undefined, undefined, undefined, anchorOf(e.currentTarget, "below"))} className="!w-auto shrink-0" {...newEventHint}>
384
470
  + New event
385
471
  </Button>
386
472
  <div className="flex items-center gap-1">
@@ -408,37 +494,49 @@ function CalendarContent({ userUid }: { userUid?: string }) {
408
494
  </div>
409
495
  )}
410
496
 
411
- {loading ? (
412
- <p className="p-4 text-sm text-text-muted">Loading&hellip;</p>
413
- ) : (
414
- <DndContext sensors={sensors} onDragEnd={handleDragEnd}>
415
- {view === "month" ? (
416
- <MonthView
417
- viewDate={viewDate}
418
- occurrences={occurrences}
419
- folderColors={folderColors}
420
- onSelectDay={handleSelectDay}
421
- onSelectEvent={openEvent}
422
- />
423
- ) : view === "split" ? (
424
- <SplitDayView
425
- day={viewDate}
426
- columns={splitColumns}
427
- occurrences={occurrences}
428
- onSelectEvent={openEvent}
429
- onSelectSlot={(start, end, targetFolderUid) => openNewEvent(start, end, targetFolderUid)}
430
- />
431
- ) : (
432
- <TimeGridView
433
- days={days}
434
- occurrences={occurrences}
435
- folderColors={folderColors}
436
- onSelectEvent={openEvent}
437
- onSelectSlot={openNewEvent}
438
- />
439
- )}
440
- </DndContext>
441
- )}
497
+ <div ref={swipe.ref} className="flex-1 min-w-0 flex flex-col min-h-0 overflow-hidden" {...swipe.handlers} style={swipe.style}>
498
+ <div data-swipe-phase={swipe.phase} className="flex-1 min-w-0 flex flex-col min-h-0" style={swipe.contentStyle}>
499
+ <div className="flex-1 min-w-0 flex flex-col min-h-0" style={wheelSlide.style}>
500
+ {loading ? (
501
+ <p className="p-4 text-sm text-text-muted">Loading&hellip;</p>
502
+ ) : (
503
+ <DndContext
504
+ sensors={sensors}
505
+ onDragStart={() => setDragging(true)}
506
+ onDragEnd={handleDragEnd}
507
+ onDragCancel={() => setDragging(false)}
508
+ >
509
+ {view === "month" ? (
510
+ <MonthView
511
+ viewDate={viewDate}
512
+ occurrences={occurrences}
513
+ folderColors={folderColors}
514
+ onSelectDay={handleSelectDay}
515
+ onSelectEvent={openEvent}
516
+ onSelectSlot={(start, end, anchor) => openNewEvent(start, end, undefined, anchor, true)}
517
+ />
518
+ ) : view === "split" ? (
519
+ <SplitDayView
520
+ day={viewDate}
521
+ columns={splitColumns}
522
+ occurrences={occurrences}
523
+ onSelectEvent={openEvent}
524
+ onSelectSlot={(start, end, targetFolderUid, anchor) => openNewEvent(start, end, targetFolderUid, anchor)}
525
+ />
526
+ ) : (
527
+ <TimeGridView
528
+ days={days}
529
+ occurrences={occurrences}
530
+ folderColors={folderColors}
531
+ onSelectEvent={openEvent}
532
+ onSelectSlot={(start, end, anchor) => openNewEvent(start, end, undefined, anchor)}
533
+ />
534
+ )}
535
+ </DndContext>
536
+ )}
537
+ </div>
538
+ </div>
539
+ </div>
442
540
 
443
541
  {/* `folderUid` is guaranteed defined whenever `modal` is: `openNewEvent` only sets it after
444
542
  checking it, and `openEvent` only fires from an occurrence that itself required a
@@ -452,11 +550,15 @@ function CalendarContent({ userUid }: { userUid?: string }) {
452
550
  folderUid={modalFolderUid!}
453
551
  calendars={calendarFolders.filter((f) => f.mailboxUid === modalMailbox.uid).map((f) => ({ uid: f.uid, name: f.name }))}
454
552
  mailboxOptions={mailboxOptions}
553
+ folderColors={folderColors}
455
554
  organizerAddress={modalMailbox.primarySmtpAddress}
456
555
  organizerAliases={modalMailbox.aliasAddresses}
457
556
  occurrence={modal.occurrence}
458
557
  initialStart={modal.initialStart}
459
558
  initialEnd={modal.initialEnd}
559
+ initialAllDay={modal.initialAllDay}
560
+ anchor={modal.anchor}
561
+ bookingHref={bookingHref}
460
562
  onSaved={handleSaved}
461
563
  onDeleted={handleDeleted}
462
564
  />