calendarkit-pro 1.2.1 → 1.3.0

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");
@@ -200,19 +200,24 @@ var import_lucide_react3 = require("lucide-react");
200
200
  var import_react2 = __toESM(require("react"));
201
201
  var import_date_fns3 = require("date-fns");
202
202
 
203
- // src/lib/date.ts
203
+ // src/core/week.ts
204
204
  var import_date_fns2 = require("date-fns");
205
- var getMonthGrid = (date, weekStartOn = 0) => {
205
+ function resolveWeekStartsOn(locale, fallback) {
206
+ return locale?.options?.weekStartsOn ?? fallback;
207
+ }
208
+ function getWeekDays(date, weekStartsOn) {
209
+ return (0, import_date_fns2.eachDayOfInterval)({
210
+ start: (0, import_date_fns2.startOfWeek)(date, { weekStartsOn }),
211
+ end: (0, import_date_fns2.endOfWeek)(date, { weekStartsOn })
212
+ });
213
+ }
214
+ function getMonthGrid(date, weekStartsOn) {
206
215
  const monthStart = (0, import_date_fns2.startOfMonth)(date);
207
216
  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 })
217
+ start: (0, import_date_fns2.startOfWeek)(monthStart, { weekStartsOn }),
218
+ end: (0, import_date_fns2.endOfWeek)((0, import_date_fns2.endOfMonth)(monthStart), { weekStartsOn })
210
219
  });
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
- };
220
+ }
216
221
 
217
222
  // src/components/MiniCalendar.tsx
218
223
  var import_lucide_react2 = require("lucide-react");
@@ -227,7 +232,7 @@ var MiniCalendar = ({
227
232
  import_react2.default.useEffect(() => {
228
233
  setViewDate(currentDate);
229
234
  }, [currentDate]);
230
- const weekStartsOn = locale?.options?.weekStartsOn ?? 0;
235
+ const weekStartsOn = resolveWeekStartsOn(locale, 0);
231
236
  const days = import_react2.default.useMemo(
232
237
  () => getMonthGrid(viewDate, weekStartsOn),
233
238
  [viewDate, weekStartsOn]
@@ -296,7 +301,195 @@ var MiniCalendar = ({
296
301
 
297
302
  // src/components/Sidebar.tsx
298
303
  var import_date_fns_tz = require("date-fns-tz");
304
+ var import_date_fns5 = require("date-fns");
305
+
306
+ // src/core/format.ts
299
307
  var import_date_fns4 = require("date-fns");
308
+
309
+ // src/locales/index.ts
310
+ var en = {
311
+ today: "Today",
312
+ month: "Month",
313
+ week: "Week",
314
+ day: "Day",
315
+ agenda: "Agenda",
316
+ resource: "Resources",
317
+ createEvent: "Create Event",
318
+ editEvent: "Edit Event",
319
+ delete: "Delete",
320
+ save: "Save",
321
+ cancel: "Cancel",
322
+ title: "Add title",
323
+ start: "Start",
324
+ end: "End",
325
+ allDay: "All Day",
326
+ description: "Description",
327
+ repeat: "Repeat",
328
+ noRepeat: "Does not repeat",
329
+ selectCalendar: "Select Calendar",
330
+ selectType: "Select Type",
331
+ daily: "Daily",
332
+ weekly: "Weekly",
333
+ monthly: "Monthly",
334
+ yearly: "Yearly",
335
+ search: "Search events...",
336
+ allTypes: "All Types",
337
+ timezone: "Time zone",
338
+ localTime: "Local Time",
339
+ create: "Create",
340
+ calendars: "Calendars",
341
+ event: "Event",
342
+ task: "Task",
343
+ appointmentSchedule: "Appointment schedule",
344
+ new: "New",
345
+ dateAndTime: "Date & Time",
346
+ whosJoining: "Who's joining?",
347
+ suggestedTimes: "Suggested times for guests",
348
+ viewSuggestions: "View suggestions",
349
+ whereWillItBe: "Where will it be?",
350
+ location: "Enter address",
351
+ descriptionAndAttachments: "Description",
352
+ dragAndDrop: "Drag & drop files or",
353
+ guests: "Guests",
354
+ addGuests: "Add guests",
355
+ addAttachment: "Upload files",
356
+ moreOptions: "More options",
357
+ doesNotRepeat: "Does not repeat",
358
+ locationHelpText: 'Enter a physical address (e.g. "123 Main St") to generate the map.',
359
+ notes: "Notes...",
360
+ guestCount: "guest",
361
+ guestsCount: "guests",
362
+ repeatFor: "Repeat for",
363
+ times: "times",
364
+ until: "Until",
365
+ endRepeat: "End repeat",
366
+ never: "Never",
367
+ afterOccurrences: "After occurrences",
368
+ onDate: "On date",
369
+ tomorrow: "Tomorrow",
370
+ edit: "Edit",
371
+ duplicate: "Duplicate",
372
+ eventSingular: "event",
373
+ eventPlural: "events",
374
+ allCaughtUp: "All caught up!",
375
+ noEventsScheduled: "No events scheduled",
376
+ noUpcoming: "Your schedule is clear",
377
+ noUpcomingHint: "No upcoming events to show. Create a new event to start planning."
378
+ };
379
+ var fr = {
380
+ today: "Aujourd'hui",
381
+ month: "Mois",
382
+ week: "Semaine",
383
+ day: "Jour",
384
+ agenda: "Agenda",
385
+ resource: "Ressources",
386
+ createEvent: "Cr\xE9er un \xE9v\xE9nement",
387
+ editEvent: "Modifier l'\xE9v\xE9nement",
388
+ delete: "Supprimer",
389
+ save: "Enregistrer",
390
+ cancel: "Annuler",
391
+ title: "Ajouter un titre",
392
+ start: "D\xE9but",
393
+ end: "Fin",
394
+ allDay: "Toute la journ\xE9e",
395
+ description: "Description",
396
+ repeat: "R\xE9p\xE9ter",
397
+ noRepeat: "Ne se r\xE9p\xE8te pas",
398
+ selectCalendar: "S\xE9lectionner un calendrier",
399
+ selectType: "S\xE9lectionner un type",
400
+ daily: "Quotidien",
401
+ weekly: "Hebdomadaire",
402
+ monthly: "Mensuel",
403
+ yearly: "Annuel",
404
+ search: "Rechercher...",
405
+ allTypes: "Tous les types",
406
+ timezone: "Fuseau horaire",
407
+ localTime: "Heure locale",
408
+ create: "Cr\xE9er",
409
+ calendars: "Calendriers",
410
+ event: "\xC9v\xE9nement",
411
+ task: "T\xE2che",
412
+ appointmentSchedule: "Planning de rendez-vous",
413
+ new: "Nouveau",
414
+ dateAndTime: "Date et heure",
415
+ whosJoining: "Qui participe ?",
416
+ suggestedTimes: "Horaires sugg\xE9r\xE9s",
417
+ viewSuggestions: "Voir les suggestions",
418
+ whereWillItBe: "O\xF9 aura lieu ?",
419
+ location: "Entrer une adresse",
420
+ descriptionAndAttachments: "Description",
421
+ dragAndDrop: "Glisser-d\xE9poser des fichiers ou",
422
+ guests: "Invit\xE9s",
423
+ addGuests: "Ajouter des invit\xE9s",
424
+ addAttachment: "T\xE9l\xE9charger des fichiers",
425
+ moreOptions: "Plus d'options",
426
+ doesNotRepeat: "Ne se r\xE9p\xE8te pas",
427
+ locationHelpText: 'Entrez une adresse physique (ex: "123 Rue Principale") pour g\xE9n\xE9rer la carte.',
428
+ notes: "Notes...",
429
+ guestCount: "invit\xE9",
430
+ guestsCount: "invit\xE9s",
431
+ repeatFor: "R\xE9p\xE9ter pendant",
432
+ times: "fois",
433
+ until: "Jusqu'au",
434
+ endRepeat: "Fin de r\xE9p\xE9tition",
435
+ never: "Jamais",
436
+ afterOccurrences: "Apr\xE8s occurrences",
437
+ onDate: "\xC0 la date",
438
+ tomorrow: "Demain",
439
+ edit: "Modifier",
440
+ duplicate: "Dupliquer",
441
+ eventSingular: "\xE9v\xE9nement",
442
+ eventPlural: "\xE9v\xE9nements",
443
+ allCaughtUp: "Tout est \xE0 jour !",
444
+ noEventsScheduled: "Aucun \xE9v\xE9nement pr\xE9vu",
445
+ noUpcoming: "Votre planning est libre",
446
+ noUpcomingHint: "Aucun \xE9v\xE9nement \xE0 venir. Cr\xE9ez un nouvel \xE9v\xE9nement pour commencer \xE0 planifier."
447
+ };
448
+
449
+ // src/core/format.ts
450
+ var clockCache = /* @__PURE__ */ new WeakMap();
451
+ function uses24HourClock(locale) {
452
+ if (!locale) return false;
453
+ let is24 = clockCache.get(locale);
454
+ if (is24 === void 0) {
455
+ is24 = !/[ap]\.?m/i.test((0, import_date_fns4.format)(new Date(2e3, 0, 1, 13, 0), "p", { locale }));
456
+ clockCache.set(locale, is24);
457
+ }
458
+ return is24;
459
+ }
460
+ var H24 = { hour: "H:mm", time: "H:mm", compact: "H:mm" };
461
+ var H12 = { hour: "h a", time: "h:mm a", compact: "h:mm" };
462
+ function timeFormats(locale) {
463
+ return uses24HourClock(locale) ? H24 : H12;
464
+ }
465
+ var MAX_FORMATTERS = 50;
466
+ var formatters = /* @__PURE__ */ new Map();
467
+ function getDateTimeFormat(locale, timeZone, options) {
468
+ const key = `${locale}|${timeZone ?? ""}|${JSON.stringify(options)}`;
469
+ let formatter = formatters.get(key);
470
+ if (formatter) {
471
+ formatters.delete(key);
472
+ } else {
473
+ formatter = new Intl.DateTimeFormat(locale, { ...options, timeZone });
474
+ if (formatters.size >= MAX_FORMATTERS) {
475
+ formatters.delete(formatters.keys().next().value);
476
+ }
477
+ }
478
+ formatters.set(key, formatter);
479
+ return formatter;
480
+ }
481
+ function timeZoneAbbreviation(date, timeZone) {
482
+ try {
483
+ return getDateTimeFormat("en-US", timeZone, { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "";
484
+ } catch {
485
+ return "";
486
+ }
487
+ }
488
+ function resolveTranslations(language, overrides) {
489
+ return { ...language === "fr" ? fr : en, ...overrides };
490
+ }
491
+
492
+ // src/components/Sidebar.tsx
300
493
  var Sidebar = ({
301
494
  currentDate,
302
495
  onDateChange,
@@ -327,14 +520,7 @@ var Sidebar = ({
327
520
  { id: "3", label: "Tasks", color: "#6366f1", active: true }
328
521
  ];
329
522
  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
- };
523
+ const getAcronym = (tz) => !tz || !now ? "LOC" : timeZoneAbbreviation(now, tz);
338
524
  const timezones = [
339
525
  { value: "", label: translations?.localTime || "Local Time", acronym: "LOC" },
340
526
  { value: "UTC", label: "UTC", acronym: "UTC" },
@@ -358,14 +544,11 @@ var Sidebar = ({
358
544
  let acronym = tz.acronym;
359
545
  try {
360
546
  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
- }
547
+ time = (0, import_date_fns5.format)(now, "HH:mm");
548
+ acronym = timeZoneAbbreviation(now) || "LOC";
366
549
  } else {
367
550
  const zDate = (0, import_date_fns_tz.toZonedTime)(now, tz.value);
368
- time = (0, import_date_fns4.format)(zDate, "HH:mm");
551
+ time = (0, import_date_fns5.format)(zDate, "HH:mm");
369
552
  const dynAcronym = getAcronym(tz.value);
370
553
  if (dynAcronym && !dynAcronym.includes("GMT") && !dynAcronym.includes("Time")) {
371
554
  acronym = dynAcronym;
@@ -458,31 +641,197 @@ var Sidebar = ({
458
641
 
459
642
  // src/views/MonthView.tsx
460
643
  var import_react6 = __toESM(require("react"));
461
- var import_date_fns5 = require("date-fns");
644
+ var import_date_fns8 = require("date-fns");
645
+
646
+ // src/core/events.ts
647
+ var import_date_fns7 = require("date-fns");
648
+
649
+ // src/core/timezone.ts
650
+ var import_date_fns6 = require("date-fns");
462
651
  var import_date_fns_tz2 = require("date-fns-tz");
463
652
 
653
+ // src/core/warn.ts
654
+ var warned = /* @__PURE__ */ new Set();
655
+ function isProductionBuild() {
656
+ try {
657
+ return process.env.NODE_ENV === "production";
658
+ } catch {
659
+ return false;
660
+ }
661
+ }
662
+ function warnOnce(key, message) {
663
+ if (isProductionBuild() || warned.has(key)) return;
664
+ warned.add(key);
665
+ console.warn(`[calendarkit-pro] ${message}`);
666
+ }
667
+
668
+ // src/core/timezone.ts
669
+ var validity = /* @__PURE__ */ new Map();
670
+ function resolveTimeZone(tz) {
671
+ if (!tz) return void 0;
672
+ let valid = validity.get(tz);
673
+ if (valid === void 0) {
674
+ try {
675
+ new Intl.DateTimeFormat("en-US", { timeZone: tz });
676
+ valid = true;
677
+ } catch {
678
+ valid = false;
679
+ }
680
+ validity.set(tz, valid);
681
+ }
682
+ if (!valid) {
683
+ warnOnce(`invalid-timezone:${tz}`, `Unknown timezone "${tz}", falling back to local time.`);
684
+ return void 0;
685
+ }
686
+ return tz;
687
+ }
688
+ function toDisplay(date, tz) {
689
+ const zone = resolveTimeZone(tz);
690
+ return zone ? (0, import_date_fns_tz2.toZonedTime)(date, zone) : date;
691
+ }
692
+ function fromDisplay(wall, tz) {
693
+ const zone = resolveTimeZone(tz);
694
+ return zone ? (0, import_date_fns_tz2.fromZonedTime)(wall, zone) : new Date(wall);
695
+ }
696
+ function minutesSinceDisplayMidnight(date, tz) {
697
+ const wall = toDisplay(date, tz);
698
+ return wall.getHours() * 60 + wall.getMinutes();
699
+ }
700
+ function isSameDisplayDay(date, day, tz) {
701
+ return (0, import_date_fns6.isSameDay)(toDisplay(date, tz), day);
702
+ }
703
+ function displayToday(tz, now = /* @__PURE__ */ new Date()) {
704
+ return (0, import_date_fns6.startOfDay)(toDisplay(now, tz));
705
+ }
706
+
707
+ // src/core/events.ts
708
+ var toDate = (value) => value === void 0 || value === null ? void 0 : value instanceof Date ? value : new Date(value);
709
+ var isValidDate = (date) => !!date && !Number.isNaN(date.getTime());
710
+ var normalizedEvent = /* @__PURE__ */ new WeakMap();
711
+ var normalizedList = /* @__PURE__ */ new WeakMap();
712
+ function normalizeEvent(event) {
713
+ const cached = normalizedEvent.get(event);
714
+ if (cached !== void 0) return cached;
715
+ const start = toDate(event.start);
716
+ const end = toDate(event.end);
717
+ let result = null;
718
+ if (!isValidDate(start) || !isValidDate(end)) {
719
+ warnOnce(`invalid-date:${event.id}`, `Event "${event.id}" has an invalid start or end date and was skipped.`);
720
+ } else if (end.getTime() < start.getTime()) {
721
+ warnOnce(`end-before-start:${event.id}`, `Event "${event.id}" ends before it starts and was skipped.`);
722
+ } else {
723
+ const needsCopy = start !== event.start || end !== event.end || needsRecurrenceCopy(event);
724
+ result = needsCopy ? {
725
+ ...event,
726
+ start,
727
+ end,
728
+ recurrence: event.recurrence && {
729
+ ...event.recurrence,
730
+ until: toDate(event.recurrence.until),
731
+ exdates: event.recurrence.exdates?.map((d) => toDate(d))
732
+ }
733
+ } : event;
734
+ }
735
+ normalizedEvent.set(event, result);
736
+ return result;
737
+ }
738
+ function needsRecurrenceCopy(event) {
739
+ const r = event.recurrence;
740
+ if (!r) return false;
741
+ return r.until !== void 0 && !(r.until instanceof Date) || !!r.exdates?.some((d) => !(d instanceof Date));
742
+ }
743
+ function normalizeEvents(events) {
744
+ const cached = normalizedList.get(events);
745
+ if (cached) return cached;
746
+ const seen = /* @__PURE__ */ new Set();
747
+ const result = [];
748
+ for (const event of events) {
749
+ const normalized = normalizeEvent(event);
750
+ if (!normalized) continue;
751
+ if (seen.has(normalized.id)) {
752
+ warnOnce(`duplicate-id:${normalized.id}`, `Duplicate event id "${normalized.id}": ids must be unique.`);
753
+ }
754
+ seen.add(normalized.id);
755
+ result.push(normalized);
756
+ }
757
+ normalizedList.set(events, result);
758
+ return result;
759
+ }
760
+ var dayKey = (wall) => (0, import_date_fns7.format)(wall, "yyyy-MM-dd");
761
+ function memoByZone(cache, events, tz, compute) {
762
+ let byZone = cache.get(events);
763
+ if (!byZone) {
764
+ byZone = /* @__PURE__ */ new Map();
765
+ cache.set(events, byZone);
766
+ }
767
+ const key = tz ?? "";
768
+ let value = byZone.get(key);
769
+ if (value === void 0) {
770
+ value = compute();
771
+ byZone.set(key, value);
772
+ }
773
+ return value;
774
+ }
775
+ var startDayCache = /* @__PURE__ */ new WeakMap();
776
+ function eventsByStartDay(events, tz) {
777
+ return memoByZone(startDayCache, events, tz, () => {
778
+ const map = /* @__PURE__ */ new Map();
779
+ for (const event of events) {
780
+ const key = dayKey(toDisplay(event.start, tz));
781
+ const bucket = map.get(key);
782
+ if (bucket) bucket.push(event);
783
+ else map.set(key, [event]);
784
+ }
785
+ return map;
786
+ });
787
+ }
788
+ var segmentCache = /* @__PURE__ */ new WeakMap();
789
+ function segmentsByDisplayDay(events, tz, range) {
790
+ const cacheKey = range ? `${tz ?? ""}|${range.start.getTime()}|${range.end.getTime()}` : tz;
791
+ return memoByZone(segmentCache, events, cacheKey, () => {
792
+ const map = /* @__PURE__ */ new Map();
793
+ for (const event of events) {
794
+ const wallStart = toDisplay(event.start, tz);
795
+ const rawEnd = toDisplay(event.end, tz);
796
+ const wallEnd = rawEnd < wallStart ? wallStart : rawEnd;
797
+ if (range && (wallEnd < range.start || wallStart >= range.end)) continue;
798
+ let day = (0, import_date_fns7.startOfDay)(range && wallStart < range.start ? range.start : wallStart);
799
+ const stop = range && wallEnd > range.end ? range.end : wallEnd;
800
+ do {
801
+ const next = (0, import_date_fns7.addDays)(day, 1);
802
+ const start = wallStart > day ? wallStart : day;
803
+ const end = wallEnd < next ? wallEnd : next;
804
+ if (end > start || wallStart.getTime() === wallEnd.getTime()) {
805
+ const key = dayKey(day);
806
+ const bucket = map.get(key);
807
+ const segment = { event, start, end };
808
+ if (bucket) bucket.push(segment);
809
+ else map.set(key, [segment]);
810
+ }
811
+ day = next;
812
+ } while (day < stop);
813
+ }
814
+ return map;
815
+ });
816
+ }
817
+
464
818
  // src/components/dnd/DraggableEvent.tsx
465
819
  var import_react4 = __toESM(require("react"));
466
820
  var import_core = require("@dnd-kit/core");
467
821
  var import_utilities = require("@dnd-kit/utilities");
468
- var DraggableEvent = ({ event, children, className, style: propStyle, ...props }) => {
822
+ var DraggableEventImpl = ({ event, dragId, grabOffsetMs, children, className, style: propStyle, ...props }) => {
469
823
  const dragDisabled = isEventDragDisabled(event);
470
824
  const { attributes, listeners, setNodeRef, transform, isDragging } = (0, import_core.useDraggable)({
471
- id: event.id,
472
- data: { event },
825
+ id: dragId ?? event.id,
826
+ data: { event, grabOffsetMs },
473
827
  disabled: dragDisabled
474
828
  });
475
829
  const style = {
476
830
  ...propStyle,
477
831
  transform: import_utilities.CSS.Translate.toString(transform),
478
832
  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.
833
+ // Hide the original while dragging: the DragOverlay ghost is what the user follows.
484
834
  opacity: isDragging ? 0 : propStyle?.opacity
485
- // Hiding original element completely
486
835
  };
487
836
  return /* @__PURE__ */ import_react4.default.createElement(
488
837
  "div",
@@ -497,33 +846,56 @@ var DraggableEvent = ({ event, children, className, style: propStyle, ...props }
497
846
  children
498
847
  );
499
848
  };
849
+ var DraggableEvent = import_react4.default.memo(DraggableEventImpl);
500
850
 
501
851
  // src/components/dnd/DroppableCell.tsx
502
852
  var import_react5 = __toESM(require("react"));
503
853
  var import_core2 = require("@dnd-kit/core");
504
- var DroppableCell = ({ id, date, resourceId, children, className, style, onClick }) => {
854
+ var QUARTER_HOVER = [
855
+ "hover:bg-blue-50/50 dark:hover:bg-blue-900/10",
856
+ "hover:bg-blue-50/80 dark:hover:bg-blue-900/20",
857
+ "hover:bg-blue-100/50 dark:hover:bg-blue-900/30",
858
+ "hover:bg-blue-100/80 dark:hover:bg-blue-900/40"
859
+ ];
860
+ var QUARTER_ACTIVE = [
861
+ "bg-blue-50/50 dark:bg-blue-900/10 ring-2 ring-primary ring-inset",
862
+ "bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-primary ring-inset",
863
+ "bg-blue-100/50 dark:bg-blue-900/30 ring-2 ring-primary ring-inset",
864
+ "bg-blue-100/80 dark:bg-blue-900/40 ring-2 ring-primary ring-inset"
865
+ ];
866
+ var quarterOf = (date) => Math.min(3, Math.floor(date.getMinutes() / 15));
867
+ var DroppableCellImpl = ({ id, date, resourceId, children, className, style, onClick, onSlotClick }) => {
505
868
  const { isOver, setNodeRef } = (0, import_core2.useDroppable)({
506
869
  id,
507
870
  data: { date, resourceId }
508
871
  });
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";
872
+ const quarter = quarterOf(date);
512
873
  return /* @__PURE__ */ import_react5.default.createElement(
513
874
  "div",
514
875
  {
515
876
  ref: setNodeRef,
516
877
  className: cn(
517
878
  className,
518
- quarterClass,
519
- isOver && activeQuarterClass
879
+ QUARTER_HOVER[quarter],
880
+ isOver && QUARTER_ACTIVE[quarter],
881
+ onSlotClick && "cursor-pointer"
520
882
  ),
521
883
  style,
522
- onClick
884
+ onClick: onSlotClick ? () => onSlotClick(date, resourceId) : onClick
523
885
  },
524
886
  children
525
887
  );
526
888
  };
889
+ var shallowEqualStyle = (a, b) => {
890
+ if (a === b) return true;
891
+ if (!a || !b) return false;
892
+ const keys = Object.keys(a);
893
+ return keys.length === Object.keys(b).length && keys.every((key) => a[key] === b[key]);
894
+ };
895
+ var DroppableCell = import_react5.default.memo(
896
+ DroppableCellImpl,
897
+ (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)
898
+ );
527
899
 
528
900
  // src/views/MonthView.tsx
529
901
  var EventItem = import_react6.default.memo(({ event, onEventClick }) => /* @__PURE__ */ import_react6.default.createElement(DraggableEvent, { event }, /* @__PURE__ */ import_react6.default.createElement(
@@ -555,42 +927,22 @@ var MonthView = ({
555
927
  timezone,
556
928
  locale
557
929
  }) => {
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]);
930
+ const weekStartsOn = resolveWeekStartsOn(locale, 1);
931
+ const days = (0, import_react6.useMemo)(() => getMonthGrid(currentDate, weekStartsOn), [currentDate, weekStartsOn]);
932
+ const weekDays = (0, import_react6.useMemo)(() => getWeekDays(currentDate, weekStartsOn), [currentDate, weekStartsOn]);
933
+ const eventsByDay = eventsByStartDay(events, timezone);
934
+ const today = displayToday(timezone);
583
935
  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
936
  "div",
585
937
  {
586
938
  key: day.toISOString(),
587
939
  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
940
  },
589
- (0, import_date_fns5.format)(day, "EEE", { locale })
941
+ (0, import_date_fns8.format)(day, "EEE", { locale })
590
942
  ))), /* @__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);
943
+ const dayKey2 = (0, import_date_fns8.format)(day, "yyyy-MM-dd");
944
+ const dayEvents = eventsByDay.get(dayKey2) || [];
945
+ const isCurrentMonth = (0, import_date_fns8.isSameMonth)(day, currentDate);
594
946
  const cellId = day.toISOString();
595
947
  return /* @__PURE__ */ import_react6.default.createElement(
596
948
  DroppableCell,
@@ -601,26 +953,87 @@ var MonthView = ({
601
953
  className: cn(
602
954
  "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
955
  !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"
956
+ (0, import_date_fns8.isSameDay)(day, today) && "bg-primary/5 ring-1 ring-inset ring-primary/20"
605
957
  ),
606
958
  onClick: () => onDateClick?.(day)
607
959
  },
608
960
  /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex justify-between items-start" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: cn(
609
961
  "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"))
962
+ (0, import_date_fns8.isSameDay)(day, today) ? "bg-primary text-primary-foreground shadow-md shadow-primary/30" : "group-hover:bg-accent"
963
+ ) }, (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)),
964
+ /* @__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 })), 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"))
613
965
  );
614
966
  }))));
615
967
  };
616
968
 
617
969
  // src/views/WeekView.tsx
618
970
  var import_react8 = __toESM(require("react"));
619
- var import_date_fns6 = require("date-fns");
620
- var import_date_fns_tz3 = require("date-fns-tz");
971
+ var import_date_fns9 = require("date-fns");
972
+
973
+ // src/core/layout.ts
974
+ var layoutCache = /* @__PURE__ */ new WeakMap();
975
+ function layoutDayEvents(segments) {
976
+ const cached = layoutCache.get(segments);
977
+ if (cached) return cached;
978
+ const sorted = [...segments].sort(
979
+ (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)
980
+ );
981
+ const result = [];
982
+ let cluster = [];
983
+ let columnEnds = [];
984
+ let clusterEnd = -Infinity;
985
+ const closeCluster = () => {
986
+ for (const placed of cluster) placed.columns = columnEnds.length;
987
+ cluster = [];
988
+ columnEnds = [];
989
+ };
990
+ for (const segment of sorted) {
991
+ const start = segment.start.getTime();
992
+ const end = Math.max(segment.end.getTime(), start + 1);
993
+ if (start >= clusterEnd) closeCluster();
994
+ let column = columnEnds.findIndex((columnEnd) => columnEnd <= start);
995
+ if (column === -1) {
996
+ column = columnEnds.length;
997
+ columnEnds.push(end);
998
+ } else {
999
+ columnEnds[column] = end;
1000
+ }
1001
+ const placed = { ...segment, column, columns: 1 };
1002
+ cluster.push(placed);
1003
+ result.push(placed);
1004
+ clusterEnd = Math.max(clusterEnd, end);
1005
+ }
1006
+ closeCluster();
1007
+ layoutCache.set(segments, result);
1008
+ return result;
1009
+ }
621
1010
 
622
1011
  // src/components/dnd/ResizableEvent.tsx
623
1012
  var import_react7 = __toESM(require("react"));
1013
+
1014
+ // src/core/dnd.ts
1015
+ function computeDrop({ event, overDate, overResourceId, grabOffsetMs = 0, view, tz }) {
1016
+ let wallStart;
1017
+ if (view === "month") {
1018
+ const original = toDisplay(event.start, tz);
1019
+ wallStart = new Date(overDate);
1020
+ wallStart.setHours(original.getHours(), original.getMinutes(), original.getSeconds(), original.getMilliseconds());
1021
+ } else {
1022
+ wallStart = new Date(overDate.getTime() - grabOffsetMs);
1023
+ wallStart.setSeconds(0, 0);
1024
+ }
1025
+ const start = fromDisplay(wallStart, tz);
1026
+ const end = new Date(start.getTime() + (event.end.getTime() - event.start.getTime()));
1027
+ const resourceId = overResourceId ?? event.resourceId;
1028
+ if (start.getTime() === event.start.getTime() && resourceId === event.resourceId) return null;
1029
+ return { start, end, resourceId };
1030
+ }
1031
+ function computeResizeEnd(event, deltaMinutes, minDuration = 15) {
1032
+ const minEnd = event.start.getTime() + minDuration * 6e4;
1033
+ return new Date(Math.max(event.end.getTime() + deltaMinutes * 6e4, minEnd));
1034
+ }
1035
+
1036
+ // src/components/dnd/ResizableEvent.tsx
624
1037
  var ResizableEvent = ({
625
1038
  event,
626
1039
  hourHeight,
@@ -662,14 +1075,8 @@ var ResizableEvent = ({
662
1075
  setIsResizing(false);
663
1076
  const finalHeight = resizeHeightRef.current;
664
1077
  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
- }
1078
+ const minutesDiff = (finalHeight - startHeightRef.current) / hourHeight * 60;
1079
+ onResize(event, computeResizeEnd(event, minutesDiff, minDuration));
673
1080
  }
674
1081
  resizeHeightRef.current = null;
675
1082
  setResizeHeight(null);
@@ -720,7 +1127,16 @@ var ResizableEvent = ({
720
1127
  };
721
1128
 
722
1129
  // src/views/WeekView.tsx
723
- var WeekView = ({
1130
+ var HOUR_HEIGHT = 60;
1131
+ var HOURS = Array.from({ length: 24 }, (_, i) => i);
1132
+ var QUARTERS = [0, 15, 30, 45];
1133
+ var NO_SEGMENTS = [];
1134
+ var slotDate = (day, hour, minute) => {
1135
+ const date = new Date(day);
1136
+ date.setHours(hour, minute, 0, 0);
1137
+ return date;
1138
+ };
1139
+ var WeekViewImpl = ({
724
1140
  currentDate,
725
1141
  events,
726
1142
  onEventClick,
@@ -730,14 +1146,16 @@ var WeekView = ({
730
1146
  locale,
731
1147
  readonly
732
1148
  }) => {
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;
1149
+ const weekDays = import_react8.default.useMemo(
1150
+ () => getWeekDays(currentDate, resolveWeekStartsOn(locale, 1)),
1151
+ [currentDate, locale]
1152
+ );
1153
+ const visibleRange = import_react8.default.useMemo(
1154
+ () => ({ start: weekDays[0], end: (0, import_date_fns9.addDays)(weekDays[weekDays.length - 1], 1) }),
1155
+ [weekDays]
1156
+ );
739
1157
  const scrollContainerRef = import_react8.default.useRef(null);
740
- const [now, setNow] = import_react8.default.useState(/* @__PURE__ */ new Date());
1158
+ const [now, setNow] = import_react8.default.useState(() => /* @__PURE__ */ new Date());
741
1159
  import_react8.default.useEffect(() => {
742
1160
  const interval = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
743
1161
  return () => clearInterval(interval);
@@ -745,132 +1163,93 @@ var WeekView = ({
745
1163
  import_react8.default.useEffect(() => {
746
1164
  if (scrollContainerRef.current) {
747
1165
  const offsetHour = 4;
748
- scrollContainerRef.current.scrollTop = ((/* @__PURE__ */ new Date()).getHours() - offsetHour) * hourHeight;
1166
+ scrollContainerRef.current.scrollTop = (toDisplay(/* @__PURE__ */ new Date(), timezone).getHours() - offsetHour) * HOUR_HEIGHT;
749
1167
  }
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";
1168
+ }, [timezone]);
1169
+ const segmentsByDay = segmentsByDisplayDay(events, timezone, visibleRange);
1170
+ const today = displayToday(timezone, now);
1171
+ const zonedNow = toDisplay(now, timezone);
1172
+ const nowTop = minutesSinceDisplayMidnight(now, timezone) / 60 * HOUR_HEIGHT;
1173
+ const showsToday = weekDays.some((day) => (0, import_date_fns9.isSameDay)(day, today));
1174
+ const zone = resolveTimeZone(timezone);
1175
+ const zoneLabel = timeZoneAbbreviation(now, zone) || zone || "LOC";
1176
+ const formats = timeFormats(locale);
782
1177
  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
1178
  "div",
784
1179
  {
785
1180
  ref: scrollContainerRef,
786
1181
  className: "flex-1 overflow-y-auto scrollbar-hide relative bg-background scroll-smooth"
787
1182
  },
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(
1183
+ /* @__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
1184
  "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 })))))),
1185
+ (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"
1186
+ ) }, (0, import_date_fns9.format)(day, "d", { locale })))))),
792
1187
  /* @__PURE__ */ import_react8.default.createElement(
793
1188
  "div",
794
1189
  {
795
1190
  className: "flex min-w-full relative",
796
- style: { height: hours.length * hourHeight }
1191
+ style: { height: HOURS.length * HOUR_HEIGHT }
797
1192
  },
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(
1193
+ /* @__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
1194
  "div",
800
1195
  {
801
1196
  key: hour,
802
1197
  className: "relative w-full text-[11px] text-muted-foreground/80 text-right pr-3 font-medium tabular-nums box-border",
803
- style: { height: hourHeight }
1198
+ style: { height: HOUR_HEIGHT }
804
1199
  },
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
- ))),
1200
+ /* @__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 }))
1201
+ )), showsToday && /* @__PURE__ */ import_react8.default.createElement(
1202
+ "div",
1203
+ {
1204
+ className: "absolute left-0 w-full pointer-events-none z-30 flex justify-end pr-2",
1205
+ style: { top: `${nowTop}px` }
1206
+ },
1207
+ /* @__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 }))
1208
+ )),
807
1209
  /* @__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
- }
1210
+ const dayKey2 = (0, import_date_fns9.format)(day, "yyyy-MM-dd");
1211
+ const positioned = layoutDayEvents(segmentsByDay.get(dayKey2) ?? NO_SEGMENTS);
1212
+ 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(
1213
+ "div",
1214
+ {
1215
+ key: hour,
1216
+ className: "w-full border-b-[0.5px] border-dashed border-border/20 box-border relative hover:bg-accent/5 transition-colors",
1217
+ style: { height: HOUR_HEIGHT }
1218
+ },
1219
+ QUARTERS.map((minute) => {
1220
+ const cellDate = slotDate(day, hour, minute);
1221
+ return /* @__PURE__ */ import_react8.default.createElement(
1222
+ DroppableCell,
1223
+ {
1224
+ key: minute,
1225
+ id: cellDate.toISOString(),
1226
+ date: cellDate,
1227
+ className: "w-full absolute left-0 right-0 z-0 transition-colors",
1228
+ style: {
1229
+ height: "25%",
1230
+ top: `${minute / 60 * 100}%`
835
1231
  },
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;
1232
+ onSlotClick: onTimeSlotClick
1233
+ }
1234
+ );
1235
+ })
1236
+ )), positioned.map(({ event, start, end, column, columns }) => {
1237
+ const widthPercent = 100 / columns;
1238
+ const leftPercent = column * widthPercent;
1239
+ const zonedEventStart = toDisplay(event.start, timezone);
1240
+ const zonedEventEnd = toDisplay(event.end, timezone);
1241
+ const startMinutes = start.getHours() * 60 + start.getMinutes();
1242
+ const durationMinutes = (0, import_date_fns9.differenceInMinutes)(end, start);
1243
+ const top = startMinutes / 60 * HOUR_HEIGHT;
1244
+ const height = durationMinutes / 60 * HOUR_HEIGHT;
869
1245
  const isShortEvent = durationMinutes < 60;
1246
+ const isLastSegment = end.getTime() === zonedEventEnd.getTime();
870
1247
  return /* @__PURE__ */ import_react8.default.createElement(
871
1248
  DraggableEvent,
872
1249
  {
873
- key: `${event.id}-${day.toISOString()}`,
1250
+ key: `${event.id}-${dayKey2}`,
1251
+ dragId: `${event.id}@${dayKey2}`,
1252
+ grabOffsetMs: start.getTime() - zonedEventStart.getTime(),
874
1253
  event,
875
1254
  className: `absolute z-10 transition-all ${readonly ? "cursor-default" : ""}`,
876
1255
  style: {
@@ -879,15 +1258,15 @@ var WeekView = ({
879
1258
  left: `${leftPercent}%`,
880
1259
  width: `${widthPercent}%`,
881
1260
  // Add minimal spacing between overlapping events
882
- paddingRight: count > 1 ? "2px" : "0"
1261
+ paddingRight: columns > 1 ? "2px" : "0"
883
1262
  }
884
1263
  },
885
1264
  /* @__PURE__ */ import_react8.default.createElement(
886
1265
  ResizableEvent,
887
1266
  {
888
- readonly,
1267
+ readonly: readonly || !isLastSegment,
889
1268
  event,
890
- hourHeight,
1269
+ hourHeight: HOUR_HEIGHT,
891
1270
  onResize: onEventResize,
892
1271
  className: "h-full",
893
1272
  style: { height: "100%" }
@@ -902,7 +1281,7 @@ var WeekView = ({
902
1281
  !event.color && "border-primary/20 bg-primary/10",
903
1282
  isShortEvent ? "px-1 flex items-center justify-center" : "p-2",
904
1283
  // Add active border for overlapped events to distinguish them
905
- count > 1 && "border-l-4 border-l-primary/50"
1284
+ columns > 1 && "border-l-4 border-l-primary/50"
906
1285
  ),
907
1286
  style: {
908
1287
  height: "100%",
@@ -915,46 +1294,43 @@ var WeekView = ({
915
1294
  e.stopPropagation();
916
1295
  onEventClick?.(event);
917
1296
  },
918
- title: count > 1 ? `${event.title} (${index + 1}/${count})` : void 0
1297
+ title: columns > 1 ? `${event.title} (${column + 1}/${columns})` : void 0
919
1298
  },
920
1299
  /* @__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
1300
  "font-semibold truncate text-foreground/90 leading-tight",
922
1301
  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))
1302
+ ) }, 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
1303
  )
925
1304
  )
926
1305
  );
927
- }), (0, import_date_fns6.isToday)(day) && /* @__PURE__ */ import_react8.default.createElement(
1306
+ }), (0, import_date_fns9.isSameDay)(day, today) && /* @__PURE__ */ import_react8.default.createElement(
928
1307
  "div",
929
1308
  {
930
1309
  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
- }
1310
+ style: { top: `${nowTop}px` }
934
1311
  },
935
1312
  /* @__PURE__ */ import_react8.default.createElement("div", { className: "h-[2px] w-full bg-gradient-to-r from-primary via-primary to-primary/50" }),
936
1313
  /* @__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
1314
  ));
938
1315
  }))
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
1316
  )
950
1317
  ));
951
1318
  };
1319
+ var WeekView = import_react8.default.memo(WeekViewImpl);
952
1320
 
953
1321
  // src/views/DayView.tsx
954
1322
  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 = ({
1323
+ var import_date_fns10 = require("date-fns");
1324
+ var HOUR_HEIGHT2 = 80;
1325
+ var HOURS2 = Array.from({ length: 24 }, (_, i) => i);
1326
+ var QUARTERS2 = [0, 15, 30, 45];
1327
+ var NO_SEGMENTS2 = [];
1328
+ var slotDate2 = (day, hour, minute) => {
1329
+ const date = new Date(day);
1330
+ date.setHours(hour, minute, 0, 0);
1331
+ return date;
1332
+ };
1333
+ var DayViewImpl = ({
958
1334
  currentDate,
959
1335
  events,
960
1336
  onEventClick,
@@ -964,10 +1340,12 @@ var DayView = ({
964
1340
  locale,
965
1341
  readonly
966
1342
  }) => {
967
- const hours = Array.from({ length: 24 }, (_, i) => i);
968
- const hourHeight = 80;
1343
+ const visibleRange = import_react9.default.useMemo(() => {
1344
+ const start = (0, import_date_fns10.startOfDay)(currentDate);
1345
+ return { start, end: (0, import_date_fns10.addDays)(start, 1) };
1346
+ }, [currentDate]);
969
1347
  const scrollContainerRef = import_react9.default.useRef(null);
970
- const [now, setNow] = import_react9.default.useState(/* @__PURE__ */ new Date());
1348
+ const [now, setNow] = import_react9.default.useState(() => /* @__PURE__ */ new Date());
971
1349
  import_react9.default.useEffect(() => {
972
1350
  const interval = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
973
1351
  return () => clearInterval(interval);
@@ -975,99 +1353,71 @@ var DayView = ({
975
1353
  import_react9.default.useEffect(() => {
976
1354
  if (scrollContainerRef.current) {
977
1355
  const offsetHour = 3;
978
- scrollContainerRef.current.scrollTop = ((/* @__PURE__ */ new Date()).getHours() - offsetHour) * hourHeight;
1356
+ scrollContainerRef.current.scrollTop = (toDisplay(/* @__PURE__ */ new Date(), timezone).getHours() - offsetHour) * HOUR_HEIGHT2;
979
1357
  }
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(
1358
+ }, [timezone]);
1359
+ const dayKey2 = (0, import_date_fns10.format)(currentDate, "yyyy-MM-dd");
1360
+ const positioned = layoutDayEvents(segmentsByDisplayDay(events, timezone, visibleRange).get(dayKey2) ?? NO_SEGMENTS2);
1361
+ const isToday2 = (0, import_date_fns10.isSameDay)(currentDate, displayToday(timezone, now));
1362
+ const zonedNow = toDisplay(now, timezone);
1363
+ const nowTop = minutesSinceDisplayMidnight(now, timezone) / 60 * HOUR_HEIGHT2;
1364
+ const formats = timeFormats(locale);
1365
+ 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" }, "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
1366
  "div",
995
1367
  {
996
1368
  key: hour,
997
1369
  className: "relative w-full",
998
- style: { height: hourHeight }
1370
+ style: { height: HOUR_HEIGHT2 }
999
1371
  },
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(
1372
+ 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 }))
1373
+ )), isToday2 && /* @__PURE__ */ import_react9.default.createElement(
1002
1374
  "div",
1003
1375
  {
1004
1376
  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
- }
1377
+ style: { top: `${nowTop}px` }
1008
1378
  },
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
- }
1379
+ /* @__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 }))
1380
+ )), /* @__PURE__ */ import_react9.default.createElement("div", { className: "flex-1 relative" }, HOURS2.map((hour) => /* @__PURE__ */ import_react9.default.createElement(
1381
+ "div",
1382
+ {
1383
+ key: hour,
1384
+ className: "border-b-[0.5px] border-dashed border-border/20 box-border relative hover:bg-accent/5 transition-colors",
1385
+ style: { height: HOUR_HEIGHT2 }
1386
+ },
1387
+ QUARTERS2.map((minute) => {
1388
+ const cellDate = slotDate2(currentDate, hour, minute);
1389
+ return /* @__PURE__ */ import_react9.default.createElement(
1390
+ DroppableCell,
1391
+ {
1392
+ key: minute,
1393
+ id: cellDate.toISOString(),
1394
+ date: cellDate,
1395
+ className: "w-full absolute left-0 right-0 z-0 transition-colors",
1396
+ style: {
1397
+ height: "25%",
1398
+ top: `${minute / 60 * 100}%`
1033
1399
  },
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;
1400
+ onSlotClick: onTimeSlotClick
1401
+ }
1402
+ );
1403
+ })
1404
+ )), positioned.map(({ event, start, end, column, columns }) => {
1405
+ const widthPercent = 100 / columns;
1406
+ const leftPercent = column * widthPercent;
1407
+ const zonedStart = toDisplay(event.start, timezone);
1408
+ const zonedEnd = toDisplay(event.end, timezone);
1409
+ const startMinutes = start.getHours() * 60 + start.getMinutes();
1410
+ const durationMinutes = (0, import_date_fns10.differenceInMinutes)(end, start);
1411
+ const top = startMinutes / 60 * HOUR_HEIGHT2;
1412
+ const height = durationMinutes / 60 * HOUR_HEIGHT2;
1066
1413
  const isShortEvent = durationMinutes < 45;
1414
+ const isLastSegment = end.getTime() === zonedEnd.getTime();
1067
1415
  return /* @__PURE__ */ import_react9.default.createElement(
1068
1416
  DraggableEvent,
1069
1417
  {
1070
- key: `${event.id}-${currentDate.toISOString()}`,
1418
+ key: `${event.id}-${dayKey2}`,
1419
+ dragId: `${event.id}@${dayKey2}`,
1420
+ grabOffsetMs: start.getTime() - zonedStart.getTime(),
1071
1421
  event,
1072
1422
  className: `absolute z-10 transition-all ${readonly ? "cursor-default" : ""}`,
1073
1423
  style: {
@@ -1075,15 +1425,15 @@ var DayView = ({
1075
1425
  height: `${Math.max(height, 28)}px`,
1076
1426
  left: `calc(${leftPercent}% + 4px)`,
1077
1427
  width: `calc(${widthPercent}% - 8px)`,
1078
- paddingRight: count > 1 ? "2px" : "0"
1428
+ paddingRight: columns > 1 ? "2px" : "0"
1079
1429
  }
1080
1430
  },
1081
1431
  /* @__PURE__ */ import_react9.default.createElement(
1082
1432
  ResizableEvent,
1083
1433
  {
1084
- readonly,
1434
+ readonly: readonly || !isLastSegment,
1085
1435
  event,
1086
- hourHeight,
1436
+ hourHeight: HOUR_HEIGHT2,
1087
1437
  onResize: onEventResize,
1088
1438
  className: "h-full",
1089
1439
  style: { height: "100%" }
@@ -1097,44 +1447,43 @@ var DayView = ({
1097
1447
  readonly || isEventDragDisabled(event) ? "cursor-default" : "cursor-grab active:cursor-grabbing",
1098
1448
  !event.color && "bg-primary/10 border-primary/20",
1099
1449
  isShortEvent ? "px-2 flex items-center" : "px-3 py-2",
1100
- count > 1 && "border-l-4"
1450
+ columns > 1 && "border-l-4"
1101
1451
  ),
1102
1452
  style: {
1103
1453
  backgroundColor: event.color ? `${event.color}15` : void 0,
1104
1454
  borderColor: event.color ? `${event.color}30` : void 0,
1105
1455
  borderLeftColor: event.color || "var(--primary)",
1106
- borderLeftWidth: count > 1 ? "4px" : "3px"
1456
+ borderLeftWidth: columns > 1 ? "4px" : "3px"
1107
1457
  },
1108
1458
  onClick: (e) => {
1109
1459
  e.stopPropagation();
1110
1460
  onEventClick?.(event);
1111
1461
  },
1112
- title: count > 1 ? `${event.title} (${index + 1}/${count})` : void 0
1462
+ title: columns > 1 ? `${event.title} (${column + 1}/${columns})` : void 0
1113
1463
  },
1114
1464
  /* @__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
1465
  "font-semibold truncate leading-tight",
1116
1466
  isShortEvent ? "text-xs" : "text-sm",
1117
1467
  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))
1468
+ ) }, 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
1469
  )
1120
1470
  )
1121
1471
  );
1122
- }), (0, import_date_fns7.isToday)(currentDate) && /* @__PURE__ */ import_react9.default.createElement(
1472
+ }), isToday2 && /* @__PURE__ */ import_react9.default.createElement(
1123
1473
  "div",
1124
1474
  {
1125
1475
  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
- }
1476
+ style: { top: `${nowTop}px` }
1129
1477
  },
1130
1478
  /* @__PURE__ */ import_react9.default.createElement("div", { className: "h-[2px] w-full bg-gradient-to-r from-primary via-primary to-primary/50" }),
1131
1479
  /* @__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
1480
  )))));
1133
1481
  };
1482
+ var DayView = import_react9.default.memo(DayViewImpl);
1134
1483
 
1135
1484
  // src/views/AgendaView.tsx
1136
1485
  var import_react11 = __toESM(require("react"));
1137
- var import_date_fns8 = require("date-fns");
1486
+ var import_date_fns11 = require("date-fns");
1138
1487
 
1139
1488
  // src/components/EmptyState.tsx
1140
1489
  var import_react10 = __toESM(require("react"));
@@ -1153,17 +1502,17 @@ var AgendaEmptyState = ({ onCreateEvent, translations }) => /* @__PURE__ */ impo
1153
1502
  var import_framer_motion = require("framer-motion");
1154
1503
  var import_lucide_react5 = require("lucide-react");
1155
1504
  var formatDuration = (start, end) => {
1156
- const minutes = (0, import_date_fns8.differenceInMinutes)(end, start);
1505
+ const minutes = (0, import_date_fns11.differenceInMinutes)(end, start);
1157
1506
  if (minutes < 60) return `${minutes}m`;
1158
1507
  const hours = Math.floor(minutes / 60);
1159
1508
  const remainingMinutes = minutes % 60;
1160
1509
  if (remainingMinutes === 0) return `${hours}h`;
1161
1510
  return `${hours}h ${remainingMinutes}m`;
1162
1511
  };
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 });
1512
+ var getDateLabel = (date, today, locale, translations) => {
1513
+ if ((0, import_date_fns11.isSameDay)(date, today)) return translations?.today || "Today";
1514
+ if ((0, import_date_fns11.isSameDay)(date, (0, import_date_fns11.addDays)(today, 1))) return translations?.tomorrow || "Tomorrow";
1515
+ return (0, import_date_fns11.format)(date, "EEEE", { locale });
1167
1516
  };
1168
1517
  var AgendaView = ({
1169
1518
  currentDate,
@@ -1171,24 +1520,24 @@ var AgendaView = ({
1171
1520
  onEventClick,
1172
1521
  onCreateEvent,
1173
1522
  locale,
1174
- translations
1523
+ translations,
1524
+ timezone
1175
1525
  }) => {
1176
1526
  const is24h = uses24HourClock(locale);
1177
- const timePattern = is24h ? "H:mm" : "h:mm a";
1178
- const compactTimePattern = is24h ? "H:mm" : "h:mm";
1527
+ const { time: timePattern, compact: compactTimePattern } = timeFormats(locale);
1528
+ const today = displayToday(timezone);
1179
1529
  const groupedEvents = (0, import_react11.useMemo)(() => {
1180
- const startDate = (0, import_date_fns8.startOfDay)(currentDate);
1530
+ const byDay = eventsByStartDay(events, timezone);
1181
1531
  const groups = [];
1182
- const sortedEvents = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
1183
1532
  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 });
1533
+ const day = (0, import_date_fns11.addDays)((0, import_date_fns11.startOfDay)(currentDate), i);
1534
+ const dayEvents = byDay.get((0, import_date_fns11.format)(day, "yyyy-MM-dd"));
1535
+ if (dayEvents?.length) {
1536
+ groups.push({ date: day, events: [...dayEvents].sort((a, b) => a.start.getTime() - b.start.getTime()) });
1188
1537
  }
1189
1538
  }
1190
1539
  return groups;
1191
- }, [currentDate, events]);
1540
+ }, [currentDate, events, timezone]);
1192
1541
  const container = {
1193
1542
  hidden: { opacity: 0 },
1194
1543
  show: {
@@ -1219,11 +1568,11 @@ var AgendaView = ({
1219
1568
  },
1220
1569
  /* @__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
1570
  "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(
1571
+ (0, import_date_fns11.isSameDay)(group.date, today) ? "bg-primary text-primary-foreground shadow-lg shadow-primary/30" : "bg-muted/50 text-foreground"
1572
+ ) }, /* @__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
1573
  "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")))),
1574
+ (0, import_date_fns11.isSameDay)(group.date, today) && "text-primary"
1575
+ ) }, 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
1576
  /* @__PURE__ */ import_react11.default.createElement("div", { className: "py-4 space-y-3" }, group.events.map((event) => /* @__PURE__ */ import_react11.default.createElement(
1228
1577
  import_framer_motion.motion.div,
1229
1578
  {
@@ -1245,14 +1594,14 @@ var AgendaView = ({
1245
1594
  style: { backgroundColor: event.color || "var(--primary)" }
1246
1595
  }
1247
1596
  ),
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)))),
1597
+ /* @__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
1598
  /* @__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
1599
  "div",
1251
1600
  {
1252
1601
  className: "w-3 h-3 rounded-full shrink-0 mt-1.5",
1253
1602
  style: { backgroundColor: event.color || "var(--primary)" }
1254
1603
  }
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)))),
1604
+ )), 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
1605
  /* @__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
1606
  )))
1258
1607
  ))
@@ -1261,25 +1610,26 @@ var AgendaView = ({
1261
1610
 
1262
1611
  // src/views/ResourceView.tsx
1263
1612
  var import_react12 = __toESM(require("react"));
1264
- var import_date_fns9 = require("date-fns");
1613
+ var import_date_fns12 = require("date-fns");
1265
1614
  var ResourceView = ({
1266
1615
  currentDate,
1267
1616
  events,
1268
1617
  resources,
1269
1618
  onEventClick,
1270
1619
  onTimeSlotClick,
1271
- locale
1620
+ locale,
1621
+ timezone
1272
1622
  }) => {
1273
1623
  const containerRef = (0, import_react12.useRef)(null);
1274
1624
  const hours = Array.from({ length: 24 }, (_, i) => i);
1275
1625
  const hourWidth = 100;
1276
- const timeFormat = uses24HourClock(locale) ? "H:mm" : "h a";
1626
+ const timeFormat = timeFormats(locale).hour;
1277
1627
  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);
1628
+ const start = toDisplay(event.start, timezone);
1629
+ const end = toDisplay(event.end, timezone);
1630
+ const dayStart = (0, import_date_fns12.startOfDay)(currentDate);
1631
+ const startMinutes = (0, import_date_fns12.differenceInMinutes)(start, dayStart);
1632
+ const durationMinutes = (0, import_date_fns12.differenceInMinutes)(end, start);
1283
1633
  const left = startMinutes / 60 * hourWidth;
1284
1634
  const width = durationMinutes / 60 * hourWidth;
1285
1635
  return {
@@ -1295,10 +1645,10 @@ var ResourceView = ({
1295
1645
  className: "border-r border-border/50 text-xs text-muted-foreground p-2 font-medium shrink-0",
1296
1646
  style: { width: hourWidth }
1297
1647
  },
1298
- (0, import_date_fns9.format)((/* @__PURE__ */ new Date()).setHours(hour, 0, 0, 0), timeFormat, { locale })
1648
+ (0, import_date_fns12.format)((/* @__PURE__ */ new Date()).setHours(hour, 0, 0, 0), timeFormat, { locale })
1299
1649
  ))))), /* @__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
1650
  const resourceEvents = events.filter(
1301
- (e) => e.resourceId === resource.id && (0, import_date_fns9.isSameDay)(new Date(e.start), currentDate)
1651
+ (e) => e.resourceId === resource.id && isSameDisplayDay(e.start, currentDate, timezone)
1302
1652
  );
1303
1653
  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(
1304
1654
  "div",
@@ -1308,20 +1658,18 @@ var ResourceView = ({
1308
1658
  style: { width: hourWidth }
1309
1659
  },
1310
1660
  [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()}`;
1661
+ const slotDate3 = new Date(currentDate);
1662
+ slotDate3.setHours(hour, minute, 0, 0);
1663
+ const slotId = `${resource.id}-${slotDate3.toISOString()}`;
1314
1664
  return /* @__PURE__ */ import_react12.default.createElement(
1315
1665
  DroppableCell,
1316
1666
  {
1317
1667
  key: minute,
1318
1668
  id: slotId,
1319
- date: slotDate,
1669
+ date: slotDate3,
1320
1670
  resourceId: resource.id,
1321
1671
  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
- }
1672
+ onSlotClick: onTimeSlotClick
1325
1673
  }
1326
1674
  );
1327
1675
  })
@@ -1407,7 +1755,7 @@ var Modal = ({
1407
1755
  };
1408
1756
 
1409
1757
  // src/components/EventModal.tsx
1410
- var import_date_fns10 = require("date-fns");
1758
+ var import_date_fns13 = require("date-fns");
1411
1759
  var import_framer_motion2 = require("framer-motion");
1412
1760
  var import_lucide_react7 = require("lucide-react");
1413
1761
  var REMINDER_OPTIONS = [
@@ -1544,7 +1892,7 @@ var EventModal = ({
1544
1892
  };
1545
1893
  const formatDateForInput = (date) => {
1546
1894
  if (!date) return "";
1547
- return (0, import_date_fns10.format)(date, "yyyy-MM-dd'T'HH:mm");
1895
+ return (0, import_date_fns13.format)(date, "yyyy-MM-dd'T'HH:mm");
1548
1896
  };
1549
1897
  const handleDateChange = (field, value) => {
1550
1898
  const date = new Date(value);
@@ -1594,7 +1942,7 @@ var EventModal = ({
1594
1942
  };
1595
1943
  const getDurationText = () => {
1596
1944
  if (!formData.start || !formData.end) return "";
1597
- const mins = (0, import_date_fns10.differenceInMinutes)(formData.end, formData.start);
1945
+ const mins = (0, import_date_fns13.differenceInMinutes)(formData.end, formData.start);
1598
1946
  if (mins < 60) return `${mins} min`;
1599
1947
  const hours = Math.floor(mins / 60);
1600
1948
  const remainingMins = mins % 60;
@@ -1641,7 +1989,7 @@ var EventModal = ({
1641
1989
  /* @__PURE__ */ import_react14.default.createElement(import_lucide_react7.X, { className: "w-4 h-4" })
1642
1990
  ))),
1643
1991
  /* @__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"))))
1992
+ /* @__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"))), /* @__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), "h:mm a"), " \u2013 ", event?.end && (0, import_date_fns13.format)(new Date(event.end), "h:mm a"))))
1645
1993
  ), /* @__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
1994
  import_framer_motion2.motion.div,
1647
1995
  {
@@ -2167,146 +2515,6 @@ var useEventContextMenu = () => {
2167
2515
  };
2168
2516
  };
2169
2517
 
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."
2239
- };
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."
2308
- };
2309
-
2310
2518
  // src/lib/theme.ts
2311
2519
  function colorToHsl(color) {
2312
2520
  let r = 0, g = 0, b = 0;
@@ -2387,10 +2595,149 @@ function getThemeStyles(theme) {
2387
2595
  }
2388
2596
 
2389
2597
  // 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");
2598
+ var import_react18 = require("react");
2599
+ var import_date_fns15 = require("date-fns");
2600
+
2601
+ // src/core/recurrence.ts
2602
+ var import_date_fns14 = require("date-fns");
2393
2603
  var import_rrule = require("rrule");
2604
+ 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];
2605
+ var toFloating = (instant, tz) => {
2606
+ const w = toDisplay(instant, tz);
2607
+ return new Date(Date.UTC(w.getFullYear(), w.getMonth(), w.getDate(), w.getHours(), w.getMinutes(), w.getSeconds(), w.getMilliseconds()));
2608
+ };
2609
+ var fromFloating = (floating, tz) => fromDisplay(
2610
+ new Date(
2611
+ floating.getUTCFullYear(),
2612
+ floating.getUTCMonth(),
2613
+ floating.getUTCDate(),
2614
+ floating.getUTCHours(),
2615
+ floating.getUTCMinutes(),
2616
+ floating.getUTCSeconds(),
2617
+ floating.getUTCMilliseconds()
2618
+ ),
2619
+ tz
2620
+ );
2621
+ var minuteKey = (date) => Math.floor(date.getTime() / 6e4);
2622
+ var FREQUENCIES = { DAILY: import_rrule.RRule.DAILY, WEEKLY: import_rrule.RRule.WEEKLY, MONTHLY: import_rrule.RRule.MONTHLY, YEARLY: import_rrule.RRule.YEARLY };
2623
+ function buildRule(event, tz) {
2624
+ const r = event.recurrence;
2625
+ const freq = FREQUENCIES[r.freq];
2626
+ if (freq === void 0) throw new Error(`unsupported frequency "${r.freq}"`);
2627
+ return new import_rrule.RRule({
2628
+ freq,
2629
+ interval: r.interval || 1,
2630
+ dtstart: toFloating(event.start, tz),
2631
+ until: r.until ? toFloating(r.until, tz) : void 0,
2632
+ count: r.count,
2633
+ byweekday: r.byweekday?.length ? r.byweekday.map((d) => WEEKDAYS[d]) : void 0
2634
+ });
2635
+ }
2636
+ function occurrencesInMonth(event, monthStart, tz) {
2637
+ const rule = buildRule(event, tz);
2638
+ const excluded = new Set((event.recurrence.exdates ?? []).map(minuteKey));
2639
+ const duration = event.end.getTime() - event.start.getTime();
2640
+ const from = toFloating(monthStart, tz);
2641
+ const to = toFloating((0, import_date_fns14.addMonths)(monthStart, 1), tz);
2642
+ const result = [];
2643
+ for (const floating of rule.between(from, to, true)) {
2644
+ if (floating.getTime() === to.getTime()) continue;
2645
+ const start = fromFloating(floating, tz);
2646
+ if (excluded.has(minuteKey(start))) continue;
2647
+ result.push({
2648
+ ...event,
2649
+ id: `${event.id}-${start.getTime()}`,
2650
+ originalEventId: event.id,
2651
+ occurrenceStart: start,
2652
+ start,
2653
+ end: new Date(start.getTime() + duration)
2654
+ });
2655
+ }
2656
+ return result;
2657
+ }
2658
+ var MAX_MONTHS_PER_EVENT = 12;
2659
+ var occurrenceCache = /* @__PURE__ */ new WeakMap();
2660
+ function cachedMonth(event, monthStart, tz) {
2661
+ let months = occurrenceCache.get(event);
2662
+ if (!months) {
2663
+ months = /* @__PURE__ */ new Map();
2664
+ occurrenceCache.set(event, months);
2665
+ }
2666
+ const key = `${monthStart.getFullYear()}-${monthStart.getMonth()}|${tz ?? ""}`;
2667
+ let occurrences = months.get(key);
2668
+ if (occurrences) {
2669
+ months.delete(key);
2670
+ } else {
2671
+ occurrences = occurrencesInMonth(event, monthStart, tz);
2672
+ if (months.size >= MAX_MONTHS_PER_EVENT) months.delete(months.keys().next().value);
2673
+ }
2674
+ months.set(key, occurrences);
2675
+ return occurrences;
2676
+ }
2677
+ function expandRecurrences(events, range, anchorTz) {
2678
+ const result = [];
2679
+ const firstMonth = (0, import_date_fns14.startOfMonth)(toDisplay(range.start, anchorTz));
2680
+ const lastMonth = (0, import_date_fns14.startOfMonth)(toDisplay(range.end, anchorTz));
2681
+ for (const event of events) {
2682
+ if (!event.recurrence) {
2683
+ result.push(event);
2684
+ continue;
2685
+ }
2686
+ try {
2687
+ for (let month = firstMonth; month <= lastMonth; month = (0, import_date_fns14.addMonths)(month, 1)) {
2688
+ for (const occurrence of cachedMonth(event, fromDisplay(month, anchorTz), anchorTz)) {
2689
+ if (occurrence.end >= range.start && occurrence.start <= range.end) result.push(occurrence);
2690
+ }
2691
+ }
2692
+ } catch (error) {
2693
+ warnOnce(`invalid-recurrence:${event.id}`, `Event "${event.id}" has an invalid recurrence rule: ${String(error)}`);
2694
+ result.push(event);
2695
+ }
2696
+ }
2697
+ return result;
2698
+ }
2699
+ function stripOccurrenceFields(event) {
2700
+ const { originalEventId: _originalEventId, occurrenceStart: _occurrenceStart, ...rest } = event;
2701
+ return rest;
2702
+ }
2703
+ var shiftFloating = (instant, deltaMs, tz) => fromFloating(new Date(toFloating(instant, tz).getTime() + deltaMs), tz);
2704
+ var DAY_MS = 24 * 60 * 60 * 1e3;
2705
+ var floatingDay = (floating) => Math.floor(floating.getTime() / DAY_MS);
2706
+ function shiftSeries(master, occurrence, newStart, newEnd, anchorTz) {
2707
+ const fromFloat = toFloating(occurrence.start, anchorTz);
2708
+ const toFloat = toFloating(newStart, anchorTz);
2709
+ const deltaMs = toFloat.getTime() - fromFloat.getTime();
2710
+ const dayDelta = floatingDay(toFloat) - floatingDay(fromFloat);
2711
+ const start = shiftFloating(master.start, deltaMs, anchorTz);
2712
+ const end = new Date(start.getTime() + (newEnd.getTime() - newStart.getTime()));
2713
+ const recurrence = master.recurrence && {
2714
+ ...master.recurrence,
2715
+ byweekday: master.recurrence.byweekday && dayDelta % 7 !== 0 ? master.recurrence.byweekday.map((d) => ((d + dayDelta) % 7 + 7) % 7) : master.recurrence.byweekday,
2716
+ exdates: master.recurrence.exdates?.map((d) => shiftFloating(d, deltaMs, anchorTz))
2717
+ };
2718
+ return stripOccurrenceFields({ ...master, start, end, recurrence });
2719
+ }
2720
+ function excludeOccurrence(master, occurrenceStart) {
2721
+ const recurrence = master.recurrence ?? { freq: "DAILY" };
2722
+ return { ...master, recurrence: { ...recurrence, exdates: [...recurrence.exdates ?? [], occurrenceStart] } };
2723
+ }
2724
+
2725
+ // src/hooks/useStableCallback.ts
2726
+ var import_react17 = require("react");
2727
+ function useStableCallback(fn) {
2728
+ const ref = (0, import_react17.useRef)(fn);
2729
+ ref.current = fn;
2730
+ return (0, import_react17.useCallback)((...args) => ref.current(...args), []);
2731
+ }
2732
+
2733
+ // src/hooks/useCalendarLogic.ts
2734
+ function expansionRange(monthStart) {
2735
+ return {
2736
+ start: (0, import_date_fns15.startOfWeek)((0, import_date_fns15.startOfMonth)((0, import_date_fns15.subMonths)(monthStart, 1))),
2737
+ end: (0, import_date_fns15.addDays)((0, import_date_fns15.endOfMonth)((0, import_date_fns15.addMonths)(monthStart, 1)), 37)
2738
+ };
2739
+ }
2740
+ var DAY_MS2 = 24 * 60 * 60 * 1e3;
2394
2741
  var useCalendarLogic = ({
2395
2742
  events,
2396
2743
  view: controlledView,
@@ -2401,211 +2748,192 @@ var useCalendarLogic = ({
2401
2748
  onEventUpdate,
2402
2749
  onEventCreate,
2403
2750
  onEventDelete,
2751
+ onEventDrop,
2752
+ onEventResize,
2753
+ onOccurrenceDelete,
2404
2754
  readOnly,
2405
2755
  timezone
2406
2756
  }) => {
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);
2757
+ const [internalView, setInternalView] = (0, import_react18.useState)("week");
2758
+ const [internalDate, setInternalDate] = (0, import_react18.useState)(() => /* @__PURE__ */ new Date());
2759
+ const [isSidebarOpen, setIsSidebarOpen] = (0, import_react18.useState)(true);
2760
+ const [isModalOpen, setIsModalOpen] = (0, import_react18.useState)(false);
2761
+ const [selectedEvent, setSelectedEvent] = (0, import_react18.useState)(null);
2762
+ const [modalInitialDate, setModalInitialDate] = (0, import_react18.useState)(void 0);
2763
+ const [modalInitialResourceId, setModalInitialResourceId] = (0, import_react18.useState)(void 0);
2413
2764
  const view = controlledView ?? internalView;
2414
2765
  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) => {
2766
+ const normalizedEvents = (0, import_react18.useMemo)(() => normalizeEvents(events), [events]);
2767
+ const monthKey = (0, import_date_fns15.format)(currentDate, "yyyy-MM");
2768
+ const range = (0, import_react18.useMemo)(() => expansionRange(/* @__PURE__ */ new Date(`${monthKey}-01T00:00:00`)), [monthKey]);
2769
+ const expandedEvents = (0, import_react18.useMemo)(
2770
+ () => expandRecurrences(normalizedEvents, range, timezone),
2771
+ [normalizedEvents, range, timezone]
2772
+ );
2773
+ const masterOf = (occurrence) => normalizedEvents.find((e) => e.id === occurrence.originalEventId);
2774
+ const handleViewChange = useStableCallback((newView) => {
2453
2775
  if (controlledOnViewChange) {
2454
2776
  controlledOnViewChange(newView);
2455
2777
  } else {
2456
2778
  setInternalView(newView);
2457
2779
  }
2458
- };
2459
- const handleDateChange = (newDate) => {
2780
+ });
2781
+ const handleDateChange = useStableCallback((newDate) => {
2460
2782
  if (controlledOnDateChange) {
2461
2783
  controlledOnDateChange(newDate);
2462
2784
  } else {
2463
2785
  setInternalDate(newDate);
2464
2786
  }
2465
- };
2466
- const handlePrev = () => {
2787
+ });
2788
+ const handlePrev = useStableCallback(() => {
2467
2789
  switch (view) {
2468
2790
  case "month":
2469
- handleDateChange((0, import_date_fns11.subMonths)(currentDate, 1));
2791
+ handleDateChange((0, import_date_fns15.subMonths)(currentDate, 1));
2470
2792
  break;
2471
2793
  case "week":
2472
- handleDateChange((0, import_date_fns11.subWeeks)(currentDate, 1));
2794
+ handleDateChange((0, import_date_fns15.subWeeks)(currentDate, 1));
2473
2795
  break;
2474
2796
  case "day":
2475
2797
  case "resource":
2476
- handleDateChange((0, import_date_fns11.subDays)(currentDate, 1));
2798
+ handleDateChange((0, import_date_fns15.subDays)(currentDate, 1));
2477
2799
  break;
2478
2800
  case "agenda":
2479
- handleDateChange((0, import_date_fns11.subDays)(currentDate, 7));
2801
+ handleDateChange((0, import_date_fns15.subDays)(currentDate, 7));
2480
2802
  break;
2481
2803
  }
2482
- };
2483
- const handleNext = () => {
2804
+ });
2805
+ const handleNext = useStableCallback(() => {
2484
2806
  switch (view) {
2485
2807
  case "month":
2486
- handleDateChange((0, import_date_fns11.addMonths)(currentDate, 1));
2808
+ handleDateChange((0, import_date_fns15.addMonths)(currentDate, 1));
2487
2809
  break;
2488
2810
  case "week":
2489
- handleDateChange((0, import_date_fns11.addWeeks)(currentDate, 1));
2811
+ handleDateChange((0, import_date_fns15.addWeeks)(currentDate, 1));
2490
2812
  break;
2491
2813
  case "day":
2492
2814
  case "resource":
2493
- handleDateChange((0, import_date_fns11.addDays)(currentDate, 1));
2815
+ handleDateChange((0, import_date_fns15.addDays)(currentDate, 1));
2494
2816
  break;
2495
2817
  case "agenda":
2496
- handleDateChange((0, import_date_fns11.addDays)(currentDate, 7));
2818
+ handleDateChange((0, import_date_fns15.addDays)(currentDate, 7));
2497
2819
  break;
2498
2820
  }
2499
- };
2500
- const handleToday = () => {
2821
+ });
2822
+ const handleToday = useStableCallback(() => {
2501
2823
  handleDateChange(/* @__PURE__ */ new Date());
2502
- };
2503
- const handleDateClick = (date) => {
2824
+ });
2825
+ const handleDateClick = useStableCallback((date) => {
2504
2826
  handleDateChange(date);
2505
2827
  handleViewChange("day");
2506
- };
2507
- const handleTimeSlotClick = (date) => {
2828
+ });
2829
+ const handleTimeSlotClick = useStableCallback((date, resourceId) => {
2508
2830
  if (readOnly) return;
2509
2831
  setSelectedEvent(null);
2510
2832
  setModalInitialDate(date);
2833
+ setModalInitialResourceId(resourceId);
2511
2834
  setIsModalOpen(true);
2512
- };
2513
- const handleEventClickInternal = (event) => {
2514
- if (onEventClick) {
2515
- onEventClick(event);
2516
- }
2835
+ });
2836
+ const handleEventClickInternal = useStableCallback((event) => {
2837
+ onEventClick?.(event);
2517
2838
  if (!readOnly) {
2518
2839
  setSelectedEvent(event);
2519
2840
  setModalInitialDate(void 0);
2841
+ setModalInitialResourceId(void 0);
2520
2842
  setIsModalOpen(true);
2521
2843
  }
2522
- };
2523
- const handleCreateEvent = () => {
2844
+ });
2845
+ const handleCreateEvent = useStableCallback(() => {
2524
2846
  if (readOnly) return;
2525
2847
  setSelectedEvent(null);
2526
2848
  setModalInitialDate(/* @__PURE__ */ new Date());
2849
+ setModalInitialResourceId(void 0);
2527
2850
  setIsModalOpen(true);
2528
- };
2529
- const handleModalSave = (eventData) => {
2530
- let effectiveEventId = selectedEvent?.id;
2851
+ });
2852
+ const handleModalSave = useStableCallback((eventData) => {
2531
2853
  if (selectedEvent?.originalEventId) {
2532
- effectiveEventId = selectedEvent.originalEventId;
2533
- }
2534
- if (effectiveEventId) {
2535
- if (onEventUpdate) {
2536
- onEventUpdate({
2537
- ...eventData,
2538
- id: effectiveEventId
2539
- });
2540
- }
2854
+ const master = masterOf(selectedEvent);
2855
+ if (!master) return;
2856
+ const edited = {
2857
+ ...master,
2858
+ ...stripOccurrenceFields(eventData),
2859
+ id: master.id,
2860
+ start: master.start,
2861
+ end: master.end
2862
+ };
2863
+ onEventUpdate?.(
2864
+ shiftSeries(edited, selectedEvent, eventData.start ?? selectedEvent.start, eventData.end ?? selectedEvent.end, timezone)
2865
+ );
2866
+ } else if (selectedEvent) {
2867
+ onEventUpdate?.({ ...eventData, id: selectedEvent.id });
2541
2868
  } 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;
2869
+ onEventCreate?.(
2870
+ modalInitialResourceId && eventData.resourceId === void 0 ? { ...eventData, resourceId: modalInitialResourceId } : eventData
2871
+ );
2551
2872
  }
2552
- if (onEventDelete) {
2553
- onEventDelete(effectiveEventId);
2873
+ });
2874
+ const handleEventDelete = useStableCallback((event) => {
2875
+ if (!event.originalEventId) {
2876
+ onEventDelete?.(event.id);
2877
+ return;
2554
2878
  }
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
- }
2879
+ const master = masterOf(event);
2880
+ if (onOccurrenceDelete && master) {
2881
+ onOccurrenceDelete(event, excludeOccurrence(master, event.occurrenceStart ?? event.start));
2582
2882
  } 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);
2590
- }
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;
2883
+ onEventDelete?.(event.originalEventId);
2596
2884
  }
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
- });
2885
+ });
2886
+ const handleModalDelete = useStableCallback((eventId) => {
2887
+ if (selectedEvent && selectedEvent.id === eventId) {
2888
+ handleEventDelete(selectedEvent);
2889
+ } else {
2890
+ onEventDelete?.(eventId);
2607
2891
  }
2892
+ });
2893
+ const handleEventDuplicate = useStableCallback((event) => {
2894
+ const { recurrence: _recurrence, ...copy } = stripOccurrenceFields(event);
2895
+ onEventCreate?.({
2896
+ ...copy,
2897
+ id: `${event.originalEventId ?? event.id}-copy-${Date.now()}`,
2898
+ title: `${event.title} (Copy)`,
2899
+ start: new Date(event.start.getTime() + DAY_MS2),
2900
+ end: new Date(event.end.getTime() + DAY_MS2)
2901
+ });
2902
+ });
2903
+ const applyChange = (event, start, end) => {
2904
+ if (!event.originalEventId) return { original: event, updated: { ...event, start, end } };
2905
+ const master = masterOf(event);
2906
+ if (!master) return null;
2907
+ return { original: master, updated: shiftSeries(master, event, start, end, timezone) };
2608
2908
  };
2909
+ const handleDragEnd = useStableCallback((dragEvent) => {
2910
+ const { active, over } = dragEvent;
2911
+ if (!over) return;
2912
+ const dragged = active.data.current?.event;
2913
+ const overDate = over.data.current?.date;
2914
+ if (!dragged || !overDate) return;
2915
+ const drop = computeDrop({
2916
+ event: dragged,
2917
+ overDate,
2918
+ overResourceId: over.data.current?.resourceId,
2919
+ grabOffsetMs: active.data.current?.grabOffsetMs,
2920
+ view,
2921
+ tz: timezone
2922
+ });
2923
+ if (!drop) return;
2924
+ const change = applyChange(dragged, drop.start, drop.end);
2925
+ if (!change) return;
2926
+ const updated = { ...change.updated, resourceId: drop.resourceId };
2927
+ onEventDrop?.(change.original, updated.start, updated.end);
2928
+ onEventUpdate?.(updated);
2929
+ });
2930
+ const handleEventResize = useStableCallback((event, newEnd) => {
2931
+ if (readOnly) return;
2932
+ const change = applyChange(event, event.start, newEnd);
2933
+ if (!change) return;
2934
+ onEventResize?.(change.original, change.updated.start, change.updated.end);
2935
+ onEventUpdate?.(change.updated);
2936
+ });
2609
2937
  return {
2610
2938
  view,
2611
2939
  currentDate,
@@ -2627,15 +2955,18 @@ var useCalendarLogic = ({
2627
2955
  handleCreateEvent,
2628
2956
  handleModalSave,
2629
2957
  handleModalDelete,
2958
+ handleEventDelete,
2959
+ handleEventDuplicate,
2960
+ handleEventResize,
2630
2961
  handleDragEnd
2631
2962
  };
2632
2963
  };
2633
2964
 
2634
2965
  // src/index.tsx
2635
- var import_date_fns12 = require("date-fns");
2966
+ var import_date_fns16 = require("date-fns");
2636
2967
 
2637
2968
  // src/hooks/useSwipeGesture.ts
2638
- var import_react18 = require("react");
2969
+ var import_react19 = require("react");
2639
2970
  var useSwipeGesture = (options) => {
2640
2971
  const {
2641
2972
  onSwipeLeft,
@@ -2647,9 +2978,9 @@ var useSwipeGesture = (options) => {
2647
2978
  allowedTime = 500,
2648
2979
  enabled = true
2649
2980
  } = 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) => {
2981
+ const touchInfoRef = (0, import_react19.useRef)(null);
2982
+ const elementRef = (0, import_react19.useRef)(null);
2983
+ const handleTouchStart = (0, import_react19.useCallback)((e) => {
2653
2984
  if (!enabled) return;
2654
2985
  const touch = e.touches[0];
2655
2986
  touchInfoRef.current = {
@@ -2658,7 +2989,7 @@ var useSwipeGesture = (options) => {
2658
2989
  startTime: Date.now()
2659
2990
  };
2660
2991
  }, [enabled]);
2661
- const handleTouchEnd = (0, import_react18.useCallback)((e) => {
2992
+ const handleTouchEnd = (0, import_react19.useCallback)((e) => {
2662
2993
  if (!enabled || !touchInfoRef.current) return;
2663
2994
  const touch = e.changedTouches[0];
2664
2995
  const { startX, startY, startTime } = touchInfoRef.current;
@@ -2682,7 +3013,7 @@ var useSwipeGesture = (options) => {
2682
3013
  }
2683
3014
  touchInfoRef.current = null;
2684
3015
  }, [enabled, threshold, restraint, allowedTime, onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown]);
2685
- (0, import_react18.useEffect)(() => {
3016
+ (0, import_react19.useEffect)(() => {
2686
3017
  const element = elementRef.current;
2687
3018
  if (!element) return;
2688
3019
  element.addEventListener("touchstart", handleTouchStart, { passive: true });
@@ -2716,7 +3047,9 @@ var Scheduler = ({
2716
3047
  onEventCreate,
2717
3048
  onEventUpdate,
2718
3049
  onEventDelete,
3050
+ onEventDrop,
2719
3051
  onEventResize,
3052
+ onOccurrenceDelete,
2720
3053
  timezone,
2721
3054
  onTimezoneChange,
2722
3055
  className,
@@ -2738,7 +3071,7 @@ var Scheduler = ({
2738
3071
  locale
2739
3072
  // Date-fns locale
2740
3073
  }) => {
2741
- const [activeDragEvent, setActiveDragEvent] = (0, import_react19.useState)(null);
3074
+ const [activeDragEvent, setActiveDragEvent] = (0, import_react20.useState)(null);
2742
3075
  const {
2743
3076
  contextMenuEvent,
2744
3077
  contextMenuPosition,
@@ -2764,9 +3097,11 @@ var Scheduler = ({
2764
3097
  handleCreateEvent,
2765
3098
  handleModalSave,
2766
3099
  handleModalDelete,
3100
+ handleEventDelete,
3101
+ handleEventDuplicate,
3102
+ handleEventResize,
2767
3103
  handleDragEnd,
2768
3104
  expandedEvents
2769
- // New export
2770
3105
  } = useCalendarLogic({
2771
3106
  events,
2772
3107
  view: controlledView,
@@ -2777,6 +3112,9 @@ var Scheduler = ({
2777
3112
  onEventUpdate,
2778
3113
  onEventCreate,
2779
3114
  onEventDelete,
3115
+ onEventDrop,
3116
+ onEventResize,
3117
+ onOccurrenceDelete,
2780
3118
  readOnly,
2781
3119
  timezone
2782
3120
  });
@@ -2791,57 +3129,40 @@ var Scheduler = ({
2791
3129
  const snapToGrid = (0, import_modifiers.createSnapModifier)(gridSize);
2792
3130
  const modifiers = [snapToGrid, import_modifiers.restrictToWindowEdges];
2793
3131
  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)();
3132
+ const id = (0, import_react20.useId)();
2807
3133
  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);
3134
+ const t = (0, import_react20.useMemo)(() => resolveTranslations(language, translations), [language, translations]);
3135
+ const overlayTimeFormat = timeFormats(locale).time;
3136
+ const handleDragStart = (0, import_react20.useCallback)((event) => {
3137
+ const draggedEvent = event.active.data.current?.event;
2817
3138
  if (draggedEvent) {
2818
3139
  setActiveDragEvent(draggedEvent);
2819
3140
  }
2820
- };
2821
- const onDragEndWrapper = (event) => {
3141
+ }, []);
3142
+ const onDragEndWrapper = (0, import_react20.useCallback)((event) => {
2822
3143
  setActiveDragEvent(null);
2823
3144
  handleDragEnd(event);
2824
- };
3145
+ }, [handleDragEnd]);
2825
3146
  const getDragHeight = () => {
2826
3147
  if (!activeDragEvent) return void 0;
2827
3148
  if (view === "resource") {
2828
3149
  return 80;
2829
3150
  }
2830
3151
  if (view !== "week" && view !== "day") return void 0;
2831
- const duration = (0, import_date_fns12.differenceInMinutes)(activeDragEvent.end, activeDragEvent.start);
3152
+ const duration = (0, import_date_fns16.differenceInMinutes)(activeDragEvent.end, activeDragEvent.start);
2832
3153
  const hourHeight = view === "day" ? 80 : 60;
2833
3154
  return duration / 60 * hourHeight;
2834
3155
  };
2835
3156
  const getDragWidth = () => {
2836
3157
  if (view === "month") return "100%";
2837
3158
  if (view === "resource" && activeDragEvent) {
2838
- const duration = (0, import_date_fns12.differenceInMinutes)(activeDragEvent.end, activeDragEvent.start);
3159
+ const duration = (0, import_date_fns16.differenceInMinutes)(activeDragEvent.end, activeDragEvent.start);
2839
3160
  const width = duration / 60 * 100;
2840
3161
  return `${Math.max(width, 4)}px`;
2841
3162
  }
2842
3163
  return "150px";
2843
3164
  };
2844
- const filteredEvents = (0, import_react19.useMemo)(() => {
3165
+ const filteredEvents = (0, import_react20.useMemo)(() => {
2845
3166
  if (!calendars) return expandedEvents;
2846
3167
  const activeCalendarIds = calendars.filter((c) => c.active !== false).map((c) => c.id);
2847
3168
  return expandedEvents.filter((e) => {
@@ -2849,7 +3170,7 @@ var Scheduler = ({
2849
3170
  return activeCalendarIds.includes(e.calendarId);
2850
3171
  });
2851
3172
  }, [expandedEvents, calendars]);
2852
- return /* @__PURE__ */ import_react19.default.createElement(
3173
+ return /* @__PURE__ */ import_react20.default.createElement(
2853
3174
  import_core3.DndContext,
2854
3175
  {
2855
3176
  id,
@@ -2858,13 +3179,13 @@ var Scheduler = ({
2858
3179
  onDragEnd: onDragEndWrapper,
2859
3180
  modifiers
2860
3181
  },
2861
- /* @__PURE__ */ import_react19.default.createElement(
3182
+ /* @__PURE__ */ import_react20.default.createElement(
2862
3183
  "div",
2863
3184
  {
2864
3185
  className: cn("flex flex-col h-full bg-background text-foreground relative", className),
2865
3186
  style: getThemeStyles(theme)
2866
3187
  },
2867
- /* @__PURE__ */ import_react19.default.createElement(
3188
+ /* @__PURE__ */ import_react20.default.createElement(
2868
3189
  CalendarHeader,
2869
3190
  {
2870
3191
  currentDate,
@@ -2883,7 +3204,7 @@ var Scheduler = ({
2883
3204
  locale
2884
3205
  }
2885
3206
  ),
2886
- /* @__PURE__ */ import_react19.default.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ import_react19.default.createElement(
3207
+ /* @__PURE__ */ import_react20.default.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ import_react20.default.createElement(
2887
3208
  import_framer_motion4.motion.div,
2888
3209
  {
2889
3210
  className: cn(
@@ -2899,7 +3220,7 @@ var Scheduler = ({
2899
3220
  ease: [0.4, 0, 0.2, 1]
2900
3221
  }
2901
3222
  },
2902
- /* @__PURE__ */ import_react19.default.createElement(
3223
+ /* @__PURE__ */ import_react20.default.createElement(
2903
3224
  Sidebar,
2904
3225
  {
2905
3226
  currentDate,
@@ -2916,7 +3237,7 @@ var Scheduler = ({
2916
3237
  locale
2917
3238
  }
2918
3239
  )
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(
3240
+ ), /* @__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
3241
  import_framer_motion4.motion.div,
2921
3242
  {
2922
3243
  key: `${view}-${currentDate.toISOString()}-${timezone || "local"}`,
@@ -2941,7 +3262,7 @@ var Scheduler = ({
2941
3262
  },
2942
3263
  className: "h-full"
2943
3264
  },
2944
- view === "month" && /* @__PURE__ */ import_react19.default.createElement(
3265
+ view === "month" && /* @__PURE__ */ import_react20.default.createElement(
2945
3266
  MonthView,
2946
3267
  {
2947
3268
  currentDate,
@@ -2952,7 +3273,7 @@ var Scheduler = ({
2952
3273
  locale
2953
3274
  }
2954
3275
  ),
2955
- view === "week" && /* @__PURE__ */ import_react19.default.createElement(
3276
+ view === "week" && /* @__PURE__ */ import_react20.default.createElement(
2956
3277
  WeekView,
2957
3278
  {
2958
3279
  currentDate,
@@ -2965,7 +3286,7 @@ var Scheduler = ({
2965
3286
  readonly: readOnly
2966
3287
  }
2967
3288
  ),
2968
- view === "day" && /* @__PURE__ */ import_react19.default.createElement(
3289
+ view === "day" && /* @__PURE__ */ import_react20.default.createElement(
2969
3290
  DayView,
2970
3291
  {
2971
3292
  currentDate,
@@ -2978,7 +3299,7 @@ var Scheduler = ({
2978
3299
  readonly: readOnly
2979
3300
  }
2980
3301
  ),
2981
- view === "agenda" && /* @__PURE__ */ import_react19.default.createElement(
3302
+ view === "agenda" && /* @__PURE__ */ import_react20.default.createElement(
2982
3303
  AgendaView,
2983
3304
  {
2984
3305
  currentDate,
@@ -2986,30 +3307,29 @@ var Scheduler = ({
2986
3307
  onEventClick: handleEventClickInternal,
2987
3308
  onCreateEvent: handleCreateEvent,
2988
3309
  locale,
2989
- translations: t
3310
+ translations: t,
3311
+ timezone
2990
3312
  }
2991
3313
  ),
2992
- view === "resource" && resources && /* @__PURE__ */ import_react19.default.createElement(
3314
+ view === "resource" && resources && /* @__PURE__ */ import_react20.default.createElement(
2993
3315
  ResourceView,
2994
3316
  {
2995
3317
  currentDate,
2996
3318
  events: filteredEvents,
2997
3319
  resources,
2998
3320
  onEventClick: handleEventClickInternal,
2999
- onTimeSlotClick: (date) => {
3000
- if (readOnly) return;
3001
- handleTimeSlotClick(date);
3002
- },
3003
- locale
3321
+ onTimeSlotClick: handleTimeSlotClick,
3322
+ locale,
3323
+ timezone
3004
3324
  }
3005
3325
  )
3006
- )))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "md:hidden absolute bottom-6 right-6 z-50" }, /* @__PURE__ */ import_react19.default.createElement(
3326
+ )))), /* @__PURE__ */ import_react20.default.createElement("div", { className: "md:hidden absolute bottom-6 right-6 z-50" }, /* @__PURE__ */ import_react20.default.createElement(
3007
3327
  "button",
3008
3328
  {
3009
3329
  onClick: handleCreateEvent,
3010
3330
  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
3331
  },
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" }))
3332
+ /* @__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
3333
  )))),
3014
3334
  renderEventForm ? renderEventForm({
3015
3335
  isOpen: isModalOpen,
@@ -3018,7 +3338,7 @@ var Scheduler = ({
3018
3338
  initialDate: modalInitialDate,
3019
3339
  onSave: handleModalSave,
3020
3340
  onDelete: handleModalDelete
3021
- }) : /* @__PURE__ */ import_react19.default.createElement(
3341
+ }) : /* @__PURE__ */ import_react20.default.createElement(
3022
3342
  EventModal,
3023
3343
  {
3024
3344
  isOpen: isModalOpen,
@@ -3032,7 +3352,7 @@ var Scheduler = ({
3032
3352
  translations: t
3033
3353
  }
3034
3354
  ),
3035
- /* @__PURE__ */ import_react19.default.createElement(
3355
+ /* @__PURE__ */ import_react20.default.createElement(
3036
3356
  EventContextMenu,
3037
3357
  {
3038
3358
  event: contextMenuEvent,
@@ -3042,20 +3362,12 @@ var Scheduler = ({
3042
3362
  handleEventClickInternal(event);
3043
3363
  closeContextMenu();
3044
3364
  },
3045
- onDelete: (eventId) => {
3046
- onEventDelete?.(eventId);
3365
+ onDelete: () => {
3366
+ if (contextMenuEvent) handleEventDelete(contextMenuEvent);
3047
3367
  closeContextMenu();
3048
3368
  },
3049
3369
  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);
3370
+ handleEventDuplicate(event);
3059
3371
  closeContextMenu();
3060
3372
  },
3061
3373
  translations: {
@@ -3065,7 +3377,7 @@ var Scheduler = ({
3065
3377
  }
3066
3378
  }
3067
3379
  ),
3068
- /* @__PURE__ */ import_react19.default.createElement(import_core3.DragOverlay, { dropAnimation: null }, activeDragEvent ? /* @__PURE__ */ import_react19.default.createElement(
3380
+ /* @__PURE__ */ import_react20.default.createElement(import_core3.DragOverlay, { dropAnimation: null }, activeDragEvent ? /* @__PURE__ */ import_react20.default.createElement(
3069
3381
  "div",
3070
3382
  {
3071
3383
  className: cn(
@@ -3083,13 +3395,13 @@ var Scheduler = ({
3083
3395
  transform: "rotate(-2deg) scale(1.02)"
3084
3396
  }
3085
3397
  },
3086
- /* @__PURE__ */ import_react19.default.createElement("div", { className: "p-2.5 h-full flex flex-col" }, /* @__PURE__ */ import_react19.default.createElement(
3398
+ /* @__PURE__ */ import_react20.default.createElement("div", { className: "p-2.5 h-full flex flex-col" }, /* @__PURE__ */ import_react20.default.createElement(
3087
3399
  "div",
3088
3400
  {
3089
3401
  className: "absolute left-0 top-0 bottom-0 w-1 rounded-l-lg",
3090
3402
  style: { backgroundColor: activeDragEvent.color || "var(--primary)" }
3091
3403
  }
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" }))))
3404
+ ), /* @__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)(activeDragEvent.start, overlayTimeFormat, { locale }), " - ", (0, import_date_fns16.format)(activeDragEvent.end, 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
3405
  ) : null)
3094
3406
  )
3095
3407
  );