@arbidocs/blocks 0.4.71 → 0.4.73

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