@marco.colia/react-fast-scheduler 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,1410 +1 @@
1
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { format, startOfMonth, startOfWeek, endOfWeek, endOfMonth, addDays, addMonths, isSameDay, isToday, isSameMonth } from 'date-fns';
3
- import * as React2 from 'react';
4
-
5
- // src/components/defaultRenderAppointment/index.tsx
6
- function defaultRenderAppointment({
7
- appointment,
8
- isDropInvalid,
9
- onPointerDown,
10
- onResizePointerDown,
11
- appointmentAppearance,
12
- appointmentBackgroundColor
13
- }) {
14
- const rawMaybe = appointment.raw;
15
- const description = typeof rawMaybe.description === "string" ? rawMaybe.description : void 0;
16
- const legacyClassName = typeof appointmentBackgroundColor === "string" ? appointmentBackgroundColor : void 0;
17
- const backgroundClassName = appointmentAppearance?.className ?? legacyClassName ?? "bg-muted/40 dark:bg-muted/30";
18
- const invalidStateClassName = isDropInvalid ? "border-red-500 bg-red-100/80 text-red-950 ring-1 ring-red-500/60 dark:bg-red-950/40 dark:text-red-100" : "";
19
- const visualStateClassName = appointment.visualState === "ghost" ? "cursor-grab border-dashed opacity-35" : appointment.visualState === "dragging" ? "cursor-grabbing opacity-55 shadow-xl ring-1 ring-border/70" : "cursor-grab";
20
- const className = `relative h-full overflow-hidden rounded-md border border-border p-2 pb-5 text-foreground ${backgroundClassName} ${invalidStateClassName} ${visualStateClassName}`;
21
- return /* @__PURE__ */ jsxs(
22
- "div",
23
- {
24
- onPointerDown,
25
- className,
26
- children: [
27
- isDropInvalid ? /* @__PURE__ */ jsx("div", { className: "absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-red-600 text-[10px] font-bold text-white", children: "X" }) : null,
28
- /* @__PURE__ */ jsx("div", { className: "text-xs font-semibold", children: appointment.title }),
29
- description ? /* @__PURE__ */ jsx("div", { className: "mt-1 text-[10px] font-medium text-muted-foreground", children: description }) : null,
30
- /* @__PURE__ */ jsx(
31
- "div",
32
- {
33
- role: "button",
34
- "aria-label": "Resize appointment",
35
- onPointerDown: (event) => {
36
- if (appointment.visualState === "ghost") {
37
- return;
38
- }
39
- event.stopPropagation();
40
- onResizePointerDown(event);
41
- },
42
- className: "absolute inset-x-1 bottom-1 h-2 cursor-ns-resize rounded-full bg-border/90 transition-colors hover:bg-border"
43
- }
44
- )
45
- ]
46
- }
47
- );
48
- }
49
-
50
- // src/components/constants.ts
51
- var STEP_MIN = 15;
52
- var MIN_EVENT_MIN = 15;
53
- var PX_PER_MIN = 2;
54
- var GUTTER_W = 72;
55
- var TOP_PAD = 12;
56
- var BOTTOM_PAD = 24;
57
- var RESOURCE_MIN_W = 420;
58
- function isNameLike(resource) {
59
- if (!resource || typeof resource !== "object") {
60
- return false;
61
- }
62
- const maybe = resource;
63
- return typeof maybe.firstName === "string" || typeof maybe.lastName === "string";
64
- }
65
- var DEFAULT_APPOINTMENT_COLOR_TOKEN_CLASS_MAP = {
66
- slate: "bg-slate-100 dark:bg-slate-900/40",
67
- gray: "bg-gray-100 dark:bg-gray-900/40",
68
- zinc: "bg-zinc-100 dark:bg-zinc-900/40",
69
- neutral: "bg-neutral-100 dark:bg-neutral-900/40",
70
- stone: "bg-stone-100 dark:bg-stone-900/40",
71
- red: "bg-red-100 dark:bg-red-950/40",
72
- orange: "bg-orange-100 dark:bg-orange-950/40",
73
- amber: "bg-amber-100 dark:bg-amber-950/40",
74
- yellow: "bg-yellow-100 dark:bg-yellow-950/40",
75
- lime: "bg-lime-100 dark:bg-lime-950/40",
76
- green: "bg-green-100 dark:bg-green-950/40",
77
- emerald: "bg-emerald-100 dark:bg-emerald-950/40",
78
- teal: "bg-teal-100 dark:bg-teal-950/40",
79
- cyan: "bg-cyan-100 dark:bg-cyan-950/40",
80
- sky: "bg-sky-100 dark:bg-sky-950/40",
81
- blue: "bg-blue-100 dark:bg-blue-950/40",
82
- indigo: "bg-indigo-100 dark:bg-indigo-950/40",
83
- violet: "bg-violet-100 dark:bg-violet-950/40",
84
- purple: "bg-purple-100 dark:bg-purple-950/40",
85
- fuchsia: "bg-fuchsia-100 dark:bg-fuchsia-950/40",
86
- pink: "bg-pink-100 dark:bg-pink-950/40",
87
- rose: "bg-rose-100 dark:bg-rose-950/40"
88
- };
89
- function clamp(n, min, max) {
90
- return Math.max(min, Math.min(max, n));
91
- }
92
- function snap(mins, stepMin = STEP_MIN) {
93
- return Math.round(mins / stepMin) * stepMin;
94
- }
95
- function parseHHMM(hhmm) {
96
- const [h, m] = hhmm.split(":").map(Number);
97
- return h * 60 + m;
98
- }
99
- function fullName(resource) {
100
- return `${resource.firstName ?? ""} ${resource.lastName ?? ""}`.trim();
101
- }
102
- function dayISO(date) {
103
- return format(date, "yyyy-MM-dd");
104
- }
105
- function shiftDays(date, delta) {
106
- return new Date(date.getFullYear(), date.getMonth(), date.getDate() + delta);
107
- }
108
- function dateAtMinute(selectedDate, dayStartAbs, minuteFromStart) {
109
- const base = new Date(
110
- selectedDate.getFullYear(),
111
- selectedDate.getMonth(),
112
- selectedDate.getDate(),
113
- 0,
114
- 0,
115
- 0,
116
- 0
117
- );
118
- const abs = dayStartAbs + minuteFromStart;
119
- const hh = Math.floor(abs / 60);
120
- const mm = abs % 60;
121
- base.setHours(hh, mm, 0, 0);
122
- return base;
123
- }
124
- function minutesFromDayStart(dayStartAbs, date) {
125
- return date.getHours() * 60 + date.getMinutes() - dayStartAbs;
126
- }
127
- function overlaps(a, b) {
128
- return a.startMin < b.endMin && b.startMin < a.endMin;
129
- }
130
- function layoutOverlaps(blocks) {
131
- const sorted = [...blocks].sort((a, b) => a.startMin - b.startMin || a.endMin - b.endMin);
132
- const groups = [];
133
- let active = [];
134
- let current = [];
135
- for (const block of sorted) {
136
- active = active.filter((item) => item.endMin > block.startMin);
137
- const conflicts = active.some((item) => overlaps(item, block));
138
- if (!conflicts && current.length > 0) {
139
- groups.push(current);
140
- current = [];
141
- }
142
- current.push(block);
143
- active.push(block);
144
- }
145
- if (current.length) {
146
- groups.push(current);
147
- }
148
- const output = [];
149
- for (const group of groups) {
150
- const sortedGroup = [...group].sort((a, b) => a.startMin - b.startMin || a.endMin - b.endMin);
151
- const laneEnd = [];
152
- const laneOf = /* @__PURE__ */ new Map();
153
- for (const block of sortedGroup) {
154
- let placed = false;
155
- for (let i = 0; i < laneEnd.length; i++) {
156
- if (laneEnd[i] <= block.startMin) {
157
- laneEnd[i] = block.endMin;
158
- laneOf.set(block.id, i);
159
- placed = true;
160
- break;
161
- }
162
- }
163
- if (!placed) {
164
- laneEnd.push(block.endMin);
165
- laneOf.set(block.id, laneEnd.length - 1);
166
- }
167
- }
168
- const lanes = laneEnd.length;
169
- for (const block of sortedGroup) {
170
- output.push({ ...block, lane: laneOf.get(block.id) ?? 0, lanes });
171
- }
172
- }
173
- return output;
174
- }
175
-
176
- // src/utils/scheduler-data.utils.ts
177
- function buildResourceMap({
178
- fallback,
179
- getValue,
180
- resources,
181
- toKey
182
- }) {
183
- const map = /* @__PURE__ */ new Map();
184
- for (const resource of resources) {
185
- map.set(toKey(resource), getValue(resource) ?? fallback);
186
- }
187
- return map;
188
- }
189
- function normalizeAppointments({
190
- adapter,
191
- appointments,
192
- dayMinutes,
193
- dayStartAbs,
194
- selectedISO
195
- }) {
196
- return appointments.map((appointment) => {
197
- const start = new Date(adapter.getStart(appointment));
198
- const end = new Date(adapter.getEnd(appointment));
199
- return {
200
- raw: appointment,
201
- id: adapter.getId(appointment),
202
- resourceId: adapter.getResourceId(appointment),
203
- start,
204
- end,
205
- startMin: clamp(minutesFromDayStart(dayStartAbs, start), 0, dayMinutes),
206
- endMin: clamp(minutesFromDayStart(dayStartAbs, end), 0, dayMinutes),
207
- title: adapter.getTitle(appointment),
208
- renderKey: String(adapter.getId(appointment)),
209
- visualState: "normal"
210
- };
211
- }).filter((appointment) => dayISO(appointment.start) === selectedISO);
212
- }
213
- function buildAppointmentMap(appointments) {
214
- return new Map(appointments.map((appointment) => [appointment.id, appointment]));
215
- }
216
- function buildPresentationAppointments(appointments, drag, selectedDate, dayStartAbs) {
217
- if (drag.kind === "none") {
218
- return appointments;
219
- }
220
- if (drag.kind === "move") {
221
- return appointments;
222
- }
223
- return appointments.map((appointment) => {
224
- if (appointment.id !== drag.appointmentId) {
225
- return appointment;
226
- }
227
- return {
228
- ...appointment,
229
- resourceId: drag.resourceId,
230
- startMin: drag.startMin,
231
- endMin: drag.endMin,
232
- start: dateAtMinute(selectedDate, dayStartAbs, drag.startMin),
233
- end: dateAtMinute(selectedDate, dayStartAbs, drag.endMin),
234
- visualState: "dragging"
235
- };
236
- });
237
- }
238
- function buildLaidOutByResource(resources, appointments) {
239
- const appointmentsByResource = /* @__PURE__ */ new Map();
240
- for (const appointment of appointments) {
241
- const current = appointmentsByResource.get(appointment.resourceId) ?? [];
242
- current.push(appointment);
243
- appointmentsByResource.set(appointment.resourceId, current);
244
- }
245
- const result = /* @__PURE__ */ new Map();
246
- for (const resource of resources) {
247
- result.set(resource.id, buildLaidOutAppointments(appointmentsByResource.get(resource.id) ?? []));
248
- }
249
- return result;
250
- }
251
- function buildLaidOutAppointments(appointments) {
252
- return layoutOverlaps(
253
- appointments.map((appointment) => ({
254
- ...appointment,
255
- appointmentId: appointment.id,
256
- id: appointment.renderKey
257
- }))
258
- ).map(({ appointmentId, ...appointment }) => ({
259
- ...appointment,
260
- id: appointmentId
261
- }));
262
- }
263
- function buildAppointmentAppearanceByResource(resources, resourceAppointmentClassMap, getResourceAppointmentAppearance, appointmentColorTokenClassByResource, appointmentBgByResource) {
264
- const map = /* @__PURE__ */ new Map();
265
- for (const resource of resources) {
266
- const classFromIdMap = resourceAppointmentClassMap?.[String(resource.id)];
267
- if (classFromIdMap) {
268
- map.set(resource.id, { className: classFromIdMap });
269
- continue;
270
- }
271
- const appearance = getResourceAppointmentAppearance?.(resource);
272
- if (appearance) {
273
- map.set(resource.id, appearance);
274
- continue;
275
- }
276
- const tokenClass = appointmentColorTokenClassByResource.get(resource.id);
277
- if (tokenClass) {
278
- map.set(resource.id, { className: tokenClass });
279
- continue;
280
- }
281
- const legacyBackground = appointmentBgByResource.get(resource.id);
282
- map.set(resource.id, legacyBackground ? { className: legacyBackground } : void 0);
283
- }
284
- return map;
285
- }
286
- function useSchedulerBaseData({
287
- adapter,
288
- appointments,
289
- dayEnd = "18:00",
290
- dayStart = "09:00",
291
- resources,
292
- selectedDate
293
- }) {
294
- const dayStartAbs = React2.useMemo(() => parseHHMM(dayStart), [dayStart]);
295
- const dayEndAbs = React2.useMemo(() => parseHHMM(dayEnd), [dayEnd]);
296
- const dayMinutes = React2.useMemo(
297
- () => Math.max(0, dayEndAbs - dayStartAbs),
298
- [dayEndAbs, dayStartAbs]
299
- );
300
- const gridHeight = React2.useMemo(
301
- () => TOP_PAD + dayMinutes * PX_PER_MIN + BOTTOM_PAD,
302
- [dayMinutes]
303
- );
304
- const gridMinWidth = React2.useMemo(
305
- () => GUTTER_W + Math.max(resources.length, 1) * RESOURCE_MIN_W,
306
- [resources.length]
307
- );
308
- const selectedISO = React2.useMemo(() => dayISO(selectedDate), [selectedDate]);
309
- const renderAppts = React2.useMemo(() => {
310
- return normalizeAppointments({
311
- adapter,
312
- appointments,
313
- dayMinutes,
314
- dayStartAbs,
315
- selectedISO
316
- });
317
- }, [adapter, appointments, dayMinutes, dayStartAbs, selectedISO]);
318
- const appointmentMap = React2.useMemo(() => buildAppointmentMap(renderAppts), [renderAppts]);
319
- return {
320
- appointmentMap,
321
- dayEndAbs,
322
- dayMinutes,
323
- dayStartAbs,
324
- gridHeight,
325
- gridMinWidth,
326
- renderAppts
327
- };
328
- }
329
-
330
- // src/hooks/useSchedulerInteractions.helpers.ts
331
- var DRAG_START_THRESHOLD_PX = 4;
332
- var updateMovePreviewPosition = (setMovePreview, pointer) => {
333
- setMovePreview(
334
- (current) => current ? {
335
- ...current,
336
- clientX: pointer.clientX,
337
- clientY: pointer.clientY
338
- } : current
339
- );
340
- };
341
- var startPendingDragIfThresholdPassed = (args) => {
342
- const { dragRef, pendingDragRef, pointer, setDrag, setMovePreview, suppressClickRef } = args;
343
- const pending = pendingDragRef.current;
344
- if (!pending || pending.drag.pointerId !== pointer.pointerId) {
345
- return;
346
- }
347
- const deltaX = pointer.clientX - pending.startClientX;
348
- const deltaY = pointer.clientY - pending.startClientY;
349
- if (Math.hypot(deltaX, deltaY) < DRAG_START_THRESHOLD_PX) {
350
- return;
351
- }
352
- suppressClickRef.current = true;
353
- dragRef.current = pending.drag;
354
- setDrag(pending.drag);
355
- if (pending.drag.kind === "move") {
356
- setMovePreview({
357
- appointment: pending.appointment,
358
- clientX: pointer.clientX,
359
- clientY: pointer.clientY,
360
- width: pending.width,
361
- height: pending.height,
362
- offsetX: pending.offsetX,
363
- offsetY: pending.offsetY
364
- });
365
- }
366
- pendingDragRef.current = null;
367
- };
368
- var buildNextMoveDrag = (drag, pointer, dayMinutes, pointerToResourceId, pointerToMin) => {
369
- const nextResourceId = pointerToResourceId(pointer.clientX, drag.resourceId);
370
- const pointerMin = pointerToMin(nextResourceId, pointer.clientY);
371
- const rawStart = pointerMin - drag.offsetMin;
372
- const nextStartMin = clamp(snap(rawStart), 0, dayMinutes - drag.durationMin);
373
- const nextEndMin = nextStartMin + drag.durationMin;
374
- if (nextStartMin === drag.startMin && nextEndMin === drag.endMin && nextResourceId === drag.resourceId) {
375
- return null;
376
- }
377
- return {
378
- ...drag,
379
- resourceId: nextResourceId,
380
- startMin: nextStartMin,
381
- endMin: nextEndMin
382
- };
383
- };
384
- var buildNextResizeDrag = (drag, pointer, dayMinutes, pointerToMin) => {
385
- const pointerMin = pointerToMin(drag.resourceId, pointer.clientY);
386
- const nextEndMin = clamp(snap(pointerMin), drag.startMin + MIN_EVENT_MIN, dayMinutes);
387
- if (nextEndMin === drag.endMin) {
388
- return null;
389
- }
390
- return {
391
- ...drag,
392
- endMin: nextEndMin
393
- };
394
- };
395
- var processPointerFrame = (args) => {
396
- const {
397
- dayMinutes,
398
- dragRef,
399
- latestPointerRef,
400
- pendingDragRef,
401
- pointerToMin,
402
- pointerToResourceId,
403
- setDrag,
404
- setMovePreview,
405
- suppressClickRef
406
- } = args;
407
- const pointer = latestPointerRef.current;
408
- if (!pointer) {
409
- return;
410
- }
411
- const currentDrag = dragRef.current;
412
- if (currentDrag.kind === "none" && pendingDragRef.current) {
413
- startPendingDragIfThresholdPassed({
414
- pointer,
415
- pendingDragRef,
416
- setDrag,
417
- setMovePreview,
418
- dragRef,
419
- suppressClickRef
420
- });
421
- return;
422
- }
423
- if (currentDrag.kind === "none") {
424
- return;
425
- }
426
- if (currentDrag.kind === "move") {
427
- updateMovePreviewPosition(setMovePreview, pointer);
428
- const nextDrag2 = buildNextMoveDrag(
429
- currentDrag,
430
- pointer,
431
- dayMinutes,
432
- pointerToResourceId,
433
- pointerToMin
434
- );
435
- if (nextDrag2) {
436
- suppressClickRef.current = true;
437
- dragRef.current = nextDrag2;
438
- setDrag(nextDrag2);
439
- }
440
- return;
441
- }
442
- const nextDrag = buildNextResizeDrag(currentDrag, pointer, dayMinutes, pointerToMin);
443
- if (nextDrag) {
444
- suppressClickRef.current = true;
445
- dragRef.current = nextDrag;
446
- setDrag(nextDrag);
447
- }
448
- };
449
- var finishDrag = (args) => {
450
- const {
451
- dragRef,
452
- frameRef,
453
- latestPointerRef,
454
- pendingDragRef,
455
- persistDrag,
456
- setDrag,
457
- setMovePreview,
458
- suppressClickRef
459
- } = args;
460
- if (frameRef.current !== null) {
461
- window.cancelAnimationFrame(frameRef.current);
462
- frameRef.current = null;
463
- }
464
- latestPointerRef.current = null;
465
- pendingDragRef.current = null;
466
- setMovePreview(null);
467
- const current = dragRef.current;
468
- if (current.kind === "none") {
469
- return;
470
- }
471
- setDrag({ kind: "none" });
472
- dragRef.current = { kind: "none" };
473
- if (suppressClickRef.current) {
474
- void persistDrag(current);
475
- }
476
- };
477
-
478
- // src/hooks/useSchedulerInteractions.ts
479
- var useSchedulerInteractions = ({
480
- appointmentMap,
481
- dayMinutes,
482
- dayStartAbs,
483
- onAppointmentChange,
484
- onPersistMoveResize,
485
- renderAppts,
486
- resources,
487
- selectedDate
488
- }) => {
489
- const [drag, setDrag] = React2.useState({ kind: "none" });
490
- const [movePreview, setMovePreview] = React2.useState(
491
- null
492
- );
493
- const colRefs = React2.useRef({});
494
- const suppressClickRef = React2.useRef(false);
495
- const dragRef = React2.useRef({ kind: "none" });
496
- const pendingDragRef = React2.useRef(null);
497
- const latestPointerRef = React2.useRef(null);
498
- const frameRef = React2.useRef(null);
499
- React2.useEffect(() => {
500
- dragRef.current = drag;
501
- }, [drag]);
502
- React2.useEffect(() => {
503
- return () => {
504
- if (frameRef.current !== null) {
505
- window.cancelAnimationFrame(frameRef.current);
506
- }
507
- };
508
- }, []);
509
- const pointerToMin = React2.useCallback(
510
- (resourceId, clientY) => {
511
- const col = colRefs.current[String(resourceId)];
512
- if (!col) {
513
- return 0;
514
- }
515
- const y = clientY - col.getBoundingClientRect().top - TOP_PAD;
516
- return clamp(snap(y / PX_PER_MIN), 0, dayMinutes);
517
- },
518
- [dayMinutes]
519
- );
520
- const pointerToResourceId = React2.useCallback(
521
- (clientX, fallbackResourceId) => {
522
- for (const resource of resources) {
523
- const col = colRefs.current[String(resource.id)];
524
- if (!col) {
525
- continue;
526
- }
527
- const rect = col.getBoundingClientRect();
528
- if (clientX >= rect.left && clientX <= rect.right) {
529
- return resource.id;
530
- }
531
- }
532
- return fallbackResourceId;
533
- },
534
- [resources]
535
- );
536
- const hasOverlap = React2.useCallback(
537
- (appointmentId, resourceId, startMin, endMin) => {
538
- const events = renderAppts.filter(
539
- (appointment) => appointment.resourceId === resourceId && appointment.id !== appointmentId
540
- );
541
- return events.some(
542
- (appointment) => overlaps(appointment, { startMin, endMin })
543
- );
544
- },
545
- [renderAppts]
546
- );
547
- const isDropInvalid = React2.useMemo(() => {
548
- if (drag.kind === "none") {
549
- return false;
550
- }
551
- return hasOverlap(drag.appointmentId, drag.resourceId, drag.startMin, drag.endMin);
552
- }, [drag, hasOverlap]);
553
- const persistDrag = React2.useCallback(
554
- async (state) => {
555
- const target = appointmentMap.get(state.appointmentId);
556
- if (!target) {
557
- return;
558
- }
559
- if (hasOverlap(state.appointmentId, state.resourceId, state.startMin, state.endMin)) {
560
- return;
561
- }
562
- const next = {
563
- resourceId: state.resourceId,
564
- start: dateAtMinute(selectedDate, dayStartAbs, state.startMin),
565
- end: dateAtMinute(selectedDate, dayStartAbs, state.endMin)
566
- };
567
- if (onAppointmentChange) {
568
- await onAppointmentChange({
569
- kind: state.kind,
570
- appointment: target.raw,
571
- previous: {
572
- resourceId: target.resourceId,
573
- start: target.start,
574
- end: target.end
575
- },
576
- next
577
- });
578
- return;
579
- }
580
- if (onPersistMoveResize) {
581
- await onPersistMoveResize({
582
- appointment: target.raw,
583
- resourceId: next.resourceId,
584
- start: next.start,
585
- end: next.end
586
- });
587
- }
588
- },
589
- [
590
- appointmentMap,
591
- dayStartAbs,
592
- hasOverlap,
593
- onAppointmentChange,
594
- onPersistMoveResize,
595
- selectedDate
596
- ]
597
- );
598
- const onApptPointerDown = React2.useCallback(
599
- (event, appointment) => {
600
- if (event.button !== 0) {
601
- return;
602
- }
603
- event.stopPropagation();
604
- suppressClickRef.current = false;
605
- const rect = event.currentTarget.getBoundingClientRect();
606
- const pointerMin = pointerToMin(appointment.resourceId, event.clientY);
607
- pendingDragRef.current = {
608
- drag: {
609
- kind: "move",
610
- appointmentId: appointment.id,
611
- pointerId: event.pointerId,
612
- resourceId: appointment.resourceId,
613
- durationMin: Math.max(MIN_EVENT_MIN, appointment.endMin - appointment.startMin),
614
- offsetMin: pointerMin - appointment.startMin,
615
- startMin: appointment.startMin,
616
- endMin: appointment.endMin
617
- },
618
- appointment,
619
- startClientX: event.clientX,
620
- startClientY: event.clientY,
621
- width: rect.width,
622
- height: rect.height,
623
- offsetX: event.clientX - rect.left,
624
- offsetY: event.clientY - rect.top
625
- };
626
- },
627
- [pointerToMin]
628
- );
629
- const onResizePointerDown = React2.useCallback(
630
- (event, appointment) => {
631
- if (event.button !== 0) {
632
- return;
633
- }
634
- event.stopPropagation();
635
- suppressClickRef.current = false;
636
- pendingDragRef.current = {
637
- drag: {
638
- kind: "resize",
639
- appointmentId: appointment.id,
640
- pointerId: event.pointerId,
641
- resourceId: appointment.resourceId,
642
- startMin: appointment.startMin,
643
- endMin: appointment.endMin
644
- },
645
- appointment,
646
- startClientX: event.clientX,
647
- startClientY: event.clientY,
648
- width: 0,
649
- height: 0,
650
- offsetX: 0,
651
- offsetY: 0
652
- };
653
- },
654
- []
655
- );
656
- const onGlobalPointerMove = React2.useCallback(
657
- (event) => {
658
- latestPointerRef.current = {
659
- clientX: event.clientX,
660
- clientY: event.clientY,
661
- pointerId: event.pointerId
662
- };
663
- if (frameRef.current !== null) {
664
- return;
665
- }
666
- frameRef.current = window.requestAnimationFrame(() => {
667
- frameRef.current = null;
668
- processPointerFrame({
669
- dayMinutes,
670
- dragRef,
671
- latestPointerRef,
672
- pendingDragRef,
673
- pointerToMin,
674
- pointerToResourceId,
675
- setDrag,
676
- setMovePreview,
677
- suppressClickRef
678
- });
679
- });
680
- },
681
- [dayMinutes, pointerToMin, pointerToResourceId]
682
- );
683
- const onGlobalPointerUp = React2.useCallback(() => {
684
- finishDrag({
685
- dragRef,
686
- frameRef,
687
- latestPointerRef,
688
- pendingDragRef,
689
- persistDrag,
690
- setDrag,
691
- setMovePreview,
692
- suppressClickRef
693
- });
694
- }, [persistDrag]);
695
- return {
696
- colRefs,
697
- drag,
698
- isDropInvalid,
699
- movePreview,
700
- onApptPointerDown,
701
- onGlobalPointerMove,
702
- onGlobalPointerUp,
703
- onResizePointerDown,
704
- suppressClickRef
705
- };
706
- };
707
- function useSchedulerPresentationData({
708
- appointmentColorTokenClassMap,
709
- dayStartAbs,
710
- drag,
711
- getResourceAppointmentAppearance,
712
- getResourceAppointmentBackground,
713
- getResourceAppointmentColorToken,
714
- renderAppts,
715
- resourceAppointmentClassMap,
716
- resources,
717
- selectedDate
718
- }) {
719
- const effectiveAppts = React2.useMemo(() => {
720
- return buildPresentationAppointments(renderAppts, drag, selectedDate, dayStartAbs);
721
- }, [dayStartAbs, drag, renderAppts, selectedDate]);
722
- const baseLaidOutByResource = React2.useMemo(() => {
723
- return buildLaidOutByResource(resources, renderAppts);
724
- }, [renderAppts, resources]);
725
- const laidOutByResource = React2.useMemo(() => {
726
- if (drag.kind !== "resize") {
727
- return baseLaidOutByResource;
728
- }
729
- const next = new Map(baseLaidOutByResource);
730
- const nextAppointmentsForResource = effectiveAppts.filter(
731
- (appointment) => appointment.resourceId === drag.resourceId
732
- );
733
- next.set(drag.resourceId, buildLaidOutAppointments(nextAppointmentsForResource));
734
- return next;
735
- }, [baseLaidOutByResource, drag, effectiveAppts]);
736
- const appointmentBgByResource = React2.useMemo(() => {
737
- if (!getResourceAppointmentBackground) {
738
- return /* @__PURE__ */ new Map();
739
- }
740
- return buildResourceMap({
741
- fallback: void 0,
742
- getValue: getResourceAppointmentBackground,
743
- resources,
744
- toKey: (resource) => resource.id
745
- });
746
- }, [getResourceAppointmentBackground, resources]);
747
- const appointmentColorTokenClassByResource = React2.useMemo(() => {
748
- if (!getResourceAppointmentColorToken) {
749
- return /* @__PURE__ */ new Map();
750
- }
751
- const mergedClassMap = {
752
- ...DEFAULT_APPOINTMENT_COLOR_TOKEN_CLASS_MAP,
753
- ...appointmentColorTokenClassMap ?? {}
754
- };
755
- return buildResourceMap({
756
- fallback: void 0,
757
- getValue: (resource) => {
758
- const token = getResourceAppointmentColorToken(resource);
759
- return token ? mergedClassMap[token] : void 0;
760
- },
761
- resources,
762
- toKey: (resource) => resource.id
763
- });
764
- }, [appointmentColorTokenClassMap, getResourceAppointmentColorToken, resources]);
765
- const appointmentAppearanceByResource = React2.useMemo(() => {
766
- return buildAppointmentAppearanceByResource(
767
- resources,
768
- resourceAppointmentClassMap,
769
- getResourceAppointmentAppearance,
770
- appointmentColorTokenClassByResource,
771
- appointmentBgByResource
772
- );
773
- }, [
774
- appointmentBgByResource,
775
- appointmentColorTokenClassByResource,
776
- getResourceAppointmentAppearance,
777
- resourceAppointmentClassMap,
778
- resources
779
- ]);
780
- return {
781
- appointmentAppearanceByResource,
782
- appointmentBgByResource,
783
- effectiveAppts,
784
- laidOutByResource
785
- };
786
- }
787
- function Gutter({ dayMinutes, dayStartAbs, gridHeight }) {
788
- return /* @__PURE__ */ jsx(
789
- "div",
790
- {
791
- className: "rfs-times border-border bg-card",
792
- style: { width: GUTTER_W, height: gridHeight },
793
- children: Array.from({ length: Math.floor(dayMinutes / 60) + 1 }).map((_, i) => /* @__PURE__ */ jsxs(
794
- "div",
795
- {
796
- className: "rfs-time-tick text-muted-foreground",
797
- style: { top: TOP_PAD + i * 60 * PX_PER_MIN },
798
- children: [
799
- String(Math.floor((dayStartAbs + i * 60) / 60)).padStart(2, "0"),
800
- ":",
801
- String((dayStartAbs + i * 60) % 60).padStart(2, "0")
802
- ]
803
- },
804
- `tick-${i}`
805
- ))
806
- }
807
- );
808
- }
809
- function defaultRenderResourceHeader({ resource }) {
810
- return /* @__PURE__ */ jsx("strong", { children: resource.label });
811
- }
812
- function Header({ resources, renderResourceHeader }) {
813
- return /* @__PURE__ */ jsxs("div", { className: "rfs-header border-border bg-card", children: [
814
- /* @__PURE__ */ jsx(
815
- "div",
816
- {
817
- className: "rfs-gutter border-border",
818
- style: { width: GUTTER_W }
819
- }
820
- ),
821
- resources.map((resource) => /* @__PURE__ */ jsxs(
822
- "div",
823
- {
824
- className: "rfs-resource-header border-border bg-card text-foreground",
825
- style: { minWidth: RESOURCE_MIN_W, flex: 1 },
826
- children: [
827
- (renderResourceHeader ?? ((r) => defaultRenderResourceHeader({ resource: r })))(
828
- resource
829
- ),
830
- isNameLike(resource) ? /* @__PURE__ */ jsx("div", { className: "rfs-resource-subtitle text-muted-foreground", children: fullName(resource) }) : null
831
- ]
832
- },
833
- String(resource.id)
834
- ))
835
- ] });
836
- }
837
- var ResourceColInner = ({
838
- resource,
839
- colRefs,
840
- gridHeight,
841
- dayMinutes,
842
- appointments,
843
- appointmentAppearance,
844
- appointmentBg,
845
- isDropInvalid,
846
- renderAppointment,
847
- onApptPointerDown,
848
- onResizePointerDown,
849
- drag,
850
- suppressClickRef
851
- }) => {
852
- return /* @__PURE__ */ jsx(
853
- "div",
854
- {
855
- className: "rfs-col border-border bg-card",
856
- style: { minWidth: RESOURCE_MIN_W, flex: 1 },
857
- children: /* @__PURE__ */ jsxs(
858
- "div",
859
- {
860
- ref: (el) => {
861
- colRefs.current[String(resource.id)] = el;
862
- },
863
- className: "rfs-col-inner bg-card",
864
- style: { height: gridHeight },
865
- children: [
866
- Array.from({ length: Math.floor(dayMinutes / 60) + 1 }).map((_, i) => /* @__PURE__ */ jsx(
867
- "div",
868
- {
869
- className: "rfs-hour-line border-border/70",
870
- style: { top: TOP_PAD + i * 60 * PX_PER_MIN }
871
- },
872
- `major-${i}`
873
- )),
874
- Array.from({ length: Math.floor(dayMinutes / STEP_MIN) + 1 }).map((_, i) => /* @__PURE__ */ jsx(
875
- "div",
876
- {
877
- className: "rfs-slot-line border-border/50",
878
- style: { top: TOP_PAD + i * STEP_MIN * PX_PER_MIN }
879
- },
880
- `minor-${i}`
881
- )),
882
- appointments.map((appointment) => {
883
- const laneWidthPct = 100 / appointment.lanes;
884
- const top = TOP_PAD + appointment.startMin * PX_PER_MIN;
885
- const height = Math.max(
886
- MIN_EVENT_MIN * PX_PER_MIN,
887
- (appointment.endMin - appointment.startMin) * PX_PER_MIN
888
- );
889
- return /* @__PURE__ */ jsx(
890
- "div",
891
- {
892
- className: `rfs-card-slot ${appointment.visualState === "ghost" ? "rfs-card-slot--ghost" : ""} ${appointment.visualState === "dragging" ? "rfs-card-slot--dragging" : ""}`,
893
- style: {
894
- top,
895
- left: `${laneWidthPct * appointment.lane}%`,
896
- width: `${laneWidthPct}%`,
897
- height
898
- },
899
- children: (renderAppointment ?? defaultRenderAppointment)({
900
- appointment,
901
- isDropInvalid: isDropInvalid && drag.kind === "resize" && appointment.id === drag.appointmentId,
902
- onPointerDown: (event) => onApptPointerDown(event, appointment),
903
- onResizePointerDown: (event) => onResizePointerDown(event, appointment),
904
- appointmentAppearance,
905
- appointmentBackgroundColor: appointmentBg,
906
- drag,
907
- suppressClickRef
908
- })
909
- },
910
- appointment.renderKey
911
- );
912
- })
913
- ]
914
- }
915
- )
916
- }
917
- );
918
- };
919
-
920
- // src/components/scheduler/parts/ResourceCol.tsx
921
- var areResourceColsEqual = (prev, next) => [
922
- prev.resource === next.resource,
923
- prev.colRefs === next.colRefs,
924
- prev.gridHeight === next.gridHeight,
925
- prev.dayMinutes === next.dayMinutes,
926
- prev.appointments === next.appointments,
927
- prev.appointmentAppearance === next.appointmentAppearance,
928
- prev.appointmentBg === next.appointmentBg,
929
- prev.isDropInvalid === next.isDropInvalid,
930
- prev.renderAppointment === next.renderAppointment,
931
- prev.onApptPointerDown === next.onApptPointerDown,
932
- prev.onResizePointerDown === next.onResizePointerDown,
933
- prev.drag === next.drag,
934
- prev.suppressClickRef === next.suppressClickRef
935
- ].every(Boolean);
936
- var ResourceCol = React2.memo(
937
- ResourceColInner,
938
- areResourceColsEqual
939
- );
940
-
941
- // src/lib/cn.ts
942
- function cn(...inputs) {
943
- return inputs.filter(Boolean).join(" ");
944
- }
945
- var variantClasses = {
946
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
947
- outline: "border border-border bg-card text-foreground hover:bg-muted",
948
- ghost: "text-foreground hover:bg-muted"
949
- };
950
- var sizeClasses = {
951
- default: "h-9 px-4 py-2",
952
- sm: "h-8 px-3 text-xs",
953
- icon: "h-8 w-8"
954
- };
955
- var Button = React2.forwardRef(function Button2({ className, variant = "default", size = "default", type = "button", ...props }, ref) {
956
- return /* @__PURE__ */ jsx(
957
- "button",
958
- {
959
- ref,
960
- type,
961
- className: cn(
962
- "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
963
- variantClasses[variant],
964
- sizeClasses[size],
965
- className
966
- ),
967
- ...props
968
- }
969
- );
970
- });
971
- function Calendar({
972
- selected,
973
- onSelect
974
- }) {
975
- const [month, setMonth] = React2.useState(selected ?? /* @__PURE__ */ new Date());
976
- React2.useEffect(() => {
977
- if (selected) {
978
- setMonth(selected);
979
- }
980
- }, [selected]);
981
- const monthStart = startOfMonth(month);
982
- const calendarStart = startOfWeek(monthStart, { weekStartsOn: 1 });
983
- const calendarEnd = endOfWeek(endOfMonth(month), { weekStartsOn: 1 });
984
- const days = [];
985
- for (let day = calendarStart; day <= calendarEnd; day = addDays(day, 1)) {
986
- days.push(day);
987
- }
988
- return /* @__PURE__ */ jsxs("div", { className: "w-72 rounded-md bg-popover", children: [
989
- /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
990
- /* @__PURE__ */ jsx(
991
- Button,
992
- {
993
- variant: "ghost",
994
- size: "icon",
995
- "aria-label": "Previous month",
996
- onClick: () => setMonth(addMonths(month, -1)),
997
- children: "<"
998
- }
999
- ),
1000
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: format(month, "MMMM yyyy") }),
1001
- /* @__PURE__ */ jsx(
1002
- Button,
1003
- {
1004
- variant: "ghost",
1005
- size: "icon",
1006
- "aria-label": "Next month",
1007
- onClick: () => setMonth(addMonths(month, 1)),
1008
- children: ">"
1009
- }
1010
- )
1011
- ] }),
1012
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-1 text-center text-xs text-muted-foreground", children: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((label) => /* @__PURE__ */ jsx(
1013
- "div",
1014
- {
1015
- className: "py-1",
1016
- children: label
1017
- },
1018
- label
1019
- )) }),
1020
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-1", children: days.map((day) => {
1021
- const isSelected = selected ? isSameDay(day, selected) : false;
1022
- return /* @__PURE__ */ jsx(
1023
- Button,
1024
- {
1025
- variant: isSelected ? "default" : "ghost",
1026
- size: "icon",
1027
- className: cn(
1028
- "text-xs",
1029
- !isSameMonth(day, month) && "text-muted-foreground opacity-60",
1030
- isToday(day) && !isSelected && "border border-border"
1031
- ),
1032
- onClick: () => onSelect?.(day),
1033
- children: format(day, "d")
1034
- },
1035
- day.toISOString()
1036
- );
1037
- }) })
1038
- ] });
1039
- }
1040
- var PopoverContext = React2.createContext(null);
1041
- function usePopoverContext() {
1042
- const context = React2.useContext(PopoverContext);
1043
- if (!context) {
1044
- throw new Error("Popover components must be used within Popover.");
1045
- }
1046
- return context;
1047
- }
1048
- function Popover({
1049
- children,
1050
- open: controlledOpen,
1051
- onOpenChange
1052
- }) {
1053
- const [uncontrolledOpen, setUncontrolledOpen] = React2.useState(false);
1054
- const triggerRef = React2.useRef(null);
1055
- const contentRef = React2.useRef(null);
1056
- const open = controlledOpen ?? uncontrolledOpen;
1057
- const setOpen = React2.useCallback(
1058
- (value) => {
1059
- const nextValue = typeof value === "function" ? value(open) : value;
1060
- if (controlledOpen === void 0) {
1061
- setUncontrolledOpen(nextValue);
1062
- }
1063
- onOpenChange?.(nextValue);
1064
- },
1065
- [controlledOpen, onOpenChange, open]
1066
- );
1067
- React2.useEffect(() => {
1068
- if (!open) {
1069
- return void 0;
1070
- }
1071
- const handlePointerDown = (event) => {
1072
- const target = event.target;
1073
- if (triggerRef.current?.contains(target) || contentRef.current?.contains(target)) {
1074
- return;
1075
- }
1076
- setOpen(false);
1077
- };
1078
- const handleKeyDown = (event) => {
1079
- if (event.key === "Escape") {
1080
- setOpen(false);
1081
- }
1082
- };
1083
- document.addEventListener("mousedown", handlePointerDown);
1084
- document.addEventListener("keydown", handleKeyDown);
1085
- return () => {
1086
- document.removeEventListener("mousedown", handlePointerDown);
1087
- document.removeEventListener("keydown", handleKeyDown);
1088
- };
1089
- }, [open, setOpen]);
1090
- return /* @__PURE__ */ jsx(PopoverContext.Provider, { value: { open, setOpen, triggerRef, contentRef }, children: /* @__PURE__ */ jsx("div", { className: "relative inline-flex", children }) });
1091
- }
1092
- function PopoverTrigger({
1093
- children,
1094
- asChild = false
1095
- }) {
1096
- const { open, setOpen, triggerRef } = usePopoverContext();
1097
- if (asChild && React2.isValidElement(children)) {
1098
- const child = children;
1099
- return React2.cloneElement(child, {
1100
- ref: triggerRef,
1101
- "aria-expanded": open,
1102
- onClick: (event) => {
1103
- child.props.onClick?.(event);
1104
- setOpen((current) => !current);
1105
- }
1106
- });
1107
- }
1108
- return /* @__PURE__ */ jsx(
1109
- "button",
1110
- {
1111
- ref: triggerRef,
1112
- type: "button",
1113
- "aria-expanded": open,
1114
- onClick: () => setOpen((current) => !current),
1115
- children
1116
- }
1117
- );
1118
- }
1119
- function PopoverContent({
1120
- children,
1121
- className,
1122
- align = "start"
1123
- }) {
1124
- const { open, contentRef } = usePopoverContext();
1125
- if (!open) {
1126
- return null;
1127
- }
1128
- return /* @__PURE__ */ jsx(
1129
- "div",
1130
- {
1131
- ref: contentRef,
1132
- className: cn(
1133
- "absolute top-full z-50 mt-2 min-w-72 rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-md outline-none",
1134
- align === "start" && "left-0",
1135
- align === "center" && "left-1/2 -translate-x-1/2",
1136
- align === "end" && "right-0",
1137
- className
1138
- ),
1139
- children
1140
- }
1141
- );
1142
- }
1143
- function defaultRenderDatePicker({
1144
- selectedDate,
1145
- onSelectedDateChange
1146
- }) {
1147
- return /* @__PURE__ */ jsxs(Popover, { children: [
1148
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1149
- Button,
1150
- {
1151
- variant: "outline",
1152
- size: "sm",
1153
- className: "min-w-40 justify-start text-left font-normal",
1154
- children: format(selectedDate, "PPP")
1155
- }
1156
- ) }),
1157
- /* @__PURE__ */ jsx(
1158
- PopoverContent,
1159
- {
1160
- className: "w-auto p-2",
1161
- align: "center",
1162
- children: /* @__PURE__ */ jsx(
1163
- Calendar,
1164
- {
1165
- selected: selectedDate,
1166
- onSelect: onSelectedDateChange
1167
- }
1168
- )
1169
- }
1170
- )
1171
- ] });
1172
- }
1173
- function defaultRenderToolbar({
1174
- goToPreviousDay,
1175
- goToNextDay,
1176
- defaultDatePicker,
1177
- prevButtonLabel = "Prev",
1178
- nextButtonLabel = "Next"
1179
- }) {
1180
- return /* @__PURE__ */ jsx("div", { className: "rfs-toolbar", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-border bg-card p-1 shadow-xs", children: [
1181
- /* @__PURE__ */ jsx(
1182
- Button,
1183
- {
1184
- variant: "outline",
1185
- size: "sm",
1186
- onClick: goToPreviousDay,
1187
- children: prevButtonLabel
1188
- }
1189
- ),
1190
- defaultDatePicker,
1191
- /* @__PURE__ */ jsx(
1192
- Button,
1193
- {
1194
- variant: "outline",
1195
- size: "sm",
1196
- onClick: goToNextDay,
1197
- children: nextButtonLabel
1198
- }
1199
- )
1200
- ] }) });
1201
- }
1202
- function Toolbar({
1203
- selectedDate,
1204
- onSelectedDateChange,
1205
- prevButtonLabel,
1206
- nextButtonLabel,
1207
- renderToolbar,
1208
- renderDatePicker
1209
- }) {
1210
- const goToPreviousDay = () => onSelectedDateChange(shiftDays(selectedDate, -1));
1211
- const goToNextDay = () => onSelectedDateChange(shiftDays(selectedDate, 1));
1212
- const defaultDatePicker = (renderDatePicker ?? defaultRenderDatePicker)({
1213
- selectedDate,
1214
- onSelectedDateChange
1215
- });
1216
- if (renderToolbar) {
1217
- return /* @__PURE__ */ jsx(Fragment, { children: renderToolbar({
1218
- selectedDate,
1219
- onSelectedDateChange,
1220
- goToPreviousDay,
1221
- goToNextDay,
1222
- defaultDatePicker
1223
- }) });
1224
- }
1225
- return /* @__PURE__ */ jsx(Fragment, { children: defaultRenderToolbar({
1226
- goToPreviousDay,
1227
- goToNextDay,
1228
- defaultDatePicker,
1229
- prevButtonLabel,
1230
- nextButtonLabel
1231
- }) });
1232
- }
1233
- var IDLE_DRAG = { kind: "none" };
1234
- function Scheduler({
1235
- resources,
1236
- appointments,
1237
- selectedDate,
1238
- onSelectedDateChange,
1239
- adapter,
1240
- onPersistMoveResize,
1241
- onAppointmentChange,
1242
- renderResourceHeader,
1243
- renderAppointment,
1244
- resourceAppointmentClassMap,
1245
- getResourceAppointmentAppearance,
1246
- getResourceAppointmentColorToken,
1247
- appointmentColorTokenClassMap,
1248
- getResourceAppointmentBackground,
1249
- renderToolbar,
1250
- renderDatePicker,
1251
- prevButtonLabel = "Prev",
1252
- nextButtonLabel = "Next",
1253
- dayStart = "09:00",
1254
- dayEnd = "18:00"
1255
- }) {
1256
- const { appointmentMap, dayMinutes, dayStartAbs, gridHeight, gridMinWidth, renderAppts } = useSchedulerBaseData({ adapter, appointments, dayEnd, dayStart, resources, selectedDate });
1257
- const {
1258
- colRefs,
1259
- drag,
1260
- isDropInvalid,
1261
- movePreview,
1262
- onApptPointerDown,
1263
- onGlobalPointerMove,
1264
- onGlobalPointerUp,
1265
- onResizePointerDown,
1266
- suppressClickRef
1267
- } = useSchedulerInteractions({
1268
- appointmentMap,
1269
- dayMinutes,
1270
- dayStartAbs,
1271
- onAppointmentChange,
1272
- onPersistMoveResize,
1273
- renderAppts,
1274
- resources,
1275
- selectedDate
1276
- });
1277
- const { appointmentAppearanceByResource, appointmentBgByResource, laidOutByResource } = useSchedulerPresentationData({
1278
- appointmentColorTokenClassMap,
1279
- dayStartAbs,
1280
- drag,
1281
- getResourceAppointmentAppearance,
1282
- getResourceAppointmentBackground,
1283
- getResourceAppointmentColorToken,
1284
- renderAppts,
1285
- resourceAppointmentClassMap,
1286
- resources,
1287
- selectedDate
1288
- });
1289
- const moveOverlay = React2.useMemo(() => {
1290
- if (drag.kind !== "move" || !movePreview) {
1291
- return null;
1292
- }
1293
- return {
1294
- appointment: {
1295
- ...movePreview.appointment,
1296
- resourceId: drag.resourceId,
1297
- startMin: drag.startMin,
1298
- endMin: drag.endMin,
1299
- visualState: "ghost",
1300
- renderKey: `${movePreview.appointment.renderKey}-overlay`,
1301
- lane: 0,
1302
- lanes: 1
1303
- },
1304
- appearance: appointmentAppearanceByResource.get(drag.resourceId),
1305
- background: appointmentBgByResource.get(drag.resourceId),
1306
- left: movePreview.clientX - movePreview.offsetX,
1307
- top: movePreview.clientY - movePreview.offsetY,
1308
- width: movePreview.width,
1309
- height: movePreview.height
1310
- };
1311
- }, [appointmentAppearanceByResource, appointmentBgByResource, drag, movePreview]);
1312
- return /* @__PURE__ */ jsxs(
1313
- "section",
1314
- {
1315
- className: `rfs-root rounded-lg bg-background text-foreground ${drag.kind === "move" ? "rfs-root--drag-move" : ""} ${drag.kind === "resize" ? "rfs-root--drag-resize" : ""}`,
1316
- onPointerMove: onGlobalPointerMove,
1317
- onPointerUp: onGlobalPointerUp,
1318
- onPointerCancel: onGlobalPointerUp,
1319
- children: [
1320
- /* @__PURE__ */ jsx(
1321
- Toolbar,
1322
- {
1323
- selectedDate,
1324
- onSelectedDateChange,
1325
- prevButtonLabel,
1326
- nextButtonLabel,
1327
- renderToolbar,
1328
- renderDatePicker
1329
- }
1330
- ),
1331
- /* @__PURE__ */ jsx("div", { className: "rfs-shell border-border bg-card", children: /* @__PURE__ */ jsxs("div", { style: { minWidth: gridMinWidth }, children: [
1332
- /* @__PURE__ */ jsx(
1333
- Header,
1334
- {
1335
- resources,
1336
- renderResourceHeader
1337
- }
1338
- ),
1339
- /* @__PURE__ */ jsxs("div", { className: "rfs-body", children: [
1340
- /* @__PURE__ */ jsx(
1341
- Gutter,
1342
- {
1343
- dayMinutes,
1344
- dayStartAbs,
1345
- gridHeight
1346
- }
1347
- ),
1348
- /* @__PURE__ */ jsx("div", { className: "rfs-grid", children: resources.map((resource) => /* @__PURE__ */ jsx(
1349
- ResourceCol,
1350
- {
1351
- resource,
1352
- colRefs,
1353
- gridHeight,
1354
- dayMinutes,
1355
- appointments: laidOutByResource.get(resource.id) ?? [],
1356
- appointmentAppearance: appointmentAppearanceByResource.get(resource.id),
1357
- appointmentBg: appointmentBgByResource.get(resource.id),
1358
- isDropInvalid,
1359
- renderAppointment,
1360
- onApptPointerDown,
1361
- onResizePointerDown,
1362
- drag: drag.kind === "resize" && drag.resourceId === resource.id ? drag : IDLE_DRAG,
1363
- suppressClickRef
1364
- },
1365
- String(resource.id)
1366
- )) })
1367
- ] })
1368
- ] }) }),
1369
- moveOverlay ? /* @__PURE__ */ jsx(
1370
- "div",
1371
- {
1372
- className: "rfs-drag-overlay",
1373
- style: {
1374
- left: moveOverlay.left,
1375
- top: moveOverlay.top,
1376
- width: moveOverlay.width,
1377
- height: moveOverlay.height
1378
- },
1379
- children: (renderAppointment ?? defaultRenderAppointment)({
1380
- appointment: moveOverlay.appointment,
1381
- isDropInvalid,
1382
- onPointerDown: () => void 0,
1383
- onResizePointerDown: () => void 0,
1384
- appointmentAppearance: moveOverlay.appearance,
1385
- appointmentBackgroundColor: moveOverlay.background,
1386
- drag,
1387
- suppressClickRef
1388
- })
1389
- }
1390
- ) : null
1391
- ]
1392
- }
1393
- );
1394
- }
1395
-
1396
- // src/utils/scheduler-appointment-change.utils.ts
1397
- function applySchedulerAppointmentChange(appointments, change, options) {
1398
- return appointments.map((item) => {
1399
- if (options.getId(item) !== options.getId(change.appointment)) {
1400
- return item;
1401
- }
1402
- const withResource = options.setResourceId(item, change.next.resourceId);
1403
- const withStart = options.setStart(withResource, change.next.start);
1404
- return options.setEnd(withStart, change.next.end);
1405
- });
1406
- }
1407
-
1408
- export { Scheduler, applySchedulerAppointmentChange };
1409
- //# sourceMappingURL=index.js.map
1410
- //# sourceMappingURL=index.js.map
1
+ import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {format,startOfMonth,startOfWeek,endOfWeek,endOfMonth,addDays,addMonths,isSameDay,isToday,isSameMonth}from'date-fns';import*as h from'react';function G({appointment:e,isDropInvalid:t,onPointerDown:r,onResizePointerDown:s,appointmentAppearance:o,appointmentBackgroundColor:i}){let c=e.raw,a=typeof c.description=="string"?c.description:void 0,n=typeof i=="string"?i:void 0,d=o?.className??n??"bg-muted/40 dark:bg-muted/30",f=t?"border-red-500 bg-red-100/80 text-red-950 ring-1 ring-red-500/60 dark:bg-red-950/40 dark:text-red-100":"",p=e.visualState==="ghost"?"cursor-grab border-dashed opacity-35":e.visualState==="dragging"?"cursor-grabbing opacity-55 shadow-xl ring-1 ring-border/70":"cursor-grab",R=`relative h-full overflow-hidden rounded-md border border-border p-2 pb-5 text-foreground ${d} ${f} ${p}`;return jsxs("div",{onPointerDown:r,className:R,children:[t?jsx("div",{className:"absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-red-600 text-[10px] font-bold text-white",children:"X"}):null,jsx("div",{className:"text-xs font-semibold",children:e.title}),a?jsx("div",{className:"mt-1 text-[10px] font-medium text-muted-foreground",children:a}):null,jsx("div",{role:"button","aria-label":"Resize appointment",onPointerDown:l=>{e.visualState!=="ghost"&&(l.stopPropagation(),s(l));},className:"absolute inset-x-1 bottom-1 h-2 cursor-ns-resize rounded-full bg-border/90 transition-colors hover:bg-border"})]})}function me(e){if(!e||typeof e!="object")return false;let t=e;return typeof t.firstName=="string"||typeof t.lastName=="string"}var fe={slate:"bg-slate-100 dark:bg-slate-900/40",gray:"bg-gray-100 dark:bg-gray-900/40",zinc:"bg-zinc-100 dark:bg-zinc-900/40",neutral:"bg-neutral-100 dark:bg-neutral-900/40",stone:"bg-stone-100 dark:bg-stone-900/40",red:"bg-red-100 dark:bg-red-950/40",orange:"bg-orange-100 dark:bg-orange-950/40",amber:"bg-amber-100 dark:bg-amber-950/40",yellow:"bg-yellow-100 dark:bg-yellow-950/40",lime:"bg-lime-100 dark:bg-lime-950/40",green:"bg-green-100 dark:bg-green-950/40",emerald:"bg-emerald-100 dark:bg-emerald-950/40",teal:"bg-teal-100 dark:bg-teal-950/40",cyan:"bg-cyan-100 dark:bg-cyan-950/40",sky:"bg-sky-100 dark:bg-sky-950/40",blue:"bg-blue-100 dark:bg-blue-950/40",indigo:"bg-indigo-100 dark:bg-indigo-950/40",violet:"bg-violet-100 dark:bg-violet-950/40",purple:"bg-purple-100 dark:bg-purple-950/40",fuchsia:"bg-fuchsia-100 dark:bg-fuchsia-950/40",pink:"bg-pink-100 dark:bg-pink-950/40",rose:"bg-rose-100 dark:bg-rose-950/40"};function D(e,t,r){return Math.max(t,Math.min(r,e))}function X(e,t=15){return Math.round(e/t)*t}function ne(e){let[t,r]=e.split(":").map(Number);return t*60+r}function Re(e){return `${e.firstName??""} ${e.lastName??""}`.trim()}function K(e){return format(e,"yyyy-MM-dd")}function oe(e,t){return new Date(e.getFullYear(),e.getMonth(),e.getDate()+t)}function _(e,t,r){let s=new Date(e.getFullYear(),e.getMonth(),e.getDate(),0,0,0,0),o=t+r,i=Math.floor(o/60),c=o%60;return s.setHours(i,c,0,0),s}function se(e,t){return t.getHours()*60+t.getMinutes()-e}function ie(e,t){return e.startMin<t.endMin&&t.startMin<e.endMin}function ge(e){let t=[...e].sort((c,a)=>c.startMin-a.startMin||c.endMin-a.endMin),r=[],s=[],o=[];for(let c of t)s=s.filter(n=>n.endMin>c.startMin),!s.some(n=>ie(n,c))&&o.length>0&&(r.push(o),o=[]),o.push(c),s.push(c);o.length&&r.push(o);let i=[];for(let c of r){let a=[...c].sort((p,R)=>p.startMin-R.startMin||p.endMin-R.endMin),n=[],d=new Map;for(let p of a){let R=false;for(let l=0;l<n.length;l++)if(n[l]<=p.startMin){n[l]=p.endMin,d.set(p.id,l),R=true;break}R||(n.push(p.endMin),d.set(p.id,n.length-1));}let f=n.length;for(let p of a)i.push({...p,lane:d.get(p.id)??0,lanes:f});}return i}function ae({fallback:e,getValue:t,resources:r,toKey:s}){let o=new Map;for(let i of r)o.set(s(i),t(i)??e);return o}function he({adapter:e,appointments:t,dayMinutes:r,dayStartAbs:s,selectedISO:o}){return t.map(i=>{let c=new Date(e.getStart(i)),a=new Date(e.getEnd(i));return {raw:i,id:e.getId(i),resourceId:e.getResourceId(i),start:c,end:a,startMin:D(se(s,c),0,r),endMin:D(se(s,a),0,r),title:e.getTitle(i),renderKey:String(e.getId(i)),visualState:"normal"}}).filter(i=>K(i.start)===o)}function Te(e){return new Map(e.map(t=>[t.id,t]))}function be(e,t,r,s){return t.kind==="none"||t.kind==="move"?e:e.map(o=>o.id!==t.appointmentId?o:{...o,resourceId:t.resourceId,startMin:t.startMin,endMin:t.endMin,start:_(r,s,t.startMin),end:_(r,s,t.endMin),visualState:"dragging"})}function Se(e,t){let r=new Map;for(let o of t){let i=r.get(o.resourceId)??[];i.push(o),r.set(o.resourceId,i);}let s=new Map;for(let o of e)s.set(o.id,ce(r.get(o.id)??[]));return s}function ce(e){return ge(e.map(t=>({...t,appointmentId:t.id,id:t.renderKey}))).map(({appointmentId:t,...r})=>({...r,id:t}))}function Ie(e,t,r,s,o){let i=new Map;for(let c of e){let a=t?.[String(c.id)];if(a){i.set(c.id,{className:a});continue}let n=r?.(c);if(n){i.set(c.id,n);continue}let d=s.get(c.id);if(d){i.set(c.id,{className:d});continue}let f=o.get(c.id);i.set(c.id,f?{className:f}:void 0);}return i}function Ae({adapter:e,appointments:t,dayEnd:r="18:00",dayStart:s="09:00",resources:o,selectedDate:i}){let c=h.useMemo(()=>ne(s),[s]),a=h.useMemo(()=>ne(r),[r]),n=h.useMemo(()=>Math.max(0,a-c),[a,c]),d=h.useMemo(()=>12+n*2+24,[n]),f=h.useMemo(()=>72+Math.max(o.length,1)*420,[o.length]),p=h.useMemo(()=>K(i),[i]),R=h.useMemo(()=>he({adapter:e,appointments:t,dayMinutes:n,dayStartAbs:c,selectedISO:p}),[e,t,n,c,p]);return {appointmentMap:h.useMemo(()=>Te(R),[R]),dayEndAbs:a,dayMinutes:n,dayStartAbs:c,gridHeight:d,gridMinWidth:f,renderAppts:R}}var ot=4,st=(e,t)=>{e(r=>r&&{...r,clientX:t.clientX,clientY:t.clientY});},it=e=>{let{dragRef:t,pendingDragRef:r,pointer:s,setDrag:o,setMovePreview:i,suppressClickRef:c}=e,a=r.current;if(!a||a.drag.pointerId!==s.pointerId)return;let n=s.clientX-a.startClientX,d=s.clientY-a.startClientY;Math.hypot(n,d)<ot||(c.current=true,t.current=a.drag,o(a.drag),a.drag.kind==="move"&&i({appointment:a.appointment,clientX:s.clientX,clientY:s.clientY,width:a.width,height:a.height,offsetX:a.offsetX,offsetY:a.offsetY}),r.current=null);},at=(e,t,r,s,o)=>{let i=s(t.clientX,e.resourceId),a=o(i,t.clientY)-e.offsetMin,n=D(X(a),0,r-e.durationMin),d=n+e.durationMin;return n===e.startMin&&d===e.endMin&&i===e.resourceId?null:{...e,resourceId:i,startMin:n,endMin:d}},ct=(e,t,r,s)=>{let o=s(e.resourceId,t.clientY),i=D(X(o),e.startMin+15,r);return i===e.endMin?null:{...e,endMin:i}},Pe=e=>{let{dayMinutes:t,dragRef:r,latestPointerRef:s,pendingDragRef:o,pointerToMin:i,pointerToResourceId:c,setDrag:a,setMovePreview:n,suppressClickRef:d}=e,f=s.current;if(!f)return;let p=r.current;if(p.kind==="none"&&o.current){it({pointer:f,pendingDragRef:o,setDrag:a,setMovePreview:n,dragRef:r,suppressClickRef:d});return}if(p.kind==="none")return;if(p.kind==="move"){st(n,f);let l=at(p,f,t,c,i);l&&(d.current=true,r.current=l,a(l));return}let R=ct(p,f,t,i);R&&(d.current=true,r.current=R,a(R));},xe=e=>{let{dragRef:t,frameRef:r,latestPointerRef:s,pendingDragRef:o,persistDrag:i,setDrag:c,setMovePreview:a,suppressClickRef:n}=e;r.current!==null&&(window.cancelAnimationFrame(r.current),r.current=null),s.current=null,o.current=null,a(null);let d=t.current;d.kind!=="none"&&(c({kind:"none"}),t.current={kind:"none"},n.current&&i(d));};var De=({appointmentMap:e,dayMinutes:t,dayStartAbs:r,onAppointmentChange:s,onPersistMoveResize:o,renderAppts:i,resources:c,selectedDate:a})=>{let[n,d]=h.useState({kind:"none"}),[f,p]=h.useState(null),R=h.useRef({}),l=h.useRef(false),b=h.useRef({kind:"none"}),A=h.useRef(null),I=h.useRef(null),T=h.useRef(null);h.useEffect(()=>{b.current=n;},[n]),h.useEffect(()=>()=>{T.current!==null&&window.cancelAnimationFrame(T.current);},[]);let M=h.useCallback((u,m)=>{let g=R.current[String(u)];if(!g)return 0;let y=m-g.getBoundingClientRect().top-12;return D(X(y/2),0,t)},[t]),V=h.useCallback((u,m)=>{for(let g of c){let y=R.current[String(g.id)];if(!y)continue;let Y=y.getBoundingClientRect();if(u>=Y.left&&u<=Y.right)return g.id}return m},[c]),E=h.useCallback((u,m,g,y)=>i.filter(k=>k.resourceId===m&&k.id!==u).some(k=>ie(k,{startMin:g,endMin:y})),[i]),z=h.useMemo(()=>n.kind==="none"?false:E(n.appointmentId,n.resourceId,n.startMin,n.endMin),[n,E]),O=h.useCallback(async u=>{let m=e.get(u.appointmentId);if(!m||E(u.appointmentId,u.resourceId,u.startMin,u.endMin))return;let g={resourceId:u.resourceId,start:_(a,r,u.startMin),end:_(a,r,u.endMin)};if(s){await s({kind:u.kind,appointment:m.raw,previous:{resourceId:m.resourceId,start:m.start,end:m.end},next:g});return}o&&await o({appointment:m.raw,resourceId:g.resourceId,start:g.start,end:g.end});},[e,r,E,s,o,a]),$=h.useCallback((u,m)=>{if(u.button!==0)return;u.stopPropagation(),l.current=false;let g=u.currentTarget.getBoundingClientRect(),y=M(m.resourceId,u.clientY);A.current={drag:{kind:"move",appointmentId:m.id,pointerId:u.pointerId,resourceId:m.resourceId,durationMin:Math.max(15,m.endMin-m.startMin),offsetMin:y-m.startMin,startMin:m.startMin,endMin:m.endMin},appointment:m,startClientX:u.clientX,startClientY:u.clientY,width:g.width,height:g.height,offsetX:u.clientX-g.left,offsetY:u.clientY-g.top};},[M]),j=h.useCallback((u,m)=>{u.button===0&&(u.stopPropagation(),l.current=false,A.current={drag:{kind:"resize",appointmentId:m.id,pointerId:u.pointerId,resourceId:m.resourceId,startMin:m.startMin,endMin:m.endMin},appointment:m,startClientX:u.clientX,startClientY:u.clientY,width:0,height:0,offsetX:0,offsetY:0});},[]),F=h.useCallback(u=>{I.current={clientX:u.clientX,clientY:u.clientY,pointerId:u.pointerId},T.current===null&&(T.current=window.requestAnimationFrame(()=>{T.current=null,Pe({dayMinutes:t,dragRef:b,latestPointerRef:I,pendingDragRef:A,pointerToMin:M,pointerToResourceId:V,setDrag:d,setMovePreview:p,suppressClickRef:l});}));},[t,M,V]),ee=h.useCallback(()=>{xe({dragRef:b,frameRef:T,latestPointerRef:I,pendingDragRef:A,persistDrag:O,setDrag:d,setMovePreview:p,suppressClickRef:l});},[O]);return {colRefs:R,drag:n,isDropInvalid:z,movePreview:f,onApptPointerDown:$,onGlobalPointerMove:F,onGlobalPointerUp:ee,onResizePointerDown:j,suppressClickRef:l}};function ke({appointmentColorTokenClassMap:e,dayStartAbs:t,drag:r,getResourceAppointmentAppearance:s,getResourceAppointmentBackground:o,getResourceAppointmentColorToken:i,renderAppts:c,resourceAppointmentClassMap:a,resources:n,selectedDate:d}){let f=h.useMemo(()=>be(c,r,d,t),[t,r,c,d]),p=h.useMemo(()=>Se(n,c),[c,n]),R=h.useMemo(()=>{if(r.kind!=="resize")return p;let I=new Map(p),T=f.filter(M=>M.resourceId===r.resourceId);return I.set(r.resourceId,ce(T)),I},[p,r,f]),l=h.useMemo(()=>o?ae({fallback:void 0,getValue:o,resources:n,toKey:I=>I.id}):new Map,[o,n]),b=h.useMemo(()=>{if(!i)return new Map;let I={...fe,...e??{}};return ae({fallback:void 0,getValue:T=>{let M=i(T);return M?I[M]:void 0},resources:n,toKey:T=>T.id})},[e,i,n]);return {appointmentAppearanceByResource:h.useMemo(()=>Ie(n,a,s,b,l),[l,b,s,a,n]),appointmentBgByResource:l,effectiveAppts:f,laidOutByResource:R}}function we({dayMinutes:e,dayStartAbs:t,gridHeight:r}){return jsx("div",{className:"rfs-times border-border bg-card",style:{width:72,height:r},children:Array.from({length:Math.floor(e/60)+1}).map((s,o)=>jsxs("div",{className:"rfs-time-tick text-muted-foreground",style:{top:12+o*60*2},children:[String(Math.floor((t+o*60)/60)).padStart(2,"0"),":",String((t+o*60)%60).padStart(2,"0")]},`tick-${o}`))})}function Ne({resource:e}){return jsx("strong",{children:e.label})}function Ee({resources:e,renderResourceHeader:t}){return jsxs("div",{className:"rfs-header border-border bg-card",children:[jsx("div",{className:"rfs-gutter border-border",style:{width:72}}),e.map(r=>jsxs("div",{className:"rfs-resource-header border-border bg-card text-foreground",style:{minWidth:420,flex:1},children:[(t??(s=>Ne({resource:s})))(r),me(r)?jsx("div",{className:"rfs-resource-subtitle text-muted-foreground",children:Re(r)}):null]},String(r.id)))]})}var Oe=({resource:e,colRefs:t,gridHeight:r,dayMinutes:s,appointments:o,appointmentAppearance:i,appointmentBg:c,isDropInvalid:a,renderAppointment:n,onApptPointerDown:d,onResizePointerDown:f,drag:p,suppressClickRef:R})=>jsx("div",{className:"rfs-col border-border bg-card",style:{minWidth:420,flex:1},children:jsxs("div",{ref:l=>{t.current[String(e.id)]=l;},className:"rfs-col-inner bg-card",style:{height:r},children:[Array.from({length:Math.floor(s/60)+1}).map((l,b)=>jsx("div",{className:"rfs-hour-line border-border/70",style:{top:12+b*60*2}},`major-${b}`)),Array.from({length:Math.floor(s/15)+1}).map((l,b)=>jsx("div",{className:"rfs-slot-line border-border/50",style:{top:12+b*15*2}},`minor-${b}`)),o.map(l=>{let b=100/l.lanes,A=12+l.startMin*2,I=Math.max(30,(l.endMin-l.startMin)*2);return jsx("div",{className:`rfs-card-slot ${l.visualState==="ghost"?"rfs-card-slot--ghost":""} ${l.visualState==="dragging"?"rfs-card-slot--dragging":""}`,style:{top:A,left:`${b*l.lane}%`,width:`${b}%`,height:I},children:(n??G)({appointment:l,isDropInvalid:a&&p.kind==="resize"&&l.id===p.appointmentId,onPointerDown:T=>d(T,l),onResizePointerDown:T=>f(T,l),appointmentAppearance:i,appointmentBackgroundColor:c,drag:p,suppressClickRef:R})},l.renderKey)})]})});var mt=(e,t)=>[e.resource===t.resource,e.colRefs===t.colRefs,e.gridHeight===t.gridHeight,e.dayMinutes===t.dayMinutes,e.appointments===t.appointments,e.appointmentAppearance===t.appointmentAppearance,e.appointmentBg===t.appointmentBg,e.isDropInvalid===t.isDropInvalid,e.renderAppointment===t.renderAppointment,e.onApptPointerDown===t.onApptPointerDown,e.onResizePointerDown===t.onResizePointerDown,e.drag===t.drag,e.suppressClickRef===t.suppressClickRef].every(Boolean),_e=h.memo(Oe,mt);function H(...e){return e.filter(Boolean).join(" ")}var ft={default:"bg-primary text-primary-foreground hover:bg-primary/90",outline:"border border-border bg-card text-foreground hover:bg-muted",ghost:"text-foreground hover:bg-muted"},Rt={default:"h-9 px-4 py-2",sm:"h-8 px-3 text-xs",icon:"h-8 w-8"},P=h.forwardRef(function({className:t,variant:r="default",size:s="default",type:o="button",...i},c){return jsx("button",{ref:c,type:o,className:H("inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",ft[r],Rt[s],t),...i})});function Ue({selected:e,onSelect:t}){let[r,s]=h.useState(e??new Date);h.useEffect(()=>{e&&s(e);},[e]);let o=startOfMonth(r),i=startOfWeek(o,{weekStartsOn:1}),c=endOfWeek(endOfMonth(r),{weekStartsOn:1}),a=[];for(let n=i;n<=c;n=addDays(n,1))a.push(n);return jsxs("div",{className:"w-72 rounded-md bg-popover",children:[jsxs("div",{className:"mb-2 flex items-center justify-between",children:[jsx(P,{variant:"ghost",size:"icon","aria-label":"Previous month",onClick:()=>s(addMonths(r,-1)),children:"<"}),jsx("div",{className:"text-sm font-medium",children:format(r,"MMMM yyyy")}),jsx(P,{variant:"ghost",size:"icon","aria-label":"Next month",onClick:()=>s(addMonths(r,1)),children:">"})]}),jsx("div",{className:"grid grid-cols-7 gap-1 text-center text-xs text-muted-foreground",children:["Mo","Tu","We","Th","Fr","Sa","Su"].map(n=>jsx("div",{className:"py-1",children:n},n))}),jsx("div",{className:"grid grid-cols-7 gap-1",children:a.map(n=>{let d=e?isSameDay(n,e):false;return jsx(P,{variant:d?"default":"ghost",size:"icon",className:H("text-xs",!isSameMonth(n,r)&&"text-muted-foreground opacity-60",isToday(n)&&!d&&"border border-border"),onClick:()=>t?.(n),children:format(n,"d")},n.toISOString())})})]})}var Ve=h.createContext(null);function $e(){let e=h.useContext(Ve);if(!e)throw new Error("Popover components must be used within Popover.");return e}function Fe({children:e,open:t,onOpenChange:r}){let[s,o]=h.useState(false),i=h.useRef(null),c=h.useRef(null),a=t??s,n=h.useCallback(d=>{let f=typeof d=="function"?d(a):d;t===void 0&&o(f),r?.(f);},[t,r,a]);return h.useEffect(()=>{if(!a)return;let d=p=>{let R=p.target;i.current?.contains(R)||c.current?.contains(R)||n(false);},f=p=>{p.key==="Escape"&&n(false);};return document.addEventListener("mousedown",d),document.addEventListener("keydown",f),()=>{document.removeEventListener("mousedown",d),document.removeEventListener("keydown",f);}},[a,n]),jsx(Ve.Provider,{value:{open:a,setOpen:n,triggerRef:i,contentRef:c},children:jsx("div",{className:"relative inline-flex",children:e})})}function We({children:e,asChild:t=false}){let{open:r,setOpen:s,triggerRef:o}=$e();if(t&&h.isValidElement(e)){let i=e;return h.cloneElement(i,{ref:o,"aria-expanded":r,onClick:c=>{i.props.onClick?.(c),s(a=>!a);}})}return jsx("button",{ref:o,type:"button","aria-expanded":r,onClick:()=>s(i=>!i),children:e})}function Ge({children:e,className:t,align:r="start"}){let{open:s,contentRef:o}=$e();return s?jsx("div",{ref:o,className:H("absolute top-full z-50 mt-2 min-w-72 rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-md outline-none",r==="start"&&"left-0",r==="center"&&"left-1/2 -translate-x-1/2",r==="end"&&"right-0",t),children:e}):null}function Ke({selectedDate:e,onSelectedDateChange:t}){return jsxs(Fe,{children:[jsx(We,{asChild:true,children:jsx(P,{variant:"outline",size:"sm",className:"min-w-40 justify-start text-left font-normal",children:format(e,"PPP")})}),jsx(Ge,{className:"w-auto p-2",align:"center",children:jsx(Ue,{selected:e,onSelect:t})})]})}function qe({goToPreviousDay:e,goToNextDay:t,defaultDatePicker:r,prevButtonLabel:s="Prev",nextButtonLabel:o="Next"}){return jsx("div",{className:"rfs-toolbar",children:jsxs("div",{className:"flex items-center gap-2 rounded-lg border border-border bg-card p-1 shadow-xs",children:[jsx(P,{variant:"outline",size:"sm",onClick:e,children:s}),r,jsx(P,{variant:"outline",size:"sm",onClick:t,children:o})]})})}function Ze({selectedDate:e,onSelectedDateChange:t,prevButtonLabel:r,nextButtonLabel:s,renderToolbar:o,renderDatePicker:i}){let c=()=>t(oe(e,-1)),a=()=>t(oe(e,1)),n=(i??Ke)({selectedDate:e,onSelectedDateChange:t});return o?jsx(Fragment,{children:o({selectedDate:e,onSelectedDateChange:t,goToPreviousDay:c,goToNextDay:a,defaultDatePicker:n})}):jsx(Fragment,{children:qe({goToPreviousDay:c,goToNextDay:a,defaultDatePicker:n,prevButtonLabel:r,nextButtonLabel:s})})}var Dt={kind:"none"};function kt({resources:e,appointments:t,selectedDate:r,onSelectedDateChange:s,adapter:o,onPersistMoveResize:i,onAppointmentChange:c,renderResourceHeader:a,renderAppointment:n,resourceAppointmentClassMap:d,getResourceAppointmentAppearance:f,getResourceAppointmentColorToken:p,appointmentColorTokenClassMap:R,getResourceAppointmentBackground:l,renderToolbar:b,renderDatePicker:A,prevButtonLabel:I="Prev",nextButtonLabel:T="Next",dayStart:M="09:00",dayEnd:V="18:00"}){let{appointmentMap:E,dayMinutes:z,dayStartAbs:O,gridHeight:$,gridMinWidth:j,renderAppts:F}=Ae({adapter:o,appointments:t,dayEnd:V,dayStart:M,resources:e,selectedDate:r}),{colRefs:ee,drag:u,isDropInvalid:m,movePreview:g,onApptPointerDown:y,onGlobalPointerMove:Y,onGlobalPointerUp:k,onResizePointerDown:et,suppressClickRef:le}=De({appointmentMap:E,dayMinutes:z,dayStartAbs:O,onAppointmentChange:c,onPersistMoveResize:i,renderAppts:F,resources:e,selectedDate:r}),{appointmentAppearanceByResource:te,appointmentBgByResource:re,laidOutByResource:tt}=ke({appointmentColorTokenClassMap:R,dayStartAbs:O,drag:u,getResourceAppointmentAppearance:f,getResourceAppointmentBackground:l,getResourceAppointmentColorToken:p,renderAppts:F,resourceAppointmentClassMap:d,resources:e,selectedDate:r}),x=h.useMemo(()=>u.kind!=="move"||!g?null:{appointment:{...g.appointment,resourceId:u.resourceId,startMin:u.startMin,endMin:u.endMin,visualState:"ghost",renderKey:`${g.appointment.renderKey}-overlay`,lane:0,lanes:1},appearance:te.get(u.resourceId),background:re.get(u.resourceId),left:g.clientX-g.offsetX,top:g.clientY-g.offsetY,width:g.width,height:g.height},[te,re,u,g]);return jsxs("section",{className:`rfs-root rounded-lg bg-background text-foreground ${u.kind==="move"?"rfs-root--drag-move":""} ${u.kind==="resize"?"rfs-root--drag-resize":""}`,onPointerMove:Y,onPointerUp:k,onPointerCancel:k,children:[jsx(Ze,{selectedDate:r,onSelectedDateChange:s,prevButtonLabel:I,nextButtonLabel:T,renderToolbar:b,renderDatePicker:A}),jsx("div",{className:"rfs-shell border-border bg-card",children:jsxs("div",{style:{minWidth:j},children:[jsx(Ee,{resources:e,renderResourceHeader:a}),jsxs("div",{className:"rfs-body",children:[jsx(we,{dayMinutes:z,dayStartAbs:O,gridHeight:$}),jsx("div",{className:"rfs-grid",children:e.map(L=>jsx(_e,{resource:L,colRefs:ee,gridHeight:$,dayMinutes:z,appointments:tt.get(L.id)??[],appointmentAppearance:te.get(L.id),appointmentBg:re.get(L.id),isDropInvalid:m,renderAppointment:n,onApptPointerDown:y,onResizePointerDown:et,drag:u.kind==="resize"&&u.resourceId===L.id?u:Dt,suppressClickRef:le},String(L.id)))})]})]})}),x?jsx("div",{className:"rfs-drag-overlay",style:{left:x.left,top:x.top,width:x.width,height:x.height},children:(n??G)({appointment:x.appointment,isDropInvalid:m,onPointerDown:()=>{},onResizePointerDown:()=>{},appointmentAppearance:x.appearance,appointmentBackgroundColor:x.background,drag:u,suppressClickRef:le})}):null]})}function wt(e,t,r){return e.map(s=>{if(r.getId(s)!==r.getId(t.appointment))return s;let o=r.setResourceId(s,t.next.resourceId),i=r.setStart(o,t.next.start);return r.setEnd(i,t.next.end)})}export{kt as Scheduler,wt as applySchedulerAppointmentChange};