calendarkit-pro 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/index.tsx
4
- import React18, { useId, useState as useState6, useMemo as useMemo4 } from "react";
4
+ import React18, { useCallback as useCallback5, useId, useState as useState6, useMemo as useMemo4 } from "react";
5
5
  import { DndContext, useSensor, useSensors, PointerSensor, DragOverlay } from "@dnd-kit/core";
6
6
  import { createSnapModifier, restrictToWindowEdges } from "@dnd-kit/modifiers";
7
7
  import { AnimatePresence as AnimatePresence3, motion as motion4 } from "framer-motion";
@@ -94,7 +94,8 @@ var CalendarHeader = ({
94
94
  variant: "ghost",
95
95
  size: "icon",
96
96
  className: "text-muted-foreground hover:text-foreground hover:bg-accent/80 rounded-xl h-10 w-10 hidden md:inline-flex transition-all duration-200",
97
- onClick: onMenuClick
97
+ onClick: onMenuClick,
98
+ "aria-label": translations.toggleSidebar
98
99
  },
99
100
  /* @__PURE__ */ React2.createElement(Menu, { className: "h-5 w-5" })
100
101
  ), /* @__PURE__ */ React2.createElement(
@@ -111,7 +112,8 @@ var CalendarHeader = ({
111
112
  variant: "ghost",
112
113
  size: "icon",
113
114
  onClick: onPrev,
114
- className: "rounded-lg h-8 w-8 hover:bg-background/80 transition-all duration-200"
115
+ className: "rounded-lg h-8 w-8 hover:bg-background/80 transition-all duration-200",
116
+ "aria-label": translations.previous
115
117
  },
116
118
  /* @__PURE__ */ React2.createElement(ChevronLeft, { className: "h-4 w-4 text-muted-foreground" })
117
119
  ), /* @__PURE__ */ React2.createElement(
@@ -120,7 +122,8 @@ var CalendarHeader = ({
120
122
  variant: "ghost",
121
123
  size: "icon",
122
124
  onClick: onNext,
123
- className: "rounded-lg h-8 w-8 hover:bg-background/80 transition-all duration-200"
125
+ className: "rounded-lg h-8 w-8 hover:bg-background/80 transition-all duration-200",
126
+ "aria-label": translations.next
124
127
  },
125
128
  /* @__PURE__ */ React2.createElement(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })
126
129
  )), /* @__PURE__ */ React2.createElement("div", { className: "ml-2 md:ml-4" }, /* @__PURE__ */ React2.createElement("h2", { className: "text-lg md:text-xl font-semibold text-foreground whitespace-nowrap capitalize tracking-tight" }, format(currentDate, "MMMM yyyy", { locale }))))), /* @__PURE__ */ React2.createElement("div", { className: "flex items-center gap-2 md:gap-3 w-full md:w-auto justify-end" }, onLanguageChange && language && /* @__PURE__ */ React2.createElement(
@@ -129,6 +132,7 @@ var CalendarHeader = ({
129
132
  variant: "ghost",
130
133
  size: "sm",
131
134
  onClick: () => onLanguageChange(language === "en" ? "fr" : "en"),
135
+ "aria-label": translations.switchLanguage,
132
136
  className: "h-9 px-3 rounded-xl text-xs font-semibold text-muted-foreground hover:text-foreground hover:bg-accent/80 transition-all duration-200 uppercase tracking-wider"
133
137
  },
134
138
  language
@@ -138,7 +142,8 @@ var CalendarHeader = ({
138
142
  variant: "ghost",
139
143
  size: "icon",
140
144
  className: "rounded-xl h-9 w-9 hover:bg-accent/80 transition-all duration-200",
141
- onClick: onThemeToggle
145
+ onClick: onThemeToggle,
146
+ "aria-label": translations.toggleTheme
142
147
  },
143
148
  isDarkMode ? /* @__PURE__ */ React2.createElement(Sun, { className: "h-4 w-4 text-amber-500" }) : /* @__PURE__ */ React2.createElement(Moon, { className: "h-4 w-4 text-muted-foreground" })
144
149
  ), !hideViewSwitcher && /* @__PURE__ */ React2.createElement("div", { className: "flex items-center bg-muted/50 backdrop-blur-sm rounded-xl p-1" }, viewConfig.map(({ key, icon: Icon }) => /* @__PURE__ */ React2.createElement(
@@ -148,6 +153,8 @@ var CalendarHeader = ({
148
153
  variant: "ghost",
149
154
  size: "sm",
150
155
  onClick: () => onViewChange(key),
156
+ "aria-pressed": view === key,
157
+ "aria-label": translations[key],
151
158
  className: cn(
152
159
  "h-8 px-3 text-xs rounded-lg transition-all duration-200 gap-1.5",
153
160
  view === key ? "bg-background shadow-sm text-foreground font-medium border-[0.5px] border-border/50" : "text-muted-foreground hover:text-foreground hover:bg-background/50"
@@ -164,21 +171,26 @@ import { Plus, ChevronDown, Globe } from "lucide-react";
164
171
 
165
172
  // src/components/MiniCalendar.tsx
166
173
  import React3 from "react";
167
- import { format as format3, isSameMonth, isSameDay, isToday, addMonths, subMonths } from "date-fns";
174
+ import { format as format2, isSameMonth, isSameDay, isToday, addMonths, subMonths } from "date-fns";
168
175
 
169
- // src/lib/date.ts
170
- import { startOfMonth, endOfMonth, startOfWeek, endOfWeek, eachDayOfInterval, format as format2 } from "date-fns";
171
- var getMonthGrid = (date, weekStartOn = 0) => {
176
+ // src/core/week.ts
177
+ import { eachDayOfInterval, endOfMonth, endOfWeek, startOfMonth, startOfWeek } from "date-fns";
178
+ function resolveWeekStartsOn(locale, fallback) {
179
+ return locale?.options?.weekStartsOn ?? fallback;
180
+ }
181
+ function getWeekDays(date, weekStartsOn) {
182
+ return eachDayOfInterval({
183
+ start: startOfWeek(date, { weekStartsOn }),
184
+ end: endOfWeek(date, { weekStartsOn })
185
+ });
186
+ }
187
+ function getMonthGrid(date, weekStartsOn) {
172
188
  const monthStart = startOfMonth(date);
173
189
  return eachDayOfInterval({
174
- start: startOfWeek(monthStart, { weekStartsOn: weekStartOn }),
175
- end: endOfWeek(endOfMonth(monthStart), { weekStartsOn: weekStartOn })
190
+ start: startOfWeek(monthStart, { weekStartsOn }),
191
+ end: endOfWeek(endOfMonth(monthStart), { weekStartsOn })
176
192
  });
177
- };
178
- var uses24HourClock = (locale) => {
179
- if (!locale) return false;
180
- return !/[ap]\.?m/i.test(format2(new Date(2e3, 0, 1, 13, 0), "p", { locale }));
181
- };
193
+ }
182
194
 
183
195
  // src/components/MiniCalendar.tsx
184
196
  import { ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight2 } from "lucide-react";
@@ -187,13 +199,14 @@ var MiniCalendar = ({
187
199
  onDateChange,
188
200
  onViewChange,
189
201
  className,
190
- locale
202
+ locale,
203
+ labels
191
204
  }) => {
192
205
  const [viewDate, setViewDate] = React3.useState(currentDate);
193
206
  React3.useEffect(() => {
194
207
  setViewDate(currentDate);
195
208
  }, [currentDate]);
196
- const weekStartsOn = locale?.options?.weekStartsOn ?? 0;
209
+ const weekStartsOn = resolveWeekStartsOn(locale, 0);
197
210
  const days = React3.useMemo(
198
211
  () => getMonthGrid(viewDate, weekStartsOn),
199
212
  [viewDate, weekStartsOn]
@@ -218,14 +231,14 @@ var MiniCalendar = ({
218
231
  onDateChange(day);
219
232
  if (onViewChange) onViewChange("day");
220
233
  };
221
- return /* @__PURE__ */ React3.createElement("div", { className: cn("px-4 w-[260px]", className) }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center justify-between mb-4" }, /* @__PURE__ */ React3.createElement("span", { className: "text-sm font-semibold text-foreground capitalize" }, format3(viewDate, "MMMM yyyy", { locale })), /* @__PURE__ */ React3.createElement("div", { className: "flex items-center bg-muted/40 rounded-lg p-0.5" }, /* @__PURE__ */ React3.createElement(
234
+ return /* @__PURE__ */ React3.createElement("div", { className: cn("px-4 w-[260px]", className) }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center justify-between mb-4" }, /* @__PURE__ */ React3.createElement("span", { className: "text-sm font-semibold text-foreground capitalize" }, format2(viewDate, "MMMM yyyy", { locale })), /* @__PURE__ */ React3.createElement("div", { className: "flex items-center bg-muted/40 rounded-lg p-0.5" }, /* @__PURE__ */ React3.createElement(
222
235
  Button,
223
236
  {
224
237
  variant: "ghost",
225
238
  size: "icon",
226
239
  className: "h-7 w-7 rounded-md hover:bg-background/80 transition-all",
227
240
  onClick: handlePrev,
228
- "aria-label": "Previous month"
241
+ "aria-label": labels?.previousMonth || "Previous month"
229
242
  },
230
243
  /* @__PURE__ */ React3.createElement(ChevronLeft2, { className: "h-4 w-4" })
231
244
  ), /* @__PURE__ */ React3.createElement(
@@ -235,7 +248,7 @@ var MiniCalendar = ({
235
248
  size: "icon",
236
249
  className: "h-7 w-7 rounded-md hover:bg-background/80 transition-all",
237
250
  onClick: handleNext,
238
- "aria-label": "Next month"
251
+ "aria-label": labels?.nextMonth || "Next month"
239
252
  },
240
253
  /* @__PURE__ */ React3.createElement(ChevronRight2, { className: "h-4 w-4" })
241
254
  ))), /* @__PURE__ */ React3.createElement("div", { className: "grid grid-cols-7 gap-y-2 text-center mb-2" }, weekDays.map((day, i) => /* @__PURE__ */ React3.createElement("div", { key: `${day}-${i}`, className: "text-[10px] text-muted-foreground/70 font-semibold uppercase" }, day))), /* @__PURE__ */ React3.createElement("div", { className: "grid grid-cols-7 gap-y-1 text-center" }, days.map((day) => {
@@ -255,7 +268,7 @@ var MiniCalendar = ({
255
268
  !isSelected && isTodayDate && "bg-primary/10 text-primary ring-1 ring-primary/30"
256
269
  )
257
270
  },
258
- format3(day, "d")
271
+ format2(day, "d")
259
272
  );
260
273
  })));
261
274
  };
@@ -263,6 +276,281 @@ var MiniCalendar = ({
263
276
  // src/components/Sidebar.tsx
264
277
  import { toZonedTime } from "date-fns-tz";
265
278
  import { format as format4 } from "date-fns";
279
+
280
+ // src/core/format.ts
281
+ import { format as format3 } from "date-fns";
282
+
283
+ // src/locales/index.ts
284
+ var en = {
285
+ today: "Today",
286
+ month: "Month",
287
+ week: "Week",
288
+ day: "Day",
289
+ agenda: "Agenda",
290
+ resource: "Resources",
291
+ createEvent: "Create Event",
292
+ editEvent: "Edit Event",
293
+ delete: "Delete",
294
+ save: "Save",
295
+ cancel: "Cancel",
296
+ title: "Add title",
297
+ start: "Start",
298
+ end: "End",
299
+ allDay: "All Day",
300
+ description: "Description",
301
+ repeat: "Repeat",
302
+ noRepeat: "Does not repeat",
303
+ selectCalendar: "Select Calendar",
304
+ selectType: "Select Type",
305
+ daily: "Daily",
306
+ weekly: "Weekly",
307
+ monthly: "Monthly",
308
+ yearly: "Yearly",
309
+ search: "Search events...",
310
+ allTypes: "All Types",
311
+ timezone: "Time zone",
312
+ localTime: "Local Time",
313
+ create: "Create",
314
+ calendars: "Calendars",
315
+ event: "Event",
316
+ task: "Task",
317
+ appointmentSchedule: "Appointment schedule",
318
+ new: "New",
319
+ dateAndTime: "Date & Time",
320
+ whosJoining: "Who's joining?",
321
+ suggestedTimes: "Suggested times for guests",
322
+ viewSuggestions: "View suggestions",
323
+ whereWillItBe: "Where will it be?",
324
+ location: "Enter address",
325
+ descriptionAndAttachments: "Description",
326
+ dragAndDrop: "Drag & drop files or",
327
+ guests: "Guests",
328
+ addGuests: "Add guests",
329
+ addAttachment: "Upload files",
330
+ moreOptions: "More options",
331
+ doesNotRepeat: "Does not repeat",
332
+ locationHelpText: 'Enter a physical address (e.g. "123 Main St") to generate the map.',
333
+ notes: "Notes...",
334
+ guestCount: "guest",
335
+ guestsCount: "guests",
336
+ repeatFor: "Repeat for",
337
+ times: "times",
338
+ until: "Until",
339
+ endRepeat: "End repeat",
340
+ never: "Never",
341
+ afterOccurrences: "After occurrences",
342
+ onDate: "On date",
343
+ tomorrow: "Tomorrow",
344
+ edit: "Edit",
345
+ duplicate: "Duplicate",
346
+ eventSingular: "event",
347
+ eventPlural: "events",
348
+ allCaughtUp: "All caught up!",
349
+ noEventsScheduled: "No events scheduled",
350
+ noUpcoming: "Your schedule is clear",
351
+ noUpcomingHint: "No upcoming events to show. Create a new event to start planning.",
352
+ newEvent: "New Event",
353
+ saveChanges: "Save Changes",
354
+ close: "Close",
355
+ noTitle: "(No title)",
356
+ openInMaps: "Open in maps",
357
+ notesLabel: "Notes",
358
+ addNotes: "Add description or notes...",
359
+ attachments: "Attachments",
360
+ reminders: "Reminders",
361
+ add: "Add",
362
+ calendar: "Calendar",
363
+ select: "Select",
364
+ addLocation: "Add location",
365
+ guestSingular: "Guest",
366
+ addGuestPlaceholder: "Add a guest email and press Enter",
367
+ invalidEmail: "Enter a valid email address",
368
+ removeGuest: "Remove {email}",
369
+ reminderAtTime: "At time of event",
370
+ reminderMinutes: "{n} minutes before",
371
+ reminderHour: "1 hour before",
372
+ reminderHours: "{n} hours before",
373
+ reminderDay: "1 day before",
374
+ reminderDays: "{n} days before",
375
+ reminderWeek: "1 week before",
376
+ removeReminder: "Remove reminder",
377
+ durationMinutes: "{n} min",
378
+ durationHours: "{n} hr",
379
+ durationHoursMinutes: "{h} hr {m} min",
380
+ moreEvents: "+{n} more",
381
+ idLabel: "ID: {id}",
382
+ previous: "Previous",
383
+ next: "Next",
384
+ previousMonth: "Previous month",
385
+ nextMonth: "Next month",
386
+ toggleSidebar: "Toggle sidebar",
387
+ toggleTheme: "Toggle dark mode",
388
+ switchLanguage: "Switch language",
389
+ eventColor: "Event color"
390
+ };
391
+ var fr = {
392
+ today: "Aujourd'hui",
393
+ month: "Mois",
394
+ week: "Semaine",
395
+ day: "Jour",
396
+ agenda: "Agenda",
397
+ resource: "Ressources",
398
+ createEvent: "Cr\xE9er un \xE9v\xE9nement",
399
+ editEvent: "Modifier l'\xE9v\xE9nement",
400
+ delete: "Supprimer",
401
+ save: "Enregistrer",
402
+ cancel: "Annuler",
403
+ title: "Ajouter un titre",
404
+ start: "D\xE9but",
405
+ end: "Fin",
406
+ allDay: "Toute la journ\xE9e",
407
+ description: "Description",
408
+ repeat: "R\xE9p\xE9ter",
409
+ noRepeat: "Ne se r\xE9p\xE8te pas",
410
+ selectCalendar: "S\xE9lectionner un calendrier",
411
+ selectType: "S\xE9lectionner un type",
412
+ daily: "Quotidien",
413
+ weekly: "Hebdomadaire",
414
+ monthly: "Mensuel",
415
+ yearly: "Annuel",
416
+ search: "Rechercher...",
417
+ allTypes: "Tous les types",
418
+ timezone: "Fuseau horaire",
419
+ localTime: "Heure locale",
420
+ create: "Cr\xE9er",
421
+ calendars: "Calendriers",
422
+ event: "\xC9v\xE9nement",
423
+ task: "T\xE2che",
424
+ appointmentSchedule: "Planning de rendez-vous",
425
+ new: "Nouveau",
426
+ dateAndTime: "Date et heure",
427
+ whosJoining: "Qui participe ?",
428
+ suggestedTimes: "Horaires sugg\xE9r\xE9s",
429
+ viewSuggestions: "Voir les suggestions",
430
+ whereWillItBe: "O\xF9 aura lieu ?",
431
+ location: "Entrer une adresse",
432
+ descriptionAndAttachments: "Description",
433
+ dragAndDrop: "Glisser-d\xE9poser des fichiers ou",
434
+ guests: "Invit\xE9s",
435
+ addGuests: "Ajouter des invit\xE9s",
436
+ addAttachment: "T\xE9l\xE9charger des fichiers",
437
+ moreOptions: "Plus d'options",
438
+ doesNotRepeat: "Ne se r\xE9p\xE8te pas",
439
+ locationHelpText: 'Entrez une adresse physique (ex: "123 Rue Principale") pour g\xE9n\xE9rer la carte.',
440
+ notes: "Notes...",
441
+ guestCount: "invit\xE9",
442
+ guestsCount: "invit\xE9s",
443
+ repeatFor: "R\xE9p\xE9ter pendant",
444
+ times: "fois",
445
+ until: "Jusqu'au",
446
+ endRepeat: "Fin de r\xE9p\xE9tition",
447
+ never: "Jamais",
448
+ afterOccurrences: "Apr\xE8s occurrences",
449
+ onDate: "\xC0 la date",
450
+ tomorrow: "Demain",
451
+ edit: "Modifier",
452
+ duplicate: "Dupliquer",
453
+ eventSingular: "\xE9v\xE9nement",
454
+ eventPlural: "\xE9v\xE9nements",
455
+ allCaughtUp: "Tout est \xE0 jour !",
456
+ noEventsScheduled: "Aucun \xE9v\xE9nement pr\xE9vu",
457
+ noUpcoming: "Votre planning est libre",
458
+ noUpcomingHint: "Aucun \xE9v\xE9nement \xE0 venir. Cr\xE9ez un nouvel \xE9v\xE9nement pour commencer \xE0 planifier.",
459
+ newEvent: "Nouvel \xE9v\xE9nement",
460
+ saveChanges: "Enregistrer",
461
+ close: "Fermer",
462
+ noTitle: "(Sans titre)",
463
+ openInMaps: "Ouvrir dans Maps",
464
+ notesLabel: "Notes",
465
+ addNotes: "Ajouter une description ou des notes\u2026",
466
+ attachments: "Pi\xE8ces jointes",
467
+ reminders: "Rappels",
468
+ add: "Ajouter",
469
+ calendar: "Calendrier",
470
+ select: "Choisir",
471
+ addLocation: "Ajouter un lieu",
472
+ guestSingular: "Invit\xE9",
473
+ addGuestPlaceholder: "Ajoutez l'e-mail d'un invit\xE9 puis Entr\xE9e",
474
+ invalidEmail: "Saisissez une adresse e-mail valide",
475
+ removeGuest: "Retirer {email}",
476
+ reminderAtTime: "Au moment de l'\xE9v\xE9nement",
477
+ reminderMinutes: "{n} minutes avant",
478
+ reminderHour: "1 heure avant",
479
+ reminderHours: "{n} heures avant",
480
+ reminderDay: "1 jour avant",
481
+ reminderDays: "{n} jours avant",
482
+ reminderWeek: "1 semaine avant",
483
+ removeReminder: "Supprimer le rappel",
484
+ durationMinutes: "{n} min",
485
+ durationHours: "{n} h",
486
+ durationHoursMinutes: "{h} h {m} min",
487
+ moreEvents: "+{n} de plus",
488
+ idLabel: "ID : {id}",
489
+ previous: "Pr\xE9c\xE9dent",
490
+ next: "Suivant",
491
+ previousMonth: "Mois pr\xE9c\xE9dent",
492
+ nextMonth: "Mois suivant",
493
+ toggleSidebar: "Afficher ou masquer la barre lat\xE9rale",
494
+ toggleTheme: "Basculer le th\xE8me sombre",
495
+ switchLanguage: "Changer de langue",
496
+ eventColor: "Couleur de l'\xE9v\xE9nement"
497
+ };
498
+
499
+ // src/core/format.ts
500
+ var clockCache = /* @__PURE__ */ new WeakMap();
501
+ function uses24HourClock(locale) {
502
+ if (!locale) return false;
503
+ let is24 = clockCache.get(locale);
504
+ if (is24 === void 0) {
505
+ is24 = !/[ap]\.?m/i.test(format3(new Date(2e3, 0, 1, 13, 0), "p", { locale }));
506
+ clockCache.set(locale, is24);
507
+ }
508
+ return is24;
509
+ }
510
+ var H24 = { hour: "H:mm", time: "H:mm", compact: "H:mm" };
511
+ var H12 = { hour: "h a", time: "h:mm a", compact: "h:mm" };
512
+ function timeFormats(locale) {
513
+ return uses24HourClock(locale) ? H24 : H12;
514
+ }
515
+ var MAX_FORMATTERS = 50;
516
+ var formatters = /* @__PURE__ */ new Map();
517
+ function getDateTimeFormat(locale, timeZone, options) {
518
+ const key = `${locale}|${timeZone ?? ""}|${JSON.stringify(options)}`;
519
+ let formatter = formatters.get(key);
520
+ if (formatter) {
521
+ formatters.delete(key);
522
+ } else {
523
+ formatter = new Intl.DateTimeFormat(locale, { ...options, timeZone });
524
+ if (formatters.size >= MAX_FORMATTERS) {
525
+ formatters.delete(formatters.keys().next().value);
526
+ }
527
+ }
528
+ formatters.set(key, formatter);
529
+ return formatter;
530
+ }
531
+ function timeZoneAbbreviation(date, timeZone) {
532
+ try {
533
+ return getDateTimeFormat("en-US", timeZone, { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "";
534
+ } catch {
535
+ return "";
536
+ }
537
+ }
538
+ function resolveTranslations(language, overrides) {
539
+ const defined = Object.fromEntries(Object.entries(overrides ?? {}).filter(([, value]) => value !== void 0));
540
+ return { ...language === "fr" ? fr : en, ...defined };
541
+ }
542
+ function interpolate(template, values) {
543
+ return template.replace(/\{(\w+)\}/g, (match, key) => key in values ? String(values[key]) : match);
544
+ }
545
+
546
+ // src/core/color.ts
547
+ var SAFE_COLOR = /^[#\w\s%.,/()+-]+$/;
548
+ var FORBIDDEN = /url\(|expression\(/i;
549
+ function safeColor(value, fallback) {
550
+ return value && SAFE_COLOR.test(value.trim()) && !FORBIDDEN.test(value) ? value.trim() : fallback;
551
+ }
552
+
553
+ // src/components/Sidebar.tsx
266
554
  var Sidebar = ({
267
555
  currentDate,
268
556
  onDateChange,
@@ -287,20 +575,7 @@ var Sidebar = ({
287
575
  const timer = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
288
576
  return () => clearInterval(timer);
289
577
  }, []);
290
- const defaultCalendars = [
291
- { id: "1", label: "My Calendar", color: "#3b82f6", active: true },
292
- { id: "2", label: "Birthdays", color: "#10b981", active: true },
293
- { id: "3", label: "Tasks", color: "#6366f1", active: true }
294
- ];
295
- const displayCalendars = calendars || defaultCalendars;
296
- const getAcronym = (tz) => {
297
- if (!tz || !now) return "LOC";
298
- try {
299
- return new Intl.DateTimeFormat("en-US", { timeZone: tz, timeZoneName: "short" }).formatToParts(now).find((part) => part.type === "timeZoneName")?.value || "";
300
- } catch {
301
- return "";
302
- }
303
- };
578
+ const getAcronym = (tz) => !tz || !now ? "LOC" : timeZoneAbbreviation(now, tz);
304
579
  const timezones = [
305
580
  { value: "", label: translations?.localTime || "Local Time", acronym: "LOC" },
306
581
  { value: "UTC", label: "UTC", acronym: "UTC" },
@@ -325,10 +600,7 @@ var Sidebar = ({
325
600
  try {
326
601
  if (!tz.value) {
327
602
  time = format4(now, "HH:mm");
328
- try {
329
- acronym = new Intl.DateTimeFormat("en-US", { timeZoneName: "short" }).formatToParts(now).find((part) => part.type === "timeZoneName")?.value || "LOC";
330
- } catch {
331
- }
603
+ acronym = timeZoneAbbreviation(now) || "LOC";
332
604
  } else {
333
605
  const zDate = toZonedTime(now, tz.value);
334
606
  time = format4(zDate, "HH:mm");
@@ -352,7 +624,16 @@ var Sidebar = ({
352
624
  },
353
625
  /* @__PURE__ */ React4.createElement(Plus, { className: "w-5 h-5" }),
354
626
  /* @__PURE__ */ React4.createElement("span", { className: "text-sm" }, translations?.create || "Create")
355
- )), /* @__PURE__ */ React4.createElement(MiniCalendar, { currentDate, onDateChange, onViewChange, locale }), /* @__PURE__ */ React4.createElement("div", { className: "flex-1 px-4 space-y-5 mt-5" }, /* @__PURE__ */ React4.createElement("div", { className: "bg-muted/20 rounded-2xl p-3 border-[0px] border-border/30" }, /* @__PURE__ */ React4.createElement(
627
+ )), /* @__PURE__ */ React4.createElement(
628
+ MiniCalendar,
629
+ {
630
+ currentDate,
631
+ onDateChange,
632
+ onViewChange,
633
+ locale,
634
+ labels: { previousMonth: translations?.previousMonth, nextMonth: translations?.nextMonth }
635
+ }
636
+ ), /* @__PURE__ */ React4.createElement("div", { className: "flex-1 px-4 space-y-5 mt-5" }, calendars && calendars.length > 0 && /* @__PURE__ */ React4.createElement("div", { className: "bg-muted/20 rounded-2xl p-3 border-[0px] border-border/30" }, /* @__PURE__ */ React4.createElement(
356
637
  "div",
357
638
  {
358
639
  className: "flex items-center justify-between cursor-pointer hover:bg-accent/50 p-2 -m-1 rounded-xl mb-2 transition-all duration-200",
@@ -360,41 +641,40 @@ var Sidebar = ({
360
641
  },
361
642
  /* @__PURE__ */ React4.createElement("span", { className: "text-sm font-semibold text-foreground" }, translations?.calendars || "Calendars"),
362
643
  /* @__PURE__ */ React4.createElement(ChevronDown, { className: cn("w-4 h-4 text-muted-foreground transition-transform duration-200", calendarsOpen && "rotate-180") })
363
- ), calendarsOpen && /* @__PURE__ */ React4.createElement("div", { className: "space-y-1" }, displayCalendars.map((cal) => /* @__PURE__ */ React4.createElement(
644
+ ), calendarsOpen && /* @__PURE__ */ React4.createElement("div", { className: "space-y-1" }, calendars.map((cal) => /* @__PURE__ */ React4.createElement(
364
645
  "div",
365
646
  {
366
647
  key: cal.id,
367
648
  className: "flex items-center gap-3 py-2 px-2 hover:bg-accent/60 rounded-xl cursor-pointer group transition-all duration-200",
368
649
  onClick: () => onCalendarToggle?.(cal.id, !(cal.active ?? true))
369
650
  },
370
- /* @__PURE__ */ React4.createElement("div", { className: "relative flex items-center justify-center" }, /* @__PURE__ */ React4.createElement(
371
- "input",
651
+ /* @__PURE__ */ React4.createElement(
652
+ "div",
372
653
  {
373
- type: "checkbox",
374
- checked: cal.active ?? true,
375
- onChange: (e) => {
376
- e.stopPropagation();
377
- onCalendarToggle?.(cal.id, e.target.checked);
378
- },
379
- className: "peer h-5 w-5 rounded-md border-2 border-border/60 cursor-pointer appearance-none checked:border-transparent transition-all duration-200",
380
- style: { "--primary-color": cal.color },
381
- "data-cal-id": cal.id
382
- }
383
- ), /* @__PURE__ */ React4.createElement("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none opacity-0 peer-checked:opacity-100 transition-opacity" }, /* @__PURE__ */ React4.createElement("svg", { className: "w-3.5 h-3.5 text-white", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3 }, /* @__PURE__ */ React4.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }))), /* @__PURE__ */ React4.createElement("style", null, `
384
- input[type="checkbox"][data-cal-id="${cal.id}"]:checked {
385
- background-color: ${cal.color} !important;
386
- border-color: ${cal.color} !important;
387
- }
388
- input[type="checkbox"][data-cal-id="${cal.id}"]:focus {
389
- --tw-ring-color: ${cal.color}40 !important;
390
- }
391
- `)),
654
+ className: "relative flex items-center justify-center",
655
+ style: { "--cal-color": safeColor(cal.color, "hsl(var(--primary))") }
656
+ },
657
+ /* @__PURE__ */ React4.createElement(
658
+ "input",
659
+ {
660
+ type: "checkbox",
661
+ checked: cal.active ?? true,
662
+ onChange: (e) => {
663
+ e.stopPropagation();
664
+ onCalendarToggle?.(cal.id, e.target.checked);
665
+ },
666
+ "aria-label": cal.label,
667
+ className: "peer h-5 w-5 rounded-md border-2 border-border/60 cursor-pointer appearance-none checked:border-transparent checked:bg-[var(--cal-color)] focus:ring-2 focus:ring-[var(--cal-color)] transition-all duration-200"
668
+ }
669
+ ),
670
+ /* @__PURE__ */ React4.createElement("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none opacity-0 peer-checked:opacity-100 transition-opacity" }, /* @__PURE__ */ React4.createElement("svg", { className: "w-3.5 h-3.5 text-white", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3 }, /* @__PURE__ */ React4.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" })))
671
+ ),
392
672
  /* @__PURE__ */ React4.createElement("div", { className: "flex items-center gap-2 flex-1 min-w-0" }, /* @__PURE__ */ React4.createElement("span", { className: "text-sm text-foreground/90 truncate font-medium" }, cal.label)),
393
673
  /* @__PURE__ */ React4.createElement(
394
674
  "div",
395
675
  {
396
676
  className: "w-2 h-2 rounded-full opacity-60 group-hover:opacity-100 transition-opacity",
397
- style: { backgroundColor: cal.color }
677
+ style: { backgroundColor: safeColor(cal.color, "hsl(var(--primary))") }
398
678
  }
399
679
  )
400
680
  ))))), /* @__PURE__ */ React4.createElement("div", { className: "mt-auto px-4 pt-5" }, /* @__PURE__ */ React4.createElement("div", { className: "bg-muted/20 rounded-2xl p-3" }, /* @__PURE__ */ React4.createElement("div", { className: "flex items-center gap-2 mb-3" }, /* @__PURE__ */ React4.createElement("div", { className: "p-1.5 bg-primary/10 rounded-lg" }, /* @__PURE__ */ React4.createElement(Globe, { className: "w-4 h-4 text-primary" })), /* @__PURE__ */ React4.createElement("span", { className: "text-sm font-semibold text-foreground" }, translations?.timezone || "Timezone")), /* @__PURE__ */ React4.createElement("div", { className: "relative" }, /* @__PURE__ */ React4.createElement(
@@ -423,32 +703,198 @@ var Sidebar = ({
423
703
  };
424
704
 
425
705
  // src/views/MonthView.tsx
426
- import React7, { useMemo, useCallback } from "react";
427
- import { format as format5, isSameMonth as isSameMonth2, isToday as isToday2, startOfWeek as startOfWeek2, endOfWeek as endOfWeek2, eachDayOfInterval as eachDayOfInterval2 } from "date-fns";
428
- import { toZonedTime as toZonedTime2 } from "date-fns-tz";
706
+ import React7, { useMemo } from "react";
707
+ import { format as format7, isSameDay as isSameDay3, isSameMonth as isSameMonth2 } from "date-fns";
708
+
709
+ // src/core/events.ts
710
+ import { addDays, format as format6, startOfDay as startOfDay2 } from "date-fns";
711
+
712
+ // src/core/timezone.ts
713
+ import { format as format5, isSameDay as isSameDay2, startOfDay } from "date-fns";
714
+ import { fromZonedTime, toZonedTime as toZonedTime2 } from "date-fns-tz";
715
+
716
+ // src/core/warn.ts
717
+ var warned = /* @__PURE__ */ new Set();
718
+ function isProductionBuild() {
719
+ try {
720
+ return process.env.NODE_ENV === "production";
721
+ } catch {
722
+ return false;
723
+ }
724
+ }
725
+ function warnOnce(key, message) {
726
+ if (isProductionBuild() || warned.has(key)) return;
727
+ warned.add(key);
728
+ console.warn(`[calendarkit-pro] ${message}`);
729
+ }
730
+
731
+ // src/core/timezone.ts
732
+ var validity = /* @__PURE__ */ new Map();
733
+ function resolveTimeZone(tz) {
734
+ if (!tz) return void 0;
735
+ let valid = validity.get(tz);
736
+ if (valid === void 0) {
737
+ try {
738
+ new Intl.DateTimeFormat("en-US", { timeZone: tz });
739
+ valid = true;
740
+ } catch {
741
+ valid = false;
742
+ }
743
+ validity.set(tz, valid);
744
+ }
745
+ if (!valid) {
746
+ warnOnce(`invalid-timezone:${tz}`, `Unknown timezone "${tz}", falling back to local time.`);
747
+ return void 0;
748
+ }
749
+ return tz;
750
+ }
751
+ function toDisplay(date, tz) {
752
+ const zone = resolveTimeZone(tz);
753
+ return zone ? toZonedTime2(date, zone) : date;
754
+ }
755
+ function fromDisplay(wall, tz) {
756
+ const zone = resolveTimeZone(tz);
757
+ return zone ? fromZonedTime(wall, zone) : new Date(wall);
758
+ }
759
+ function minutesSinceDisplayMidnight(date, tz) {
760
+ const wall = toDisplay(date, tz);
761
+ return wall.getHours() * 60 + wall.getMinutes();
762
+ }
763
+ function isSameDisplayDay(date, day, tz) {
764
+ return isSameDay2(toDisplay(date, tz), day);
765
+ }
766
+ function displayToday(tz, now = /* @__PURE__ */ new Date()) {
767
+ return startOfDay(toDisplay(now, tz));
768
+ }
769
+
770
+ // src/core/events.ts
771
+ var toDate = (value) => value === void 0 || value === null ? void 0 : value instanceof Date ? value : new Date(value);
772
+ var isValidDate = (date) => !!date && !Number.isNaN(date.getTime());
773
+ var normalizedEvent = /* @__PURE__ */ new WeakMap();
774
+ var normalizedList = /* @__PURE__ */ new WeakMap();
775
+ function normalizeEvent(event) {
776
+ const cached = normalizedEvent.get(event);
777
+ if (cached !== void 0) return cached;
778
+ const start = toDate(event.start);
779
+ const end = toDate(event.end);
780
+ let result = null;
781
+ if (!isValidDate(start) || !isValidDate(end)) {
782
+ warnOnce(`invalid-date:${event.id}`, `Event "${event.id}" has an invalid start or end date and was skipped.`);
783
+ } else if (end.getTime() < start.getTime()) {
784
+ warnOnce(`end-before-start:${event.id}`, `Event "${event.id}" ends before it starts and was skipped.`);
785
+ } else {
786
+ const needsCopy = start !== event.start || end !== event.end || needsRecurrenceCopy(event);
787
+ result = needsCopy ? {
788
+ ...event,
789
+ start,
790
+ end,
791
+ recurrence: event.recurrence && {
792
+ ...event.recurrence,
793
+ until: toDate(event.recurrence.until),
794
+ exdates: event.recurrence.exdates?.map((d) => toDate(d))
795
+ }
796
+ } : event;
797
+ }
798
+ normalizedEvent.set(event, result);
799
+ return result;
800
+ }
801
+ function needsRecurrenceCopy(event) {
802
+ const r = event.recurrence;
803
+ if (!r) return false;
804
+ return r.until !== void 0 && !(r.until instanceof Date) || !!r.exdates?.some((d) => !(d instanceof Date));
805
+ }
806
+ function normalizeEvents(events) {
807
+ const cached = normalizedList.get(events);
808
+ if (cached) return cached;
809
+ const seen = /* @__PURE__ */ new Set();
810
+ const result = [];
811
+ for (const event of events) {
812
+ const normalized = normalizeEvent(event);
813
+ if (!normalized) continue;
814
+ if (seen.has(normalized.id)) {
815
+ warnOnce(`duplicate-id:${normalized.id}`, `Duplicate event id "${normalized.id}": ids must be unique.`);
816
+ }
817
+ seen.add(normalized.id);
818
+ result.push(normalized);
819
+ }
820
+ normalizedList.set(events, result);
821
+ return result;
822
+ }
823
+ var dayKey = (wall) => format6(wall, "yyyy-MM-dd");
824
+ function memoByZone(cache, events, tz, compute) {
825
+ let byZone = cache.get(events);
826
+ if (!byZone) {
827
+ byZone = /* @__PURE__ */ new Map();
828
+ cache.set(events, byZone);
829
+ }
830
+ const key = tz ?? "";
831
+ let value = byZone.get(key);
832
+ if (value === void 0) {
833
+ value = compute();
834
+ byZone.set(key, value);
835
+ }
836
+ return value;
837
+ }
838
+ var startDayCache = /* @__PURE__ */ new WeakMap();
839
+ function eventsByStartDay(events, tz) {
840
+ return memoByZone(startDayCache, events, tz, () => {
841
+ const map = /* @__PURE__ */ new Map();
842
+ for (const event of events) {
843
+ const key = dayKey(toDisplay(event.start, tz));
844
+ const bucket = map.get(key);
845
+ if (bucket) bucket.push(event);
846
+ else map.set(key, [event]);
847
+ }
848
+ return map;
849
+ });
850
+ }
851
+ var segmentCache = /* @__PURE__ */ new WeakMap();
852
+ function segmentsByDisplayDay(events, tz, range) {
853
+ const cacheKey = range ? `${tz ?? ""}|${range.start.getTime()}|${range.end.getTime()}` : tz;
854
+ return memoByZone(segmentCache, events, cacheKey, () => {
855
+ const map = /* @__PURE__ */ new Map();
856
+ for (const event of events) {
857
+ const wallStart = toDisplay(event.start, tz);
858
+ const rawEnd = toDisplay(event.end, tz);
859
+ const wallEnd = rawEnd < wallStart ? wallStart : rawEnd;
860
+ if (range && (wallEnd < range.start || wallStart >= range.end)) continue;
861
+ let day = startOfDay2(range && wallStart < range.start ? range.start : wallStart);
862
+ const stop = range && wallEnd > range.end ? range.end : wallEnd;
863
+ do {
864
+ const next = addDays(day, 1);
865
+ const start = wallStart > day ? wallStart : day;
866
+ const end = wallEnd < next ? wallEnd : next;
867
+ if (end > start || wallStart.getTime() === wallEnd.getTime()) {
868
+ const key = dayKey(day);
869
+ const bucket = map.get(key);
870
+ const segment = { event, start, end };
871
+ if (bucket) bucket.push(segment);
872
+ else map.set(key, [segment]);
873
+ }
874
+ day = next;
875
+ } while (day < stop);
876
+ }
877
+ return map;
878
+ });
879
+ }
429
880
 
430
881
  // src/components/dnd/DraggableEvent.tsx
431
882
  import React5 from "react";
432
883
  import { useDraggable } from "@dnd-kit/core";
433
884
  import { CSS } from "@dnd-kit/utilities";
434
- var DraggableEvent = ({ event, children, className, style: propStyle, ...props }) => {
885
+ var DraggableEventImpl = ({ event, dragId, grabOffsetMs, children, className, style: propStyle, ...props }) => {
435
886
  const dragDisabled = isEventDragDisabled(event);
436
887
  const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
437
- id: event.id,
438
- data: { event },
888
+ id: dragId ?? event.id,
889
+ data: { event, grabOffsetMs },
439
890
  disabled: dragDisabled
440
891
  });
441
892
  const style = {
442
893
  ...propStyle,
443
894
  transform: CSS.Translate.toString(transform),
444
895
  zIndex: isDragging ? 100 : propStyle?.zIndex,
445
- // When dragging, we want the ORIGINAL element to be almost invisible (or fully invisible)
446
- // so that the DragOverlay (the "ghost") is the only thing the user focuses on.
447
- // If we set opacity: 0, it disappears. If 0.1, it's a faint placeholder.
448
- // The user asked for "Make the entire dragged item transparent".
449
- // Usually this means the original item should be faded out while the drag overlay is moving.
896
+ // Hide the original while dragging: the DragOverlay ghost is what the user follows.
450
897
  opacity: isDragging ? 0 : propStyle?.opacity
451
- // Hiding original element completely
452
898
  };
453
899
  return /* @__PURE__ */ React5.createElement(
454
900
  "div",
@@ -463,36 +909,59 @@ var DraggableEvent = ({ event, children, className, style: propStyle, ...props }
463
909
  children
464
910
  );
465
911
  };
912
+ var DraggableEvent = React5.memo(DraggableEventImpl);
466
913
 
467
914
  // src/components/dnd/DroppableCell.tsx
468
915
  import React6 from "react";
469
916
  import { useDroppable } from "@dnd-kit/core";
470
- var DroppableCell = ({ id, date, resourceId, children, className, style, onClick }) => {
917
+ var QUARTER_HOVER = [
918
+ "hover:bg-blue-50/50 dark:hover:bg-blue-900/10",
919
+ "hover:bg-blue-50/80 dark:hover:bg-blue-900/20",
920
+ "hover:bg-blue-100/50 dark:hover:bg-blue-900/30",
921
+ "hover:bg-blue-100/80 dark:hover:bg-blue-900/40"
922
+ ];
923
+ var QUARTER_ACTIVE = [
924
+ "bg-blue-50/50 dark:bg-blue-900/10 ring-2 ring-primary ring-inset",
925
+ "bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-primary ring-inset",
926
+ "bg-blue-100/50 dark:bg-blue-900/30 ring-2 ring-primary ring-inset",
927
+ "bg-blue-100/80 dark:bg-blue-900/40 ring-2 ring-primary ring-inset"
928
+ ];
929
+ var quarterOf = (date) => Math.min(3, Math.floor(date.getMinutes() / 15));
930
+ var DroppableCellImpl = ({ id, date, resourceId, children, className, style, onClick, onSlotClick }) => {
471
931
  const { isOver, setNodeRef } = useDroppable({
472
932
  id,
473
933
  data: { date, resourceId }
474
934
  });
475
- const minutes = date.getMinutes();
476
- const quarterClass = minutes === 0 ? "hover:bg-blue-50/50 dark:hover:bg-blue-900/10" : minutes === 15 ? "hover:bg-blue-50/80 dark:hover:bg-blue-900/20" : minutes === 30 ? "hover:bg-blue-100/50 dark:hover:bg-blue-900/30" : "hover:bg-blue-100/80 dark:hover:bg-blue-900/40";
477
- const activeQuarterClass = minutes === 0 ? "bg-blue-50/50 dark:bg-blue-900/10 ring-2 ring-primary ring-inset" : minutes === 15 ? "bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-primary ring-inset" : minutes === 30 ? "bg-blue-100/50 dark:bg-blue-900/30 ring-2 ring-primary ring-inset" : "bg-blue-100/80 dark:bg-blue-900/40 ring-2 ring-primary ring-inset";
935
+ const quarter = quarterOf(date);
478
936
  return /* @__PURE__ */ React6.createElement(
479
937
  "div",
480
938
  {
481
939
  ref: setNodeRef,
482
940
  className: cn(
483
941
  className,
484
- quarterClass,
485
- isOver && activeQuarterClass
942
+ QUARTER_HOVER[quarter],
943
+ isOver && QUARTER_ACTIVE[quarter],
944
+ onSlotClick && "cursor-pointer"
486
945
  ),
487
946
  style,
488
- onClick
947
+ onClick: onSlotClick ? () => onSlotClick(date, resourceId) : onClick
489
948
  },
490
949
  children
491
950
  );
492
951
  };
952
+ var shallowEqualStyle = (a, b) => {
953
+ if (a === b) return true;
954
+ if (!a || !b) return false;
955
+ const keys = Object.keys(a);
956
+ return keys.length === Object.keys(b).length && keys.every((key) => a[key] === b[key]);
957
+ };
958
+ var DroppableCell = React6.memo(
959
+ DroppableCellImpl,
960
+ (prev, next) => prev.id === next.id && prev.date.getTime() === next.date.getTime() && prev.resourceId === next.resourceId && prev.children === next.children && prev.className === next.className && prev.onClick === next.onClick && prev.onSlotClick === next.onSlotClick && shallowEqualStyle(prev.style, next.style)
961
+ );
493
962
 
494
963
  // src/views/MonthView.tsx
495
- var EventItem = React7.memo(({ event, onEventClick }) => /* @__PURE__ */ React7.createElement(DraggableEvent, { event }, /* @__PURE__ */ React7.createElement(
964
+ var EventItem = React7.memo(({ event, onEventClick, onEventContextMenu }) => /* @__PURE__ */ React7.createElement(DraggableEvent, { event }, /* @__PURE__ */ React7.createElement(
496
965
  "div",
497
966
  {
498
967
  className: cn(
@@ -508,7 +977,8 @@ var EventItem = React7.memo(({ event, onEventClick }) => /* @__PURE__ */ React7.
508
977
  onClick: (e) => {
509
978
  e.stopPropagation();
510
979
  onEventClick?.(event);
511
- }
980
+ },
981
+ onContextMenu: onEventContextMenu ? (e) => onEventContextMenu(event, e) : void 0
512
982
  },
513
983
  /* @__PURE__ */ React7.createElement("span", { className: "font-medium" }, event.title)
514
984
  )));
@@ -519,43 +989,25 @@ var MonthView = ({
519
989
  onEventClick,
520
990
  onDateClick,
521
991
  timezone,
522
- locale
992
+ locale,
993
+ translations,
994
+ onEventContextMenu
523
995
  }) => {
524
- const weekStartsOn = locale?.options?.weekStartsOn ?? 1;
525
- const days = useMemo(
526
- () => getMonthGrid(currentDate, weekStartsOn),
527
- [currentDate, weekStartsOn]
528
- );
529
- const weekDays = useMemo(() => {
530
- const start = startOfWeek2(currentDate, { weekStartsOn });
531
- const end = endOfWeek2(currentDate, { weekStartsOn });
532
- return eachDayOfInterval2({ start, end });
533
- }, [currentDate, weekStartsOn]);
534
- const getZonedDate = useCallback((date) => {
535
- return timezone ? toZonedTime2(date, timezone) : date;
536
- }, [timezone]);
537
- const eventsByDay = useMemo(() => {
538
- const map = /* @__PURE__ */ new Map();
539
- events.forEach((event) => {
540
- const zonedStart = getZonedDate(event.start);
541
- const key = format5(zonedStart, "yyyy-MM-dd");
542
- if (!map.has(key)) {
543
- map.set(key, []);
544
- }
545
- map.get(key).push(event);
546
- });
547
- return map;
548
- }, [events, getZonedDate]);
996
+ const weekStartsOn = resolveWeekStartsOn(locale, 1);
997
+ const days = useMemo(() => getMonthGrid(currentDate, weekStartsOn), [currentDate, weekStartsOn]);
998
+ const weekDays = useMemo(() => getWeekDays(currentDate, weekStartsOn), [currentDate, weekStartsOn]);
999
+ const eventsByDay = eventsByStartDay(events, timezone);
1000
+ const today = displayToday(timezone);
549
1001
  return /* @__PURE__ */ React7.createElement("div", { className: "flex flex-col h-full bg-background border-[0.5px] scrollbar-hide border-border/50 rounded-2xl overflow-hidden min-w-[800px] md:min-w-0 shadow-sm" }, /* @__PURE__ */ React7.createElement("div", { className: "overflow-y-auto flex-1 relative" }, /* @__PURE__ */ React7.createElement("div", { className: "grid grid-cols-7 border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/30 via-muted/40 to-muted/30 sticky top-0 z-20 backdrop-blur-sm" }, weekDays.map((day) => /* @__PURE__ */ React7.createElement(
550
1002
  "div",
551
1003
  {
552
1004
  key: day.toISOString(),
553
1005
  className: "py-3 text-center text-xs font-semibold text-muted-foreground border-r-[0.5px] border-border/30 last:border-r-0 uppercase tracking-wider"
554
1006
  },
555
- format5(day, "EEE", { locale })
1007
+ format7(day, "EEE", { locale })
556
1008
  ))), /* @__PURE__ */ React7.createElement("div", { className: "grid grid-cols-7", style: { gridAutoRows: "130px" } }, days.map((day) => {
557
- const dayKey = format5(day, "yyyy-MM-dd");
558
- const dayEvents = eventsByDay.get(dayKey) || [];
1009
+ const dayKey2 = format7(day, "yyyy-MM-dd");
1010
+ const dayEvents = eventsByDay.get(dayKey2) || [];
559
1011
  const isCurrentMonth = isSameMonth2(day, currentDate);
560
1012
  const cellId = day.toISOString();
561
1013
  return /* @__PURE__ */ React7.createElement(
@@ -567,26 +1019,99 @@ var MonthView = ({
567
1019
  className: cn(
568
1020
  "h-[130px] p-2 border-b-[0.5px] border-r-[0.5px] border-border/30 last:border-r-0 relative transition-all duration-200 hover:bg-accent/5 flex flex-col gap-1.5 overflow-hidden group",
569
1021
  !isCurrentMonth && "bg-muted/5 text-muted-foreground/60",
570
- isToday2(day) && "bg-primary/5 ring-1 ring-inset ring-primary/20"
1022
+ isSameDay3(day, today) && "bg-primary/5 ring-1 ring-inset ring-primary/20"
571
1023
  ),
572
1024
  onClick: () => onDateClick?.(day)
573
1025
  },
574
1026
  /* @__PURE__ */ React7.createElement("div", { className: "flex justify-between items-start" }, /* @__PURE__ */ React7.createElement("div", { className: cn(
575
1027
  "text-sm font-semibold w-7 h-7 flex items-center justify-center rounded-full transition-all duration-200",
576
- isToday2(day) ? "bg-primary text-primary-foreground shadow-md shadow-primary/30" : "group-hover:bg-accent"
577
- ) }, format5(day, "d", { locale })), dayEvents.length > 0 && /* @__PURE__ */ React7.createElement("div", { className: "text-[10px] font-medium text-muted-foreground/60 bg-muted/50 px-1.5 py-0.5 rounded-full" }, dayEvents.length)),
578
- /* @__PURE__ */ React7.createElement("div", { className: "flex-1 flex flex-col gap-1 scrollbar-hide overflow-y-auto overflow-x-hidden" }, dayEvents.slice(0, 4).map((event) => /* @__PURE__ */ React7.createElement(EventItem, { key: `${event.id}-${dayKey}`, event, onEventClick })), dayEvents.length > 4 && /* @__PURE__ */ React7.createElement("div", { className: "text-[10px] text-primary font-semibold text-center py-1 px-2 rounded-md bg-primary/5 hover:bg-primary/10 cursor-pointer transition-colors" }, "+", dayEvents.length - 4, " more"))
1028
+ isSameDay3(day, today) ? "bg-primary text-primary-foreground shadow-md shadow-primary/30" : "group-hover:bg-accent"
1029
+ ) }, format7(day, "d", { locale })), dayEvents.length > 0 && /* @__PURE__ */ React7.createElement("div", { className: "text-[10px] font-medium text-muted-foreground/60 bg-muted/50 px-1.5 py-0.5 rounded-full" }, dayEvents.length)),
1030
+ /* @__PURE__ */ React7.createElement("div", { className: "flex-1 flex flex-col gap-1 scrollbar-hide overflow-y-auto overflow-x-hidden" }, dayEvents.slice(0, 4).map((event) => /* @__PURE__ */ React7.createElement(EventItem, { key: `${event.id}-${dayKey2}`, event, onEventClick, onEventContextMenu })), dayEvents.length > 4 && // U7: "+N more" opens the day instead of doing nothing.
1031
+ /* @__PURE__ */ React7.createElement(
1032
+ "button",
1033
+ {
1034
+ type: "button",
1035
+ className: "text-[10px] text-primary font-semibold text-center py-1 px-2 rounded-md bg-primary/5 hover:bg-primary/10 cursor-pointer transition-colors",
1036
+ onClick: (e) => {
1037
+ e.stopPropagation();
1038
+ onDateClick?.(day);
1039
+ }
1040
+ },
1041
+ interpolate(translations?.moreEvents || "+{n} more", { n: dayEvents.length - 4 })
1042
+ ))
579
1043
  );
580
1044
  }))));
581
1045
  };
582
1046
 
583
1047
  // src/views/WeekView.tsx
584
1048
  import React9 from "react";
585
- import { format as format6, isSameDay as isSameDay2, differenceInMinutes, isToday as isToday3, startOfWeek as startOfWeek3, endOfWeek as endOfWeek3, eachDayOfInterval as eachDayOfInterval3 } from "date-fns";
586
- import { toZonedTime as toZonedTime3 } from "date-fns-tz";
1049
+ import { addDays as addDays2, format as format8, differenceInMinutes, isSameDay as isSameDay4 } from "date-fns";
1050
+
1051
+ // src/core/layout.ts
1052
+ var layoutCache = /* @__PURE__ */ new WeakMap();
1053
+ function layoutDayEvents(segments) {
1054
+ const cached = layoutCache.get(segments);
1055
+ if (cached) return cached;
1056
+ const sorted = [...segments].sort(
1057
+ (a, b) => a.start.getTime() - b.start.getTime() || b.end.getTime() - a.end.getTime() || (a.event.id < b.event.id ? -1 : a.event.id > b.event.id ? 1 : 0)
1058
+ );
1059
+ const result = [];
1060
+ let cluster = [];
1061
+ let columnEnds = [];
1062
+ let clusterEnd = -Infinity;
1063
+ const closeCluster = () => {
1064
+ for (const placed of cluster) placed.columns = columnEnds.length;
1065
+ cluster = [];
1066
+ columnEnds = [];
1067
+ };
1068
+ for (const segment of sorted) {
1069
+ const start = segment.start.getTime();
1070
+ const end = Math.max(segment.end.getTime(), start + 1);
1071
+ if (start >= clusterEnd) closeCluster();
1072
+ let column = columnEnds.findIndex((columnEnd) => columnEnd <= start);
1073
+ if (column === -1) {
1074
+ column = columnEnds.length;
1075
+ columnEnds.push(end);
1076
+ } else {
1077
+ columnEnds[column] = end;
1078
+ }
1079
+ const placed = { ...segment, column, columns: 1 };
1080
+ cluster.push(placed);
1081
+ result.push(placed);
1082
+ clusterEnd = Math.max(clusterEnd, end);
1083
+ }
1084
+ closeCluster();
1085
+ layoutCache.set(segments, result);
1086
+ return result;
1087
+ }
1088
+
1089
+ // src/components/dnd/ResizableEvent.tsx
1090
+ import React8, { useState as useState2, useRef, useCallback } from "react";
1091
+
1092
+ // src/core/dnd.ts
1093
+ function computeDrop({ event, overDate, overResourceId, grabOffsetMs = 0, view, tz }) {
1094
+ let wallStart;
1095
+ if (view === "month") {
1096
+ const original = toDisplay(event.start, tz);
1097
+ wallStart = new Date(overDate);
1098
+ wallStart.setHours(original.getHours(), original.getMinutes(), original.getSeconds(), original.getMilliseconds());
1099
+ } else {
1100
+ wallStart = new Date(overDate.getTime() - grabOffsetMs);
1101
+ wallStart.setSeconds(0, 0);
1102
+ }
1103
+ const start = fromDisplay(wallStart, tz);
1104
+ const end = new Date(start.getTime() + (event.end.getTime() - event.start.getTime()));
1105
+ const resourceId = overResourceId ?? event.resourceId;
1106
+ if (start.getTime() === event.start.getTime() && resourceId === event.resourceId) return null;
1107
+ return { start, end, resourceId };
1108
+ }
1109
+ function computeResizeEnd(event, deltaMinutes, minDuration = 15) {
1110
+ const minEnd = event.start.getTime() + minDuration * 6e4;
1111
+ return new Date(Math.max(event.end.getTime() + deltaMinutes * 6e4, minEnd));
1112
+ }
587
1113
 
588
1114
  // src/components/dnd/ResizableEvent.tsx
589
- import React8, { useState as useState2, useRef, useCallback as useCallback2 } from "react";
590
1115
  var ResizableEvent = ({
591
1116
  event,
592
1117
  hourHeight,
@@ -604,7 +1129,7 @@ var ResizableEvent = ({
604
1129
  const startYRef = useRef(0);
605
1130
  const startHeightRef = useRef(0);
606
1131
  const resizeHeightRef = useRef(null);
607
- const handleResizeStart = useCallback2((e) => {
1132
+ const handleResizeStart = useCallback((e) => {
608
1133
  e.preventDefault();
609
1134
  e.stopPropagation();
610
1135
  const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
@@ -628,14 +1153,8 @@ var ResizableEvent = ({
628
1153
  setIsResizing(false);
629
1154
  const finalHeight = resizeHeightRef.current;
630
1155
  if (finalHeight !== null && containerRef.current && onResize) {
631
- const originalHeight = startHeightRef.current;
632
- const heightDiff = finalHeight - originalHeight;
633
- const minutesDiff = heightDiff / hourHeight * 60;
634
- const newEnd = new Date(event.end);
635
- newEnd.setMinutes(newEnd.getMinutes() + minutesDiff);
636
- if (newEnd > event.start) {
637
- onResize(event, newEnd);
638
- }
1156
+ const minutesDiff = (finalHeight - startHeightRef.current) / hourHeight * 60;
1157
+ onResize(event, computeResizeEnd(event, minutesDiff, minDuration));
639
1158
  }
640
1159
  resizeHeightRef.current = null;
641
1160
  setResizeHeight(null);
@@ -649,7 +1168,7 @@ var ResizableEvent = ({
649
1168
  document.addEventListener("touchmove", handleMove, { passive: false });
650
1169
  document.addEventListener("touchend", handleEnd);
651
1170
  }, [event, hourHeight, minDuration, onResize]);
652
- const handlePointerDown = useCallback2((e) => {
1171
+ const handlePointerDown = useCallback((e) => {
653
1172
  e.stopPropagation();
654
1173
  }, []);
655
1174
  return /* @__PURE__ */ React8.createElement(
@@ -686,7 +1205,16 @@ var ResizableEvent = ({
686
1205
  };
687
1206
 
688
1207
  // src/views/WeekView.tsx
689
- var WeekView = ({
1208
+ var HOUR_HEIGHT = 60;
1209
+ var HOURS = Array.from({ length: 24 }, (_, i) => i);
1210
+ var QUARTERS = [0, 15, 30, 45];
1211
+ var NO_SEGMENTS = [];
1212
+ var slotDate = (day, hour, minute) => {
1213
+ const date = new Date(day);
1214
+ date.setHours(hour, minute, 0, 0);
1215
+ return date;
1216
+ };
1217
+ var WeekViewImpl = ({
690
1218
  currentDate,
691
1219
  events,
692
1220
  onEventClick,
@@ -694,16 +1222,19 @@ var WeekView = ({
694
1222
  onEventResize,
695
1223
  timezone,
696
1224
  locale,
697
- readonly
1225
+ readonly,
1226
+ onEventContextMenu
698
1227
  }) => {
699
- const weekStartsOn = locale?.options?.weekStartsOn ?? 1;
700
- const start = startOfWeek3(currentDate, { weekStartsOn });
701
- const end = endOfWeek3(currentDate, { weekStartsOn });
702
- const weekDays = eachDayOfInterval3({ start, end });
703
- const hours = Array.from({ length: 24 }, (_, i) => i);
704
- const hourHeight = 60;
1228
+ const weekDays = React9.useMemo(
1229
+ () => getWeekDays(currentDate, resolveWeekStartsOn(locale, 1)),
1230
+ [currentDate, locale]
1231
+ );
1232
+ const visibleRange = React9.useMemo(
1233
+ () => ({ start: weekDays[0], end: addDays2(weekDays[weekDays.length - 1], 1) }),
1234
+ [weekDays]
1235
+ );
705
1236
  const scrollContainerRef = React9.useRef(null);
706
- const [now, setNow] = React9.useState(/* @__PURE__ */ new Date());
1237
+ const [now, setNow] = React9.useState(() => /* @__PURE__ */ new Date());
707
1238
  React9.useEffect(() => {
708
1239
  const interval = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
709
1240
  return () => clearInterval(interval);
@@ -711,132 +1242,93 @@ var WeekView = ({
711
1242
  React9.useEffect(() => {
712
1243
  if (scrollContainerRef.current) {
713
1244
  const offsetHour = 4;
714
- scrollContainerRef.current.scrollTop = ((/* @__PURE__ */ new Date()).getHours() - offsetHour) * hourHeight;
715
- }
716
- }, []);
717
- const getZonedDate = (date) => {
718
- return timezone ? toZonedTime3(date, timezone) : date;
719
- };
720
- const zonedNow = getZonedDate(/* @__PURE__ */ new Date());
721
- const getTimezoneDisplay = (tz) => {
722
- const date = now;
723
- let displayTime = "";
724
- let acronym = "";
725
- if (!tz) {
726
- displayTime = format6(date, "HH:mm");
727
- try {
728
- acronym = new Intl.DateTimeFormat("en-US", { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value || "";
729
- } catch {
730
- acronym = "LOC";
731
- }
732
- } else {
733
- try {
734
- const zDate = toZonedTime3(date, tz);
735
- displayTime = format6(zDate, "HH:mm");
736
- acronym = new Intl.DateTimeFormat("en-US", { timeZone: tz, timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value || "";
737
- } catch {
738
- displayTime = format6(date, "HH:mm");
739
- acronym = tz;
740
- }
1245
+ scrollContainerRef.current.scrollTop = (toDisplay(/* @__PURE__ */ new Date(), timezone).getHours() - offsetHour) * HOUR_HEIGHT;
741
1246
  }
742
- return /* @__PURE__ */ React9.createElement("div", { className: "flex flex-col items-center justify-center leading-tight" }, /* @__PURE__ */ React9.createElement("span", null, displayTime), /* @__PURE__ */ React9.createElement("span", { className: "text-[10px] opacity-75" }, "(", acronym, ")"));
743
- };
744
- const is24h = uses24HourClock(locale);
745
- const timeFormat = is24h ? "H:mm" : "h a";
746
- const eventTimeFormat = is24h ? "H:mm" : "h:mm a";
747
- const nowFormat = is24h ? "H:mm" : "h:mm";
1247
+ }, [timezone]);
1248
+ const segmentsByDay = segmentsByDisplayDay(events, timezone, visibleRange);
1249
+ const today = displayToday(timezone, now);
1250
+ const zonedNow = toDisplay(now, timezone);
1251
+ const nowTop = minutesSinceDisplayMidnight(now, timezone) / 60 * HOUR_HEIGHT;
1252
+ const showsToday = weekDays.some((day) => isSameDay4(day, today));
1253
+ const zone = resolveTimeZone(timezone);
1254
+ const zoneLabel = timeZoneAbbreviation(now, zone) || zone || "LOC";
1255
+ const formats = timeFormats(locale);
748
1256
  return /* @__PURE__ */ React9.createElement("div", { className: "flex flex-col h-full bg-background border-[0.5px] border-border/50 rounded-2xl overflow-hidden min-w-[800px] md:min-w-0 shadow-sm" }, /* @__PURE__ */ React9.createElement(
749
1257
  "div",
750
1258
  {
751
1259
  ref: scrollContainerRef,
752
1260
  className: "flex-1 overflow-y-auto scrollbar-hide relative bg-background scroll-smooth"
753
1261
  },
754
- /* @__PURE__ */ React9.createElement("div", { className: "flex border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/20 via-background to-muted/20 z-20 sticky top-0 backdrop-blur-sm" }, /* @__PURE__ */ React9.createElement("div", { className: "flex-none p-3 text-center text-xs font-semibold text-muted-foreground w-16 flex items-center justify-center border-r-[0.5px] border-border/30 bg-muted/10" }, getTimezoneDisplay(timezone)), /* @__PURE__ */ React9.createElement("div", { className: "flex-1 grid grid-cols-7" }, weekDays.map((day, index) => /* @__PURE__ */ React9.createElement("div", { key: day.toISOString(), className: cn("py-3 px-2 text-center", index > 0 && "border-l-[0.5px] border-border/30") }, /* @__PURE__ */ React9.createElement("div", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1.5" }, format6(day, "EEE", { locale })), /* @__PURE__ */ React9.createElement("div", { className: cn(
1262
+ /* @__PURE__ */ React9.createElement("div", { className: "flex border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/20 via-background to-muted/20 z-20 sticky top-0 backdrop-blur-sm" }, /* @__PURE__ */ React9.createElement("div", { className: "flex-none p-3 text-center text-xs font-semibold text-muted-foreground w-16 flex items-center justify-center border-r-[0.5px] border-border/30 bg-muted/10" }, /* @__PURE__ */ React9.createElement("div", { className: "flex flex-col items-center justify-center leading-tight" }, /* @__PURE__ */ React9.createElement("span", null, format8(zonedNow, "HH:mm")), /* @__PURE__ */ React9.createElement("span", { className: "text-[10px] opacity-75" }, "(", zoneLabel, ")"))), /* @__PURE__ */ React9.createElement("div", { className: "flex-1 grid grid-cols-7" }, weekDays.map((day, index) => /* @__PURE__ */ React9.createElement("div", { key: day.toISOString(), className: cn("py-3 px-2 text-center", index > 0 && "border-l-[0.5px] border-border/30") }, /* @__PURE__ */ React9.createElement("div", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1.5" }, format8(day, "EEE", { locale })), /* @__PURE__ */ React9.createElement("div", { className: cn(
755
1263
  "w-9 h-9 flex items-center justify-center rounded-xl mx-auto text-sm font-semibold transition-all duration-200",
756
- isToday3(day) ? "bg-primary text-primary-foreground shadow-lg shadow-primary/30 scale-110" : "text-foreground hover:bg-accent/80"
757
- ) }, format6(day, "d", { locale })))))),
1264
+ isSameDay4(day, today) ? "bg-primary text-primary-foreground shadow-lg shadow-primary/30 scale-110" : "text-foreground hover:bg-accent/80"
1265
+ ) }, format8(day, "d", { locale })))))),
758
1266
  /* @__PURE__ */ React9.createElement(
759
1267
  "div",
760
1268
  {
761
1269
  className: "flex min-w-full relative",
762
- style: { height: hours.length * hourHeight }
1270
+ style: { height: HOURS.length * HOUR_HEIGHT }
763
1271
  },
764
- /* @__PURE__ */ React9.createElement("div", { className: "flex-none w-16 border-r-[0.5px] border-border/30 relative bg-muted/5" }, hours.map((hour) => /* @__PURE__ */ React9.createElement(
1272
+ /* @__PURE__ */ React9.createElement("div", { className: "flex-none w-16 border-r-[0.5px] border-border/30 relative bg-muted/5" }, HOURS.map((hour) => /* @__PURE__ */ React9.createElement(
765
1273
  "div",
766
1274
  {
767
1275
  key: hour,
768
1276
  className: "relative w-full text-[11px] text-muted-foreground/80 text-right pr-3 font-medium tabular-nums box-border",
769
- style: { height: hourHeight }
1277
+ style: { height: HOUR_HEIGHT }
1278
+ },
1279
+ /* @__PURE__ */ React9.createElement("span", { className: "block -translate-y-1/2" }, hour !== 0 && format8(slotDate(currentDate, hour, 0), formats.hour, { locale }))
1280
+ )), showsToday && /* @__PURE__ */ React9.createElement(
1281
+ "div",
1282
+ {
1283
+ className: "absolute left-0 w-full pointer-events-none z-30 flex justify-end pr-2",
1284
+ style: { top: `${nowTop}px` }
770
1285
  },
771
- /* @__PURE__ */ React9.createElement("span", { className: "block -translate-y-1/2" }, hour !== 0 && format6((/* @__PURE__ */ new Date()).setHours(hour, 0, 0, 0), timeFormat, { locale }))
772
- ))),
1286
+ /* @__PURE__ */ React9.createElement("span", { className: "text-[10px] font-bold text-primary-foreground bg-primary px-1.5 py-0.5 rounded-md shadow-md -translate-y-1/2 backdrop-blur-none" }, format8(zonedNow, formats.compact, { locale }))
1287
+ )),
773
1288
  /* @__PURE__ */ React9.createElement("div", { className: "flex-1 grid grid-cols-7 relative" }, weekDays.map((day, dayIndex) => {
774
- const dayEvents = events.filter((e) => {
775
- const zonedStart = getZonedDate(e.start);
776
- return isSameDay2(zonedStart, day);
777
- });
778
- return /* @__PURE__ */ React9.createElement("div", { key: day.toISOString(), className: cn("relative h-full", dayIndex > 0 && "border-l-[0.5px] border-border/30") }, hours.map((hour) => {
779
- return /* @__PURE__ */ React9.createElement(
780
- "div",
781
- {
782
- key: hour,
783
- className: "w-full border-b-[0.5px] border-dashed border-border/20 box-border relative hover:bg-accent/5 transition-colors",
784
- style: { height: hourHeight }
785
- },
786
- [0, 15, 30, 45].map((minute) => {
787
- const cellDate = new Date(day);
788
- cellDate.setHours(hour, minute, 0, 0);
789
- const cellId = cellDate.toISOString();
790
- return /* @__PURE__ */ React9.createElement(
791
- DroppableCell,
792
- {
793
- key: minute,
794
- id: cellId,
795
- date: cellDate,
796
- className: "w-full absolute left-0 right-0 z-0 transition-colors",
797
- style: {
798
- height: "25%",
799
- top: `${minute / 60 * 100}%`
800
- }
1289
+ const dayKey2 = format8(day, "yyyy-MM-dd");
1290
+ const positioned = layoutDayEvents(segmentsByDay.get(dayKey2) ?? NO_SEGMENTS);
1291
+ return /* @__PURE__ */ React9.createElement("div", { key: dayKey2, className: cn("relative h-full", dayIndex > 0 && "border-l-[0.5px] border-border/30") }, HOURS.map((hour) => /* @__PURE__ */ React9.createElement(
1292
+ "div",
1293
+ {
1294
+ key: hour,
1295
+ className: "w-full border-b-[0.5px] border-dashed border-border/20 box-border relative hover:bg-accent/5 transition-colors",
1296
+ style: { height: HOUR_HEIGHT }
1297
+ },
1298
+ QUARTERS.map((minute) => {
1299
+ const cellDate = slotDate(day, hour, minute);
1300
+ return /* @__PURE__ */ React9.createElement(
1301
+ DroppableCell,
1302
+ {
1303
+ key: minute,
1304
+ id: cellDate.toISOString(),
1305
+ date: cellDate,
1306
+ className: "w-full absolute left-0 right-0 z-0 transition-colors",
1307
+ style: {
1308
+ height: "25%",
1309
+ top: `${minute / 60 * 100}%`
801
1310
  },
802
- /* @__PURE__ */ React9.createElement(
803
- "div",
804
- {
805
- className: "w-full h-full bg-transparent cursor-pointer",
806
- onClick: () => onTimeSlotClick?.(cellDate)
807
- }
808
- )
809
- );
810
- })
811
- );
812
- }), dayEvents.map((event) => {
813
- const overlappingEvents = dayEvents.filter((e) => {
814
- if (e.id === event.id) return false;
815
- const s1 = getZonedDate(event.start).getTime();
816
- const e1 = getZonedDate(event.end).getTime();
817
- const s2 = getZonedDate(e.start).getTime();
818
- const e2 = getZonedDate(e.end).getTime();
819
- return s1 < e2 && e1 > s2;
820
- });
821
- const group = [event, ...overlappingEvents].sort(
822
- (a, b) => getZonedDate(a.start).getTime() - getZonedDate(b.start).getTime() || (a.id > b.id ? 1 : -1)
823
- // stable sort
824
- );
825
- const index = group.findIndex((e) => e.id === event.id);
826
- const count = group.length;
827
- const widthPercent = 100 / count;
828
- const leftPercent = index * widthPercent;
829
- const zonedEventStart = getZonedDate(event.start);
830
- const zonedEventEnd = getZonedDate(event.end);
831
- const startMinutes = zonedEventStart.getHours() * 60 + zonedEventStart.getMinutes();
832
- const durationMinutes = differenceInMinutes(zonedEventEnd, zonedEventStart);
833
- const top = startMinutes / 60 * hourHeight;
834
- const height = durationMinutes / 60 * hourHeight;
1311
+ onSlotClick: onTimeSlotClick
1312
+ }
1313
+ );
1314
+ })
1315
+ )), positioned.map(({ event, start, end, column, columns }) => {
1316
+ const widthPercent = 100 / columns;
1317
+ const leftPercent = column * widthPercent;
1318
+ const zonedEventStart = toDisplay(event.start, timezone);
1319
+ const zonedEventEnd = toDisplay(event.end, timezone);
1320
+ const startMinutes = start.getHours() * 60 + start.getMinutes();
1321
+ const durationMinutes = differenceInMinutes(end, start);
1322
+ const top = startMinutes / 60 * HOUR_HEIGHT;
1323
+ const height = durationMinutes / 60 * HOUR_HEIGHT;
835
1324
  const isShortEvent = durationMinutes < 60;
1325
+ const isLastSegment = end.getTime() === zonedEventEnd.getTime();
836
1326
  return /* @__PURE__ */ React9.createElement(
837
1327
  DraggableEvent,
838
1328
  {
839
- key: `${event.id}-${day.toISOString()}`,
1329
+ key: `${event.id}-${dayKey2}`,
1330
+ dragId: `${event.id}@${dayKey2}`,
1331
+ grabOffsetMs: start.getTime() - zonedEventStart.getTime(),
840
1332
  event,
841
1333
  className: `absolute z-10 transition-all ${readonly ? "cursor-default" : ""}`,
842
1334
  style: {
@@ -845,15 +1337,15 @@ var WeekView = ({
845
1337
  left: `${leftPercent}%`,
846
1338
  width: `${widthPercent}%`,
847
1339
  // Add minimal spacing between overlapping events
848
- paddingRight: count > 1 ? "2px" : "0"
1340
+ paddingRight: columns > 1 ? "2px" : "0"
849
1341
  }
850
1342
  },
851
1343
  /* @__PURE__ */ React9.createElement(
852
1344
  ResizableEvent,
853
1345
  {
854
- readonly,
1346
+ readonly: readonly || !isLastSegment,
855
1347
  event,
856
- hourHeight,
1348
+ hourHeight: HOUR_HEIGHT,
857
1349
  onResize: onEventResize,
858
1350
  className: "h-full",
859
1351
  style: { height: "100%" }
@@ -868,7 +1360,7 @@ var WeekView = ({
868
1360
  !event.color && "border-primary/20 bg-primary/10",
869
1361
  isShortEvent ? "px-1 flex items-center justify-center" : "p-2",
870
1362
  // Add active border for overlapped events to distinguish them
871
- count > 1 && "border-l-4 border-l-primary/50"
1363
+ columns > 1 && "border-l-4 border-l-primary/50"
872
1364
  ),
873
1365
  style: {
874
1366
  height: "100%",
@@ -881,46 +1373,44 @@ var WeekView = ({
881
1373
  e.stopPropagation();
882
1374
  onEventClick?.(event);
883
1375
  },
884
- title: count > 1 ? `${event.title} (${index + 1}/${count})` : void 0
1376
+ onContextMenu: onEventContextMenu ? (e) => onEventContextMenu(event, e) : void 0,
1377
+ title: columns > 1 ? `${event.title} (${column + 1}/${columns})` : void 0
885
1378
  },
886
1379
  /* @__PURE__ */ React9.createElement("div", { className: "flex flex-col h-full overflow-hidden w-full" }, /* @__PURE__ */ React9.createElement("div", { className: cn(
887
1380
  "font-semibold truncate text-foreground/90 leading-tight",
888
1381
  isShortEvent ? "text-xs text-center" : "text-xs"
889
- ) }, event.title), !isShortEvent && /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement("div", { className: "text-[10px] text-muted-foreground truncate mt-0.5 font-medium leading-tight" }, format6(zonedEventStart, eventTimeFormat, { locale }), " - ", format6(zonedEventEnd, eventTimeFormat, { locale })), event.description && height > 50 && /* @__PURE__ */ React9.createElement("div", { className: "text-[10px] text-muted-foreground/80 truncate mt-1 leading-tight opacity-80" }, event.description)), count > 1 && !isShortEvent && /* @__PURE__ */ React9.createElement("div", { className: "absolute top-1 right-1 bg-background/80 backdrop-blur-sm rounded-full w-4 h-4 flex items-center justify-center text-[9px] font-bold text-muted-foreground border border-border shadow-sm" }, count))
1382
+ ) }, event.title), !isShortEvent && /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement("div", { className: "text-[10px] text-muted-foreground truncate mt-0.5 font-medium leading-tight" }, format8(zonedEventStart, formats.time, { locale }), " - ", format8(zonedEventEnd, formats.time, { locale })), event.description && height > 50 && /* @__PURE__ */ React9.createElement("div", { className: "text-[10px] text-muted-foreground/80 truncate mt-1 leading-tight opacity-80" }, event.description)), columns > 1 && !isShortEvent && /* @__PURE__ */ React9.createElement("div", { className: "absolute top-1 right-1 bg-background/80 backdrop-blur-sm rounded-full w-4 h-4 flex items-center justify-center text-[9px] font-bold text-muted-foreground border border-border shadow-sm" }, columns))
890
1383
  )
891
1384
  )
892
1385
  );
893
- }), isToday3(day) && /* @__PURE__ */ React9.createElement(
1386
+ }), isSameDay4(day, today) && /* @__PURE__ */ React9.createElement(
894
1387
  "div",
895
1388
  {
896
1389
  className: "absolute left-0 right-0 z-20 pointer-events-none flex items-center",
897
- style: {
898
- top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight}px`
899
- }
1390
+ style: { top: `${nowTop}px` }
900
1391
  },
901
1392
  /* @__PURE__ */ React9.createElement("div", { className: "h-[2px] w-full bg-gradient-to-r from-primary via-primary to-primary/50" }),
902
1393
  /* @__PURE__ */ React9.createElement("div", { className: "absolute -left-1.5 w-3 h-3 bg-primary rounded-full shadow-lg shadow-primary/40 ring-2 ring-background animate-pulse" })
903
1394
  ));
904
1395
  }))
905
- ),
906
- /* @__PURE__ */ React9.createElement(
907
- "div",
908
- {
909
- className: "absolute left-0 w-16 pointer-events-none z-30 flex justify-end pr-2",
910
- style: {
911
- top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight + 80}px`
912
- }
913
- },
914
- /* @__PURE__ */ React9.createElement("span", { className: "text-[10px] font-bold text-primary-foreground bg-primary px-1.5 py-0.5 rounded-md shadow-md -translate-y-1/2 backdrop-blur-none" }, format6(zonedNow, nowFormat, { locale }))
915
1396
  )
916
1397
  ));
917
1398
  };
1399
+ var WeekView = React9.memo(WeekViewImpl);
918
1400
 
919
1401
  // src/views/DayView.tsx
920
1402
  import React10 from "react";
921
- import { format as format7, differenceInMinutes as differenceInMinutes2, isToday as isToday4, isSameDay as isSameDay3 } from "date-fns";
922
- import { toZonedTime as toZonedTime4 } from "date-fns-tz";
923
- var DayView = ({
1403
+ import { addDays as addDays3, format as format9, differenceInMinutes as differenceInMinutes2, isSameDay as isSameDay5, startOfDay as startOfDay3 } from "date-fns";
1404
+ var HOUR_HEIGHT2 = 80;
1405
+ var HOURS2 = Array.from({ length: 24 }, (_, i) => i);
1406
+ var QUARTERS2 = [0, 15, 30, 45];
1407
+ var NO_SEGMENTS2 = [];
1408
+ var slotDate2 = (day, hour, minute) => {
1409
+ const date = new Date(day);
1410
+ date.setHours(hour, minute, 0, 0);
1411
+ return date;
1412
+ };
1413
+ var DayViewImpl = ({
924
1414
  currentDate,
925
1415
  events,
926
1416
  onEventClick,
@@ -928,12 +1418,16 @@ var DayView = ({
928
1418
  onEventResize,
929
1419
  timezone,
930
1420
  locale,
931
- readonly
1421
+ readonly,
1422
+ onEventContextMenu,
1423
+ translations
932
1424
  }) => {
933
- const hours = Array.from({ length: 24 }, (_, i) => i);
934
- const hourHeight = 80;
1425
+ const visibleRange = React10.useMemo(() => {
1426
+ const start = startOfDay3(currentDate);
1427
+ return { start, end: addDays3(start, 1) };
1428
+ }, [currentDate]);
935
1429
  const scrollContainerRef = React10.useRef(null);
936
- const [now, setNow] = React10.useState(/* @__PURE__ */ new Date());
1430
+ const [now, setNow] = React10.useState(() => /* @__PURE__ */ new Date());
937
1431
  React10.useEffect(() => {
938
1432
  const interval = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
939
1433
  return () => clearInterval(interval);
@@ -941,99 +1435,71 @@ var DayView = ({
941
1435
  React10.useEffect(() => {
942
1436
  if (scrollContainerRef.current) {
943
1437
  const offsetHour = 3;
944
- scrollContainerRef.current.scrollTop = ((/* @__PURE__ */ new Date()).getHours() - offsetHour) * hourHeight;
1438
+ scrollContainerRef.current.scrollTop = (toDisplay(/* @__PURE__ */ new Date(), timezone).getHours() - offsetHour) * HOUR_HEIGHT2;
945
1439
  }
946
- }, []);
947
- const getZonedDate = (date) => {
948
- return timezone ? toZonedTime4(date, timezone) : date;
949
- };
950
- const zonedNow = getZonedDate(now);
951
- const dayEvents = events.filter((e) => {
952
- const zonedStart = getZonedDate(e.start);
953
- return isSameDay3(zonedStart, currentDate);
954
- });
955
- const is24h = uses24HourClock(locale);
956
- const timeFormat = is24h ? "H:mm" : "h a";
957
- const eventTimeFormat = is24h ? "H:mm" : "h:mm a";
958
- const nowFormat = is24h ? "H:mm" : "h:mm a";
959
- return /* @__PURE__ */ React10.createElement("div", { className: "flex flex-col h-full bg-background border-[0.5px] border-border/50 rounded-2xl overflow-hidden shadow-sm" }, /* @__PURE__ */ React10.createElement("div", { className: "px-6 py-4 border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/20 via-background to-muted/20 text-center shrink-0" }, /* @__PURE__ */ React10.createElement("div", { className: "flex items-center justify-center gap-3" }, /* @__PURE__ */ React10.createElement("h2", { className: "text-xl font-semibold capitalize text-foreground" }, format7(currentDate, "EEEE, MMMM d, yyyy", { locale })), isToday4(currentDate) && /* @__PURE__ */ React10.createElement("span", { className: "text-xs font-semibold bg-primary text-primary-foreground px-3 py-1 rounded-full shadow-md shadow-primary/20" }, "Today"))), /* @__PURE__ */ React10.createElement("div", { ref: scrollContainerRef, className: "flex-1 overflow-y-auto relative" }, /* @__PURE__ */ React10.createElement("div", { className: "flex relative", style: { height: hours.length * hourHeight } }, /* @__PURE__ */ React10.createElement("div", { className: "w-20 bg-muted/5 border-r-[0.5px] border-border/30 relative" }, hours.map((hour) => /* @__PURE__ */ React10.createElement(
1440
+ }, [timezone]);
1441
+ const dayKey2 = format9(currentDate, "yyyy-MM-dd");
1442
+ const positioned = layoutDayEvents(segmentsByDisplayDay(events, timezone, visibleRange).get(dayKey2) ?? NO_SEGMENTS2);
1443
+ const isToday2 = isSameDay5(currentDate, displayToday(timezone, now));
1444
+ const zonedNow = toDisplay(now, timezone);
1445
+ const nowTop = minutesSinceDisplayMidnight(now, timezone) / 60 * HOUR_HEIGHT2;
1446
+ const formats = timeFormats(locale);
1447
+ return /* @__PURE__ */ React10.createElement("div", { className: "flex flex-col h-full bg-background border-[0.5px] border-border/50 rounded-2xl overflow-hidden shadow-sm" }, /* @__PURE__ */ React10.createElement("div", { className: "px-6 py-4 border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/20 via-background to-muted/20 text-center shrink-0" }, /* @__PURE__ */ React10.createElement("div", { className: "flex items-center justify-center gap-3" }, /* @__PURE__ */ React10.createElement("h2", { className: "text-xl font-semibold capitalize text-foreground" }, format9(currentDate, "EEEE, MMMM d, yyyy", { locale })), isToday2 && /* @__PURE__ */ React10.createElement("span", { className: "text-xs font-semibold bg-primary text-primary-foreground px-3 py-1 rounded-full shadow-md shadow-primary/20" }, translations?.today || "Today"))), /* @__PURE__ */ React10.createElement("div", { ref: scrollContainerRef, className: "flex-1 overflow-y-auto relative" }, /* @__PURE__ */ React10.createElement("div", { className: "flex relative", style: { height: HOURS2.length * HOUR_HEIGHT2 } }, /* @__PURE__ */ React10.createElement("div", { className: "w-20 bg-muted/5 border-r-[0.5px] border-border/30 relative" }, HOURS2.map((hour) => /* @__PURE__ */ React10.createElement(
960
1448
  "div",
961
1449
  {
962
1450
  key: hour,
963
1451
  className: "relative w-full",
964
- style: { height: hourHeight }
1452
+ style: { height: HOUR_HEIGHT2 }
965
1453
  },
966
- hour !== 0 && /* @__PURE__ */ React10.createElement("span", { className: "absolute w-full text-center -top-3 left-1/2 -translate-x-1/2 text-[11px] text-muted-foreground/80 font-medium tabular-nums bg-background px-1.5 py-0.5 rounded-md" }, format7((/* @__PURE__ */ new Date()).setHours(hour, 0, 0, 0), timeFormat, { locale }))
967
- )), isToday4(currentDate) && /* @__PURE__ */ React10.createElement(
1454
+ hour !== 0 && /* @__PURE__ */ React10.createElement("span", { className: "absolute w-full text-center -top-3 left-1/2 -translate-x-1/2 text-[11px] text-muted-foreground/80 font-medium tabular-nums bg-background px-1.5 py-0.5 rounded-md" }, format9(slotDate2(currentDate, hour, 0), formats.hour, { locale }))
1455
+ )), isToday2 && /* @__PURE__ */ React10.createElement(
968
1456
  "div",
969
1457
  {
970
1458
  className: "absolute left-0 w-full pointer-events-none z-30 flex justify-end pr-2",
971
- style: {
972
- top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight}px`
973
- }
1459
+ style: { top: `${nowTop}px` }
974
1460
  },
975
- /* @__PURE__ */ React10.createElement("span", { className: "text-[10px] font-bold text-white bg-primary px-2 py-1 rounded-lg shadow-md shadow-primary/30 -translate-y-1/2" }, format7(zonedNow, nowFormat, { locale }))
976
- )), /* @__PURE__ */ React10.createElement("div", { className: "flex-1 relative" }, hours.map((hour) => {
977
- return /* @__PURE__ */ React10.createElement(
978
- "div",
979
- {
980
- key: hour,
981
- className: "border-b-[0.5px] border-dashed border-border/20 box-border relative hover:bg-accent/5 transition-colors",
982
- style: { height: hourHeight }
983
- },
984
- [0, 15, 30, 45].map((minute) => {
985
- const cellDate = new Date(currentDate);
986
- cellDate.setHours(hour, minute, 0, 0);
987
- const cellId = cellDate.toISOString();
988
- return /* @__PURE__ */ React10.createElement(
989
- DroppableCell,
990
- {
991
- key: minute,
992
- id: cellId,
993
- date: cellDate,
994
- className: "w-full absolute left-0 right-0 z-0 transition-colors",
995
- style: {
996
- height: "25%",
997
- top: `${minute / 60 * 100}%`
998
- }
1461
+ /* @__PURE__ */ React10.createElement("span", { className: "text-[10px] font-bold text-white bg-primary px-2 py-1 rounded-lg shadow-md shadow-primary/30 -translate-y-1/2" }, format9(zonedNow, formats.time, { locale }))
1462
+ )), /* @__PURE__ */ React10.createElement("div", { className: "flex-1 relative" }, HOURS2.map((hour) => /* @__PURE__ */ React10.createElement(
1463
+ "div",
1464
+ {
1465
+ key: hour,
1466
+ className: "border-b-[0.5px] border-dashed border-border/20 box-border relative hover:bg-accent/5 transition-colors",
1467
+ style: { height: HOUR_HEIGHT2 }
1468
+ },
1469
+ QUARTERS2.map((minute) => {
1470
+ const cellDate = slotDate2(currentDate, hour, minute);
1471
+ return /* @__PURE__ */ React10.createElement(
1472
+ DroppableCell,
1473
+ {
1474
+ key: minute,
1475
+ id: cellDate.toISOString(),
1476
+ date: cellDate,
1477
+ className: "w-full absolute left-0 right-0 z-0 transition-colors",
1478
+ style: {
1479
+ height: "25%",
1480
+ top: `${minute / 60 * 100}%`
999
1481
  },
1000
- /* @__PURE__ */ React10.createElement(
1001
- "div",
1002
- {
1003
- className: "h-full w-full bg-transparent cursor-pointer",
1004
- onClick: () => onTimeSlotClick?.(cellDate)
1005
- }
1006
- )
1007
- );
1008
- })
1009
- );
1010
- }), dayEvents.map((event) => {
1011
- const overlappingEvents = dayEvents.filter((e) => {
1012
- if (e.id === event.id) return false;
1013
- const s1 = getZonedDate(event.start).getTime();
1014
- const e1 = getZonedDate(event.end).getTime();
1015
- const s2 = getZonedDate(e.start).getTime();
1016
- const e2 = getZonedDate(e.end).getTime();
1017
- return s1 < e2 && e1 > s2;
1018
- });
1019
- const group = [event, ...overlappingEvents].sort(
1020
- (a, b) => getZonedDate(a.start).getTime() - getZonedDate(b.start).getTime() || (a.id > b.id ? 1 : -1)
1021
- );
1022
- const index = group.findIndex((e) => e.id === event.id);
1023
- const count = group.length;
1024
- const widthPercent = 100 / count;
1025
- const leftPercent = index * widthPercent;
1026
- const zonedStart = getZonedDate(event.start);
1027
- const zonedEnd = getZonedDate(event.end);
1028
- const startMinutes = zonedStart.getHours() * 60 + zonedStart.getMinutes();
1029
- const durationMinutes = differenceInMinutes2(zonedEnd, zonedStart);
1030
- const top = startMinutes / 60 * hourHeight;
1031
- const height = durationMinutes / 60 * hourHeight;
1482
+ onSlotClick: onTimeSlotClick
1483
+ }
1484
+ );
1485
+ })
1486
+ )), positioned.map(({ event, start, end, column, columns }) => {
1487
+ const widthPercent = 100 / columns;
1488
+ const leftPercent = column * widthPercent;
1489
+ const zonedStart = toDisplay(event.start, timezone);
1490
+ const zonedEnd = toDisplay(event.end, timezone);
1491
+ const startMinutes = start.getHours() * 60 + start.getMinutes();
1492
+ const durationMinutes = differenceInMinutes2(end, start);
1493
+ const top = startMinutes / 60 * HOUR_HEIGHT2;
1494
+ const height = durationMinutes / 60 * HOUR_HEIGHT2;
1032
1495
  const isShortEvent = durationMinutes < 45;
1496
+ const isLastSegment = end.getTime() === zonedEnd.getTime();
1033
1497
  return /* @__PURE__ */ React10.createElement(
1034
1498
  DraggableEvent,
1035
1499
  {
1036
- key: `${event.id}-${currentDate.toISOString()}`,
1500
+ key: `${event.id}-${dayKey2}`,
1501
+ dragId: `${event.id}@${dayKey2}`,
1502
+ grabOffsetMs: start.getTime() - zonedStart.getTime(),
1037
1503
  event,
1038
1504
  className: `absolute z-10 transition-all ${readonly ? "cursor-default" : ""}`,
1039
1505
  style: {
@@ -1041,15 +1507,15 @@ var DayView = ({
1041
1507
  height: `${Math.max(height, 28)}px`,
1042
1508
  left: `calc(${leftPercent}% + 4px)`,
1043
1509
  width: `calc(${widthPercent}% - 8px)`,
1044
- paddingRight: count > 1 ? "2px" : "0"
1510
+ paddingRight: columns > 1 ? "2px" : "0"
1045
1511
  }
1046
1512
  },
1047
1513
  /* @__PURE__ */ React10.createElement(
1048
1514
  ResizableEvent,
1049
1515
  {
1050
- readonly,
1516
+ readonly: readonly || !isLastSegment,
1051
1517
  event,
1052
- hourHeight,
1518
+ hourHeight: HOUR_HEIGHT2,
1053
1519
  onResize: onEventResize,
1054
1520
  className: "h-full",
1055
1521
  style: { height: "100%" }
@@ -1063,44 +1529,44 @@ var DayView = ({
1063
1529
  readonly || isEventDragDisabled(event) ? "cursor-default" : "cursor-grab active:cursor-grabbing",
1064
1530
  !event.color && "bg-primary/10 border-primary/20",
1065
1531
  isShortEvent ? "px-2 flex items-center" : "px-3 py-2",
1066
- count > 1 && "border-l-4"
1532
+ columns > 1 && "border-l-4"
1067
1533
  ),
1068
1534
  style: {
1069
1535
  backgroundColor: event.color ? `${event.color}15` : void 0,
1070
1536
  borderColor: event.color ? `${event.color}30` : void 0,
1071
1537
  borderLeftColor: event.color || "var(--primary)",
1072
- borderLeftWidth: count > 1 ? "4px" : "3px"
1538
+ borderLeftWidth: columns > 1 ? "4px" : "3px"
1073
1539
  },
1074
1540
  onClick: (e) => {
1075
1541
  e.stopPropagation();
1076
1542
  onEventClick?.(event);
1077
1543
  },
1078
- title: count > 1 ? `${event.title} (${index + 1}/${count})` : void 0
1544
+ onContextMenu: onEventContextMenu ? (e) => onEventContextMenu(event, e) : void 0,
1545
+ title: columns > 1 ? `${event.title} (${column + 1}/${columns})` : void 0
1079
1546
  },
1080
1547
  /* @__PURE__ */ React10.createElement("div", { className: "flex flex-col h-full overflow-hidden w-full" }, /* @__PURE__ */ React10.createElement("div", { className: cn(
1081
1548
  "font-semibold truncate leading-tight",
1082
1549
  isShortEvent ? "text-xs" : "text-sm",
1083
1550
  event.color ? "text-foreground" : "text-foreground/90"
1084
- ) }, event.title), !isShortEvent && /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement("div", { className: "text-xs text-muted-foreground mt-0.5 font-medium" }, format7(zonedStart, eventTimeFormat, { locale }), " - ", format7(zonedEnd, eventTimeFormat, { locale })), event.description && height > 60 && /* @__PURE__ */ React10.createElement("div", { className: "text-xs text-muted-foreground/80 mt-1 line-clamp-2" }, event.description)), count > 1 && !isShortEvent && /* @__PURE__ */ React10.createElement("div", { className: "absolute top-1.5 right-1.5 bg-background/90 backdrop-blur-sm rounded-full w-5 h-5 flex items-center justify-center text-[10px] font-bold text-muted-foreground border border-border shadow-sm" }, count))
1551
+ ) }, event.title), !isShortEvent && /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement("div", { className: "text-xs text-muted-foreground mt-0.5 font-medium" }, format9(zonedStart, formats.time, { locale }), " - ", format9(zonedEnd, formats.time, { locale })), event.description && height > 60 && /* @__PURE__ */ React10.createElement("div", { className: "text-xs text-muted-foreground/80 mt-1 line-clamp-2" }, event.description)), columns > 1 && !isShortEvent && /* @__PURE__ */ React10.createElement("div", { className: "absolute top-1.5 right-1.5 bg-background/90 backdrop-blur-sm rounded-full w-5 h-5 flex items-center justify-center text-[10px] font-bold text-muted-foreground border border-border shadow-sm" }, columns))
1085
1552
  )
1086
1553
  )
1087
1554
  );
1088
- }), isToday4(currentDate) && /* @__PURE__ */ React10.createElement(
1555
+ }), isToday2 && /* @__PURE__ */ React10.createElement(
1089
1556
  "div",
1090
1557
  {
1091
1558
  className: "absolute left-0 right-0 z-20 pointer-events-none flex items-center",
1092
- style: {
1093
- top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight}px`
1094
- }
1559
+ style: { top: `${nowTop}px` }
1095
1560
  },
1096
1561
  /* @__PURE__ */ React10.createElement("div", { className: "h-[2px] w-full bg-gradient-to-r from-primary via-primary to-primary/50" }),
1097
1562
  /* @__PURE__ */ React10.createElement("div", { className: "absolute -left-1.5 w-3 h-3 bg-primary rounded-full shadow-lg shadow-primary/40 ring-2 ring-background animate-pulse" })
1098
1563
  )))));
1099
1564
  };
1565
+ var DayView = React10.memo(DayViewImpl);
1100
1566
 
1101
1567
  // src/views/AgendaView.tsx
1102
1568
  import React12, { useMemo as useMemo2 } from "react";
1103
- import { format as format8, isSameDay as isSameDay4, isToday as isToday5, isTomorrow, addDays, startOfDay, differenceInMinutes as differenceInMinutes3 } from "date-fns";
1569
+ import { format as format10, isSameDay as isSameDay6, addDays as addDays4, startOfDay as startOfDay4, differenceInMinutes as differenceInMinutes3 } from "date-fns";
1104
1570
 
1105
1571
  // src/components/EmptyState.tsx
1106
1572
  import React11 from "react";
@@ -1126,10 +1592,10 @@ var formatDuration = (start, end) => {
1126
1592
  if (remainingMinutes === 0) return `${hours}h`;
1127
1593
  return `${hours}h ${remainingMinutes}m`;
1128
1594
  };
1129
- var getDateLabel = (date, locale, translations) => {
1130
- if (isToday5(date)) return translations?.today || "Today";
1131
- if (isTomorrow(date)) return translations?.tomorrow || "Tomorrow";
1132
- return format8(date, "EEEE", { locale });
1595
+ var getDateLabel = (date, today, locale, translations) => {
1596
+ if (isSameDay6(date, today)) return translations?.today || "Today";
1597
+ if (isSameDay6(date, addDays4(today, 1))) return translations?.tomorrow || "Tomorrow";
1598
+ return format10(date, "EEEE", { locale });
1133
1599
  };
1134
1600
  var AgendaView = ({
1135
1601
  currentDate,
@@ -1137,24 +1603,25 @@ var AgendaView = ({
1137
1603
  onEventClick,
1138
1604
  onCreateEvent,
1139
1605
  locale,
1140
- translations
1606
+ translations,
1607
+ timezone,
1608
+ onEventContextMenu
1141
1609
  }) => {
1142
1610
  const is24h = uses24HourClock(locale);
1143
- const timePattern = is24h ? "H:mm" : "h:mm a";
1144
- const compactTimePattern = is24h ? "H:mm" : "h:mm";
1611
+ const { time: timePattern, compact: compactTimePattern } = timeFormats(locale);
1612
+ const today = displayToday(timezone);
1145
1613
  const groupedEvents = useMemo2(() => {
1146
- const startDate = startOfDay(currentDate);
1614
+ const byDay = eventsByStartDay(events, timezone);
1147
1615
  const groups = [];
1148
- const sortedEvents = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
1149
1616
  for (let i = 0; i < 30; i++) {
1150
- const day = addDays(startDate, i);
1151
- const dayEvents = sortedEvents.filter((event) => isSameDay4(event.start, day));
1152
- if (dayEvents.length > 0) {
1153
- groups.push({ date: day, events: dayEvents });
1617
+ const day = addDays4(startOfDay4(currentDate), i);
1618
+ const dayEvents = byDay.get(format10(day, "yyyy-MM-dd"));
1619
+ if (dayEvents?.length) {
1620
+ groups.push({ date: day, events: [...dayEvents].sort((a, b) => a.start.getTime() - b.start.getTime()) });
1154
1621
  }
1155
1622
  }
1156
1623
  return groups;
1157
- }, [currentDate, events]);
1624
+ }, [currentDate, events, timezone]);
1158
1625
  const container = {
1159
1626
  hidden: { opacity: 0 },
1160
1627
  show: {
@@ -1185,16 +1652,17 @@ var AgendaView = ({
1185
1652
  },
1186
1653
  /* @__PURE__ */ React12.createElement("div", { className: "sticky top-0 bg-background/95 backdrop-blur-md py-4 z-10 border-b border-border/50" }, /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ React12.createElement("div", { className: cn(
1187
1654
  "flex flex-col items-center justify-center w-16 h-16 rounded-2xl transition-all",
1188
- isToday5(group.date) ? "bg-primary text-primary-foreground shadow-lg shadow-primary/30" : "bg-muted/50 text-foreground"
1189
- ) }, /* @__PURE__ */ React12.createElement("span", { className: "text-2xl font-bold leading-none" }, format8(group.date, "d")), /* @__PURE__ */ React12.createElement("span", { className: "text-xs font-medium uppercase tracking-wide opacity-80" }, format8(group.date, "MMM", { locale }))), /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React12.createElement("span", { className: cn(
1655
+ isSameDay6(group.date, today) ? "bg-primary text-primary-foreground shadow-lg shadow-primary/30" : "bg-muted/50 text-foreground"
1656
+ ) }, /* @__PURE__ */ React12.createElement("span", { className: "text-2xl font-bold leading-none" }, format10(group.date, "d")), /* @__PURE__ */ React12.createElement("span", { className: "text-xs font-medium uppercase tracking-wide opacity-80" }, format10(group.date, "MMM", { locale }))), /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React12.createElement("span", { className: cn(
1190
1657
  "text-lg font-semibold",
1191
- isToday5(group.date) && "text-primary"
1192
- ) }, getDateLabel(group.date, locale, translations)), /* @__PURE__ */ React12.createElement("span", { className: "text-sm text-muted-foreground" }, group.events.length, " ", group.events.length !== 1 ? translations?.eventPlural || "events" : translations?.eventSingular || "event")))),
1658
+ isSameDay6(group.date, today) && "text-primary"
1659
+ ) }, getDateLabel(group.date, today, locale, translations)), /* @__PURE__ */ React12.createElement("span", { className: "text-sm text-muted-foreground" }, group.events.length, " ", group.events.length !== 1 ? translations?.eventPlural || "events" : translations?.eventSingular || "event")))),
1193
1660
  /* @__PURE__ */ React12.createElement("div", { className: "py-4 space-y-3" }, group.events.map((event) => /* @__PURE__ */ React12.createElement(
1194
1661
  motion.div,
1195
1662
  {
1196
1663
  key: event.id,
1197
1664
  onClick: () => onEventClick?.(event),
1665
+ onContextMenu: onEventContextMenu ? (e) => onEventContextMenu(event, e) : void 0,
1198
1666
  className: cn(
1199
1667
  "group relative flex gap-4 p-4 rounded-2xl border border-border/40",
1200
1668
  "hover:border-primary/40 hover:shadow-lg hover:shadow-primary/5",
@@ -1211,14 +1679,14 @@ var AgendaView = ({
1211
1679
  style: { backgroundColor: event.color || "var(--primary)" }
1212
1680
  }
1213
1681
  ),
1214
- /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center min-w-[70px] pl-2" }, event.allDay ? /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ React12.createElement("span", { className: "text-xs font-semibold text-muted-foreground bg-muted/80 px-2.5 py-1 rounded-full" }, translations?.allDay || "All Day")) : /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ React12.createElement("span", { className: "text-base font-semibold text-foreground" }, format8(event.start, compactTimePattern, { locale })), !is24h && /* @__PURE__ */ React12.createElement("span", { className: "text-xs text-muted-foreground uppercase" }, format8(event.start, "a")), /* @__PURE__ */ React12.createElement("div", { className: "w-px h-3 bg-border my-1" }), /* @__PURE__ */ React12.createElement("span", { className: "text-xs text-muted-foreground/70 font-medium" }, formatDuration(event.start, event.end)))),
1682
+ /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center min-w-[70px] pl-2" }, event.allDay ? /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ React12.createElement("span", { className: "text-xs font-semibold text-muted-foreground bg-muted/80 px-2.5 py-1 rounded-full" }, translations?.allDay || "All Day")) : /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ React12.createElement("span", { className: "text-base font-semibold text-foreground" }, format10(toDisplay(event.start, timezone), compactTimePattern, { locale })), !is24h && /* @__PURE__ */ React12.createElement("span", { className: "text-xs text-muted-foreground uppercase" }, format10(toDisplay(event.start, timezone), "a")), /* @__PURE__ */ React12.createElement("div", { className: "w-px h-3 bg-border my-1" }), /* @__PURE__ */ React12.createElement("span", { className: "text-xs text-muted-foreground/70 font-medium" }, formatDuration(event.start, event.end)))),
1215
1683
  /* @__PURE__ */ React12.createElement("div", { className: "flex-1 min-w-0 space-y-2" }, /* @__PURE__ */ React12.createElement("div", { className: "flex items-start justify-between gap-2" }, /* @__PURE__ */ React12.createElement("h4", { className: "text-base font-semibold text-foreground group-hover:text-primary transition-colors line-clamp-1" }, event.title), /* @__PURE__ */ React12.createElement(
1216
1684
  "div",
1217
1685
  {
1218
1686
  className: "w-3 h-3 rounded-full shrink-0 mt-1.5",
1219
1687
  style: { backgroundColor: event.color || "var(--primary)" }
1220
1688
  }
1221
- )), event.description && /* @__PURE__ */ React12.createElement("p", { className: "text-sm text-muted-foreground line-clamp-2 leading-relaxed" }, event.description), /* @__PURE__ */ React12.createElement("div", { className: "flex flex-wrap items-center gap-3 pt-1" }, !event.allDay && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Clock, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, format8(event.start, timePattern, { locale }), " - ", format8(event.end, timePattern, { locale }))), event.guests && event.guests.length > 0 && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Users, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, event.guests.length, " ", event.guests.length !== 1 ? translations?.guestsCount || "guests" : translations?.guestCount || "guest")), event.attachments && event.attachments.length > 0 && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Paperclip, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, event.attachments.length)), event.reminders && event.reminders.length > 0 && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Bell, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, event.reminders.length)))),
1689
+ )), event.description && /* @__PURE__ */ React12.createElement("p", { className: "text-sm text-muted-foreground line-clamp-2 leading-relaxed" }, event.description), /* @__PURE__ */ React12.createElement("div", { className: "flex flex-wrap items-center gap-3 pt-1" }, !event.allDay && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Clock, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, format10(toDisplay(event.start, timezone), timePattern, { locale }), " - ", format10(toDisplay(event.end, timezone), timePattern, { locale }))), event.guests && event.guests.length > 0 && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Users, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, event.guests.length, " ", event.guests.length !== 1 ? translations?.guestsCount || "guests" : translations?.guestCount || "guest")), event.attachments && event.attachments.length > 0 && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Paperclip, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, event.attachments.length)), event.reminders && event.reminders.length > 0 && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Bell, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, event.reminders.length)))),
1222
1690
  /* @__PURE__ */ React12.createElement("div", { className: "absolute right-4 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity" }, /* @__PURE__ */ React12.createElement("svg", { className: "w-5 h-5 text-muted-foreground", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React12.createElement("path", { d: "M9 18l6-6-6-6" })))
1223
1691
  )))
1224
1692
  ))
@@ -1227,23 +1695,26 @@ var AgendaView = ({
1227
1695
 
1228
1696
  // src/views/ResourceView.tsx
1229
1697
  import React13, { useRef as useRef2 } from "react";
1230
- import { format as format9, startOfDay as startOfDay2, differenceInMinutes as differenceInMinutes4, isSameDay as isSameDay5 } from "date-fns";
1698
+ import { format as format11, startOfDay as startOfDay5, differenceInMinutes as differenceInMinutes4 } from "date-fns";
1231
1699
  var ResourceView = ({
1232
1700
  currentDate,
1233
1701
  events,
1234
1702
  resources,
1235
1703
  onEventClick,
1236
1704
  onTimeSlotClick,
1237
- locale
1705
+ locale,
1706
+ timezone,
1707
+ translations,
1708
+ onEventContextMenu
1238
1709
  }) => {
1239
1710
  const containerRef = useRef2(null);
1240
1711
  const hours = Array.from({ length: 24 }, (_, i) => i);
1241
1712
  const hourWidth = 100;
1242
- const timeFormat = uses24HourClock(locale) ? "H:mm" : "h a";
1713
+ const timeFormat = timeFormats(locale).hour;
1243
1714
  const getEventStyle = (event) => {
1244
- const start = new Date(event.start);
1245
- const end = new Date(event.end);
1246
- const dayStart = startOfDay2(currentDate);
1715
+ const start = toDisplay(event.start, timezone);
1716
+ const end = toDisplay(event.end, timezone);
1717
+ const dayStart = startOfDay5(currentDate);
1247
1718
  const startMinutes = differenceInMinutes4(start, dayStart);
1248
1719
  const durationMinutes = differenceInMinutes4(end, start);
1249
1720
  const left = startMinutes / 60 * hourWidth;
@@ -1254,19 +1725,19 @@ var ResourceView = ({
1254
1725
  // Min width for visibility
1255
1726
  };
1256
1727
  };
1257
- return /* @__PURE__ */ React13.createElement("div", { className: "flex flex-col h-full bg-background overflow-hidden" }, /* @__PURE__ */ React13.createElement("div", { className: "flex border-b border-border bg-muted/20" }, /* @__PURE__ */ React13.createElement("div", { className: "w-48 shrink-0 border-r border-border p-4 font-semibold text-sm bg-background sticky left-0 z-20" }, "Resources"), /* @__PURE__ */ React13.createElement("div", { className: "flex overflow-hidden" }, /* @__PURE__ */ React13.createElement("div", { className: "flex relative", style: { width: hours.length * hourWidth } }, hours.map((hour) => /* @__PURE__ */ React13.createElement(
1728
+ return /* @__PURE__ */ React13.createElement("div", { className: "flex flex-col h-full bg-background overflow-hidden" }, /* @__PURE__ */ React13.createElement("div", { className: "flex border-b border-border bg-muted/20" }, /* @__PURE__ */ React13.createElement("div", { className: "w-48 shrink-0 border-r border-border p-4 font-semibold text-sm bg-background sticky left-0 z-20" }, translations?.resource || "Resources"), /* @__PURE__ */ React13.createElement("div", { className: "flex overflow-hidden" }, /* @__PURE__ */ React13.createElement("div", { className: "flex relative", style: { width: hours.length * hourWidth } }, hours.map((hour) => /* @__PURE__ */ React13.createElement(
1258
1729
  "div",
1259
1730
  {
1260
1731
  key: hour,
1261
1732
  className: "border-r border-border/50 text-xs text-muted-foreground p-2 font-medium shrink-0",
1262
1733
  style: { width: hourWidth }
1263
1734
  },
1264
- format9((/* @__PURE__ */ new Date()).setHours(hour, 0, 0, 0), timeFormat, { locale })
1735
+ format11((/* @__PURE__ */ new Date()).setHours(hour, 0, 0, 0), timeFormat, { locale })
1265
1736
  ))))), /* @__PURE__ */ React13.createElement("div", { className: "flex-1 overflow-auto relative", ref: containerRef }, /* @__PURE__ */ React13.createElement("div", { className: "min-w-fit" }, resources.map((resource) => {
1266
1737
  const resourceEvents = events.filter(
1267
- (e) => e.resourceId === resource.id && isSameDay5(new Date(e.start), currentDate)
1738
+ (e) => e.resourceId === resource.id && isSameDisplayDay(e.start, currentDate, timezone)
1268
1739
  );
1269
- return /* @__PURE__ */ React13.createElement("div", { key: resource.id, className: "flex border-b border-border min-h-[100px]" }, /* @__PURE__ */ React13.createElement("div", { className: "w-48 shrink-0 border-r border-border p-4 bg-background sticky left-0 z-10 flex items-center gap-3" }, resource.avatar ? /* @__PURE__ */ React13.createElement("img", { src: resource.avatar, alt: resource.label, className: "w-8 h-8 rounded-full object-cover" }) : /* @__PURE__ */ React13.createElement("div", { className: "w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-xs font-bold text-primary" }, resource.label.substring(0, 2).toUpperCase()), /* @__PURE__ */ React13.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React13.createElement("span", { className: "text-sm font-medium" }, resource.label), /* @__PURE__ */ React13.createElement("span", { className: "text-xs text-muted-foreground" }, "ID: ", resource.id))), /* @__PURE__ */ React13.createElement("div", { className: "relative flex", style: { width: hours.length * hourWidth } }, hours.map((hour) => /* @__PURE__ */ React13.createElement(
1740
+ return /* @__PURE__ */ React13.createElement("div", { key: resource.id, className: "flex border-b border-border min-h-[100px]" }, /* @__PURE__ */ React13.createElement("div", { className: "w-48 shrink-0 border-r border-border p-4 bg-background sticky left-0 z-10 flex items-center gap-3" }, resource.avatar ? /* @__PURE__ */ React13.createElement("img", { src: resource.avatar, alt: resource.label, className: "w-8 h-8 rounded-full object-cover" }) : /* @__PURE__ */ React13.createElement("div", { className: "w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-xs font-bold text-primary" }, resource.label.substring(0, 2).toUpperCase()), /* @__PURE__ */ React13.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React13.createElement("span", { className: "text-sm font-medium" }, resource.label), /* @__PURE__ */ React13.createElement("span", { className: "text-xs text-muted-foreground" }, interpolate(translations?.idLabel || "ID: {id}", { id: resource.id })))), /* @__PURE__ */ React13.createElement("div", { className: "relative flex", style: { width: hours.length * hourWidth } }, hours.map((hour) => /* @__PURE__ */ React13.createElement(
1270
1741
  "div",
1271
1742
  {
1272
1743
  key: hour,
@@ -1274,20 +1745,18 @@ var ResourceView = ({
1274
1745
  style: { width: hourWidth }
1275
1746
  },
1276
1747
  [0, 15, 30, 45].map((minute) => {
1277
- const slotDate = new Date(currentDate);
1278
- slotDate.setHours(hour, minute, 0, 0);
1279
- const slotId = `${resource.id}-${slotDate.toISOString()}`;
1748
+ const slotDate3 = new Date(currentDate);
1749
+ slotDate3.setHours(hour, minute, 0, 0);
1750
+ const slotId = `${resource.id}-${slotDate3.toISOString()}`;
1280
1751
  return /* @__PURE__ */ React13.createElement(
1281
1752
  DroppableCell,
1282
1753
  {
1283
1754
  key: minute,
1284
1755
  id: slotId,
1285
- date: slotDate,
1756
+ date: slotDate3,
1286
1757
  resourceId: resource.id,
1287
1758
  className: "h-full flex-1 border-r border-border/10 last:border-border/30 hover:bg-accent/10 transition-colors",
1288
- onClick: () => {
1289
- onTimeSlotClick?.(slotDate, resource.id);
1290
- }
1759
+ onSlotClick: onTimeSlotClick
1291
1760
  }
1292
1761
  );
1293
1762
  })
@@ -1300,6 +1769,7 @@ var ResourceView = ({
1300
1769
  e.stopPropagation();
1301
1770
  onEventClick?.(event);
1302
1771
  },
1772
+ onContextMenu: onEventContextMenu ? (e) => onEventContextMenu(event, e) : void 0,
1303
1773
  className: "absolute top-2 bottom-2 rounded-md px-2 py-1 text-xs font-medium border shadow-sm cursor-pointer overflow-hidden hover:brightness-95 transition-all z-10",
1304
1774
  style: {
1305
1775
  ...getEventStyle(event),
@@ -1314,7 +1784,7 @@ var ResourceView = ({
1314
1784
  };
1315
1785
 
1316
1786
  // src/components/EventModal.tsx
1317
- import React15, { useState as useState3, useEffect as useEffect3 } from "react";
1787
+ import React15, { useState as useState3, useEffect as useEffect3, useInsertionEffect, useRef as useRef4 } from "react";
1318
1788
 
1319
1789
  // src/components/ui/Modal.tsx
1320
1790
  import React14, { useEffect as useEffect2, useRef as useRef3 } from "react";
@@ -1325,7 +1795,8 @@ var Modal = ({
1325
1795
  title,
1326
1796
  children,
1327
1797
  className,
1328
- hideHeader = false
1798
+ hideHeader = false,
1799
+ closeLabel = "Close"
1329
1800
  }) => {
1330
1801
  const overlayRef = useRef3(null);
1331
1802
  useEffect2(() => {
@@ -1366,14 +1837,14 @@ var Modal = ({
1366
1837
  className: "h-9 w-9 p-0 rounded-xl hover:bg-accent/80 transition-all duration-200"
1367
1838
  },
1368
1839
  /* @__PURE__ */ React14.createElement(X, { className: "h-4 w-4" }),
1369
- /* @__PURE__ */ React14.createElement("span", { className: "sr-only" }, "Close")
1840
+ /* @__PURE__ */ React14.createElement("span", { className: "sr-only" }, closeLabel)
1370
1841
  )),
1371
1842
  /* @__PURE__ */ React14.createElement("div", { className: cn("overflow-y-auto flex-1", !hideHeader && "p-6") }, children)
1372
1843
  ));
1373
1844
  };
1374
1845
 
1375
1846
  // src/components/EventModal.tsx
1376
- import { format as format10, differenceInMinutes as differenceInMinutes5 } from "date-fns";
1847
+ import { format as format12, differenceInMinutes as differenceInMinutes5 } from "date-fns";
1377
1848
  import { motion as motion2, AnimatePresence } from "framer-motion";
1378
1849
  import {
1379
1850
  Clock as Clock2,
@@ -1396,18 +1867,7 @@ import {
1396
1867
  Tag,
1397
1868
  ExternalLink
1398
1869
  } from "lucide-react";
1399
- var REMINDER_OPTIONS = [
1400
- { value: 0, label: "At time of event" },
1401
- { value: 5, label: "5 minutes before" },
1402
- { value: 10, label: "10 minutes before" },
1403
- { value: 15, label: "15 minutes before" },
1404
- { value: 30, label: "30 minutes before" },
1405
- { value: 60, label: "1 hour before" },
1406
- { value: 120, label: "2 hours before" },
1407
- { value: 1440, label: "1 day before" },
1408
- { value: 2880, label: "2 days before" },
1409
- { value: 10080, label: "1 week before" }
1410
- ];
1870
+ var REMINDER_OPTIONS = [0, 5, 10, 15, 30, 60, 120, 1440, 2880, 10080];
1411
1871
  var COLOR_PALETTE = [
1412
1872
  "#3b82f6",
1413
1873
  // Blue
@@ -1430,6 +1890,30 @@ var COLOR_PALETTE = [
1430
1890
  "#6366f1"
1431
1891
  // Indigo
1432
1892
  ];
1893
+ var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1894
+ var mergeGuests = (current, added) => {
1895
+ const seen = new Set(current.map((email) => email.toLowerCase()));
1896
+ const merged = [...current];
1897
+ for (const email of added) {
1898
+ if (!seen.has(email.toLowerCase())) {
1899
+ seen.add(email.toLowerCase());
1900
+ merged.push(email);
1901
+ }
1902
+ }
1903
+ return merged;
1904
+ };
1905
+ var reminderLabel = (minutes, t) => {
1906
+ if (minutes === 0) return t.reminderAtTime;
1907
+ if (minutes === 10080) return t.reminderWeek;
1908
+ if (minutes === 1440) return t.reminderDay;
1909
+ if (minutes % 1440 === 0) return interpolate(t.reminderDays, { n: minutes / 1440 });
1910
+ if (minutes === 60) return t.reminderHour;
1911
+ if (minutes % 60 === 0) return interpolate(t.reminderHours, { n: minutes / 60 });
1912
+ return interpolate(t.reminderMinutes, { n: minutes });
1913
+ };
1914
+ var avatarHue = (email) => [...email].reduce((hash, char) => (hash * 31 + char.charCodeAt(0)) % 360, 7);
1915
+ var mapsUrl = (location) => `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(location)}`;
1916
+ var isSafeUrl = (url) => /^(https?:|mailto:|blob:|\/|\.\/|\.\.\/|[^:]*$)/i.test(url.trim());
1433
1917
  var EventModal = ({
1434
1918
  isOpen,
1435
1919
  onClose,
@@ -1437,9 +1921,15 @@ var EventModal = ({
1437
1921
  initialDate,
1438
1922
  onSave,
1439
1923
  onDelete,
1440
- calendars
1924
+ calendars,
1925
+ translations: t,
1926
+ locale
1441
1927
  }) => {
1442
1928
  const [mode, setMode] = useState3("create");
1929
+ const translationsRef = useRef4(t);
1930
+ useInsertionEffect(() => {
1931
+ translationsRef.current = t;
1932
+ });
1443
1933
  const [formData, setFormData] = useState3({
1444
1934
  title: "",
1445
1935
  description: "",
@@ -1452,22 +1942,18 @@ var EventModal = ({
1452
1942
  type: void 0,
1453
1943
  recurrence: void 0,
1454
1944
  attachments: [],
1455
- reminders: [{ id: "1", type: "notification", time: 30, label: "30 minutes before" }]
1945
+ reminders: []
1456
1946
  });
1457
1947
  const [isCalendarDropdownOpen, setIsCalendarDropdownOpen] = useState3(false);
1458
1948
  const [isColorPickerOpen, setIsColorPickerOpen] = useState3(false);
1459
1949
  const [isReminderDropdownOpen, setIsReminderDropdownOpen] = useState3(false);
1460
- const [isGuestsDropdownOpen, setIsGuestsDropdownOpen] = useState3(false);
1461
- const fakeGuests = [
1462
- "alice@example.com",
1463
- "bob@example.com",
1464
- "charlie@example.com",
1465
- "diana@example.com",
1466
- "evan@example.com"
1467
- ];
1468
- const [selectedGuests, setSelectedGuests] = useState3([]);
1950
+ const [guestInput, setGuestInput] = useState3("");
1951
+ const [guestError, setGuestError] = useState3(false);
1469
1952
  useEffect3(() => {
1953
+ const defaultReminders = () => [{ id: "1", type: "notification", time: 30, label: reminderLabel(30, translationsRef.current) }];
1470
1954
  if (isOpen) {
1955
+ setGuestInput("");
1956
+ setGuestError(false);
1471
1957
  if (event) {
1472
1958
  setMode("view");
1473
1959
  setFormData({
@@ -1475,9 +1961,9 @@ var EventModal = ({
1475
1961
  start: new Date(event.start),
1476
1962
  end: new Date(event.end),
1477
1963
  attachments: event.attachments || [],
1478
- reminders: event.reminders || [{ id: "1", type: "notification", time: 30, label: "30 minutes before" }]
1964
+ reminders: event.reminders || defaultReminders(),
1965
+ guests: event.guests || []
1479
1966
  });
1480
- setSelectedGuests(event.guests || []);
1481
1967
  } else {
1482
1968
  setMode("create");
1483
1969
  const start = initialDate || /* @__PURE__ */ new Date();
@@ -1493,21 +1979,34 @@ var EventModal = ({
1493
1979
  color: "#3b82f6",
1494
1980
  calendarId: calendars?.[0]?.id,
1495
1981
  attachments: [],
1496
- reminders: [{ id: "1", type: "notification", time: 30, label: "30 minutes before" }]
1982
+ reminders: defaultReminders(),
1983
+ guests: []
1497
1984
  });
1498
- setSelectedGuests([]);
1499
1985
  }
1500
1986
  }
1501
1987
  }, [isOpen, event, initialDate, calendars]);
1502
- const toggleGuest = (email) => {
1503
- setSelectedGuests((prev) => {
1504
- const newGuests = prev.includes(email) ? prev.filter((e) => e !== email) : [...prev, email];
1505
- setFormData((curr) => ({ ...curr, guests: newGuests }));
1506
- return newGuests;
1507
- });
1988
+ const guests = formData.guests || [];
1989
+ const addGuests = (raw) => {
1990
+ const entries = raw.split(/[\s,;]+/).filter(Boolean);
1991
+ const valid = entries.filter((entry) => EMAIL.test(entry));
1992
+ const invalid = entries.filter((entry) => !EMAIL.test(entry));
1993
+ if (valid.length > 0) {
1994
+ setFormData((prev) => ({ ...prev, guests: mergeGuests(prev.guests || [], valid) }));
1995
+ }
1996
+ setGuestInput(invalid.join(", "));
1997
+ setGuestError(invalid.length > 0);
1998
+ return invalid.length === 0;
1999
+ };
2000
+ const removeGuest = (email) => {
2001
+ setFormData((prev) => ({ ...prev, guests: (prev.guests || []).filter((g) => g !== email) }));
1508
2002
  };
1509
2003
  const handleSubmit = (e) => {
1510
2004
  e.preventDefault();
2005
+ const pending = guestInput.split(/[\s,;]+/).filter(Boolean);
2006
+ if (pending.some((entry) => !EMAIL.test(entry))) {
2007
+ addGuests(guestInput);
2008
+ return;
2009
+ }
1511
2010
  let eventColor = formData.color;
1512
2011
  if (formData.calendarId && calendars) {
1513
2012
  const selectedCal = calendars.find((c) => c.id === formData.calendarId);
@@ -1515,8 +2014,10 @@ var EventModal = ({
1515
2014
  eventColor = selectedCal.color;
1516
2015
  }
1517
2016
  }
2017
+ const finalGuests = mergeGuests(guests, pending);
1518
2018
  onSave({
1519
2019
  ...formData,
2020
+ guests: finalGuests,
1520
2021
  color: eventColor,
1521
2022
  id: event?.id
1522
2023
  });
@@ -1530,7 +2031,7 @@ var EventModal = ({
1530
2031
  };
1531
2032
  const formatDateForInput = (date) => {
1532
2033
  if (!date) return "";
1533
- return format10(date, "yyyy-MM-dd'T'HH:mm");
2034
+ return format12(date, "yyyy-MM-dd'T'HH:mm");
1534
2035
  };
1535
2036
  const handleDateChange = (field, value) => {
1536
2037
  const date = new Date(value);
@@ -1550,12 +2051,12 @@ var EventModal = ({
1550
2051
  }
1551
2052
  }
1552
2053
  };
1553
- const addReminder = (minutes, label) => {
2054
+ const addReminder = (minutes) => {
1554
2055
  const newReminder = {
1555
- id: Math.random().toString(36).substr(2, 9),
2056
+ id: Math.random().toString(36).slice(2, 11),
1556
2057
  type: "notification",
1557
2058
  time: minutes,
1558
- label
2059
+ label: reminderLabel(minutes, t)
1559
2060
  };
1560
2061
  setFormData((prev) => ({
1561
2062
  ...prev,
@@ -1569,23 +2070,20 @@ var EventModal = ({
1569
2070
  reminders: prev.reminders?.filter((r) => r.id !== id)
1570
2071
  }));
1571
2072
  };
1572
- const handleDownload = (attachment) => {
1573
- const element = document.createElement("a");
1574
- const file = new Blob(["Dummy content for " + attachment.name], { type: "text/plain" });
1575
- element.href = URL.createObjectURL(file);
1576
- element.download = attachment.name;
1577
- document.body.appendChild(element);
1578
- element.click();
1579
- document.body.removeChild(element);
1580
- };
1581
2073
  const getDurationText = () => {
1582
2074
  if (!formData.start || !formData.end) return "";
1583
2075
  const mins = differenceInMinutes5(formData.end, formData.start);
1584
- if (mins < 60) return `${mins} min`;
2076
+ if (mins < 60) return interpolate(t.durationMinutes, { n: mins });
1585
2077
  const hours = Math.floor(mins / 60);
1586
2078
  const remainingMins = mins % 60;
1587
- if (remainingMins === 0) return `${hours} hr`;
1588
- return `${hours} hr ${remainingMins} min`;
2079
+ if (remainingMins === 0) return interpolate(t.durationHours, { n: hours });
2080
+ return interpolate(t.durationHoursMinutes, { h: hours, m: remainingMins });
2081
+ };
2082
+ const { time: timeFormat } = timeFormats(locale);
2083
+ const renderAttachment = (att) => {
2084
+ const content = /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement("div", { className: "p-2 bg-primary/10 rounded-lg" }, /* @__PURE__ */ React15.createElement(File, { className: "w-4 h-4 text-primary" })), /* @__PURE__ */ React15.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React15.createElement("p", { className: "text-sm font-medium text-foreground truncate" }, att.name), /* @__PURE__ */ React15.createElement("p", { className: "text-xs text-muted-foreground" }, att.size)), att.url && /* @__PURE__ */ React15.createElement(Download, { className: "w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" }));
2085
+ const className = "flex items-center gap-3 p-3 rounded-xl border border-border/30 bg-muted/20 transition-all group";
2086
+ return att.url && isSafeUrl(att.url) ? /* @__PURE__ */ React15.createElement("a", { key: att.id, href: att.url, target: "_blank", rel: "noopener noreferrer", download: att.name, className: cn(className, "hover:bg-muted/40 cursor-pointer") }, content) : /* @__PURE__ */ React15.createElement("div", { key: att.id, className }, content);
1589
2087
  };
1590
2088
  const renderViewMode = () => /* @__PURE__ */ React15.createElement("div", { className: "flex flex-col bg-background overflow-hidden w-full max-h-[90vh]" }, /* @__PURE__ */ React15.createElement(
1591
2089
  "div",
@@ -1601,12 +2099,13 @@ var EventModal = ({
1601
2099
  className: "w-3 h-3 rounded-full",
1602
2100
  style: { backgroundColor: event?.color || "#3b82f6" }
1603
2101
  }
1604
- ), /* @__PURE__ */ React15.createElement("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider" }, calendars?.find((c) => c.id === event?.calendarId)?.label || "Event")), /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React15.createElement(
2102
+ ), /* @__PURE__ */ React15.createElement("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider" }, calendars?.find((c) => c.id === event?.calendarId)?.label || t.event)), /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React15.createElement(
1605
2103
  "button",
1606
2104
  {
1607
2105
  onClick: () => setMode("edit"),
1608
2106
  className: "p-2 hover:bg-background/80 rounded-lg transition-all text-muted-foreground hover:text-foreground",
1609
- title: "Edit"
2107
+ title: t.edit,
2108
+ "aria-label": t.edit
1610
2109
  },
1611
2110
  /* @__PURE__ */ React15.createElement(Edit2, { className: "w-4 h-4" })
1612
2111
  ), /* @__PURE__ */ React15.createElement(
@@ -1614,7 +2113,8 @@ var EventModal = ({
1614
2113
  {
1615
2114
  onClick: handleDelete,
1616
2115
  className: "p-2 hover:bg-destructive/10 rounded-lg transition-all text-muted-foreground hover:text-destructive",
1617
- title: "Delete"
2116
+ title: t.delete,
2117
+ "aria-label": t.delete
1618
2118
  },
1619
2119
  /* @__PURE__ */ React15.createElement(Trash2, { className: "w-4 h-4" })
1620
2120
  ), /* @__PURE__ */ React15.createElement(
@@ -1622,21 +2122,25 @@ var EventModal = ({
1622
2122
  {
1623
2123
  onClick: onClose,
1624
2124
  className: "p-2 hover:bg-background/80 rounded-lg transition-all text-muted-foreground hover:text-foreground",
1625
- title: "Close"
2125
+ title: t.close,
2126
+ "aria-label": t.close
1626
2127
  },
1627
2128
  /* @__PURE__ */ React15.createElement(X2, { className: "w-4 h-4" })
1628
2129
  ))),
1629
- /* @__PURE__ */ React15.createElement("h1", { className: "text-2xl font-bold text-foreground mb-3 leading-tight" }, event?.title || "(No title)"),
1630
- /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-3 text-sm" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-background/60 backdrop-blur-sm rounded-lg border border-border/30" }, /* @__PURE__ */ React15.createElement(Calendar2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "font-medium" }, event?.start && format10(new Date(event.start), "EEE, MMM d"))), /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-background/60 backdrop-blur-sm rounded-lg border border-border/30" }, /* @__PURE__ */ React15.createElement(Clock2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "font-medium" }, event?.start && format10(new Date(event.start), "h:mm a"), " \u2013 ", event?.end && format10(new Date(event.end), "h:mm a"))))
2130
+ /* @__PURE__ */ React15.createElement("h1", { className: "text-2xl font-bold text-foreground mb-3 leading-tight" }, event?.title || t.noTitle),
2131
+ /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-3 text-sm" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-background/60 backdrop-blur-sm rounded-lg border border-border/30" }, /* @__PURE__ */ React15.createElement(Calendar2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "font-medium" }, event?.start && format12(new Date(event.start), "EEE, MMM d", { locale }))), /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-background/60 backdrop-blur-sm rounded-lg border border-border/30" }, /* @__PURE__ */ React15.createElement(Clock2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "font-medium" }, event?.start && format12(new Date(event.start), timeFormat, { locale }), " \u2013 ", event?.end && format12(new Date(event.end), timeFormat, { locale }))))
1631
2132
  ), /* @__PURE__ */ React15.createElement("div", { className: "px-6 pb-6 space-y-4 overflow-y-auto flex-1" }, event?.location && /* @__PURE__ */ React15.createElement(
1632
- motion2.div,
2133
+ motion2.a,
1633
2134
  {
2135
+ href: mapsUrl(event.location),
2136
+ target: "_blank",
2137
+ rel: "noopener noreferrer",
1634
2138
  initial: { opacity: 0, y: 10 },
1635
2139
  animate: { opacity: 1, y: 0 },
1636
2140
  className: "flex items-start gap-3 p-4 rounded-2xl bg-muted/30 border border-border/30 group hover:bg-muted/50 transition-all cursor-pointer"
1637
2141
  },
1638
2142
  /* @__PURE__ */ React15.createElement("div", { className: "p-2 bg-primary/10 rounded-xl" }, /* @__PURE__ */ React15.createElement(MapPin, { className: "w-5 h-5 text-primary" })),
1639
- /* @__PURE__ */ React15.createElement("div", { className: "flex-1" }, /* @__PURE__ */ React15.createElement("p", { className: "text-sm font-medium text-foreground" }, event.location), /* @__PURE__ */ React15.createElement("p", { className: "text-xs text-muted-foreground mt-0.5" }, "Click to open in maps")),
2143
+ /* @__PURE__ */ React15.createElement("div", { className: "flex-1" }, /* @__PURE__ */ React15.createElement("p", { className: "text-sm font-medium text-foreground" }, event.location), /* @__PURE__ */ React15.createElement("p", { className: "text-xs text-muted-foreground mt-0.5" }, t.openInMaps)),
1640
2144
  /* @__PURE__ */ React15.createElement(ExternalLink, { className: "w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" })
1641
2145
  ), event?.guests && event.guests.length > 0 && /* @__PURE__ */ React15.createElement(
1642
2146
  motion2.div,
@@ -1646,18 +2150,18 @@ var EventModal = ({
1646
2150
  transition: { delay: 0.05 },
1647
2151
  className: "space-y-3"
1648
2152
  },
1649
- /* @__PURE__ */ React15.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(Users2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm font-medium text-foreground" }, event.guests.length, " ", event.guests.length > 1 ? "Guests" : "Guest"))),
1650
- /* @__PURE__ */ React15.createElement("div", { className: "flex flex-wrap gap-2" }, event.guests.map((email, index) => /* @__PURE__ */ React15.createElement(
2153
+ /* @__PURE__ */ React15.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(Users2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm font-medium text-foreground" }, event.guests.length, " ", event.guests.length > 1 ? t.guests : t.guestSingular))),
2154
+ /* @__PURE__ */ React15.createElement("div", { className: "flex flex-wrap gap-2" }, event.guests.map((email) => /* @__PURE__ */ React15.createElement(
1651
2155
  "div",
1652
2156
  {
1653
- key: index,
1654
- className: "flex items-center gap-2 px-3 py-2 rounded-xl bg-muted/30 border border-border/30 hover:bg-muted/50 transition-all cursor-pointer group"
2157
+ key: email,
2158
+ className: "flex items-center gap-2 px-3 py-2 rounded-xl bg-muted/30 border border-border/30"
1655
2159
  },
1656
2160
  /* @__PURE__ */ React15.createElement(
1657
2161
  "div",
1658
2162
  {
1659
2163
  className: "w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold text-white",
1660
- style: { backgroundColor: `hsl(${index * 60}, 70%, 50%)` }
2164
+ style: { backgroundColor: `hsl(${avatarHue(email)}, 70%, 50%)` }
1661
2165
  },
1662
2166
  email[0].toUpperCase()
1663
2167
  ),
@@ -1678,7 +2182,7 @@ var EventModal = ({
1678
2182
  key: reminder.id,
1679
2183
  className: "text-xs font-medium px-2.5 py-1 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400"
1680
2184
  },
1681
- reminder.label || `${reminder.time} min before`
2185
+ reminder.label || reminderLabel(reminder.time, t)
1682
2186
  )))
1683
2187
  ), event?.description && /* @__PURE__ */ React15.createElement(
1684
2188
  motion2.div,
@@ -1688,7 +2192,7 @@ var EventModal = ({
1688
2192
  transition: { delay: 0.15 },
1689
2193
  className: "space-y-2"
1690
2194
  },
1691
- /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(AlignLeft, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm font-medium text-foreground" }, "Notes")),
2195
+ /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(AlignLeft, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm font-medium text-foreground" }, t.notesLabel)),
1692
2196
  /* @__PURE__ */ React15.createElement("div", { className: "text-sm text-foreground/80 whitespace-pre-wrap leading-relaxed pl-6 p-3 rounded-xl bg-muted/20 border border-border/30" }, event.description)
1693
2197
  ), event?.attachments && event.attachments.length > 0 && /* @__PURE__ */ React15.createElement(
1694
2198
  motion2.div,
@@ -1698,25 +2202,16 @@ var EventModal = ({
1698
2202
  transition: { delay: 0.2 },
1699
2203
  className: "space-y-2"
1700
2204
  },
1701
- /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(Paperclip2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm font-medium text-foreground" }, "Attachments")),
1702
- /* @__PURE__ */ React15.createElement("div", { className: "grid gap-2" }, event.attachments.map((att) => /* @__PURE__ */ React15.createElement(
1703
- "div",
1704
- {
1705
- key: att.id,
1706
- onClick: () => handleDownload(att),
1707
- className: "flex items-center gap-3 p-3 rounded-xl border border-border/30 bg-muted/20 hover:bg-muted/40 transition-all cursor-pointer group"
1708
- },
1709
- /* @__PURE__ */ React15.createElement("div", { className: "p-2 bg-primary/10 rounded-lg" }, /* @__PURE__ */ React15.createElement(File, { className: "w-4 h-4 text-primary" })),
1710
- /* @__PURE__ */ React15.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React15.createElement("p", { className: "text-sm font-medium text-foreground truncate" }, att.name), /* @__PURE__ */ React15.createElement("p", { className: "text-xs text-muted-foreground" }, att.size)),
1711
- /* @__PURE__ */ React15.createElement(Download, { className: "w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" })
1712
- )))
2205
+ /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(Paperclip2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm font-medium text-foreground" }, t.attachments)),
2206
+ /* @__PURE__ */ React15.createElement("div", { className: "grid gap-2" }, event.attachments.map(renderAttachment))
1713
2207
  )));
1714
2208
  const renderEditMode = () => /* @__PURE__ */ React15.createElement("form", { onSubmit: handleSubmit, className: "flex flex-col bg-background overflow-hidden w-full max-h-[85vh]" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center justify-between px-5 py-4 border-b border-border/50 shrink-0" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React15.createElement(
1715
2209
  "button",
1716
2210
  {
1717
2211
  type: "button",
1718
2212
  onClick: () => setIsColorPickerOpen(!isColorPickerOpen),
1719
- className: "relative p-1"
2213
+ className: "relative p-1",
2214
+ "aria-label": t.eventColor
1720
2215
  },
1721
2216
  /* @__PURE__ */ React15.createElement(
1722
2217
  "div",
@@ -1733,6 +2228,7 @@ var EventModal = ({
1733
2228
  {
1734
2229
  key: color,
1735
2230
  type: "button",
2231
+ "aria-label": color,
1736
2232
  onClick: () => {
1737
2233
  setFormData((prev) => ({ ...prev, color }));
1738
2234
  setIsColorPickerOpen(false);
@@ -1744,12 +2240,13 @@ var EventModal = ({
1744
2240
  style: { backgroundColor: color, color }
1745
2241
  }
1746
2242
  )))
1747
- ), /* @__PURE__ */ React15.createElement("h2", { className: "text-lg font-semibold text-foreground" }, mode === "edit" ? "Edit Event" : "New Event")), /* @__PURE__ */ React15.createElement(
2243
+ ), /* @__PURE__ */ React15.createElement("h2", { className: "text-lg font-semibold text-foreground" }, mode === "edit" ? t.editEvent : t.newEvent)), /* @__PURE__ */ React15.createElement(
1748
2244
  "button",
1749
2245
  {
1750
2246
  type: "button",
1751
2247
  onClick: onClose,
1752
- className: "p-2 hover:bg-accent rounded-xl transition-all text-muted-foreground hover:text-foreground"
2248
+ className: "p-2 hover:bg-accent rounded-xl transition-all text-muted-foreground hover:text-foreground",
2249
+ "aria-label": t.close
1753
2250
  },
1754
2251
  /* @__PURE__ */ React15.createElement(X2, { className: "w-4 h-4" })
1755
2252
  )), /* @__PURE__ */ React15.createElement("div", { className: "flex-1 overflow-y-auto" }, /* @__PURE__ */ React15.createElement("div", { className: "px-5 py-4 space-y-5" }, /* @__PURE__ */ React15.createElement("div", null, /* @__PURE__ */ React15.createElement(
@@ -1761,25 +2258,28 @@ var EventModal = ({
1761
2258
  className: "w-full text-xl font-semibold border-0 border-b-2 border-transparent focus:border-primary bg-transparent px-0 py-2 placeholder-muted-foreground/50 transition-all text-foreground focus:outline-none",
1762
2259
  value: formData.title,
1763
2260
  onChange: (e) => setFormData({ ...formData, title: e.target.value }),
1764
- placeholder: "Add title"
2261
+ placeholder: t.title,
2262
+ "aria-label": t.title
1765
2263
  }
1766
- )), /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-3 p-4 rounded-2xl bg-muted/20 border border-border/30" }, /* @__PURE__ */ React15.createElement("div", { className: "p-2 bg-primary/10 rounded-xl shrink-0" }, /* @__PURE__ */ React15.createElement(Clock2, { className: "w-5 h-5 text-primary" })), /* @__PURE__ */ React15.createElement("div", { className: "flex-1 grid grid-cols-2 gap-3" }, /* @__PURE__ */ React15.createElement("div", null, /* @__PURE__ */ React15.createElement("label", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1 block" }, "Start"), /* @__PURE__ */ React15.createElement(
2264
+ )), /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-3 p-4 rounded-2xl bg-muted/20 border border-border/30" }, /* @__PURE__ */ React15.createElement("div", { className: "p-2 bg-primary/10 rounded-xl shrink-0" }, /* @__PURE__ */ React15.createElement(Clock2, { className: "w-5 h-5 text-primary" })), /* @__PURE__ */ React15.createElement("div", { className: "flex-1 grid grid-cols-2 gap-3" }, /* @__PURE__ */ React15.createElement("div", null, /* @__PURE__ */ React15.createElement("label", { htmlFor: "calendarkit-event-start", className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1 block" }, t.start), /* @__PURE__ */ React15.createElement(
1767
2265
  "input",
1768
2266
  {
2267
+ id: "calendarkit-event-start",
1769
2268
  type: "datetime-local",
1770
2269
  className: "w-full bg-background border border-border/50 rounded-xl px-3 py-2 text-sm font-medium text-foreground focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all",
1771
2270
  value: formatDateForInput(formData.start),
1772
2271
  onChange: (e) => handleDateChange("start", e.target.value)
1773
2272
  }
1774
- )), /* @__PURE__ */ React15.createElement("div", null, /* @__PURE__ */ React15.createElement("label", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1 block" }, "End"), /* @__PURE__ */ React15.createElement(
2273
+ )), /* @__PURE__ */ React15.createElement("div", null, /* @__PURE__ */ React15.createElement("label", { htmlFor: "calendarkit-event-end", className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1 block" }, t.end), /* @__PURE__ */ React15.createElement(
1775
2274
  "input",
1776
2275
  {
2276
+ id: "calendarkit-event-end",
1777
2277
  type: "datetime-local",
1778
2278
  className: "w-full bg-background border border-border/50 rounded-xl px-3 py-2 text-sm font-medium text-foreground focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all",
1779
2279
  value: formatDateForInput(formData.end),
1780
2280
  onChange: (e) => handleDateChange("end", e.target.value)
1781
2281
  }
1782
- )))), getDurationText() && /* @__PURE__ */ React15.createElement("div", { className: "flex justify-center" }, /* @__PURE__ */ React15.createElement("span", { className: "text-xs font-medium px-3 py-1 rounded-full bg-primary/10 text-primary" }, getDurationText())), /* @__PURE__ */ React15.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(Bell2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm font-medium text-foreground" }, "Reminders")), /* @__PURE__ */ React15.createElement("div", { className: "relative" }, /* @__PURE__ */ React15.createElement(
2282
+ )))), getDurationText() && /* @__PURE__ */ React15.createElement("div", { className: "flex justify-center" }, /* @__PURE__ */ React15.createElement("span", { className: "text-xs font-medium px-3 py-1 rounded-full bg-primary/10 text-primary" }, getDurationText())), /* @__PURE__ */ React15.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(Bell2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm font-medium text-foreground" }, t.reminders)), /* @__PURE__ */ React15.createElement("div", { className: "relative" }, /* @__PURE__ */ React15.createElement(
1783
2283
  "button",
1784
2284
  {
1785
2285
  type: "button",
@@ -1787,16 +2287,16 @@ var EventModal = ({
1787
2287
  className: "flex items-center gap-1 text-xs font-medium text-primary hover:text-primary/80 transition-colors"
1788
2288
  },
1789
2289
  /* @__PURE__ */ React15.createElement(Plus3, { className: "w-3.5 h-3.5" }),
1790
- "Add"
1791
- ), isReminderDropdownOpen && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement("div", { className: "fixed inset-0 z-40", onClick: () => setIsReminderDropdownOpen(false) }), /* @__PURE__ */ React15.createElement("div", { className: "absolute right-0 top-full mt-1 w-48 bg-background border border-border rounded-xl shadow-xl z-50 py-1 max-h-60 overflow-y-auto" }, REMINDER_OPTIONS.map((option) => /* @__PURE__ */ React15.createElement(
2290
+ t.add
2291
+ ), isReminderDropdownOpen && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement("div", { className: "fixed inset-0 z-40", onClick: () => setIsReminderDropdownOpen(false) }), /* @__PURE__ */ React15.createElement("div", { className: "absolute right-0 top-full mt-1 w-48 bg-background border border-border rounded-xl shadow-xl z-50 py-1 max-h-60 overflow-y-auto" }, REMINDER_OPTIONS.map((minutes) => /* @__PURE__ */ React15.createElement(
1792
2292
  "button",
1793
2293
  {
1794
- key: option.value,
2294
+ key: minutes,
1795
2295
  type: "button",
1796
- onClick: () => addReminder(option.value, option.label),
2296
+ onClick: () => addReminder(minutes),
1797
2297
  className: "w-full px-3 py-2 text-left text-sm hover:bg-accent transition-colors"
1798
2298
  },
1799
- option.label
2299
+ reminderLabel(minutes, t)
1800
2300
  )))))), formData.reminders && formData.reminders.length > 0 && /* @__PURE__ */ React15.createElement("div", { className: "flex flex-wrap gap-2" }, formData.reminders.map((reminder) => /* @__PURE__ */ React15.createElement(
1801
2301
  "div",
1802
2302
  {
@@ -1804,45 +2304,44 @@ var EventModal = ({
1804
2304
  className: "flex items-center gap-2 px-3 py-1.5 rounded-full bg-amber-500/10 border border-amber-500/20"
1805
2305
  },
1806
2306
  /* @__PURE__ */ React15.createElement(BellRing, { className: "w-3.5 h-3.5 text-amber-500" }),
1807
- /* @__PURE__ */ React15.createElement("span", { className: "text-xs font-medium text-amber-600 dark:text-amber-400" }, reminder.label),
2307
+ /* @__PURE__ */ React15.createElement("span", { className: "text-xs font-medium text-amber-600 dark:text-amber-400" }, reminder.label || reminderLabel(reminder.time, t)),
1808
2308
  /* @__PURE__ */ React15.createElement(
1809
2309
  "button",
1810
2310
  {
1811
2311
  type: "button",
1812
2312
  onClick: () => removeReminder(reminder.id),
1813
- className: "p-0.5 hover:bg-amber-500/20 rounded-full transition-colors"
2313
+ className: "p-0.5 hover:bg-amber-500/20 rounded-full transition-colors",
2314
+ "aria-label": t.removeReminder
1814
2315
  },
1815
2316
  /* @__PURE__ */ React15.createElement(X2, { className: "w-3 h-3 text-amber-500" })
1816
2317
  )
1817
- )))), /* @__PURE__ */ React15.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(Users2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm font-medium text-foreground" }, "Guests")), /* @__PURE__ */ React15.createElement("div", { className: "relative" }, /* @__PURE__ */ React15.createElement(
1818
- "button",
1819
- {
1820
- type: "button",
1821
- onClick: () => setIsGuestsDropdownOpen(!isGuestsDropdownOpen),
1822
- className: "w-full flex items-center justify-between px-4 py-3 bg-muted/20 border border-border/30 rounded-xl text-sm text-left hover:bg-muted/30 transition-all"
1823
- },
1824
- /* @__PURE__ */ React15.createElement("span", { className: selectedGuests.length > 0 ? "text-foreground font-medium" : "text-muted-foreground" }, selectedGuests.length > 0 ? `${selectedGuests.length} guest${selectedGuests.length > 1 ? "s" : ""} added` : "Add guests"),
1825
- /* @__PURE__ */ React15.createElement(ChevronDown2, { className: cn("w-4 h-4 text-muted-foreground transition-transform", isGuestsDropdownOpen && "rotate-180") })
1826
- ), isGuestsDropdownOpen && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement("div", { className: "fixed inset-0 z-40", onClick: () => setIsGuestsDropdownOpen(false) }), /* @__PURE__ */ React15.createElement("div", { className: "absolute z-50 w-full mt-2 bg-background border border-border rounded-xl shadow-xl max-h-48 overflow-y-auto" }, fakeGuests.map((email) => /* @__PURE__ */ React15.createElement(
1827
- "div",
2318
+ )))), /* @__PURE__ */ React15.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(Users2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("label", { htmlFor: "calendarkit-event-guest", className: "text-sm font-medium text-foreground" }, t.guests)), /* @__PURE__ */ React15.createElement(
2319
+ "input",
1828
2320
  {
1829
- key: email,
1830
- className: "px-4 py-3 hover:bg-accent cursor-pointer flex items-center gap-3 transition-colors",
1831
- onClick: () => toggleGuest(email)
1832
- },
1833
- /* @__PURE__ */ React15.createElement("div", { className: cn(
1834
- "w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all",
1835
- selectedGuests.includes(email) ? "bg-primary border-primary" : "border-border"
1836
- ) }, selectedGuests.includes(email) && /* @__PURE__ */ React15.createElement(Check, { className: "w-3 h-3 text-primary-foreground" })),
1837
- /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(
1838
- "div",
1839
- {
1840
- className: "w-7 h-7 rounded-full flex items-center justify-center text-xs font-semibold text-white",
1841
- style: { backgroundColor: `hsl(${fakeGuests.indexOf(email) * 60}, 70%, 50%)` }
2321
+ id: "calendarkit-event-guest",
2322
+ type: "text",
2323
+ inputMode: "email",
2324
+ autoComplete: "email",
2325
+ value: guestInput,
2326
+ onChange: (e) => {
2327
+ setGuestInput(e.target.value);
2328
+ setGuestError(false);
1842
2329
  },
1843
- email[0].toUpperCase()
1844
- ), /* @__PURE__ */ React15.createElement("span", { className: "text-sm" }, email))
1845
- ))))), selectedGuests.length > 0 && /* @__PURE__ */ React15.createElement("div", { className: "flex flex-wrap gap-2 mt-2" }, selectedGuests.map((email) => /* @__PURE__ */ React15.createElement(
2330
+ onKeyDown: (e) => {
2331
+ if (e.key === "Enter" || e.key === ",") {
2332
+ e.preventDefault();
2333
+ addGuests(guestInput);
2334
+ }
2335
+ },
2336
+ onBlur: () => guestInput.trim() && addGuests(guestInput),
2337
+ placeholder: t.addGuestPlaceholder,
2338
+ "aria-invalid": guestError,
2339
+ className: cn(
2340
+ "w-full px-4 py-3 bg-muted/20 border rounded-xl text-sm text-foreground placeholder-muted-foreground focus:outline-none focus:border-primary transition-all",
2341
+ guestError ? "border-destructive" : "border-border/30"
2342
+ )
2343
+ }
2344
+ ), guestError && /* @__PURE__ */ React15.createElement("p", { role: "alert", className: "text-xs text-destructive" }, t.invalidEmail), guests.length > 0 && /* @__PURE__ */ React15.createElement("div", { className: "flex flex-wrap gap-2 mt-2" }, guests.map((email) => /* @__PURE__ */ React15.createElement(
1846
2345
  "div",
1847
2346
  {
1848
2347
  key: email,
@@ -1852,17 +2351,18 @@ var EventModal = ({
1852
2351
  "div",
1853
2352
  {
1854
2353
  className: "w-6 h-6 rounded-full flex items-center justify-center text-xs font-semibold text-white",
1855
- style: { backgroundColor: `hsl(${fakeGuests.indexOf(email) * 60}, 70%, 50%)` }
2354
+ style: { backgroundColor: `hsl(${avatarHue(email)}, 70%, 50%)` }
1856
2355
  },
1857
2356
  email[0].toUpperCase()
1858
2357
  ),
1859
- /* @__PURE__ */ React15.createElement("span", { className: "text-xs font-medium text-foreground" }, email.split("@")[0]),
2358
+ /* @__PURE__ */ React15.createElement("span", { className: "text-xs font-medium text-foreground" }, email),
1860
2359
  /* @__PURE__ */ React15.createElement(
1861
2360
  "button",
1862
2361
  {
1863
2362
  type: "button",
1864
- onClick: () => toggleGuest(email),
1865
- className: "p-0.5 hover:bg-destructive/10 rounded-full transition-colors"
2363
+ onClick: () => removeGuest(email),
2364
+ className: "p-0.5 hover:bg-destructive/10 rounded-full transition-colors",
2365
+ "aria-label": interpolate(t.removeGuest, { email })
1866
2366
  },
1867
2367
  /* @__PURE__ */ React15.createElement(X2, { className: "w-3 h-3 text-muted-foreground" })
1868
2368
  )
@@ -1871,15 +2371,17 @@ var EventModal = ({
1871
2371
  {
1872
2372
  type: "text",
1873
2373
  className: "flex-1 bg-transparent text-sm text-foreground placeholder-muted-foreground focus:outline-none",
1874
- placeholder: "Add location",
2374
+ placeholder: t.addLocation,
2375
+ "aria-label": t.addLocation,
1875
2376
  value: formData.location || "",
1876
2377
  onChange: (e) => setFormData({ ...formData, location: e.target.value })
1877
2378
  }
1878
- )), /* @__PURE__ */ React15.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(AlignLeft, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm font-medium text-foreground" }, "Notes")), /* @__PURE__ */ React15.createElement(
2379
+ )), /* @__PURE__ */ React15.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(AlignLeft, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm font-medium text-foreground" }, t.notesLabel)), /* @__PURE__ */ React15.createElement(
1879
2380
  "textarea",
1880
2381
  {
1881
2382
  className: "w-full bg-muted/20 border border-border/30 rounded-xl px-4 py-3 text-sm min-h-[100px] resize-none text-foreground placeholder-muted-foreground focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all",
1882
- placeholder: "Add description or notes...",
2383
+ placeholder: t.addNotes,
2384
+ "aria-label": t.notesLabel,
1883
2385
  value: formData.description || "",
1884
2386
  onChange: (e) => setFormData({ ...formData, description: e.target.value })
1885
2387
  }
@@ -1887,6 +2389,7 @@ var EventModal = ({
1887
2389
  "select",
1888
2390
  {
1889
2391
  className: "flex-1 bg-transparent text-sm text-foreground focus:outline-none cursor-pointer",
2392
+ "aria-label": t.repeat,
1890
2393
  value: formData.recurrence?.freq || "",
1891
2394
  onChange: (e) => {
1892
2395
  if (e.target.value === "") {
@@ -1902,12 +2405,12 @@ var EventModal = ({
1902
2405
  }
1903
2406
  }
1904
2407
  },
1905
- /* @__PURE__ */ React15.createElement("option", { value: "" }, "Does not repeat"),
1906
- /* @__PURE__ */ React15.createElement("option", { value: "DAILY" }, "Daily"),
1907
- /* @__PURE__ */ React15.createElement("option", { value: "WEEKLY" }, "Weekly"),
1908
- /* @__PURE__ */ React15.createElement("option", { value: "MONTHLY" }, "Monthly"),
1909
- /* @__PURE__ */ React15.createElement("option", { value: "YEARLY" }, "Yearly")
1910
- )), /* @__PURE__ */ React15.createElement("div", { className: "flex items-center justify-between p-3 rounded-xl bg-muted/20 border border-border/30" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(Tag, { className: "w-5 h-5 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm text-foreground" }, "Calendar")), /* @__PURE__ */ React15.createElement("div", { className: "relative" }, /* @__PURE__ */ React15.createElement(
2408
+ /* @__PURE__ */ React15.createElement("option", { value: "" }, t.doesNotRepeat),
2409
+ /* @__PURE__ */ React15.createElement("option", { value: "DAILY" }, t.daily),
2410
+ /* @__PURE__ */ React15.createElement("option", { value: "WEEKLY" }, t.weekly),
2411
+ /* @__PURE__ */ React15.createElement("option", { value: "MONTHLY" }, t.monthly),
2412
+ /* @__PURE__ */ React15.createElement("option", { value: "YEARLY" }, t.yearly)
2413
+ )), calendars && calendars.length > 0 && /* @__PURE__ */ React15.createElement("div", { className: "flex items-center justify-between p-3 rounded-xl bg-muted/20 border border-border/30" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(Tag, { className: "w-5 h-5 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "text-sm text-foreground" }, t.calendar)), /* @__PURE__ */ React15.createElement("div", { className: "relative" }, /* @__PURE__ */ React15.createElement(
1911
2414
  "button",
1912
2415
  {
1913
2416
  type: "button",
@@ -1918,12 +2421,12 @@ var EventModal = ({
1918
2421
  "div",
1919
2422
  {
1920
2423
  className: "w-3 h-3 rounded-full",
1921
- style: { backgroundColor: calendars?.find((c) => c.id === formData.calendarId)?.color || "#3b82f6" }
2424
+ style: { backgroundColor: calendars.find((c) => c.id === formData.calendarId)?.color || "#3b82f6" }
1922
2425
  }
1923
2426
  ),
1924
- /* @__PURE__ */ React15.createElement("span", { className: "font-medium" }, calendars?.find((c) => c.id === formData.calendarId)?.label || "Select"),
2427
+ /* @__PURE__ */ React15.createElement("span", { className: "font-medium" }, calendars.find((c) => c.id === formData.calendarId)?.label || t.select),
1925
2428
  /* @__PURE__ */ React15.createElement(ChevronDown2, { className: "w-3.5 h-3.5 text-muted-foreground" })
1926
- ), isCalendarDropdownOpen && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement("div", { className: "fixed inset-0 z-40", onClick: () => setIsCalendarDropdownOpen(false) }), /* @__PURE__ */ React15.createElement("div", { className: "absolute right-0 bottom-full mb-2 w-48 bg-background border border-border rounded-xl shadow-xl z-50 py-1" }, calendars?.map((cal) => /* @__PURE__ */ React15.createElement(
2429
+ ), isCalendarDropdownOpen && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement("div", { className: "fixed inset-0 z-40", onClick: () => setIsCalendarDropdownOpen(false) }), /* @__PURE__ */ React15.createElement("div", { className: "absolute right-0 bottom-full mb-2 w-48 bg-background border border-border rounded-xl shadow-xl z-50 py-1" }, calendars.map((cal) => /* @__PURE__ */ React15.createElement(
1927
2430
  "button",
1928
2431
  {
1929
2432
  key: cal.id,
@@ -1949,7 +2452,7 @@ var EventModal = ({
1949
2452
  className: "flex items-center gap-2 px-4 py-2 text-destructive hover:bg-destructive/10 rounded-xl transition-all text-sm font-medium"
1950
2453
  },
1951
2454
  /* @__PURE__ */ React15.createElement(Trash2, { className: "w-4 h-4" }),
1952
- "Delete"
2455
+ t.delete
1953
2456
  ), mode === "create" && /* @__PURE__ */ React15.createElement("div", null), /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15.createElement(
1954
2457
  "button",
1955
2458
  {
@@ -1957,14 +2460,14 @@ var EventModal = ({
1957
2460
  onClick: onClose,
1958
2461
  className: "px-4 py-2.5 text-muted-foreground hover:text-foreground font-medium text-sm rounded-xl transition-all hover:bg-accent"
1959
2462
  },
1960
- "Cancel"
2463
+ t.cancel
1961
2464
  ), /* @__PURE__ */ React15.createElement(
1962
2465
  "button",
1963
2466
  {
1964
2467
  type: "submit",
1965
2468
  className: "px-6 py-2.5 bg-primary hover:bg-primary/90 text-primary-foreground font-semibold text-sm rounded-xl shadow-md transition-all hover:shadow-lg hover:shadow-primary/20 active:scale-95"
1966
2469
  },
1967
- mode === "edit" ? "Save Changes" : "Create Event"
2470
+ mode === "edit" ? t.saveChanges : t.createEvent
1968
2471
  ))));
1969
2472
  return /* @__PURE__ */ React15.createElement(Modal, { isOpen, onClose, hideHeader: true, className: "p-0 overflow-hidden max-w-lg rounded-2xl shadow-2xl" }, /* @__PURE__ */ React15.createElement(AnimatePresence, { mode: "wait" }, mode === "view" && event ? /* @__PURE__ */ React15.createElement(
1970
2473
  motion2.div,
@@ -1992,7 +2495,7 @@ var EventModal = ({
1992
2495
  // src/components/Skeleton.tsx
1993
2496
  import React16 from "react";
1994
2497
  var Skeleton = ({ className, style }) => /* @__PURE__ */ React16.createElement("div", { className: cn("animate-pulse bg-muted/40 rounded", className), style });
1995
- var MonthViewSkeleton = () => /* @__PURE__ */ React16.createElement("div", { className: "h-full bg-background border border-border/50 rounded-2xl overflow-hidden" }, /* @__PURE__ */ React16.createElement("div", { className: "grid grid-cols-7 border-b border-border/50 bg-muted/10" }, Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ React16.createElement("div", { key: i, className: "py-3 px-2 text-center border-r border-border/30 last:border-r-0" }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-4 w-8 mx-auto" })))), /* @__PURE__ */ React16.createElement("div", { className: "grid grid-cols-7 flex-1" }, Array.from({ length: 35 }).map((_, i) => /* @__PURE__ */ React16.createElement("div", { key: i, className: "min-h-[120px] border-r border-b border-border/30 p-2" }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-4 w-6 mb-2" }), /* @__PURE__ */ React16.createElement("div", { className: "space-y-1" }, Math.random() > 0.5 && /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-5 w-full rounded-md" }), Math.random() > 0.7 && /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-5 w-3/4 rounded-md" }))))));
2498
+ var MonthViewSkeleton = () => /* @__PURE__ */ React16.createElement("div", { className: "h-full bg-background border border-border/50 rounded-2xl overflow-hidden" }, /* @__PURE__ */ React16.createElement("div", { className: "grid grid-cols-7 border-b border-border/50 bg-muted/10" }, Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ React16.createElement("div", { key: i, className: "py-3 px-2 text-center border-r border-border/30 last:border-r-0" }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-4 w-8 mx-auto" })))), /* @__PURE__ */ React16.createElement("div", { className: "grid grid-cols-7 flex-1" }, Array.from({ length: 35 }).map((_, i) => /* @__PURE__ */ React16.createElement("div", { key: i, className: "min-h-[120px] border-r border-b border-border/30 p-2" }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-4 w-6 mb-2" }), /* @__PURE__ */ React16.createElement("div", { className: "space-y-1" }, i * 7 % 10 > 4 && /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-5 w-full rounded-md" }), i * 3 % 10 > 6 && /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-5 w-3/4 rounded-md" }))))));
1996
2499
  var WeekViewSkeleton = () => /* @__PURE__ */ React16.createElement("div", { className: "h-full bg-background border border-border/50 rounded-2xl overflow-hidden" }, /* @__PURE__ */ React16.createElement("div", { className: "flex border-b border-border/50 bg-muted/10" }, /* @__PURE__ */ React16.createElement("div", { className: "w-16 p-3 border-r border-border/30" }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-8 w-10 mx-auto" })), /* @__PURE__ */ React16.createElement("div", { className: "flex-1 grid grid-cols-7" }, Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ React16.createElement("div", { key: i, className: "py-3 px-2 text-center border-r border-border/30 last:border-r-0" }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-3 w-8 mx-auto mb-1" }), /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-8 w-8 mx-auto rounded-xl" }))))), /* @__PURE__ */ React16.createElement("div", { className: "flex flex-1", style: { height: "600px" } }, /* @__PURE__ */ React16.createElement("div", { className: "w-16 border-r border-border/30" }, Array.from({ length: 10 }).map((_, i) => /* @__PURE__ */ React16.createElement("div", { key: i, className: "h-[60px] relative" }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-4 w-10 absolute right-2 -translate-y-1/2" })))), /* @__PURE__ */ React16.createElement("div", { className: "flex-1 grid grid-cols-7 relative" }, Array.from({ length: 7 }).map((_, colIdx) => /* @__PURE__ */ React16.createElement("div", { key: colIdx, className: "relative border-r border-border/30 last:border-r-0" }, Array.from({ length: 10 }).map((_2, rowIdx) => /* @__PURE__ */ React16.createElement("div", { key: rowIdx, className: "h-[60px] border-b border-dashed border-border/20" })), Math.random() > 0.3 && /* @__PURE__ */ React16.createElement(
1997
2500
  Skeleton,
1998
2501
  {
@@ -2021,7 +2524,7 @@ var DayViewSkeleton = () => /* @__PURE__ */ React16.createElement("div", { class
2021
2524
  var AgendaViewSkeleton = () => /* @__PURE__ */ React16.createElement("div", { className: "h-full bg-background border border-border/50 rounded-2xl overflow-hidden p-6" }, /* @__PURE__ */ React16.createElement("div", { className: "space-y-6" }, Array.from({ length: 4 }).map((_, dayIdx) => /* @__PURE__ */ React16.createElement("div", { key: dayIdx }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-5 w-40 mb-4" }), /* @__PURE__ */ React16.createElement("div", { className: "space-y-3" }, Array.from({ length: 2 + Math.floor(Math.random() * 3) }).map((_2, eventIdx) => /* @__PURE__ */ React16.createElement("div", { key: eventIdx, className: "flex items-center gap-4 p-3 rounded-xl bg-muted/10" }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-10 w-10 rounded-lg" }), /* @__PURE__ */ React16.createElement("div", { className: "flex-1 space-y-2" }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-4 w-3/4" }), /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-3 w-1/2" })))))))));
2022
2525
 
2023
2526
  // src/components/ContextMenu.tsx
2024
- import React17, { useState as useState4, useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4 } from "react";
2527
+ import React17, { useState as useState4, useCallback as useCallback2, useEffect as useEffect4, useRef as useRef5 } from "react";
2025
2528
  import { Edit3, Trash2 as Trash22, Copy } from "lucide-react";
2026
2529
  import { AnimatePresence as AnimatePresence2, motion as motion3 } from "framer-motion";
2027
2530
  var EventContextMenu = ({
@@ -2034,7 +2537,7 @@ var EventContextMenu = ({
2034
2537
  customActions = [],
2035
2538
  translations
2036
2539
  }) => {
2037
- const menuRef = useRef4(null);
2540
+ const menuRef = useRef5(null);
2038
2541
  useEffect4(() => {
2039
2542
  const handleKeyDown = (e) => {
2040
2543
  if (e.key === "Escape") {
@@ -2134,7 +2637,7 @@ var EventContextMenu = ({
2134
2637
  };
2135
2638
  var useEventContextMenu = () => {
2136
2639
  const [contextMenu, setContextMenu] = useState4({ event: null, position: null });
2137
- const openContextMenu = useCallback3((event, e) => {
2640
+ const openContextMenu = useCallback2((event, e) => {
2138
2641
  e.preventDefault();
2139
2642
  e.stopPropagation();
2140
2643
  setContextMenu({
@@ -2142,7 +2645,7 @@ var useEventContextMenu = () => {
2142
2645
  position: { x: e.clientX, y: e.clientY }
2143
2646
  });
2144
2647
  }, []);
2145
- const closeContextMenu = useCallback3(() => {
2648
+ const closeContextMenu = useCallback2(() => {
2146
2649
  setContextMenu({ event: null, position: null });
2147
2650
  }, []);
2148
2651
  return {
@@ -2153,146 +2656,6 @@ var useEventContextMenu = () => {
2153
2656
  };
2154
2657
  };
2155
2658
 
2156
- // src/locales/index.ts
2157
- var en = {
2158
- today: "Today",
2159
- month: "Month",
2160
- week: "Week",
2161
- day: "Day",
2162
- agenda: "Agenda",
2163
- resource: "Resources",
2164
- createEvent: "Create Event",
2165
- editEvent: "Edit Event",
2166
- delete: "Delete",
2167
- save: "Save",
2168
- cancel: "Cancel",
2169
- title: "Add title",
2170
- start: "Start",
2171
- end: "End",
2172
- allDay: "All Day",
2173
- description: "Description",
2174
- repeat: "Repeat",
2175
- noRepeat: "Does not repeat",
2176
- selectCalendar: "Select Calendar",
2177
- selectType: "Select Type",
2178
- daily: "Daily",
2179
- weekly: "Weekly",
2180
- monthly: "Monthly",
2181
- yearly: "Yearly",
2182
- search: "Search events...",
2183
- allTypes: "All Types",
2184
- timezone: "Time zone",
2185
- localTime: "Local Time",
2186
- create: "Create",
2187
- calendars: "Calendars",
2188
- event: "Event",
2189
- task: "Task",
2190
- appointmentSchedule: "Appointment schedule",
2191
- new: "New",
2192
- dateAndTime: "Date & Time",
2193
- whosJoining: "Who's joining?",
2194
- suggestedTimes: "Suggested times for guests",
2195
- viewSuggestions: "View suggestions",
2196
- whereWillItBe: "Where will it be?",
2197
- location: "Enter address",
2198
- descriptionAndAttachments: "Description",
2199
- dragAndDrop: "Drag & drop files or",
2200
- guests: "Guests",
2201
- addGuests: "Add guests",
2202
- addAttachment: "Upload files",
2203
- moreOptions: "More options",
2204
- doesNotRepeat: "Does not repeat",
2205
- locationHelpText: 'Enter a physical address (e.g. "123 Main St") to generate the map.',
2206
- notes: "Notes...",
2207
- guestCount: "guest",
2208
- guestsCount: "guests",
2209
- repeatFor: "Repeat for",
2210
- times: "times",
2211
- until: "Until",
2212
- endRepeat: "End repeat",
2213
- never: "Never",
2214
- afterOccurrences: "After occurrences",
2215
- onDate: "On date",
2216
- tomorrow: "Tomorrow",
2217
- edit: "Edit",
2218
- duplicate: "Duplicate",
2219
- eventSingular: "event",
2220
- eventPlural: "events",
2221
- allCaughtUp: "All caught up!",
2222
- noEventsScheduled: "No events scheduled",
2223
- noUpcoming: "Your schedule is clear",
2224
- noUpcomingHint: "No upcoming events to show. Create a new event to start planning."
2225
- };
2226
- var fr = {
2227
- today: "Aujourd'hui",
2228
- month: "Mois",
2229
- week: "Semaine",
2230
- day: "Jour",
2231
- agenda: "Agenda",
2232
- resource: "Ressources",
2233
- createEvent: "Cr\xE9er un \xE9v\xE9nement",
2234
- editEvent: "Modifier l'\xE9v\xE9nement",
2235
- delete: "Supprimer",
2236
- save: "Enregistrer",
2237
- cancel: "Annuler",
2238
- title: "Ajouter un titre",
2239
- start: "D\xE9but",
2240
- end: "Fin",
2241
- allDay: "Toute la journ\xE9e",
2242
- description: "Description",
2243
- repeat: "R\xE9p\xE9ter",
2244
- noRepeat: "Ne se r\xE9p\xE8te pas",
2245
- selectCalendar: "S\xE9lectionner un calendrier",
2246
- selectType: "S\xE9lectionner un type",
2247
- daily: "Quotidien",
2248
- weekly: "Hebdomadaire",
2249
- monthly: "Mensuel",
2250
- yearly: "Annuel",
2251
- search: "Rechercher...",
2252
- allTypes: "Tous les types",
2253
- timezone: "Fuseau horaire",
2254
- localTime: "Heure locale",
2255
- create: "Cr\xE9er",
2256
- calendars: "Calendriers",
2257
- event: "\xC9v\xE9nement",
2258
- task: "T\xE2che",
2259
- appointmentSchedule: "Planning de rendez-vous",
2260
- new: "Nouveau",
2261
- dateAndTime: "Date et heure",
2262
- whosJoining: "Qui participe ?",
2263
- suggestedTimes: "Horaires sugg\xE9r\xE9s",
2264
- viewSuggestions: "Voir les suggestions",
2265
- whereWillItBe: "O\xF9 aura lieu ?",
2266
- location: "Entrer une adresse",
2267
- descriptionAndAttachments: "Description",
2268
- dragAndDrop: "Glisser-d\xE9poser des fichiers ou",
2269
- guests: "Invit\xE9s",
2270
- addGuests: "Ajouter des invit\xE9s",
2271
- addAttachment: "T\xE9l\xE9charger des fichiers",
2272
- moreOptions: "Plus d'options",
2273
- doesNotRepeat: "Ne se r\xE9p\xE8te pas",
2274
- locationHelpText: 'Entrez une adresse physique (ex: "123 Rue Principale") pour g\xE9n\xE9rer la carte.',
2275
- notes: "Notes...",
2276
- guestCount: "invit\xE9",
2277
- guestsCount: "invit\xE9s",
2278
- repeatFor: "R\xE9p\xE9ter pendant",
2279
- times: "fois",
2280
- until: "Jusqu'au",
2281
- endRepeat: "Fin de r\xE9p\xE9tition",
2282
- never: "Jamais",
2283
- afterOccurrences: "Apr\xE8s occurrences",
2284
- onDate: "\xC0 la date",
2285
- tomorrow: "Demain",
2286
- edit: "Modifier",
2287
- duplicate: "Dupliquer",
2288
- eventSingular: "\xE9v\xE9nement",
2289
- eventPlural: "\xE9v\xE9nements",
2290
- allCaughtUp: "Tout est \xE0 jour !",
2291
- noEventsScheduled: "Aucun \xE9v\xE9nement pr\xE9vu",
2292
- noUpcoming: "Votre planning est libre",
2293
- noUpcomingHint: "Aucun \xE9v\xE9nement \xE0 venir. Cr\xE9ez un nouvel \xE9v\xE9nement pour commencer \xE0 planifier."
2294
- };
2295
-
2296
2659
  // src/lib/theme.ts
2297
2660
  function colorToHsl(color) {
2298
2661
  let r = 0, g = 0, b = 0;
@@ -2373,18 +2736,164 @@ function getThemeStyles(theme) {
2373
2736
  }
2374
2737
 
2375
2738
  // src/hooks/useCalendarLogic.ts
2376
- import { useState as useState5, useMemo as useMemo3 } from "react";
2739
+ import { useMemo as useMemo3, useState as useState5 } from "react";
2377
2740
  import {
2378
- addMonths as addMonths2,
2379
- subMonths as subMonths2,
2741
+ addDays as addDays5,
2742
+ addMonths as addMonths3,
2380
2743
  addWeeks,
2381
- subWeeks,
2382
- addDays as addDays2,
2744
+ endOfMonth as endOfMonth2,
2745
+ format as format13,
2746
+ startOfMonth as startOfMonth3,
2747
+ startOfWeek as startOfWeek2,
2383
2748
  subDays,
2384
- differenceInMilliseconds
2749
+ subMonths as subMonths2,
2750
+ subWeeks
2385
2751
  } from "date-fns";
2386
- import { toZonedTime as toZonedTime5, fromZonedTime } from "date-fns-tz";
2752
+
2753
+ // src/core/recurrence.ts
2754
+ import { addMonths as addMonths2, startOfMonth as startOfMonth2 } from "date-fns";
2387
2755
  import { RRule } from "rrule";
2756
+ var WEEKDAYS = [RRule.MO, RRule.TU, RRule.WE, RRule.TH, RRule.FR, RRule.SA, RRule.SU];
2757
+ var toFloating = (instant, tz) => {
2758
+ const w = toDisplay(instant, tz);
2759
+ return new Date(Date.UTC(w.getFullYear(), w.getMonth(), w.getDate(), w.getHours(), w.getMinutes(), w.getSeconds(), w.getMilliseconds()));
2760
+ };
2761
+ var fromFloating = (floating, tz) => fromDisplay(
2762
+ new Date(
2763
+ floating.getUTCFullYear(),
2764
+ floating.getUTCMonth(),
2765
+ floating.getUTCDate(),
2766
+ floating.getUTCHours(),
2767
+ floating.getUTCMinutes(),
2768
+ floating.getUTCSeconds(),
2769
+ floating.getUTCMilliseconds()
2770
+ ),
2771
+ tz
2772
+ );
2773
+ var minuteKey = (date) => Math.floor(date.getTime() / 6e4);
2774
+ var FREQUENCIES = { DAILY: RRule.DAILY, WEEKLY: RRule.WEEKLY, MONTHLY: RRule.MONTHLY, YEARLY: RRule.YEARLY };
2775
+ function buildRule(event, tz) {
2776
+ const r = event.recurrence;
2777
+ const freq = FREQUENCIES[r.freq];
2778
+ if (freq === void 0) throw new Error(`unsupported frequency "${r.freq}"`);
2779
+ return new RRule({
2780
+ freq,
2781
+ interval: r.interval || 1,
2782
+ dtstart: toFloating(event.start, tz),
2783
+ until: r.until ? toFloating(r.until, tz) : void 0,
2784
+ count: r.count,
2785
+ byweekday: r.byweekday?.length ? r.byweekday.map((d) => WEEKDAYS[d]) : void 0
2786
+ });
2787
+ }
2788
+ function occurrencesInMonth(event, monthStart, tz) {
2789
+ const rule = buildRule(event, tz);
2790
+ const excluded = new Set((event.recurrence.exdates ?? []).map(minuteKey));
2791
+ const duration = event.end.getTime() - event.start.getTime();
2792
+ const from = toFloating(monthStart, tz);
2793
+ const to = toFloating(addMonths2(monthStart, 1), tz);
2794
+ const result = [];
2795
+ for (const floating of rule.between(from, to, true)) {
2796
+ if (floating.getTime() === to.getTime()) continue;
2797
+ const start = fromFloating(floating, tz);
2798
+ if (excluded.has(minuteKey(start))) continue;
2799
+ result.push({
2800
+ ...event,
2801
+ id: `${event.id}-${start.getTime()}`,
2802
+ originalEventId: event.id,
2803
+ occurrenceStart: start,
2804
+ start,
2805
+ end: new Date(start.getTime() + duration)
2806
+ });
2807
+ }
2808
+ return result;
2809
+ }
2810
+ var MAX_MONTHS_PER_EVENT = 12;
2811
+ var occurrenceCache = /* @__PURE__ */ new WeakMap();
2812
+ function cachedMonth(event, monthStart, tz) {
2813
+ let months = occurrenceCache.get(event);
2814
+ if (!months) {
2815
+ months = /* @__PURE__ */ new Map();
2816
+ occurrenceCache.set(event, months);
2817
+ }
2818
+ const key = `${monthStart.getFullYear()}-${monthStart.getMonth()}|${tz ?? ""}`;
2819
+ let occurrences = months.get(key);
2820
+ if (occurrences) {
2821
+ months.delete(key);
2822
+ } else {
2823
+ occurrences = occurrencesInMonth(event, monthStart, tz);
2824
+ if (months.size >= MAX_MONTHS_PER_EVENT) months.delete(months.keys().next().value);
2825
+ }
2826
+ months.set(key, occurrences);
2827
+ return occurrences;
2828
+ }
2829
+ function expandRecurrences(events, range, anchorTz) {
2830
+ const result = [];
2831
+ const lastMonth = startOfMonth2(toDisplay(range.end, anchorTz));
2832
+ for (const event of events) {
2833
+ if (!event.recurrence) {
2834
+ result.push(event);
2835
+ continue;
2836
+ }
2837
+ const duration = event.end.getTime() - event.start.getTime();
2838
+ const firstMonth = startOfMonth2(toDisplay(new Date(range.start.getTime() - duration), anchorTz));
2839
+ try {
2840
+ for (let month = firstMonth; month <= lastMonth; month = addMonths2(month, 1)) {
2841
+ for (const occurrence of cachedMonth(event, fromDisplay(month, anchorTz), anchorTz)) {
2842
+ if (occurrence.end >= range.start && occurrence.start <= range.end) result.push(occurrence);
2843
+ }
2844
+ }
2845
+ } catch (error) {
2846
+ warnOnce(`invalid-recurrence:${event.id}`, `Event "${event.id}" has an invalid recurrence rule: ${String(error)}`);
2847
+ result.push(event);
2848
+ }
2849
+ }
2850
+ return result;
2851
+ }
2852
+ function stripOccurrenceFields(event) {
2853
+ const { originalEventId: _originalEventId, occurrenceStart: _occurrenceStart, ...rest } = event;
2854
+ return rest;
2855
+ }
2856
+ var shiftFloating = (instant, deltaMs, tz) => fromFloating(new Date(toFloating(instant, tz).getTime() + deltaMs), tz);
2857
+ var DAY_MS = 24 * 60 * 60 * 1e3;
2858
+ var floatingDay = (floating) => Math.floor(floating.getTime() / DAY_MS);
2859
+ function shiftSeries(master, occurrence, newStart, newEnd, anchorTz) {
2860
+ const fromFloat = toFloating(occurrence.start, anchorTz);
2861
+ const toFloat = toFloating(newStart, anchorTz);
2862
+ const deltaMs = toFloat.getTime() - fromFloat.getTime();
2863
+ const dayDelta = floatingDay(toFloat) - floatingDay(fromFloat);
2864
+ const start = shiftFloating(master.start, deltaMs, anchorTz);
2865
+ const end = new Date(start.getTime() + (newEnd.getTime() - newStart.getTime()));
2866
+ const recurrence = master.recurrence && {
2867
+ ...master.recurrence,
2868
+ byweekday: master.recurrence.byweekday && dayDelta % 7 !== 0 ? master.recurrence.byweekday.map((d) => ((d + dayDelta) % 7 + 7) % 7) : master.recurrence.byweekday,
2869
+ until: master.recurrence.until && shiftFloating(master.recurrence.until, deltaMs, anchorTz),
2870
+ exdates: master.recurrence.exdates?.map((d) => shiftFloating(d, deltaMs, anchorTz))
2871
+ };
2872
+ return stripOccurrenceFields({ ...master, start, end, recurrence });
2873
+ }
2874
+ function excludeOccurrence(master, occurrenceStart) {
2875
+ const recurrence = master.recurrence ?? { freq: "DAILY" };
2876
+ return { ...master, recurrence: { ...recurrence, exdates: [...recurrence.exdates ?? [], occurrenceStart] } };
2877
+ }
2878
+
2879
+ // src/hooks/useStableCallback.ts
2880
+ import { useCallback as useCallback3, useInsertionEffect as useInsertionEffect2, useRef as useRef6 } from "react";
2881
+ function useStableCallback(fn) {
2882
+ const ref = useRef6(fn);
2883
+ useInsertionEffect2(() => {
2884
+ ref.current = fn;
2885
+ });
2886
+ return useCallback3((...args) => ref.current(...args), []);
2887
+ }
2888
+
2889
+ // src/hooks/useCalendarLogic.ts
2890
+ function expansionRange(monthStart) {
2891
+ return {
2892
+ start: startOfWeek2(startOfMonth3(subMonths2(monthStart, 1))),
2893
+ end: addDays5(endOfMonth2(addMonths3(monthStart, 1)), 37)
2894
+ };
2895
+ }
2896
+ var DAY_MS2 = 24 * 60 * 60 * 1e3;
2388
2897
  var useCalendarLogic = ({
2389
2898
  events,
2390
2899
  view: controlledView,
@@ -2395,69 +2904,44 @@ var useCalendarLogic = ({
2395
2904
  onEventUpdate,
2396
2905
  onEventCreate,
2397
2906
  onEventDelete,
2907
+ onEventDrop,
2908
+ onEventResize,
2909
+ onOccurrenceDelete,
2398
2910
  readOnly,
2399
2911
  timezone
2400
2912
  }) => {
2401
2913
  const [internalView, setInternalView] = useState5("week");
2402
- const [internalDate, setInternalDate] = useState5(/* @__PURE__ */ new Date());
2914
+ const [internalDate, setInternalDate] = useState5(() => /* @__PURE__ */ new Date());
2403
2915
  const [isSidebarOpen, setIsSidebarOpen] = useState5(true);
2404
2916
  const [isModalOpen, setIsModalOpen] = useState5(false);
2405
2917
  const [selectedEvent, setSelectedEvent] = useState5(null);
2406
2918
  const [modalInitialDate, setModalInitialDate] = useState5(void 0);
2919
+ const [modalInitialResourceId, setModalInitialResourceId] = useState5(void 0);
2407
2920
  const view = controlledView ?? internalView;
2408
2921
  const currentDate = controlledDate ?? internalDate;
2409
- const expandedEvents = useMemo3(() => {
2410
- const allEvents = [];
2411
- const rangeStart = subDays(currentDate, 365);
2412
- const rangeEnd = addDays2(currentDate, 365);
2413
- events.forEach((event) => {
2414
- if (event.recurrence) {
2415
- try {
2416
- const rule = new RRule({
2417
- freq: RRule[event.recurrence.freq],
2418
- interval: event.recurrence.interval || 1,
2419
- dtstart: new Date(event.start),
2420
- until: event.recurrence.until ? new Date(event.recurrence.until) : void 0,
2421
- count: event.recurrence.count
2422
- });
2423
- const dates = rule.between(rangeStart, rangeEnd);
2424
- dates.forEach((date) => {
2425
- const duration = differenceInMilliseconds(new Date(event.end), new Date(event.start));
2426
- allEvents.push({
2427
- ...event,
2428
- id: `${event.id}-${date.getTime()}`,
2429
- // Unique ID for each instance
2430
- originalEventId: event.id,
2431
- // Reference to original
2432
- start: date,
2433
- end: new Date(date.getTime() + duration)
2434
- });
2435
- });
2436
- } catch (e) {
2437
- console.error("Error parsing recurrence rule", e);
2438
- allEvents.push(event);
2439
- }
2440
- } else {
2441
- allEvents.push(event);
2442
- }
2443
- });
2444
- return allEvents;
2445
- }, [events, currentDate]);
2446
- const handleViewChange = (newView) => {
2922
+ const normalizedEvents = useMemo3(() => normalizeEvents(events), [events]);
2923
+ const monthKey = format13(currentDate, "yyyy-MM");
2924
+ const range = useMemo3(() => expansionRange(/* @__PURE__ */ new Date(`${monthKey}-01T00:00:00`)), [monthKey]);
2925
+ const expandedEvents = useMemo3(
2926
+ () => expandRecurrences(normalizedEvents, range, timezone),
2927
+ [normalizedEvents, range, timezone]
2928
+ );
2929
+ const masterOf = (occurrence) => normalizedEvents.find((e) => e.id === occurrence.originalEventId);
2930
+ const handleViewChange = useStableCallback((newView) => {
2447
2931
  if (controlledOnViewChange) {
2448
2932
  controlledOnViewChange(newView);
2449
2933
  } else {
2450
2934
  setInternalView(newView);
2451
2935
  }
2452
- };
2453
- const handleDateChange = (newDate) => {
2936
+ });
2937
+ const handleDateChange = useStableCallback((newDate) => {
2454
2938
  if (controlledOnDateChange) {
2455
2939
  controlledOnDateChange(newDate);
2456
2940
  } else {
2457
2941
  setInternalDate(newDate);
2458
2942
  }
2459
- };
2460
- const handlePrev = () => {
2943
+ });
2944
+ const handlePrev = useStableCallback(() => {
2461
2945
  switch (view) {
2462
2946
  case "month":
2463
2947
  handleDateChange(subMonths2(currentDate, 1));
@@ -2473,133 +2957,140 @@ var useCalendarLogic = ({
2473
2957
  handleDateChange(subDays(currentDate, 7));
2474
2958
  break;
2475
2959
  }
2476
- };
2477
- const handleNext = () => {
2960
+ });
2961
+ const handleNext = useStableCallback(() => {
2478
2962
  switch (view) {
2479
2963
  case "month":
2480
- handleDateChange(addMonths2(currentDate, 1));
2964
+ handleDateChange(addMonths3(currentDate, 1));
2481
2965
  break;
2482
2966
  case "week":
2483
2967
  handleDateChange(addWeeks(currentDate, 1));
2484
2968
  break;
2485
2969
  case "day":
2486
2970
  case "resource":
2487
- handleDateChange(addDays2(currentDate, 1));
2971
+ handleDateChange(addDays5(currentDate, 1));
2488
2972
  break;
2489
2973
  case "agenda":
2490
- handleDateChange(addDays2(currentDate, 7));
2974
+ handleDateChange(addDays5(currentDate, 7));
2491
2975
  break;
2492
2976
  }
2493
- };
2494
- const handleToday = () => {
2977
+ });
2978
+ const handleToday = useStableCallback(() => {
2495
2979
  handleDateChange(/* @__PURE__ */ new Date());
2496
- };
2497
- const handleDateClick = (date) => {
2980
+ });
2981
+ const handleDateClick = useStableCallback((date) => {
2498
2982
  handleDateChange(date);
2499
2983
  handleViewChange("day");
2500
- };
2501
- const handleTimeSlotClick = (date) => {
2984
+ });
2985
+ const handleTimeSlotClick = useStableCallback((date, resourceId) => {
2502
2986
  if (readOnly) return;
2503
2987
  setSelectedEvent(null);
2504
2988
  setModalInitialDate(date);
2989
+ setModalInitialResourceId(resourceId);
2505
2990
  setIsModalOpen(true);
2506
- };
2507
- const handleEventClickInternal = (event) => {
2508
- if (onEventClick) {
2509
- onEventClick(event);
2510
- }
2991
+ });
2992
+ const handleEventClickInternal = useStableCallback((event) => {
2993
+ onEventClick?.(event);
2511
2994
  if (!readOnly) {
2512
2995
  setSelectedEvent(event);
2513
2996
  setModalInitialDate(void 0);
2997
+ setModalInitialResourceId(void 0);
2514
2998
  setIsModalOpen(true);
2515
2999
  }
2516
- };
2517
- const handleCreateEvent = () => {
3000
+ });
3001
+ const handleCreateEvent = useStableCallback(() => {
2518
3002
  if (readOnly) return;
2519
3003
  setSelectedEvent(null);
2520
3004
  setModalInitialDate(/* @__PURE__ */ new Date());
3005
+ setModalInitialResourceId(void 0);
2521
3006
  setIsModalOpen(true);
2522
- };
2523
- const handleModalSave = (eventData) => {
2524
- let effectiveEventId = selectedEvent?.id;
3007
+ });
3008
+ const handleModalSave = useStableCallback((eventData) => {
2525
3009
  if (selectedEvent?.originalEventId) {
2526
- effectiveEventId = selectedEvent.originalEventId;
2527
- }
2528
- if (effectiveEventId) {
2529
- if (onEventUpdate) {
2530
- onEventUpdate({
2531
- ...eventData,
2532
- id: effectiveEventId
2533
- });
2534
- }
3010
+ const master = masterOf(selectedEvent);
3011
+ if (!master) return;
3012
+ const edited = {
3013
+ ...master,
3014
+ ...stripOccurrenceFields(eventData),
3015
+ id: master.id,
3016
+ start: master.start,
3017
+ end: master.end
3018
+ };
3019
+ onEventUpdate?.(
3020
+ shiftSeries(edited, selectedEvent, eventData.start ?? selectedEvent.start, eventData.end ?? selectedEvent.end, timezone)
3021
+ );
3022
+ } else if (selectedEvent) {
3023
+ onEventUpdate?.({ ...eventData, id: selectedEvent.id });
2535
3024
  } else {
2536
- if (onEventCreate) {
2537
- onEventCreate(eventData);
2538
- }
2539
- }
2540
- };
2541
- const handleModalDelete = (eventId) => {
2542
- let effectiveEventId = eventId;
2543
- if (selectedEvent?.originalEventId && selectedEvent.id === eventId) {
2544
- effectiveEventId = selectedEvent.originalEventId;
3025
+ onEventCreate?.(
3026
+ modalInitialResourceId && eventData.resourceId === void 0 ? { ...eventData, resourceId: modalInitialResourceId } : eventData
3027
+ );
2545
3028
  }
2546
- if (onEventDelete) {
2547
- onEventDelete(effectiveEventId);
3029
+ });
3030
+ const handleEventDelete = useStableCallback((event) => {
3031
+ if (!event.originalEventId) {
3032
+ onEventDelete?.(event.id);
3033
+ return;
2548
3034
  }
2549
- };
2550
- const handleDragEnd = (event) => {
2551
- const { active, over } = event;
2552
- if (!over) return;
2553
- const activeEvent = active.data.current?.event;
2554
- const overDate = over.data.current?.date;
2555
- if (!activeEvent || !overDate) return;
2556
- const originalStart = new Date(activeEvent.start);
2557
- const originalEnd = new Date(activeEvent.end);
2558
- const duration = differenceInMilliseconds(originalEnd, originalStart);
2559
- let newStart;
2560
- if (view === "month") {
2561
- if (timezone) {
2562
- const zonedOriginal = toZonedTime5(originalStart, timezone);
2563
- const zonedNew = new Date(overDate);
2564
- zonedNew.setHours(zonedOriginal.getHours());
2565
- zonedNew.setMinutes(zonedOriginal.getMinutes());
2566
- zonedNew.setSeconds(zonedOriginal.getSeconds());
2567
- zonedNew.setMilliseconds(zonedOriginal.getMilliseconds());
2568
- newStart = fromZonedTime(zonedNew, timezone);
2569
- } else {
2570
- newStart = new Date(overDate);
2571
- newStart.setHours(originalStart.getHours());
2572
- newStart.setMinutes(originalStart.getMinutes());
2573
- newStart.setSeconds(originalStart.getSeconds());
2574
- newStart.setMilliseconds(originalStart.getMilliseconds());
2575
- }
3035
+ const master = masterOf(event);
3036
+ if (onOccurrenceDelete && master) {
3037
+ onOccurrenceDelete(event, excludeOccurrence(master, event.occurrenceStart ?? event.start));
2576
3038
  } else {
2577
- if (timezone) {
2578
- newStart = fromZonedTime(overDate, timezone);
2579
- } else {
2580
- newStart = new Date(overDate);
2581
- }
2582
- newStart.setSeconds(0);
2583
- newStart.setMilliseconds(0);
2584
- }
2585
- const newEnd = new Date(newStart.getTime() + duration);
2586
- let newResourceId = activeEvent.resourceId;
2587
- const overResourceId = over.data.current?.resourceId;
2588
- if (overResourceId) {
2589
- newResourceId = overResourceId;
2590
- }
2591
- if (newStart.getTime() === originalStart.getTime() && newEnd.getTime() === originalEnd.getTime() && newResourceId === activeEvent.resourceId) {
2592
- return;
3039
+ onEventDelete?.(event.originalEventId);
2593
3040
  }
2594
- if (onEventUpdate) {
2595
- onEventUpdate({
2596
- ...activeEvent,
2597
- start: newStart,
2598
- end: newEnd,
2599
- resourceId: newResourceId
2600
- });
3041
+ });
3042
+ const handleModalDelete = useStableCallback((eventId) => {
3043
+ if (selectedEvent && selectedEvent.id === eventId) {
3044
+ handleEventDelete(selectedEvent);
3045
+ } else {
3046
+ onEventDelete?.(eventId);
2601
3047
  }
3048
+ });
3049
+ const handleEventDuplicate = useStableCallback((event) => {
3050
+ const { recurrence: _recurrence, ...copy } = stripOccurrenceFields(event);
3051
+ onEventCreate?.({
3052
+ ...copy,
3053
+ id: `${event.originalEventId ?? event.id}-copy-${Date.now()}`,
3054
+ title: `${event.title} (Copy)`,
3055
+ start: new Date(event.start.getTime() + DAY_MS2),
3056
+ end: new Date(event.end.getTime() + DAY_MS2)
3057
+ });
3058
+ });
3059
+ const applyChange = (event, start, end) => {
3060
+ if (!event.originalEventId) return { original: event, updated: { ...event, start, end } };
3061
+ const master = masterOf(event);
3062
+ if (!master) return null;
3063
+ return { original: master, updated: shiftSeries(master, event, start, end, timezone) };
2602
3064
  };
3065
+ const handleDragEnd = useStableCallback((dragEvent) => {
3066
+ if (readOnly) return;
3067
+ const { active, over } = dragEvent;
3068
+ if (!over) return;
3069
+ const dragged = active.data.current?.event;
3070
+ const overDate = over.data.current?.date;
3071
+ if (!dragged || !overDate) return;
3072
+ const drop = computeDrop({
3073
+ event: dragged,
3074
+ overDate,
3075
+ overResourceId: over.data.current?.resourceId,
3076
+ grabOffsetMs: active.data.current?.grabOffsetMs,
3077
+ view,
3078
+ tz: timezone
3079
+ });
3080
+ if (!drop) return;
3081
+ const change = applyChange(dragged, drop.start, drop.end);
3082
+ if (!change) return;
3083
+ const updated = { ...change.updated, resourceId: drop.resourceId };
3084
+ onEventDrop?.(change.original, updated.start, updated.end);
3085
+ onEventUpdate?.(updated);
3086
+ });
3087
+ const handleEventResize = useStableCallback((event, newEnd) => {
3088
+ if (readOnly) return;
3089
+ const change = applyChange(event, event.start, newEnd);
3090
+ if (!change) return;
3091
+ onEventResize?.(change.original, change.updated.start, change.updated.end);
3092
+ onEventUpdate?.(change.updated);
3093
+ });
2603
3094
  return {
2604
3095
  view,
2605
3096
  currentDate,
@@ -2621,15 +3112,18 @@ var useCalendarLogic = ({
2621
3112
  handleCreateEvent,
2622
3113
  handleModalSave,
2623
3114
  handleModalDelete,
3115
+ handleEventDelete,
3116
+ handleEventDuplicate,
3117
+ handleEventResize,
2624
3118
  handleDragEnd
2625
3119
  };
2626
3120
  };
2627
3121
 
2628
3122
  // src/index.tsx
2629
- import { differenceInMinutes as differenceInMinutes6, format as format11 } from "date-fns";
3123
+ import { differenceInMinutes as differenceInMinutes6, format as format14, startOfWeek as startOfWeek3 } from "date-fns";
2630
3124
 
2631
3125
  // src/hooks/useSwipeGesture.ts
2632
- import { useRef as useRef5, useCallback as useCallback4, useEffect as useEffect5 } from "react";
3126
+ import { useRef as useRef7, useCallback as useCallback4, useEffect as useEffect5 } from "react";
2633
3127
  var useSwipeGesture = (options) => {
2634
3128
  const {
2635
3129
  onSwipeLeft,
@@ -2641,8 +3135,8 @@ var useSwipeGesture = (options) => {
2641
3135
  allowedTime = 500,
2642
3136
  enabled = true
2643
3137
  } = options;
2644
- const touchInfoRef = useRef5(null);
2645
- const elementRef = useRef5(null);
3138
+ const touchInfoRef = useRef7(null);
3139
+ const elementRef = useRef7(null);
2646
3140
  const handleTouchStart = useCallback4((e) => {
2647
3141
  if (!enabled) return;
2648
3142
  const touch = e.touches[0];
@@ -2710,7 +3204,9 @@ var Scheduler = ({
2710
3204
  onEventCreate,
2711
3205
  onEventUpdate,
2712
3206
  onEventDelete,
3207
+ onEventDrop,
2713
3208
  onEventResize,
3209
+ onOccurrenceDelete,
2714
3210
  timezone,
2715
3211
  onTimezoneChange,
2716
3212
  className,
@@ -2736,6 +3232,7 @@ var Scheduler = ({
2736
3232
  const {
2737
3233
  contextMenuEvent,
2738
3234
  contextMenuPosition,
3235
+ openContextMenu,
2739
3236
  closeContextMenu
2740
3237
  } = useEventContextMenu();
2741
3238
  const {
@@ -2758,9 +3255,11 @@ var Scheduler = ({
2758
3255
  handleCreateEvent,
2759
3256
  handleModalSave,
2760
3257
  handleModalDelete,
3258
+ handleEventDelete,
3259
+ handleEventDuplicate,
3260
+ handleEventResize,
2761
3261
  handleDragEnd,
2762
3262
  expandedEvents
2763
- // New export
2764
3263
  } = useCalendarLogic({
2765
3264
  events,
2766
3265
  view: controlledView,
@@ -2771,6 +3270,9 @@ var Scheduler = ({
2771
3270
  onEventUpdate,
2772
3271
  onEventCreate,
2773
3272
  onEventDelete,
3273
+ onEventDrop,
3274
+ onEventResize,
3275
+ onOccurrenceDelete,
2774
3276
  readOnly,
2775
3277
  timezone
2776
3278
  });
@@ -2785,37 +3287,20 @@ var Scheduler = ({
2785
3287
  const snapToGrid = createSnapModifier(gridSize);
2786
3288
  const modifiers = [snapToGrid, restrictToWindowEdges];
2787
3289
  const dndSensors = readOnly ? [] : sensors;
2788
- const handleEventResize = (event, newEnd) => {
2789
- if (readOnly) return;
2790
- if (onEventResize) {
2791
- onEventResize(event, event.start, newEnd);
2792
- }
2793
- if (onEventUpdate) {
2794
- onEventUpdate({
2795
- ...event,
2796
- end: newEnd
2797
- });
2798
- }
2799
- };
2800
3290
  const id = useId();
2801
3291
  const swipeRef = useViewSwipe(handlePrev, handleNext, true);
2802
- const languagePack = language === "fr" ? fr : en;
2803
- const t = {
2804
- ...languagePack,
2805
- ...translations
2806
- };
2807
- const overlayTimeFormat = uses24HourClock(locale) ? "H:mm" : "h:mm a";
2808
- const handleDragStart = (event) => {
2809
- const { active } = event;
2810
- const draggedEvent = expandedEvents.find((e) => e.id === active.id);
3292
+ const t = useMemo4(() => resolveTranslations(language, translations), [language, translations]);
3293
+ const overlayTimeFormat = timeFormats(locale).time;
3294
+ const handleDragStart = useCallback5((event) => {
3295
+ const draggedEvent = event.active.data.current?.event;
2811
3296
  if (draggedEvent) {
2812
3297
  setActiveDragEvent(draggedEvent);
2813
3298
  }
2814
- };
2815
- const onDragEndWrapper = (event) => {
3299
+ }, []);
3300
+ const onDragEndWrapper = useCallback5((event) => {
2816
3301
  setActiveDragEvent(null);
2817
3302
  handleDragEnd(event);
2818
- };
3303
+ }, [handleDragEnd]);
2819
3304
  const getDragHeight = () => {
2820
3305
  if (!activeDragEvent) return void 0;
2821
3306
  if (view === "resource") {
@@ -2835,14 +3320,15 @@ var Scheduler = ({
2835
3320
  }
2836
3321
  return "150px";
2837
3322
  };
3323
+ const activeCalendarKey = calendars ? calendars.filter((c) => c.active !== false).map((c) => c.id).join("\0") : null;
2838
3324
  const filteredEvents = useMemo4(() => {
2839
- if (!calendars) return expandedEvents;
2840
- const activeCalendarIds = calendars.filter((c) => c.active !== false).map((c) => c.id);
2841
- return expandedEvents.filter((e) => {
2842
- if (!e.calendarId) return true;
2843
- return activeCalendarIds.includes(e.calendarId);
2844
- });
2845
- }, [expandedEvents, calendars]);
3325
+ if (activeCalendarKey === null) return expandedEvents;
3326
+ const activeCalendarIds = new Set(activeCalendarKey.split("\0"));
3327
+ return expandedEvents.filter((e) => !e.calendarId || activeCalendarIds.has(String(e.calendarId)));
3328
+ }, [expandedEvents, activeCalendarKey]);
3329
+ const periodKey = view === "month" ? format14(currentDate, "yyyy-MM") : view === "week" ? format14(startOfWeek3(currentDate, { weekStartsOn: resolveWeekStartsOn(locale, 1) }), "yyyy-MM-dd") : format14(currentDate, "yyyy-MM-dd");
3330
+ const slotClickHandler = readOnly ? void 0 : handleTimeSlotClick;
3331
+ const contextMenuHandler = readOnly ? void 0 : openContextMenu;
2846
3332
  return /* @__PURE__ */ React18.createElement(
2847
3333
  DndContext,
2848
3334
  {
@@ -2913,7 +3399,7 @@ var Scheduler = ({
2913
3399
  ), /* @__PURE__ */ React18.createElement("div", { className: "flex-1 flex flex-col overflow-hidden relative" }, isLoading ? /* @__PURE__ */ React18.createElement("div", { className: "flex-1 overflow-auto p-0 md:p-4" }, /* @__PURE__ */ React18.createElement("div", { className: "h-full min-w-full" }, view === "month" && /* @__PURE__ */ React18.createElement(MonthViewSkeleton, null), view === "week" && /* @__PURE__ */ React18.createElement(WeekViewSkeleton, null), view === "day" && /* @__PURE__ */ React18.createElement(DayViewSkeleton, null), view === "agenda" && /* @__PURE__ */ React18.createElement(AgendaViewSkeleton, null), view === "resource" && /* @__PURE__ */ React18.createElement(WeekViewSkeleton, null))) : /* @__PURE__ */ React18.createElement("div", { ref: swipeRef, className: "flex-1 overflow-auto p-0 md:p-4 touch-pan-y" }, /* @__PURE__ */ React18.createElement("div", { className: "h-full min-w-full" }, /* @__PURE__ */ React18.createElement(AnimatePresence3, { mode: "wait", initial: false }, /* @__PURE__ */ React18.createElement(
2914
3400
  motion4.div,
2915
3401
  {
2916
- key: `${view}-${currentDate.toISOString()}-${timezone || "local"}`,
3402
+ key: `${view}-${periodKey}-${timezone || "local"}`,
2917
3403
  initial: { opacity: 0, scale: 0.98, y: 15 },
2918
3404
  animate: {
2919
3405
  opacity: 1,
@@ -2943,7 +3429,9 @@ var Scheduler = ({
2943
3429
  onEventClick: handleEventClickInternal,
2944
3430
  onDateClick: handleDateClick,
2945
3431
  timezone,
2946
- locale
3432
+ locale,
3433
+ translations: t,
3434
+ onEventContextMenu: contextMenuHandler
2947
3435
  }
2948
3436
  ),
2949
3437
  view === "week" && /* @__PURE__ */ React18.createElement(
@@ -2952,11 +3440,12 @@ var Scheduler = ({
2952
3440
  currentDate,
2953
3441
  events: filteredEvents,
2954
3442
  onEventClick: handleEventClickInternal,
2955
- onTimeSlotClick: handleTimeSlotClick,
3443
+ onTimeSlotClick: slotClickHandler,
2956
3444
  onEventResize: handleEventResize,
2957
3445
  timezone,
2958
3446
  locale,
2959
- readonly: readOnly
3447
+ readonly: readOnly,
3448
+ onEventContextMenu: contextMenuHandler
2960
3449
  }
2961
3450
  ),
2962
3451
  view === "day" && /* @__PURE__ */ React18.createElement(
@@ -2965,11 +3454,13 @@ var Scheduler = ({
2965
3454
  currentDate,
2966
3455
  events: filteredEvents,
2967
3456
  onEventClick: handleEventClickInternal,
2968
- onTimeSlotClick: handleTimeSlotClick,
3457
+ onTimeSlotClick: slotClickHandler,
2969
3458
  onEventResize: handleEventResize,
2970
3459
  timezone,
2971
3460
  locale,
2972
- readonly: readOnly
3461
+ readonly: readOnly,
3462
+ translations: t,
3463
+ onEventContextMenu: contextMenuHandler
2973
3464
  }
2974
3465
  ),
2975
3466
  view === "agenda" && /* @__PURE__ */ React18.createElement(
@@ -2980,7 +3471,9 @@ var Scheduler = ({
2980
3471
  onEventClick: handleEventClickInternal,
2981
3472
  onCreateEvent: handleCreateEvent,
2982
3473
  locale,
2983
- translations: t
3474
+ translations: t,
3475
+ timezone,
3476
+ onEventContextMenu: contextMenuHandler
2984
3477
  }
2985
3478
  ),
2986
3479
  view === "resource" && resources && /* @__PURE__ */ React18.createElement(
@@ -2990,17 +3483,18 @@ var Scheduler = ({
2990
3483
  events: filteredEvents,
2991
3484
  resources,
2992
3485
  onEventClick: handleEventClickInternal,
2993
- onTimeSlotClick: (date) => {
2994
- if (readOnly) return;
2995
- handleTimeSlotClick(date);
2996
- },
2997
- locale
3486
+ onTimeSlotClick: slotClickHandler,
3487
+ locale,
3488
+ timezone,
3489
+ translations: t,
3490
+ onEventContextMenu: contextMenuHandler
2998
3491
  }
2999
3492
  )
3000
- )))), /* @__PURE__ */ React18.createElement("div", { className: "md:hidden absolute bottom-6 right-6 z-50" }, /* @__PURE__ */ React18.createElement(
3493
+ )))), !readOnly && /* @__PURE__ */ React18.createElement("div", { className: "md:hidden absolute bottom-6 right-6 z-50" }, /* @__PURE__ */ React18.createElement(
3001
3494
  "button",
3002
3495
  {
3003
3496
  onClick: handleCreateEvent,
3497
+ "aria-label": t.createEvent,
3004
3498
  className: "w-14 h-14 bg-blue-600 rounded-full shadow-lg flex items-center justify-center text-white active:scale-90 transition-transform"
3005
3499
  },
3006
3500
  /* @__PURE__ */ React18.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React18.createElement("path", { d: "M5 12h14" }), /* @__PURE__ */ React18.createElement("path", { d: "M12 5v14" }))
@@ -3023,7 +3517,8 @@ var Scheduler = ({
3023
3517
  onDelete: handleModalDelete,
3024
3518
  calendars,
3025
3519
  eventTypes,
3026
- translations: t
3520
+ translations: t,
3521
+ locale
3027
3522
  }
3028
3523
  ),
3029
3524
  /* @__PURE__ */ React18.createElement(
@@ -3036,20 +3531,12 @@ var Scheduler = ({
3036
3531
  handleEventClickInternal(event);
3037
3532
  closeContextMenu();
3038
3533
  },
3039
- onDelete: (eventId) => {
3040
- onEventDelete?.(eventId);
3534
+ onDelete: () => {
3535
+ if (contextMenuEvent) handleEventDelete(contextMenuEvent);
3041
3536
  closeContextMenu();
3042
3537
  },
3043
3538
  onDuplicate: (event) => {
3044
- const duplicatedEvent = {
3045
- ...event,
3046
- id: `${event.id}-copy-${Date.now()}`,
3047
- title: `${event.title} (Copy)`,
3048
- start: new Date(event.start.getTime() + 24 * 60 * 60 * 1e3),
3049
- // +1 day
3050
- end: new Date(event.end.getTime() + 24 * 60 * 60 * 1e3)
3051
- };
3052
- onEventCreate?.(duplicatedEvent);
3539
+ handleEventDuplicate(event);
3053
3540
  closeContextMenu();
3054
3541
  },
3055
3542
  translations: {
@@ -3083,7 +3570,7 @@ var Scheduler = ({
3083
3570
  className: "absolute left-0 top-0 bottom-0 w-1 rounded-l-lg",
3084
3571
  style: { backgroundColor: activeDragEvent.color || "var(--primary)" }
3085
3572
  }
3086
- ), /* @__PURE__ */ React18.createElement("div", { className: "pl-2" }, /* @__PURE__ */ React18.createElement("div", { className: "font-semibold truncate text-sm" }, activeDragEvent.title), (view === "week" || view === "day") && getDragHeight() && getDragHeight() > 40 && /* @__PURE__ */ React18.createElement("div", { className: "text-xs opacity-80 mt-0.5 flex items-center gap-1" }, /* @__PURE__ */ React18.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React18.createElement("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ React18.createElement("path", { d: "M12 6v6l4 2" })), format11(activeDragEvent.start, overlayTimeFormat, { locale }), " - ", format11(activeDragEvent.end, overlayTimeFormat, { locale }))), /* @__PURE__ */ React18.createElement("div", { className: "absolute bottom-1.5 right-1.5 opacity-60" }, /* @__PURE__ */ React18.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React18.createElement("circle", { cx: "9", cy: "5", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "15", cy: "5", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "9", cy: "12", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "15", cy: "12", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "9", cy: "19", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "15", cy: "19", r: "1.5" }))))
3573
+ ), /* @__PURE__ */ React18.createElement("div", { className: "pl-2" }, /* @__PURE__ */ React18.createElement("div", { className: "font-semibold truncate text-sm" }, activeDragEvent.title), (view === "week" || view === "day") && getDragHeight() && getDragHeight() > 40 && /* @__PURE__ */ React18.createElement("div", { className: "text-xs opacity-80 mt-0.5 flex items-center gap-1" }, /* @__PURE__ */ React18.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React18.createElement("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ React18.createElement("path", { d: "M12 6v6l4 2" })), format14(toDisplay(activeDragEvent.start, timezone), overlayTimeFormat, { locale }), " - ", format14(toDisplay(activeDragEvent.end, timezone), overlayTimeFormat, { locale }))), /* @__PURE__ */ React18.createElement("div", { className: "absolute bottom-1.5 right-1.5 opacity-60" }, /* @__PURE__ */ React18.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React18.createElement("circle", { cx: "9", cy: "5", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "15", cy: "5", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "9", cy: "12", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "15", cy: "12", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "9", cy: "19", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "15", cy: "19", r: "1.5" }))))
3087
3574
  ) : null)
3088
3575
  )
3089
3576
  );