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