@allhailai/tempusmachina-react 1.0.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 ADDED
@@ -0,0 +1,2902 @@
1
+ import * as React16 from 'react';
2
+ import React16__default, { createContext, useState, useCallback, useMemo, useEffect, useRef, useContext } from 'react';
3
+ import { Temporal } from 'temporal-polyfill';
4
+ import { Button as Button$1 } from '@base-ui/react/button';
5
+ import { cva } from 'class-variance-authority';
6
+ import { clsx } from 'clsx';
7
+ import { twMerge } from 'tailwind-merge';
8
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
+ import { Toggle as Toggle$1 } from '@base-ui/react/toggle';
10
+ import { ToggleGroup as ToggleGroup$1 } from '@base-ui/react/toggle-group';
11
+ import { getToday, createPluginRegistry, EventStore, getVisibleRange, viewTypeFromPreset, resolveLayoutSolver, navigateNext, navigatePrev, getViewTitle, formatEventTime, getWeekNumber, formatTime, isRtl, createEventSources, fetchEventSource, mergeEventSources } from '@allhailai/tempusmachina-core';
12
+ import { draggable, monitorForElements, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
13
+ import { monitorForExternal, dropTargetForExternal } from '@atlaskit/pragmatic-drag-and-drop/external/adapter';
14
+
15
+ // src/components/calendar.tsx
16
+ function cn(...inputs) {
17
+ return twMerge(clsx(inputs));
18
+ }
19
+ var buttonVariants = cva(
20
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
21
+ {
22
+ variants: {
23
+ variant: {
24
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
25
+ outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
26
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
27
+ ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
28
+ destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
29
+ link: "text-primary underline-offset-4 hover:underline"
30
+ },
31
+ size: {
32
+ default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
33
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
34
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
35
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
36
+ icon: "size-8",
37
+ "icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
38
+ "icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
39
+ "icon-lg": "size-9"
40
+ }
41
+ },
42
+ defaultVariants: {
43
+ variant: "default",
44
+ size: "default"
45
+ }
46
+ }
47
+ );
48
+ function Button({
49
+ className,
50
+ variant = "default",
51
+ size = "default",
52
+ ...props
53
+ }) {
54
+ return /* @__PURE__ */ jsx(
55
+ Button$1,
56
+ {
57
+ "data-slot": "button",
58
+ className: cn(buttonVariants({ variant, size, className })),
59
+ ...props
60
+ }
61
+ );
62
+ }
63
+ var toggleVariants = cva(
64
+ "group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
65
+ {
66
+ variants: {
67
+ variant: {
68
+ default: "bg-transparent",
69
+ outline: "border border-input bg-transparent hover:bg-muted"
70
+ },
71
+ size: {
72
+ default: "h-8 min-w-8 px-2",
73
+ sm: "h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-1.5 text-[0.8rem]",
74
+ lg: "h-9 min-w-9 px-2.5"
75
+ }
76
+ },
77
+ defaultVariants: {
78
+ variant: "default",
79
+ size: "default"
80
+ }
81
+ }
82
+ );
83
+ function Toggle({
84
+ className,
85
+ variant = "default",
86
+ size = "default",
87
+ ...props
88
+ }) {
89
+ return /* @__PURE__ */ jsx(
90
+ Toggle$1,
91
+ {
92
+ "data-slot": "toggle",
93
+ className: cn(toggleVariants({ variant, size, className })),
94
+ ...props
95
+ }
96
+ );
97
+ }
98
+ var ToggleGroupContext = React16.createContext({
99
+ size: "default",
100
+ variant: "default",
101
+ spacing: 0,
102
+ orientation: "horizontal"
103
+ });
104
+ function ToggleGroup({
105
+ className,
106
+ variant,
107
+ size,
108
+ spacing = 0,
109
+ orientation = "horizontal",
110
+ children,
111
+ ...props
112
+ }) {
113
+ return /* @__PURE__ */ jsx(
114
+ ToggleGroup$1,
115
+ {
116
+ "data-slot": "toggle-group",
117
+ "data-variant": variant,
118
+ "data-size": size,
119
+ "data-spacing": spacing,
120
+ "data-orientation": orientation,
121
+ style: { "--gap": spacing },
122
+ className: cn(
123
+ "group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-lg data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-vertical:flex-col data-vertical:items-stretch",
124
+ className
125
+ ),
126
+ ...props,
127
+ children: /* @__PURE__ */ jsx(ToggleGroupContext.Provider, { value: { variant, size, spacing, orientation }, children })
128
+ }
129
+ );
130
+ }
131
+ function ToggleGroupItem({
132
+ className,
133
+ children,
134
+ variant = "default",
135
+ size = "default",
136
+ ...props
137
+ }) {
138
+ const context = React16.useContext(ToggleGroupContext);
139
+ return /* @__PURE__ */ jsx(
140
+ Toggle$1,
141
+ {
142
+ "data-slot": "toggle-group-item",
143
+ "data-variant": context.variant || variant,
144
+ "data-size": context.size || size,
145
+ "data-spacing": context.spacing,
146
+ className: cn(
147
+ "shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-lg group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-lg group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-lg group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-lg group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
148
+ toggleVariants({
149
+ variant: context.variant || variant,
150
+ size: context.size || size
151
+ }),
152
+ className
153
+ ),
154
+ ...props,
155
+ children
156
+ }
157
+ );
158
+ }
159
+ function useCalendar(options) {
160
+ const {
161
+ events: eventInputs,
162
+ plugins,
163
+ defaultDate,
164
+ date: controlledDate,
165
+ onDateChange,
166
+ defaultView = "dayGridMonth",
167
+ view: controlledView,
168
+ onViewChange,
169
+ timezone = "UTC",
170
+ locale = "en-US",
171
+ firstDayOfWeek = 0,
172
+ slotDuration,
173
+ slotMinTime,
174
+ slotMaxTime,
175
+ resources = [],
176
+ businessHours,
177
+ eventMaxStack = 3,
178
+ eventDefaults,
179
+ validRange,
180
+ titleFormat,
181
+ titleRangeSeparator,
182
+ onDatesSet,
183
+ onEventAdd,
184
+ onEventChange,
185
+ onEventRemove,
186
+ onEventsSet
187
+ } = options;
188
+ const [internalDate, setInternalDate] = useState(
189
+ () => defaultDate ?? getToday()
190
+ );
191
+ const currentDate = controlledDate ?? internalDate;
192
+ const setDate = useCallback(
193
+ (date) => {
194
+ if (controlledDate === void 0) setInternalDate(date);
195
+ onDateChange?.(date);
196
+ },
197
+ [controlledDate, onDateChange]
198
+ );
199
+ const [internalView, setInternalView] = useState(defaultView);
200
+ const currentView = controlledView ?? internalView;
201
+ const setView = useCallback(
202
+ (view) => {
203
+ if (controlledView === void 0) setInternalView(view);
204
+ onViewChange?.(view);
205
+ },
206
+ [controlledView, onViewChange]
207
+ );
208
+ const registry = useMemo(() => createPluginRegistry(plugins), [plugins]);
209
+ const [eventStore, setEventStore] = useState(
210
+ () => EventStore.from(eventInputs, timezone, eventDefaults)
211
+ );
212
+ useEffect(() => {
213
+ setEventStore(EventStore.from(eventInputs, timezone, eventDefaults));
214
+ }, [eventInputs, timezone, eventDefaults]);
215
+ const visibleRange = useMemo(
216
+ () => getVisibleRange(currentDate, currentView, firstDayOfWeek),
217
+ [currentDate, currentView, firstDayOfWeek]
218
+ );
219
+ const prevRangeRef = useRef(visibleRange);
220
+ useEffect(() => {
221
+ const prevRange = prevRangeRef.current;
222
+ if (!prevRange.start.equals(visibleRange.start) || !prevRange.end.equals(visibleRange.end)) {
223
+ onDatesSet?.({
224
+ start: visibleRange.start,
225
+ end: visibleRange.end,
226
+ view: currentView
227
+ });
228
+ }
229
+ prevRangeRef.current = visibleRange;
230
+ }, [visibleRange, currentView, onDatesSet]);
231
+ useEffect(() => {
232
+ onEventsSet?.(eventStore.getAll());
233
+ }, [eventStore, onEventsSet]);
234
+ const viewConfig = useMemo(() => {
235
+ const defaults = {
236
+ slotDuration: slotDuration ?? Temporal.Duration.from({ minutes: 30 }),
237
+ slotMinTime: slotMinTime ?? Temporal.PlainTime.from("00:00"),
238
+ slotMaxTime: slotMaxTime ?? Temporal.PlainTime.from("23:59:59")
239
+ };
240
+ return {
241
+ type: viewTypeFromPreset(currentView),
242
+ preset: currentView,
243
+ visibleRange,
244
+ slotDuration: defaults.slotDuration,
245
+ slotMinTime: defaults.slotMinTime,
246
+ slotMaxTime: defaults.slotMaxTime,
247
+ firstDayOfWeek,
248
+ hiddenDays: [],
249
+ businessHours,
250
+ eventMaxStack,
251
+ validRange
252
+ };
253
+ }, [
254
+ currentView,
255
+ visibleRange,
256
+ slotDuration,
257
+ slotMinTime,
258
+ slotMaxTime,
259
+ firstDayOfWeek,
260
+ businessHours,
261
+ eventMaxStack,
262
+ validRange
263
+ ]);
264
+ const viewLayout = useMemo(() => {
265
+ const viewType = viewTypeFromPreset(currentView);
266
+ const layoutSolver = resolveLayoutSolver(viewType, registry);
267
+ if (!layoutSolver) return null;
268
+ const eventsInRange = eventStore.getInRange(visibleRange, timezone);
269
+ const context = {
270
+ now: () => Temporal.Now.zonedDateTimeISO(timezone),
271
+ getEvents: () => eventsInRange,
272
+ getResources: () => resources,
273
+ getViewConfig: () => viewConfig
274
+ };
275
+ return layoutSolver(eventsInRange, viewConfig, context);
276
+ }, [currentView, eventStore, visibleRange, timezone, resources, viewConfig, registry]);
277
+ const next = useCallback(
278
+ () => setDate(navigateNext(currentDate, currentView, validRange)),
279
+ [currentDate, currentView, setDate, validRange]
280
+ );
281
+ const prev = useCallback(
282
+ () => setDate(navigatePrev(currentDate, currentView, validRange)),
283
+ [currentDate, currentView, setDate, validRange]
284
+ );
285
+ const todayAction = useCallback(() => setDate(getToday()), [setDate]);
286
+ const goToDate = useCallback((date) => setDate(date), [setDate]);
287
+ const changeView = useCallback((view) => setView(view), [setView]);
288
+ const addEvent = useCallback(
289
+ (event) => {
290
+ setEventStore((store) => {
291
+ const next2 = store.add(event, timezone, eventDefaults);
292
+ const added = next2.getById(event.id);
293
+ if (added) onEventAdd?.({ event: added });
294
+ return next2;
295
+ });
296
+ },
297
+ [timezone, eventDefaults, onEventAdd]
298
+ );
299
+ const updateEvent = useCallback(
300
+ (id, patch) => {
301
+ setEventStore((store) => {
302
+ const oldEvent = store.getById(id);
303
+ const next2 = store.update(id, patch, timezone, eventDefaults);
304
+ const updated = next2.getById(id);
305
+ if (oldEvent && updated) onEventChange?.({ event: updated, oldEvent });
306
+ return next2;
307
+ });
308
+ },
309
+ [timezone, eventDefaults, onEventChange]
310
+ );
311
+ const removeEvent = useCallback(
312
+ (id) => {
313
+ setEventStore((store) => {
314
+ const removed = store.getById(id);
315
+ const next2 = store.remove(id);
316
+ if (removed) onEventRemove?.({ event: removed });
317
+ return next2;
318
+ });
319
+ },
320
+ [onEventRemove]
321
+ );
322
+ const title = useMemo(
323
+ () => getViewTitle(currentDate, currentView, locale, titleFormat, titleRangeSeparator),
324
+ [currentDate, currentView, locale, titleFormat, titleRangeSeparator]
325
+ );
326
+ return {
327
+ currentDate,
328
+ currentView,
329
+ viewLayout,
330
+ visibleRange,
331
+ events: eventStore.getAll(),
332
+ next,
333
+ prev,
334
+ today: todayAction,
335
+ goToDate,
336
+ changeView,
337
+ title,
338
+ addEvent,
339
+ updateEvent,
340
+ removeEvent
341
+ };
342
+ }
343
+ var CALENDAR_EVENT_TYPE = "tempus-machina/event";
344
+ function useDragEvent(options) {
345
+ const {
346
+ event,
347
+ editable = event.editable ?? true,
348
+ startEditable = event.startEditable ?? true,
349
+ dragMinDistance,
350
+ onDragStart,
351
+ onDragEnd
352
+ } = options;
353
+ const dragRef = useRef(null);
354
+ const dragHandleRef = useRef(null);
355
+ const [isDragging, setIsDragging] = useState(false);
356
+ useEffect(() => {
357
+ const el = dragRef.current;
358
+ if (!el) return;
359
+ if (editable === false || startEditable === false) return;
360
+ const cleanup = draggable({
361
+ element: el,
362
+ dragHandle: dragHandleRef.current ?? void 0,
363
+ getInitialData: () => ({
364
+ type: CALENDAR_EVENT_TYPE,
365
+ eventId: event.id,
366
+ eventTitle: event.title,
367
+ eventStart: event.start.toString(),
368
+ eventEnd: event.end.toString(),
369
+ isAllDay: event.isAllDay,
370
+ resourceIds: event.resourceIds
371
+ }),
372
+ onDragStart: () => {
373
+ setIsDragging(true);
374
+ onDragStart?.(event);
375
+ },
376
+ onDrop: () => {
377
+ setIsDragging(false);
378
+ onDragEnd?.(event);
379
+ }
380
+ });
381
+ return cleanup;
382
+ }, [event, editable, startEditable, onDragStart, onDragEnd]);
383
+ return { dragRef, dragHandleRef, isDragging };
384
+ }
385
+
386
+ // src/hooks/use-interaction.ts
387
+ var IDLE_STATE = {
388
+ mode: "idle",
389
+ activeEvent: null,
390
+ ghostEvent: null,
391
+ previewStart: null,
392
+ previewEnd: null,
393
+ dropTargetDate: null,
394
+ dropTargetTime: null,
395
+ dropTargetResourceId: null
396
+ };
397
+ function useInteraction(options = {}) {
398
+ const {
399
+ events = [],
400
+ viewConfig,
401
+ timezone = "UTC",
402
+ dragRevertDuration = 300,
403
+ allDayMaintainDuration = true,
404
+ eventResourceEditable = true,
405
+ onEventDragStart,
406
+ onEventDragStop,
407
+ onEventDrop,
408
+ onEventResize
409
+ } = options;
410
+ const [state, setState] = useState(IDLE_STATE);
411
+ const eventsRef = useRef(events);
412
+ useEffect(() => {
413
+ eventsRef.current = events;
414
+ }, [events]);
415
+ useEffect(() => {
416
+ const cleanup = monitorForElements({
417
+ canMonitor: ({ source }) => source.data.type === CALENDAR_EVENT_TYPE,
418
+ onDragStart: ({ source }) => {
419
+ const eventId = source.data.eventId;
420
+ const activeEvent = eventsRef.current.find((e) => e.id === eventId) ?? null;
421
+ const resizeEdge = source.data.resizeEdge;
422
+ if (activeEvent) {
423
+ const ghostEvent = {
424
+ ...activeEvent,
425
+ id: `ghost-${activeEvent.id}`
426
+ };
427
+ setState({
428
+ mode: resizeEdge ? "resizing" : "dragging",
429
+ activeEvent,
430
+ ghostEvent,
431
+ previewStart: activeEvent.start,
432
+ previewEnd: activeEvent.end,
433
+ dropTargetDate: null,
434
+ dropTargetTime: null,
435
+ dropTargetResourceId: null
436
+ });
437
+ onEventDragStart?.({ event: activeEvent });
438
+ }
439
+ },
440
+ onDrag: ({ location }) => {
441
+ const dropTarget = location.current.dropTargets[0];
442
+ if (dropTarget) {
443
+ setState((prev) => ({
444
+ ...prev,
445
+ dropTargetDate: dropTarget.data.date ?? null,
446
+ dropTargetTime: dropTarget.data.time ?? null,
447
+ dropTargetResourceId: dropTarget.data.resourceId ?? null
448
+ }));
449
+ }
450
+ },
451
+ onDrop: ({ source, location }) => {
452
+ const eventId = source.data.eventId;
453
+ const activeEvent = eventsRef.current.find((e) => e.id === eventId);
454
+ const resizeEdge = source.data.resizeEdge;
455
+ if (activeEvent) {
456
+ onEventDragStop?.({ event: activeEvent });
457
+ const dropTarget = location.current.dropTargets[0];
458
+ if (dropTarget && dropTarget.data.date) {
459
+ const targetDate = Temporal.PlainDate.from(dropTarget.data.date);
460
+ const targetTime = dropTarget.data.time ? Temporal.PlainTime.from(dropTarget.data.time) : Temporal.PlainTime.from("00:00");
461
+ const targetResourceId = dropTarget.data.resourceId ?? void 0;
462
+ const isAllDay = !dropTarget.data.time;
463
+ const oldResourceId = activeEvent.resourceIds?.[0];
464
+ if (!eventResourceEditable && targetResourceId && targetResourceId !== oldResourceId) {
465
+ setState(IDLE_STATE);
466
+ return;
467
+ }
468
+ if (resizeEdge) {
469
+ let newStart = activeEvent.start;
470
+ let newEnd = activeEvent.end;
471
+ const targetZdt = targetDate.toZonedDateTime({
472
+ timeZone: timezone,
473
+ plainTime: targetTime
474
+ });
475
+ if (resizeEdge === "start") {
476
+ newStart = targetZdt;
477
+ } else {
478
+ newEnd = targetZdt;
479
+ }
480
+ onEventResize?.({
481
+ event: activeEvent,
482
+ newStart,
483
+ newEnd,
484
+ revert: () => {
485
+ onEventResize?.({
486
+ event: activeEvent,
487
+ newStart: activeEvent.start,
488
+ newEnd: activeEvent.end,
489
+ revert: () => {
490
+ }
491
+ });
492
+ }
493
+ });
494
+ } else {
495
+ let newStart;
496
+ let newEnd;
497
+ if (isAllDay && allDayMaintainDuration) {
498
+ newStart = targetDate.toZonedDateTime({
499
+ timeZone: timezone,
500
+ plainTime: Temporal.PlainTime.from("00:00")
501
+ });
502
+ const duration = activeEvent.start.until(activeEvent.end);
503
+ newEnd = newStart.add(duration);
504
+ } else {
505
+ newStart = targetDate.toZonedDateTime({
506
+ timeZone: timezone,
507
+ plainTime: targetTime
508
+ });
509
+ const duration = activeEvent.start.until(activeEvent.end);
510
+ newEnd = newStart.add(duration);
511
+ }
512
+ onEventDrop?.({
513
+ event: activeEvent,
514
+ newStart,
515
+ newEnd,
516
+ newResourceId: targetResourceId,
517
+ oldResourceId,
518
+ revert: () => {
519
+ onEventDrop?.({
520
+ event: activeEvent,
521
+ newStart: activeEvent.start,
522
+ newEnd: activeEvent.end,
523
+ newResourceId: oldResourceId,
524
+ oldResourceId: targetResourceId,
525
+ revert: () => {
526
+ }
527
+ });
528
+ }
529
+ });
530
+ }
531
+ }
532
+ }
533
+ setState(IDLE_STATE);
534
+ }
535
+ });
536
+ return cleanup;
537
+ }, [timezone, onEventDragStart, onEventDragStop, onEventDrop]);
538
+ const startDrag = useCallback((event, position) => {
539
+ const ghostEvent = { ...event, id: `ghost-${event.id}` };
540
+ setState({
541
+ mode: "dragging",
542
+ activeEvent: event,
543
+ ghostEvent,
544
+ previewStart: event.start,
545
+ previewEnd: event.end,
546
+ dropTargetDate: null,
547
+ dropTargetTime: null,
548
+ dropTargetResourceId: null
549
+ });
550
+ }, []);
551
+ const startResize = useCallback(
552
+ (event, edge, position) => {
553
+ setState({
554
+ mode: "resizing",
555
+ activeEvent: event,
556
+ ghostEvent: null,
557
+ previewStart: event.start,
558
+ previewEnd: event.end,
559
+ dropTargetDate: null,
560
+ dropTargetTime: null,
561
+ dropTargetResourceId: null
562
+ });
563
+ },
564
+ []
565
+ );
566
+ const startSelect = useCallback((position) => {
567
+ setState({
568
+ mode: "selecting",
569
+ activeEvent: null,
570
+ ghostEvent: null,
571
+ previewStart: null,
572
+ previewEnd: null,
573
+ dropTargetDate: null,
574
+ dropTargetTime: null,
575
+ dropTargetResourceId: null
576
+ });
577
+ }, []);
578
+ const updatePosition = useCallback((_position) => {
579
+ }, []);
580
+ const endInteraction = useCallback(() => {
581
+ const result = {
582
+ type: state.mode === "dragging" ? "move" : state.mode === "resizing" ? "resize" : "select",
583
+ event: state.activeEvent ?? void 0
584
+ };
585
+ setState(IDLE_STATE);
586
+ return result;
587
+ }, [state.mode, state.activeEvent]);
588
+ const cancel = useCallback(() => {
589
+ setState(IDLE_STATE);
590
+ }, []);
591
+ return {
592
+ state,
593
+ startDrag,
594
+ startResize,
595
+ startSelect,
596
+ updatePosition,
597
+ endInteraction,
598
+ cancel
599
+ };
600
+ }
601
+ var CalendarContext = createContext(null);
602
+ function useCalendarContext() {
603
+ const context = useContext(CalendarContext);
604
+ if (!context) {
605
+ throw new Error(
606
+ "[tempus-machina] useCalendarContext must be used within a <Calendar> component."
607
+ );
608
+ }
609
+ return context;
610
+ }
611
+ var DEFAULT_BUTTON_TEXT = {
612
+ today: "Today",
613
+ dayGridMonth: "Month",
614
+ dayGridWeek: "Week",
615
+ dayGridDay: "Day",
616
+ timeGridWeek: "Week",
617
+ timeGridDay: "Day",
618
+ timelineDay: "Timeline",
619
+ multiMonthStack: "Year",
620
+ prev: "\u2039",
621
+ next: "\u203A",
622
+ prevYear: "\xAB",
623
+ nextYear: "\xBB"
624
+ };
625
+ var VIEW_PRESETS = [
626
+ "dayGridMonth",
627
+ "dayGridWeek",
628
+ "dayGridDay",
629
+ "timeGridWeek",
630
+ "timeGridDay",
631
+ "timelineDay",
632
+ "timelineWeek",
633
+ "timelineMonth",
634
+ "multiMonthStack",
635
+ "multiMonthGrid",
636
+ "resourceTimeGridDay",
637
+ "resourceTimeGridWeek"
638
+ ];
639
+ function CalendarHeader({
640
+ className,
641
+ children,
642
+ toolbar,
643
+ buttonText,
644
+ customButtons,
645
+ sticky
646
+ }) {
647
+ const { title, next, prev, today, currentView, changeView, slots, currentDate } = useCalendarContext();
648
+ const mergedText = {
649
+ ...DEFAULT_BUTTON_TEXT,
650
+ ...Object.fromEntries(
651
+ Object.entries(buttonText ?? {}).filter(
652
+ (entry) => entry[1] !== void 0
653
+ )
654
+ )
655
+ };
656
+ if (slots.headerContent) {
657
+ return /* @__PURE__ */ jsx(
658
+ "div",
659
+ {
660
+ className: cn(
661
+ "flex items-center justify-between px-4 py-3 border-b border-cal-border bg-cal-header-bg",
662
+ sticky && "sticky top-0 z-10",
663
+ className
664
+ ),
665
+ children: slots.headerContent({ title, currentView, currentDate })
666
+ }
667
+ );
668
+ }
669
+ if (children) {
670
+ return /* @__PURE__ */ jsx(
671
+ "div",
672
+ {
673
+ className: cn(
674
+ "flex items-center justify-between px-4 py-3 border-b border-cal-border bg-cal-header-bg",
675
+ sticky && "sticky top-0 z-10",
676
+ className
677
+ ),
678
+ children
679
+ }
680
+ );
681
+ }
682
+ if (toolbar) {
683
+ return /* @__PURE__ */ jsxs(
684
+ "div",
685
+ {
686
+ className: cn(
687
+ "flex items-center justify-between px-4 py-3 border-b border-cal-border bg-cal-header-bg",
688
+ sticky && "sticky top-0 z-10",
689
+ className
690
+ ),
691
+ children: [
692
+ /* @__PURE__ */ jsx(
693
+ ToolbarSection,
694
+ {
695
+ tokens: toolbar.left,
696
+ mergedText,
697
+ customButtons,
698
+ prev,
699
+ next,
700
+ today,
701
+ title,
702
+ currentView,
703
+ changeView
704
+ }
705
+ ),
706
+ /* @__PURE__ */ jsx(
707
+ ToolbarSection,
708
+ {
709
+ tokens: toolbar.center,
710
+ mergedText,
711
+ customButtons,
712
+ prev,
713
+ next,
714
+ today,
715
+ title,
716
+ currentView,
717
+ changeView
718
+ }
719
+ ),
720
+ /* @__PURE__ */ jsx(
721
+ ToolbarSection,
722
+ {
723
+ tokens: toolbar.right,
724
+ mergedText,
725
+ customButtons,
726
+ prev,
727
+ next,
728
+ today,
729
+ title,
730
+ currentView,
731
+ changeView
732
+ }
733
+ )
734
+ ]
735
+ }
736
+ );
737
+ }
738
+ return /* @__PURE__ */ jsxs(
739
+ "div",
740
+ {
741
+ className: cn(
742
+ "flex items-center justify-between px-4 py-3 border-b border-cal-border bg-cal-header-bg",
743
+ sticky && "sticky top-0 z-10",
744
+ className
745
+ ),
746
+ children: [
747
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
748
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: prev, "aria-label": "Previous", children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx(
749
+ "path",
750
+ {
751
+ d: "M10 12L6 8L10 4",
752
+ stroke: "currentColor",
753
+ strokeWidth: "2",
754
+ strokeLinecap: "round",
755
+ strokeLinejoin: "round"
756
+ }
757
+ ) }) }),
758
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: next, "aria-label": "Next", children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx(
759
+ "path",
760
+ {
761
+ d: "M6 4L10 8L6 12",
762
+ stroke: "currentColor",
763
+ strokeWidth: "2",
764
+ strokeLinecap: "round",
765
+ strokeLinejoin: "round"
766
+ }
767
+ ) }) }),
768
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: today, children: "Today" })
769
+ ] }),
770
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-cal-header-fg", children: title }),
771
+ /* @__PURE__ */ jsx(
772
+ ToggleGroup,
773
+ {
774
+ value: [currentView],
775
+ onValueChange: (newValue) => {
776
+ if (newValue.length > 0) {
777
+ changeView(newValue[0]);
778
+ }
779
+ },
780
+ variant: "outline",
781
+ children: ["dayGridMonth", "timeGridWeek", "timeGridDay"].map((view) => /* @__PURE__ */ jsx(ToggleGroupItem, { value: view, size: "sm", children: mergedText[view] ?? view }, view))
782
+ }
783
+ )
784
+ ]
785
+ }
786
+ );
787
+ }
788
+ function ToolbarSection({
789
+ tokens,
790
+ mergedText,
791
+ customButtons,
792
+ prev,
793
+ next,
794
+ today,
795
+ title,
796
+ currentView,
797
+ changeView
798
+ }) {
799
+ if (!tokens) return /* @__PURE__ */ jsx("div", {});
800
+ const groups = tokens.split(" ").filter(Boolean);
801
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: groups.map((group, gIdx) => {
802
+ const items = group.split(",").filter(Boolean);
803
+ const viewItems = items.filter((t) => VIEW_PRESETS.includes(t));
804
+ const actionItems = items.filter((t) => !VIEW_PRESETS.includes(t));
805
+ if (viewItems.length > 0 && actionItems.length === 0) {
806
+ return /* @__PURE__ */ jsx(
807
+ ToggleGroup,
808
+ {
809
+ value: [currentView],
810
+ onValueChange: (v) => v.length > 0 && changeView(v[0]),
811
+ variant: "outline",
812
+ children: viewItems.map((view) => /* @__PURE__ */ jsx(ToggleGroupItem, { value: view, size: "sm", children: mergedText[view] ?? view }, view))
813
+ },
814
+ gIdx
815
+ );
816
+ }
817
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: items.map(
818
+ (token) => renderToken(token, mergedText, customButtons, {
819
+ prev,
820
+ next,
821
+ today,
822
+ title,
823
+ changeView,
824
+ currentView
825
+ })
826
+ ) }, gIdx);
827
+ }) });
828
+ }
829
+ function renderToken(token, text, customButtons, actions) {
830
+ switch (token) {
831
+ case "title":
832
+ return /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-cal-header-fg", children: actions?.title }, "title");
833
+ case "prev":
834
+ return /* @__PURE__ */ jsx(
835
+ Button,
836
+ {
837
+ variant: "outline",
838
+ size: "icon",
839
+ onClick: actions?.prev,
840
+ "aria-label": "Previous",
841
+ children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx(
842
+ "path",
843
+ {
844
+ d: "M10 12L6 8L10 4",
845
+ stroke: "currentColor",
846
+ strokeWidth: "2",
847
+ strokeLinecap: "round",
848
+ strokeLinejoin: "round"
849
+ }
850
+ ) })
851
+ },
852
+ "prev"
853
+ );
854
+ case "next":
855
+ return /* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: actions?.next, "aria-label": "Next", children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx(
856
+ "path",
857
+ {
858
+ d: "M6 4L10 8L6 12",
859
+ stroke: "currentColor",
860
+ strokeWidth: "2",
861
+ strokeLinecap: "round",
862
+ strokeLinejoin: "round"
863
+ }
864
+ ) }) }, "next");
865
+ case "today":
866
+ return /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: actions?.today, children: text.today ?? "Today" }, "today");
867
+ default:
868
+ if (customButtons?.[token]) {
869
+ const btn = customButtons[token];
870
+ return /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: btn.click, children: [
871
+ btn.icon,
872
+ btn.text
873
+ ] }, token);
874
+ }
875
+ if (VIEW_PRESETS.includes(token)) {
876
+ return /* @__PURE__ */ jsx(
877
+ Button,
878
+ {
879
+ variant: actions?.currentView === token ? "default" : "outline",
880
+ size: "sm",
881
+ onClick: () => actions?.changeView(token),
882
+ children: text[token] ?? token
883
+ },
884
+ token
885
+ );
886
+ }
887
+ return null;
888
+ }
889
+ }
890
+ var eventCardVariants = cva(
891
+ [
892
+ "rounded-[var(--cal-event-border-radius)] px-2 py-1 text-xs font-medium",
893
+ "cursor-pointer select-none",
894
+ "transition-all duration-[var(--cal-transition-duration)]",
895
+ // Hover: subtle lift + glow
896
+ "hover:brightness-110 hover:shadow-md hover:-translate-y-px",
897
+ // Active: pressed feedback
898
+ "active:scale-[0.98] active:shadow-sm"
899
+ ].join(" "),
900
+ {
901
+ variants: {
902
+ variant: {
903
+ default: "bg-cal-event-default-bg text-cal-event-default-fg",
904
+ outline: "border border-current bg-transparent",
905
+ ghost: "border border-dashed border-current bg-transparent opacity-60"
906
+ },
907
+ size: {
908
+ sm: "text-[10px] px-1.5 py-0.5 leading-tight",
909
+ default: "text-xs px-2 py-1",
910
+ lg: "text-sm px-3 py-1.5"
911
+ },
912
+ state: {
913
+ idle: "",
914
+ dragging: "opacity-[var(--cal-drag-opacity)] shadow-lg scale-[1.02] z-50",
915
+ resizing: "opacity-90 z-50",
916
+ selected: "ring-2 ring-cal-event-selected-ring ring-offset-1",
917
+ focused: "ring-2 ring-cal-event-selected-ring ring-offset-1"
918
+ }
919
+ },
920
+ defaultVariants: {
921
+ variant: "default",
922
+ size: "default",
923
+ state: "idle"
924
+ }
925
+ }
926
+ );
927
+ var dayCellVariants = cva(
928
+ [
929
+ "min-h-24 p-1.5 border-b border-r border-cal-border",
930
+ "transition-colors duration-[var(--cal-transition-duration)]",
931
+ // Hover: subtle highlight
932
+ "hover:bg-cal-slot-bg-odd"
933
+ ].join(" "),
934
+ {
935
+ variants: {
936
+ variant: {
937
+ default: "bg-cal-slot-bg",
938
+ today: "bg-cal-today-bg hover:bg-cal-today-bg",
939
+ weekend: "bg-cal-weekend-bg",
940
+ otherMonth: "bg-cal-slot-bg opacity-50"
941
+ }
942
+ },
943
+ defaultVariants: {
944
+ variant: "default"
945
+ }
946
+ }
947
+ );
948
+ var timeSlotVariants = cva(
949
+ ["border-b border-cal-slot-border", "transition-colors duration-75"].join(" "),
950
+ {
951
+ variants: {
952
+ variant: {
953
+ default: "bg-cal-slot-bg",
954
+ odd: "bg-cal-slot-bg-odd",
955
+ business: "bg-cal-slot-business",
956
+ nonBusiness: "bg-cal-slot-non-business"
957
+ }
958
+ },
959
+ defaultVariants: {
960
+ variant: "default"
961
+ }
962
+ }
963
+ );
964
+ function resolveEventStyles(event) {
965
+ const bgColor = event.backgroundColor ?? event.color ?? void 0;
966
+ const borderClr = event.borderColor ?? event.color ?? void 0;
967
+ const textClr = event.textColor ?? void 0;
968
+ if (!bgColor && !borderClr && !textClr) return void 0;
969
+ return {
970
+ ...bgColor ? { backgroundColor: bgColor } : {},
971
+ ...borderClr ? { borderLeft: `3px solid ${borderClr}` } : {},
972
+ ...textClr ? { color: textClr } : { ...bgColor ? { color: "white" } : {} }
973
+ };
974
+ }
975
+ function EventCard({
976
+ positionedEvent,
977
+ compact = false,
978
+ eventTimeFormat,
979
+ displayEventTime = true,
980
+ displayEventEnd = false
981
+ }) {
982
+ const {
983
+ slots,
984
+ onEventClick,
985
+ onEventMouseEnter,
986
+ onEventMouseLeave,
987
+ currentView,
988
+ locale,
989
+ editable
990
+ } = useCalendarContext();
991
+ const { event } = positionedEvent;
992
+ const { dragRef, isDragging } = useDragEvent({
993
+ event,
994
+ editable: editable && event.editable !== false
995
+ });
996
+ const handleClick = useCallback(
997
+ (e) => {
998
+ e.stopPropagation();
999
+ onEventClick?.(event);
1000
+ },
1001
+ [event, onEventClick]
1002
+ );
1003
+ const handleMouseEnter = useCallback(
1004
+ (e) => {
1005
+ onEventMouseEnter?.({ event, el: e.currentTarget });
1006
+ },
1007
+ [event, onEventMouseEnter]
1008
+ );
1009
+ const handleMouseLeave = useCallback(
1010
+ (e) => {
1011
+ onEventMouseLeave?.({ event, el: e.currentTarget });
1012
+ },
1013
+ [event, onEventMouseLeave]
1014
+ );
1015
+ if (event.display === "none") return null;
1016
+ const state = isDragging ? "dragging" : positionedEvent.isDragging ? "dragging" : positionedEvent.isResizing ? "resizing" : positionedEvent.isSelected ? "selected" : positionedEvent.isFocused ? "focused" : "idle";
1017
+ const timeText = event.isAllDay ? "All day" : displayEventTime ? formatEventTime(event.start, locale, eventTimeFormat) : "";
1018
+ const endTimeText = displayEventEnd && !event.isAllDay ? formatEventTime(event.end, locale, eventTimeFormat) : "";
1019
+ const fullTimeText = endTimeText ? `${timeText} \u2013 ${endTimeText}` : timeText;
1020
+ const eventStyles = resolveEventStyles(event);
1021
+ if (slots.eventContent) {
1022
+ return /* @__PURE__ */ jsx(
1023
+ "div",
1024
+ {
1025
+ ref: dragRef,
1026
+ onClick: handleClick,
1027
+ onMouseEnter: handleMouseEnter,
1028
+ onMouseLeave: handleMouseLeave,
1029
+ className: cn(eventCardVariants({ state }), event.className, "cursor-pointer"),
1030
+ style: { ...eventStyles, ...isDragging ? { opacity: 0.5 } : {} },
1031
+ children: slots.eventContent({
1032
+ event,
1033
+ view: currentView,
1034
+ isStart: true,
1035
+ isEnd: true,
1036
+ isDragging,
1037
+ timeText: fullTimeText
1038
+ })
1039
+ }
1040
+ );
1041
+ }
1042
+ if (event.display === "list-item") {
1043
+ const dotColor = event.backgroundColor ?? event.color ?? "var(--cal-event-default-bg)";
1044
+ return /* @__PURE__ */ jsxs(
1045
+ "div",
1046
+ {
1047
+ ref: dragRef,
1048
+ onClick: handleClick,
1049
+ onMouseEnter: handleMouseEnter,
1050
+ onMouseLeave: handleMouseLeave,
1051
+ className: cn(
1052
+ "flex items-center gap-1 text-xs cursor-pointer truncate py-0.5 px-1",
1053
+ event.className
1054
+ ),
1055
+ style: isDragging ? { opacity: 0.5 } : void 0,
1056
+ children: [
1057
+ /* @__PURE__ */ jsx(
1058
+ "span",
1059
+ {
1060
+ className: "inline-block w-2 h-2 rounded-full shrink-0",
1061
+ style: { backgroundColor: dotColor }
1062
+ }
1063
+ ),
1064
+ displayEventTime && !event.isAllDay && /* @__PURE__ */ jsx("span", { className: "font-normal opacity-75", children: fullTimeText }),
1065
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: event.title })
1066
+ ]
1067
+ }
1068
+ );
1069
+ }
1070
+ if (compact) {
1071
+ return /* @__PURE__ */ jsxs(
1072
+ "div",
1073
+ {
1074
+ ref: dragRef,
1075
+ onClick: handleClick,
1076
+ onMouseEnter: handleMouseEnter,
1077
+ onMouseLeave: handleMouseLeave,
1078
+ className: cn(
1079
+ eventCardVariants({ size: "sm", state }),
1080
+ event.className,
1081
+ "truncate cursor-pointer"
1082
+ ),
1083
+ style: { ...eventStyles, ...isDragging ? { opacity: 0.5 } : {} },
1084
+ title: event.title,
1085
+ children: [
1086
+ displayEventTime && !event.isAllDay && /* @__PURE__ */ jsx("span", { className: "font-normal opacity-75 mr-1", children: fullTimeText }),
1087
+ event.title
1088
+ ]
1089
+ }
1090
+ );
1091
+ }
1092
+ return /* @__PURE__ */ jsxs(
1093
+ "div",
1094
+ {
1095
+ ref: dragRef,
1096
+ onClick: handleClick,
1097
+ onMouseEnter: handleMouseEnter,
1098
+ onMouseLeave: handleMouseLeave,
1099
+ className: cn(eventCardVariants({ state }), event.className, "cursor-pointer"),
1100
+ style: { ...eventStyles, ...isDragging ? { opacity: 0.5 } : {} },
1101
+ children: [
1102
+ /* @__PURE__ */ jsx("div", { className: "font-semibold truncate", children: event.title }),
1103
+ displayEventTime && !event.isAllDay && /* @__PURE__ */ jsx("div", { className: "text-[10px] opacity-75", children: fullTimeText })
1104
+ ]
1105
+ }
1106
+ );
1107
+ }
1108
+ function MorePopover({
1109
+ events,
1110
+ count,
1111
+ dateStr,
1112
+ moreLinkClick = "popover",
1113
+ onNavigateToDay,
1114
+ allEvents,
1115
+ className
1116
+ }) {
1117
+ const [isOpen, setIsOpen] = useState(false);
1118
+ const triggerRef = useRef(null);
1119
+ const popoverRef = useRef(null);
1120
+ useEffect(() => {
1121
+ if (!isOpen) return;
1122
+ const handleClick2 = (e) => {
1123
+ if (popoverRef.current && !popoverRef.current.contains(e.target) && triggerRef.current && !triggerRef.current.contains(e.target)) {
1124
+ setIsOpen(false);
1125
+ }
1126
+ };
1127
+ document.addEventListener("mousedown", handleClick2);
1128
+ return () => document.removeEventListener("mousedown", handleClick2);
1129
+ }, [isOpen]);
1130
+ useEffect(() => {
1131
+ if (!isOpen) return;
1132
+ const handleKey = (e) => {
1133
+ if (e.key === "Escape") setIsOpen(false);
1134
+ };
1135
+ document.addEventListener("keydown", handleKey);
1136
+ return () => document.removeEventListener("keydown", handleKey);
1137
+ }, [isOpen]);
1138
+ const handleClick = () => {
1139
+ if (moreLinkClick === "day") {
1140
+ onNavigateToDay?.(dateStr);
1141
+ return;
1142
+ }
1143
+ if (typeof moreLinkClick === "function") {
1144
+ moreLinkClick({
1145
+ date: dateStr,
1146
+ allEvents: allEvents ?? events,
1147
+ hiddenEvents: events
1148
+ });
1149
+ return;
1150
+ }
1151
+ setIsOpen(!isOpen);
1152
+ };
1153
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
1154
+ /* @__PURE__ */ jsxs(
1155
+ "button",
1156
+ {
1157
+ ref: triggerRef,
1158
+ onClick: handleClick,
1159
+ className: "text-[10px] font-semibold text-cal-today-fg mt-0.5 cursor-pointer px-1.5 py-0.5 rounded-full hover:bg-cal-today-bg hover:underline focus:outline-none transition-colors",
1160
+ "aria-expanded": isOpen,
1161
+ "aria-haspopup": "true",
1162
+ children: [
1163
+ "+",
1164
+ count,
1165
+ " more"
1166
+ ]
1167
+ }
1168
+ ),
1169
+ isOpen && /* @__PURE__ */ jsxs(
1170
+ "div",
1171
+ {
1172
+ ref: popoverRef,
1173
+ className: cn(
1174
+ "absolute z-50 top-full left-0 mt-1",
1175
+ "w-48 max-h-64 overflow-y-auto",
1176
+ "bg-cal-bg border border-cal-border rounded-lg shadow-lg",
1177
+ "p-2 space-y-1"
1178
+ ),
1179
+ role: "dialog",
1180
+ "aria-label": `Events for ${dateStr}`,
1181
+ children: [
1182
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-semibold text-cal-fg-muted mb-1 px-1", children: dateStr }),
1183
+ events.map((posEvent) => /* @__PURE__ */ jsx(EventCard, { positionedEvent: posEvent, compact: true }, posEvent.event.id))
1184
+ ]
1185
+ }
1186
+ )
1187
+ ] });
1188
+ }
1189
+ function DayCell({ cell, moreLinkClick, navLinkDayClick }) {
1190
+ const { slots, onDateSelect, changeView, editable } = useCalendarContext();
1191
+ const cellRef = useRef(null);
1192
+ const [isDragOver, setIsDragOver] = useState(false);
1193
+ const variant = cell.isToday ? "today" : !cell.isCurrentMonth ? "otherMonth" : cell.isWeekend ? "weekend" : "default";
1194
+ useEffect(() => {
1195
+ const el = cellRef.current;
1196
+ if (!el || !editable) return;
1197
+ const cleanup = dropTargetForElements({
1198
+ element: el,
1199
+ getData: () => ({
1200
+ date: cell.date.toString(),
1201
+ time: null,
1202
+ // All-day drop for month view
1203
+ resourceId: null
1204
+ }),
1205
+ canDrop: ({ source }) => source.data.type === CALENDAR_EVENT_TYPE,
1206
+ onDragEnter: () => setIsDragOver(true),
1207
+ onDragLeave: () => setIsDragOver(false),
1208
+ onDrop: () => setIsDragOver(false)
1209
+ });
1210
+ return cleanup;
1211
+ }, [editable, cell.date]);
1212
+ const handleClick = () => {
1213
+ if (onDateSelect) {
1214
+ const start = cell.date.toPlainDateTime(Temporal.PlainTime.from("00:00"));
1215
+ const end = cell.date.add({ days: 1 }).toPlainDateTime(Temporal.PlainTime.from("00:00"));
1216
+ onDateSelect({ start, end });
1217
+ }
1218
+ };
1219
+ const handleDayNumberClick = (e) => {
1220
+ if (!navLinkDayClick) return;
1221
+ e.stopPropagation();
1222
+ if (typeof navLinkDayClick === "function") {
1223
+ navLinkDayClick(cell.date);
1224
+ } else {
1225
+ changeView("timeGridDay");
1226
+ }
1227
+ };
1228
+ if (slots.dayCellContent) {
1229
+ return /* @__PURE__ */ jsx(
1230
+ "div",
1231
+ {
1232
+ ref: cellRef,
1233
+ className: cn(
1234
+ dayCellVariants({ variant }),
1235
+ isDragOver && "ring-2 ring-inset ring-cal-today-fg/50 bg-cal-today-bg/30"
1236
+ ),
1237
+ onClick: handleClick,
1238
+ children: slots.dayCellContent({
1239
+ date: cell.date,
1240
+ isToday: cell.isToday,
1241
+ isCurrentMonth: cell.isCurrentMonth,
1242
+ isWeekend: cell.isWeekend,
1243
+ dayNumberText: String(cell.date.day),
1244
+ events: cell.events.map((e) => e.event)
1245
+ })
1246
+ }
1247
+ );
1248
+ }
1249
+ return /* @__PURE__ */ jsxs(
1250
+ "div",
1251
+ {
1252
+ ref: cellRef,
1253
+ className: cn(
1254
+ dayCellVariants({ variant }),
1255
+ isDragOver && "ring-2 ring-inset ring-cal-today-fg/50 bg-cal-today-bg/30"
1256
+ ),
1257
+ onClick: handleClick,
1258
+ children: [
1259
+ /* @__PURE__ */ jsx(
1260
+ "div",
1261
+ {
1262
+ className: cn(
1263
+ "text-xs font-medium mb-1",
1264
+ cell.isToday && "inline-flex items-center justify-center w-6 h-6 rounded-full bg-cal-today-fg text-white",
1265
+ !cell.isCurrentMonth && "opacity-40",
1266
+ navLinkDayClick && "cursor-pointer hover:text-cal-today-fg hover:underline"
1267
+ ),
1268
+ onClick: navLinkDayClick ? handleDayNumberClick : void 0,
1269
+ role: navLinkDayClick ? "link" : void 0,
1270
+ children: cell.date.day
1271
+ }
1272
+ ),
1273
+ /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: cell.events.map((posEvent) => /* @__PURE__ */ jsx(EventCard, { positionedEvent: posEvent, compact: true }, posEvent.event.id)) }),
1274
+ cell.moreCount > 0 && /* @__PURE__ */ jsx(
1275
+ MorePopover,
1276
+ {
1277
+ events: cell.events,
1278
+ count: cell.moreCount,
1279
+ dateStr: cell.date.toString(),
1280
+ moreLinkClick
1281
+ }
1282
+ )
1283
+ ]
1284
+ }
1285
+ );
1286
+ }
1287
+ function DayGrid({
1288
+ className,
1289
+ fixedWeekCount = false,
1290
+ showNonCurrentDates = true,
1291
+ weekNumbers = false
1292
+ }) {
1293
+ const { viewLayout, locale } = useCalendarContext();
1294
+ if (!viewLayout?.weeks) return null;
1295
+ let weeks = viewLayout.weeks;
1296
+ if (fixedWeekCount && weeks.length < 6) {
1297
+ weeks = [...weeks];
1298
+ while (weeks.length < 6) {
1299
+ const lastWeek = weeks[weeks.length - 1];
1300
+ const lastDate = lastWeek[lastWeek.length - 1].date;
1301
+ const newWeek = [];
1302
+ for (let d = 1; d <= 7; d++) {
1303
+ const date = lastDate.add({ days: d });
1304
+ newWeek.push({
1305
+ date,
1306
+ dayOfWeek: date.dayOfWeek,
1307
+ isToday: false,
1308
+ isCurrentMonth: false,
1309
+ isWeekend: date.dayOfWeek === 6 || date.dayOfWeek === 7,
1310
+ isHidden: false,
1311
+ events: [],
1312
+ moreCount: 0
1313
+ });
1314
+ }
1315
+ weeks.push(newWeek);
1316
+ }
1317
+ } else if (!fixedWeekCount && weeks.length > 5) {
1318
+ const lastWeek = weeks[weeks.length - 1];
1319
+ if (lastWeek.every((cell) => !cell.isCurrentMonth)) {
1320
+ weeks = weeks.slice(0, -1);
1321
+ }
1322
+ }
1323
+ const dayLabels = weeks[0]?.map((cell) => {
1324
+ const jsDate = new Date(cell.date.toString());
1325
+ return new Intl.DateTimeFormat(locale, { weekday: "short" }).format(jsDate);
1326
+ }) ?? [];
1327
+ return /* @__PURE__ */ jsxs("div", { className: cn("tm-day-grid-wrapper flex-1 flex flex-col overflow-hidden", className), children: [
1328
+ /* @__PURE__ */ jsxs(
1329
+ "div",
1330
+ {
1331
+ className: "border-b border-cal-border",
1332
+ style: {
1333
+ display: "grid",
1334
+ gridTemplateColumns: weekNumbers ? `2rem repeat(7, 1fr)` : `repeat(7, 1fr)`
1335
+ },
1336
+ children: [
1337
+ weekNumbers && /* @__PURE__ */ jsx("div", { className: "py-2 text-center text-xs font-medium text-cal-fg opacity-40", children: "Wk" }),
1338
+ dayLabels.map((label, i) => /* @__PURE__ */ jsx("div", { className: "py-2 text-center text-xs font-medium text-cal-fg opacity-60", children: label }, i))
1339
+ ]
1340
+ }
1341
+ ),
1342
+ /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col", children: weeks.map((week, weekIdx) => /* @__PURE__ */ jsxs(
1343
+ "div",
1344
+ {
1345
+ className: "flex-1",
1346
+ style: {
1347
+ display: "grid",
1348
+ gridTemplateColumns: weekNumbers ? `2rem repeat(7, 1fr)` : `repeat(7, 1fr)`,
1349
+ minHeight: "5rem"
1350
+ },
1351
+ children: [
1352
+ weekNumbers && week[0] && /* @__PURE__ */ jsx("div", { className: "flex items-start justify-center pt-1 text-[10px] text-cal-fg-muted border-r border-cal-border", children: getWeekNumber(week[0].date) }),
1353
+ week.map((cell) => {
1354
+ if (!showNonCurrentDates && !cell.isCurrentMonth) {
1355
+ return /* @__PURE__ */ jsx(
1356
+ "div",
1357
+ {
1358
+ className: "min-h-[5rem] border-r border-b border-cal-border"
1359
+ },
1360
+ cell.date.toString()
1361
+ );
1362
+ }
1363
+ return /* @__PURE__ */ jsx(DayCell, { cell }, cell.date.toString());
1364
+ })
1365
+ ]
1366
+ },
1367
+ weekIdx
1368
+ )) })
1369
+ ] });
1370
+ }
1371
+ function TimeSlot({ slot, date, resourceId }) {
1372
+ const { slots, onDateSelect, selectable, editable } = useCalendarContext();
1373
+ const slotRef = useRef(null);
1374
+ const [isDragOver, setIsDragOver] = useState(false);
1375
+ const variant = slot.isBusinessHour ? "business" : slot.index % 2 === 0 ? "default" : "odd";
1376
+ useEffect(() => {
1377
+ const el = slotRef.current;
1378
+ if (!el || !editable || !date) return;
1379
+ const cleanup = dropTargetForElements({
1380
+ element: el,
1381
+ getData: () => ({
1382
+ date: date.toString(),
1383
+ time: slot.time.toString(),
1384
+ resourceId: resourceId ?? null
1385
+ }),
1386
+ canDrop: ({ source }) => source.data.type === CALENDAR_EVENT_TYPE,
1387
+ onDragEnter: () => setIsDragOver(true),
1388
+ onDragLeave: () => setIsDragOver(false),
1389
+ onDrop: () => setIsDragOver(false)
1390
+ });
1391
+ return cleanup;
1392
+ }, [editable, date, slot.time, resourceId]);
1393
+ const handleClick = () => {
1394
+ if (selectable && onDateSelect) {
1395
+ onDateSelect({ start: slot.start, end: slot.end });
1396
+ }
1397
+ };
1398
+ if (slots.slotContent) {
1399
+ return /* @__PURE__ */ jsx(
1400
+ "div",
1401
+ {
1402
+ ref: slotRef,
1403
+ className: cn(timeSlotVariants({ variant }), isDragOver && "bg-cal-today-bg/50"),
1404
+ style: { height: "var(--cal-slot-height)" },
1405
+ onClick: handleClick,
1406
+ children: slots.slotContent({
1407
+ start: slot.start,
1408
+ end: slot.end,
1409
+ isBusinessHour: slot.isBusinessHour
1410
+ })
1411
+ }
1412
+ );
1413
+ }
1414
+ return /* @__PURE__ */ jsx(
1415
+ "div",
1416
+ {
1417
+ ref: slotRef,
1418
+ className: cn(timeSlotVariants({ variant }), isDragOver && "bg-cal-today-bg/50"),
1419
+ style: { height: "var(--cal-slot-height)" },
1420
+ onClick: handleClick,
1421
+ role: selectable ? "button" : void 0,
1422
+ tabIndex: selectable ? 0 : void 0
1423
+ }
1424
+ );
1425
+ }
1426
+ function NowIndicator({ position }) {
1427
+ const { slots } = useCalendarContext();
1428
+ if (slots.nowIndicatorContent) {
1429
+ return /* @__PURE__ */ jsx("div", { className: "tm-now-indicator", style: { top: `${position}%` }, children: slots.nowIndicatorContent() });
1430
+ }
1431
+ return /* @__PURE__ */ jsx("div", { className: "tm-now-indicator", style: { top: `${position}%` } });
1432
+ }
1433
+ function AllDayRow({ events, className }) {
1434
+ if (events.length === 0) return null;
1435
+ return /* @__PURE__ */ jsx("div", { className: cn("tm-all-day-row border-b border-cal-border px-2", className), children: events.map((posEvent) => /* @__PURE__ */ jsx(EventCard, { positionedEvent: posEvent, compact: true }, posEvent.event.id)) });
1436
+ }
1437
+ function TimeGrid({
1438
+ className,
1439
+ scrollTime = "06:00:00",
1440
+ scrollTimeReset = true,
1441
+ stickyHeaderDates = true,
1442
+ slotLabelFormat,
1443
+ stickyFooterScrollbar = false
1444
+ }) {
1445
+ const { viewLayout, locale, currentView } = useCalendarContext();
1446
+ const scrollContainerRef = useRef(null);
1447
+ useEffect(() => {
1448
+ const container = scrollContainerRef.current;
1449
+ if (!container || !viewLayout?.timeSlots) return;
1450
+ try {
1451
+ const scrollTarget = Temporal.PlainTime.from(scrollTime);
1452
+ const slots = viewLayout.timeSlots;
1453
+ if (slots.length === 0) return;
1454
+ const firstSlot = slots[0];
1455
+ const totalMinutes = slots[slots.length - 1].time.hour * 60 + slots[slots.length - 1].time.minute - (firstSlot.time.hour * 60 + firstSlot.time.minute);
1456
+ const targetMinutes = scrollTarget.hour * 60 + scrollTarget.minute - (firstSlot.time.hour * 60 + firstSlot.time.minute);
1457
+ if (totalMinutes > 0) {
1458
+ const scrollPercent = Math.max(0, Math.min(1, targetMinutes / totalMinutes));
1459
+ container.scrollTop = scrollPercent * container.scrollHeight;
1460
+ }
1461
+ } catch {
1462
+ }
1463
+ }, [scrollTime, scrollTimeReset ? currentView : null, viewLayout?.timeSlots]);
1464
+ if (!viewLayout?.columns) return null;
1465
+ const columns = viewLayout.columns;
1466
+ const timeSlots = viewLayout.timeSlots ?? [];
1467
+ const allDayRow = viewLayout.allDayRow ?? [];
1468
+ const nowPosition = viewLayout.nowPosition;
1469
+ const gridCols = `var(--cal-time-axis-width, 4rem) repeat(${columns.length}, 1fr)`;
1470
+ const formatSlotTime = (time) => {
1471
+ if (slotLabelFormat) {
1472
+ const jsDate = new Date(2e3, 0, 1, time.hour, time.minute);
1473
+ return new Intl.DateTimeFormat(locale, slotLabelFormat).format(jsDate);
1474
+ }
1475
+ return formatTime(time, locale);
1476
+ };
1477
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex-1 flex flex-col overflow-hidden", className), children: [
1478
+ allDayRow.length > 0 && /* @__PURE__ */ jsx(AllDayRow, { events: allDayRow }),
1479
+ /* @__PURE__ */ jsxs(
1480
+ "div",
1481
+ {
1482
+ className: cn(
1483
+ "border-b border-cal-border bg-cal-header-bg shrink-0",
1484
+ stickyHeaderDates && "sticky top-0 z-10"
1485
+ ),
1486
+ style: {
1487
+ display: "grid",
1488
+ gridTemplateColumns: gridCols
1489
+ },
1490
+ children: [
1491
+ /* @__PURE__ */ jsx("div", {}),
1492
+ " ",
1493
+ columns.map((col) => {
1494
+ const jsDate = new Date(col.date.toString());
1495
+ const dayName = new Intl.DateTimeFormat(locale, { weekday: "short" }).format(jsDate);
1496
+ const dayNum = col.date.day;
1497
+ const isToday = col.date.equals(Temporal.Now.plainDateISO());
1498
+ return /* @__PURE__ */ jsxs("div", { className: "text-center py-2 border-l border-cal-border", children: [
1499
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium opacity-60", children: dayName }),
1500
+ /* @__PURE__ */ jsx(
1501
+ "div",
1502
+ {
1503
+ className: cn(
1504
+ "text-lg font-semibold",
1505
+ isToday && "inline-flex items-center justify-center w-8 h-8 rounded-full bg-cal-today-fg text-white"
1506
+ ),
1507
+ children: dayNum
1508
+ }
1509
+ )
1510
+ ] }, col.date.toString());
1511
+ })
1512
+ ]
1513
+ }
1514
+ ),
1515
+ /* @__PURE__ */ jsx("div", { ref: scrollContainerRef, className: "flex-1 overflow-y-auto tm-scrollable", children: /* @__PURE__ */ jsxs(
1516
+ "div",
1517
+ {
1518
+ style: {
1519
+ display: "grid",
1520
+ gridTemplateColumns: gridCols,
1521
+ position: "relative"
1522
+ },
1523
+ children: [
1524
+ /* @__PURE__ */ jsx("div", { children: timeSlots.map((slot) => /* @__PURE__ */ jsx(
1525
+ "div",
1526
+ {
1527
+ className: "relative border-b border-cal-slot-border",
1528
+ style: { height: "var(--cal-slot-height, 3rem)" },
1529
+ children: slot.time.minute === 0 && /* @__PURE__ */ jsx("span", { className: "absolute -top-3 right-2 text-[10px] text-cal-fg opacity-50", children: formatSlotTime(slot.time) })
1530
+ },
1531
+ slot.index
1532
+ )) }),
1533
+ columns.map((col) => /* @__PURE__ */ jsxs("div", { className: "relative border-l border-cal-border", children: [
1534
+ col.slots.map((slot) => /* @__PURE__ */ jsx(TimeSlot, { slot, date: col.date }, slot.index)),
1535
+ col.events.map((posEvent) => /* @__PURE__ */ jsx(
1536
+ "div",
1537
+ {
1538
+ className: "absolute overflow-hidden z-1",
1539
+ style: {
1540
+ top: `${posEvent.top}%`,
1541
+ height: `${Math.max(posEvent.height, 2)}%`,
1542
+ left: `${posEvent.left}%`,
1543
+ width: `${posEvent.width}%`,
1544
+ minHeight: "1.25rem"
1545
+ },
1546
+ children: /* @__PURE__ */ jsx(EventCard, { positionedEvent: posEvent })
1547
+ },
1548
+ posEvent.event.id
1549
+ )),
1550
+ nowPosition !== void 0 && nowPosition >= 0 && col.date.equals(Temporal.Now.plainDateISO()) && /* @__PURE__ */ jsx(NowIndicator, { position: nowPosition })
1551
+ ] }, col.date.toString()))
1552
+ ]
1553
+ }
1554
+ ) }),
1555
+ stickyFooterScrollbar && /* @__PURE__ */ jsx("div", { className: "sticky bottom-0 h-3 bg-cal-bg border-t border-cal-border overflow-x-auto shrink-0" })
1556
+ ] });
1557
+ }
1558
+ function ResourceLane({ lane, slotCount }) {
1559
+ const { slots } = useCalendarContext();
1560
+ return /* @__PURE__ */ jsxs(
1561
+ "div",
1562
+ {
1563
+ className: "grid border-b border-cal-border",
1564
+ style: { gridTemplateColumns: `var(--cal-resource-header-width) repeat(${slotCount}, 1fr)` },
1565
+ children: [
1566
+ /* @__PURE__ */ jsx("div", { className: "p-2 border-r border-cal-border bg-cal-header-bg", children: slots.resourceLabelContent ? slots.resourceLabelContent({ resource: lane.resource }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1567
+ lane.resource.color && /* @__PURE__ */ jsx(
1568
+ "div",
1569
+ {
1570
+ className: "w-2 h-2 rounded-full flex-shrink-0",
1571
+ style: { backgroundColor: lane.resource.color }
1572
+ }
1573
+ ),
1574
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: lane.resource.title })
1575
+ ] }) }),
1576
+ /* @__PURE__ */ jsx(
1577
+ "div",
1578
+ {
1579
+ className: cn("relative col-span-full", `col-start-2`),
1580
+ style: { gridColumn: `2 / -1`, minHeight: "var(--cal-slot-height)" },
1581
+ children: lane.events.map((posEvent) => /* @__PURE__ */ jsx(
1582
+ "div",
1583
+ {
1584
+ className: "tm-event",
1585
+ style: {
1586
+ left: `${posEvent.left}%`,
1587
+ width: `${posEvent.width}%`,
1588
+ top: "2px",
1589
+ height: "calc(100% - 4px)"
1590
+ },
1591
+ children: /* @__PURE__ */ jsx(EventCard, { positionedEvent: posEvent, compact: true })
1592
+ },
1593
+ posEvent.event.id
1594
+ ))
1595
+ }
1596
+ )
1597
+ ]
1598
+ }
1599
+ );
1600
+ }
1601
+ function Timeline({ className }) {
1602
+ const { viewLayout, locale } = useCalendarContext();
1603
+ if (!viewLayout?.lanes) return null;
1604
+ const lanes = viewLayout.lanes;
1605
+ const timeSlots = viewLayout.timeSlots ?? [];
1606
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex-1 overflow-hidden tm-timeline", className), children: [
1607
+ /* @__PURE__ */ jsx("div", { className: "tm-timeline__header border-b border-cal-border bg-cal-header-bg", children: /* @__PURE__ */ jsxs(
1608
+ "div",
1609
+ {
1610
+ className: "grid",
1611
+ style: {
1612
+ gridTemplateColumns: `var(--cal-resource-header-width) repeat(${timeSlots.length}, 1fr)`
1613
+ },
1614
+ children: [
1615
+ /* @__PURE__ */ jsx("div", { className: "p-2 text-xs font-medium text-cal-fg opacity-60", children: "Resources" }),
1616
+ timeSlots.map((slot) => /* @__PURE__ */ jsx(
1617
+ "div",
1618
+ {
1619
+ className: "p-1 text-[10px] text-center border-l border-cal-slot-border opacity-50",
1620
+ children: slot.time.minute === 0 ? formatTime(slot.time, locale) : ""
1621
+ },
1622
+ slot.index
1623
+ ))
1624
+ ]
1625
+ }
1626
+ ) }),
1627
+ /* @__PURE__ */ jsx("div", { className: "overflow-y-auto tm-scrollable", children: lanes.map((lane) => /* @__PURE__ */ jsx(ResourceLane, { lane, slotCount: timeSlots.length }, lane.resource.id)) })
1628
+ ] });
1629
+ }
1630
+ function Agenda({ className }) {
1631
+ const { viewLayout, locale, onEventClick } = useCalendarContext();
1632
+ if (!viewLayout?.groups) return null;
1633
+ const groups = viewLayout.groups;
1634
+ if (groups.length === 0) {
1635
+ return /* @__PURE__ */ jsx("div", { className: cn("flex-1 flex items-center justify-center text-sm opacity-50", className), children: "No events to display" });
1636
+ }
1637
+ return /* @__PURE__ */ jsx("div", { className: cn("flex-1 overflow-y-auto tm-scrollable", className), children: groups.map((group) => {
1638
+ const jsDate = new Date(group.date.toString());
1639
+ const dateLabel = new Intl.DateTimeFormat(locale, {
1640
+ weekday: "long",
1641
+ month: "long",
1642
+ day: "numeric"
1643
+ }).format(jsDate);
1644
+ const isToday = group.date.equals(Temporal.Now.plainDateISO());
1645
+ return /* @__PURE__ */ jsxs("div", { className: "border-b border-cal-border", children: [
1646
+ /* @__PURE__ */ jsxs(
1647
+ "div",
1648
+ {
1649
+ className: cn(
1650
+ "px-4 py-2 text-sm font-semibold bg-cal-header-bg sticky top-0",
1651
+ isToday && "text-cal-today-fg"
1652
+ ),
1653
+ children: [
1654
+ dateLabel,
1655
+ isToday && /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs font-normal opacity-60", children: "(Today)" })
1656
+ ]
1657
+ }
1658
+ ),
1659
+ /* @__PURE__ */ jsx("div", { className: "divide-y divide-cal-border", children: group.events.map((event) => /* @__PURE__ */ jsxs(
1660
+ "div",
1661
+ {
1662
+ className: "px-4 py-3 flex items-start gap-3 hover:bg-cal-slot-bg-odd cursor-pointer transition-colors",
1663
+ onClick: () => onEventClick?.(event),
1664
+ children: [
1665
+ /* @__PURE__ */ jsx(
1666
+ "div",
1667
+ {
1668
+ className: "w-1 h-full min-h-[2rem] rounded-full flex-shrink-0 mt-0.5",
1669
+ style: { backgroundColor: event.color ?? "var(--cal-event-default-bg)" }
1670
+ }
1671
+ ),
1672
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1673
+ /* @__PURE__ */ jsx("div", { className: "font-medium text-sm truncate", children: event.title }),
1674
+ /* @__PURE__ */ jsx("div", { className: "text-xs opacity-60 mt-0.5", children: event.isAllDay ? "All day" : `${formatEventTime(event.start, locale)} \u2013 ${formatEventTime(event.end, locale)}` }),
1675
+ event.description && /* @__PURE__ */ jsx("div", { className: "text-xs opacity-50 mt-1 truncate", children: event.description })
1676
+ ] })
1677
+ ]
1678
+ },
1679
+ event.id
1680
+ )) })
1681
+ ] }, group.date.toString());
1682
+ }) });
1683
+ }
1684
+ function MultiMonth({ className }) {
1685
+ const { viewLayout, currentView, locale } = useCalendarContext();
1686
+ if (!viewLayout) return null;
1687
+ const months = viewLayout.months;
1688
+ if (!months || months.length === 0) return null;
1689
+ const isGrid = currentView === "multiMonthGrid";
1690
+ return /* @__PURE__ */ jsx(
1691
+ "div",
1692
+ {
1693
+ className: cn(
1694
+ "tm-multi-month overflow-auto",
1695
+ isGrid ? "grid grid-cols-2 gap-4 p-4" : "flex flex-col gap-6 p-4",
1696
+ className
1697
+ ),
1698
+ children: months.map((monthLayout, idx) => /* @__PURE__ */ jsx(
1699
+ MonthMini,
1700
+ {
1701
+ month: monthLayout.month,
1702
+ weeks: monthLayout.weeks,
1703
+ locale
1704
+ },
1705
+ monthLayout.month.toString()
1706
+ ))
1707
+ }
1708
+ );
1709
+ }
1710
+ function MonthMini({
1711
+ month,
1712
+ weeks,
1713
+ locale
1714
+ }) {
1715
+ const monthName = month.toLocaleString(locale, { month: "long", year: "numeric" });
1716
+ return /* @__PURE__ */ jsxs("div", { className: "tm-mini-month border border-cal-border rounded-lg overflow-hidden", children: [
1717
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-2 font-semibold text-sm bg-cal-header-bg text-cal-fg border-b border-cal-border", children: monthName }),
1718
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 text-center text-[10px] text-cal-fg-muted py-1 border-b border-cal-border", children: weeks[0]?.map((cell, i) => {
1719
+ const dayName = cell.date.toLocaleString(locale, { weekday: "narrow" });
1720
+ return /* @__PURE__ */ jsx("div", { className: "font-medium", children: dayName }, i);
1721
+ }) ?? null }),
1722
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7", children: weeks.flat().map((cell) => /* @__PURE__ */ jsx(DayCell, { cell }, cell.date.toString())) })
1723
+ ] });
1724
+ }
1725
+ function ResourceTimeGrid({ className }) {
1726
+ const { viewLayout } = useCalendarContext();
1727
+ if (!viewLayout) return null;
1728
+ const slots = viewLayout.slots;
1729
+ const resourceLanes = viewLayout.resourceLanes;
1730
+ if (!slots || !resourceLanes || resourceLanes.length === 0) return null;
1731
+ return /* @__PURE__ */ jsxs("div", { className: cn("tm-resource-time-grid flex flex-1 overflow-auto", className), children: [
1732
+ /* @__PURE__ */ jsxs("div", { className: "tm-time-gutter w-16 shrink-0 border-r border-cal-border", children: [
1733
+ /* @__PURE__ */ jsx("div", { className: "h-10 border-b border-cal-border" }),
1734
+ slots.map((slot, idx) => /* @__PURE__ */ jsx(
1735
+ "div",
1736
+ {
1737
+ className: "h-10 text-[10px] text-cal-fg-muted text-right pr-2 border-b border-cal-slot-border",
1738
+ children: slot.time.minute === 0 ? slot.time.toLocaleString("en-US", { hour: "numeric", minute: "2-digit" }) : ""
1739
+ },
1740
+ idx
1741
+ ))
1742
+ ] }),
1743
+ resourceLanes.map((lane) => /* @__PURE__ */ jsx(ResourceColumn, { lane, slots }, lane.resource.id))
1744
+ ] });
1745
+ }
1746
+ function ResourceColumn({ lane, slots }) {
1747
+ return /* @__PURE__ */ jsxs("div", { className: "tm-resource-column flex-1 min-w-[120px] border-r border-cal-border last:border-r-0", children: [
1748
+ /* @__PURE__ */ jsx("div", { className: "h-10 flex items-center justify-center text-xs font-semibold border-b border-cal-border bg-cal-header-bg truncate px-2", children: lane.resource.title }),
1749
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1750
+ slots.map((slot, idx) => /* @__PURE__ */ jsx(
1751
+ "div",
1752
+ {
1753
+ className: cn(
1754
+ timeSlotVariants({
1755
+ variant: slot.isBusinessHour ? "business" : idx % 2 === 0 ? "default" : "odd"
1756
+ }),
1757
+ "h-10"
1758
+ )
1759
+ },
1760
+ idx
1761
+ )),
1762
+ lane.events.map((positioned) => /* @__PURE__ */ jsx(
1763
+ "div",
1764
+ {
1765
+ className: "absolute px-0.5",
1766
+ style: {
1767
+ top: `${positioned.top}%`,
1768
+ height: `${positioned.height}%`,
1769
+ left: `${positioned.left}%`,
1770
+ width: `${positioned.width}%`
1771
+ },
1772
+ children: /* @__PURE__ */ jsx(EventCard, { positionedEvent: positioned })
1773
+ },
1774
+ positioned.event.id
1775
+ ))
1776
+ ] })
1777
+ ] });
1778
+ }
1779
+ function Calendar({
1780
+ className,
1781
+ children,
1782
+ slots = {},
1783
+ direction,
1784
+ height,
1785
+ contentHeight,
1786
+ aspectRatio,
1787
+ onEventClick,
1788
+ onEventDrop,
1789
+ onDateSelect,
1790
+ onEventResize,
1791
+ onEventMouseEnter,
1792
+ onEventMouseLeave,
1793
+ onDateClick,
1794
+ ...options
1795
+ }) {
1796
+ const calendarState = useCalendar(options);
1797
+ const resolvedDirection = direction ?? (options.locale && isRtl(options.locale) ? "rtl" : "ltr");
1798
+ useInteraction({
1799
+ events: calendarState.events,
1800
+ timezone: options.timezone ?? "UTC",
1801
+ onEventDrop: onEventDrop ? (info) => {
1802
+ onEventDrop({
1803
+ event: info.event,
1804
+ newStart: info.newStart,
1805
+ newEnd: info.newEnd,
1806
+ newResourceId: info.newResourceId
1807
+ });
1808
+ } : void 0,
1809
+ onEventResize: onEventResize ? (info) => {
1810
+ onEventResize({
1811
+ event: info.event,
1812
+ newStart: info.newStart,
1813
+ newEnd: info.newEnd
1814
+ });
1815
+ } : void 0
1816
+ });
1817
+ const contextValue = {
1818
+ ...calendarState,
1819
+ resources: options.resources ?? [],
1820
+ timezone: options.timezone ?? "UTC",
1821
+ locale: options.locale ?? "en-US",
1822
+ firstDayOfWeek: options.firstDayOfWeek ?? 0,
1823
+ editable: options.editable ?? true,
1824
+ selectable: options.selectable ?? false,
1825
+ selectedRange: null,
1826
+ select: (_start, _end) => {
1827
+ },
1828
+ unselect: () => {
1829
+ },
1830
+ slots,
1831
+ onEventClick,
1832
+ onEventDrop,
1833
+ onDateSelect,
1834
+ onEventResize,
1835
+ onEventMouseEnter,
1836
+ onEventMouseLeave,
1837
+ onDateClick
1838
+ };
1839
+ const containerStyle = {
1840
+ ...height ? { height: typeof height === "number" ? `${height}px` : height } : {},
1841
+ ...contentHeight ? { maxHeight: typeof contentHeight === "number" ? `${contentHeight}px` : contentHeight } : {},
1842
+ ...aspectRatio ? { aspectRatio: String(aspectRatio) } : {}
1843
+ };
1844
+ return /* @__PURE__ */ jsx(CalendarContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
1845
+ "div",
1846
+ {
1847
+ dir: resolvedDirection,
1848
+ style: containerStyle,
1849
+ className: cn(
1850
+ "tm-calendar flex flex-col bg-cal-bg text-cal-fg border border-cal-border overflow-hidden",
1851
+ className
1852
+ ),
1853
+ role: "application",
1854
+ "aria-label": "Calendar",
1855
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
1856
+ /* @__PURE__ */ jsx(Calendar.Header, {}),
1857
+ /* @__PURE__ */ jsx(Calendar.View, {})
1858
+ ] })
1859
+ }
1860
+ ) });
1861
+ }
1862
+ function CalendarHeaderSlot({ className }) {
1863
+ return /* @__PURE__ */ jsx(CalendarHeader, { className });
1864
+ }
1865
+ CalendarHeaderSlot.displayName = "Calendar.Header";
1866
+ function CalendarViewSlot({ className }) {
1867
+ return /* @__PURE__ */ jsx(CalendarViewRenderer, { className });
1868
+ }
1869
+ CalendarViewSlot.displayName = "Calendar.View";
1870
+ Calendar.Header = CalendarHeaderSlot;
1871
+ Calendar.View = CalendarViewSlot;
1872
+ Calendar.NavPrev = CalendarNavPrev;
1873
+ Calendar.NavNext = CalendarNavNext;
1874
+ Calendar.NavToday = CalendarNavToday;
1875
+ Calendar.Title = CalendarTitle;
1876
+ Calendar.ViewSwitcher = CalendarViewSwitcher;
1877
+ function CalendarNavPrev({ className }) {
1878
+ const { prev } = React16__default.useContext(CalendarContext);
1879
+ return /* @__PURE__ */ jsx(
1880
+ Button,
1881
+ {
1882
+ variant: "outline",
1883
+ size: "icon",
1884
+ onClick: prev,
1885
+ className,
1886
+ "aria-label": "Previous",
1887
+ children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx(
1888
+ "path",
1889
+ {
1890
+ d: "M10 12L6 8L10 4",
1891
+ stroke: "currentColor",
1892
+ strokeWidth: "2",
1893
+ strokeLinecap: "round",
1894
+ strokeLinejoin: "round"
1895
+ }
1896
+ ) })
1897
+ }
1898
+ );
1899
+ }
1900
+ CalendarNavPrev.displayName = "Calendar.NavPrev";
1901
+ function CalendarNavNext({ className }) {
1902
+ const { next } = React16__default.useContext(CalendarContext);
1903
+ return /* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: next, className, "aria-label": "Next", children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx(
1904
+ "path",
1905
+ {
1906
+ d: "M6 4L10 8L6 12",
1907
+ stroke: "currentColor",
1908
+ strokeWidth: "2",
1909
+ strokeLinecap: "round",
1910
+ strokeLinejoin: "round"
1911
+ }
1912
+ ) }) });
1913
+ }
1914
+ CalendarNavNext.displayName = "Calendar.NavNext";
1915
+ function CalendarNavToday({ className }) {
1916
+ const { today } = React16__default.useContext(CalendarContext);
1917
+ return /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: today, className, children: "Today" });
1918
+ }
1919
+ CalendarNavToday.displayName = "Calendar.NavToday";
1920
+ function CalendarTitle({ className }) {
1921
+ const { title } = React16__default.useContext(CalendarContext);
1922
+ return /* @__PURE__ */ jsx("h2", { className: cn("text-lg font-semibold text-cal-header-fg", className), children: title });
1923
+ }
1924
+ CalendarTitle.displayName = "Calendar.Title";
1925
+ function CalendarViewSwitcher({ views, className }) {
1926
+ const { currentView, changeView } = React16__default.useContext(CalendarContext);
1927
+ const defaultViews = views ?? ["dayGridMonth", "timeGridWeek", "timeGridDay"];
1928
+ const viewLabels = {
1929
+ dayGridMonth: "Month",
1930
+ dayGridWeek: "Week",
1931
+ dayGridDay: "Day",
1932
+ timeGridWeek: "Week",
1933
+ timeGridDay: "Day",
1934
+ timelineDay: "Timeline",
1935
+ timelineWeek: "Timeline",
1936
+ timelineMonth: "Timeline",
1937
+ agendaDay: "Agenda",
1938
+ agendaWeek: "Agenda",
1939
+ agendaMonth: "Agenda"
1940
+ };
1941
+ return /* @__PURE__ */ jsx(
1942
+ ToggleGroup,
1943
+ {
1944
+ value: [currentView],
1945
+ onValueChange: (newValue) => {
1946
+ if (newValue.length > 0) {
1947
+ changeView(newValue[0]);
1948
+ }
1949
+ },
1950
+ variant: "outline",
1951
+ className,
1952
+ children: defaultViews.map((view) => /* @__PURE__ */ jsx(ToggleGroupItem, { value: view, size: "sm", children: viewLabels[view] ?? view }, view))
1953
+ }
1954
+ );
1955
+ }
1956
+ CalendarViewSwitcher.displayName = "Calendar.ViewSwitcher";
1957
+ function CalendarViewRenderer({ className }) {
1958
+ const context = React16__default.useContext(CalendarContext);
1959
+ if (!context) return null;
1960
+ const { currentView, viewLayout } = context;
1961
+ if (!viewLayout)
1962
+ return /* @__PURE__ */ jsx("div", { className: cn("flex-1 flex items-center justify-center text-sm opacity-50", className), children: "No plugin registered for this view" });
1963
+ const viewType = currentView.startsWith("dayGrid") ? "dayGrid" : currentView.startsWith("timeGrid") ? "timeGrid" : currentView.startsWith("timeline") ? "timeline" : currentView.startsWith("multiMonth") ? "multiMonth" : currentView.startsWith("resourceTimeGrid") ? "resourceTimeGrid" : "agenda";
1964
+ switch (viewType) {
1965
+ case "dayGrid":
1966
+ return /* @__PURE__ */ jsx(DayGrid, { className });
1967
+ case "timeGrid":
1968
+ return /* @__PURE__ */ jsx(TimeGrid, { className });
1969
+ case "timeline":
1970
+ return /* @__PURE__ */ jsx(Timeline, { className });
1971
+ case "multiMonth":
1972
+ return /* @__PURE__ */ jsx(MultiMonth, { className });
1973
+ case "resourceTimeGrid":
1974
+ return /* @__PURE__ */ jsx(ResourceTimeGrid, { className });
1975
+ case "agenda":
1976
+ return /* @__PURE__ */ jsx(Agenda, { className });
1977
+ default:
1978
+ return null;
1979
+ }
1980
+ }
1981
+ function CalendarFooter({
1982
+ className,
1983
+ toolbar,
1984
+ buttonText,
1985
+ customButtons
1986
+ }) {
1987
+ if (!toolbar) return null;
1988
+ return /* @__PURE__ */ jsx("div", { className: cn("border-t border-cal-border", className), children: /* @__PURE__ */ jsx(
1989
+ CalendarHeader,
1990
+ {
1991
+ toolbar,
1992
+ buttonText,
1993
+ customButtons,
1994
+ className: "border-b-0"
1995
+ }
1996
+ ) });
1997
+ }
1998
+ function BackgroundEvent({ backgroundEvent, variant = "timeGrid" }) {
1999
+ const { event, top, height } = backgroundEvent;
2000
+ const bgColor = event.backgroundColor ?? event.color ?? "var(--cal-event-default-bg)";
2001
+ if (variant === "dayGrid") {
2002
+ return /* @__PURE__ */ jsx(
2003
+ "div",
2004
+ {
2005
+ className: cn("absolute inset-0 pointer-events-none", event.className),
2006
+ style: {
2007
+ backgroundColor: bgColor,
2008
+ opacity: 0.3
2009
+ },
2010
+ "aria-hidden": "true",
2011
+ title: event.title
2012
+ }
2013
+ );
2014
+ }
2015
+ return /* @__PURE__ */ jsx(
2016
+ "div",
2017
+ {
2018
+ className: cn("absolute left-0 right-0 pointer-events-none rounded-sm", event.className),
2019
+ style: {
2020
+ top: `${top}%`,
2021
+ height: `${Math.max(height, 0.5)}%`,
2022
+ backgroundColor: bgColor,
2023
+ opacity: 0.3
2024
+ },
2025
+ "aria-hidden": "true",
2026
+ title: event.title
2027
+ }
2028
+ );
2029
+ }
2030
+
2031
+ // src/hooks/use-navigation.ts
2032
+ function useNavigation() {
2033
+ const { next, prev, today, goToDate, currentDate, title } = useCalendarContext();
2034
+ return { next, prev, today, goToDate, currentDate, title };
2035
+ }
2036
+
2037
+ // src/hooks/use-events.ts
2038
+ function useEvents() {
2039
+ const { events, addEvent, updateEvent, removeEvent } = useCalendarContext();
2040
+ return { events, addEvent, updateEvent, removeEvent };
2041
+ }
2042
+
2043
+ // src/hooks/use-view.ts
2044
+ function useView() {
2045
+ const { currentView, changeView, viewLayout } = useCalendarContext();
2046
+ return { currentView, changeView, viewLayout };
2047
+ }
2048
+ function useSlots() {
2049
+ const { viewLayout } = useCalendarContext();
2050
+ return {
2051
+ timeSlots: viewLayout?.timeSlots ?? [],
2052
+ columns: viewLayout?.columns ?? []
2053
+ };
2054
+ }
2055
+ function useDropSlot(options) {
2056
+ const { date, time, resourceId, viewConfig, timezone, onEventDrop, onDragEnter, onDragLeave } = options;
2057
+ const dropRef = useRef(null);
2058
+ const [isDragOver, setIsDragOver] = useState(false);
2059
+ const [canDrop, setCanDrop] = useState(false);
2060
+ useEffect(() => {
2061
+ const el = dropRef.current;
2062
+ if (!el) return;
2063
+ const cleanup = dropTargetForElements({
2064
+ element: el,
2065
+ getData: () => ({
2066
+ date: date.toString(),
2067
+ time: time?.toString() ?? null,
2068
+ resourceId: resourceId ?? null
2069
+ }),
2070
+ canDrop: ({ source }) => {
2071
+ return source.data.type === CALENDAR_EVENT_TYPE;
2072
+ },
2073
+ onDragEnter: () => {
2074
+ setIsDragOver(true);
2075
+ setCanDrop(true);
2076
+ onDragEnter?.();
2077
+ },
2078
+ onDragLeave: () => {
2079
+ setIsDragOver(false);
2080
+ setCanDrop(false);
2081
+ onDragLeave?.();
2082
+ },
2083
+ onDrop: ({ source }) => {
2084
+ setIsDragOver(false);
2085
+ setCanDrop(false);
2086
+ const data = source.data;
2087
+ if (data.type !== CALENDAR_EVENT_TYPE) return;
2088
+ const slotTime = time ?? Temporal.PlainTime.from("00:00");
2089
+ const newStart = date.toZonedDateTime({
2090
+ timeZone: timezone,
2091
+ plainTime: slotTime
2092
+ });
2093
+ const originalStart = Temporal.ZonedDateTime.from(data.eventStart);
2094
+ const originalEnd = Temporal.ZonedDateTime.from(data.eventEnd);
2095
+ const duration = originalStart.until(originalEnd);
2096
+ const newEnd = newStart.add(duration);
2097
+ onEventDrop?.({
2098
+ event: {
2099
+ id: data.eventId,
2100
+ title: data.eventTitle
2101
+ },
2102
+ newStart,
2103
+ newEnd,
2104
+ newResourceId: resourceId
2105
+ });
2106
+ }
2107
+ });
2108
+ return cleanup;
2109
+ }, [date, time, resourceId, viewConfig, timezone, onEventDrop, onDragEnter, onDragLeave]);
2110
+ return { dropRef, isDragOver, canDrop };
2111
+ }
2112
+ function useSelection(options = {}) {
2113
+ const {
2114
+ selectable = true,
2115
+ selectMinDistance = 0,
2116
+ selectMirror = false,
2117
+ unselectAuto = true,
2118
+ unselectCancel,
2119
+ timezone = "UTC",
2120
+ onSelect,
2121
+ onUnselect,
2122
+ onDateClick
2123
+ } = options;
2124
+ const [state, setState] = useState({
2125
+ isSelecting: false,
2126
+ selection: null,
2127
+ previewStart: null,
2128
+ previewEnd: null
2129
+ });
2130
+ const previewEndRef = useRef(null);
2131
+ const selectionContext = useRef(null);
2132
+ const startSelection = useCallback(
2133
+ (info) => {
2134
+ if (!selectable) return;
2135
+ const plainTime = info.time ?? Temporal.PlainTime.from("00:00");
2136
+ const startZdt = info.date.toZonedDateTime({
2137
+ timeZone: timezone,
2138
+ plainTime
2139
+ });
2140
+ selectionContext.current = {
2141
+ startDate: info.date,
2142
+ startTime: info.time ?? null,
2143
+ resourceId: info.resourceId,
2144
+ allDay: info.allDay ?? !info.time,
2145
+ startX: 0,
2146
+ startY: 0,
2147
+ hasMovedEnough: selectMinDistance === 0
2148
+ };
2149
+ setState({
2150
+ isSelecting: true,
2151
+ selection: null,
2152
+ previewStart: selectMirror ? startZdt : null,
2153
+ previewEnd: selectMirror ? startZdt : null
2154
+ });
2155
+ },
2156
+ [selectable, timezone, selectMinDistance, selectMirror]
2157
+ );
2158
+ const updateSelection = useCallback(
2159
+ (info) => {
2160
+ if (!selectionContext.current) return;
2161
+ const ctx = selectionContext.current;
2162
+ ctx.hasMovedEnough = true;
2163
+ const endTime = info.time ?? Temporal.PlainTime.from("23:59:59");
2164
+ const endZdt = info.date.toZonedDateTime({
2165
+ timeZone: timezone,
2166
+ plainTime: endTime
2167
+ });
2168
+ const startTime = ctx.startTime ?? Temporal.PlainTime.from("00:00");
2169
+ const startZdt = ctx.startDate.toZonedDateTime({
2170
+ timeZone: timezone,
2171
+ plainTime: startTime
2172
+ });
2173
+ const [realStart, realEnd] = Temporal.ZonedDateTime.compare(startZdt, endZdt) <= 0 ? [startZdt, endZdt] : [endZdt, startZdt];
2174
+ if (selectMirror) {
2175
+ previewEndRef.current = realEnd;
2176
+ }
2177
+ setState((prev) => ({
2178
+ ...prev,
2179
+ previewStart: selectMirror ? realStart : null,
2180
+ previewEnd: selectMirror ? realEnd : null
2181
+ }));
2182
+ },
2183
+ [timezone, selectMirror]
2184
+ );
2185
+ const endSelection = useCallback(() => {
2186
+ const ctx = selectionContext.current;
2187
+ if (!ctx) return;
2188
+ const startTime = ctx.startTime ?? Temporal.PlainTime.from("00:00");
2189
+ const startZdt = ctx.startDate.toZonedDateTime({
2190
+ timeZone: timezone,
2191
+ plainTime: startTime
2192
+ });
2193
+ if (!ctx.hasMovedEnough) {
2194
+ onDateClick?.({
2195
+ date: ctx.startDate,
2196
+ dateStr: ctx.startDate.toString(),
2197
+ allDay: ctx.allDay,
2198
+ resourceId: ctx.resourceId
2199
+ });
2200
+ setState({
2201
+ isSelecting: false,
2202
+ selection: null,
2203
+ previewStart: null,
2204
+ previewEnd: null
2205
+ });
2206
+ selectionContext.current = null;
2207
+ return;
2208
+ }
2209
+ const endZdt = previewEndRef.current ?? startZdt.add({ hours: 1 });
2210
+ const [realStart, realEnd] = Temporal.ZonedDateTime.compare(startZdt, endZdt) <= 0 ? [startZdt, endZdt] : [endZdt, startZdt];
2211
+ const selection = {
2212
+ start: realStart,
2213
+ end: realEnd,
2214
+ allDay: ctx.allDay,
2215
+ resourceId: ctx.resourceId
2216
+ };
2217
+ setState({
2218
+ isSelecting: false,
2219
+ selection,
2220
+ previewStart: null,
2221
+ previewEnd: null
2222
+ });
2223
+ previewEndRef.current = null;
2224
+ onSelect?.(selection);
2225
+ selectionContext.current = null;
2226
+ }, [timezone, onSelect, onDateClick]);
2227
+ const clearSelection = useCallback(() => {
2228
+ setState({
2229
+ isSelecting: false,
2230
+ selection: null,
2231
+ previewStart: null,
2232
+ previewEnd: null
2233
+ });
2234
+ selectionContext.current = null;
2235
+ onUnselect?.();
2236
+ }, [onUnselect]);
2237
+ const select = useCallback(
2238
+ (start, end) => {
2239
+ const selection = {
2240
+ start,
2241
+ end,
2242
+ allDay: false
2243
+ };
2244
+ setState({
2245
+ isSelecting: false,
2246
+ selection,
2247
+ previewStart: null,
2248
+ previewEnd: null
2249
+ });
2250
+ onSelect?.(selection);
2251
+ },
2252
+ [onSelect]
2253
+ );
2254
+ useEffect(() => {
2255
+ if (!unselectAuto || !state.selection) return;
2256
+ const handleClick = (e) => {
2257
+ if (unselectCancel && e.target.closest(unselectCancel)) {
2258
+ return;
2259
+ }
2260
+ clearSelection();
2261
+ };
2262
+ const timeoutId = setTimeout(() => {
2263
+ document.addEventListener("mousedown", handleClick);
2264
+ }, 0);
2265
+ return () => {
2266
+ clearTimeout(timeoutId);
2267
+ document.removeEventListener("mousedown", handleClick);
2268
+ };
2269
+ }, [unselectAuto, unselectCancel, state.selection, clearSelection]);
2270
+ return {
2271
+ state,
2272
+ startSelection,
2273
+ updateSelection,
2274
+ endSelection,
2275
+ clearSelection,
2276
+ select
2277
+ };
2278
+ }
2279
+ function useTemporalNow(intervalMs = 6e4, timezone) {
2280
+ const [now, setNow] = useState(
2281
+ () => timezone ? Temporal.Now.zonedDateTimeISO(timezone) : Temporal.Now.zonedDateTimeISO()
2282
+ );
2283
+ useEffect(() => {
2284
+ const update = () => {
2285
+ setNow(timezone ? Temporal.Now.zonedDateTimeISO(timezone) : Temporal.Now.zonedDateTimeISO());
2286
+ };
2287
+ const id = setInterval(update, intervalMs);
2288
+ return () => clearInterval(id);
2289
+ }, [intervalMs, timezone]);
2290
+ return now;
2291
+ }
2292
+ function useEventSources(options) {
2293
+ const {
2294
+ eventSources: sourceInputs,
2295
+ visibleRange,
2296
+ timezone,
2297
+ eventDefaults,
2298
+ progressiveEventRendering = false,
2299
+ onLoading,
2300
+ onSourceError
2301
+ } = options;
2302
+ const [sources, setSources] = useState(
2303
+ () => createEventSources(sourceInputs)
2304
+ );
2305
+ const [eventStore, setEventStore] = useState(() => EventStore.from([], timezone, eventDefaults));
2306
+ const [isLoading, setIsLoading] = useState(false);
2307
+ const prevRangeRef = useRef("");
2308
+ const abortRef = useRef(null);
2309
+ const sourcesRef = useRef(sources);
2310
+ useEffect(() => {
2311
+ sourcesRef.current = sources;
2312
+ }, [sources]);
2313
+ useEffect(() => {
2314
+ setSources(createEventSources(sourceInputs));
2315
+ }, [sourceInputs]);
2316
+ const fetchAll = useCallback(async () => {
2317
+ abortRef.current?.abort();
2318
+ const abort = new AbortController();
2319
+ abortRef.current = abort;
2320
+ setIsLoading(true);
2321
+ onLoading?.(true);
2322
+ setSources(
2323
+ (prev) => prev.map((s) => ({ ...s, state: "loading", error: null }))
2324
+ );
2325
+ const sourceDefs = sourcesRef.current.map((s) => s.def);
2326
+ if (progressiveEventRendering) {
2327
+ const allEvents = [];
2328
+ for (const source of sourceDefs) {
2329
+ if (abort.signal.aborted) break;
2330
+ try {
2331
+ const events = await fetchEventSource(source, visibleRange, timezone);
2332
+ allEvents.push(...events);
2333
+ setSources(
2334
+ (prev) => prev.map(
2335
+ (s) => s.id === source.id ? { ...s, state: "success", events, lastFetched: Date.now() } : s
2336
+ )
2337
+ );
2338
+ setEventStore(EventStore.from(allEvents, timezone, eventDefaults));
2339
+ } catch (error) {
2340
+ const err = error;
2341
+ setSources(
2342
+ (prev) => prev.map(
2343
+ (s) => s.id === source.id ? { ...s, state: "failure", error: err } : s
2344
+ )
2345
+ );
2346
+ onSourceError?.(source.id, err);
2347
+ }
2348
+ }
2349
+ } else {
2350
+ const { events, errors } = await mergeEventSources(sourceDefs, visibleRange, timezone);
2351
+ if (!abort.signal.aborted) {
2352
+ setEventStore(EventStore.from(events, timezone, eventDefaults));
2353
+ setSources(
2354
+ (prev) => prev.map((s) => {
2355
+ if (errors.has(s.id)) {
2356
+ const err = errors.get(s.id);
2357
+ onSourceError?.(s.id, err);
2358
+ return { ...s, state: "failure", error: err };
2359
+ }
2360
+ return { ...s, state: "success", lastFetched: Date.now() };
2361
+ })
2362
+ );
2363
+ }
2364
+ }
2365
+ if (!abort.signal.aborted) {
2366
+ setIsLoading(false);
2367
+ onLoading?.(false);
2368
+ }
2369
+ }, [visibleRange, timezone, eventDefaults, progressiveEventRendering, onLoading, onSourceError]);
2370
+ useEffect(() => {
2371
+ const rangeKey = `${visibleRange.start.toString()}-${visibleRange.end.toString()}`;
2372
+ if (rangeKey === prevRangeRef.current) return;
2373
+ prevRangeRef.current = rangeKey;
2374
+ fetchAll();
2375
+ }, [visibleRange, fetchAll]);
2376
+ const refetchAll = useCallback(() => {
2377
+ prevRangeRef.current = "";
2378
+ fetchAll();
2379
+ }, [fetchAll]);
2380
+ const refetch = useCallback(
2381
+ async (sourceId) => {
2382
+ const source = sources.find((s) => s.id === sourceId);
2383
+ if (!source) return;
2384
+ setSources(
2385
+ (prev) => prev.map((s) => s.id === sourceId ? { ...s, state: "loading" } : s)
2386
+ );
2387
+ try {
2388
+ const events = await fetchEventSource(source.def, visibleRange, timezone);
2389
+ setSources(
2390
+ (prev) => prev.map(
2391
+ (s) => s.id === sourceId ? { ...s, state: "success", events, lastFetched: Date.now() } : s
2392
+ )
2393
+ );
2394
+ const allEvents = [];
2395
+ sources.forEach((s) => {
2396
+ if (s.id === sourceId) {
2397
+ allEvents.push(...events);
2398
+ } else {
2399
+ allEvents.push(...s.events);
2400
+ }
2401
+ });
2402
+ setEventStore(EventStore.from(allEvents, timezone, eventDefaults));
2403
+ } catch (error) {
2404
+ const err = error;
2405
+ setSources(
2406
+ (prev) => prev.map(
2407
+ (s) => s.id === sourceId ? { ...s, state: "failure", error: err } : s
2408
+ )
2409
+ );
2410
+ onSourceError?.(sourceId, err);
2411
+ }
2412
+ },
2413
+ [sources, visibleRange, timezone, eventDefaults, onSourceError]
2414
+ );
2415
+ return {
2416
+ events: eventStore.getAll(),
2417
+ isLoading,
2418
+ sources,
2419
+ refetchAll,
2420
+ refetch
2421
+ };
2422
+ }
2423
+ function useExternalDrop(options) {
2424
+ const { droppable = true, dropAccept, onDrop, onEventReceive } = options;
2425
+ const dropRef = useRef(null);
2426
+ const [isDraggingOver, setIsDraggingOver] = useState(false);
2427
+ const [isExternalDragActive, setIsExternalDragActive] = useState(false);
2428
+ const isAccepted = useCallback(
2429
+ (type) => {
2430
+ if (!dropAccept) return true;
2431
+ if (typeof dropAccept === "string") return type === dropAccept;
2432
+ if (Array.isArray(dropAccept)) return dropAccept.includes(type);
2433
+ return dropAccept(type);
2434
+ },
2435
+ [dropAccept]
2436
+ );
2437
+ useEffect(() => {
2438
+ if (!droppable) return;
2439
+ return monitorForExternal({
2440
+ onDragStart: () => setIsExternalDragActive(true),
2441
+ onDrop: () => setIsExternalDragActive(false)
2442
+ });
2443
+ }, [droppable]);
2444
+ useEffect(() => {
2445
+ const element = dropRef.current;
2446
+ if (!element || !droppable) return;
2447
+ return dropTargetForExternal({
2448
+ element,
2449
+ onDragEnter: () => setIsDraggingOver(true),
2450
+ onDragLeave: () => setIsDraggingOver(false),
2451
+ onDrop: ({ source, location }) => {
2452
+ setIsDraggingOver(false);
2453
+ const dragData = {
2454
+ type: "external",
2455
+ raw: source
2456
+ };
2457
+ const types = source.types;
2458
+ if (types.includes("text/plain")) {
2459
+ dragData.type = "text";
2460
+ }
2461
+ if (types.includes("Files")) {
2462
+ dragData.type = "files";
2463
+ }
2464
+ if (types.includes("application/json")) {
2465
+ dragData.type = "json";
2466
+ }
2467
+ if (!isAccepted(dragData.type)) return;
2468
+ const innerTarget = location.current.dropTargets[0];
2469
+ const targetDateStr = innerTarget?.data?.date;
2470
+ const targetTimeStr = innerTarget?.data?.time;
2471
+ const dropInfo = {
2472
+ date: targetDateStr ? Temporal.PlainDate.from(targetDateStr) : Temporal.Now.plainDateISO(),
2473
+ // Fallback only if no date on drop target
2474
+ time: targetTimeStr ? Temporal.PlainTime.from(targetTimeStr) : void 0,
2475
+ resourceId: innerTarget?.data?.resourceId ?? void 0,
2476
+ dragData,
2477
+ allDay: !targetTimeStr
2478
+ };
2479
+ onDrop?.(dropInfo);
2480
+ if (dragData.eventData) {
2481
+ onEventReceive?.(dragData.eventData);
2482
+ }
2483
+ }
2484
+ });
2485
+ }, [droppable, isAccepted, onDrop, onEventReceive]);
2486
+ return {
2487
+ dropRef,
2488
+ isDraggingOver,
2489
+ isExternalDragActive
2490
+ };
2491
+ }
2492
+ var CALENDAR_BRIDGE_TYPE = "tempus-machina-bridge";
2493
+ function useCalendarBridge(options) {
2494
+ const { calendarId, onEventLeave, onEventReceive, onEventReturn, enabled = true } = options;
2495
+ const calendarIdRef = useRef(calendarId);
2496
+ useEffect(() => {
2497
+ calendarIdRef.current = calendarId;
2498
+ }, [calendarId]);
2499
+ useEffect(() => {
2500
+ if (!enabled) return;
2501
+ return monitorForElements({
2502
+ onDrop: ({ source, location }) => {
2503
+ const sourceData = source.data;
2504
+ if (sourceData[CALENDAR_BRIDGE_TYPE] !== true) return;
2505
+ const sourceCalId = sourceData.sourceCalendarId;
2506
+ const event = sourceData.event;
2507
+ if (!event || !sourceCalId) return;
2508
+ const dropTargets = location.current.dropTargets;
2509
+ if (dropTargets.length === 0) return;
2510
+ const targetData = dropTargets[0].data;
2511
+ const targetCalId = targetData.calendarId;
2512
+ if (targetCalId && targetCalId !== sourceCalId) {
2513
+ if (sourceCalId === calendarIdRef.current) {
2514
+ onEventLeave?.(event);
2515
+ }
2516
+ if (targetCalId === calendarIdRef.current) {
2517
+ onEventReceive?.(event, sourceCalId);
2518
+ }
2519
+ } else if (targetCalId === sourceCalId && sourceCalId === calendarIdRef.current) {
2520
+ onEventReturn?.(event);
2521
+ }
2522
+ }
2523
+ });
2524
+ }, [enabled, onEventLeave, onEventReceive, onEventReturn]);
2525
+ const getBridgeData = useCallback(
2526
+ (event) => ({
2527
+ [CALENDAR_BRIDGE_TYPE]: true,
2528
+ sourceCalendarId: calendarIdRef.current,
2529
+ event
2530
+ }),
2531
+ []
2532
+ );
2533
+ const isFromOtherCalendar = useCallback((data) => {
2534
+ return data[CALENDAR_BRIDGE_TYPE] === true && data.sourceCalendarId !== calendarIdRef.current;
2535
+ }, []);
2536
+ return {
2537
+ calendarId,
2538
+ getBridgeData,
2539
+ isFromOtherCalendar
2540
+ };
2541
+ }
2542
+ function useKeyboardNav(options) {
2543
+ const {
2544
+ containerRef,
2545
+ enabled = true,
2546
+ onDateNav,
2547
+ onDateSelect,
2548
+ onCancel,
2549
+ onEventFocus,
2550
+ currentView,
2551
+ hints = {}
2552
+ } = options;
2553
+ const focusedIndexRef = useRef(0);
2554
+ const handleKeyDown = useCallback(
2555
+ (e) => {
2556
+ if (!enabled) return;
2557
+ const isMonthView = currentView?.startsWith("dayGrid") || currentView?.startsWith("multiMonth");
2558
+ switch (e.key) {
2559
+ case "ArrowLeft":
2560
+ e.preventDefault();
2561
+ onDateNav?.("left");
2562
+ break;
2563
+ case "ArrowRight":
2564
+ e.preventDefault();
2565
+ onDateNav?.("right");
2566
+ break;
2567
+ case "ArrowUp":
2568
+ e.preventDefault();
2569
+ onDateNav?.(isMonthView ? "up" : "up");
2570
+ break;
2571
+ case "ArrowDown":
2572
+ e.preventDefault();
2573
+ onDateNav?.(isMonthView ? "down" : "down");
2574
+ break;
2575
+ case "Enter":
2576
+ case " ":
2577
+ e.preventDefault();
2578
+ onDateSelect?.();
2579
+ break;
2580
+ case "Escape":
2581
+ e.preventDefault();
2582
+ onCancel?.();
2583
+ break;
2584
+ case "Tab": {
2585
+ const container = containerRef.current;
2586
+ if (!container) break;
2587
+ const focusableEvents = container.querySelectorAll(
2588
+ '[role="button"][tabindex="0"]'
2589
+ );
2590
+ if (focusableEvents.length === 0) break;
2591
+ e.preventDefault();
2592
+ const direction = e.shiftKey ? -1 : 1;
2593
+ focusedIndexRef.current = (focusedIndexRef.current + direction + focusableEvents.length) % focusableEvents.length;
2594
+ const target = focusableEvents[focusedIndexRef.current];
2595
+ target.focus();
2596
+ onEventFocus?.(target.dataset.eventId ?? "");
2597
+ break;
2598
+ }
2599
+ }
2600
+ },
2601
+ [enabled, onDateNav, onDateSelect, onCancel, onEventFocus, containerRef, currentView]
2602
+ );
2603
+ const ariaRoleDescription = currentView?.startsWith("dayGrid") ? "Month calendar" : currentView?.startsWith("timeGrid") ? "Time grid calendar" : currentView?.startsWith("timeline") ? "Timeline calendar" : "Calendar";
2604
+ const containerProps = {
2605
+ role: "grid",
2606
+ tabIndex: 0,
2607
+ "aria-label": "Calendar",
2608
+ "aria-roledescription": ariaRoleDescription,
2609
+ onKeyDown: handleKeyDown
2610
+ };
2611
+ const getCellProps = useCallback(
2612
+ (opts) => ({
2613
+ role: "gridcell",
2614
+ tabIndex: opts.isSelected ? 0 : -1,
2615
+ "aria-selected": opts.isSelected ?? false,
2616
+ "aria-label": hints.navLinkHint ? `${hints.navLinkHint} ${opts.date}` : opts.date
2617
+ }),
2618
+ [hints.navLinkHint]
2619
+ );
2620
+ const getEventProps = useCallback(
2621
+ (opts) => ({
2622
+ role: "button",
2623
+ tabIndex: 0,
2624
+ "aria-label": hints.eventHint ? hints.eventHint(opts.title) : `${opts.title}, ${opts.timeText}`,
2625
+ "data-event-id": opts.id
2626
+ }),
2627
+ [hints]
2628
+ );
2629
+ return {
2630
+ containerProps,
2631
+ getCellProps,
2632
+ getEventProps
2633
+ };
2634
+ }
2635
+ var SWIPE_THRESHOLD = 50;
2636
+ var SWIPE_VELOCITY_THRESHOLD = 0.3;
2637
+ function useTouch(options) {
2638
+ const {
2639
+ elementRef,
2640
+ enabled = true,
2641
+ longPressDelay = 1e3,
2642
+ eventLongPressDelay,
2643
+ selectLongPressDelay,
2644
+ onEventLongPress,
2645
+ onSlotLongPress,
2646
+ onTap,
2647
+ onSwipe
2648
+ } = options;
2649
+ const longPressTimerRef = useRef(null);
2650
+ const [isLongPressing, setIsLongPressing] = useState(false);
2651
+ const touchStartRef = useRef(null);
2652
+ const hasMoved = useRef(false);
2653
+ const clearLongPress = useCallback(() => {
2654
+ if (longPressTimerRef.current) {
2655
+ clearTimeout(longPressTimerRef.current);
2656
+ longPressTimerRef.current = null;
2657
+ }
2658
+ setIsLongPressing(false);
2659
+ }, []);
2660
+ const extractTouchInfo = useCallback((touch, target) => {
2661
+ const dataset = target.dataset;
2662
+ return {
2663
+ clientX: touch.clientX,
2664
+ clientY: touch.clientY,
2665
+ target,
2666
+ date: dataset.date,
2667
+ time: dataset.time,
2668
+ eventId: dataset.eventId,
2669
+ resourceId: dataset.resourceId
2670
+ };
2671
+ }, []);
2672
+ useEffect(() => {
2673
+ const element = elementRef.current;
2674
+ if (!element || !enabled) return;
2675
+ const handleTouchStart = (e) => {
2676
+ if (e.touches.length !== 1) return;
2677
+ const touch = e.touches[0];
2678
+ const target = e.target;
2679
+ touchStartRef.current = { x: touch.clientX, y: touch.clientY, time: Date.now() };
2680
+ hasMoved.current = false;
2681
+ const info = extractTouchInfo(touch, target);
2682
+ const isEvent = !!info.eventId;
2683
+ const delay = isEvent ? eventLongPressDelay ?? longPressDelay : selectLongPressDelay ?? longPressDelay;
2684
+ longPressTimerRef.current = setTimeout(() => {
2685
+ setIsLongPressing(true);
2686
+ if (isEvent) {
2687
+ onEventLongPress?.(info);
2688
+ } else {
2689
+ onSlotLongPress?.(info);
2690
+ }
2691
+ }, delay);
2692
+ };
2693
+ const handleTouchMove = (e) => {
2694
+ if (!touchStartRef.current) return;
2695
+ const touch = e.touches[0];
2696
+ const dx = Math.abs(touch.clientX - touchStartRef.current.x);
2697
+ const dy = Math.abs(touch.clientY - touchStartRef.current.y);
2698
+ if (dx > 10 || dy > 10) {
2699
+ hasMoved.current = true;
2700
+ clearLongPress();
2701
+ }
2702
+ };
2703
+ const handleTouchEnd = (e) => {
2704
+ clearLongPress();
2705
+ if (!touchStartRef.current) return;
2706
+ const touch = e.changedTouches[0];
2707
+ const dx = touch.clientX - touchStartRef.current.x;
2708
+ const dy = touch.clientY - touchStartRef.current.y;
2709
+ const elapsed = Date.now() - touchStartRef.current.time;
2710
+ if (Math.abs(dx) > SWIPE_THRESHOLD || Math.abs(dy) > SWIPE_THRESHOLD) {
2711
+ const velocity = Math.max(Math.abs(dx), Math.abs(dy)) / elapsed;
2712
+ if (velocity > SWIPE_VELOCITY_THRESHOLD) {
2713
+ if (Math.abs(dx) > Math.abs(dy)) {
2714
+ onSwipe?.(dx > 0 ? "right" : "left");
2715
+ } else {
2716
+ onSwipe?.(dy > 0 ? "down" : "up");
2717
+ }
2718
+ }
2719
+ } else if (!hasMoved.current && elapsed < 300) {
2720
+ const target = e.target;
2721
+ const info = extractTouchInfo(touch, target);
2722
+ onTap?.(info);
2723
+ }
2724
+ touchStartRef.current = null;
2725
+ };
2726
+ const handleTouchCancel = () => {
2727
+ clearLongPress();
2728
+ touchStartRef.current = null;
2729
+ };
2730
+ element.addEventListener("touchstart", handleTouchStart, { passive: true });
2731
+ element.addEventListener("touchmove", handleTouchMove, { passive: true });
2732
+ element.addEventListener("touchend", handleTouchEnd, { passive: true });
2733
+ element.addEventListener("touchcancel", handleTouchCancel);
2734
+ return () => {
2735
+ clearLongPress();
2736
+ element.removeEventListener("touchstart", handleTouchStart);
2737
+ element.removeEventListener("touchmove", handleTouchMove);
2738
+ element.removeEventListener("touchend", handleTouchEnd);
2739
+ element.removeEventListener("touchcancel", handleTouchCancel);
2740
+ };
2741
+ }, [
2742
+ elementRef,
2743
+ enabled,
2744
+ longPressDelay,
2745
+ eventLongPressDelay,
2746
+ selectLongPressDelay,
2747
+ onEventLongPress,
2748
+ onSlotLongPress,
2749
+ onTap,
2750
+ onSwipe,
2751
+ clearLongPress,
2752
+ extractTouchInfo
2753
+ ]);
2754
+ return {
2755
+ isLongPressing
2756
+ };
2757
+ }
2758
+ function useResources(options = {}) {
2759
+ const {
2760
+ resources: resourcesInput,
2761
+ resourceOrder,
2762
+ resourceAreaWidth = 200,
2763
+ refetchResourcesOnNavigate = false,
2764
+ onResourceAdd,
2765
+ onResourceChange,
2766
+ onResourceRemove,
2767
+ onResourcesSet
2768
+ } = options;
2769
+ const [resources, setResourcesState] = useState([]);
2770
+ const [isLoading, setIsLoading] = useState(false);
2771
+ const fetchIdRef = useRef(0);
2772
+ const sortResources = useCallback(
2773
+ (items) => {
2774
+ if (!resourceOrder) return items;
2775
+ const sorted = [...items];
2776
+ if (typeof resourceOrder === "string") {
2777
+ sorted.sort((a, b) => {
2778
+ const aVal = a[resourceOrder] ?? "";
2779
+ const bVal = b[resourceOrder] ?? "";
2780
+ return String(aVal).localeCompare(String(bVal));
2781
+ });
2782
+ } else {
2783
+ sorted.sort(resourceOrder);
2784
+ }
2785
+ return sorted;
2786
+ },
2787
+ [resourceOrder]
2788
+ );
2789
+ const resolveResources = useCallback(async () => {
2790
+ if (!resourcesInput) return;
2791
+ const fetchId = ++fetchIdRef.current;
2792
+ if (Array.isArray(resourcesInput)) {
2793
+ setResourcesState(sortResources(resourcesInput));
2794
+ return;
2795
+ }
2796
+ setIsLoading(true);
2797
+ try {
2798
+ let resolved;
2799
+ if (typeof resourcesInput === "string") {
2800
+ const response = await fetch(resourcesInput);
2801
+ resolved = await response.json();
2802
+ } else {
2803
+ resolved = await resourcesInput({
2804
+ start: (/* @__PURE__ */ new Date()).toISOString(),
2805
+ end: (/* @__PURE__ */ new Date()).toISOString()
2806
+ });
2807
+ }
2808
+ if (fetchId === fetchIdRef.current) {
2809
+ setResourcesState(sortResources(resolved));
2810
+ }
2811
+ } catch (error) {
2812
+ console.error("[TempusMachina] Resource fetch error:", error);
2813
+ } finally {
2814
+ if (fetchId === fetchIdRef.current) {
2815
+ setIsLoading(false);
2816
+ }
2817
+ }
2818
+ }, [resourcesInput, sortResources]);
2819
+ useEffect(() => {
2820
+ resolveResources();
2821
+ }, [resolveResources]);
2822
+ const addResource = useCallback(
2823
+ (resource) => {
2824
+ const newResource = {
2825
+ id: resource.id ?? `resource-${Date.now()}`,
2826
+ ...resource
2827
+ };
2828
+ setResourcesState((prev) => sortResources([...prev, newResource]));
2829
+ onResourceAdd?.(newResource);
2830
+ },
2831
+ [sortResources, onResourceAdd]
2832
+ );
2833
+ const removeResource = useCallback(
2834
+ (resourceId) => {
2835
+ setResourcesState((prev) => {
2836
+ const removed = prev.find((r) => r.id === resourceId);
2837
+ const next = prev.filter((r) => r.id !== resourceId);
2838
+ if (removed) onResourceRemove?.(removed);
2839
+ return next;
2840
+ });
2841
+ },
2842
+ [onResourceRemove]
2843
+ );
2844
+ const updateResource = useCallback(
2845
+ (resourceId, updates) => {
2846
+ setResourcesState((prev) => {
2847
+ const next = prev.map((r) => {
2848
+ if (r.id !== resourceId) return r;
2849
+ const updated = { ...r, ...updates };
2850
+ onResourceChange?.(updated);
2851
+ return updated;
2852
+ });
2853
+ return sortResources(next);
2854
+ });
2855
+ },
2856
+ [sortResources, onResourceChange]
2857
+ );
2858
+ const setResources = useCallback(
2859
+ (newResources) => {
2860
+ const sorted = sortResources(newResources);
2861
+ setResourcesState(sorted);
2862
+ onResourcesSet?.(sorted);
2863
+ },
2864
+ [sortResources, onResourcesSet]
2865
+ );
2866
+ const refetchResources = useCallback(() => {
2867
+ resolveResources();
2868
+ }, [resolveResources]);
2869
+ return {
2870
+ resources,
2871
+ isLoading,
2872
+ addResource,
2873
+ removeResource,
2874
+ updateResource,
2875
+ setResources,
2876
+ refetchResources,
2877
+ resourceAreaWidth
2878
+ };
2879
+ }
2880
+ var ThemeContext = createContext(null);
2881
+ function useTheme() {
2882
+ const context = useContext(ThemeContext);
2883
+ if (!context) {
2884
+ throw new Error("[tempus-machina] useTheme must be used within a CalendarThemeProvider.");
2885
+ }
2886
+ return context;
2887
+ }
2888
+ function CalendarThemeProvider({
2889
+ children,
2890
+ defaultTheme = "light"
2891
+ }) {
2892
+ const [theme, setTheme] = useState(defaultTheme);
2893
+ const toggleTheme = useCallback(() => {
2894
+ setTheme((prev) => prev === "light" ? "dark" : "light");
2895
+ }, []);
2896
+ const value = useMemo(() => ({ theme, setTheme, toggleTheme }), [theme, toggleTheme]);
2897
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { className: theme === "dark" ? "dark" : "", children }) });
2898
+ }
2899
+
2900
+ export { Agenda, AllDayRow, BackgroundEvent, Button, CALENDAR_BRIDGE_TYPE, CALENDAR_EVENT_TYPE, Calendar, CalendarContext, CalendarFooter, CalendarHeader, CalendarThemeProvider, DayCell, DayGrid, EventCard, MultiMonth, NowIndicator, ResourceLane, ResourceTimeGrid, TimeGrid, TimeSlot, Timeline, Toggle, ToggleGroup, ToggleGroupItem, buttonVariants, cn, dayCellVariants, eventCardVariants, timeSlotVariants, toggleVariants, useCalendar, useCalendarBridge, useCalendarContext, useDragEvent, useDropSlot, useEventSources, useEvents, useExternalDrop, useInteraction, useKeyboardNav, useNavigation, useResources, useSelection, useSlots, useTemporalNow, useTheme, useTouch, useView };
2901
+ //# sourceMappingURL=index.js.map
2902
+ //# sourceMappingURL=index.js.map