@marco.colia/react-fast-scheduler 2.1.0 → 2.1.1
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/README.md +32 -4
- package/dist/index.cjs +244 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +245 -86
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,7 +90,8 @@ export function SchedulerExample() {
|
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
`renderAppointment` is optional. If you omit it, the scheduler uses the built-in appointment card with light/dark mode support.
|
|
93
|
-
`
|
|
93
|
+
`renderToolbar` is optional. If you omit it, the scheduler uses a built-in shadcn-style toolbar with prev/next buttons and a calendar popover date picker.
|
|
94
|
+
`renderDatePicker` is optional. If you omit it, the default toolbar uses the built-in calendar popover date picker.
|
|
94
95
|
`onAppointmentChange` is a simple callback: use it to update your state and run your own side effects.
|
|
95
96
|
|
|
96
97
|
Main props you need to pass:
|
|
@@ -104,8 +105,9 @@ Main props you need to pass:
|
|
|
104
105
|
- `appointmentColorTokenClassMap`: optional map to override token -> class resolution.
|
|
105
106
|
- `getResourceAppointmentAppearance`: optional low-level function to provide appointment `className` per resource.
|
|
106
107
|
- `getResourceAppointmentBackground`: legacy optional function to provide appointment background as a string.
|
|
108
|
+
- `renderToolbar`: optional render hook to replace the entire scheduler toolbar/header controls.
|
|
107
109
|
- `prevButtonLabel` and `nextButtonLabel`: custom labels for toolbar navigation buttons.
|
|
108
|
-
- `renderDatePicker`: optional render hook for
|
|
110
|
+
- `renderDatePicker`: optional render hook for the date picker slot used by the default toolbar.
|
|
109
111
|
- `renderResourceHeader`: optional render hook for resource column headers.
|
|
110
112
|
- `renderAppointment`: optional render hook to fully customize appointment cards.
|
|
111
113
|
|
|
@@ -164,7 +166,32 @@ Main props you need to pass:
|
|
|
164
166
|
/>
|
|
165
167
|
```
|
|
166
168
|
|
|
167
|
-
###
|
|
169
|
+
### Custom toolbar
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<Scheduler
|
|
173
|
+
// ...other props
|
|
174
|
+
renderToolbar={({ selectedDate, goToPreviousDay, goToNextDay, defaultDatePicker }) => (
|
|
175
|
+
<div className="mb-4 flex items-center justify-between gap-3 rounded-xl border border-border bg-card px-4 py-3">
|
|
176
|
+
<div>
|
|
177
|
+
<div className="text-sm font-medium">{selectedDate.toDateString()}</div>
|
|
178
|
+
<div className="text-xs text-muted-foreground">Team schedule</div>
|
|
179
|
+
</div>
|
|
180
|
+
<div className="flex items-center gap-2">
|
|
181
|
+
<button className="rounded-md border px-3 py-2 text-sm" type="button" onClick={goToPreviousDay}>
|
|
182
|
+
Previous
|
|
183
|
+
</button>
|
|
184
|
+
{defaultDatePicker}
|
|
185
|
+
<button className="rounded-md border px-3 py-2 text-sm" type="button" onClick={goToNextDay}>
|
|
186
|
+
Next
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
/>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Custom date picker slot
|
|
168
195
|
|
|
169
196
|
```tsx
|
|
170
197
|
import { Button } from "@/components/ui/button";
|
|
@@ -248,4 +275,5 @@ This repo uses [Changesets](https://github.com/changesets/changesets) and GitHub
|
|
|
248
275
|
1. Create a changeset: `npx changeset`
|
|
249
276
|
2. Merge to `main`
|
|
250
277
|
3. The `Release` workflow opens/updates a release PR
|
|
251
|
-
4. Merging that PR publishes to npm
|
|
278
|
+
4. Merging that PR publishes to npm via npm Trusted Publishing and GitHub OIDC
|
|
279
|
+
4. Merging that PR publishes to npm via npm Trusted Publishing and GitHub OIDC
|
package/dist/index.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var dateFns = require('date-fns');
|
|
5
|
-
var
|
|
5
|
+
var React5 = require('react');
|
|
6
6
|
|
|
7
7
|
function _interopNamespace(e) {
|
|
8
8
|
if (e && e.__esModule) return e;
|
|
@@ -22,7 +22,7 @@ function _interopNamespace(e) {
|
|
|
22
22
|
return Object.freeze(n);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
var
|
|
25
|
+
var React5__namespace = /*#__PURE__*/_interopNamespace(React5);
|
|
26
26
|
|
|
27
27
|
// src/components/defaultRenderAppointment/index.tsx
|
|
28
28
|
function defaultRenderAppointment({
|
|
@@ -55,6 +55,206 @@ function defaultRenderAppointment({
|
|
|
55
55
|
] });
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// src/lib/cn.ts
|
|
59
|
+
function cn(...inputs) {
|
|
60
|
+
return inputs.filter(Boolean).join(" ");
|
|
61
|
+
}
|
|
62
|
+
var variantClasses = {
|
|
63
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
64
|
+
outline: "border border-border bg-card text-foreground hover:bg-muted",
|
|
65
|
+
ghost: "text-foreground hover:bg-muted"
|
|
66
|
+
};
|
|
67
|
+
var sizeClasses = {
|
|
68
|
+
default: "h-9 px-4 py-2",
|
|
69
|
+
sm: "h-8 px-3 text-xs",
|
|
70
|
+
icon: "h-8 w-8"
|
|
71
|
+
};
|
|
72
|
+
var Button = React5__namespace.forwardRef(function Button2({ className, variant = "default", size = "default", type = "button", ...props }, ref) {
|
|
73
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
74
|
+
"button",
|
|
75
|
+
{
|
|
76
|
+
ref,
|
|
77
|
+
type,
|
|
78
|
+
className: cn(
|
|
79
|
+
"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",
|
|
80
|
+
variantClasses[variant],
|
|
81
|
+
sizeClasses[size],
|
|
82
|
+
className
|
|
83
|
+
),
|
|
84
|
+
...props
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
function Calendar({
|
|
89
|
+
selected,
|
|
90
|
+
onSelect
|
|
91
|
+
}) {
|
|
92
|
+
const [month, setMonth] = React5__namespace.useState(selected ?? /* @__PURE__ */ new Date());
|
|
93
|
+
React5__namespace.useEffect(() => {
|
|
94
|
+
if (selected) {
|
|
95
|
+
setMonth(selected);
|
|
96
|
+
}
|
|
97
|
+
}, [selected]);
|
|
98
|
+
const monthStart = dateFns.startOfMonth(month);
|
|
99
|
+
const calendarStart = dateFns.startOfWeek(monthStart, { weekStartsOn: 1 });
|
|
100
|
+
const calendarEnd = dateFns.endOfWeek(dateFns.endOfMonth(month), { weekStartsOn: 1 });
|
|
101
|
+
const days = [];
|
|
102
|
+
for (let day = calendarStart; day <= calendarEnd; day = dateFns.addDays(day, 1)) {
|
|
103
|
+
days.push(day);
|
|
104
|
+
}
|
|
105
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-72 rounded-md bg-popover", children: [
|
|
106
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
107
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Previous month", onClick: () => setMonth(dateFns.addMonths(month, -1)), children: "<" }),
|
|
108
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium", children: dateFns.format(month, "MMMM yyyy") }),
|
|
109
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Next month", onClick: () => setMonth(dateFns.addMonths(month, 1)), children: ">" })
|
|
110
|
+
] }),
|
|
111
|
+
/* @__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("div", { className: "py-1", children: label }, label)) }),
|
|
112
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-1", children: days.map((day) => {
|
|
113
|
+
const isSelected = selected ? dateFns.isSameDay(day, selected) : false;
|
|
114
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
115
|
+
Button,
|
|
116
|
+
{
|
|
117
|
+
variant: isSelected ? "default" : "ghost",
|
|
118
|
+
size: "icon",
|
|
119
|
+
className: cn(
|
|
120
|
+
"text-xs",
|
|
121
|
+
!dateFns.isSameMonth(day, month) && "text-muted-foreground opacity-60",
|
|
122
|
+
dateFns.isToday(day) && !isSelected && "border border-border"
|
|
123
|
+
),
|
|
124
|
+
onClick: () => onSelect?.(day),
|
|
125
|
+
children: dateFns.format(day, "d")
|
|
126
|
+
},
|
|
127
|
+
day.toISOString()
|
|
128
|
+
);
|
|
129
|
+
}) })
|
|
130
|
+
] });
|
|
131
|
+
}
|
|
132
|
+
var PopoverContext = React5__namespace.createContext(null);
|
|
133
|
+
function usePopoverContext() {
|
|
134
|
+
const context = React5__namespace.useContext(PopoverContext);
|
|
135
|
+
if (!context) {
|
|
136
|
+
throw new Error("Popover components must be used within Popover.");
|
|
137
|
+
}
|
|
138
|
+
return context;
|
|
139
|
+
}
|
|
140
|
+
function Popover({
|
|
141
|
+
children,
|
|
142
|
+
open: controlledOpen,
|
|
143
|
+
onOpenChange
|
|
144
|
+
}) {
|
|
145
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React5__namespace.useState(false);
|
|
146
|
+
const triggerRef = React5__namespace.useRef(null);
|
|
147
|
+
const contentRef = React5__namespace.useRef(null);
|
|
148
|
+
const open = controlledOpen ?? uncontrolledOpen;
|
|
149
|
+
const setOpen = React5__namespace.useCallback(
|
|
150
|
+
(value) => {
|
|
151
|
+
const nextValue = typeof value === "function" ? value(open) : value;
|
|
152
|
+
if (controlledOpen === void 0) {
|
|
153
|
+
setUncontrolledOpen(nextValue);
|
|
154
|
+
}
|
|
155
|
+
onOpenChange?.(nextValue);
|
|
156
|
+
},
|
|
157
|
+
[controlledOpen, onOpenChange, open]
|
|
158
|
+
);
|
|
159
|
+
React5__namespace.useEffect(() => {
|
|
160
|
+
if (!open) {
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
163
|
+
const handlePointerDown = (event) => {
|
|
164
|
+
const target = event.target;
|
|
165
|
+
if (triggerRef.current?.contains(target) || contentRef.current?.contains(target)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
setOpen(false);
|
|
169
|
+
};
|
|
170
|
+
const handleKeyDown = (event) => {
|
|
171
|
+
if (event.key === "Escape") {
|
|
172
|
+
setOpen(false);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
document.addEventListener("mousedown", handlePointerDown);
|
|
176
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
177
|
+
return () => {
|
|
178
|
+
document.removeEventListener("mousedown", handlePointerDown);
|
|
179
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
180
|
+
};
|
|
181
|
+
}, [open, setOpen]);
|
|
182
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PopoverContext.Provider, { value: { open, setOpen, triggerRef, contentRef }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative inline-flex", children }) });
|
|
183
|
+
}
|
|
184
|
+
function PopoverTrigger({
|
|
185
|
+
children,
|
|
186
|
+
asChild = false
|
|
187
|
+
}) {
|
|
188
|
+
const { open, setOpen, triggerRef } = usePopoverContext();
|
|
189
|
+
if (asChild && React5__namespace.isValidElement(children)) {
|
|
190
|
+
const child = children;
|
|
191
|
+
return React5__namespace.cloneElement(child, {
|
|
192
|
+
ref: triggerRef,
|
|
193
|
+
"aria-expanded": open,
|
|
194
|
+
onClick: (event) => {
|
|
195
|
+
child.props.onClick?.(event);
|
|
196
|
+
setOpen((current) => !current);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
201
|
+
"button",
|
|
202
|
+
{
|
|
203
|
+
ref: triggerRef,
|
|
204
|
+
type: "button",
|
|
205
|
+
"aria-expanded": open,
|
|
206
|
+
onClick: () => setOpen((current) => !current),
|
|
207
|
+
children
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
function PopoverContent({
|
|
212
|
+
children,
|
|
213
|
+
className,
|
|
214
|
+
align = "start"
|
|
215
|
+
}) {
|
|
216
|
+
const { open, contentRef } = usePopoverContext();
|
|
217
|
+
if (!open) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
221
|
+
"div",
|
|
222
|
+
{
|
|
223
|
+
ref: contentRef,
|
|
224
|
+
className: cn(
|
|
225
|
+
"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",
|
|
226
|
+
align === "start" && "left-0",
|
|
227
|
+
align === "center" && "left-1/2 -translate-x-1/2",
|
|
228
|
+
align === "end" && "right-0",
|
|
229
|
+
className
|
|
230
|
+
),
|
|
231
|
+
children
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
function defaultRenderDatePicker({ selectedDate, onSelectedDateChange }) {
|
|
236
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
|
|
237
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", className: "min-w-40 justify-start text-left font-normal", children: dateFns.format(selectedDate, "PPP") }) }),
|
|
238
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "w-auto p-2", align: "center", children: /* @__PURE__ */ jsxRuntime.jsx(Calendar, { selected: selectedDate, onSelect: onSelectedDateChange }) })
|
|
239
|
+
] });
|
|
240
|
+
}
|
|
241
|
+
function defaultRenderResourceHeader({ resource }) {
|
|
242
|
+
return /* @__PURE__ */ jsxRuntime.jsx("strong", { children: resource.label });
|
|
243
|
+
}
|
|
244
|
+
function defaultRenderToolbar({
|
|
245
|
+
goToPreviousDay,
|
|
246
|
+
goToNextDay,
|
|
247
|
+
defaultDatePicker,
|
|
248
|
+
prevButtonLabel = "Prev",
|
|
249
|
+
nextButtonLabel = "Next"
|
|
250
|
+
}) {
|
|
251
|
+
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: [
|
|
252
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", onClick: goToPreviousDay, children: prevButtonLabel }),
|
|
253
|
+
defaultDatePicker,
|
|
254
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", onClick: goToNextDay, children: nextButtonLabel })
|
|
255
|
+
] }) });
|
|
256
|
+
}
|
|
257
|
+
|
|
58
258
|
// src/components/constants.ts
|
|
59
259
|
var STEP_MIN = 15;
|
|
60
260
|
var MIN_EVENT_MIN = 15;
|
|
@@ -63,8 +263,6 @@ var GUTTER_W = 72;
|
|
|
63
263
|
var TOP_PAD = 12;
|
|
64
264
|
var BOTTOM_PAD = 24;
|
|
65
265
|
var RESOURCE_MIN_W = 420;
|
|
66
|
-
|
|
67
|
-
// src/utils/scheduler-core.utils.ts
|
|
68
266
|
function isNameLike(resource) {
|
|
69
267
|
if (!resource || typeof resource !== "object") {
|
|
70
268
|
return false;
|
|
@@ -72,22 +270,6 @@ function isNameLike(resource) {
|
|
|
72
270
|
const maybe = resource;
|
|
73
271
|
return typeof maybe.firstName === "string" || typeof maybe.lastName === "string";
|
|
74
272
|
}
|
|
75
|
-
function toInputDateValue(date) {
|
|
76
|
-
const year = date.getFullYear();
|
|
77
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
78
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
79
|
-
return `${year}-${month}-${day}`;
|
|
80
|
-
}
|
|
81
|
-
function fromInputDateValue(value) {
|
|
82
|
-
const [yearRaw, monthRaw, dayRaw] = value.split("-");
|
|
83
|
-
const year = Number(yearRaw);
|
|
84
|
-
const month = Number(monthRaw);
|
|
85
|
-
const day = Number(dayRaw);
|
|
86
|
-
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
return new Date(year, month - 1, day);
|
|
90
|
-
}
|
|
91
273
|
var DEFAULT_APPOINTMENT_COLOR_TOKEN_CLASS_MAP = {
|
|
92
274
|
slate: "bg-slate-100 dark:bg-slate-900/40",
|
|
93
275
|
gray: "bg-gray-100 dark:bg-gray-900/40",
|
|
@@ -198,26 +380,6 @@ function layoutOverlaps(blocks) {
|
|
|
198
380
|
}
|
|
199
381
|
return output;
|
|
200
382
|
}
|
|
201
|
-
function defaultRenderDatePicker({ selectedDate, onSelectedDateChange }) {
|
|
202
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
203
|
-
"input",
|
|
204
|
-
{
|
|
205
|
-
type: "date",
|
|
206
|
-
value: toInputDateValue(selectedDate),
|
|
207
|
-
onChange: (event) => {
|
|
208
|
-
const nextDate = fromInputDateValue(event.target.value);
|
|
209
|
-
if (nextDate) {
|
|
210
|
-
onSelectedDateChange(nextDate);
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
className: "h-7 rounded-md border border-border bg-card px-2 text-xs text-foreground",
|
|
214
|
-
"aria-label": "Select date"
|
|
215
|
-
}
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
function defaultRenderResourceHeader({ resource }) {
|
|
219
|
-
return /* @__PURE__ */ jsxRuntime.jsx("strong", { children: resource.label });
|
|
220
|
-
}
|
|
221
383
|
|
|
222
384
|
// src/utils/scheduler-data.utils.ts
|
|
223
385
|
function buildResourceMap({
|
|
@@ -321,13 +483,13 @@ function useSchedulerBaseData({
|
|
|
321
483
|
resources,
|
|
322
484
|
selectedDate
|
|
323
485
|
}) {
|
|
324
|
-
const dayStartAbs =
|
|
325
|
-
const dayEndAbs =
|
|
326
|
-
const dayMinutes =
|
|
327
|
-
const gridHeight =
|
|
328
|
-
const gridMinWidth =
|
|
329
|
-
const selectedISO =
|
|
330
|
-
const renderAppts =
|
|
486
|
+
const dayStartAbs = React5__namespace.useMemo(() => parseHHMM(dayStart), [dayStart]);
|
|
487
|
+
const dayEndAbs = React5__namespace.useMemo(() => parseHHMM(dayEnd), [dayEnd]);
|
|
488
|
+
const dayMinutes = React5__namespace.useMemo(() => Math.max(0, dayEndAbs - dayStartAbs), [dayEndAbs, dayStartAbs]);
|
|
489
|
+
const gridHeight = React5__namespace.useMemo(() => TOP_PAD + dayMinutes * PX_PER_MIN + BOTTOM_PAD, [dayMinutes]);
|
|
490
|
+
const gridMinWidth = React5__namespace.useMemo(() => GUTTER_W + Math.max(resources.length, 1) * RESOURCE_MIN_W, [resources.length]);
|
|
491
|
+
const selectedISO = React5__namespace.useMemo(() => dayISO(selectedDate), [selectedDate]);
|
|
492
|
+
const renderAppts = React5__namespace.useMemo(() => {
|
|
331
493
|
return normalizeAppointments({
|
|
332
494
|
adapter,
|
|
333
495
|
appointments,
|
|
@@ -336,7 +498,7 @@ function useSchedulerBaseData({
|
|
|
336
498
|
selectedISO
|
|
337
499
|
});
|
|
338
500
|
}, [adapter, appointments, dayMinutes, dayStartAbs, selectedISO]);
|
|
339
|
-
const appointmentMap =
|
|
501
|
+
const appointmentMap = React5__namespace.useMemo(() => buildAppointmentMap(renderAppts), [renderAppts]);
|
|
340
502
|
return {
|
|
341
503
|
appointmentMap,
|
|
342
504
|
dayEndAbs,
|
|
@@ -357,10 +519,10 @@ function useSchedulerInteractions({
|
|
|
357
519
|
resources,
|
|
358
520
|
selectedDate
|
|
359
521
|
}) {
|
|
360
|
-
const [drag, setDrag] =
|
|
361
|
-
const colRefs =
|
|
362
|
-
const suppressClickRef =
|
|
363
|
-
const pointerToMin =
|
|
522
|
+
const [drag, setDrag] = React5__namespace.useState({ kind: "none" });
|
|
523
|
+
const colRefs = React5__namespace.useRef({});
|
|
524
|
+
const suppressClickRef = React5__namespace.useRef(false);
|
|
525
|
+
const pointerToMin = React5__namespace.useCallback(
|
|
364
526
|
(resourceId, clientY) => {
|
|
365
527
|
const col = colRefs.current[String(resourceId)];
|
|
366
528
|
if (!col) {
|
|
@@ -371,7 +533,7 @@ function useSchedulerInteractions({
|
|
|
371
533
|
},
|
|
372
534
|
[dayMinutes]
|
|
373
535
|
);
|
|
374
|
-
const pointerToResourceId =
|
|
536
|
+
const pointerToResourceId = React5__namespace.useCallback(
|
|
375
537
|
(clientX, fallbackResourceId) => {
|
|
376
538
|
for (const resource of resources) {
|
|
377
539
|
const col = colRefs.current[String(resource.id)];
|
|
@@ -387,14 +549,14 @@ function useSchedulerInteractions({
|
|
|
387
549
|
},
|
|
388
550
|
[resources]
|
|
389
551
|
);
|
|
390
|
-
const hasOverlap =
|
|
552
|
+
const hasOverlap = React5__namespace.useCallback(
|
|
391
553
|
(appointmentId, resourceId, startMin, endMin) => {
|
|
392
554
|
const events = renderAppts.filter((appointment) => appointment.resourceId === resourceId && appointment.id !== appointmentId);
|
|
393
555
|
return events.some((appointment) => overlaps(appointment, { startMin, endMin }));
|
|
394
556
|
},
|
|
395
557
|
[renderAppts]
|
|
396
558
|
);
|
|
397
|
-
const persistDrag =
|
|
559
|
+
const persistDrag = React5__namespace.useCallback(
|
|
398
560
|
async (state) => {
|
|
399
561
|
const target = appointmentMap.get(state.appointmentId);
|
|
400
562
|
if (!target) {
|
|
@@ -432,7 +594,7 @@ function useSchedulerInteractions({
|
|
|
432
594
|
},
|
|
433
595
|
[appointmentMap, dayStartAbs, hasOverlap, onAppointmentChange, onPersistMoveResize, selectedDate]
|
|
434
596
|
);
|
|
435
|
-
const onApptPointerDown =
|
|
597
|
+
const onApptPointerDown = React5__namespace.useCallback(
|
|
436
598
|
(event, appointment) => {
|
|
437
599
|
if (event.button !== 0) {
|
|
438
600
|
return;
|
|
@@ -453,7 +615,7 @@ function useSchedulerInteractions({
|
|
|
453
615
|
},
|
|
454
616
|
[pointerToMin]
|
|
455
617
|
);
|
|
456
|
-
const onResizePointerDown =
|
|
618
|
+
const onResizePointerDown = React5__namespace.useCallback((event, appointment) => {
|
|
457
619
|
if (event.button !== 0) {
|
|
458
620
|
return;
|
|
459
621
|
}
|
|
@@ -468,7 +630,7 @@ function useSchedulerInteractions({
|
|
|
468
630
|
endMin: appointment.endMin
|
|
469
631
|
});
|
|
470
632
|
}, []);
|
|
471
|
-
const onGlobalPointerMove =
|
|
633
|
+
const onGlobalPointerMove = React5__namespace.useCallback(
|
|
472
634
|
(event) => {
|
|
473
635
|
if (drag.kind === "none") {
|
|
474
636
|
return;
|
|
@@ -502,7 +664,7 @@ function useSchedulerInteractions({
|
|
|
502
664
|
},
|
|
503
665
|
[dayMinutes, drag, pointerToMin, pointerToResourceId]
|
|
504
666
|
);
|
|
505
|
-
const onGlobalPointerUp =
|
|
667
|
+
const onGlobalPointerUp = React5__namespace.useCallback(() => {
|
|
506
668
|
if (drag.kind === "none") {
|
|
507
669
|
return;
|
|
508
670
|
}
|
|
@@ -534,13 +696,13 @@ function useSchedulerPresentationData({
|
|
|
534
696
|
resources,
|
|
535
697
|
selectedDate
|
|
536
698
|
}) {
|
|
537
|
-
const effectiveAppts =
|
|
699
|
+
const effectiveAppts = React5__namespace.useMemo(() => {
|
|
538
700
|
return applyDragToAppointments(renderAppts, drag, selectedDate, dayStartAbs);
|
|
539
701
|
}, [dayStartAbs, drag, renderAppts, selectedDate]);
|
|
540
|
-
const laidOutByResource =
|
|
702
|
+
const laidOutByResource = React5__namespace.useMemo(() => {
|
|
541
703
|
return buildLaidOutByResource(resources, effectiveAppts);
|
|
542
704
|
}, [effectiveAppts, resources]);
|
|
543
|
-
const appointmentBgByResource =
|
|
705
|
+
const appointmentBgByResource = React5__namespace.useMemo(() => {
|
|
544
706
|
if (!getResourceAppointmentBackground) {
|
|
545
707
|
return /* @__PURE__ */ new Map();
|
|
546
708
|
}
|
|
@@ -551,7 +713,7 @@ function useSchedulerPresentationData({
|
|
|
551
713
|
toKey: (resource) => resource.id
|
|
552
714
|
});
|
|
553
715
|
}, [getResourceAppointmentBackground, resources]);
|
|
554
|
-
const appointmentColorTokenClassByResource =
|
|
716
|
+
const appointmentColorTokenClassByResource = React5__namespace.useMemo(() => {
|
|
555
717
|
if (!getResourceAppointmentColorToken) {
|
|
556
718
|
return /* @__PURE__ */ new Map();
|
|
557
719
|
}
|
|
@@ -569,7 +731,7 @@ function useSchedulerPresentationData({
|
|
|
569
731
|
toKey: (resource) => resource.id
|
|
570
732
|
});
|
|
571
733
|
}, [appointmentColorTokenClassMap, getResourceAppointmentColorToken, resources]);
|
|
572
|
-
const appointmentAppearanceByResource =
|
|
734
|
+
const appointmentAppearanceByResource = React5__namespace.useMemo(() => {
|
|
573
735
|
return buildAppointmentAppearanceByResource(
|
|
574
736
|
resources,
|
|
575
737
|
resourceAppointmentClassMap,
|
|
@@ -606,6 +768,7 @@ function Scheduler({
|
|
|
606
768
|
getResourceAppointmentColorToken,
|
|
607
769
|
appointmentColorTokenClassMap,
|
|
608
770
|
getResourceAppointmentBackground,
|
|
771
|
+
renderToolbar,
|
|
609
772
|
renderDatePicker,
|
|
610
773
|
prevButtonLabel = "Prev",
|
|
611
774
|
nextButtonLabel = "Next",
|
|
@@ -662,6 +825,22 @@ function Scheduler({
|
|
|
662
825
|
selectedDate
|
|
663
826
|
});
|
|
664
827
|
const defaultResourceHeaderRenderer = (resource) => defaultRenderResourceHeader({ resource });
|
|
828
|
+
const goToPreviousDay = () => onSelectedDateChange(shiftDays(selectedDate, -1));
|
|
829
|
+
const goToNextDay = () => onSelectedDateChange(shiftDays(selectedDate, 1));
|
|
830
|
+
const defaultDatePicker = (renderDatePicker ?? defaultRenderDatePicker)({ selectedDate, onSelectedDateChange });
|
|
831
|
+
const toolbar = renderToolbar ? renderToolbar({
|
|
832
|
+
selectedDate,
|
|
833
|
+
onSelectedDateChange,
|
|
834
|
+
goToPreviousDay,
|
|
835
|
+
goToNextDay,
|
|
836
|
+
defaultDatePicker
|
|
837
|
+
}) : defaultRenderToolbar({
|
|
838
|
+
goToPreviousDay,
|
|
839
|
+
goToNextDay,
|
|
840
|
+
defaultDatePicker,
|
|
841
|
+
prevButtonLabel,
|
|
842
|
+
nextButtonLabel
|
|
843
|
+
});
|
|
665
844
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
666
845
|
"section",
|
|
667
846
|
{
|
|
@@ -670,27 +849,7 @@ function Scheduler({
|
|
|
670
849
|
onPointerUp: onGlobalPointerUp,
|
|
671
850
|
onPointerCancel: onGlobalPointerUp,
|
|
672
851
|
children: [
|
|
673
|
-
|
|
674
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
675
|
-
"button",
|
|
676
|
-
{
|
|
677
|
-
className: "rfs-btn border-border bg-card text-foreground hover:bg-muted",
|
|
678
|
-
type: "button",
|
|
679
|
-
onClick: () => onSelectedDateChange(shiftDays(selectedDate, -1)),
|
|
680
|
-
children: prevButtonLabel
|
|
681
|
-
}
|
|
682
|
-
),
|
|
683
|
-
(renderDatePicker ?? defaultRenderDatePicker)({ selectedDate, onSelectedDateChange }),
|
|
684
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
685
|
-
"button",
|
|
686
|
-
{
|
|
687
|
-
className: "rfs-btn border-border bg-card text-foreground hover:bg-muted",
|
|
688
|
-
type: "button",
|
|
689
|
-
onClick: () => onSelectedDateChange(shiftDays(selectedDate, 1)),
|
|
690
|
-
children: nextButtonLabel
|
|
691
|
-
}
|
|
692
|
-
)
|
|
693
|
-
] }),
|
|
852
|
+
toolbar,
|
|
694
853
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rfs-shell border-border bg-card", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: gridMinWidth }, children: [
|
|
695
854
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rfs-header border-border bg-card", children: [
|
|
696
855
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rfs-gutter border-border", style: { width: GUTTER_W } }),
|