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