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