@arbidocs/blocks 0.4.70 → 0.4.72

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 (179) hide show
  1. package/README.md +90 -14
  2. package/bin/arbidocs-copy-pdf-assets.mjs +112 -0
  3. package/dist/{StreamedArtifactEditor-OPL4KXOT.js → StreamedArtifactEditor-HTNPG4NB.js} +3 -2
  4. package/dist/StreamedArtifactEditor-HTNPG4NB.js.map +1 -0
  5. package/dist/{StreamedArtifactEditor-RIKG6CJQ.cjs → StreamedArtifactEditor-IZWYNFMM.cjs} +3 -2
  6. package/dist/StreamedArtifactEditor-IZWYNFMM.cjs.map +1 -0
  7. package/dist/agents/index.cjs +224 -224
  8. package/dist/agents/index.js +8 -8
  9. package/dist/artifacts/editor/index.cjs +1 -0
  10. package/dist/artifacts/editor/index.js +1 -0
  11. package/dist/artifacts/index.cjs +1 -0
  12. package/dist/artifacts/index.js +1 -0
  13. package/dist/auth/index.cjs +1 -0
  14. package/dist/auth/index.js +1 -0
  15. package/dist/calendar/CalendarGrid.d.ts +2 -0
  16. package/dist/calendar/CalendarList.d.ts +2 -0
  17. package/dist/calendar/CalendarSettingsForm.d.ts +2 -0
  18. package/dist/calendar/EventDetails.d.ts +2 -0
  19. package/dist/calendar/EventEditor.d.ts +2 -0
  20. package/dist/calendar/MiniMonth.d.ts +2 -0
  21. package/dist/calendar/RecurrenceEditor.d.ts +13 -0
  22. package/dist/calendar/RoomFinder.d.ts +2 -0
  23. package/dist/calendar/SchedulingAssistant.d.ts +2 -0
  24. package/dist/calendar/ScopeDialog.d.ts +2 -0
  25. package/dist/calendar/TimeZonePicker.d.ts +2 -0
  26. package/dist/calendar/colors.d.ts +4 -0
  27. package/dist/calendar/draft.d.ts +43 -0
  28. package/dist/calendar/format.d.ts +14 -0
  29. package/dist/calendar/grid-model.d.ts +24 -0
  30. package/dist/calendar/index.cjs +4122 -0
  31. package/dist/calendar/index.cjs.map +1 -0
  32. package/dist/calendar/index.d.ts +30 -0
  33. package/dist/calendar/index.js +4043 -0
  34. package/dist/calendar/index.js.map +1 -0
  35. package/dist/calendar/labels.d.ts +4 -0
  36. package/dist/calendar/recurrence-labels.d.ts +22 -0
  37. package/dist/calendar/recurrence.d.ts +66 -0
  38. package/dist/calendar/reminders.d.ts +26 -0
  39. package/dist/calendar/scheduling.d.ts +27 -0
  40. package/dist/calendar/types.d.ts +605 -0
  41. package/dist/calendar/ui.d.ts +16 -0
  42. package/dist/calendar/zone.d.ts +56 -0
  43. package/dist/chat/index.cjs +124 -123
  44. package/dist/chat/index.js +4 -3
  45. package/dist/{chunk-XNRGCQPZ.cjs → chunk-3SFFTSOI.cjs} +5 -5
  46. package/dist/{chunk-XNRGCQPZ.cjs.map → chunk-3SFFTSOI.cjs.map} +1 -1
  47. package/dist/{chunk-EHXSBH6U.js → chunk-5HOAOKZL.js} +4 -2
  48. package/dist/chunk-5HOAOKZL.js.map +1 -0
  49. package/dist/{chunk-7WM62NMM.cjs → chunk-6OL5S7MJ.cjs} +5 -2
  50. package/dist/chunk-6OL5S7MJ.cjs.map +1 -0
  51. package/dist/{chunk-VUEZARW5.cjs → chunk-B3WCKBJ7.cjs} +278 -5
  52. package/dist/chunk-B3WCKBJ7.cjs.map +1 -0
  53. package/dist/{chunk-L5UPP2HC.js → chunk-D46ZAPEE.js} +5 -2
  54. package/dist/chunk-D46ZAPEE.js.map +1 -0
  55. package/dist/chunk-DES3K4SD.cjs +18 -0
  56. package/dist/chunk-DES3K4SD.cjs.map +1 -0
  57. package/dist/{chunk-7M22KND4.cjs → chunk-DH7C2UJO.cjs} +10 -10
  58. package/dist/{chunk-7M22KND4.cjs.map → chunk-DH7C2UJO.cjs.map} +1 -1
  59. package/dist/{chunk-CXWESDIZ.cjs → chunk-F4RUCUXF.cjs} +6 -6
  60. package/dist/{chunk-CXWESDIZ.cjs.map → chunk-F4RUCUXF.cjs.map} +1 -1
  61. package/dist/{chunk-DABEWNL2.js → chunk-G5DP27DU.js} +3 -3
  62. package/dist/{chunk-DABEWNL2.js.map → chunk-G5DP27DU.js.map} +1 -1
  63. package/dist/{chunk-BR2CPKFU.cjs → chunk-G5QTGKDJ.cjs} +8 -8
  64. package/dist/{chunk-BR2CPKFU.cjs.map → chunk-G5QTGKDJ.cjs.map} +1 -1
  65. package/dist/{chunk-VZGBCBYB.js → chunk-GJV2G4FC.js} +276 -7
  66. package/dist/chunk-GJV2G4FC.js.map +1 -0
  67. package/dist/{chunk-HLMFU7L2.js → chunk-IMC2Z32C.js} +8 -8
  68. package/dist/{chunk-HLMFU7L2.js.map → chunk-IMC2Z32C.js.map} +1 -1
  69. package/dist/{chunk-5DM6VU4L.cjs → chunk-KJ3QEKOJ.cjs} +3 -3
  70. package/dist/{chunk-5DM6VU4L.cjs.map → chunk-KJ3QEKOJ.cjs.map} +1 -1
  71. package/dist/{chunk-CW4WXGEN.js → chunk-KOXUBCLL.js} +4 -4
  72. package/dist/{chunk-CW4WXGEN.js.map → chunk-KOXUBCLL.js.map} +1 -1
  73. package/dist/{chunk-ILGHS6ZV.js → chunk-SB5ZKOYN.js} +3 -3
  74. package/dist/{chunk-ILGHS6ZV.js.map → chunk-SB5ZKOYN.js.map} +1 -1
  75. package/dist/{chunk-TLQQV7WW.cjs → chunk-THO7DZNN.cjs} +4 -2
  76. package/dist/chunk-THO7DZNN.cjs.map +1 -0
  77. package/dist/{chunk-CPMNAJLF.js → chunk-TX2ZICS7.js} +24 -4
  78. package/dist/chunk-TX2ZICS7.js.map +1 -0
  79. package/dist/{chunk-2ZTONLT5.cjs → chunk-ULWRNGGE.cjs} +25 -4
  80. package/dist/chunk-ULWRNGGE.cjs.map +1 -0
  81. package/dist/{chunk-BMWZTNZB.js → chunk-VRPRAU3J.js} +3 -3
  82. package/dist/{chunk-BMWZTNZB.js.map → chunk-VRPRAU3J.js.map} +1 -1
  83. package/dist/chunk-VUNV25KB.js +15 -0
  84. package/dist/chunk-VUNV25KB.js.map +1 -0
  85. package/dist/citations/index.cjs +1 -0
  86. package/dist/citations/index.js +1 -0
  87. package/dist/collaboration/index.cjs +45 -45
  88. package/dist/collaboration/index.js +3 -3
  89. package/dist/connect/docx.cjs +3 -2
  90. package/dist/connect/docx.cjs.map +1 -1
  91. package/dist/connect/docx.js +2 -1
  92. package/dist/connect/docx.js.map +1 -1
  93. package/dist/connect/index.cjs +79 -49
  94. package/dist/connect/index.cjs.map +1 -1
  95. package/dist/connect/index.d.ts +1 -1
  96. package/dist/connect/index.js +39 -11
  97. package/dist/connect/index.js.map +1 -1
  98. package/dist/connection/ConnectedSettings.d.ts +13 -0
  99. package/dist/connection/ConnectionProvider.d.ts +5 -0
  100. package/dist/connection/types.d.ts +6 -0
  101. package/dist/document-tools/SignatureStatus.d.ts +4 -2
  102. package/dist/document-tools/index.cjs +14 -11
  103. package/dist/document-tools/index.cjs.map +1 -1
  104. package/dist/document-tools/index.js +12 -9
  105. package/dist/document-tools/index.js.map +1 -1
  106. package/dist/document-tools/production/ProductionSetDialog.d.ts +23 -0
  107. package/dist/document-tools/production/index.cjs +102 -0
  108. package/dist/document-tools/production/index.cjs.map +1 -0
  109. package/dist/document-tools/production/index.d.ts +1 -0
  110. package/dist/document-tools/production/index.js +100 -0
  111. package/dist/document-tools/production/index.js.map +1 -0
  112. package/dist/document-tools/viewer/index.cjs +1 -1
  113. package/dist/document-tools/viewer/index.js +1 -1
  114. package/dist/docx/index.cjs +1 -0
  115. package/dist/docx/index.cjs.map +1 -1
  116. package/dist/docx/index.js +1 -0
  117. package/dist/docx/index.js.map +1 -1
  118. package/dist/facts/index.cjs +3 -2
  119. package/dist/facts/index.cjs.map +1 -1
  120. package/dist/facts/index.js +3 -2
  121. package/dist/facts/index.js.map +1 -1
  122. package/dist/files/import/index.d.ts +0 -1
  123. package/dist/files/index.cjs +25 -88
  124. package/dist/files/index.cjs.map +1 -1
  125. package/dist/files/index.js +7 -68
  126. package/dist/files/index.js.map +1 -1
  127. package/dist/home/index.cjs +2 -1
  128. package/dist/home/index.cjs.map +1 -1
  129. package/dist/home/index.js +2 -1
  130. package/dist/home/index.js.map +1 -1
  131. package/dist/index.cjs +537 -537
  132. package/dist/index.js +14 -14
  133. package/dist/lib-6QPUXAQS.cjs +9674 -0
  134. package/dist/lib-6QPUXAQS.cjs.map +1 -0
  135. package/dist/lib-NRGL2JGF.js +9672 -0
  136. package/dist/lib-NRGL2JGF.js.map +1 -0
  137. package/dist/retrieval/index.cjs +1 -0
  138. package/dist/retrieval/index.js +1 -0
  139. package/dist/settings/Billing.d.ts +4 -0
  140. package/dist/settings/IdentityVerification.d.ts +83 -0
  141. package/dist/settings/PlanBadge.d.ts +2 -2
  142. package/dist/settings/PlanCard.d.ts +6 -1
  143. package/dist/settings/ProfileCard.d.ts +5 -1
  144. package/dist/settings/SubscriptionParts.d.ts +6 -2
  145. package/dist/settings/index.cjs +129 -95
  146. package/dist/settings/index.cjs.map +1 -1
  147. package/dist/settings/index.d.ts +1 -0
  148. package/dist/settings/index.js +65 -47
  149. package/dist/settings/index.js.map +1 -1
  150. package/dist/shell/ActionCluster.d.ts +3 -1
  151. package/dist/shell/index.cjs +56 -55
  152. package/dist/shell/index.js +4 -3
  153. package/dist/site/index.cjs +1 -0
  154. package/dist/site/index.js +1 -0
  155. package/dist/studio/index.cjs +8 -7
  156. package/dist/studio/index.cjs.map +1 -1
  157. package/dist/studio/index.js +6 -5
  158. package/dist/studio/index.js.map +1 -1
  159. package/dist/timeline/index.cjs +1 -0
  160. package/dist/timeline/index.js +1 -0
  161. package/dist/workspaces/index.cjs +3 -3
  162. package/dist/workspaces/index.js +2 -2
  163. package/package.json +35 -5
  164. package/src/calendar/calendar.css +316 -0
  165. package/dist/StreamedArtifactEditor-OPL4KXOT.js.map +0 -1
  166. package/dist/StreamedArtifactEditor-RIKG6CJQ.cjs.map +0 -1
  167. package/dist/chunk-2ZTONLT5.cjs.map +0 -1
  168. package/dist/chunk-7WM62NMM.cjs.map +0 -1
  169. package/dist/chunk-CPMNAJLF.js.map +0 -1
  170. package/dist/chunk-DCDVWPMB.cjs +0 -29
  171. package/dist/chunk-DCDVWPMB.cjs.map +0 -1
  172. package/dist/chunk-EHXSBH6U.js.map +0 -1
  173. package/dist/chunk-L5UPP2HC.js.map +0 -1
  174. package/dist/chunk-TJMKFY5P.js +0 -27
  175. package/dist/chunk-TJMKFY5P.js.map +0 -1
  176. package/dist/chunk-TLQQV7WW.cjs.map +0 -1
  177. package/dist/chunk-VUEZARW5.cjs.map +0 -1
  178. package/dist/chunk-VZGBCBYB.js.map +0 -1
  179. package/dist/files/import/CalendarImportRow.d.ts +0 -26
@@ -0,0 +1,4043 @@
1
+ "use client";
2
+ import '../chunk-VUNV25KB.js';
3
+ import { forwardRef, useMemo, useRef, useState, useCallback, useEffect, useId } from 'react';
4
+ import FullCalendar from '@fullcalendar/react';
5
+ import dayGridPlugin from '@fullcalendar/daygrid';
6
+ import timeGridPlugin from '@fullcalendar/timegrid';
7
+ import listPlugin from '@fullcalendar/list';
8
+ import interactionPlugin from '@fullcalendar/interaction';
9
+ import multiMonthPlugin from '@fullcalendar/multimonth';
10
+ import { ChevronLeft, ChevronRight, Plus, Lightbulb, Workflow, ListTodo, Lock, EllipsisVertical, Globe, ChevronDown, Check, Clock, Repeat, CalendarDays, X, Video, MapPin, Users, TextAlignStart, Bell, Tag, Palette, Paperclip, Trash2, Pencil, CircleCheck, CircleX, DoorOpen, User, Sparkles } from 'lucide-react';
11
+ import { cn, Button, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, Input, Popover, PopoverTrigger, PopoverContent, Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, Switch, Textarea, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@arbidocs/react/ui';
12
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
+ import { Temporal } from 'temporal-polyfill';
14
+ import { createPlugin } from '@fullcalendar/core';
15
+ import { NamedTimeZoneImpl } from '@fullcalendar/core/internal';
16
+
17
+ // src/calendar/labels.ts
18
+ var CALENDAR_DEFAULT_LABELS = {
19
+ today: "Today",
20
+ view: "View",
21
+ previous: "Previous",
22
+ next: "Next",
23
+ day: "Day",
24
+ week: "Week",
25
+ workWeek: "Work week",
26
+ month: "Month",
27
+ agenda: "Agenda",
28
+ year: "Year",
29
+ allDay: "All day",
30
+ busy: "Busy",
31
+ noTitle: "(No title)",
32
+ noEvents: "No events",
33
+ weekAbbrev: "W",
34
+ more: "more",
35
+ newEvent: "New event",
36
+ myCalendars: "My calendars",
37
+ sharedCalendars: "Shared with me",
38
+ workspaceCalendars: "Workspace calendars",
39
+ subscribedCalendars: "Subscribed",
40
+ overlays: "Also show",
41
+ facts: "Dated facts",
42
+ workflows: "Workflows",
43
+ tasks: "Tasks",
44
+ createCalendar: "New calendar",
45
+ editCalendar: "Settings and sharing",
46
+ importCalendar: "Import .ics",
47
+ exportCalendar: "Export .ics",
48
+ deleteCalendar: "Delete calendar",
49
+ locked: "Locked calendar",
50
+ showCalendar: "Show",
51
+ hideCalendar: "Hide",
52
+ calendarColor: "Colour",
53
+ title: "Title",
54
+ titlePlaceholder: "Add a title",
55
+ calendar: "Calendar",
56
+ start: "Start",
57
+ end: "End",
58
+ startTime: "Start time",
59
+ endTime: "End time",
60
+ arbiCall: "ARBI call",
61
+ startTimeZone: "Start time zone",
62
+ endTimeZone: "End time zone",
63
+ separateTimeZones: "Time zones",
64
+ repeat: "Repeat",
65
+ location: "Location",
66
+ description: "Description",
67
+ attendees: "Guests",
68
+ addAttendee: "Add guests by email",
69
+ required: "Required",
70
+ optional: "Optional",
71
+ showAs: "Show as",
72
+ sensitivity: "Visibility",
73
+ categories: "Categories",
74
+ addCategory: "Add a category",
75
+ color: "Colour",
76
+ defaultColor: "Calendar colour",
77
+ reminders: "Reminders",
78
+ addReminder: "Add reminder",
79
+ useDefaultReminders: "Use calendar default reminders",
80
+ conference: "Video call",
81
+ addArbiCall: "Add ARBI call",
82
+ addMeetingLink: "Add meeting link",
83
+ meetingLinkPlaceholder: "https://\u2026",
84
+ removeConference: "Remove video call",
85
+ attachments: "Attachments",
86
+ addAttachment: "Add link",
87
+ attachmentUrlPlaceholder: "https://\u2026",
88
+ attachmentTitlePlaceholder: "Title (optional)",
89
+ status: "Status",
90
+ save: "Save",
91
+ saving: "Saving\u2026",
92
+ cancel: "Cancel",
93
+ delete: "Delete",
94
+ edit: "Edit",
95
+ remove: "Remove",
96
+ createTitle: "New event",
97
+ editTitle: "Edit event",
98
+ minutes: "minutes",
99
+ hours: "hours",
100
+ days: "days",
101
+ weeks: "weeks",
102
+ beforeStart: "before",
103
+ popup: "Notification",
104
+ email: "Email",
105
+ atStart: "At time of event",
106
+ free: "Free",
107
+ tentative: "Tentative",
108
+ oof: "Out of office",
109
+ workingElsewhere: "Working elsewhere",
110
+ normal: "Default visibility",
111
+ private: "Private",
112
+ confidential: "Confidential",
113
+ confirmed: "Confirmed",
114
+ cancelled: "Cancelled",
115
+ join: "Join",
116
+ accept: "Yes",
117
+ decline: "No",
118
+ maybe: "Maybe",
119
+ going: "Going?",
120
+ organizer: "Organiser",
121
+ needsAction: "Awaiting reply",
122
+ accepted: "Accepted",
123
+ declined: "Declined",
124
+ delegated: "Delegated",
125
+ recurring: "Repeats",
126
+ scopeEditTitle: "Edit recurring event",
127
+ scopeDeleteTitle: "Delete recurring event",
128
+ scopeThis: "This event",
129
+ scopeFollowing: "This and following events",
130
+ scopeAll: "All events",
131
+ settingsTitle: "Calendar settings",
132
+ timeZone: "Time zone",
133
+ secondaryTimeZone: "Secondary time zone",
134
+ none: "None",
135
+ weekStart: "Week starts on",
136
+ workingHours: "Working hours",
137
+ defaultView: "Default view",
138
+ defaultDuration: "Default event length",
139
+ defaultCalendar: "Default calendar",
140
+ showDeclined: "Show declined events",
141
+ showWeekNumbers: "Show week numbers",
142
+ autoAddInvitations: "Add invitations to my calendar",
143
+ autoAddAll: "From everyone",
144
+ autoAddKnown: "Only from people I know",
145
+ autoAddNone: "Only after I respond",
146
+ declineMessage: "Default decline message",
147
+ searchTimeZones: "Search time zones",
148
+ invalidRange: "The end must be after the start.",
149
+ titleRequired: "Add a title.",
150
+ removeFromCalendar: "Remove from calendar",
151
+ proposeNewTime: "Propose new time",
152
+ scopeRespondTitle: "Respond to a recurring event",
153
+ send: "Send",
154
+ focusTime: "Focus time",
155
+ autoDecline: "Decline new invitations",
156
+ declineMessagePlaceholder: "Message for declined invitations (optional)",
157
+ schedulingAssistant: "Scheduling assistant",
158
+ findATime: "Find a time",
159
+ findingTimes: "Finding times\u2026",
160
+ noSuggestions: "No free times found in this range.",
161
+ everyoneFree: "Everyone available",
162
+ unavailableCount: "unavailable",
163
+ unknownAvailability: "Availability not shared",
164
+ outsideWorkingHours: "Outside working hours",
165
+ rooms: "Rooms",
166
+ findRoom: "Find a room",
167
+ minCapacity: "Min. capacity",
168
+ featureFilter: "Feature",
169
+ addRoom: "Add",
170
+ roomAdded: "Added",
171
+ roomFree: "Free",
172
+ roomBusy: "Busy",
173
+ noRooms: "No rooms match.",
174
+ room: "Room",
175
+ you: "You",
176
+ addCalendar: "Add calendar",
177
+ subscribeFromUrl: "Subscribe from URL",
178
+ addHolidays: "Holidays",
179
+ importIcs: "Import .ics file",
180
+ shareCalendar: "Share\u2026",
181
+ refreshCalendar: "Refresh",
182
+ publishTo: "Publish to\u2026",
183
+ suggestions: "Suggestions"
184
+ };
185
+ var mergeLabels = (labels) => ({
186
+ ...CALENDAR_DEFAULT_LABELS,
187
+ ...labels
188
+ });
189
+
190
+ // src/calendar/colors.ts
191
+ var CALENDAR_COLORS = [
192
+ "#3b82f6",
193
+ "#0ea5e9",
194
+ "#14b8a6",
195
+ "#22c55e",
196
+ "#84cc16",
197
+ "#eab308",
198
+ "#f97316",
199
+ "#ef4444",
200
+ "#ec4899",
201
+ "#a855f7",
202
+ "#6366f1",
203
+ "#64748b"
204
+ ];
205
+ function contrastText(hex) {
206
+ const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
207
+ if (!m) return "#ffffff";
208
+ const n = parseInt(m[1], 16);
209
+ const ch = [n >> 16 & 255, n >> 8 & 255, n & 255].map((v) => {
210
+ const c = v / 255;
211
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
212
+ });
213
+ const lum = 0.2126 * ch[0] + 0.7152 * ch[1] + 0.0722 * ch[2];
214
+ return 1.05 / (lum + 0.05) >= (lum + 0.05) / 0.05 ? "#ffffff" : "#111827";
215
+ }
216
+ var NativeSelect = forwardRef(
217
+ function NativeSelect2({ className, children, ...props }, ref) {
218
+ return /* @__PURE__ */ jsx(
219
+ "select",
220
+ {
221
+ ref,
222
+ className: cn(
223
+ "h-9 rounded-md border border-input bg-background px-2 text-sm text-foreground shadow-sm",
224
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50",
225
+ className
226
+ ),
227
+ ...props,
228
+ children
229
+ }
230
+ );
231
+ }
232
+ );
233
+ function FieldRow({
234
+ icon,
235
+ label,
236
+ children,
237
+ className
238
+ }) {
239
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-start gap-3", className), children: [
240
+ /* @__PURE__ */ jsx(
241
+ "div",
242
+ {
243
+ className: "mt-2 flex h-5 w-5 shrink-0 items-center justify-center text-muted-foreground",
244
+ "aria-hidden": true,
245
+ children: icon
246
+ }
247
+ ),
248
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [
249
+ label && /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: label }),
250
+ children
251
+ ] })
252
+ ] });
253
+ }
254
+ function ColorSwatches({
255
+ value,
256
+ onChange,
257
+ allowNone,
258
+ noneLabel,
259
+ testIdPrefix
260
+ }) {
261
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", role: "radiogroup", children: [
262
+ allowNone && /* @__PURE__ */ jsx(
263
+ "button",
264
+ {
265
+ type: "button",
266
+ role: "radio",
267
+ "aria-checked": !value,
268
+ "aria-label": noneLabel,
269
+ title: noneLabel,
270
+ onClick: () => onChange(null),
271
+ className: cn(
272
+ "h-6 rounded-full border border-border px-2 text-xs text-muted-foreground",
273
+ !value && "ring-2 ring-ring ring-offset-1 ring-offset-background"
274
+ ),
275
+ children: noneLabel
276
+ }
277
+ ),
278
+ CALENDAR_COLORS.map((c) => /* @__PURE__ */ jsx(
279
+ "button",
280
+ {
281
+ type: "button",
282
+ role: "radio",
283
+ "aria-checked": value?.toLowerCase() === c,
284
+ "aria-label": c,
285
+ title: c,
286
+ "data-testid": testIdPrefix ? `${testIdPrefix}-${c.slice(1)}` : void 0,
287
+ onClick: () => onChange(c),
288
+ className: cn(
289
+ "h-6 w-6 rounded-full border border-black/10",
290
+ value?.toLowerCase() === c && "ring-2 ring-ring ring-offset-1 ring-offset-background"
291
+ ),
292
+ style: { backgroundColor: c }
293
+ },
294
+ c
295
+ ))
296
+ ] });
297
+ }
298
+ var pad2 = (n) => String(n).padStart(2, "0");
299
+ var pad4 = (n) => String(n).padStart(4, "0");
300
+ function isValidTimeZone(zone) {
301
+ if (!zone) return false;
302
+ try {
303
+ Temporal.Now.instant().toZonedDateTimeISO(zone);
304
+ return true;
305
+ } catch {
306
+ return false;
307
+ }
308
+ }
309
+ function safeZone(zone, fallback = "UTC") {
310
+ if (isValidTimeZone(zone)) return zone;
311
+ return isValidTimeZone(fallback) ? fallback : "UTC";
312
+ }
313
+ function toPlainDateTime(wall) {
314
+ return /^\d{4}-\d{2}-\d{2}$/.test(wall) ? Temporal.PlainDate.from(wall).toPlainDateTime() : Temporal.PlainDateTime.from(wall);
315
+ }
316
+ function wallToUtc(wall, zone) {
317
+ const zdt = toPlainDateTime(wall).toZonedDateTime(safeZone(zone), {
318
+ disambiguation: "compatible"
319
+ });
320
+ return zdt.toInstant().toString();
321
+ }
322
+ function wallToEpochMs(wall, zone) {
323
+ return toPlainDateTime(wall).toZonedDateTime(safeZone(zone), { disambiguation: "compatible" }).epochMilliseconds;
324
+ }
325
+ function instantOf(utc) {
326
+ if (typeof utc === "number") return Temporal.Instant.fromEpochMilliseconds(utc);
327
+ if (utc instanceof Date) return Temporal.Instant.fromEpochMilliseconds(utc.getTime());
328
+ return Temporal.Instant.from(utc);
329
+ }
330
+ function zoned(utc, zone) {
331
+ return instantOf(utc).toZonedDateTimeISO(safeZone(zone));
332
+ }
333
+ function utcToWall(utc, zone) {
334
+ const z = zoned(utc, zone);
335
+ return `${pad4(z.year)}-${pad2(z.month)}-${pad2(z.day)}T${pad2(z.hour)}:${pad2(z.minute)}`;
336
+ }
337
+ function utcToDate(utc, zone) {
338
+ const z = zoned(utc, zone);
339
+ return `${pad4(z.year)}-${pad2(z.month)}-${pad2(z.day)}`;
340
+ }
341
+ function utcToTime(utc, zone) {
342
+ const z = zoned(utc, zone);
343
+ return `${pad2(z.hour)}:${pad2(z.minute)}`;
344
+ }
345
+ function convertWall(wall, fromZone, toZone) {
346
+ return utcToWall(wallToUtc(wall, fromZone), toZone);
347
+ }
348
+ function todayInZone(zone, now = Date.now()) {
349
+ return utcToDate(now, zone);
350
+ }
351
+ function addDays(date, days) {
352
+ return Temporal.PlainDate.from(date).add({ days }).toString();
353
+ }
354
+ function addMonths(date, months) {
355
+ return Temporal.PlainDate.from(date).add({ months }).toString();
356
+ }
357
+ function diffDays(a, b) {
358
+ return Temporal.PlainDate.from(a).until(Temporal.PlainDate.from(b), { largestUnit: "days" }).days;
359
+ }
360
+ function dayOfWeekSun0(date) {
361
+ return Temporal.PlainDate.from(date).dayOfWeek % 7;
362
+ }
363
+ function startOfWeek(date, weekStart) {
364
+ const back = (dayOfWeekSun0(date) - weekStart + 7) % 7;
365
+ return addDays(date, -back);
366
+ }
367
+ function isoWeekNumber(date) {
368
+ return Temporal.PlainDate.from(date).weekOfYear ?? 1;
369
+ }
370
+ function zoneOffsetMinutes(zone, at = Date.now()) {
371
+ return Math.round(zoned(at, zone).offsetNanoseconds / 6e10);
372
+ }
373
+ function zoneOffsetLabel(zone, at = Date.now()) {
374
+ const total = zoneOffsetMinutes(zone, at);
375
+ if (total === 0) return "GMT";
376
+ const sign = total > 0 ? "+" : "-";
377
+ const abs = Math.abs(total);
378
+ const h = Math.floor(abs / 60);
379
+ const m = abs % 60;
380
+ return `GMT${sign}${h}${m ? `:${pad2(m)}` : ""}`;
381
+ }
382
+ function zoneCity(zone) {
383
+ const last = zone.split("/").pop() ?? zone;
384
+ return last.replace(/_/g, " ");
385
+ }
386
+ function listTimeZones() {
387
+ try {
388
+ const fn = Intl.supportedValuesOf;
389
+ const zones = fn ? fn("timeZone") : [];
390
+ if (zones.length) return zones.includes("UTC") ? zones : ["UTC", ...zones];
391
+ } catch {
392
+ }
393
+ return [
394
+ "UTC",
395
+ "Europe/London",
396
+ "Europe/Paris",
397
+ "Europe/Berlin",
398
+ "America/New_York",
399
+ "America/Chicago",
400
+ "America/Denver",
401
+ "America/Los_Angeles",
402
+ "Asia/Tokyo",
403
+ "Asia/Singapore",
404
+ "Australia/Sydney"
405
+ ];
406
+ }
407
+ function detectTimeZone() {
408
+ try {
409
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
410
+ } catch {
411
+ return "UTC";
412
+ }
413
+ }
414
+ function formatInZone(utc, zone, options, locale) {
415
+ const ms = instantOf(utc).epochMilliseconds;
416
+ return new Intl.DateTimeFormat(locale, { ...options, timeZone: safeZone(zone) }).format(ms);
417
+ }
418
+ function formatPlainDate(date, options, locale) {
419
+ return new Intl.DateTimeFormat(locale, { ...options, timeZone: "UTC" }).format(
420
+ Date.parse(`${date}T12:00:00Z`)
421
+ );
422
+ }
423
+ function epochToWallArray(ms, zone) {
424
+ const z = Temporal.Instant.fromEpochMilliseconds(ms).toZonedDateTimeISO(zone);
425
+ return [z.year, z.month - 1, z.day, z.hour, z.minute, z.second, z.millisecond];
426
+ }
427
+ function wallArrayOffset(a, zone) {
428
+ const norm = new Date(
429
+ Date.UTC(a[0], a[1] ?? 0, a[2] ?? 1, a[3] ?? 0, a[4] ?? 0, a[5] ?? 0, a[6] ?? 0)
430
+ );
431
+ const pdt = new Temporal.PlainDateTime(
432
+ norm.getUTCFullYear(),
433
+ norm.getUTCMonth() + 1,
434
+ norm.getUTCDate(),
435
+ norm.getUTCHours(),
436
+ norm.getUTCMinutes(),
437
+ norm.getUTCSeconds(),
438
+ norm.getUTCMilliseconds()
439
+ );
440
+ const zdt = pdt.toZonedDateTime(zone, { disambiguation: "compatible" });
441
+ return Math.round(zdt.offsetNanoseconds / 6e10);
442
+ }
443
+ var temporalZonePlugin = null;
444
+ function getTemporalTimeZonePlugin() {
445
+ if (!temporalZonePlugin) {
446
+ class TemporalNamedTimeZone extends NamedTimeZoneImpl {
447
+ offsetForArray(a) {
448
+ return wallArrayOffset(a, this.timeZoneName);
449
+ }
450
+ timestampToArray(ms) {
451
+ return epochToWallArray(ms, this.timeZoneName);
452
+ }
453
+ }
454
+ temporalZonePlugin = createPlugin({
455
+ name: "arbi-temporal-timezone",
456
+ namedTimeZonedImpl: TemporalNamedTimeZone
457
+ });
458
+ }
459
+ return temporalZonePlugin;
460
+ }
461
+ var FC_VIEW = {
462
+ day: "timeGridDay",
463
+ week: "timeGridWeek",
464
+ work_week: "arbiWorkWeek",
465
+ month: "dayGridMonth",
466
+ agenda: "arbiAgenda",
467
+ year: "multiMonthYear"
468
+ };
469
+ var VIEW_OF_FC = Object.fromEntries(
470
+ Object.entries(FC_VIEW).map(([k, v]) => [v, k])
471
+ );
472
+ var VIEW_ORDER = [
473
+ "day",
474
+ "work_week",
475
+ "week",
476
+ "month",
477
+ "agenda",
478
+ "year"
479
+ ];
480
+ var NARROW_VIEWS = ["day", "month", "agenda"];
481
+ var CALENDAR_NARROW_WIDTH = 640;
482
+ function effectiveCalendarView(view, narrow) {
483
+ if (!narrow) return view;
484
+ return view === "week" || view === "work_week" || view === "year" ? "day" : view;
485
+ }
486
+ var viewLabel = (v, l) => ({
487
+ day: l.day,
488
+ week: l.week,
489
+ work_week: l.workWeek,
490
+ month: l.month,
491
+ agenda: l.agenda,
492
+ year: l.year
493
+ })[v];
494
+ function toBusinessHours(hours) {
495
+ if (!hours?.length) return false;
496
+ return hours.map((h) => ({
497
+ daysOfWeek: [(h.day + 1) % 7],
498
+ startTime: h.start,
499
+ endTime: h.end === "24:00" ? "24:00:00" : h.end
500
+ }));
501
+ }
502
+ function workWeekHiddenDays(hours) {
503
+ const shown = new Set((hours ?? []).map((h) => (h.day + 1) % 7));
504
+ if (shown.size === 0) return [0, 6];
505
+ return [0, 1, 2, 3, 4, 5, 6].filter((d) => !shown.has(d));
506
+ }
507
+ function eventStateClasses(e) {
508
+ const out = ["arbi-cal-event", `arbi-cal-layer-${e.layer}`];
509
+ if (e.layer !== "event") out.push("arbi-cal-overlay");
510
+ if (e.status === "cancelled") out.push("arbi-cal-cancelled");
511
+ if (e.myPartstat === "declined") out.push("arbi-cal-declined");
512
+ else if (e.myPartstat === "needs_action") out.push("arbi-cal-needs-action");
513
+ else if (e.myPartstat === "tentative" || e.status === "tentative" || e.showAs === "tentative")
514
+ out.push("arbi-cal-tentative");
515
+ if (e.showAs === "free") out.push("arbi-cal-free");
516
+ if (e.showAs === "oof") out.push("arbi-cal-oof");
517
+ if (e.restricted) out.push("arbi-cal-restricted");
518
+ return out;
519
+ }
520
+ function toEventInput(e, zone) {
521
+ const editable = e.editable && e.layer === "event" && !e.restricted;
522
+ const base = {
523
+ id: e.instanceId,
524
+ title: e.title,
525
+ editable,
526
+ startEditable: editable,
527
+ durationEditable: editable,
528
+ classNames: eventStateClasses(e),
529
+ // The inner content paints the block (see calendar.css); the harness only carries the border
530
+ // colour (list-view dot, needs-action outline).
531
+ backgroundColor: "transparent",
532
+ borderColor: e.color,
533
+ textColor: "inherit",
534
+ extendedProps: { grid: e }
535
+ };
536
+ if (e.allDay) {
537
+ const start = e.startDate ?? utcToDate(e.startUtc, zone);
538
+ const end = e.endDate ?? (e.endUtc ? utcToDate(e.endUtc, zone) : addDays(start, 1));
539
+ return { ...base, allDay: true, start, end: end > start ? end : addDays(start, 1) };
540
+ }
541
+ return { ...base, allDay: false, start: e.startUtc, end: e.endUtc };
542
+ }
543
+ var LAYER_ICON = { facts: Lightbulb, workflows: Workflow, tasks: ListTodo };
544
+ function EventBody({ arg, labels }) {
545
+ const g = arg.event.extendedProps.grid;
546
+ if (!g) return /* @__PURE__ */ jsx("span", { children: arg.event.title });
547
+ const title = g.restricted ? labels.busy : g.title.trim() || labels.noTitle;
548
+ const type = arg.view.type;
549
+ const dot = type === "dayGridMonth" && !arg.event.allDay;
550
+ const list = type.startsWith("list") || type === "arbiAgenda";
551
+ const style = {
552
+ "--arbi-cal-color": g.color,
553
+ "--arbi-cal-fg": contrastText(g.color)
554
+ };
555
+ const OverlayIcon = g.layer !== "event" ? LAYER_ICON[g.layer] : null;
556
+ const icons = /* @__PURE__ */ jsxs(Fragment, { children: [
557
+ OverlayIcon && /* @__PURE__ */ jsx(OverlayIcon, { className: "arbi-cal-icon", "aria-hidden": true }),
558
+ g.restricted && /* @__PURE__ */ jsx(Lock, { className: "arbi-cal-icon", "aria-hidden": true }),
559
+ g.isRecurring && /* @__PURE__ */ jsx(Repeat, { className: "arbi-cal-icon", "aria-label": labels.recurring }),
560
+ g.hasConference && /* @__PURE__ */ jsx(Video, { className: "arbi-cal-icon", "aria-hidden": true })
561
+ ] });
562
+ if (list) {
563
+ return /* @__PURE__ */ jsxs(
564
+ "div",
565
+ {
566
+ className: "arbi-cal-list-item",
567
+ style,
568
+ "data-testid": "calendar-event",
569
+ "data-instance-id": g.instanceId,
570
+ children: [
571
+ /* @__PURE__ */ jsx("span", { className: "arbi-cal-title", children: title }),
572
+ icons,
573
+ g.location && !g.restricted && /* @__PURE__ */ jsxs("span", { className: "arbi-cal-location", children: [
574
+ /* @__PURE__ */ jsx(MapPin, { className: "arbi-cal-icon", "aria-hidden": true }),
575
+ g.location
576
+ ] })
577
+ ]
578
+ }
579
+ );
580
+ }
581
+ if (dot) {
582
+ return /* @__PURE__ */ jsxs(
583
+ "div",
584
+ {
585
+ className: "arbi-cal-dot-item",
586
+ style,
587
+ "data-testid": "calendar-event",
588
+ "data-instance-id": g.instanceId,
589
+ children: [
590
+ /* @__PURE__ */ jsx("span", { className: "arbi-cal-dot", "aria-hidden": true }),
591
+ arg.timeText && /* @__PURE__ */ jsx("span", { className: "arbi-cal-time", children: arg.timeText }),
592
+ /* @__PURE__ */ jsx("span", { className: "arbi-cal-title", children: title })
593
+ ]
594
+ }
595
+ );
596
+ }
597
+ return /* @__PURE__ */ jsxs(
598
+ "div",
599
+ {
600
+ className: "arbi-cal-event-inner",
601
+ style,
602
+ "data-testid": "calendar-event",
603
+ "data-instance-id": g.instanceId,
604
+ children: [
605
+ /* @__PURE__ */ jsxs("div", { className: "arbi-cal-event-line", children: [
606
+ /* @__PURE__ */ jsx("span", { className: "arbi-cal-title", children: title }),
607
+ icons
608
+ ] }),
609
+ !arg.event.allDay && arg.timeText && /* @__PURE__ */ jsx("div", { className: "arbi-cal-time", children: arg.timeText }),
610
+ !arg.event.allDay && g.location && !g.restricted && /* @__PURE__ */ jsx("div", { className: "arbi-cal-location", children: g.location })
611
+ ]
612
+ }
613
+ );
614
+ }
615
+ function isTypingTarget(t) {
616
+ if (!(t instanceof HTMLElement)) return false;
617
+ const tag = t.tagName;
618
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || t.isContentEditable;
619
+ }
620
+ function CalendarGrid({
621
+ events,
622
+ view,
623
+ date,
624
+ timeZone,
625
+ secondaryTimeZone,
626
+ weekStart,
627
+ workingHours,
628
+ showWeekNumbers = false,
629
+ nowIndicator = true,
630
+ onRangeChange,
631
+ onSelectSlot,
632
+ onEventClick,
633
+ onEventChange,
634
+ onViewChange,
635
+ onDateChange,
636
+ onCreateShortcut,
637
+ height,
638
+ labels: labelOverrides,
639
+ hideToolbar,
640
+ toolbarEnd,
641
+ className,
642
+ keyboardShortcuts = true
643
+ }) {
644
+ const labels = useMemo(() => mergeLabels(labelOverrides), [labelOverrides]);
645
+ const zone = safeZone(timeZone);
646
+ const secondZone = secondaryTimeZone && secondaryTimeZone !== zone && isValidTimeZone(secondaryTimeZone) ? secondaryTimeZone : "";
647
+ const rootRef = useRef(null);
648
+ const calRef = useRef(null);
649
+ const [narrow, setNarrow] = useState(false);
650
+ const [title, setTitle] = useState("");
651
+ const lastRange = useRef("");
652
+ const effective = effectiveCalendarView(view, narrow);
653
+ const cb = useRef({
654
+ onRangeChange,
655
+ onSelectSlot,
656
+ onEventClick,
657
+ onEventChange,
658
+ onViewChange,
659
+ onDateChange,
660
+ onCreateShortcut
661
+ });
662
+ cb.current = {
663
+ onRangeChange,
664
+ onSelectSlot,
665
+ onEventClick,
666
+ onEventChange,
667
+ onViewChange,
668
+ onDateChange,
669
+ onCreateShortcut
670
+ };
671
+ const api = useCallback(() => calRef.current?.getApi() ?? null, []);
672
+ useEffect(() => {
673
+ const el = rootRef.current;
674
+ if (!el || typeof ResizeObserver === "undefined") return;
675
+ const ro = new ResizeObserver(([entry]) => {
676
+ setNarrow(entry.contentRect.width > 0 && entry.contentRect.width < CALENDAR_NARROW_WIDTH);
677
+ });
678
+ ro.observe(el);
679
+ return () => ro.disconnect();
680
+ }, []);
681
+ useEffect(() => {
682
+ const a = api();
683
+ if (!a) return;
684
+ const fcView = FC_VIEW[effective];
685
+ if (a.view.type !== fcView) {
686
+ a.changeView(fcView, date);
687
+ return;
688
+ }
689
+ if (utcToDate(a.getDate(), zone) !== date) a.gotoDate(date);
690
+ }, [api, effective, date, zone]);
691
+ const fcEvents = useMemo(() => events.map((e) => toEventInput(e, zone)), [events, zone]);
692
+ const changeView = useCallback(
693
+ (v) => {
694
+ const a = api();
695
+ if (a) a.changeView(FC_VIEW[effectiveCalendarView(v, narrow)]);
696
+ cb.current.onViewChange?.(v);
697
+ },
698
+ [api, narrow]
699
+ );
700
+ const nav = useCallback(
701
+ (dir) => {
702
+ const a = api();
703
+ if (!a) return;
704
+ if (dir === "prev") a.prev();
705
+ else if (dir === "next") a.next();
706
+ else a.gotoDate(utcToDate(Date.now(), zone));
707
+ },
708
+ [api, zone]
709
+ );
710
+ useEffect(() => {
711
+ if (!keyboardShortcuts) return;
712
+ const onKey = (e) => {
713
+ if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.altKey || isTypingTarget(e.target))
714
+ return;
715
+ if (typeof document !== "undefined" && document.querySelector('[role="dialog"], [role="alertdialog"]'))
716
+ return;
717
+ const viewKey = {
718
+ d: "day",
719
+ w: "week",
720
+ x: "work_week",
721
+ m: "month",
722
+ a: "agenda",
723
+ y: "year"
724
+ };
725
+ const k = e.key.length === 1 ? e.key.toLowerCase() : e.key;
726
+ if (k === "t") nav("today");
727
+ else if (k === "j" || k === "n" || k === "ArrowRight") nav("next");
728
+ else if (k === "k" || k === "p" || k === "ArrowLeft") nav("prev");
729
+ else if (viewKey[k]) changeView(viewKey[k]);
730
+ else if (k === "c" && cb.current.onCreateShortcut) cb.current.onCreateShortcut();
731
+ else return;
732
+ e.preventDefault();
733
+ };
734
+ window.addEventListener("keydown", onKey);
735
+ return () => window.removeEventListener("keydown", onKey);
736
+ }, [keyboardShortcuts, nav, changeView]);
737
+ const onDatesSet = useCallback(
738
+ (arg) => {
739
+ setTitle(arg.view.title);
740
+ const anchor = utcToDate(arg.view.calendar.getDate(), zone);
741
+ const kind = VIEW_OF_FC[arg.view.type] ?? effective;
742
+ const key = `${kind}|${arg.start.toISOString()}|${arg.end.toISOString()}|${anchor}`;
743
+ if (key === lastRange.current) return;
744
+ lastRange.current = key;
745
+ cb.current.onRangeChange({ start: arg.start, end: arg.end, view: kind, date: anchor });
746
+ if (anchor !== date) cb.current.onDateChange?.(anchor);
747
+ },
748
+ [zone, effective, date]
749
+ );
750
+ const onSelect = useCallback(
751
+ (arg) => {
752
+ if (!cb.current.onSelectSlot) return;
753
+ if (arg.allDay) {
754
+ cb.current.onSelectSlot({
755
+ startUtc: arg.start.toISOString(),
756
+ endUtc: arg.end.toISOString(),
757
+ allDay: true,
758
+ startDate: arg.startStr.slice(0, 10),
759
+ endDate: arg.endStr.slice(0, 10)
760
+ });
761
+ } else {
762
+ cb.current.onSelectSlot({
763
+ startUtc: arg.start.toISOString(),
764
+ endUtc: arg.end.toISOString(),
765
+ allDay: false
766
+ });
767
+ }
768
+ api()?.unselect();
769
+ },
770
+ [api]
771
+ );
772
+ const emitChange = useCallback((event, revert) => {
773
+ const g = event.extendedProps.grid;
774
+ if (!g || !cb.current.onEventChange || !event.start) {
775
+ revert();
776
+ return;
777
+ }
778
+ const start = event.start;
779
+ if (event.allDay) {
780
+ const startDate = event.startStr.slice(0, 10);
781
+ const endDate = event.endStr ? event.endStr.slice(0, 10) : addDays(startDate, 1);
782
+ const end2 = event.end ?? new Date(start.getTime() + 864e5);
783
+ cb.current.onEventChange({
784
+ instanceId: g.instanceId,
785
+ eventId: g.eventId,
786
+ startUtc: start.toISOString(),
787
+ endUtc: end2.toISOString(),
788
+ allDay: true,
789
+ startDate,
790
+ endDate,
791
+ revert
792
+ });
793
+ return;
794
+ }
795
+ const end = event.end ?? new Date(start.getTime() + 36e5);
796
+ cb.current.onEventChange({
797
+ instanceId: g.instanceId,
798
+ eventId: g.eventId,
799
+ startUtc: start.toISOString(),
800
+ endUtc: end.toISOString(),
801
+ allDay: false,
802
+ revert
803
+ });
804
+ }, []);
805
+ const slotLabelContent = useCallback(
806
+ (arg) => {
807
+ if (!secondZone) return arg.text;
808
+ return /* @__PURE__ */ jsxs("div", { className: "arbi-cal-slot-labels", children: [
809
+ /* @__PURE__ */ jsx("span", { className: "arbi-cal-slot-secondary", children: (() => {
810
+ const t = convertWall(`${date}T${utcToTime(arg.date, zone)}`, zone, secondZone).slice(
811
+ 11,
812
+ 16
813
+ );
814
+ return t.endsWith(":00") ? t.slice(0, 2) : t;
815
+ })() }),
816
+ /* @__PURE__ */ jsx("span", { className: "arbi-cal-slot-primary", children: arg.text })
817
+ ] });
818
+ },
819
+ [secondZone, zone, date]
820
+ );
821
+ const allDayContent = useCallback(
822
+ (arg) => {
823
+ if (!secondZone) return arg.text;
824
+ return /* @__PURE__ */ jsxs("div", { className: "arbi-cal-slot-labels arbi-cal-zone-labels", children: [
825
+ /* @__PURE__ */ jsx("span", { className: "arbi-cal-slot-secondary", title: secondZone, children: zoneOffsetLabel(secondZone) }),
826
+ /* @__PURE__ */ jsx("span", { className: "arbi-cal-slot-primary", title: zone, children: zoneOffsetLabel(zone) })
827
+ ] });
828
+ },
829
+ [secondZone, zone]
830
+ );
831
+ const dayHeaderContent = useCallback(
832
+ (arg) => {
833
+ const t = arg.view.type;
834
+ if (!t.startsWith("timeGrid") && t !== "arbiWorkWeek") return arg.text;
835
+ return /* @__PURE__ */ jsxs("div", { className: cn("arbi-cal-dayhead", arg.isToday && "arbi-cal-dayhead-today"), children: [
836
+ /* @__PURE__ */ jsx("span", { className: "arbi-cal-dayhead-dow", children: formatInZone(arg.date, zone, { weekday: "short" }) }),
837
+ /* @__PURE__ */ jsx("span", { className: "arbi-cal-dayhead-num", children: formatInZone(arg.date, zone, { day: "numeric" }) })
838
+ ] });
839
+ },
840
+ [zone]
841
+ );
842
+ const scrollTime = useMemo(() => {
843
+ const starts = (workingHours ?? []).map((h2) => h2.start).sort();
844
+ const first = starts[0] ?? "08:00";
845
+ const h = Math.max(0, Number(first.slice(0, 2)) - 1);
846
+ return `${String(h).padStart(2, "0")}:00:00`;
847
+ }, [workingHours]);
848
+ const views = useMemo(
849
+ () => ({
850
+ arbiWorkWeek: {
851
+ type: "timeGrid",
852
+ duration: { weeks: 1 },
853
+ hiddenDays: workWeekHiddenDays(workingHours),
854
+ buttonText: labels.workWeek
855
+ },
856
+ arbiAgenda: {
857
+ type: "list",
858
+ duration: { days: 14 },
859
+ dateAlignment: "day",
860
+ buttonText: labels.agenda
861
+ },
862
+ multiMonthYear: { multiMonthMaxColumns: narrow ? 1 : 3 },
863
+ // Growing rows (height auto) never overflow, so cap explicitly there; else fit the cell.
864
+ dayGridMonth: { dayMaxEvents: height === "auto" ? 3 : true }
865
+ }),
866
+ [workingHours, labels, narrow, height]
867
+ );
868
+ const plugins = useMemo(
869
+ () => [
870
+ dayGridPlugin,
871
+ timeGridPlugin,
872
+ listPlugin,
873
+ interactionPlugin,
874
+ multiMonthPlugin,
875
+ getTemporalTimeZonePlugin()
876
+ ],
877
+ []
878
+ );
879
+ const viewChoices = narrow ? NARROW_VIEWS : VIEW_ORDER;
880
+ return /* @__PURE__ */ jsxs(
881
+ "div",
882
+ {
883
+ ref: rootRef,
884
+ className: cn(
885
+ "arbi-calendar flex h-full min-h-0 flex-col",
886
+ narrow && "arbi-calendar-narrow",
887
+ secondZone && "arbi-cal-dual",
888
+ className
889
+ ),
890
+ "data-testid": "calendar-grid",
891
+ "data-view": effective,
892
+ "data-time-zone": zone,
893
+ children: [
894
+ !hideToolbar && /* @__PURE__ */ jsxs(
895
+ "div",
896
+ {
897
+ className: "arbi-cal-toolbar flex flex-wrap items-center gap-2 px-1 pb-2",
898
+ "data-testid": "calendar-toolbar",
899
+ children: [
900
+ /* @__PURE__ */ jsx(
901
+ Button,
902
+ {
903
+ type: "button",
904
+ variant: "outline",
905
+ size: "sm",
906
+ onClick: () => nav("today"),
907
+ "data-testid": "calendar-today",
908
+ children: labels.today
909
+ }
910
+ ),
911
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
912
+ /* @__PURE__ */ jsx(
913
+ Button,
914
+ {
915
+ type: "button",
916
+ variant: "ghost",
917
+ size: "icon",
918
+ className: "h-8 w-8",
919
+ "aria-label": labels.previous,
920
+ onClick: () => nav("prev"),
921
+ "data-testid": "calendar-prev",
922
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
923
+ }
924
+ ),
925
+ /* @__PURE__ */ jsx(
926
+ Button,
927
+ {
928
+ type: "button",
929
+ variant: "ghost",
930
+ size: "icon",
931
+ className: "h-8 w-8",
932
+ "aria-label": labels.next,
933
+ onClick: () => nav("next"),
934
+ "data-testid": "calendar-next",
935
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
936
+ }
937
+ )
938
+ ] }),
939
+ /* @__PURE__ */ jsx(
940
+ "h2",
941
+ {
942
+ className: "min-w-0 flex-1 truncate text-base font-semibold sm:text-lg",
943
+ "data-testid": "calendar-title",
944
+ "aria-live": "polite",
945
+ children: title
946
+ }
947
+ ),
948
+ secondZone && !narrow && /* @__PURE__ */ jsxs(
949
+ "span",
950
+ {
951
+ className: "hidden text-xs text-muted-foreground md:inline",
952
+ "data-testid": "calendar-zones",
953
+ children: [
954
+ zoneCity(zone),
955
+ " ",
956
+ zoneOffsetLabel(zone),
957
+ " \xB7 ",
958
+ zoneCity(secondZone),
959
+ " ",
960
+ zoneOffsetLabel(secondZone)
961
+ ]
962
+ }
963
+ ),
964
+ narrow ? /* @__PURE__ */ jsx(
965
+ NativeSelect,
966
+ {
967
+ "aria-label": labels.view,
968
+ value: effective,
969
+ onChange: (e) => changeView(e.target.value),
970
+ "data-testid": "calendar-view-select",
971
+ className: "h-8",
972
+ children: viewChoices.map((v) => /* @__PURE__ */ jsx("option", { value: v, children: viewLabel(v, labels) }, v))
973
+ }
974
+ ) : /* @__PURE__ */ jsx(
975
+ "div",
976
+ {
977
+ className: "inline-flex rounded-md border border-border p-0.5",
978
+ role: "group",
979
+ "data-testid": "calendar-view-switch",
980
+ children: viewChoices.map((v) => /* @__PURE__ */ jsx(
981
+ "button",
982
+ {
983
+ type: "button",
984
+ "aria-pressed": effective === v,
985
+ onClick: () => changeView(v),
986
+ "data-testid": `calendar-view-${v}`,
987
+ className: cn(
988
+ "rounded px-2.5 py-1 text-xs font-medium transition-colors",
989
+ effective === v ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
990
+ ),
991
+ children: viewLabel(v, labels)
992
+ },
993
+ v
994
+ ))
995
+ }
996
+ ),
997
+ toolbarEnd
998
+ ]
999
+ }
1000
+ ),
1001
+ /* @__PURE__ */ jsx("div", { className: "arbi-cal-body min-h-0 flex-1", children: /* @__PURE__ */ jsx(
1002
+ FullCalendar,
1003
+ {
1004
+ ref: calRef,
1005
+ plugins,
1006
+ timeZone: zone,
1007
+ initialView: FC_VIEW[effective],
1008
+ initialDate: date,
1009
+ headerToolbar: false,
1010
+ height: height ?? "100%",
1011
+ views,
1012
+ events: fcEvents,
1013
+ firstDay: weekStart,
1014
+ weekNumbers: showWeekNumbers,
1015
+ weekNumberCalculation: weekStart === 1 ? "ISO" : "local",
1016
+ weekText: labels.weekAbbrev,
1017
+ nowIndicator,
1018
+ businessHours: toBusinessHours(workingHours),
1019
+ scrollTime,
1020
+ slotDuration: "00:30:00",
1021
+ snapDuration: "00:15:00",
1022
+ slotLabelFormat: {
1023
+ hour: "numeric",
1024
+ minute: "2-digit",
1025
+ omitZeroMinute: true,
1026
+ meridiem: "short"
1027
+ },
1028
+ eventTimeFormat: {
1029
+ hour: "numeric",
1030
+ minute: "2-digit",
1031
+ omitZeroMinute: true,
1032
+ meridiem: "short"
1033
+ },
1034
+ allDayText: labels.allDay,
1035
+ noEventsText: labels.noEvents,
1036
+ moreLinkText: labels.more,
1037
+ navLinks: true,
1038
+ navLinkDayClick: (d) => {
1039
+ const a = api();
1040
+ a?.changeView(FC_VIEW.day, d);
1041
+ cb.current.onViewChange?.("day");
1042
+ },
1043
+ selectable: !!onSelectSlot,
1044
+ selectMirror: true,
1045
+ unselectAuto: true,
1046
+ editable: !!onEventChange,
1047
+ eventResizableFromStart: true,
1048
+ eventStartEditable: !!onEventChange,
1049
+ eventDurationEditable: !!onEventChange,
1050
+ stickyHeaderDates: true,
1051
+ expandRows: true,
1052
+ dayMaxEvents: height === "auto" ? 3 : true,
1053
+ eventDisplay: "auto",
1054
+ datesSet: onDatesSet,
1055
+ select: onSelect,
1056
+ eventClick: (arg) => {
1057
+ arg.jsEvent.preventDefault();
1058
+ const g = arg.event.extendedProps.grid;
1059
+ if (g) cb.current.onEventClick?.(g.instanceId, g.eventId);
1060
+ },
1061
+ eventDrop: (arg) => emitChange(arg.event, arg.revert),
1062
+ eventResize: (arg) => emitChange(arg.event, arg.revert),
1063
+ eventContent: (arg) => /* @__PURE__ */ jsx(EventBody, { arg, labels }),
1064
+ slotLabelContent,
1065
+ allDayContent,
1066
+ dayHeaderContent
1067
+ }
1068
+ ) })
1069
+ ]
1070
+ }
1071
+ );
1072
+ }
1073
+ function MiniMonth({
1074
+ date,
1075
+ weekStart,
1076
+ timeZone,
1077
+ onSelect,
1078
+ highlightRange,
1079
+ showWeekNumbers,
1080
+ labels: labelOverrides,
1081
+ className
1082
+ }) {
1083
+ const labels = mergeLabels(labelOverrides);
1084
+ const [view, setView] = useState(() => ({ for: date, month: `${date.slice(0, 7)}-01` }));
1085
+ if (view.for !== date) setView({ for: date, month: `${date.slice(0, 7)}-01` });
1086
+ const month = view.for === date ? view.month : `${date.slice(0, 7)}-01`;
1087
+ const setMonth = (m) => setView({ for: date, month: m });
1088
+ const today = todayInZone(timeZone);
1089
+ const days = useMemo(() => {
1090
+ const first = startOfWeek(month, weekStart);
1091
+ return Array.from({ length: 42 }, (_, i) => addDays(first, i));
1092
+ }, [month, weekStart]);
1093
+ const weekdays = days.slice(0, 7).map((d) => formatPlainDate(d, { weekday: "narrow" }));
1094
+ const monthTitle = formatPlainDate(month, { month: "long", year: "numeric" });
1095
+ return /* @__PURE__ */ jsxs("div", { className: cn("select-none text-xs", className), "data-testid": "calendar-mini-month", children: [
1096
+ /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between px-1", children: [
1097
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", "aria-live": "polite", children: monthTitle }),
1098
+ /* @__PURE__ */ jsxs("div", { className: "flex", children: [
1099
+ /* @__PURE__ */ jsx(
1100
+ "button",
1101
+ {
1102
+ type: "button",
1103
+ className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
1104
+ "aria-label": labels.previous,
1105
+ onClick: () => setMonth(addMonths(month, -1)),
1106
+ "data-testid": "calendar-mini-prev",
1107
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
1108
+ }
1109
+ ),
1110
+ /* @__PURE__ */ jsx(
1111
+ "button",
1112
+ {
1113
+ type: "button",
1114
+ className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
1115
+ "aria-label": labels.next,
1116
+ onClick: () => setMonth(addMonths(month, 1)),
1117
+ "data-testid": "calendar-mini-next",
1118
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
1119
+ }
1120
+ )
1121
+ ] })
1122
+ ] }),
1123
+ /* @__PURE__ */ jsxs(
1124
+ "div",
1125
+ {
1126
+ className: cn(
1127
+ "grid gap-y-0.5 text-center",
1128
+ showWeekNumbers ? "grid-cols-8" : "grid-cols-7"
1129
+ ),
1130
+ role: "grid",
1131
+ "aria-label": monthTitle,
1132
+ children: [
1133
+ showWeekNumbers && /* @__PURE__ */ jsx("span", { className: "py-1 text-[10px] text-muted-foreground", children: labels.weekAbbrev }),
1134
+ weekdays.map((w, i) => /* @__PURE__ */ jsx(
1135
+ "span",
1136
+ {
1137
+ className: "py-1 text-[10px] font-medium uppercase text-muted-foreground",
1138
+ role: "columnheader",
1139
+ children: w
1140
+ },
1141
+ i
1142
+ )),
1143
+ days.map((d, i) => {
1144
+ const inMonth = d.slice(0, 7) === month.slice(0, 7);
1145
+ const inRange = !!highlightRange && d >= highlightRange.start && d <= highlightRange.end;
1146
+ const selected = d === date;
1147
+ const isToday = d === today;
1148
+ return /* @__PURE__ */ jsx(
1149
+ MiniDay,
1150
+ {
1151
+ d,
1152
+ weekCell: showWeekNumbers && i % 7 === 0 ? isoWeekNumber(d) : null,
1153
+ inMonth,
1154
+ inRange,
1155
+ selected,
1156
+ isToday,
1157
+ onSelect
1158
+ },
1159
+ d
1160
+ );
1161
+ })
1162
+ ]
1163
+ }
1164
+ )
1165
+ ] });
1166
+ }
1167
+ function MiniDay({
1168
+ d,
1169
+ weekCell,
1170
+ inMonth,
1171
+ inRange,
1172
+ selected,
1173
+ isToday,
1174
+ onSelect
1175
+ }) {
1176
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1177
+ weekCell !== null && /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center text-[10px] text-muted-foreground", children: weekCell }),
1178
+ /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", inRange && "bg-primary/10"), children: /* @__PURE__ */ jsx(
1179
+ "button",
1180
+ {
1181
+ type: "button",
1182
+ role: "gridcell",
1183
+ "aria-selected": selected,
1184
+ "aria-current": isToday ? "date" : void 0,
1185
+ "aria-label": formatPlainDate(d, { dateStyle: "full" }),
1186
+ onClick: () => onSelect(d),
1187
+ "data-testid": `calendar-mini-day-${d}`,
1188
+ className: cn(
1189
+ "flex h-7 w-7 items-center justify-center rounded-full tabular-nums transition-colors",
1190
+ !inMonth && "text-muted-foreground/60",
1191
+ isToday && !selected && "font-semibold text-primary",
1192
+ selected ? "bg-primary text-primary-foreground" : "hover:bg-muted"
1193
+ ),
1194
+ children: Number(d.slice(8, 10))
1195
+ }
1196
+ ) })
1197
+ ] });
1198
+ }
1199
+ var GROUPS = ["mine", "shared", "workspace", "subscribed", "rooms"];
1200
+ var groupLabel = (g, l) => ({
1201
+ mine: l.myCalendars,
1202
+ shared: l.sharedCalendars,
1203
+ workspace: l.workspaceCalendars,
1204
+ subscribed: l.subscribedCalendars,
1205
+ rooms: l.rooms
1206
+ })[g];
1207
+ function Swatch({ color, checked }) {
1208
+ return /* @__PURE__ */ jsx(
1209
+ "span",
1210
+ {
1211
+ className: "flex h-4 w-4 shrink-0 items-center justify-center rounded-[4px] border-2",
1212
+ style: { borderColor: color, backgroundColor: checked ? color : "transparent" },
1213
+ "aria-hidden": true,
1214
+ children: checked && /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", className: "h-3 w-3", fill: "none", stroke: "white", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M2.5 6.5l2.5 2.5 4.5-5" }) })
1215
+ }
1216
+ );
1217
+ }
1218
+ function CalendarList({
1219
+ calendars,
1220
+ overlays,
1221
+ onToggleHidden,
1222
+ onColorChange,
1223
+ onToggleOverlay,
1224
+ onCreate,
1225
+ onEdit,
1226
+ onImport,
1227
+ onExport,
1228
+ onDelete,
1229
+ onShare,
1230
+ onRefresh,
1231
+ onPublish,
1232
+ onSubscribe,
1233
+ onAddHolidays,
1234
+ onImportFile,
1235
+ labels: labelOverrides,
1236
+ className
1237
+ }) {
1238
+ const l = mergeLabels(labelOverrides);
1239
+ const hasAddMenu = !!(onSubscribe || onAddHolidays || onImportFile);
1240
+ const plusButton = /* @__PURE__ */ jsx(
1241
+ "button",
1242
+ {
1243
+ type: "button",
1244
+ className: "rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
1245
+ "aria-label": hasAddMenu ? l.addCalendar : l.createCalendar,
1246
+ title: hasAddMenu ? l.addCalendar : l.createCalendar,
1247
+ onClick: hasAddMenu ? void 0 : onCreate,
1248
+ "data-testid": "calendar-list-create",
1249
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" })
1250
+ }
1251
+ );
1252
+ const overlayRows = [
1253
+ ["facts", l.facts, Lightbulb],
1254
+ ["workflows", l.workflows, Workflow],
1255
+ ["tasks", l.tasks, ListTodo]
1256
+ ];
1257
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3 text-sm", className), "data-testid": "calendar-list", children: [
1258
+ GROUPS.map((g) => {
1259
+ const items = calendars.filter((c) => c.group === g);
1260
+ if (!items.length && g !== "mine") return null;
1261
+ return /* @__PURE__ */ jsxs("section", { "aria-label": groupLabel(g, l), children: [
1262
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-1 pb-1", children: [
1263
+ /* @__PURE__ */ jsx("h4", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: groupLabel(g, l) }),
1264
+ g === "mine" && (hasAddMenu ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1265
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: plusButton }),
1266
+ /* @__PURE__ */ jsxs(
1267
+ DropdownMenuContent,
1268
+ {
1269
+ align: "end",
1270
+ className: "w-56",
1271
+ "data-testid": "calendar-add-menu",
1272
+ children: [
1273
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: onCreate, "data-testid": "calendar-add-new", children: l.createCalendar }),
1274
+ onSubscribe && /* @__PURE__ */ jsx(
1275
+ DropdownMenuItem,
1276
+ {
1277
+ onSelect: onSubscribe,
1278
+ "data-testid": "calendar-add-subscribe",
1279
+ children: l.subscribeFromUrl
1280
+ }
1281
+ ),
1282
+ onAddHolidays && /* @__PURE__ */ jsx(
1283
+ DropdownMenuItem,
1284
+ {
1285
+ onSelect: onAddHolidays,
1286
+ "data-testid": "calendar-add-holidays",
1287
+ children: l.addHolidays
1288
+ }
1289
+ ),
1290
+ onImportFile && /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: onImportFile, "data-testid": "calendar-add-import", children: l.importIcs })
1291
+ ]
1292
+ }
1293
+ )
1294
+ ] }) : plusButton)
1295
+ ] }),
1296
+ /* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: items.map((c) => {
1297
+ const name = c.locked ? l.locked : c.name || l.noTitle;
1298
+ const isOwner = c.role === "owner";
1299
+ return /* @__PURE__ */ jsxs(
1300
+ "li",
1301
+ {
1302
+ className: "group flex items-center gap-2 rounded-md px-1 py-1 hover:bg-muted/60",
1303
+ "data-testid": `calendar-list-item-${c.id}`,
1304
+ children: [
1305
+ /* @__PURE__ */ jsxs("label", { className: "flex min-w-0 flex-1 cursor-pointer items-center gap-2", children: [
1306
+ /* @__PURE__ */ jsx(
1307
+ "input",
1308
+ {
1309
+ type: "checkbox",
1310
+ className: "sr-only",
1311
+ checked: !c.hidden,
1312
+ onChange: () => onToggleHidden(c.id),
1313
+ "aria-label": `${c.hidden ? l.showCalendar : l.hideCalendar} ${name}`,
1314
+ "data-testid": `calendar-list-toggle-${c.id}`
1315
+ }
1316
+ ),
1317
+ /* @__PURE__ */ jsx(Swatch, { color: c.color, checked: !c.hidden }),
1318
+ /* @__PURE__ */ jsx(
1319
+ "span",
1320
+ {
1321
+ className: cn(
1322
+ "min-w-0 truncate",
1323
+ c.locked && "italic text-muted-foreground"
1324
+ ),
1325
+ children: name
1326
+ }
1327
+ ),
1328
+ c.locked && /* @__PURE__ */ jsx(Lock, { className: "h-3 w-3 shrink-0 text-muted-foreground", "aria-hidden": true }),
1329
+ c.provider && c.provider !== "native" && /* @__PURE__ */ jsx(
1330
+ "span",
1331
+ {
1332
+ className: "shrink-0 rounded border border-border px-1 text-[10px] leading-4 text-muted-foreground",
1333
+ "data-testid": `calendar-list-provider-${c.id}`,
1334
+ children: {
1335
+ google: "Google",
1336
+ microsoft: "Outlook",
1337
+ caldav: "CalDAV",
1338
+ ics: "ICS"
1339
+ }[c.provider] ?? c.provider
1340
+ }
1341
+ )
1342
+ ] }),
1343
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1344
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1345
+ "button",
1346
+ {
1347
+ type: "button",
1348
+ className: "rounded p-0.5 text-muted-foreground opacity-60 hover:bg-muted hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100",
1349
+ "aria-label": `${l.editCalendar}: ${name}`,
1350
+ "data-testid": `calendar-list-menu-${c.id}`,
1351
+ children: /* @__PURE__ */ jsx(EllipsisVertical, { className: "h-4 w-4" })
1352
+ }
1353
+ ) }),
1354
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [
1355
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs", children: l.calendarColor }),
1356
+ /* @__PURE__ */ jsx("div", { className: "px-2 pb-2", children: /* @__PURE__ */ jsx(
1357
+ ColorSwatches,
1358
+ {
1359
+ value: c.color,
1360
+ onChange: (color) => color && onColorChange(c.id, color)
1361
+ }
1362
+ ) }),
1363
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
1364
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: () => onEdit(c.id), disabled: c.locked, children: l.editCalendar }),
1365
+ onShare && isOwner && c.group !== "subscribed" && /* @__PURE__ */ jsx(
1366
+ DropdownMenuItem,
1367
+ {
1368
+ onSelect: () => onShare(c.id),
1369
+ disabled: c.locked,
1370
+ "data-testid": `calendar-list-share-${c.id}`,
1371
+ children: l.shareCalendar
1372
+ }
1373
+ ),
1374
+ onPublish && isOwner && c.group === "mine" && (!c.provider || c.provider === "native") && /* @__PURE__ */ jsx(
1375
+ DropdownMenuItem,
1376
+ {
1377
+ onSelect: () => onPublish(c.id),
1378
+ disabled: c.locked,
1379
+ "data-testid": `calendar-list-publish-${c.id}`,
1380
+ children: l.publishTo
1381
+ }
1382
+ ),
1383
+ onRefresh && c.group === "subscribed" && /* @__PURE__ */ jsx(
1384
+ DropdownMenuItem,
1385
+ {
1386
+ onSelect: () => onRefresh(c.id),
1387
+ "data-testid": `calendar-list-refresh-${c.id}`,
1388
+ children: l.refreshCalendar
1389
+ }
1390
+ ),
1391
+ c.canWrite && /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: () => onImport(c.id), disabled: c.locked, children: l.importCalendar }),
1392
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: () => onExport(c.id), disabled: c.locked, children: l.exportCalendar }),
1393
+ isOwner && !c.isDefault && /* @__PURE__ */ jsxs(Fragment, { children: [
1394
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
1395
+ /* @__PURE__ */ jsx(
1396
+ DropdownMenuItem,
1397
+ {
1398
+ className: "text-destructive focus:text-destructive",
1399
+ onSelect: () => onDelete(c.id),
1400
+ children: l.deleteCalendar
1401
+ }
1402
+ )
1403
+ ] })
1404
+ ] })
1405
+ ] })
1406
+ ]
1407
+ },
1408
+ c.id
1409
+ );
1410
+ }) })
1411
+ ] }, g);
1412
+ }),
1413
+ /* @__PURE__ */ jsxs("section", { "aria-label": l.overlays, children: [
1414
+ /* @__PURE__ */ jsx("h4", { className: "px-1 pb-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: l.overlays }),
1415
+ /* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: overlayRows.map(([layer, label, Icon]) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 rounded-md px-1 py-1 hover:bg-muted/60", children: [
1416
+ /* @__PURE__ */ jsx(
1417
+ "input",
1418
+ {
1419
+ type: "checkbox",
1420
+ className: "h-4 w-4 accent-[hsl(var(--primary))]",
1421
+ checked: overlays[layer],
1422
+ onChange: () => onToggleOverlay(layer),
1423
+ "data-testid": `calendar-overlay-${layer}`
1424
+ }
1425
+ ),
1426
+ /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 text-muted-foreground", "aria-hidden": true }),
1427
+ /* @__PURE__ */ jsx("span", { children: label })
1428
+ ] }) }, layer)) })
1429
+ ] })
1430
+ ] });
1431
+ }
1432
+
1433
+ // src/calendar/draft.ts
1434
+ var isAllDayDraft = (d) => !!d.start.date;
1435
+ var datePart = (spec) => spec.date ?? (spec.date_time ?? "").slice(0, 10);
1436
+ var timePart = (spec) => (spec.date_time ?? "").slice(11, 16);
1437
+ var specZone = (spec, defaultZone) => safeZone(spec.time_zone, defaultZone);
1438
+ var specDate = datePart;
1439
+ var specTime = timePart;
1440
+ function specEpochMs(spec, defaultZone) {
1441
+ if (spec.date) return wallToEpochMs(spec.date, defaultZone);
1442
+ return wallToEpochMs(spec.date_time ?? "", specZone(spec, defaultZone));
1443
+ }
1444
+ function draftRangeUtc(d, defaultZone) {
1445
+ return { startMs: specEpochMs(d.start, defaultZone), endMs: specEpochMs(d.end, defaultZone) };
1446
+ }
1447
+ function allDayInclusiveEnd(d) {
1448
+ const start = d.start.date ?? "";
1449
+ const end = d.end.date ?? start;
1450
+ const last = addDays(end, -1);
1451
+ return last < start ? start : last;
1452
+ }
1453
+ function timedSpec(date, time, zone) {
1454
+ return { date: null, date_time: `${date}T${time || "00:00"}`, time_zone: zone ?? null };
1455
+ }
1456
+ function setDraftStart(d, change, defaultZone) {
1457
+ if (isAllDayDraft(d)) {
1458
+ const oldStart = d.start.date ?? change.date ?? "";
1459
+ const start2 = change.date ?? oldStart;
1460
+ const days = Math.max(1, diffDays(oldStart, d.end.date ?? addDays(oldStart, 1)));
1461
+ return { ...d, start: { date: start2 }, end: { date: addDays(start2, days) } };
1462
+ }
1463
+ const { startMs, endMs } = draftRangeUtc(d, defaultZone);
1464
+ const duration = Math.max(0, endMs - startMs);
1465
+ const startZone = change.timeZone !== void 0 ? change.timeZone : d.start.time_zone ?? null;
1466
+ const start = timedSpec(
1467
+ change.date ?? datePart(d.start),
1468
+ change.time ?? timePart(d.start),
1469
+ startZone
1470
+ );
1471
+ const newStartMs = specEpochMs(start, defaultZone);
1472
+ const endZone = specZone(d.end, defaultZone);
1473
+ const endWall = utcToWall(newStartMs + duration, endZone);
1474
+ return {
1475
+ ...d,
1476
+ start,
1477
+ end: { date: null, date_time: endWall, time_zone: d.end.time_zone ?? null }
1478
+ };
1479
+ }
1480
+ function setDraftEnd(d, change) {
1481
+ if (isAllDayDraft(d)) {
1482
+ const last = change.date ?? allDayInclusiveEnd(d);
1483
+ return { ...d, end: { date: addDays(last, 1) } };
1484
+ }
1485
+ const zone = change.timeZone !== void 0 ? change.timeZone : d.end.time_zone ?? null;
1486
+ return {
1487
+ ...d,
1488
+ end: timedSpec(change.date ?? datePart(d.end), change.time ?? timePart(d.end), zone)
1489
+ };
1490
+ }
1491
+ function setDraftAllDay(d, allDay, defaultZone, defaultStartTime = "09:00", defaultDurationMinutes = 60) {
1492
+ if (allDay === isAllDayDraft(d)) return d;
1493
+ if (allDay) {
1494
+ const start2 = datePart(d.start);
1495
+ const endDay = datePart(d.end) || start2;
1496
+ const lastDay = timePart(d.end) === "00:00" && endDay > start2 ? addDays(endDay, -1) : endDay;
1497
+ return {
1498
+ ...d,
1499
+ start: { date: start2 },
1500
+ end: { date: addDays(lastDay < start2 ? start2 : lastDay, 1) }
1501
+ };
1502
+ }
1503
+ const zone = safeZone(defaultZone);
1504
+ const start = timedSpec(d.start.date ?? "", defaultStartTime, zone);
1505
+ const startMs = specEpochMs(start, zone);
1506
+ const end = {
1507
+ date: null,
1508
+ date_time: utcToWall(startMs + defaultDurationMinutes * 6e4, zone),
1509
+ time_zone: zone
1510
+ };
1511
+ return { ...d, start, end };
1512
+ }
1513
+ function draftFromSlot(slot, timeZone, calendarId, title = "") {
1514
+ const zone = safeZone(timeZone);
1515
+ if (slot.allDay) {
1516
+ const start = slot.startDate ?? utcToDate(slot.startUtc, zone);
1517
+ const end = slot.endDate ?? addDays(start, 1);
1518
+ return { calendar_id: calendarId, title, start: { date: start }, end: { date: end } };
1519
+ }
1520
+ return {
1521
+ calendar_id: calendarId,
1522
+ title,
1523
+ start: { date: null, date_time: utcToWall(slot.startUtc, zone), time_zone: zone },
1524
+ end: { date: null, date_time: utcToWall(slot.endUtc, zone), time_zone: zone }
1525
+ };
1526
+ }
1527
+ function validateDraft(d, defaultZone) {
1528
+ if (!d.title.trim()) return "titleRequired";
1529
+ if (isAllDayDraft(d)) {
1530
+ if (!d.start.date || !d.end.date || d.end.date <= d.start.date) return "invalidRange";
1531
+ return null;
1532
+ }
1533
+ if (!d.start.date_time || !d.end.date_time) return "invalidRange";
1534
+ const { startMs, endMs } = draftRangeUtc(d, defaultZone);
1535
+ if (!(endMs >= startMs)) return "invalidRange";
1536
+ return null;
1537
+ }
1538
+ var RRULE_WEEKDAYS = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];
1539
+ function weekdayOf(date) {
1540
+ return RRULE_WEEKDAYS[Temporal.PlainDate.from(date).dayOfWeek - 1];
1541
+ }
1542
+ function setPosOf(date, preferLast = false) {
1543
+ const d = Temporal.PlainDate.from(date);
1544
+ const isLast = d.day + 7 > d.daysInMonth;
1545
+ const nth = Math.ceil(d.day / 7);
1546
+ if (isLast && (preferLast || nth > 4)) return -1;
1547
+ return Math.min(nth, 4);
1548
+ }
1549
+ function defaultRecurrenceForm(startDate, freq = "none") {
1550
+ const d = Temporal.PlainDate.from(startDate);
1551
+ return {
1552
+ freq,
1553
+ interval: 1,
1554
+ weekdays: [weekdayOf(startDate)],
1555
+ monthlyMode: "day",
1556
+ monthDay: d.day,
1557
+ setPos: setPosOf(startDate),
1558
+ setPosDay: weekdayOf(startDate),
1559
+ month: d.month,
1560
+ end: "never",
1561
+ until: null,
1562
+ count: 10,
1563
+ raw: ""
1564
+ };
1565
+ }
1566
+ function untilValue(until, ctx) {
1567
+ if (ctx.allDay) return until.replace(/-/g, "");
1568
+ const zone = ctx.timeZone || "UTC";
1569
+ const utc = wallToUtc(`${until}T23:59:59`, zone);
1570
+ const t = Temporal.Instant.from(utc).toZonedDateTimeISO("UTC");
1571
+ const p = (n, w = 2) => String(n).padStart(w, "0");
1572
+ return `${p(t.year, 4)}${p(t.month)}${p(t.day)}T${p(t.hour)}${p(t.minute)}${p(t.second)}Z`;
1573
+ }
1574
+ function buildRRule(form, ctx = {}) {
1575
+ if (form.freq === "none") return null;
1576
+ if (form.freq === "custom") {
1577
+ const raw = form.raw.trim().replace(/^RRULE:/i, "");
1578
+ return raw || null;
1579
+ }
1580
+ const parts = [`FREQ=${form.freq.toUpperCase()}`];
1581
+ const interval = Math.max(1, Math.floor(form.interval || 1));
1582
+ if (interval > 1) parts.push(`INTERVAL=${interval}`);
1583
+ if (form.freq === "weekly") {
1584
+ const days = RRULE_WEEKDAYS.filter((d) => form.weekdays.includes(d));
1585
+ if (days.length) parts.push(`BYDAY=${days.join(",")}`);
1586
+ } else if (form.freq === "monthly" || form.freq === "yearly") {
1587
+ if (form.freq === "yearly") parts.push(`BYMONTH=${form.month}`);
1588
+ if (form.monthlyMode === "day") {
1589
+ parts.push(`BYMONTHDAY=${form.monthDay}`);
1590
+ } else {
1591
+ parts.push(`BYDAY=${form.setPosDay}`);
1592
+ parts.push(`BYSETPOS=${form.setPos}`);
1593
+ }
1594
+ }
1595
+ if (form.end === "until" && form.until) parts.push(`UNTIL=${untilValue(form.until, ctx)}`);
1596
+ else if (form.end === "count") parts.push(`COUNT=${Math.max(1, Math.floor(form.count || 1))}`);
1597
+ return parts.join(";");
1598
+ }
1599
+ function tokenize(rrule) {
1600
+ const out = /* @__PURE__ */ new Map();
1601
+ for (const seg of rrule.replace(/^RRULE:/i, "").split(";")) {
1602
+ const i = seg.indexOf("=");
1603
+ if (i > 0)
1604
+ out.set(
1605
+ seg.slice(0, i).trim().toUpperCase(),
1606
+ seg.slice(i + 1).trim().toUpperCase()
1607
+ );
1608
+ }
1609
+ return out;
1610
+ }
1611
+ function parseUntil(value, ctx) {
1612
+ const m = /^(\d{4})(\d{2})(\d{2})(?:T(\d{2})(\d{2})(\d{2})(Z?))?$/.exec(value);
1613
+ if (!m) return null;
1614
+ const date = `${m[1]}-${m[2]}-${m[3]}`;
1615
+ if (!m[4] || m[7] !== "Z") return date;
1616
+ const inst = Temporal.Instant.from(`${date}T${m[4]}:${m[5]}:${m[6]}Z`);
1617
+ return inst.toZonedDateTimeISO(ctx.timeZone || "UTC").toPlainDate().toString();
1618
+ }
1619
+ var SUPPORTED = /* @__PURE__ */ new Set([
1620
+ "FREQ",
1621
+ "INTERVAL",
1622
+ "BYDAY",
1623
+ "BYMONTHDAY",
1624
+ "BYMONTH",
1625
+ "BYSETPOS",
1626
+ "UNTIL",
1627
+ "COUNT",
1628
+ "WKST"
1629
+ ]);
1630
+ function parseRRule(rrule, startDate, ctx = {}) {
1631
+ const base = defaultRecurrenceForm(startDate);
1632
+ if (!rrule || !rrule.trim()) return base;
1633
+ const raw = rrule.trim().replace(/^RRULE:/i, "");
1634
+ const custom = { ...base, freq: "custom", raw };
1635
+ const t = tokenize(raw);
1636
+ for (const key of t.keys()) if (!SUPPORTED.has(key)) return custom;
1637
+ const freq = t.get("FREQ");
1638
+ const form = { ...base, raw };
1639
+ const interval = Number(t.get("INTERVAL") ?? "1");
1640
+ if (!Number.isInteger(interval) || interval < 1) return custom;
1641
+ form.interval = interval;
1642
+ if (t.has("UNTIL") && t.has("COUNT")) return custom;
1643
+ if (t.has("UNTIL")) {
1644
+ const until = parseUntil(t.get("UNTIL") ?? "", ctx);
1645
+ if (!until) return custom;
1646
+ form.end = "until";
1647
+ form.until = until;
1648
+ } else if (t.has("COUNT")) {
1649
+ const count = Number(t.get("COUNT"));
1650
+ if (!Number.isInteger(count) || count < 1) return custom;
1651
+ form.end = "count";
1652
+ form.count = count;
1653
+ }
1654
+ const byday = t.get("BYDAY");
1655
+ const bymonthday = t.get("BYMONTHDAY");
1656
+ const bymonth = t.get("BYMONTH");
1657
+ const bysetpos = t.get("BYSETPOS");
1658
+ const nthWeekday = () => {
1659
+ if (!byday || bymonthday) return false;
1660
+ const m = /^([+-]?\d)?(MO|TU|WE|TH|FR|SA|SU)$/.exec(byday);
1661
+ if (!m) return false;
1662
+ const pos = Number(m[1] ?? bysetpos ?? NaN);
1663
+ if (m[1] && bysetpos) return false;
1664
+ if (![1, 2, 3, 4, -1].includes(pos)) return false;
1665
+ form.monthlyMode = "weekday";
1666
+ form.setPos = pos;
1667
+ form.setPosDay = m[2];
1668
+ return true;
1669
+ };
1670
+ const monthDay = () => {
1671
+ if (!bymonthday || byday || bysetpos) return false;
1672
+ const n = Number(bymonthday);
1673
+ if (!Number.isInteger(n) || n < 1 || n > 31) return false;
1674
+ form.monthlyMode = "day";
1675
+ form.monthDay = n;
1676
+ return true;
1677
+ };
1678
+ switch (freq) {
1679
+ case "DAILY":
1680
+ if (byday || bymonthday || bymonth || bysetpos) return custom;
1681
+ form.freq = "daily";
1682
+ return form;
1683
+ case "WEEKLY": {
1684
+ if (bymonthday || bymonth || bysetpos) return custom;
1685
+ form.freq = "weekly";
1686
+ if (byday) {
1687
+ const days = byday.split(",");
1688
+ if (!days.every((d) => RRULE_WEEKDAYS.includes(d))) return custom;
1689
+ form.weekdays = days;
1690
+ }
1691
+ return form;
1692
+ }
1693
+ case "MONTHLY":
1694
+ if (bymonth) return custom;
1695
+ form.freq = "monthly";
1696
+ if (!byday && !bymonthday && !bysetpos) return form;
1697
+ return nthWeekday() || monthDay() ? form : custom;
1698
+ case "YEARLY": {
1699
+ form.freq = "yearly";
1700
+ if (bymonth) {
1701
+ const n = Number(bymonth);
1702
+ if (!Number.isInteger(n) || n < 1 || n > 12) return custom;
1703
+ form.month = n;
1704
+ }
1705
+ if (!byday && !bymonthday && !bysetpos) return form;
1706
+ if (!bymonth) return custom;
1707
+ return nthWeekday() || monthDay() ? form : custom;
1708
+ }
1709
+ default:
1710
+ return custom;
1711
+ }
1712
+ }
1713
+ var DEFAULT_RECURRENCE_DESCRIBE_LABELS = {
1714
+ every: "Every",
1715
+ day: "day",
1716
+ days: "days",
1717
+ week: "week",
1718
+ weeks: "weeks",
1719
+ month: "month",
1720
+ months: "months",
1721
+ year: "year",
1722
+ years: "years",
1723
+ on: "on",
1724
+ onDay: "on day",
1725
+ until: "until",
1726
+ times: "times",
1727
+ ordinals: { 1: "first", 2: "second", 3: "third", 4: "fourth", [-1]: "last" },
1728
+ weekdayNames: {
1729
+ MO: "Monday",
1730
+ TU: "Tuesday",
1731
+ WE: "Wednesday",
1732
+ TH: "Thursday",
1733
+ FR: "Friday",
1734
+ SA: "Saturday",
1735
+ SU: "Sunday"
1736
+ },
1737
+ monthNames: [
1738
+ "January",
1739
+ "February",
1740
+ "March",
1741
+ "April",
1742
+ "May",
1743
+ "June",
1744
+ "July",
1745
+ "August",
1746
+ "September",
1747
+ "October",
1748
+ "November",
1749
+ "December"
1750
+ ],
1751
+ doesNotRepeat: "Does not repeat",
1752
+ custom: "Custom rule"
1753
+ };
1754
+ function describeRecurrence(form, labels = DEFAULT_RECURRENCE_DESCRIBE_LABELS) {
1755
+ const l = labels;
1756
+ if (form.freq === "none") return l.doesNotRepeat;
1757
+ if (form.freq === "custom") return `${l.custom}: ${form.raw}`;
1758
+ const unit = {
1759
+ daily: [l.day, l.days],
1760
+ weekly: [l.week, l.weeks],
1761
+ monthly: [l.month, l.months],
1762
+ yearly: [l.year, l.years]
1763
+ }[form.freq];
1764
+ let text = form.interval > 1 ? `${l.every} ${form.interval} ${unit[1]}` : `${l.every} ${unit[0]}`;
1765
+ if (form.freq === "weekly" && form.weekdays.length) {
1766
+ const days = RRULE_WEEKDAYS.filter((d) => form.weekdays.includes(d)).map(
1767
+ (d) => l.weekdayNames[d]
1768
+ );
1769
+ text += ` ${l.on} ${days.join(", ")}`;
1770
+ }
1771
+ if (form.freq === "monthly" || form.freq === "yearly") {
1772
+ const monthName = form.freq === "yearly" ? ` ${l.monthNames[form.month - 1]}` : "";
1773
+ text += form.monthlyMode === "day" ? form.freq === "yearly" ? ` ${l.on}${monthName} ${form.monthDay}` : ` ${l.onDay} ${form.monthDay}` : ` ${l.on} ${l.ordinals[form.setPos]} ${l.weekdayNames[form.setPosDay]}${monthName ? ` of${monthName}` : ""}`;
1774
+ }
1775
+ if (form.end === "until" && form.until) text += `, ${l.until} ${form.until}`;
1776
+ if (form.end === "count") text += `, ${form.count} ${l.times}`;
1777
+ return text;
1778
+ }
1779
+
1780
+ // src/calendar/recurrence-labels.ts
1781
+ var DEFAULT_RECURRENCE_EDITOR_LABELS = {
1782
+ doesNotRepeat: "Does not repeat",
1783
+ daily: "Daily",
1784
+ weekly: "Weekly",
1785
+ monthly: "Monthly",
1786
+ yearly: "Yearly",
1787
+ custom: "Custom rule\u2026",
1788
+ every: "Every",
1789
+ units: { daily: "day(s)", weekly: "week(s)", monthly: "month(s)", yearly: "year(s)" },
1790
+ onDayOfMonth: "On day",
1791
+ onThe: "On the",
1792
+ ends: "Ends",
1793
+ never: "Never",
1794
+ onDate: "On",
1795
+ after: "After",
1796
+ occurrences: "occurrences",
1797
+ rrule: "iCalendar rule (RRULE)",
1798
+ rrulePlaceholder: "FREQ=WEEKLY;BYDAY=MO,WE",
1799
+ weekdayShort: { MO: "M", TU: "T", WE: "W", TH: "T", FR: "F", SA: "S", SU: "S" }
1800
+ };
1801
+ var SETPOS_OPTIONS = [1, 2, 3, 4, -1];
1802
+ function RecurrenceEditor({
1803
+ rrule,
1804
+ onChange,
1805
+ startDate,
1806
+ allDay,
1807
+ timeZone,
1808
+ labels: labelOverrides,
1809
+ idPrefix = "calendar-recurrence"
1810
+ }) {
1811
+ const l = { ...DEFAULT_RECURRENCE_EDITOR_LABELS, ...labelOverrides };
1812
+ const ctx = { allDay, timeZone };
1813
+ const [form, setForm] = useState(() => parseRRule(rrule, startDate, ctx));
1814
+ const [emitted, setEmitted] = useState(rrule || null);
1815
+ if ((rrule || null) !== emitted) {
1816
+ setEmitted(rrule || null);
1817
+ setForm(parseRRule(rrule, startDate, ctx));
1818
+ }
1819
+ const update = (patch) => {
1820
+ const next = { ...form, ...patch };
1821
+ setForm(next);
1822
+ const out = buildRRule(next, ctx);
1823
+ setEmitted(out);
1824
+ onChange(out);
1825
+ };
1826
+ const setFreq = (freq) => {
1827
+ if (freq === "custom") {
1828
+ update({ freq, raw: buildRRule(form, ctx) ?? form.raw ?? "" });
1829
+ return;
1830
+ }
1831
+ const seeded = { freq };
1832
+ if (form.freq === "none" || form.freq === "custom") {
1833
+ seeded.weekdays = [weekdayOf(startDate)];
1834
+ seeded.monthDay = Number(startDate.slice(8, 10));
1835
+ seeded.setPos = setPosOf(startDate);
1836
+ seeded.setPosDay = weekdayOf(startDate);
1837
+ seeded.month = Number(startDate.slice(5, 7));
1838
+ seeded.interval = 1;
1839
+ }
1840
+ update(seeded);
1841
+ };
1842
+ const weekdayName = (d) => DEFAULT_RECURRENCE_DESCRIBE_LABELS.weekdayNames[d];
1843
+ const ordinal = (p) => DEFAULT_RECURRENCE_DESCRIBE_LABELS.ordinals[p];
1844
+ const monthNames = DEFAULT_RECURRENCE_DESCRIBE_LABELS.monthNames;
1845
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": "calendar-recurrence", children: [
1846
+ /* @__PURE__ */ jsxs(
1847
+ NativeSelect,
1848
+ {
1849
+ id: `${idPrefix}-freq`,
1850
+ "aria-label": l.doesNotRepeat,
1851
+ value: form.freq,
1852
+ onChange: (e) => setFreq(e.target.value),
1853
+ "data-testid": "calendar-recurrence-freq",
1854
+ className: "w-full sm:w-56",
1855
+ children: [
1856
+ /* @__PURE__ */ jsx("option", { value: "none", children: l.doesNotRepeat }),
1857
+ /* @__PURE__ */ jsx("option", { value: "daily", children: l.daily }),
1858
+ /* @__PURE__ */ jsx("option", { value: "weekly", children: l.weekly }),
1859
+ /* @__PURE__ */ jsx("option", { value: "monthly", children: l.monthly }),
1860
+ /* @__PURE__ */ jsx("option", { value: "yearly", children: l.yearly }),
1861
+ /* @__PURE__ */ jsx("option", { value: "custom", children: l.custom })
1862
+ ]
1863
+ }
1864
+ ),
1865
+ form.freq === "custom" && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1866
+ /* @__PURE__ */ jsx("label", { htmlFor: `${idPrefix}-raw`, className: "text-xs text-muted-foreground", children: l.rrule }),
1867
+ /* @__PURE__ */ jsx(
1868
+ Input,
1869
+ {
1870
+ id: `${idPrefix}-raw`,
1871
+ value: form.raw,
1872
+ placeholder: l.rrulePlaceholder,
1873
+ onChange: (e) => update({ raw: e.target.value }),
1874
+ "data-testid": "calendar-recurrence-raw",
1875
+ className: "font-mono text-xs"
1876
+ }
1877
+ )
1878
+ ] }),
1879
+ form.freq !== "none" && form.freq !== "custom" && /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border border-border p-2", children: [
1880
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 text-sm", children: [
1881
+ /* @__PURE__ */ jsx("span", { children: l.every }),
1882
+ /* @__PURE__ */ jsx(
1883
+ Input,
1884
+ {
1885
+ type: "number",
1886
+ min: 1,
1887
+ max: 999,
1888
+ "aria-label": l.every,
1889
+ className: "h-8 w-16",
1890
+ value: form.interval,
1891
+ onChange: (e) => update({ interval: Math.max(1, Number(e.target.value) || 1) }),
1892
+ "data-testid": "calendar-recurrence-interval"
1893
+ }
1894
+ ),
1895
+ /* @__PURE__ */ jsx("span", { children: l.units[form.freq] })
1896
+ ] }),
1897
+ form.freq === "weekly" && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", role: "group", "aria-label": l.weekly, children: RRULE_WEEKDAYS.map((d) => {
1898
+ const on = form.weekdays.includes(d);
1899
+ return /* @__PURE__ */ jsx(
1900
+ "button",
1901
+ {
1902
+ type: "button",
1903
+ "aria-pressed": on,
1904
+ "aria-label": weekdayName(d),
1905
+ title: weekdayName(d),
1906
+ onClick: () => {
1907
+ const next = on ? form.weekdays.filter((x) => x !== d) : [...form.weekdays, d];
1908
+ update({ weekdays: next.length ? next : form.weekdays });
1909
+ },
1910
+ "data-testid": `calendar-recurrence-day-${d}`,
1911
+ className: cn(
1912
+ "h-8 w-8 rounded-full border text-xs font-medium transition-colors",
1913
+ on ? "border-primary bg-primary text-primary-foreground" : "border-border hover:bg-muted"
1914
+ ),
1915
+ children: l.weekdayShort[d]
1916
+ },
1917
+ d
1918
+ );
1919
+ }) }),
1920
+ (form.freq === "monthly" || form.freq === "yearly") && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 text-sm", children: [
1921
+ form.freq === "yearly" && /* @__PURE__ */ jsx(
1922
+ NativeSelect,
1923
+ {
1924
+ "aria-label": l.yearly,
1925
+ value: form.month,
1926
+ onChange: (e) => update({ month: Number(e.target.value) }),
1927
+ "data-testid": "calendar-recurrence-month",
1928
+ className: "h-8",
1929
+ children: monthNames.map((m, i) => /* @__PURE__ */ jsx("option", { value: i + 1, children: m }, m))
1930
+ }
1931
+ ),
1932
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-wrap items-center gap-2", children: [
1933
+ /* @__PURE__ */ jsx(
1934
+ "input",
1935
+ {
1936
+ type: "radio",
1937
+ name: `${idPrefix}-monthly`,
1938
+ checked: form.monthlyMode === "day",
1939
+ onChange: () => update({ monthlyMode: "day" }),
1940
+ "data-testid": "calendar-recurrence-mode-day"
1941
+ }
1942
+ ),
1943
+ /* @__PURE__ */ jsx("span", { children: l.onDayOfMonth }),
1944
+ /* @__PURE__ */ jsx(
1945
+ Input,
1946
+ {
1947
+ type: "number",
1948
+ min: 1,
1949
+ max: 31,
1950
+ "aria-label": l.onDayOfMonth,
1951
+ className: "h-8 w-16",
1952
+ value: form.monthDay,
1953
+ disabled: form.monthlyMode !== "day",
1954
+ onChange: (e) => update({ monthDay: Math.min(31, Math.max(1, Number(e.target.value) || 1)) }),
1955
+ "data-testid": "calendar-recurrence-monthday"
1956
+ }
1957
+ )
1958
+ ] }),
1959
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-wrap items-center gap-2", children: [
1960
+ /* @__PURE__ */ jsx(
1961
+ "input",
1962
+ {
1963
+ type: "radio",
1964
+ name: `${idPrefix}-monthly`,
1965
+ checked: form.monthlyMode === "weekday",
1966
+ onChange: () => update({ monthlyMode: "weekday" }),
1967
+ "data-testid": "calendar-recurrence-mode-weekday"
1968
+ }
1969
+ ),
1970
+ /* @__PURE__ */ jsx("span", { children: l.onThe }),
1971
+ /* @__PURE__ */ jsx(
1972
+ NativeSelect,
1973
+ {
1974
+ "aria-label": l.onThe,
1975
+ className: "h-8",
1976
+ value: form.setPos,
1977
+ disabled: form.monthlyMode !== "weekday",
1978
+ onChange: (e) => update({ setPos: Number(e.target.value) }),
1979
+ "data-testid": "calendar-recurrence-setpos",
1980
+ children: SETPOS_OPTIONS.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: ordinal(p) }, p))
1981
+ }
1982
+ ),
1983
+ /* @__PURE__ */ jsx(
1984
+ NativeSelect,
1985
+ {
1986
+ "aria-label": l.weekly,
1987
+ className: "h-8",
1988
+ value: form.setPosDay,
1989
+ disabled: form.monthlyMode !== "weekday",
1990
+ onChange: (e) => update({ setPosDay: e.target.value }),
1991
+ "data-testid": "calendar-recurrence-setpos-day",
1992
+ children: RRULE_WEEKDAYS.map((d) => /* @__PURE__ */ jsx("option", { value: d, children: weekdayName(d) }, d))
1993
+ }
1994
+ )
1995
+ ] })
1996
+ ] }),
1997
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 text-sm", children: [
1998
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: l.ends }),
1999
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2", children: [
2000
+ /* @__PURE__ */ jsx(
2001
+ "input",
2002
+ {
2003
+ type: "radio",
2004
+ name: `${idPrefix}-end`,
2005
+ checked: form.end === "never",
2006
+ onChange: () => update({ end: "never" }),
2007
+ "data-testid": "calendar-recurrence-end-never"
2008
+ }
2009
+ ),
2010
+ l.never
2011
+ ] }),
2012
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-wrap items-center gap-2", children: [
2013
+ /* @__PURE__ */ jsx(
2014
+ "input",
2015
+ {
2016
+ type: "radio",
2017
+ name: `${idPrefix}-end`,
2018
+ checked: form.end === "until",
2019
+ onChange: () => update({ end: "until", until: form.until ?? startDate }),
2020
+ "data-testid": "calendar-recurrence-end-until"
2021
+ }
2022
+ ),
2023
+ l.onDate,
2024
+ /* @__PURE__ */ jsx(
2025
+ Input,
2026
+ {
2027
+ type: "date",
2028
+ "aria-label": l.onDate,
2029
+ className: "h-8 w-40",
2030
+ value: form.until ?? "",
2031
+ min: startDate,
2032
+ disabled: form.end !== "until",
2033
+ onChange: (e) => update({ until: e.target.value || null }),
2034
+ "data-testid": "calendar-recurrence-until"
2035
+ }
2036
+ )
2037
+ ] }),
2038
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-wrap items-center gap-2", children: [
2039
+ /* @__PURE__ */ jsx(
2040
+ "input",
2041
+ {
2042
+ type: "radio",
2043
+ name: `${idPrefix}-end`,
2044
+ checked: form.end === "count",
2045
+ onChange: () => update({ end: "count" }),
2046
+ "data-testid": "calendar-recurrence-end-count"
2047
+ }
2048
+ ),
2049
+ l.after,
2050
+ /* @__PURE__ */ jsx(
2051
+ Input,
2052
+ {
2053
+ type: "number",
2054
+ min: 1,
2055
+ max: 999,
2056
+ "aria-label": l.occurrences,
2057
+ className: "h-8 w-20",
2058
+ value: form.count,
2059
+ disabled: form.end !== "count",
2060
+ onChange: (e) => update({ count: Math.max(1, Number(e.target.value) || 1) }),
2061
+ "data-testid": "calendar-recurrence-count"
2062
+ }
2063
+ ),
2064
+ l.occurrences
2065
+ ] })
2066
+ ] }),
2067
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", "data-testid": "calendar-recurrence-summary", children: describeRecurrence(form) })
2068
+ ] })
2069
+ ] });
2070
+ }
2071
+
2072
+ // src/calendar/format.ts
2073
+ function formatZoneLabel(zone, at = Date.now()) {
2074
+ return `${zoneCity(zone)} (${zoneOffsetLabel(zone, at)})`;
2075
+ }
2076
+ function formatEventWhen(e, zone, locale) {
2077
+ const z = safeZone(zone);
2078
+ const dayFmt = {
2079
+ weekday: "short",
2080
+ day: "numeric",
2081
+ month: "short",
2082
+ year: "numeric"
2083
+ };
2084
+ if (e.all_day) {
2085
+ const start = e.start.date ?? utcToDate(e.start_utc, z);
2086
+ const endExcl = e.end.date ?? utcToDate(e.end_utc, z);
2087
+ const last = endExcl > start ? addDays(endExcl, -1) : start;
2088
+ const a2 = formatPlainDate(start, dayFmt, locale);
2089
+ return last === start ? a2 : `${a2} \u2013 ${formatPlainDate(last, dayFmt, locale)}`;
2090
+ }
2091
+ const timeFmt = { hour: "numeric", minute: "2-digit" };
2092
+ const sameDay = utcToDate(e.start_utc, z) === utcToDate(e.end_utc, z);
2093
+ const a = `${formatInZone(e.start_utc, z, dayFmt, locale)} \xB7 ${formatInZone(e.start_utc, z, timeFmt, locale)}`;
2094
+ return sameDay ? `${a} \u2013 ${formatInZone(e.end_utc, z, timeFmt, locale)}` : `${a} \u2013 ${formatInZone(e.end_utc, z, dayFmt, locale)} \xB7 ${formatInZone(e.end_utc, z, timeFmt, locale)}`;
2095
+ }
2096
+ function TimeZonePicker({
2097
+ value,
2098
+ onChange,
2099
+ zones,
2100
+ allowNone,
2101
+ onClear,
2102
+ id,
2103
+ ariaLabel,
2104
+ disabled,
2105
+ labels: labelOverrides,
2106
+ className
2107
+ }) {
2108
+ const labels = mergeLabels(labelOverrides);
2109
+ const [open, setOpen] = useState(false);
2110
+ const options = useMemo(() => {
2111
+ const now = Date.now();
2112
+ const list = zones?.length ? zones : listTimeZones();
2113
+ return list.map((z) => {
2114
+ let offset = 0;
2115
+ try {
2116
+ offset = zoneOffsetMinutes(z, now);
2117
+ } catch {
2118
+ return null;
2119
+ }
2120
+ return { zone: z, offset, label: zoneOffsetLabel(z, now), city: zoneCity(z) };
2121
+ }).filter((o) => o !== null).sort((a, b) => a.offset - b.offset || a.zone.localeCompare(b.zone));
2122
+ }, [zones, open]);
2123
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
2124
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
2125
+ "button",
2126
+ {
2127
+ type: "button",
2128
+ id,
2129
+ disabled,
2130
+ "aria-label": ariaLabel ?? labels.timeZone,
2131
+ "aria-haspopup": "listbox",
2132
+ "aria-expanded": open,
2133
+ "data-testid": "calendar-tz-picker",
2134
+ className: cn(
2135
+ "inline-flex h-9 min-w-0 items-center gap-2 rounded-md border border-input bg-background px-2 text-left text-sm shadow-sm",
2136
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50",
2137
+ className
2138
+ ),
2139
+ children: [
2140
+ /* @__PURE__ */ jsx(Globe, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
2141
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: value ? formatZoneLabel(value) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: labels.none }) }),
2142
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true })
2143
+ ]
2144
+ }
2145
+ ) }),
2146
+ /* @__PURE__ */ jsx(PopoverContent, { className: "w-[320px] p-0", align: "start", children: /* @__PURE__ */ jsxs(
2147
+ Command,
2148
+ {
2149
+ filter: (itemValue, search) => itemValue.toLowerCase().includes(search.toLowerCase().trim()) ? 1 : 0,
2150
+ children: [
2151
+ /* @__PURE__ */ jsx(CommandInput, { placeholder: labels.searchTimeZones, "data-testid": "calendar-tz-search" }),
2152
+ /* @__PURE__ */ jsxs(CommandList, { className: "max-h-72", children: [
2153
+ /* @__PURE__ */ jsx(CommandEmpty, { children: "\u2014" }),
2154
+ /* @__PURE__ */ jsxs(CommandGroup, { children: [
2155
+ allowNone && /* @__PURE__ */ jsxs(
2156
+ CommandItem,
2157
+ {
2158
+ value: `__none__ ${labels.none}`,
2159
+ onSelect: () => {
2160
+ onClear?.();
2161
+ setOpen(false);
2162
+ },
2163
+ children: [
2164
+ /* @__PURE__ */ jsx(Check, { className: cn("mr-2 h-4 w-4", value ? "opacity-0" : "opacity-100") }),
2165
+ labels.none
2166
+ ]
2167
+ }
2168
+ ),
2169
+ options.map((o) => /* @__PURE__ */ jsxs(
2170
+ CommandItem,
2171
+ {
2172
+ value: `${o.zone} ${o.city} ${o.label}`,
2173
+ onSelect: () => {
2174
+ onChange(o.zone);
2175
+ setOpen(false);
2176
+ },
2177
+ "data-testid": `calendar-tz-option-${o.zone}`,
2178
+ children: [
2179
+ /* @__PURE__ */ jsx(
2180
+ Check,
2181
+ {
2182
+ className: cn(
2183
+ "mr-2 h-4 w-4 shrink-0",
2184
+ value === o.zone ? "opacity-100" : "opacity-0"
2185
+ )
2186
+ }
2187
+ ),
2188
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: o.zone.replace(/_/g, " ") }),
2189
+ /* @__PURE__ */ jsx("span", { className: "ml-2 shrink-0 text-xs tabular-nums text-muted-foreground", children: o.label })
2190
+ ]
2191
+ },
2192
+ o.zone
2193
+ ))
2194
+ ] })
2195
+ ] })
2196
+ ]
2197
+ }
2198
+ ) })
2199
+ ] });
2200
+ }
2201
+
2202
+ // src/calendar/reminders.ts
2203
+ var UNIT_MINUTES = {
2204
+ minutes: 1,
2205
+ hours: 60,
2206
+ days: 24 * 60,
2207
+ weeks: 7 * 24 * 60
2208
+ };
2209
+ function splitReminderMinutes(minutes) {
2210
+ const abs = Math.abs(minutes);
2211
+ if (abs === 0) return { value: 0, unit: "minutes" };
2212
+ for (const unit of ["weeks", "days", "hours"]) {
2213
+ if (abs % UNIT_MINUTES[unit] === 0) return { value: minutes / UNIT_MINUTES[unit], unit };
2214
+ }
2215
+ return { value: minutes, unit: "minutes" };
2216
+ }
2217
+ function reminderToMinutes(value, unit) {
2218
+ return Math.round(value * UNIT_MINUTES[unit]);
2219
+ }
2220
+ var DEFAULT_REMINDER_FORMAT_LABELS = {
2221
+ atStart: "At time of event",
2222
+ before: "before",
2223
+ after: "after",
2224
+ minute: "minute",
2225
+ minutes: "minutes",
2226
+ hour: "hour",
2227
+ hours: "hours",
2228
+ day: "day",
2229
+ days: "days",
2230
+ week: "week",
2231
+ weeks: "weeks"
2232
+ };
2233
+ function formatReminder(minutesBefore, labels = DEFAULT_REMINDER_FORMAT_LABELS) {
2234
+ if (minutesBefore === 0) return labels.atStart;
2235
+ const { value, unit } = splitReminderMinutes(Math.abs(minutesBefore));
2236
+ const singular = {
2237
+ minutes: labels.minute,
2238
+ hours: labels.hour,
2239
+ days: labels.day,
2240
+ weeks: labels.week
2241
+ }[unit];
2242
+ const word = value === 1 ? singular : labels[unit];
2243
+ return `${value} ${word} ${minutesBefore > 0 ? labels.before : labels.after}`;
2244
+ }
2245
+ var REMINDER_PRESETS = [0, 5, 10, 15, 30, 60, 120, 24 * 60, 2 * 24 * 60, 7 * 24 * 60];
2246
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2247
+ function Chip({
2248
+ children,
2249
+ onRemove,
2250
+ removeLabel
2251
+ }) {
2252
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex max-w-full items-center gap-1 rounded-full border border-border bg-muted/60 py-0.5 pl-2 pr-1 text-xs", children: [
2253
+ children,
2254
+ /* @__PURE__ */ jsx(
2255
+ "button",
2256
+ {
2257
+ type: "button",
2258
+ onClick: onRemove,
2259
+ "aria-label": removeLabel,
2260
+ className: "rounded-full p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
2261
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
2262
+ }
2263
+ )
2264
+ ] });
2265
+ }
2266
+ function showAsLabel(v, l) {
2267
+ return {
2268
+ free: l.free,
2269
+ tentative: l.tentative,
2270
+ busy: l.busy,
2271
+ oof: l.oof,
2272
+ working_elsewhere: l.workingElsewhere
2273
+ }[v];
2274
+ }
2275
+ function EventEditor({
2276
+ value,
2277
+ onChange,
2278
+ onSave,
2279
+ onCancel,
2280
+ onDelete,
2281
+ calendars,
2282
+ mode,
2283
+ canAddCall,
2284
+ onAddCall,
2285
+ defaultTimeZone,
2286
+ schedulingSlot,
2287
+ attendeeSuggestions,
2288
+ onAttendeeQueryChange,
2289
+ saving,
2290
+ error,
2291
+ labels: labelOverrides,
2292
+ zones,
2293
+ className
2294
+ }) {
2295
+ const l = useMemo(() => mergeLabels(labelOverrides), [labelOverrides]);
2296
+ const uid = useId();
2297
+ const id = (s) => `${uid}-${s}`;
2298
+ const allDay = isAllDayDraft(value);
2299
+ const startZone = value.start.time_zone ?? defaultTimeZone;
2300
+ const endZone = value.end.time_zone ?? startZone;
2301
+ const [showZones, setShowZones] = useState(
2302
+ () => !allDay && (startZone !== defaultTimeZone || endZone !== startZone)
2303
+ );
2304
+ const [attendeeInput, setAttendeeInput] = useState("");
2305
+ const [categoryInput, setCategoryInput] = useState("");
2306
+ const [linkInput, setLinkInput] = useState(null);
2307
+ const [attachUrl, setAttachUrl] = useState("");
2308
+ const [attachTitle, setAttachTitle] = useState("");
2309
+ const [addingCall, setAddingCall] = useState(false);
2310
+ const [touched, setTouched] = useState(false);
2311
+ const [focus, setFocus] = useState(() => !!value.auto_decline && value.show_as !== "oof");
2312
+ const showDecline = value.show_as === "oof" || focus;
2313
+ const set = (patch) => onChange({ ...value, ...patch });
2314
+ const problem = validateDraft(value, defaultTimeZone);
2315
+ const shownProblem = problem === "invalidRange" || touched && problem ? problem : null;
2316
+ const writable = calendars.filter((c) => c.canWrite && !c.locked);
2317
+ const calendarColor = calendars.find((c) => c.id === value.calendar_id)?.color;
2318
+ const attendees = value.attendees ?? [];
2319
+ const addAttendees = (raw) => {
2320
+ const emails = raw.split(/[\s,;]+/).map((s) => s.trim()).filter((s) => EMAIL_RE.test(s));
2321
+ if (!emails.length) return false;
2322
+ const have = new Set(attendees.map((a) => (a.email ?? "").toLowerCase()));
2323
+ const next = [...attendees];
2324
+ for (const email of emails) {
2325
+ if (have.has(email.toLowerCase())) continue;
2326
+ have.add(email.toLowerCase());
2327
+ next.push({ email, role: "required", kind: "individual" });
2328
+ }
2329
+ set({ attendees: next });
2330
+ return true;
2331
+ };
2332
+ const onAttendeeKey = (e) => {
2333
+ if (e.key === "Enter" || e.key === "," || e.key === ";" || e.key === "Tab") {
2334
+ if (attendeeInput.trim() && addAttendees(attendeeInput)) {
2335
+ e.preventDefault();
2336
+ setAttendeeInput("");
2337
+ }
2338
+ } else if (e.key === "Backspace" && !attendeeInput && attendees.length) {
2339
+ set({ attendees: attendees.slice(0, -1) });
2340
+ }
2341
+ };
2342
+ const categories = value.categories ?? [];
2343
+ const addCategory = () => {
2344
+ const c = categoryInput.trim();
2345
+ if (c && !categories.includes(c)) set({ categories: [...categories, c] });
2346
+ setCategoryInput("");
2347
+ };
2348
+ const reminders = value.reminders ?? [];
2349
+ const setReminder = (i, r) => set({ reminders: reminders.map((x, j) => j === i ? r : x) });
2350
+ const addArbiCall = async () => {
2351
+ if (!onAddCall) return;
2352
+ setAddingCall(true);
2353
+ try {
2354
+ const call = await onAddCall();
2355
+ if (call)
2356
+ set({
2357
+ conference: { type: "arbi", url: call.url, call_id: call.call_id, label: l.arbiCall }
2358
+ });
2359
+ } finally {
2360
+ setAddingCall(false);
2361
+ }
2362
+ };
2363
+ const attachments = value.attachments ?? [];
2364
+ const addAttachment = () => {
2365
+ const url = attachUrl.trim();
2366
+ if (!url) return;
2367
+ set({ attachments: [...attachments, { url, title: attachTitle.trim() || null }] });
2368
+ setAttachUrl("");
2369
+ setAttachTitle("");
2370
+ };
2371
+ const startDate = specDate(value.start);
2372
+ const submit = () => {
2373
+ setTouched(true);
2374
+ if (problem || saving) return;
2375
+ onSave();
2376
+ };
2377
+ return /* @__PURE__ */ jsxs(
2378
+ "form",
2379
+ {
2380
+ className: cn("flex h-full min-h-0 flex-col", className),
2381
+ onSubmit: (e) => {
2382
+ e.preventDefault();
2383
+ submit();
2384
+ },
2385
+ "data-testid": "calendar-event-editor",
2386
+ "aria-label": mode === "create" ? l.createTitle : l.editTitle,
2387
+ children: [
2388
+ /* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 space-y-4 overflow-y-auto p-1 scrollbar-themed", children: [
2389
+ /* @__PURE__ */ jsx(
2390
+ Input,
2391
+ {
2392
+ id: id("title"),
2393
+ "aria-label": l.title,
2394
+ placeholder: l.titlePlaceholder,
2395
+ value: value.title,
2396
+ autoFocus: mode === "create",
2397
+ onChange: (e) => set({ title: e.target.value }),
2398
+ className: "h-11 border-0 border-b border-border rounded-none px-1 text-lg font-medium shadow-none focus-visible:ring-0 focus-visible:border-primary",
2399
+ "data-testid": "calendar-editor-title"
2400
+ }
2401
+ ),
2402
+ /* @__PURE__ */ jsxs(FieldRow, { icon: /* @__PURE__ */ jsx(Clock, { className: "h-4 w-4" }), children: [
2403
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
2404
+ /* @__PURE__ */ jsx(
2405
+ Input,
2406
+ {
2407
+ type: "date",
2408
+ "aria-label": l.start,
2409
+ className: "h-9 w-[9.5rem]",
2410
+ value: startDate,
2411
+ onChange: (e) => e.target.value && onChange(setDraftStart(value, { date: e.target.value }, defaultTimeZone)),
2412
+ "data-testid": "calendar-editor-start-date"
2413
+ }
2414
+ ),
2415
+ !allDay && /* @__PURE__ */ jsx(
2416
+ Input,
2417
+ {
2418
+ type: "time",
2419
+ "aria-label": l.startTime,
2420
+ className: "h-9 w-[7rem]",
2421
+ step: 300,
2422
+ value: specTime(value.start),
2423
+ onChange: (e) => e.target.value && onChange(setDraftStart(value, { time: e.target.value }, defaultTimeZone)),
2424
+ "data-testid": "calendar-editor-start-time"
2425
+ }
2426
+ ),
2427
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2013" }),
2428
+ !allDay && /* @__PURE__ */ jsx(
2429
+ Input,
2430
+ {
2431
+ type: "time",
2432
+ "aria-label": l.endTime,
2433
+ className: "h-9 w-[7rem]",
2434
+ step: 300,
2435
+ value: specTime(value.end),
2436
+ onChange: (e) => e.target.value && onChange(setDraftEnd(value, { time: e.target.value })),
2437
+ "data-testid": "calendar-editor-end-time"
2438
+ }
2439
+ ),
2440
+ /* @__PURE__ */ jsx(
2441
+ Input,
2442
+ {
2443
+ type: "date",
2444
+ "aria-label": l.end,
2445
+ className: "h-9 w-[9.5rem]",
2446
+ value: allDay ? allDayInclusiveEnd(value) : specDate(value.end),
2447
+ min: startDate,
2448
+ onChange: (e) => e.target.value && onChange(setDraftEnd(value, { date: e.target.value })),
2449
+ "data-testid": "calendar-editor-end-date"
2450
+ }
2451
+ )
2452
+ ] }),
2453
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-4 pt-1 text-sm", children: [
2454
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2", children: [
2455
+ /* @__PURE__ */ jsx(
2456
+ Switch,
2457
+ {
2458
+ checked: allDay,
2459
+ onCheckedChange: (on) => onChange(setDraftAllDay(value, on, startZone)),
2460
+ "aria-label": l.allDay,
2461
+ "data-testid": "calendar-editor-all-day"
2462
+ }
2463
+ ),
2464
+ l.allDay
2465
+ ] }),
2466
+ !allDay && /* @__PURE__ */ jsxs(
2467
+ "button",
2468
+ {
2469
+ type: "button",
2470
+ className: "inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground",
2471
+ onClick: () => setShowZones((v) => !v),
2472
+ "aria-expanded": showZones,
2473
+ "data-testid": "calendar-editor-zones-toggle",
2474
+ children: [
2475
+ /* @__PURE__ */ jsx(Globe, { className: "h-3.5 w-3.5" }),
2476
+ l.separateTimeZones
2477
+ ]
2478
+ }
2479
+ )
2480
+ ] }),
2481
+ !allDay && showZones && /* @__PURE__ */ jsxs("div", { className: "grid gap-2 pt-1 sm:grid-cols-2", children: [
2482
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2483
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: l.startTimeZone }),
2484
+ /* @__PURE__ */ jsx(
2485
+ TimeZonePicker,
2486
+ {
2487
+ value: startZone,
2488
+ zones,
2489
+ ariaLabel: l.startTimeZone,
2490
+ labels: labelOverrides,
2491
+ className: "w-full",
2492
+ onChange: (z) => {
2493
+ const moved = setDraftStart(value, { timeZone: z }, defaultTimeZone);
2494
+ const endFollows = (value.end.time_zone ?? startZone) === startZone;
2495
+ onChange(endFollows ? { ...moved, end: { ...moved.end, time_zone: z } } : moved);
2496
+ }
2497
+ }
2498
+ )
2499
+ ] }),
2500
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2501
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: l.endTimeZone }),
2502
+ /* @__PURE__ */ jsx(
2503
+ TimeZonePicker,
2504
+ {
2505
+ value: endZone,
2506
+ zones,
2507
+ ariaLabel: l.endTimeZone,
2508
+ labels: labelOverrides,
2509
+ className: "w-full",
2510
+ onChange: (z) => onChange(setDraftEnd(value, { timeZone: z }))
2511
+ }
2512
+ )
2513
+ ] })
2514
+ ] })
2515
+ ] }),
2516
+ /* @__PURE__ */ jsx(FieldRow, { icon: /* @__PURE__ */ jsx(Repeat, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsx(
2517
+ RecurrenceEditor,
2518
+ {
2519
+ rrule: value.recurrence?.rrule ?? null,
2520
+ startDate,
2521
+ allDay,
2522
+ timeZone: startZone,
2523
+ idPrefix: id("rrule"),
2524
+ onChange: (rrule) => set({
2525
+ recurrence: rrule ? { ...value.recurrence ?? {}, rrule } : value.recurrence?.rdates?.length ? { ...value.recurrence, rrule: null } : null
2526
+ })
2527
+ }
2528
+ ) }),
2529
+ writable.length > 0 && /* @__PURE__ */ jsx(FieldRow, { icon: /* @__PURE__ */ jsx(CalendarDays, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsxs(
2530
+ NativeSelect,
2531
+ {
2532
+ "aria-label": l.calendar,
2533
+ value: value.calendar_id ?? "",
2534
+ onChange: (e) => set({ calendar_id: e.target.value || void 0 }),
2535
+ "data-testid": "calendar-editor-calendar",
2536
+ className: "w-full sm:w-72",
2537
+ children: [
2538
+ !value.calendar_id && /* @__PURE__ */ jsx("option", { value: "", children: l.calendar }),
2539
+ writable.map((c) => /* @__PURE__ */ jsx("option", { value: c.id, children: c.name ?? c.id }, c.id))
2540
+ ]
2541
+ }
2542
+ ) }),
2543
+ /* @__PURE__ */ jsx(FieldRow, { icon: /* @__PURE__ */ jsx(Video, { className: "h-4 w-4" }), children: value.conference ? /* @__PURE__ */ jsxs(
2544
+ "div",
2545
+ {
2546
+ className: "flex min-w-0 items-center gap-2 text-sm",
2547
+ "data-testid": "calendar-editor-conference",
2548
+ children: [
2549
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: value.conference.label || (value.conference.type === "arbi" ? l.arbiCall : l.conference) }),
2550
+ /* @__PURE__ */ jsx(
2551
+ "a",
2552
+ {
2553
+ href: value.conference.url,
2554
+ target: "_blank",
2555
+ rel: "noreferrer",
2556
+ className: "min-w-0 truncate text-xs text-primary underline-offset-2 hover:underline",
2557
+ children: value.conference.url.replace(/#.*$/, "")
2558
+ }
2559
+ ),
2560
+ /* @__PURE__ */ jsx(
2561
+ "button",
2562
+ {
2563
+ type: "button",
2564
+ className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
2565
+ "aria-label": l.removeConference,
2566
+ onClick: () => set({ conference: null }),
2567
+ "data-testid": "calendar-editor-conference-remove",
2568
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
2569
+ }
2570
+ )
2571
+ ]
2572
+ }
2573
+ ) : linkInput !== null ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2574
+ /* @__PURE__ */ jsx(
2575
+ Input,
2576
+ {
2577
+ type: "url",
2578
+ "aria-label": l.addMeetingLink,
2579
+ placeholder: l.meetingLinkPlaceholder,
2580
+ value: linkInput,
2581
+ autoFocus: true,
2582
+ onChange: (e) => setLinkInput(e.target.value),
2583
+ onKeyDown: (e) => {
2584
+ if (e.key === "Enter") {
2585
+ e.preventDefault();
2586
+ if (linkInput.trim())
2587
+ set({ conference: { type: "external", url: linkInput.trim() } });
2588
+ setLinkInput(null);
2589
+ } else if (e.key === "Escape") setLinkInput(null);
2590
+ },
2591
+ "data-testid": "calendar-editor-link-input"
2592
+ }
2593
+ ),
2594
+ /* @__PURE__ */ jsx(
2595
+ Button,
2596
+ {
2597
+ type: "button",
2598
+ size: "sm",
2599
+ variant: "outline",
2600
+ onClick: () => {
2601
+ if (linkInput.trim())
2602
+ set({ conference: { type: "external", url: linkInput.trim() } });
2603
+ setLinkInput(null);
2604
+ },
2605
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" })
2606
+ }
2607
+ )
2608
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
2609
+ canAddCall && onAddCall && /* @__PURE__ */ jsxs(
2610
+ Button,
2611
+ {
2612
+ type: "button",
2613
+ size: "sm",
2614
+ variant: "secondary",
2615
+ disabled: addingCall,
2616
+ onClick: addArbiCall,
2617
+ "data-testid": "calendar-editor-add-call",
2618
+ children: [
2619
+ /* @__PURE__ */ jsx(Video, { className: "mr-1 h-4 w-4" }),
2620
+ l.addArbiCall
2621
+ ]
2622
+ }
2623
+ ),
2624
+ /* @__PURE__ */ jsx(
2625
+ Button,
2626
+ {
2627
+ type: "button",
2628
+ size: "sm",
2629
+ variant: "ghost",
2630
+ onClick: () => setLinkInput(""),
2631
+ "data-testid": "calendar-editor-add-link",
2632
+ children: l.addMeetingLink
2633
+ }
2634
+ )
2635
+ ] }) }),
2636
+ /* @__PURE__ */ jsx(FieldRow, { icon: /* @__PURE__ */ jsx(MapPin, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsx(
2637
+ Input,
2638
+ {
2639
+ "aria-label": l.location,
2640
+ placeholder: l.location,
2641
+ value: value.location ?? "",
2642
+ onChange: (e) => set({ location: e.target.value || null }),
2643
+ "data-testid": "calendar-editor-location"
2644
+ }
2645
+ ) }),
2646
+ /* @__PURE__ */ jsxs(FieldRow, { icon: /* @__PURE__ */ jsx(Users, { className: "h-4 w-4" }), children: [
2647
+ /* @__PURE__ */ jsx(
2648
+ Input,
2649
+ {
2650
+ type: "email",
2651
+ multiple: true,
2652
+ "aria-label": l.addAttendee,
2653
+ placeholder: l.addAttendee,
2654
+ value: attendeeInput,
2655
+ onChange: (e) => {
2656
+ setAttendeeInput(e.target.value);
2657
+ onAttendeeQueryChange?.(e.target.value);
2658
+ },
2659
+ onKeyDown: onAttendeeKey,
2660
+ onBlur: () => {
2661
+ if (attendeeInput.trim() && addAttendees(attendeeInput)) setAttendeeInput("");
2662
+ },
2663
+ "data-testid": "calendar-editor-attendee-input"
2664
+ }
2665
+ ),
2666
+ attendeeInput.trim() && (attendeeSuggestions?.length ?? 0) > 0 && /* @__PURE__ */ jsx(
2667
+ "ul",
2668
+ {
2669
+ role: "listbox",
2670
+ "aria-label": l.suggestions,
2671
+ className: "mt-1 max-h-48 overflow-y-auto rounded-md border border-border bg-popover shadow-sm",
2672
+ "data-testid": "calendar-editor-attendee-suggestions",
2673
+ children: attendeeSuggestions?.filter(
2674
+ (s) => !attendees.some((a) => (a.email ?? "").toLowerCase() === s.email.toLowerCase())
2675
+ ).slice(0, 8).map((s) => /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": false, children: /* @__PURE__ */ jsxs(
2676
+ "button",
2677
+ {
2678
+ type: "button",
2679
+ className: "flex w-full items-baseline gap-2 px-3 py-1.5 text-left text-sm hover:bg-muted",
2680
+ onMouseDown: (e) => e.preventDefault(),
2681
+ onClick: () => {
2682
+ set({
2683
+ attendees: [
2684
+ ...attendees,
2685
+ {
2686
+ email: s.email,
2687
+ name: s.name ?? null,
2688
+ role: "required",
2689
+ kind: "individual"
2690
+ }
2691
+ ]
2692
+ });
2693
+ setAttendeeInput("");
2694
+ onAttendeeQueryChange?.("");
2695
+ },
2696
+ "data-testid": "calendar-editor-attendee-suggestion",
2697
+ children: [
2698
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium", children: s.name || s.email }),
2699
+ s.name && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: s.email }),
2700
+ s.detail && /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 text-xs text-muted-foreground", children: s.detail })
2701
+ ]
2702
+ }
2703
+ ) }, s.email))
2704
+ }
2705
+ ),
2706
+ attendees.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-1", "data-testid": "calendar-editor-attendees", children: attendees.map((a, i) => /* @__PURE__ */ jsxs(
2707
+ "li",
2708
+ {
2709
+ className: "flex items-center gap-2 text-sm",
2710
+ children: [
2711
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", title: a.email ?? void 0, children: a.name || a.email || a.user_id }),
2712
+ /* @__PURE__ */ jsxs(
2713
+ NativeSelect,
2714
+ {
2715
+ "aria-label": `${a.email ?? ""} ${l.required}`,
2716
+ className: "h-7 text-xs",
2717
+ value: a.role ?? "required",
2718
+ onChange: (e) => set({
2719
+ attendees: attendees.map(
2720
+ (x, j) => j === i ? { ...x, role: e.target.value } : x
2721
+ )
2722
+ }),
2723
+ children: [
2724
+ /* @__PURE__ */ jsx("option", { value: "required", children: l.required }),
2725
+ /* @__PURE__ */ jsx("option", { value: "optional", children: l.optional })
2726
+ ]
2727
+ }
2728
+ ),
2729
+ /* @__PURE__ */ jsx(
2730
+ "button",
2731
+ {
2732
+ type: "button",
2733
+ className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
2734
+ "aria-label": `${l.remove} ${a.email ?? ""}`,
2735
+ onClick: () => set({ attendees: attendees.filter((_, j) => j !== i) }),
2736
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
2737
+ }
2738
+ )
2739
+ ]
2740
+ },
2741
+ `${a.email ?? a.user_id ?? i}`
2742
+ )) })
2743
+ ] }),
2744
+ schedulingSlot && /* @__PURE__ */ jsx("div", { className: "pl-8", children: schedulingSlot }),
2745
+ /* @__PURE__ */ jsx(FieldRow, { icon: /* @__PURE__ */ jsx(TextAlignStart, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsx(
2746
+ Textarea,
2747
+ {
2748
+ "aria-label": l.description,
2749
+ placeholder: l.description,
2750
+ value: value.description ?? "",
2751
+ rows: 4,
2752
+ onChange: (e) => set({ description: e.target.value || null }),
2753
+ "data-testid": "calendar-editor-description"
2754
+ }
2755
+ ) }),
2756
+ /* @__PURE__ */ jsxs(FieldRow, { icon: /* @__PURE__ */ jsx(Bell, { className: "h-4 w-4" }), label: l.reminders, children: [
2757
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
2758
+ /* @__PURE__ */ jsx(
2759
+ "input",
2760
+ {
2761
+ type: "checkbox",
2762
+ checked: !!value.use_default_reminders,
2763
+ onChange: (e) => set({ use_default_reminders: e.target.checked }),
2764
+ "data-testid": "calendar-editor-default-reminders"
2765
+ }
2766
+ ),
2767
+ l.useDefaultReminders
2768
+ ] }),
2769
+ !value.use_default_reminders && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2770
+ reminders.map((r, i) => {
2771
+ const { value: n, unit } = splitReminderMinutes(r.minutes_before);
2772
+ return /* @__PURE__ */ jsxs(
2773
+ "div",
2774
+ {
2775
+ className: "flex flex-wrap items-center gap-2 text-sm",
2776
+ "data-testid": "calendar-editor-reminder",
2777
+ children: [
2778
+ /* @__PURE__ */ jsxs(
2779
+ NativeSelect,
2780
+ {
2781
+ "aria-label": l.reminders,
2782
+ className: "h-8",
2783
+ value: r.action,
2784
+ onChange: (e) => setReminder(i, { ...r, action: e.target.value }),
2785
+ children: [
2786
+ /* @__PURE__ */ jsx("option", { value: "display", children: l.popup }),
2787
+ /* @__PURE__ */ jsx("option", { value: "email", children: l.email })
2788
+ ]
2789
+ }
2790
+ ),
2791
+ /* @__PURE__ */ jsx(
2792
+ Input,
2793
+ {
2794
+ type: "number",
2795
+ min: 0,
2796
+ "aria-label": formatReminder(r.minutes_before),
2797
+ className: "h-8 w-20",
2798
+ value: n,
2799
+ onChange: (e) => setReminder(i, {
2800
+ ...r,
2801
+ minutes_before: reminderToMinutes(
2802
+ Math.max(0, Number(e.target.value) || 0),
2803
+ unit
2804
+ )
2805
+ })
2806
+ }
2807
+ ),
2808
+ /* @__PURE__ */ jsxs(
2809
+ NativeSelect,
2810
+ {
2811
+ "aria-label": l.minutes,
2812
+ className: "h-8",
2813
+ value: unit,
2814
+ onChange: (e) => setReminder(i, {
2815
+ ...r,
2816
+ minutes_before: reminderToMinutes(n, e.target.value)
2817
+ }),
2818
+ children: [
2819
+ /* @__PURE__ */ jsx("option", { value: "minutes", children: l.minutes }),
2820
+ /* @__PURE__ */ jsx("option", { value: "hours", children: l.hours }),
2821
+ /* @__PURE__ */ jsx("option", { value: "days", children: l.days }),
2822
+ /* @__PURE__ */ jsx("option", { value: "weeks", children: l.weeks })
2823
+ ]
2824
+ }
2825
+ ),
2826
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: l.beforeStart }),
2827
+ /* @__PURE__ */ jsx(
2828
+ "button",
2829
+ {
2830
+ type: "button",
2831
+ className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
2832
+ "aria-label": `${l.remove} ${formatReminder(r.minutes_before)}`,
2833
+ onClick: () => set({ reminders: reminders.filter((_, j) => j !== i) }),
2834
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
2835
+ }
2836
+ )
2837
+ ]
2838
+ },
2839
+ i
2840
+ );
2841
+ }),
2842
+ reminders.length < 5 && /* @__PURE__ */ jsxs(
2843
+ Button,
2844
+ {
2845
+ type: "button",
2846
+ size: "sm",
2847
+ variant: "ghost",
2848
+ onClick: () => set({
2849
+ reminders: [
2850
+ ...reminders,
2851
+ { minutes_before: allDay ? 24 * 60 : 10, action: "display" }
2852
+ ]
2853
+ }),
2854
+ "data-testid": "calendar-editor-add-reminder",
2855
+ children: [
2856
+ /* @__PURE__ */ jsx(Plus, { className: "mr-1 h-4 w-4" }),
2857
+ l.addReminder
2858
+ ]
2859
+ }
2860
+ )
2861
+ ] })
2862
+ ] }),
2863
+ /* @__PURE__ */ jsxs(FieldRow, { icon: /* @__PURE__ */ jsx(Lock, { className: "h-4 w-4" }), children: [
2864
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
2865
+ /* @__PURE__ */ jsx(
2866
+ NativeSelect,
2867
+ {
2868
+ "aria-label": l.showAs,
2869
+ value: value.show_as ?? "busy",
2870
+ onChange: (e) => set({ show_as: e.target.value }),
2871
+ "data-testid": "calendar-editor-show-as",
2872
+ children: ["busy", "free", "tentative", "oof", "working_elsewhere"].map((v) => /* @__PURE__ */ jsx("option", { value: v, children: showAsLabel(v, l) }, v))
2873
+ }
2874
+ ),
2875
+ /* @__PURE__ */ jsxs(
2876
+ NativeSelect,
2877
+ {
2878
+ "aria-label": l.sensitivity,
2879
+ value: value.sensitivity ?? "normal",
2880
+ onChange: (e) => set({ sensitivity: e.target.value }),
2881
+ "data-testid": "calendar-editor-sensitivity",
2882
+ children: [
2883
+ /* @__PURE__ */ jsx("option", { value: "normal", children: l.normal }),
2884
+ /* @__PURE__ */ jsx("option", { value: "private", children: l.private }),
2885
+ /* @__PURE__ */ jsx("option", { value: "confidential", children: l.confidential })
2886
+ ]
2887
+ }
2888
+ ),
2889
+ /* @__PURE__ */ jsxs(
2890
+ NativeSelect,
2891
+ {
2892
+ "aria-label": l.status,
2893
+ value: value.status ?? "confirmed",
2894
+ onChange: (e) => set({ status: e.target.value }),
2895
+ "data-testid": "calendar-editor-status",
2896
+ children: [
2897
+ /* @__PURE__ */ jsx("option", { value: "confirmed", children: l.confirmed }),
2898
+ /* @__PURE__ */ jsx("option", { value: "tentative", children: l.tentative }),
2899
+ /* @__PURE__ */ jsx("option", { value: "cancelled", children: l.cancelled })
2900
+ ]
2901
+ }
2902
+ )
2903
+ ] }),
2904
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-4 pt-2 text-sm", children: [
2905
+ value.show_as !== "oof" && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2", children: [
2906
+ /* @__PURE__ */ jsx(
2907
+ "input",
2908
+ {
2909
+ type: "checkbox",
2910
+ checked: focus,
2911
+ onChange: (e) => {
2912
+ setFocus(e.target.checked);
2913
+ if (!e.target.checked) set({ auto_decline: false });
2914
+ else if (!value.show_as || value.show_as === "free") set({ show_as: "busy" });
2915
+ },
2916
+ "data-testid": "calendar-editor-focus"
2917
+ }
2918
+ ),
2919
+ l.focusTime
2920
+ ] }),
2921
+ showDecline && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2", children: [
2922
+ /* @__PURE__ */ jsx(
2923
+ "input",
2924
+ {
2925
+ type: "checkbox",
2926
+ checked: !!value.auto_decline,
2927
+ onChange: (e) => set({ auto_decline: e.target.checked }),
2928
+ "data-testid": "calendar-editor-auto-decline"
2929
+ }
2930
+ ),
2931
+ l.autoDecline
2932
+ ] })
2933
+ ] }),
2934
+ showDecline && value.auto_decline && /* @__PURE__ */ jsx(
2935
+ Input,
2936
+ {
2937
+ className: "mt-2",
2938
+ "aria-label": l.declineMessage,
2939
+ placeholder: l.declineMessagePlaceholder,
2940
+ value: value.decline_message ?? "",
2941
+ maxLength: 1e3,
2942
+ onChange: (e) => set({ decline_message: e.target.value || null }),
2943
+ "data-testid": "calendar-editor-decline-message"
2944
+ }
2945
+ )
2946
+ ] }),
2947
+ /* @__PURE__ */ jsx(FieldRow, { icon: /* @__PURE__ */ jsx(Tag, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
2948
+ categories.map((c) => /* @__PURE__ */ jsx(
2949
+ Chip,
2950
+ {
2951
+ removeLabel: `${l.remove} ${c}`,
2952
+ onRemove: () => set({ categories: categories.filter((x) => x !== c) }),
2953
+ children: c
2954
+ },
2955
+ c
2956
+ )),
2957
+ /* @__PURE__ */ jsx(
2958
+ Input,
2959
+ {
2960
+ "aria-label": l.addCategory,
2961
+ placeholder: l.addCategory,
2962
+ className: "h-8 w-40",
2963
+ value: categoryInput,
2964
+ onChange: (e) => setCategoryInput(e.target.value),
2965
+ onKeyDown: (e) => {
2966
+ if (e.key === "Enter" || e.key === ",") {
2967
+ e.preventDefault();
2968
+ addCategory();
2969
+ }
2970
+ },
2971
+ onBlur: addCategory,
2972
+ "data-testid": "calendar-editor-category-input"
2973
+ }
2974
+ )
2975
+ ] }) }),
2976
+ /* @__PURE__ */ jsxs(FieldRow, { icon: /* @__PURE__ */ jsx(Palette, { className: "h-4 w-4" }), children: [
2977
+ /* @__PURE__ */ jsx(
2978
+ ColorSwatches,
2979
+ {
2980
+ value: value.color ?? null,
2981
+ onChange: (c) => set({ color: c }),
2982
+ allowNone: true,
2983
+ noneLabel: l.defaultColor,
2984
+ testIdPrefix: "calendar-editor-color"
2985
+ }
2986
+ ),
2987
+ !value.color && calendarColor && /* @__PURE__ */ jsx("span", { className: "sr-only", children: calendarColor })
2988
+ ] }),
2989
+ /* @__PURE__ */ jsxs(FieldRow, { icon: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" }), label: l.attachments, children: [
2990
+ attachments.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: attachments.map((a, i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2 text-sm", children: [
2991
+ /* @__PURE__ */ jsx(
2992
+ "a",
2993
+ {
2994
+ href: a.url,
2995
+ target: "_blank",
2996
+ rel: "noreferrer",
2997
+ className: "min-w-0 flex-1 truncate text-primary hover:underline",
2998
+ children: a.title || a.url
2999
+ }
3000
+ ),
3001
+ /* @__PURE__ */ jsx(
3002
+ "button",
3003
+ {
3004
+ type: "button",
3005
+ className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
3006
+ "aria-label": `${l.remove} ${a.title || a.url}`,
3007
+ onClick: () => set({ attachments: attachments.filter((_, j) => j !== i) }),
3008
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
3009
+ }
3010
+ )
3011
+ ] }, `${a.url}-${i}`)) }),
3012
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
3013
+ /* @__PURE__ */ jsx(
3014
+ Input,
3015
+ {
3016
+ type: "url",
3017
+ "aria-label": l.attachmentUrlPlaceholder,
3018
+ placeholder: l.attachmentUrlPlaceholder,
3019
+ className: "h-8 min-w-0 flex-1",
3020
+ value: attachUrl,
3021
+ onChange: (e) => setAttachUrl(e.target.value),
3022
+ "data-testid": "calendar-editor-attachment-url"
3023
+ }
3024
+ ),
3025
+ /* @__PURE__ */ jsx(
3026
+ Input,
3027
+ {
3028
+ "aria-label": l.attachmentTitlePlaceholder,
3029
+ placeholder: l.attachmentTitlePlaceholder,
3030
+ className: "h-8 w-40",
3031
+ value: attachTitle,
3032
+ onChange: (e) => setAttachTitle(e.target.value)
3033
+ }
3034
+ ),
3035
+ /* @__PURE__ */ jsx(
3036
+ Button,
3037
+ {
3038
+ type: "button",
3039
+ size: "sm",
3040
+ variant: "outline",
3041
+ onClick: addAttachment,
3042
+ disabled: !attachUrl.trim(),
3043
+ children: l.addAttachment
3044
+ }
3045
+ )
3046
+ ] })
3047
+ ] })
3048
+ ] }),
3049
+ (error || shownProblem) && /* @__PURE__ */ jsx(
3050
+ "p",
3051
+ {
3052
+ className: "px-1 pt-2 text-sm text-destructive",
3053
+ role: "alert",
3054
+ "data-testid": "calendar-editor-error",
3055
+ children: error || (shownProblem === "invalidRange" ? l.invalidRange : l.titleRequired)
3056
+ }
3057
+ ),
3058
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t border-border pt-3", children: [
3059
+ mode === "edit" && onDelete && /* @__PURE__ */ jsxs(
3060
+ Button,
3061
+ {
3062
+ type: "button",
3063
+ variant: "ghost",
3064
+ className: "text-destructive hover:text-destructive",
3065
+ onClick: onDelete,
3066
+ "data-testid": "calendar-editor-delete",
3067
+ children: [
3068
+ /* @__PURE__ */ jsx(Trash2, { className: "mr-1 h-4 w-4" }),
3069
+ l.delete
3070
+ ]
3071
+ }
3072
+ ),
3073
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
3074
+ /* @__PURE__ */ jsx(
3075
+ Button,
3076
+ {
3077
+ type: "button",
3078
+ variant: "outline",
3079
+ onClick: onCancel,
3080
+ "data-testid": "calendar-editor-cancel",
3081
+ children: l.cancel
3082
+ }
3083
+ ),
3084
+ /* @__PURE__ */ jsx(
3085
+ Button,
3086
+ {
3087
+ type: "submit",
3088
+ disabled: !!saving || problem === "invalidRange",
3089
+ "data-testid": "calendar-editor-save",
3090
+ children: saving ? l.saving : l.save
3091
+ }
3092
+ )
3093
+ ] })
3094
+ ]
3095
+ }
3096
+ );
3097
+ }
3098
+ var partstatLabel = (p, l) => ({
3099
+ needs_action: l.needsAction,
3100
+ accepted: l.accepted,
3101
+ declined: l.declined,
3102
+ tentative: l.tentative,
3103
+ delegated: l.delegated
3104
+ })[p ?? "needs_action"];
3105
+ function Row({ icon, children }) {
3106
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 text-sm", children: [
3107
+ /* @__PURE__ */ jsx(
3108
+ "span",
3109
+ {
3110
+ className: "mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground",
3111
+ "aria-hidden": true,
3112
+ children: icon
3113
+ }
3114
+ ),
3115
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children })
3116
+ ] });
3117
+ }
3118
+ function EventDetails({
3119
+ event,
3120
+ timeZone,
3121
+ calendarName,
3122
+ calendarColor,
3123
+ onEdit,
3124
+ onDelete,
3125
+ onRespond,
3126
+ onProposeTime,
3127
+ onJoin,
3128
+ onClose,
3129
+ canJoin = true,
3130
+ labels: labelOverrides,
3131
+ className
3132
+ }) {
3133
+ const l = mergeLabels(labelOverrides);
3134
+ const zone = safeZone(timeZone);
3135
+ const color = event.color || calendarColor || "#3b82f6";
3136
+ const restricted = !!event.restricted;
3137
+ const title = restricted ? l.busy : event.title?.trim() || l.noTitle;
3138
+ const eventZone = event.start.time_zone;
3139
+ const otherZone = !event.all_day && eventZone && eventZone !== zone ? safeZone(eventZone, zone) : null;
3140
+ const attendees = event.attendees ?? [];
3141
+ const counts = attendees.reduce((acc, a) => {
3142
+ const k = a.partstat ?? "needs_action";
3143
+ acc[k] = (acc[k] ?? 0) + 1;
3144
+ return acc;
3145
+ }, {});
3146
+ const canRespond = !!onRespond && !event.is_organizer && event.my_partstat != null && !restricted;
3147
+ const invitee = event.is_organizer === false && event.my_partstat != null;
3148
+ const conference = !restricted ? event.conference : null;
3149
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), "data-testid": "calendar-event-details", children: [
3150
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
3151
+ /* @__PURE__ */ jsx(
3152
+ "span",
3153
+ {
3154
+ className: "mt-1.5 h-3 w-3 shrink-0 rounded-sm",
3155
+ style: { backgroundColor: color },
3156
+ "aria-hidden": true
3157
+ }
3158
+ ),
3159
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3160
+ /* @__PURE__ */ jsx(
3161
+ "h3",
3162
+ {
3163
+ className: cn(
3164
+ "break-words text-lg font-semibold leading-snug",
3165
+ event.status === "cancelled" && "line-through"
3166
+ ),
3167
+ "data-testid": "calendar-details-title",
3168
+ children: title
3169
+ }
3170
+ ),
3171
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", "data-testid": "calendar-details-when", children: formatEventWhen(event, zone) }),
3172
+ otherZone && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
3173
+ formatInZone(event.start_utc, otherZone, { hour: "numeric", minute: "2-digit" }),
3174
+ " \u2013",
3175
+ " ",
3176
+ formatInZone(event.end_utc, otherZone, { hour: "numeric", minute: "2-digit" }),
3177
+ " ",
3178
+ zoneCity(otherZone),
3179
+ " (",
3180
+ zoneOffsetLabel(otherZone, event.start_utc),
3181
+ ")"
3182
+ ] })
3183
+ ] }),
3184
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center", children: [
3185
+ onEdit && !invitee && event.can_edit !== false && !restricted && /* @__PURE__ */ jsx(
3186
+ Button,
3187
+ {
3188
+ type: "button",
3189
+ variant: "ghost",
3190
+ size: "icon",
3191
+ className: "h-8 w-8",
3192
+ "aria-label": l.edit,
3193
+ onClick: onEdit,
3194
+ "data-testid": "calendar-details-edit",
3195
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
3196
+ }
3197
+ ),
3198
+ onDelete && !invitee && event.can_edit !== false && !restricted && /* @__PURE__ */ jsx(
3199
+ Button,
3200
+ {
3201
+ type: "button",
3202
+ variant: "ghost",
3203
+ size: "icon",
3204
+ className: "h-8 w-8",
3205
+ "aria-label": l.delete,
3206
+ onClick: onDelete,
3207
+ "data-testid": "calendar-details-delete",
3208
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
3209
+ }
3210
+ ),
3211
+ onDelete && invitee && /* @__PURE__ */ jsx(
3212
+ Button,
3213
+ {
3214
+ type: "button",
3215
+ variant: "ghost",
3216
+ size: "icon",
3217
+ className: "h-8 w-8",
3218
+ "aria-label": l.removeFromCalendar,
3219
+ title: l.removeFromCalendar,
3220
+ onClick: onDelete,
3221
+ "data-testid": "calendar-details-remove",
3222
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
3223
+ }
3224
+ ),
3225
+ onClose && /* @__PURE__ */ jsx(
3226
+ Button,
3227
+ {
3228
+ type: "button",
3229
+ variant: "ghost",
3230
+ size: "icon",
3231
+ className: "h-8 w-8",
3232
+ "aria-label": l.cancel,
3233
+ onClick: onClose,
3234
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
3235
+ }
3236
+ )
3237
+ ] })
3238
+ ] }),
3239
+ event.is_recurring && /* @__PURE__ */ jsx(Row, { icon: /* @__PURE__ */ jsx(Repeat, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: l.recurring }) }),
3240
+ conference?.url && /* @__PURE__ */ jsx(Row, { icon: /* @__PURE__ */ jsx(Video, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
3241
+ /* @__PURE__ */ jsxs(
3242
+ Button,
3243
+ {
3244
+ type: "button",
3245
+ size: "sm",
3246
+ disabled: !canJoin,
3247
+ onClick: () => onJoin ? onJoin() : window.open(conference.url ?? "", "_blank", "noopener"),
3248
+ "data-testid": "calendar-details-join",
3249
+ children: [
3250
+ l.join,
3251
+ conference.type === "arbi" ? ` ${l.arbiCall}` : ""
3252
+ ]
3253
+ }
3254
+ ),
3255
+ conference.type === "external" && /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-xs text-muted-foreground", children: conference.url })
3256
+ ] }) }),
3257
+ !restricted && event.location && /* @__PURE__ */ jsx(Row, { icon: /* @__PURE__ */ jsx(MapPin, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsx("span", { className: "break-words", children: event.location }) }),
3258
+ calendarName && /* @__PURE__ */ jsx(Row, { icon: /* @__PURE__ */ jsx(CalendarDays, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsx("span", { children: calendarName }) }),
3259
+ restricted && /* @__PURE__ */ jsx(Row, { icon: /* @__PURE__ */ jsx(Lock, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: l.private }) }),
3260
+ !restricted && (event.organizer || attendees.length > 0) && /* @__PURE__ */ jsx(Row, { icon: /* @__PURE__ */ jsx(Users, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", "data-testid": "calendar-details-attendees", children: [
3261
+ attendees.length > 0 && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
3262
+ attendees.length,
3263
+ " \xB7",
3264
+ " ",
3265
+ Object.entries(counts).map(([k, n]) => `${n} ${partstatLabel(k, l).toLowerCase()}`).join(", ")
3266
+ ] }),
3267
+ event.organizer && /* @__PURE__ */ jsxs("div", { className: "truncate", children: [
3268
+ event.organizer.name || event.organizer.email,
3269
+ " ",
3270
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
3271
+ "\xB7 ",
3272
+ l.organizer
3273
+ ] })
3274
+ ] }),
3275
+ /* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: attendees.filter((a) => !a.is_organizer).map((a, i) => /* @__PURE__ */ jsxs(
3276
+ "li",
3277
+ {
3278
+ className: "flex items-center gap-1.5",
3279
+ "data-testid": "calendar-details-attendee",
3280
+ "data-partstat": a.partstat ?? "needs_action",
3281
+ "data-kind": a.kind ?? "individual",
3282
+ children: [
3283
+ a.partstat === "accepted" ? /* @__PURE__ */ jsx(CircleCheck, { className: "h-3.5 w-3.5 text-green-600", "aria-label": l.accepted }) : a.partstat === "declined" ? /* @__PURE__ */ jsx(CircleX, { className: "h-3.5 w-3.5 text-destructive", "aria-label": l.declined }) : /* @__PURE__ */ jsx(
3284
+ Clock,
3285
+ {
3286
+ className: "h-3.5 w-3.5 text-muted-foreground",
3287
+ "aria-label": partstatLabel(a.partstat, l)
3288
+ }
3289
+ ),
3290
+ (a.kind === "room" || a.kind === "resource") && /* @__PURE__ */ jsx(DoorOpen, { className: "h-3.5 w-3.5 text-muted-foreground", "aria-label": l.room }),
3291
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: a.name || a.email }),
3292
+ a.role === "optional" && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
3293
+ "(",
3294
+ l.optional,
3295
+ ")"
3296
+ ] }),
3297
+ /* @__PURE__ */ jsx(
3298
+ "span",
3299
+ {
3300
+ className: cn(
3301
+ "ml-auto shrink-0 rounded-full px-1.5 py-0.5 text-[10px]",
3302
+ a.partstat === "accepted" ? "bg-green-500/15 text-green-700 dark:text-green-400" : a.partstat === "declined" ? "bg-destructive/15 text-destructive" : "bg-muted text-muted-foreground"
3303
+ ),
3304
+ children: partstatLabel(a.partstat, l)
3305
+ }
3306
+ )
3307
+ ]
3308
+ },
3309
+ `${a.email ?? a.user_id ?? i}`
3310
+ )) })
3311
+ ] }) }),
3312
+ !restricted && event.description && /* @__PURE__ */ jsx(
3313
+ "div",
3314
+ {
3315
+ className: "whitespace-pre-wrap break-words rounded-md bg-muted/40 p-2 text-sm",
3316
+ "data-testid": "calendar-details-description",
3317
+ children: event.description
3318
+ }
3319
+ ),
3320
+ !restricted && (event.attachments?.length ?? 0) > 0 && /* @__PURE__ */ jsx(Row, { icon: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: event.attachments?.map((a, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
3321
+ "a",
3322
+ {
3323
+ href: a.url,
3324
+ target: "_blank",
3325
+ rel: "noreferrer",
3326
+ className: "text-primary hover:underline",
3327
+ children: a.title || a.url
3328
+ }
3329
+ ) }, `${a.url}-${i}`)) }) }),
3330
+ !restricted && (event.categories?.length ?? 0) > 0 && /* @__PURE__ */ jsx(Row, { icon: /* @__PURE__ */ jsx(Tag, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: event.categories?.map((c) => /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-2 py-0.5 text-xs", children: c }, c)) }) }),
3331
+ !restricted && (event.reminders?.length ?? 0) > 0 && /* @__PURE__ */ jsx(Row, { icon: /* @__PURE__ */ jsx(Bell, { className: "h-4 w-4" }), children: /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: event.reminders?.map((r) => formatReminder(r.minutes_before)).join(", ") }) }),
3332
+ canRespond && /* @__PURE__ */ jsxs(
3333
+ "div",
3334
+ {
3335
+ className: "flex flex-wrap items-center gap-2 border-t border-border pt-3",
3336
+ "data-testid": "calendar-details-rsvp",
3337
+ children: [
3338
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: l.going }),
3339
+ [
3340
+ ["accepted", l.accept],
3341
+ ["declined", l.decline],
3342
+ ["tentative", l.maybe]
3343
+ ].map(([p, label]) => /* @__PURE__ */ jsx(
3344
+ Button,
3345
+ {
3346
+ type: "button",
3347
+ size: "sm",
3348
+ variant: event.my_partstat === p ? "default" : "outline",
3349
+ "aria-pressed": event.my_partstat === p,
3350
+ onClick: () => onRespond?.(p),
3351
+ "data-testid": `calendar-details-rsvp-${p}`,
3352
+ children: label
3353
+ },
3354
+ p
3355
+ )),
3356
+ onProposeTime && /* @__PURE__ */ jsx(
3357
+ Button,
3358
+ {
3359
+ type: "button",
3360
+ size: "sm",
3361
+ variant: "ghost",
3362
+ onClick: onProposeTime,
3363
+ "data-testid": "calendar-details-propose",
3364
+ children: l.proposeNewTime
3365
+ }
3366
+ )
3367
+ ]
3368
+ }
3369
+ )
3370
+ ] });
3371
+ }
3372
+ function ScopeDialog({
3373
+ open,
3374
+ action,
3375
+ onChoose,
3376
+ onCancel,
3377
+ allowFollowing = true,
3378
+ labels: labelOverrides
3379
+ }) {
3380
+ const l = mergeLabels(labelOverrides);
3381
+ const [scope, setScope] = useState("this");
3382
+ const options = [
3383
+ ["this", l.scopeThis],
3384
+ ...allowFollowing ? [["following", l.scopeFollowing]] : [],
3385
+ ["all", l.scopeAll]
3386
+ ];
3387
+ const title = action === "edit" ? l.scopeEditTitle : action === "respond" ? l.scopeRespondTitle : l.scopeDeleteTitle;
3388
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (o) => !o && onCancel(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-sm", "data-testid": "calendar-scope-dialog", children: [
3389
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
3390
+ /* @__PURE__ */ jsx(DialogTitle, { children: title }),
3391
+ /* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: title })
3392
+ ] }),
3393
+ /* @__PURE__ */ jsxs(
3394
+ "form",
3395
+ {
3396
+ onSubmit: (e) => {
3397
+ e.preventDefault();
3398
+ onChoose(scope);
3399
+ },
3400
+ children: [
3401
+ /* @__PURE__ */ jsxs("fieldset", { className: "space-y-2 py-2", children: [
3402
+ /* @__PURE__ */ jsx("legend", { className: "sr-only", children: title }),
3403
+ options.map(([value, label]) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [
3404
+ /* @__PURE__ */ jsx(
3405
+ "input",
3406
+ {
3407
+ type: "radio",
3408
+ name: "calendar-scope",
3409
+ value,
3410
+ checked: scope === value,
3411
+ onChange: () => setScope(value),
3412
+ "data-testid": `calendar-scope-${value}`
3413
+ }
3414
+ ),
3415
+ label
3416
+ ] }, value))
3417
+ ] }),
3418
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
3419
+ /* @__PURE__ */ jsx(
3420
+ Button,
3421
+ {
3422
+ type: "button",
3423
+ variant: "outline",
3424
+ onClick: onCancel,
3425
+ "data-testid": "calendar-scope-cancel",
3426
+ children: l.cancel
3427
+ }
3428
+ ),
3429
+ /* @__PURE__ */ jsx(
3430
+ Button,
3431
+ {
3432
+ type: "submit",
3433
+ variant: action === "delete" ? "destructive" : "default",
3434
+ "data-testid": "calendar-scope-ok",
3435
+ children: action === "delete" ? l.delete : action === "respond" ? l.send : l.save
3436
+ }
3437
+ )
3438
+ ] })
3439
+ ]
3440
+ }
3441
+ )
3442
+ ] }) });
3443
+ }
3444
+
3445
+ // src/calendar/scheduling.ts
3446
+ var pad22 = (n) => String(n).padStart(2, "0");
3447
+ function dayWindow(date, zone, startHour = 7, endHour = 20) {
3448
+ const startMs = wallToEpochMs(`${date}T${pad22(startHour)}:00`, zone);
3449
+ const endMs = endHour >= 24 ? wallToEpochMs(`${date}T23:59`, zone) + 6e4 : wallToEpochMs(`${date}T${pad22(endHour)}:00`, zone);
3450
+ return { startMs, endMs };
3451
+ }
3452
+ function placeIntervals(intervals, win) {
3453
+ const span = win.endMs - win.startMs;
3454
+ if (span <= 0) return [];
3455
+ const out = [];
3456
+ for (const i of intervals) {
3457
+ const s = Math.max(Date.parse(i.startUtc), win.startMs);
3458
+ const e = Math.min(Date.parse(i.endUtc), win.endMs);
3459
+ if (!(e > s)) continue;
3460
+ out.push({
3461
+ startMs: s,
3462
+ endMs: e,
3463
+ leftPct: (s - win.startMs) / span * 100,
3464
+ widthPct: (e - s) / span * 100
3465
+ });
3466
+ }
3467
+ return out.sort((a, b) => a.startMs - b.startMs);
3468
+ }
3469
+ function outsideIntervals(working, win) {
3470
+ const span = win.endMs - win.startMs;
3471
+ const placed = placeIntervals(working, win);
3472
+ const gaps = [];
3473
+ let cursor = win.startMs;
3474
+ for (const w of placed) {
3475
+ if (w.startMs > cursor) gaps.push(mk(cursor, w.startMs));
3476
+ cursor = Math.max(cursor, w.endMs);
3477
+ }
3478
+ if (cursor < win.endMs) gaps.push(mk(cursor, win.endMs));
3479
+ return gaps;
3480
+ function mk(s, e) {
3481
+ return {
3482
+ startMs: s,
3483
+ endMs: e,
3484
+ leftPct: (s - win.startMs) / span * 100,
3485
+ widthPct: (e - s) / span * 100
3486
+ };
3487
+ }
3488
+ }
3489
+ function hourTicks(date, zone, startHour, endHour) {
3490
+ const win = dayWindow(date, zone, startHour, endHour);
3491
+ const span = win.endMs - win.startMs;
3492
+ const ticks = [];
3493
+ for (let h = startHour; h < endHour; h++) {
3494
+ const ms = wallToEpochMs(`${date}T${pad22(h)}:00`, zone);
3495
+ ticks.push({ hour: h, leftPct: (ms - win.startMs) / span * 100 });
3496
+ }
3497
+ return ticks;
3498
+ }
3499
+ function SchedulingAssistant({
3500
+ date,
3501
+ timeZone,
3502
+ rows,
3503
+ selection,
3504
+ dayStartHour = 7,
3505
+ dayEndHour = 20,
3506
+ loading,
3507
+ suggestions,
3508
+ finding,
3509
+ onFindTime,
3510
+ onPickSuggestion,
3511
+ onDateChange,
3512
+ labels: labelOverrides,
3513
+ className
3514
+ }) {
3515
+ const l = mergeLabels(labelOverrides);
3516
+ const zone = safeZone(timeZone);
3517
+ const win = dayWindow(date, zone, dayStartHour, dayEndHour);
3518
+ const ticks = hourTicks(date, zone, dayStartHour, dayEndHour);
3519
+ const sel = selection ? placeIntervals([selection], win)[0] : void 0;
3520
+ const time = (iso) => formatInZone(iso, zone, { hour: "numeric", minute: "2-digit" });
3521
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), "data-testid": "calendar-scheduling", children: [
3522
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3523
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: l.schedulingAssistant }),
3524
+ onDateChange && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
3525
+ /* @__PURE__ */ jsx(
3526
+ Button,
3527
+ {
3528
+ type: "button",
3529
+ variant: "ghost",
3530
+ size: "icon",
3531
+ className: "h-7 w-7",
3532
+ "aria-label": l.previous,
3533
+ onClick: () => onDateChange(addDays(date, -1)),
3534
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
3535
+ }
3536
+ ),
3537
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", "data-testid": "calendar-scheduling-date", children: formatPlainDate(date, { weekday: "short", day: "numeric", month: "short" }) }),
3538
+ /* @__PURE__ */ jsx(
3539
+ Button,
3540
+ {
3541
+ type: "button",
3542
+ variant: "ghost",
3543
+ size: "icon",
3544
+ className: "h-7 w-7",
3545
+ "aria-label": l.next,
3546
+ onClick: () => onDateChange(addDays(date, 1)),
3547
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
3548
+ }
3549
+ )
3550
+ ] })
3551
+ ] }),
3552
+ /* @__PURE__ */ jsxs("div", { className: cn("rounded-md border border-border", loading && "opacity-60"), children: [
3553
+ /* @__PURE__ */ jsxs("div", { className: "flex border-b border-border text-[10px] text-muted-foreground", children: [
3554
+ /* @__PURE__ */ jsx("div", { className: "w-32 shrink-0 sm:w-40" }),
3555
+ /* @__PURE__ */ jsx("div", { className: "relative h-5 flex-1", children: ticks.map((t) => /* @__PURE__ */ jsx(
3556
+ "span",
3557
+ {
3558
+ className: "absolute top-0.5 -translate-x-1/2",
3559
+ style: { left: `${t.leftPct}%` },
3560
+ children: t.hour
3561
+ },
3562
+ t.hour
3563
+ )) })
3564
+ ] }),
3565
+ rows.map((row) => /* @__PURE__ */ jsxs(
3566
+ "div",
3567
+ {
3568
+ className: "flex items-stretch border-b border-border last:border-b-0",
3569
+ "data-testid": "calendar-scheduling-row",
3570
+ "data-known": row.known,
3571
+ children: [
3572
+ /* @__PURE__ */ jsxs("div", { className: "flex w-32 shrink-0 items-center gap-1.5 truncate px-2 py-1 text-xs sm:w-40", children: [
3573
+ row.kind === "room" ? /* @__PURE__ */ jsx(
3574
+ DoorOpen,
3575
+ {
3576
+ className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
3577
+ "aria-label": l.room
3578
+ }
3579
+ ) : /* @__PURE__ */ jsx(User, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true }),
3580
+ /* @__PURE__ */ jsx("span", { className: "truncate", title: row.label, children: row.label })
3581
+ ] }),
3582
+ /* @__PURE__ */ jsxs("div", { className: "relative h-7 flex-1 overflow-hidden", children: [
3583
+ !row.known ? /* @__PURE__ */ jsx(
3584
+ "div",
3585
+ {
3586
+ className: "absolute inset-0 flex items-center justify-center bg-[repeating-linear-gradient(45deg,transparent,transparent_4px,hsl(var(--muted))_4px,hsl(var(--muted))_8px)] text-[10px] text-muted-foreground",
3587
+ title: l.unknownAvailability,
3588
+ children: l.unknownAvailability
3589
+ }
3590
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
3591
+ row.working && outsideIntervals(row.working, win).map((g, i) => /* @__PURE__ */ jsx(
3592
+ "div",
3593
+ {
3594
+ className: "absolute inset-y-0 bg-muted",
3595
+ style: { left: `${g.leftPct}%`, width: `${g.widthPct}%` },
3596
+ title: l.outsideWorkingHours,
3597
+ "data-testid": "calendar-scheduling-outside"
3598
+ },
3599
+ `o${i}`
3600
+ )),
3601
+ placeIntervals(row.busy, win).map((b, i) => {
3602
+ const src = row.busy.find(
3603
+ (x) => Math.max(Date.parse(x.startUtc), win.startMs) === b.startMs && Math.min(Date.parse(x.endUtc), win.endMs) === b.endMs
3604
+ );
3605
+ const tentative = src?.showAs === "tentative";
3606
+ const oof = src?.showAs === "oof";
3607
+ return /* @__PURE__ */ jsx(
3608
+ "div",
3609
+ {
3610
+ className: cn(
3611
+ "absolute inset-y-1 rounded-sm",
3612
+ oof ? "bg-purple-500/70" : tentative ? "bg-primary/35" : "bg-primary/75"
3613
+ ),
3614
+ style: { left: `${b.leftPct}%`, width: `${Math.max(b.widthPct, 0.5)}%` },
3615
+ title: `${src?.title ? `${src.title} \xB7 ` : ""}${time(new Date(b.startMs).toISOString())} \u2013 ${time(new Date(b.endMs).toISOString())}`,
3616
+ "data-testid": "calendar-scheduling-busy"
3617
+ },
3618
+ `b${i}`
3619
+ );
3620
+ })
3621
+ ] }),
3622
+ sel && /* @__PURE__ */ jsx(
3623
+ "div",
3624
+ {
3625
+ className: "pointer-events-none absolute inset-y-0 border-x-2 border-green-600 bg-green-500/15",
3626
+ style: { left: `${sel.leftPct}%`, width: `${sel.widthPct}%` },
3627
+ "aria-hidden": true
3628
+ }
3629
+ )
3630
+ ] })
3631
+ ]
3632
+ },
3633
+ row.id
3634
+ ))
3635
+ ] }),
3636
+ onFindTime && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3637
+ /* @__PURE__ */ jsxs(
3638
+ Button,
3639
+ {
3640
+ type: "button",
3641
+ size: "sm",
3642
+ variant: "outline",
3643
+ onClick: onFindTime,
3644
+ disabled: finding,
3645
+ "data-testid": "calendar-find-time",
3646
+ children: [
3647
+ /* @__PURE__ */ jsx(Sparkles, { className: "mr-1 h-4 w-4" }),
3648
+ finding ? l.findingTimes : l.findATime
3649
+ ]
3650
+ }
3651
+ ),
3652
+ suggestions && suggestions.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: l.noSuggestions }),
3653
+ suggestions && suggestions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", "data-testid": "calendar-suggestions", children: suggestions.map((s) => /* @__PURE__ */ jsxs(
3654
+ "button",
3655
+ {
3656
+ type: "button",
3657
+ onClick: () => onPickSuggestion?.(s),
3658
+ className: "rounded-md border border-border px-2 py-1 text-left text-xs hover:bg-muted",
3659
+ "data-testid": "calendar-suggestion",
3660
+ children: [
3661
+ /* @__PURE__ */ jsxs("div", { className: "font-medium", children: [
3662
+ formatInZone(s.startUtc, zone, {
3663
+ weekday: "short",
3664
+ day: "numeric",
3665
+ month: "short"
3666
+ }),
3667
+ " ",
3668
+ time(s.startUtc),
3669
+ " \u2013 ",
3670
+ time(s.endUtc)
3671
+ ] }),
3672
+ /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: s.unavailable.length === 0 ? l.everyoneFree : `${s.unavailable.length} ${l.unavailableCount}` })
3673
+ ]
3674
+ },
3675
+ s.startUtc
3676
+ )) })
3677
+ ] })
3678
+ ] });
3679
+ }
3680
+ function RoomFinder({
3681
+ rooms,
3682
+ loading,
3683
+ minCapacity,
3684
+ feature,
3685
+ onFiltersChange,
3686
+ addedEmails,
3687
+ onAdd,
3688
+ labels: labelOverrides,
3689
+ className
3690
+ }) {
3691
+ const l = mergeLabels(labelOverrides);
3692
+ const added = new Set(addedEmails.map((e) => e.toLowerCase()));
3693
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), "data-testid": "calendar-room-finder", children: [
3694
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
3695
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: l.findRoom }),
3696
+ /* @__PURE__ */ jsx(
3697
+ Input,
3698
+ {
3699
+ type: "number",
3700
+ min: 1,
3701
+ className: "h-8 w-28",
3702
+ placeholder: l.minCapacity,
3703
+ "aria-label": l.minCapacity,
3704
+ value: minCapacity ?? "",
3705
+ onChange: (e) => onFiltersChange({
3706
+ minCapacity: e.target.value ? Math.max(1, Number(e.target.value)) : null,
3707
+ feature
3708
+ }),
3709
+ "data-testid": "calendar-room-capacity"
3710
+ }
3711
+ ),
3712
+ /* @__PURE__ */ jsx(
3713
+ Input,
3714
+ {
3715
+ className: "h-8 w-32",
3716
+ placeholder: l.featureFilter,
3717
+ "aria-label": l.featureFilter,
3718
+ value: feature,
3719
+ onChange: (e) => onFiltersChange({ minCapacity, feature: e.target.value }),
3720
+ "data-testid": "calendar-room-feature"
3721
+ }
3722
+ )
3723
+ ] }),
3724
+ /* @__PURE__ */ jsxs(
3725
+ "ul",
3726
+ {
3727
+ className: cn(
3728
+ "divide-y divide-border rounded-md border border-border",
3729
+ loading && "opacity-60"
3730
+ ),
3731
+ children: [
3732
+ rooms.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-xs text-muted-foreground", children: l.noRooms }),
3733
+ rooms.map((r) => {
3734
+ const isAdded = added.has(r.email.toLowerCase());
3735
+ return /* @__PURE__ */ jsxs(
3736
+ "li",
3737
+ {
3738
+ className: "flex items-center gap-2 px-3 py-1.5 text-sm",
3739
+ "data-testid": "calendar-room",
3740
+ "data-room-email": r.email,
3741
+ children: [
3742
+ /* @__PURE__ */ jsx(DoorOpen, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
3743
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3744
+ /* @__PURE__ */ jsx("div", { className: "truncate font-medium", children: r.name }),
3745
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-x-2 text-xs text-muted-foreground", children: [
3746
+ r.capacity != null && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-0.5", children: [
3747
+ /* @__PURE__ */ jsx(Users, { className: "h-3 w-3", "aria-hidden": true }),
3748
+ r.capacity
3749
+ ] }),
3750
+ (r.building || r.floor) && /* @__PURE__ */ jsx("span", { children: [r.building, r.floor].filter(Boolean).join(" \xB7 ") }),
3751
+ (r.features ?? []).length > 0 && /* @__PURE__ */ jsx("span", { children: r.features?.join(", ") })
3752
+ ] })
3753
+ ] }),
3754
+ r.free != null && /* @__PURE__ */ jsx(
3755
+ "span",
3756
+ {
3757
+ className: cn(
3758
+ "rounded-full px-2 py-0.5 text-xs",
3759
+ r.free ? "bg-green-500/15 text-green-700 dark:text-green-400" : "bg-destructive/15 text-destructive"
3760
+ ),
3761
+ "data-testid": "calendar-room-availability",
3762
+ children: r.free ? l.roomFree : l.roomBusy
3763
+ }
3764
+ ),
3765
+ /* @__PURE__ */ jsx(
3766
+ Button,
3767
+ {
3768
+ type: "button",
3769
+ size: "sm",
3770
+ variant: "outline",
3771
+ disabled: isAdded,
3772
+ onClick: () => onAdd(r),
3773
+ "data-testid": "calendar-room-add",
3774
+ children: isAdded ? l.roomAdded : l.addRoom
3775
+ }
3776
+ )
3777
+ ]
3778
+ },
3779
+ r.id
3780
+ );
3781
+ })
3782
+ ]
3783
+ }
3784
+ )
3785
+ ] });
3786
+ }
3787
+ var mondayFirstName = (day) => formatPlainDate(`2024-01-0${day + 1}`, { weekday: "long" });
3788
+ var sundayFirstName = (day) => formatPlainDate(day === 0 ? "2023-12-31" : `2024-01-0${day}`, { weekday: "long" });
3789
+ var VIEWS = ["day", "work_week", "week", "month", "agenda", "year"];
3790
+ var DURATIONS = [15, 30, 45, 60, 90, 120];
3791
+ var viewName = (v, l) => ({
3792
+ day: l.day,
3793
+ week: l.week,
3794
+ work_week: l.workWeek,
3795
+ month: l.month,
3796
+ agenda: l.agenda,
3797
+ year: l.year
3798
+ })[v];
3799
+ function Field({
3800
+ label,
3801
+ htmlFor,
3802
+ children
3803
+ }) {
3804
+ return /* @__PURE__ */ jsxs("div", { className: "grid gap-1.5 sm:grid-cols-[12rem_1fr] sm:items-center", children: [
3805
+ /* @__PURE__ */ jsx("label", { htmlFor, className: "text-sm font-medium", children: label }),
3806
+ /* @__PURE__ */ jsx("div", { className: "min-w-0", children })
3807
+ ] });
3808
+ }
3809
+ function CalendarSettingsForm({
3810
+ value,
3811
+ onChange,
3812
+ onSave,
3813
+ onCancel,
3814
+ calendars,
3815
+ zones,
3816
+ saving,
3817
+ error,
3818
+ labels: labelOverrides,
3819
+ className
3820
+ }) {
3821
+ const l = mergeLabels(labelOverrides);
3822
+ const uid = useId();
3823
+ const set = (patch) => onChange({ ...value, ...patch });
3824
+ const hoursFor = (day) => value.working_hours.find((h) => h.day === day);
3825
+ const setHours = (day, patch) => {
3826
+ const rest = value.working_hours.filter((h) => h.day !== day);
3827
+ if (patch === null) return set({ working_hours: rest });
3828
+ const cur = hoursFor(day) ?? { day, start: "09:00", end: "17:00" };
3829
+ set({ working_hours: [...rest, { ...cur, ...patch }].sort((a, b) => a.day - b.day) });
3830
+ };
3831
+ const overlays = value.overlays ?? [];
3832
+ const toggleOverlay = (o) => set({ overlays: overlays.includes(o) ? overlays.filter((x) => x !== o) : [...overlays, o] });
3833
+ const writable = (calendars ?? []).filter((c) => c.canWrite && !c.locked);
3834
+ return /* @__PURE__ */ jsxs(
3835
+ "form",
3836
+ {
3837
+ className: cn("space-y-5", className),
3838
+ onSubmit: (e) => {
3839
+ e.preventDefault();
3840
+ onSave();
3841
+ },
3842
+ "data-testid": "calendar-settings-form",
3843
+ "aria-label": l.settingsTitle,
3844
+ children: [
3845
+ /* @__PURE__ */ jsx(Field, { label: l.timeZone, children: /* @__PURE__ */ jsx(
3846
+ TimeZonePicker,
3847
+ {
3848
+ value: value.time_zone,
3849
+ zones,
3850
+ labels: labelOverrides,
3851
+ className: "w-full sm:w-80",
3852
+ onChange: (z) => set({ time_zone: z })
3853
+ }
3854
+ ) }),
3855
+ /* @__PURE__ */ jsx(Field, { label: l.secondaryTimeZone, children: /* @__PURE__ */ jsx(
3856
+ TimeZonePicker,
3857
+ {
3858
+ value: value.secondary_time_zone ?? null,
3859
+ zones,
3860
+ allowNone: true,
3861
+ ariaLabel: l.secondaryTimeZone,
3862
+ labels: labelOverrides,
3863
+ className: "w-full sm:w-80",
3864
+ onChange: (z) => set({ secondary_time_zone: z }),
3865
+ onClear: () => set({ secondary_time_zone: null })
3866
+ }
3867
+ ) }),
3868
+ /* @__PURE__ */ jsx(Field, { label: l.weekStart, htmlFor: `${uid}-week-start`, children: /* @__PURE__ */ jsx(
3869
+ NativeSelect,
3870
+ {
3871
+ id: `${uid}-week-start`,
3872
+ value: value.week_start,
3873
+ onChange: (e) => set({ week_start: Number(e.target.value) }),
3874
+ "data-testid": "calendar-settings-week-start",
3875
+ children: [1, 0, 6].map((d) => /* @__PURE__ */ jsx("option", { value: d, children: sundayFirstName(d) }, d))
3876
+ }
3877
+ ) }),
3878
+ /* @__PURE__ */ jsx(Field, { label: l.defaultView, htmlFor: `${uid}-view`, children: /* @__PURE__ */ jsx(
3879
+ NativeSelect,
3880
+ {
3881
+ id: `${uid}-view`,
3882
+ value: value.default_view,
3883
+ onChange: (e) => set({ default_view: e.target.value }),
3884
+ "data-testid": "calendar-settings-default-view",
3885
+ children: VIEWS.map((v) => /* @__PURE__ */ jsx("option", { value: v, children: viewName(v, l) }, v))
3886
+ }
3887
+ ) }),
3888
+ /* @__PURE__ */ jsx(Field, { label: l.defaultDuration, htmlFor: `${uid}-duration`, children: /* @__PURE__ */ jsx(
3889
+ NativeSelect,
3890
+ {
3891
+ id: `${uid}-duration`,
3892
+ value: value.default_duration_minutes,
3893
+ onChange: (e) => set({ default_duration_minutes: Number(e.target.value) }),
3894
+ "data-testid": "calendar-settings-duration",
3895
+ children: Array.from(/* @__PURE__ */ new Set([...DURATIONS, value.default_duration_minutes])).sort((a, b) => a - b).map((m) => /* @__PURE__ */ jsx("option", { value: m, children: `${m} ${l.minutes}` }, m))
3896
+ }
3897
+ ) }),
3898
+ writable.length > 0 && /* @__PURE__ */ jsx(Field, { label: l.defaultCalendar, htmlFor: `${uid}-calendar`, children: /* @__PURE__ */ jsxs(
3899
+ NativeSelect,
3900
+ {
3901
+ id: `${uid}-calendar`,
3902
+ value: value.default_calendar_id ?? "",
3903
+ onChange: (e) => set({ default_calendar_id: e.target.value || null }),
3904
+ "data-testid": "calendar-settings-default-calendar",
3905
+ children: [
3906
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
3907
+ writable.map((c) => /* @__PURE__ */ jsx("option", { value: c.id, children: c.name ?? c.id }, c.id))
3908
+ ]
3909
+ }
3910
+ ) }),
3911
+ /* @__PURE__ */ jsxs("fieldset", { className: "space-y-2", children: [
3912
+ /* @__PURE__ */ jsx("legend", { className: "pb-1 text-sm font-medium", children: l.workingHours }),
3913
+ [0, 1, 2, 3, 4, 5, 6].map((day) => {
3914
+ const h = hoursFor(day);
3915
+ return /* @__PURE__ */ jsxs(
3916
+ "div",
3917
+ {
3918
+ className: "flex flex-wrap items-center gap-2 text-sm",
3919
+ "data-testid": `calendar-settings-hours-${day}`,
3920
+ children: [
3921
+ /* @__PURE__ */ jsxs("label", { className: "flex w-36 items-center gap-2", children: [
3922
+ /* @__PURE__ */ jsx(
3923
+ "input",
3924
+ {
3925
+ type: "checkbox",
3926
+ checked: !!h,
3927
+ onChange: (e) => setHours(day, e.target.checked ? {} : null)
3928
+ }
3929
+ ),
3930
+ mondayFirstName(day)
3931
+ ] }),
3932
+ h && /* @__PURE__ */ jsxs(Fragment, { children: [
3933
+ /* @__PURE__ */ jsx(
3934
+ Input,
3935
+ {
3936
+ type: "time",
3937
+ className: "h-8 w-28",
3938
+ "aria-label": `${mondayFirstName(day)} ${l.start}`,
3939
+ value: h.start,
3940
+ onChange: (e) => e.target.value && setHours(day, { start: e.target.value })
3941
+ }
3942
+ ),
3943
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2013" }),
3944
+ /* @__PURE__ */ jsx(
3945
+ Input,
3946
+ {
3947
+ type: "time",
3948
+ className: "h-8 w-28",
3949
+ "aria-label": `${mondayFirstName(day)} ${l.end}`,
3950
+ value: h.end === "24:00" ? "23:59" : h.end,
3951
+ onChange: (e) => e.target.value && setHours(day, { end: e.target.value })
3952
+ }
3953
+ )
3954
+ ] })
3955
+ ]
3956
+ },
3957
+ day
3958
+ );
3959
+ })
3960
+ ] }),
3961
+ /* @__PURE__ */ jsx("div", { className: "space-y-3", children: [
3962
+ ["show_week_numbers", l.showWeekNumbers],
3963
+ ["show_declined", l.showDeclined]
3964
+ ].map(([key, label]) => /* @__PURE__ */ jsxs(
3965
+ "label",
3966
+ {
3967
+ className: "flex items-center justify-between gap-4 text-sm sm:justify-start",
3968
+ children: [
3969
+ /* @__PURE__ */ jsx("span", { className: "sm:w-48", children: label }),
3970
+ /* @__PURE__ */ jsx(
3971
+ Switch,
3972
+ {
3973
+ checked: !!value[key],
3974
+ onCheckedChange: (on) => set({ [key]: on }),
3975
+ "aria-label": label,
3976
+ "data-testid": `calendar-settings-${key}`
3977
+ }
3978
+ )
3979
+ ]
3980
+ },
3981
+ key
3982
+ )) }),
3983
+ /* @__PURE__ */ jsx(Field, { label: l.autoAddInvitations, htmlFor: `${uid}-auto-add`, children: /* @__PURE__ */ jsxs(
3984
+ NativeSelect,
3985
+ {
3986
+ id: `${uid}-auto-add`,
3987
+ value: value.auto_add_invitations ?? "all",
3988
+ onChange: (e) => set({
3989
+ auto_add_invitations: e.target.value
3990
+ }),
3991
+ "data-testid": "calendar-settings-auto-add",
3992
+ children: [
3993
+ /* @__PURE__ */ jsx("option", { value: "all", children: l.autoAddAll }),
3994
+ /* @__PURE__ */ jsx("option", { value: "known", children: l.autoAddKnown }),
3995
+ /* @__PURE__ */ jsx("option", { value: "none", children: l.autoAddNone })
3996
+ ]
3997
+ }
3998
+ ) }),
3999
+ /* @__PURE__ */ jsxs("fieldset", { className: "space-y-1.5", children: [
4000
+ /* @__PURE__ */ jsx("legend", { className: "pb-1 text-sm font-medium", children: l.overlays }),
4001
+ [
4002
+ ["facts", l.facts],
4003
+ ["workflows", l.workflows],
4004
+ ["tasks", l.tasks]
4005
+ ].map(([o, label]) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
4006
+ /* @__PURE__ */ jsx(
4007
+ "input",
4008
+ {
4009
+ type: "checkbox",
4010
+ checked: overlays.includes(o),
4011
+ onChange: () => toggleOverlay(o),
4012
+ "data-testid": `calendar-settings-overlay-${o}`
4013
+ }
4014
+ ),
4015
+ label
4016
+ ] }, o))
4017
+ ] }),
4018
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
4019
+ /* @__PURE__ */ jsx("label", { htmlFor: `${uid}-decline`, className: "text-sm font-medium", children: l.declineMessage }),
4020
+ /* @__PURE__ */ jsx(
4021
+ Textarea,
4022
+ {
4023
+ id: `${uid}-decline`,
4024
+ rows: 2,
4025
+ maxLength: 1e3,
4026
+ value: value.decline_message ?? "",
4027
+ onChange: (e) => set({ decline_message: e.target.value || null })
4028
+ }
4029
+ )
4030
+ ] }),
4031
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", role: "alert", children: error }),
4032
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
4033
+ onCancel && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: onCancel, children: l.cancel }),
4034
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: !!saving, "data-testid": "calendar-settings-save", children: saving ? l.saving : l.save })
4035
+ ] })
4036
+ ]
4037
+ }
4038
+ );
4039
+ }
4040
+
4041
+ export { CALENDAR_COLORS, CALENDAR_DEFAULT_LABELS, CALENDAR_NARROW_WIDTH, CalendarGrid, CalendarList, CalendarSettingsForm, DEFAULT_RECURRENCE_DESCRIBE_LABELS, DEFAULT_RECURRENCE_EDITOR_LABELS, DEFAULT_REMINDER_FORMAT_LABELS, EventDetails, EventEditor, MiniMonth, REMINDER_PRESETS, RRULE_WEEKDAYS, RecurrenceEditor, RoomFinder, SchedulingAssistant, ScopeDialog, TimeZonePicker, addDays, addMonths, allDayInclusiveEnd, buildRRule, contrastText, convertWall, dayWindow, defaultRecurrenceForm, describeRecurrence, detectTimeZone, diffDays, draftFromSlot, draftRangeUtc, effectiveCalendarView, eventStateClasses, formatEventWhen, formatInZone, formatPlainDate, formatReminder, formatZoneLabel, getTemporalTimeZonePlugin, hourTicks, isAllDayDraft, isValidTimeZone, listTimeZones, outsideIntervals, parseRRule, placeIntervals, reminderToMinutes, safeZone, setDraftAllDay, setDraftEnd, setDraftStart, setPosOf, specDate, specTime, splitReminderMinutes, startOfWeek, toBusinessHours, todayInZone, utcToDate, utcToTime, utcToWall, validateDraft, wallToUtc, weekdayOf, workWeekHiddenDays, zoneCity, zoneOffsetLabel, zoneOffsetMinutes };
4042
+ //# sourceMappingURL=index.js.map
4043
+ //# sourceMappingURL=index.js.map