@marco.colia/react-fast-scheduler 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,414 @@
1
+ import * as React from 'react';
2
+ import { format } from 'date-fns';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ // src/components/Scheduler.tsx
6
+
7
+ // src/components/constants.ts
8
+ var STEP_MIN = 15;
9
+ var MIN_EVENT_MIN = 15;
10
+ var PX_PER_MIN = 2;
11
+ var GUTTER_W = 72;
12
+ var TOP_PAD = 12;
13
+ var BOTTOM_PAD = 24;
14
+ var RESOURCE_MIN_W = 420;
15
+ var clamp = (n, min, max) => {
16
+ return Math.max(min, Math.min(max, n));
17
+ };
18
+ var snap = (mins, stepMin = STEP_MIN) => {
19
+ return Math.round(mins / stepMin) * stepMin;
20
+ };
21
+ var parseHHMM = (hhmm) => {
22
+ const [h, m] = hhmm.split(":").map(Number);
23
+ return h * 60 + m;
24
+ };
25
+ var fullName = (u) => {
26
+ return `${u.firstName ?? ""} ${u.lastName ?? ""}`.trim();
27
+ };
28
+ var dayISO = (d) => {
29
+ return format(d, "yyyy-MM-dd");
30
+ };
31
+ var shiftDays = (d, delta) => {
32
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate() + delta);
33
+ };
34
+ var dateAtMinute = (selectedDate, dayStartAbs, minuteFromStart) => {
35
+ const base = new Date(
36
+ selectedDate.getFullYear(),
37
+ selectedDate.getMonth(),
38
+ selectedDate.getDate(),
39
+ 0,
40
+ 0,
41
+ 0,
42
+ 0
43
+ );
44
+ const abs = dayStartAbs + minuteFromStart;
45
+ const hh = Math.floor(abs / 60);
46
+ const mm = abs % 60;
47
+ base.setHours(hh, mm, 0, 0);
48
+ return base;
49
+ };
50
+ var minutesFromDayStart = (dayStartAbs, dt) => {
51
+ return dt.getHours() * 60 + dt.getMinutes() - dayStartAbs;
52
+ };
53
+ var overlaps = (a, b) => {
54
+ return a.startMin < b.endMin && b.startMin < a.endMin;
55
+ };
56
+ var layoutOverlaps = (blocks) => {
57
+ const sorted = [...blocks].sort((a, b) => a.startMin - b.startMin || a.endMin - b.endMin);
58
+ const groups = [];
59
+ let active = [];
60
+ let current = [];
61
+ for (const e of sorted) {
62
+ active = active.filter((x) => x.endMin > e.startMin);
63
+ const conflicts = active.some((x) => overlaps(x, e));
64
+ if (!conflicts && current.length > 0) {
65
+ groups.push(current);
66
+ current = [];
67
+ }
68
+ current.push(e);
69
+ active.push(e);
70
+ }
71
+ if (current.length) {
72
+ groups.push(current);
73
+ }
74
+ const out = [];
75
+ for (const group of groups) {
76
+ const g = [...group].sort((a, b) => a.startMin - b.startMin || a.endMin - b.endMin);
77
+ const laneEnd = [];
78
+ const laneOf = /* @__PURE__ */ new Map();
79
+ for (const e of g) {
80
+ let placed = false;
81
+ for (let i = 0; i < laneEnd.length; i++) {
82
+ if (laneEnd[i] <= e.startMin) {
83
+ laneEnd[i] = e.endMin;
84
+ laneOf.set(e.id, i);
85
+ placed = true;
86
+ break;
87
+ }
88
+ }
89
+ if (!placed) {
90
+ laneEnd.push(e.endMin);
91
+ laneOf.set(e.id, laneEnd.length - 1);
92
+ }
93
+ }
94
+ const lanes = laneEnd.length;
95
+ for (const e of g) {
96
+ out.push({ ...e, lane: laneOf.get(e.id) ?? 0, lanes });
97
+ }
98
+ }
99
+ return out;
100
+ };
101
+ function isNameLike(resource) {
102
+ if (!resource || typeof resource !== "object") {
103
+ return false;
104
+ }
105
+ const maybe = resource;
106
+ return typeof maybe.firstName === "string" || typeof maybe.lastName === "string";
107
+ }
108
+ function Scheduler({
109
+ resources,
110
+ appointments,
111
+ selectedDate,
112
+ onSelectedDateChange,
113
+ adapter,
114
+ onPersistMoveResize,
115
+ onAppointmentChange,
116
+ renderResourceHeader,
117
+ renderAppointment,
118
+ getResourceAppointmentBackground,
119
+ renderDatePicker,
120
+ prevButtonLabel = "Prev",
121
+ nextButtonLabel = "Next",
122
+ dayStart = "09:00",
123
+ dayEnd = "18:00"
124
+ }) {
125
+ const [drag, setDrag] = React.useState({ kind: "none" });
126
+ const colRefs = React.useRef({});
127
+ const suppressClickRef = React.useRef(false);
128
+ const dayStartAbs = parseHHMM(dayStart);
129
+ const dayEndAbs = parseHHMM(dayEnd);
130
+ const dayMinutes = Math.max(0, dayEndAbs - dayStartAbs);
131
+ const gridHeight = TOP_PAD + dayMinutes * PX_PER_MIN + BOTTOM_PAD;
132
+ const gridMinWidth = GUTTER_W + Math.max(resources.length, 1) * RESOURCE_MIN_W;
133
+ const selectedISO = React.useMemo(() => dayISO(selectedDate), [selectedDate]);
134
+ const renderAppts = React.useMemo(() => {
135
+ return appointments.map((appointment) => {
136
+ const start = new Date(adapter.getStart(appointment));
137
+ const end = new Date(adapter.getEnd(appointment));
138
+ return {
139
+ raw: appointment,
140
+ id: adapter.getId(appointment),
141
+ resourceId: adapter.getResourceId(appointment),
142
+ start,
143
+ end,
144
+ startMin: clamp(minutesFromDayStart(dayStartAbs, start), 0, dayMinutes),
145
+ endMin: clamp(minutesFromDayStart(dayStartAbs, end), 0, dayMinutes),
146
+ title: adapter.getTitle(appointment)
147
+ };
148
+ }).filter((appointment) => dayISO(appointment.start) === selectedISO);
149
+ }, [appointments, adapter, dayStartAbs, dayMinutes, selectedISO]);
150
+ const appointmentMap = React.useMemo(() => {
151
+ return new Map(renderAppts.map((appointment) => [appointment.id, appointment]));
152
+ }, [renderAppts]);
153
+ const effectiveAppts = React.useMemo(() => {
154
+ if (drag.kind === "none") {
155
+ return renderAppts;
156
+ }
157
+ return renderAppts.map((appointment) => {
158
+ if (appointment.id !== drag.appointmentId) {
159
+ return appointment;
160
+ }
161
+ return {
162
+ ...appointment,
163
+ resourceId: drag.resourceId,
164
+ startMin: drag.startMin,
165
+ endMin: drag.endMin,
166
+ start: dateAtMinute(selectedDate, dayStartAbs, drag.startMin),
167
+ end: dateAtMinute(selectedDate, dayStartAbs, drag.endMin)
168
+ };
169
+ });
170
+ }, [drag, renderAppts, selectedDate, dayStartAbs]);
171
+ const laidOutByResource = React.useMemo(() => {
172
+ const byResource = /* @__PURE__ */ new Map();
173
+ for (const resource of resources) {
174
+ const events = effectiveAppts.filter((appointment) => appointment.resourceId === resource.id);
175
+ byResource.set(resource.id, layoutOverlaps(events));
176
+ }
177
+ return byResource;
178
+ }, [effectiveAppts, resources]);
179
+ const appointmentBgByResource = React.useMemo(() => {
180
+ const map = /* @__PURE__ */ new Map();
181
+ if (!getResourceAppointmentBackground) {
182
+ return map;
183
+ }
184
+ for (const resource of resources) {
185
+ map.set(resource.id, getResourceAppointmentBackground(resource));
186
+ }
187
+ return map;
188
+ }, [getResourceAppointmentBackground, resources]);
189
+ const pointerToMin = (resourceId, clientY) => {
190
+ const col = colRefs.current[String(resourceId)];
191
+ if (!col) {
192
+ return 0;
193
+ }
194
+ const y = clientY - col.getBoundingClientRect().top - TOP_PAD;
195
+ return clamp(snap(y / PX_PER_MIN), 0, dayMinutes);
196
+ };
197
+ const pointerToResourceId = (clientX, fallbackResourceId) => {
198
+ for (const resource of resources) {
199
+ const col = colRefs.current[String(resource.id)];
200
+ if (!col) {
201
+ continue;
202
+ }
203
+ const rect = col.getBoundingClientRect();
204
+ if (clientX >= rect.left && clientX <= rect.right) {
205
+ return resource.id;
206
+ }
207
+ }
208
+ return fallbackResourceId;
209
+ };
210
+ const hasOverlap = (appointmentId, resourceId, startMin, endMin) => {
211
+ const events = renderAppts.filter((appointment) => appointment.resourceId === resourceId && appointment.id !== appointmentId);
212
+ return events.some((appointment) => overlaps(appointment, { startMin, endMin }));
213
+ };
214
+ const persistDrag = async (state) => {
215
+ const target = appointmentMap.get(state.appointmentId);
216
+ if (!target) {
217
+ return;
218
+ }
219
+ if (hasOverlap(state.appointmentId, state.resourceId, state.startMin, state.endMin)) {
220
+ return;
221
+ }
222
+ const next = {
223
+ resourceId: state.resourceId,
224
+ start: dateAtMinute(selectedDate, dayStartAbs, state.startMin),
225
+ end: dateAtMinute(selectedDate, dayStartAbs, state.endMin)
226
+ };
227
+ if (onAppointmentChange) {
228
+ await onAppointmentChange({
229
+ kind: state.kind,
230
+ appointment: target.raw,
231
+ previous: {
232
+ resourceId: target.resourceId,
233
+ start: target.start,
234
+ end: target.end
235
+ },
236
+ next
237
+ });
238
+ return;
239
+ }
240
+ if (onPersistMoveResize) {
241
+ await onPersistMoveResize({
242
+ appointment: target.raw,
243
+ resourceId: next.resourceId,
244
+ start: next.start,
245
+ end: next.end
246
+ });
247
+ }
248
+ };
249
+ const onApptPointerDown = (event, appointment) => {
250
+ if (event.button !== 0) {
251
+ return;
252
+ }
253
+ event.stopPropagation();
254
+ suppressClickRef.current = false;
255
+ const pointerMin = pointerToMin(appointment.resourceId, event.clientY);
256
+ setDrag({
257
+ kind: "move",
258
+ appointmentId: appointment.id,
259
+ pointerId: event.pointerId,
260
+ resourceId: appointment.resourceId,
261
+ durationMin: Math.max(MIN_EVENT_MIN, appointment.endMin - appointment.startMin),
262
+ offsetMin: pointerMin - appointment.startMin,
263
+ startMin: appointment.startMin,
264
+ endMin: appointment.endMin
265
+ });
266
+ };
267
+ const onResizePointerDown = (event, appointment) => {
268
+ if (event.button !== 0) {
269
+ return;
270
+ }
271
+ event.stopPropagation();
272
+ suppressClickRef.current = false;
273
+ setDrag({
274
+ kind: "resize",
275
+ appointmentId: appointment.id,
276
+ pointerId: event.pointerId,
277
+ resourceId: appointment.resourceId,
278
+ startMin: appointment.startMin,
279
+ endMin: appointment.endMin
280
+ });
281
+ };
282
+ const onGlobalPointerMove = (event) => {
283
+ if (drag.kind === "none") {
284
+ return;
285
+ }
286
+ if (drag.kind === "move") {
287
+ const nextResourceId = pointerToResourceId(event.clientX, drag.resourceId);
288
+ const pointerMin2 = pointerToMin(nextResourceId, event.clientY);
289
+ const rawStart = pointerMin2 - drag.offsetMin;
290
+ const nextStartMin = clamp(snap(rawStart), 0, dayMinutes - drag.durationMin);
291
+ const nextEndMin2 = nextStartMin + drag.durationMin;
292
+ if (nextStartMin !== drag.startMin || nextEndMin2 !== drag.endMin || nextResourceId !== drag.resourceId) {
293
+ suppressClickRef.current = true;
294
+ setDrag({
295
+ ...drag,
296
+ resourceId: nextResourceId,
297
+ startMin: nextStartMin,
298
+ endMin: nextEndMin2
299
+ });
300
+ }
301
+ return;
302
+ }
303
+ const pointerMin = pointerToMin(drag.resourceId, event.clientY);
304
+ const nextEndMin = clamp(snap(pointerMin), drag.startMin + MIN_EVENT_MIN, dayMinutes);
305
+ if (nextEndMin !== drag.endMin) {
306
+ suppressClickRef.current = true;
307
+ setDrag({
308
+ ...drag,
309
+ endMin: nextEndMin
310
+ });
311
+ }
312
+ };
313
+ const onGlobalPointerUp = () => {
314
+ if (drag.kind === "none") {
315
+ return;
316
+ }
317
+ const current = drag;
318
+ setDrag({ kind: "none" });
319
+ if (suppressClickRef.current) {
320
+ void persistDrag(current);
321
+ }
322
+ };
323
+ return /* @__PURE__ */ jsxs(
324
+ "section",
325
+ {
326
+ className: `rfs-root ${drag.kind === "move" ? "rfs-root--drag-move" : ""} ${drag.kind === "resize" ? "rfs-root--drag-resize" : ""}`,
327
+ onPointerMove: onGlobalPointerMove,
328
+ onPointerUp: onGlobalPointerUp,
329
+ onPointerCancel: onGlobalPointerUp,
330
+ children: [
331
+ /* @__PURE__ */ jsxs("div", { className: "rfs-toolbar", children: [
332
+ /* @__PURE__ */ jsx("button", { className: "rfs-btn", type: "button", onClick: () => onSelectedDateChange(shiftDays(selectedDate, -1)), children: prevButtonLabel }),
333
+ renderDatePicker ? renderDatePicker({ selectedDate, onSelectedDateChange }) : /* @__PURE__ */ jsx("strong", { className: "rfs-date-label", children: selectedISO }),
334
+ /* @__PURE__ */ jsx("button", { className: "rfs-btn", type: "button", onClick: () => onSelectedDateChange(shiftDays(selectedDate, 1)), children: nextButtonLabel })
335
+ ] }),
336
+ /* @__PURE__ */ jsx("div", { className: "rfs-shell", children: /* @__PURE__ */ jsxs("div", { style: { minWidth: gridMinWidth }, children: [
337
+ /* @__PURE__ */ jsxs("div", { className: "rfs-header", children: [
338
+ /* @__PURE__ */ jsx("div", { className: "rfs-gutter", style: { width: GUTTER_W } }),
339
+ resources.map((resource) => /* @__PURE__ */ jsxs("div", { className: "rfs-resource-header", style: { minWidth: RESOURCE_MIN_W, flex: 1 }, children: [
340
+ renderResourceHeader(resource),
341
+ isNameLike(resource) ? /* @__PURE__ */ jsx("div", { className: "rfs-resource-subtitle", children: fullName(resource) }) : null
342
+ ] }, String(resource.id)))
343
+ ] }),
344
+ /* @__PURE__ */ jsxs("div", { className: "rfs-body", children: [
345
+ /* @__PURE__ */ jsx("div", { className: "rfs-times", style: { width: GUTTER_W, height: gridHeight }, children: Array.from({ length: Math.floor(dayMinutes / 60) + 1 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "rfs-time-tick", style: { top: TOP_PAD + i * 60 * PX_PER_MIN }, children: [
346
+ String(Math.floor((dayStartAbs + i * 60) / 60)).padStart(2, "0"),
347
+ ":",
348
+ String((dayStartAbs + i * 60) % 60).padStart(2, "0")
349
+ ] }, `tick-${i}`)) }),
350
+ /* @__PURE__ */ jsx("div", { className: "rfs-grid", children: resources.map((resource) => /* @__PURE__ */ jsx("div", { className: "rfs-col", style: { minWidth: RESOURCE_MIN_W, flex: 1 }, children: /* @__PURE__ */ jsxs(
351
+ "div",
352
+ {
353
+ ref: (el) => {
354
+ colRefs.current[String(resource.id)] = el;
355
+ },
356
+ className: "rfs-col-inner",
357
+ style: { height: gridHeight },
358
+ children: [
359
+ Array.from({ length: Math.floor(dayMinutes / 60) + 1 }).map((_, i) => /* @__PURE__ */ jsx(
360
+ "div",
361
+ {
362
+ className: "rfs-hour-line",
363
+ style: { top: TOP_PAD + i * 60 * PX_PER_MIN }
364
+ },
365
+ `major-${String(resource.id)}-${i}`
366
+ )),
367
+ Array.from({ length: Math.floor(dayMinutes / STEP_MIN) + 1 }).map((_, i) => /* @__PURE__ */ jsx(
368
+ "div",
369
+ {
370
+ className: "rfs-slot-line",
371
+ style: { top: TOP_PAD + i * STEP_MIN * PX_PER_MIN }
372
+ },
373
+ `minor-${String(resource.id)}-${i}`
374
+ )),
375
+ (laidOutByResource.get(resource.id) ?? []).map((appointment) => {
376
+ const laneWidthPct = 100 / appointment.lanes;
377
+ const top = TOP_PAD + appointment.startMin * PX_PER_MIN;
378
+ const height = Math.max(MIN_EVENT_MIN * PX_PER_MIN, (appointment.endMin - appointment.startMin) * PX_PER_MIN);
379
+ const appointmentBackgroundColor = appointmentBgByResource.get(resource.id);
380
+ return /* @__PURE__ */ jsx(
381
+ "div",
382
+ {
383
+ className: "rfs-card-slot",
384
+ style: {
385
+ top,
386
+ left: `${laneWidthPct * appointment.lane}%`,
387
+ width: `${laneWidthPct}%`,
388
+ height
389
+ },
390
+ children: renderAppointment({
391
+ appointment,
392
+ onPointerDown: (event) => onApptPointerDown(event, appointment),
393
+ onResizePointerDown: (event) => onResizePointerDown(event, appointment),
394
+ appointmentBackgroundColor,
395
+ drag,
396
+ suppressClickRef
397
+ })
398
+ },
399
+ String(appointment.id)
400
+ );
401
+ })
402
+ ]
403
+ }
404
+ ) }, String(resource.id))) })
405
+ ] })
406
+ ] }) })
407
+ ]
408
+ }
409
+ );
410
+ }
411
+
412
+ export { Scheduler };
413
+ //# sourceMappingURL=index.js.map
414
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/constants.ts","../src/components/utils.ts","../src/components/Scheduler.tsx"],"names":["pointerMin","nextEndMin"],"mappings":"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","file":"index.js","sourcesContent":["const OPEN_TIME = '09:00';\nconst CLOSE_TIME = '18:00';\n\nconst STEP_MIN = 15;\nconst MIN_EVENT_MIN = 15;\nconst DEFAULT_CREATE_MIN = 30;\n\nconst PX_PER_MIN = 2; // visual scale\nconst HEADER_H = 56;\nconst GUTTER_W = 72;\nconst TOP_PAD = 12;\nconst BOTTOM_PAD = 24;\n\nconst RESOURCE_MIN_W = 420; // columns expand to fill available width; min width enables x-scroll when needed\nconst RESOURCE_MIN_W_TW = 'min-w-[400px] lg:w-full'; // columns expand to fill available width; min width enables x-scroll when needed\nconst LANE_GAP = 6;\n\nexport {\n OPEN_TIME,\n CLOSE_TIME,\n STEP_MIN,\n MIN_EVENT_MIN,\n DEFAULT_CREATE_MIN,\n PX_PER_MIN,\n HEADER_H,\n GUTTER_W,\n TOP_PAD,\n BOTTOM_PAD,\n RESOURCE_MIN_W,\n LANE_GAP,\n RESOURCE_MIN_W_TW,\n}","import { format } from \"date-fns\";\nimport { STEP_MIN } from \"./constants\";\n\ntype NameLike = {\n firstName?: string | null;\n lastName?: string | null;\n};\n\ntype TimeBlockLike<TId extends string | number = string | number> = {\n id: TId;\n startMin: number;\n endMin: number;\n};\n\ntype LaidOut<T extends TimeBlockLike> = T & {\n lane: number;\n lanes: number;\n};\n\nconst clamp = (n: number, min: number, max: number) => {\n return Math.max(min, Math.min(max, n));\n};\n\nconst snap = (mins: number, stepMin = STEP_MIN) => {\n return Math.round(mins / stepMin) * stepMin;\n};\n\nconst parseHHMM = (hhmm: string) => {\n const [h, m] = hhmm.split(\":\").map(Number);\n return h * 60 + m;\n};\n\nconst fullName = <T extends NameLike>(u: T) => {\n return `${u.firstName ?? \"\"} ${u.lastName ?? \"\"}`.trim();\n};\n\nconst dayISO = (d: Date) => {\n return format(d, \"yyyy-MM-dd\");\n};\n\nconst shiftDays = (d: Date, delta: number) => {\n return new Date(d.getFullYear(), d.getMonth(), d.getDate() + delta);\n};\n\nconst dateAtMinute = (\n selectedDate: Date,\n dayStartAbs: number,\n minuteFromStart: number\n) => {\n const base = new Date(\n selectedDate.getFullYear(),\n selectedDate.getMonth(),\n selectedDate.getDate(),\n 0,\n 0,\n 0,\n 0\n );\n const abs = dayStartAbs + minuteFromStart;\n const hh = Math.floor(abs / 60);\n const mm = abs % 60;\n base.setHours(hh, mm, 0, 0);\n return base;\n};\n\nconst minutesFromDayStart = (dayStartAbs: number, dt: Date) => {\n return dt.getHours() * 60 + dt.getMinutes() - dayStartAbs;\n};\n\nconst overlaps = (a: TimeBlockLike, b: TimeBlockLike) => {\n return a.startMin < b.endMin && b.startMin < a.endMin;\n};\n\nconst layoutOverlaps = <T extends TimeBlockLike>(blocks: T[]): LaidOut<T>[] => {\n const sorted = [...blocks].sort((a, b) => a.startMin - b.startMin || a.endMin - b.endMin);\n\n const groups: T[][] = [];\n let active: T[] = [];\n let current: T[] = [];\n\n for (const e of sorted) {\n active = active.filter((x) => x.endMin > e.startMin);\n const conflicts = active.some((x) => overlaps(x, e));\n\n if (!conflicts && current.length > 0) {\n groups.push(current);\n current = [];\n }\n current.push(e);\n active.push(e);\n }\n if (current.length) {\n groups.push(current);\n }\n\n const out: LaidOut<T>[] = [];\n\n for (const group of groups) {\n const g = [...group].sort((a, b) => a.startMin - b.startMin || a.endMin - b.endMin);\n const laneEnd: number[] = [];\n const laneOf = new Map<T[\"id\"], number>();\n\n for (const e of g) {\n let placed = false;\n for (let i = 0; i < laneEnd.length; i++) {\n if (laneEnd[i] <= e.startMin) {\n laneEnd[i] = e.endMin;\n laneOf.set(e.id, i);\n placed = true;\n break;\n }\n }\n if (!placed) {\n laneEnd.push(e.endMin);\n laneOf.set(e.id, laneEnd.length - 1);\n }\n }\n\n const lanes = laneEnd.length;\n for (const e of g) {\n out.push({ ...e, lane: laneOf.get(e.id) ?? 0, lanes });\n }\n }\n\n return out;\n};\n\nexport {\n clamp,\n snap,\n parseHHMM,\n fullName,\n dayISO,\n shiftDays,\n dateAtMinute,\n minutesFromDayStart,\n overlaps,\n layoutOverlaps,\n};\n","import * as React from \"react\";\nimport { BOTTOM_PAD, GUTTER_W, MIN_EVENT_MIN, PX_PER_MIN, RESOURCE_MIN_W, STEP_MIN, TOP_PAD } from \"./constants\";\nimport { clamp, dateAtMinute, dayISO, fullName, layoutOverlaps, minutesFromDayStart, overlaps, parseHHMM, shiftDays, snap } from \"./utils\";\nimport type { BaseSchedulerResource, SchedulerDragState, SchedulerEvent, SchedulerId, SchedulerProps } from \"../types/scheduler\";\n\nfunction isNameLike(resource: unknown): resource is { firstName?: string | null; lastName?: string | null } {\n if (!resource || typeof resource !== \"object\") {\n return false;\n }\n const maybe = resource as { firstName?: unknown; lastName?: unknown };\n return typeof maybe.firstName === \"string\" || typeof maybe.lastName === \"string\";\n}\n\nexport function Scheduler<TAppointment, TResource extends BaseSchedulerResource<TResourceId>, TResourceId extends SchedulerId>({\n resources,\n appointments,\n selectedDate,\n onSelectedDateChange,\n adapter,\n onPersistMoveResize,\n onAppointmentChange,\n renderResourceHeader,\n renderAppointment,\n getResourceAppointmentBackground,\n renderDatePicker,\n prevButtonLabel = \"Prev\",\n nextButtonLabel = \"Next\",\n dayStart = \"09:00\",\n dayEnd = \"18:00\"\n}: SchedulerProps<TAppointment, TResource, TResourceId>) {\n const [drag, setDrag] = React.useState<SchedulerDragState<TResourceId>>({ kind: \"none\" });\n const colRefs = React.useRef<Record<string, HTMLDivElement | null>>({});\n const suppressClickRef = React.useRef(false);\n\n const dayStartAbs = parseHHMM(dayStart);\n const dayEndAbs = parseHHMM(dayEnd);\n const dayMinutes = Math.max(0, dayEndAbs - dayStartAbs);\n const gridHeight = TOP_PAD + dayMinutes * PX_PER_MIN + BOTTOM_PAD;\n const gridMinWidth = GUTTER_W + Math.max(resources.length, 1) * RESOURCE_MIN_W;\n const selectedISO = React.useMemo(() => dayISO(selectedDate), [selectedDate]);\n\n const renderAppts = React.useMemo<SchedulerEvent<TAppointment, TResourceId>[]>(() => {\n return appointments\n .map((appointment) => {\n const start = new Date(adapter.getStart(appointment));\n const end = new Date(adapter.getEnd(appointment));\n return {\n raw: appointment,\n id: adapter.getId(appointment),\n resourceId: adapter.getResourceId(appointment),\n start,\n end,\n startMin: clamp(minutesFromDayStart(dayStartAbs, start), 0, dayMinutes),\n endMin: clamp(minutesFromDayStart(dayStartAbs, end), 0, dayMinutes),\n title: adapter.getTitle(appointment)\n };\n })\n .filter((appointment) => dayISO(appointment.start) === selectedISO);\n }, [appointments, adapter, dayStartAbs, dayMinutes, selectedISO]);\n\n const appointmentMap = React.useMemo(() => {\n return new Map(renderAppts.map((appointment) => [appointment.id, appointment]));\n }, [renderAppts]);\n\n const effectiveAppts = React.useMemo<SchedulerEvent<TAppointment, TResourceId>[]>(() => {\n if (drag.kind === \"none\") {\n return renderAppts;\n }\n return renderAppts.map((appointment) => {\n if (appointment.id !== drag.appointmentId) {\n return appointment;\n }\n return {\n ...appointment,\n resourceId: drag.resourceId,\n startMin: drag.startMin,\n endMin: drag.endMin,\n start: dateAtMinute(selectedDate, dayStartAbs, drag.startMin),\n end: dateAtMinute(selectedDate, dayStartAbs, drag.endMin)\n };\n });\n }, [drag, renderAppts, selectedDate, dayStartAbs]);\n\n const laidOutByResource = React.useMemo(() => {\n const byResource = new Map<TResourceId, ReturnType<typeof layoutOverlaps<SchedulerEvent<TAppointment, TResourceId>>>>();\n for (const resource of resources) {\n const events = effectiveAppts.filter((appointment) => appointment.resourceId === resource.id);\n byResource.set(resource.id, layoutOverlaps(events));\n }\n return byResource;\n }, [effectiveAppts, resources]);\n\n const appointmentBgByResource = React.useMemo(() => {\n const map = new Map<TResourceId, string | undefined>();\n if (!getResourceAppointmentBackground) {\n return map;\n }\n for (const resource of resources) {\n map.set(resource.id, getResourceAppointmentBackground(resource));\n }\n return map;\n }, [getResourceAppointmentBackground, resources]);\n\n const pointerToMin = (resourceId: TResourceId, clientY: number) => {\n const col = colRefs.current[String(resourceId)];\n if (!col) {\n return 0;\n }\n const y = clientY - col.getBoundingClientRect().top - TOP_PAD;\n return clamp(snap(y / PX_PER_MIN), 0, dayMinutes);\n };\n\n const pointerToResourceId = (clientX: number, fallbackResourceId: TResourceId) => {\n for (const resource of resources) {\n const col = colRefs.current[String(resource.id)];\n if (!col) {\n continue;\n }\n const rect = col.getBoundingClientRect();\n if (clientX >= rect.left && clientX <= rect.right) {\n return resource.id;\n }\n }\n return fallbackResourceId;\n };\n\n const hasOverlap = (appointmentId: SchedulerId, resourceId: TResourceId, startMin: number, endMin: number) => {\n const events = renderAppts.filter((appointment) => appointment.resourceId === resourceId && appointment.id !== appointmentId);\n return events.some((appointment) => overlaps(appointment, { id: appointmentId, startMin, endMin }));\n };\n\n const persistDrag = async (state: Exclude<SchedulerDragState<TResourceId>, { kind: \"none\" }>) => {\n const target = appointmentMap.get(state.appointmentId);\n if (!target) {\n return;\n }\n if (hasOverlap(state.appointmentId, state.resourceId, state.startMin, state.endMin)) {\n return;\n }\n const next = {\n resourceId: state.resourceId,\n start: dateAtMinute(selectedDate, dayStartAbs, state.startMin),\n end: dateAtMinute(selectedDate, dayStartAbs, state.endMin)\n };\n\n if (onAppointmentChange) {\n await onAppointmentChange({\n kind: state.kind,\n appointment: target.raw,\n previous: {\n resourceId: target.resourceId,\n start: target.start,\n end: target.end\n },\n next\n });\n return;\n }\n\n if (onPersistMoveResize) {\n await onPersistMoveResize({\n appointment: target.raw,\n resourceId: next.resourceId,\n start: next.start,\n end: next.end\n });\n }\n };\n\n const onApptPointerDown = (event: React.PointerEvent, appointment: SchedulerEvent<TAppointment, TResourceId>) => {\n if (event.button !== 0) {\n return;\n }\n event.stopPropagation();\n suppressClickRef.current = false;\n const pointerMin = pointerToMin(appointment.resourceId, event.clientY);\n setDrag({\n kind: \"move\",\n appointmentId: appointment.id,\n pointerId: event.pointerId,\n resourceId: appointment.resourceId,\n durationMin: Math.max(MIN_EVENT_MIN, appointment.endMin - appointment.startMin),\n offsetMin: pointerMin - appointment.startMin,\n startMin: appointment.startMin,\n endMin: appointment.endMin\n });\n };\n\n const onResizePointerDown = (event: React.PointerEvent, appointment: SchedulerEvent<TAppointment, TResourceId>) => {\n if (event.button !== 0) {\n return;\n }\n event.stopPropagation();\n suppressClickRef.current = false;\n setDrag({\n kind: \"resize\",\n appointmentId: appointment.id,\n pointerId: event.pointerId,\n resourceId: appointment.resourceId,\n startMin: appointment.startMin,\n endMin: appointment.endMin\n });\n };\n\n const onGlobalPointerMove = (event: React.PointerEvent<HTMLElement>) => {\n if (drag.kind === \"none\") {\n return;\n }\n\n if (drag.kind === \"move\") {\n const nextResourceId = pointerToResourceId(event.clientX, drag.resourceId);\n const pointerMin = pointerToMin(nextResourceId, event.clientY);\n const rawStart = pointerMin - drag.offsetMin;\n const nextStartMin = clamp(snap(rawStart), 0, dayMinutes - drag.durationMin);\n const nextEndMin = nextStartMin + drag.durationMin;\n if (nextStartMin !== drag.startMin || nextEndMin !== drag.endMin || nextResourceId !== drag.resourceId) {\n suppressClickRef.current = true;\n setDrag({\n ...drag,\n resourceId: nextResourceId,\n startMin: nextStartMin,\n endMin: nextEndMin\n });\n }\n return;\n }\n\n const pointerMin = pointerToMin(drag.resourceId, event.clientY);\n const nextEndMin = clamp(snap(pointerMin), drag.startMin + MIN_EVENT_MIN, dayMinutes);\n if (nextEndMin !== drag.endMin) {\n suppressClickRef.current = true;\n setDrag({\n ...drag,\n endMin: nextEndMin\n });\n }\n };\n\n const onGlobalPointerUp = () => {\n if (drag.kind === \"none\") {\n return;\n }\n const current = drag;\n setDrag({ kind: \"none\" });\n if (suppressClickRef.current) {\n void persistDrag(current);\n }\n };\n\n return (\n <section\n className={`rfs-root ${drag.kind === \"move\" ? \"rfs-root--drag-move\" : \"\"} ${drag.kind === \"resize\" ? \"rfs-root--drag-resize\" : \"\"}`}\n onPointerMove={onGlobalPointerMove}\n onPointerUp={onGlobalPointerUp}\n onPointerCancel={onGlobalPointerUp}\n >\n <div className=\"rfs-toolbar\">\n <button className=\"rfs-btn\" type=\"button\" onClick={() => onSelectedDateChange(shiftDays(selectedDate, -1))}>\n {prevButtonLabel}\n </button>\n {renderDatePicker ? (\n renderDatePicker({ selectedDate, onSelectedDateChange })\n ) : (\n <strong className=\"rfs-date-label\">{selectedISO}</strong>\n )}\n <button className=\"rfs-btn\" type=\"button\" onClick={() => onSelectedDateChange(shiftDays(selectedDate, 1))}>\n {nextButtonLabel}\n </button>\n </div>\n\n <div className=\"rfs-shell\">\n <div style={{ minWidth: gridMinWidth }}>\n <div className=\"rfs-header\">\n <div className=\"rfs-gutter\" style={{ width: GUTTER_W }} />\n {resources.map((resource) => (\n <div key={String(resource.id)} className=\"rfs-resource-header\" style={{ minWidth: RESOURCE_MIN_W, flex: 1 }}>\n {renderResourceHeader(resource)}\n {isNameLike(resource) ? <div className=\"rfs-resource-subtitle\">{fullName(resource)}</div> : null}\n </div>\n ))}\n </div>\n <div className=\"rfs-body\">\n <div className=\"rfs-times\" style={{ width: GUTTER_W, height: gridHeight }}>\n {Array.from({ length: Math.floor(dayMinutes / 60) + 1 }).map((_, i) => (\n <div key={`tick-${i}`} className=\"rfs-time-tick\" style={{ top: TOP_PAD + i * 60 * PX_PER_MIN }}>\n {String(Math.floor((dayStartAbs + i * 60) / 60)).padStart(2, \"0\")}:\n {String((dayStartAbs + i * 60) % 60).padStart(2, \"0\")}\n </div>\n ))}\n </div>\n <div className=\"rfs-grid\">\n {resources.map((resource) => (\n <div key={String(resource.id)} className=\"rfs-col\" style={{ minWidth: RESOURCE_MIN_W, flex: 1 }}>\n <div\n ref={(el) => {\n colRefs.current[String(resource.id)] = el;\n }}\n className=\"rfs-col-inner\"\n style={{ height: gridHeight }}\n >\n {Array.from({ length: Math.floor(dayMinutes / 60) + 1 }).map((_, i) => (\n <div\n key={`major-${String(resource.id)}-${i}`}\n className=\"rfs-hour-line\"\n style={{ top: TOP_PAD + i * 60 * PX_PER_MIN }}\n />\n ))}\n {Array.from({ length: Math.floor(dayMinutes / STEP_MIN) + 1 }).map((_, i) => (\n <div\n key={`minor-${String(resource.id)}-${i}`}\n className=\"rfs-slot-line\"\n style={{ top: TOP_PAD + i * STEP_MIN * PX_PER_MIN }}\n />\n ))}\n {(laidOutByResource.get(resource.id) ?? []).map((appointment) => {\n const laneWidthPct = 100 / appointment.lanes;\n const top = TOP_PAD + appointment.startMin * PX_PER_MIN;\n const height = Math.max(MIN_EVENT_MIN * PX_PER_MIN, (appointment.endMin - appointment.startMin) * PX_PER_MIN);\n const appointmentBackgroundColor = appointmentBgByResource.get(resource.id);\n return (\n <div\n key={String(appointment.id)}\n className=\"rfs-card-slot\"\n style={{\n top,\n left: `${laneWidthPct * appointment.lane}%`,\n width: `${laneWidthPct}%`,\n height\n }}\n >\n {renderAppointment({\n appointment,\n onPointerDown: (event) => onApptPointerDown(event, appointment),\n onResizePointerDown: (event) => onResizePointerDown(event, appointment),\n appointmentBackgroundColor,\n drag,\n suppressClickRef\n })}\n </div>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n </div>\n </section>\n );\n}\n"]}
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--font-serif:var(--font-serif);--font-mono:var(--font-mono);--color-amber-100:oklch(96.2% .059 95.617);--color-blue-100:oklch(93.2% .032 255.585);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-medium:500;--font-weight-semibold:600;--tracking-normal:var(--tracking-normal);--leading-tight:1.25;--shadow-2xs:var(--shadow-2xs);--shadow-xs:var(--shadow-xs);--shadow-sm:var(--shadow-sm);--shadow-md:var(--shadow-md);--shadow-lg:var(--shadow-lg);--shadow-xl:var(--shadow-xl);--shadow-2xl:var(--shadow-2xl);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--shadow:var(--shadow)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-x-1{inset-inline:calc(var(--spacing) * 1)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.bottom-1{bottom:calc(var(--spacing) * 1)}.mx-auto{margin-inline:auto}.my-6{margin-block:calc(var(--spacing) * 6)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.contents{display:contents}.flex{display:flex}.h-2{height:calc(var(--spacing) * 2)}.h-full{height:100%}.w-auto{width:auto}.min-w-\[400px\]{min-width:400px}.cursor-grab{cursor:grab}.cursor-ns-resize{cursor:ns-resize}.resize{resize:both}.overflow-hidden{overflow:hidden}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:calc(var(--radius) - 2px)}.border{border-style:var(--tw-border-style);border-width:1px}.border-slate-300{border-color:var(--color-slate-300)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-blue-100{background-color:var(--color-blue-100)}.bg-slate-100{background-color:var(--color-slate-100)}.bg-slate-300\/90{background-color:#cad5e2e6}@supports (color:color-mix(in lab, red, red)){.bg-slate-300\/90{background-color:color-mix(in oklab, var(--color-slate-300) 90%, transparent)}}.bg-white{background-color:var(--color-white)}.p-0{padding:calc(var(--spacing) * 0)}.p-2{padding:calc(var(--spacing) * 2)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-1{padding-block:calc(var(--spacing) * 1)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:calc(var(--tracking-normal) - .025em);letter-spacing:calc(var(--tracking-normal) - .025em)}.tracking-wide{--tw-tracking:calc(var(--tracking-normal) + .025em);letter-spacing:calc(var(--tracking-normal) + .025em)}.text-slate-500{color:var(--color-slate-500)}.shadow-sm{--tw-shadow:var(--shadow-sm);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}@media (hover:hover){.hover\:bg-slate-400:hover{background-color:var(--color-slate-400)}}.active\:cursor-grabbing:active{cursor:grabbing}@media (min-width:64rem){.lg\:w-full{width:100%}}}:root{--topbar-h:39px;--header-h:87px;--footer-h:359px;--site-content-min-h:calc(100dvh - var(--topbar-h) - var(--header-h) - var(--footer-h));--background:oklch(100% 0 0);--foreground:oklch(21.93% .0122 145.145);--card:oklch(100% 0 0);--card-foreground:oklch(21.93% .0122 145.145);--popover:oklch(100% 0 0);--popover-foreground:oklch(21.93% .0122 145.145);--primary:oklch(72.56% .0713 141.056);--primary-foreground:oklch(100% 0 0);--secondary:oklch(96.07% .0112 141.263);--secondary-foreground:oklch(44.45% .0502 141.758);--muted:oklch(96.07% .0112 141.263);--muted-foreground:oklch(53.51% .0432 142.652);--accent:oklch(96.07% .0112 141.263);--accent-foreground:oklch(72.56% .0713 141.056);--destructive:oklch(68.07% .1129 109.059);--destructive-foreground:oklch(100% 0 0);--border:oklch(91% .021 140.896);--input:oklch(91% .021 140.896);--ring:oklch(72.56% .0713 141.056);--chart-1:oklch(72.56% .0713 141.056);--chart-2:oklch(60.42% .0688 140.724);--chart-3:oklch(84.06% .0531 141.655);--chart-4:oklch(49.29% .0598 141.478);--chart-5:oklch(91% .021 140.896);--sidebar:oklch(97.77% .0078 139.442);--sidebar-foreground:oklch(21.93% .0122 145.145);--sidebar-primary:oklch(72.56% .0713 141.056);--sidebar-primary-foreground:oklch(100% 0 0);--sidebar-accent:oklch(91% .021 140.896);--sidebar-accent-foreground:oklch(72.56% .0713 141.056);--sidebar-border:oklch(91% .021 140.896);--sidebar-ring:oklch(72.56% .0713 141.056);--font-sans:Geist, ui-sans-serif, sans-serif, system-ui;--font-serif:PT Serif, ui-serif, serif;--font-mono:Inconsolata, ui-monospace, monospace;--radius:.28rem;--shadow-x:0px;--shadow-y:4px;--shadow-blur:10px;--shadow-spread:0px;--shadow-opacity:.05;--shadow-color:0 0 0;--shadow-2xs:0px 4px 10px 0px #00000008;--shadow-xs:0px 4px 10px 0px #00000008;--shadow-sm:0px 4px 10px 0px #0000000d, 0px 1px 2px -1px #0000000d;--shadow:0px 4px 10px 0px #0000000d, 0px 1px 2px -1px #0000000d;--shadow-md:0px 4px 10px 0px #0000000d, 0px 2px 4px -1px #0000000d;--shadow-lg:0px 4px 10px 0px #0000000d, 0px 4px 6px -1px #0000000d;--shadow-xl:0px 4px 10px 0px #0000000d, 0px 8px 10px -1px #0000000d;--shadow-2xl:0px 4px 10px 0px #00000021;--tracking-normal:.01em;--spacing:.3rem;--rfs-bg:var(--background);--rfs-surface:var(--card);--rfs-border:var(--border);--rfs-text:var(--foreground);--rfs-muted:var(--muted-foreground)}.dark{--background:oklch(21.93% .0122 145.145);--foreground:oklch(96.07% .0112 141.263);--card:oklch(21.93% .0122 145.145);--card-foreground:oklch(96.07% .0112 141.263);--popover:oklch(21.93% .0122 145.145);--popover-foreground:oklch(96.07% .0112 141.263);--primary:oklch(72.56% .0713 141.056);--primary-foreground:oklch(21.93% .0122 145.145);--secondary:oklch(27.44% .0184 145.053);--secondary-foreground:oklch(84.06% .0531 141.655);--muted:oklch(27.44% .0184 145.053);--muted-foreground:oklch(60.42% .0688 140.724);--accent:oklch(27.44% .0184 145.053);--accent-foreground:oklch(72.56% .0713 141.056);--destructive:oklch(88.44% .1663 107.49);--destructive-foreground:oklch(100% 0 0);--border:oklch(27.44% .0184 145.053);--input:oklch(27.44% .0184 145.053);--ring:oklch(72.56% .0713 141.056);--chart-1:oklch(72.56% .0713 141.056);--chart-2:oklch(84.06% .0531 141.655);--chart-3:oklch(60.42% .0688 140.724);--chart-4:oklch(49.29% .0598 141.478);--chart-5:oklch(35.96% .0355 143.305);--sidebar:oklch(21.93% .0122 145.145);--sidebar-foreground:oklch(96.07% .0112 141.263);--sidebar-primary:oklch(72.56% .0713 141.056);--sidebar-primary-foreground:oklch(21.93% .0122 145.145);--sidebar-accent:oklch(27.44% .0184 145.053);--sidebar-accent-foreground:oklch(72.56% .0713 141.056);--sidebar-border:oklch(27.44% .0184 145.053);--sidebar-ring:oklch(72.56% .0713 141.056);--font-sans:Geist, ui-sans-serif, sans-serif, system-ui;--font-serif:PT Serif, ui-serif, serif;--font-mono:Inconsolata, ui-monospace, monospace;--radius:.28rem;--shadow-x:0px;--shadow-y:10px;--shadow-blur:20px;--shadow-spread:0px;--shadow-opacity:.4;--shadow-color:0 0 0;--shadow-2xs:0px 10px 20px 0px #0003;--shadow-xs:0px 10px 20px 0px #0003;--shadow-sm:0px 10px 20px 0px #0006, 0px 1px 2px -1px #0006;--shadow:0px 10px 20px 0px #0006, 0px 1px 2px -1px #0006;--shadow-md:0px 10px 20px 0px #0006, 0px 2px 4px -1px #0006;--shadow-lg:0px 10px 20px 0px #0006, 0px 4px 6px -1px #0006;--shadow-xl:0px 10px 20px 0px #0006, 0px 8px 10px -1px #0006;--shadow-2xl:0px 10px 20px 0px #000;--rfs-bg:var(--background);--rfs-surface:var(--card);--rfs-border:var(--border);--rfs-text:var(--foreground);--rfs-muted:var(--muted-foreground)}.rfs-root{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--rfs-text)}.rfs-root--drag-move{cursor:grabbing;-webkit-user-select:none;user-select:none}.rfs-root--drag-resize{cursor:ns-resize;-webkit-user-select:none;user-select:none}.rfs-toolbar{margin-bottom:calc(var(--spacing) * 3);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.rfs-btn{border-radius:calc(var(--radius) - 2px);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-width:1px;border-color:var(--rfs-border);background:var(--rfs-surface)}.rfs-btn:hover{background:var(--rfs-bg)}.rfs-btn:disabled{cursor:not-allowed;opacity:.5}.rfs-date-label{border-radius:calc(var(--radius) - 2px);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);border-width:1px;border-color:var(--rfs-border);background:var(--rfs-surface)}.rfs-shell{border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--rfs-border);background:var(--rfs-surface);overflow-x:auto}.rfs-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--rfs-border);display:flex}.rfs-gutter{border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--rfs-border);flex-shrink:0}.rfs-resource-header{border-right-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 2);border-right-width:1px;border-color:var(--rfs-border)}.rfs-resource-subtitle{margin-top:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--rfs-muted)}.rfs-body{display:flex}.rfs-times{border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--rfs-border);flex-shrink:0;position:relative}.rfs-time-tick{left:calc(var(--spacing) * 1);color:var(--rfs-muted);font-size:11px;position:absolute}.rfs-grid{flex:1;display:flex}.rfs-col{border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--rfs-border)}.rfs-col-inner{position:relative}.rfs-hour-line,.rfs-slot-line{right:calc(var(--spacing) * 0);left:calc(var(--spacing) * 0);border-top-style:var(--tw-border-style);--tw-border-style:dotted;border-style:dotted;border-top-width:1px;border-color:var(--rfs-border);opacity:.5;position:absolute}.rfs-card-slot{padding:calc(var(--spacing) * .5);position:absolute}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@marco.colia/react-fast-scheduler",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight React scheduling library.",
5
+ "keywords": [
6
+ "react",
7
+ "scheduler",
8
+ "calendar",
9
+ "library"
10
+ ],
11
+ "license": "MIT",
12
+ "author": "Marco Colia",
13
+ "type": "module",
14
+ "main": "./dist/index.cjs",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "sideEffects": false,
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js",
25
+ "require": "./dist/index.cjs"
26
+ },
27
+ "./styles.css": "./dist/styles.css"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/justoverclockl/react-fast-scheduler.git"
35
+ },
36
+ "homepage": "https://github.com/justoverclockl/react-fast-scheduler#readme",
37
+ "bugs": {
38
+ "url": "https://github.com/justoverclockl/react-fast-scheduler/issues"
39
+ },
40
+ "scripts": {
41
+ "build": "npm run build:js && npm run build:css",
42
+ "build:js": "tsup",
43
+ "build:css": "tailwindcss -i ./src/global.css -o ./dist/styles.css --minify",
44
+ "dev:demo": "vite",
45
+ "changeset": "changeset",
46
+ "clean": "rimraf dist",
47
+ "dev": "tsup --watch",
48
+ "lint": "eslint .",
49
+ "release": "npm run build && changeset publish",
50
+ "test": "vitest run --passWithNoTests",
51
+ "test:watch": "vitest",
52
+ "typecheck": "tsc --noEmit",
53
+ "prepack": "npm run clean && npm run build"
54
+ },
55
+ "peerDependencies": {
56
+ "react": "^18.0.0 || ^19.0.0",
57
+ "react-dom": "^18.0.0 || ^19.0.0"
58
+ },
59
+ "devDependencies": {
60
+ "@tailwindcss/cli": "^4.1.13",
61
+ "@tailwindcss/vite": "^4.1.13",
62
+ "@changesets/changelog-github": "^0.5.1",
63
+ "@changesets/cli": "^2.29.4",
64
+ "@eslint/js": "^9.25.1",
65
+ "@testing-library/jest-dom": "^6.6.3",
66
+ "@testing-library/react": "^16.3.0",
67
+ "@types/react": "^19.1.8",
68
+ "@types/react-dom": "^19.1.6",
69
+ "@vitest/coverage-v8": "^3.2.4",
70
+ "eslint": "^9.25.1",
71
+ "eslint-plugin-react-hooks": "^5.2.0",
72
+ "globals": "^16.0.0",
73
+ "jsdom": "^26.1.0",
74
+ "react": "^19.1.1",
75
+ "react-dom": "^19.1.1",
76
+ "rimraf": "^6.0.1",
77
+ "tsup": "^8.5.0",
78
+ "tailwindcss": "^4.1.13",
79
+ "typescript": "^5.8.3",
80
+ "typescript-eslint": "^8.30.1",
81
+ "vitest": "^3.2.4"
82
+ },
83
+ "dependencies": {
84
+ "date-fns": "^4.1.0"
85
+ }
86
+ }