@super-calendar/dom 2.4.0 → 2.5.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.d.mts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +283 -142
- package/dist/index.mjs +283 -142
- package/package.json +2 -2
- package/src/ResourceTimeline.tsx +192 -3
package/dist/index.d.mts
CHANGED
|
@@ -627,7 +627,7 @@ declare function DateRangePicker({ value, onChange, formatLabel, weekStartsOn, h
|
|
|
627
627
|
* Styleable parts of {@link ResourceTimeline}. Pass a class or inline style per
|
|
628
628
|
* slot via the `classNames` / `styles` props.
|
|
629
629
|
*/
|
|
630
|
-
type ResourceTimelineSlot = "header" | "corner" | "timeAxis" | "hourTick" | "resourceLabel" | "row" | "track" | "gridLines" | "event" | "eventBox";
|
|
630
|
+
type ResourceTimelineSlot = "header" | "corner" | "timeAxis" | "hourTick" | "resourceLabel" | "row" | "track" | "gridLines" | "businessHours" | "event" | "eventBox" | "createGhost";
|
|
631
631
|
/** A schedulable lane (room, person, machine) that events are grouped under. */
|
|
632
632
|
interface Resource {
|
|
633
633
|
/** Stable id events reference via their `resourceId`. */
|
|
@@ -689,6 +689,19 @@ interface ResourceTimelineProps<T = unknown> extends SlotStyleProps<ResourceTime
|
|
|
689
689
|
* proposed new start/end. Return `false` to reject the drop (it snaps back).
|
|
690
690
|
*/
|
|
691
691
|
onDragEvent?: (event: CalendarEvent$1<T>, start: Date, end: Date) => void | boolean;
|
|
692
|
+
/** Tap empty lane space; called with the snapped time and the lane's resource. */
|
|
693
|
+
onPressCell?: (at: Date, resource: Resource) => void;
|
|
694
|
+
/** Drag empty lane space to create; called with the swept start/end and the lane's resource. */
|
|
695
|
+
onCreateEvent?: (start: Date, end: Date, resource: Resource) => void;
|
|
696
|
+
/**
|
|
697
|
+
* Shade the hours outside this window, per lane. Same shape as the Calendar's
|
|
698
|
+
* `businessHours` plus the lane's resource, so per-resource opening hours work
|
|
699
|
+
* (return `null` for a fully closed lane). A date-only function is accepted.
|
|
700
|
+
*/
|
|
701
|
+
businessHours?: (date: Date, resource: Resource) => {
|
|
702
|
+
start: number;
|
|
703
|
+
end: number;
|
|
704
|
+
} | null;
|
|
692
705
|
/** Snap dragged events to this many minutes (default 15). */
|
|
693
706
|
dragStepMinutes?: number;
|
|
694
707
|
/** Class applied to the root element. */
|
|
@@ -712,6 +725,6 @@ interface ResourceTimelineProps<T = unknown> extends SlotStyleProps<ResourceTime
|
|
|
712
725
|
* />
|
|
713
726
|
* ```
|
|
714
727
|
*/
|
|
715
|
-
declare function ResourceTimeline<T = unknown>({ date, orientation, resources, events, resourceId, startHour, endHour, hourWidth, hourHeight, rowHeight, labelWidth, ampm, theme: themeOverrides, renderEvent, onPressEvent, onDragEvent, dragStepMinutes, className, style, classNames, styles }: ResourceTimelineProps<T>): ReactElement;
|
|
728
|
+
declare function ResourceTimeline<T = unknown>({ date, orientation, resources, events, resourceId, startHour, endHour, hourWidth, hourHeight, rowHeight, labelWidth, ampm, theme: themeOverrides, renderEvent, onPressEvent, onDragEvent, onPressCell, onCreateEvent, businessHours, dragStepMinutes, className, style, classNames, styles }: ResourceTimelineProps<T>): ReactElement;
|
|
716
729
|
//#endregion
|
|
717
730
|
export { Agenda, type AgendaProps, type AgendaSlot, type BusinessHours, Calendar, type CalendarEvent, type CalendarMode, type CalendarProps, type CalendarSlot, DatePicker, type DatePickerProps, type DatePickerSlot, type DateRange, DateRangePicker, type DateRangePickerProps, type DateSelectionConstraints, type DaySelectionState, type DomAgendaEvent, type DomAgendaEventArgs, type DomCalendarTheme, type DomMonthEvent, type DomMonthEventArgs, type DomRenderEvent, type DomRenderEventArgs, type EventAccessibilityLabelContext, type EventAccessibilityLabeler, type ICalEvent, type ICalendarEvent, type MonthGrid, type MonthGridDay, type MonthGridWeek, type MonthGridWeekday, MonthList, type MonthListProps, type MonthListSlot, MonthView, type MonthViewProps, type MonthViewSlot, type PositionedEvent, type RecurrenceFrequency, type RecurrenceRule, type ResolvedSlot, type Resource, type ResourceEventArgs, ResourceTimeline, type ResourceTimelineProps, type ResourceTimelineSlot, type SlotDefault, type SlotStyleProps, TimeGrid, type TimeGridMode, type TimeGridProps, type TimeGridSlot, type ToICalendarOptions, type UseDateRangeOptions, type UseMonthGridOptions, type WeekStartsOn, type WeekdayFormat, buildMonthGrid, darkDomTheme, daySelectionState, defaultDomTheme, expandRecurringEvents, getViewDays, isAllDayEvent, isDateSelectable, isRangeEndpoint, isWithinDateRange, layoutDayEvents, mergeDomTheme, nextDateRange, parseICalendar, toICalendar, useDateRange, useMonthGrid, weekdayFormatToken };
|
package/dist/index.d.ts
CHANGED
|
@@ -627,7 +627,7 @@ declare function DateRangePicker({ value, onChange, formatLabel, weekStartsOn, h
|
|
|
627
627
|
* Styleable parts of {@link ResourceTimeline}. Pass a class or inline style per
|
|
628
628
|
* slot via the `classNames` / `styles` props.
|
|
629
629
|
*/
|
|
630
|
-
type ResourceTimelineSlot = "header" | "corner" | "timeAxis" | "hourTick" | "resourceLabel" | "row" | "track" | "gridLines" | "event" | "eventBox";
|
|
630
|
+
type ResourceTimelineSlot = "header" | "corner" | "timeAxis" | "hourTick" | "resourceLabel" | "row" | "track" | "gridLines" | "businessHours" | "event" | "eventBox" | "createGhost";
|
|
631
631
|
/** A schedulable lane (room, person, machine) that events are grouped under. */
|
|
632
632
|
interface Resource {
|
|
633
633
|
/** Stable id events reference via their `resourceId`. */
|
|
@@ -689,6 +689,19 @@ interface ResourceTimelineProps<T = unknown> extends SlotStyleProps<ResourceTime
|
|
|
689
689
|
* proposed new start/end. Return `false` to reject the drop (it snaps back).
|
|
690
690
|
*/
|
|
691
691
|
onDragEvent?: (event: CalendarEvent$1<T>, start: Date, end: Date) => void | boolean;
|
|
692
|
+
/** Tap empty lane space; called with the snapped time and the lane's resource. */
|
|
693
|
+
onPressCell?: (at: Date, resource: Resource) => void;
|
|
694
|
+
/** Drag empty lane space to create; called with the swept start/end and the lane's resource. */
|
|
695
|
+
onCreateEvent?: (start: Date, end: Date, resource: Resource) => void;
|
|
696
|
+
/**
|
|
697
|
+
* Shade the hours outside this window, per lane. Same shape as the Calendar's
|
|
698
|
+
* `businessHours` plus the lane's resource, so per-resource opening hours work
|
|
699
|
+
* (return `null` for a fully closed lane). A date-only function is accepted.
|
|
700
|
+
*/
|
|
701
|
+
businessHours?: (date: Date, resource: Resource) => {
|
|
702
|
+
start: number;
|
|
703
|
+
end: number;
|
|
704
|
+
} | null;
|
|
692
705
|
/** Snap dragged events to this many minutes (default 15). */
|
|
693
706
|
dragStepMinutes?: number;
|
|
694
707
|
/** Class applied to the root element. */
|
|
@@ -712,6 +725,6 @@ interface ResourceTimelineProps<T = unknown> extends SlotStyleProps<ResourceTime
|
|
|
712
725
|
* />
|
|
713
726
|
* ```
|
|
714
727
|
*/
|
|
715
|
-
declare function ResourceTimeline<T = unknown>({ date, orientation, resources, events, resourceId, startHour, endHour, hourWidth, hourHeight, rowHeight, labelWidth, ampm, theme: themeOverrides, renderEvent, onPressEvent, onDragEvent, dragStepMinutes, className, style, classNames, styles }: ResourceTimelineProps<T>): ReactElement;
|
|
728
|
+
declare function ResourceTimeline<T = unknown>({ date, orientation, resources, events, resourceId, startHour, endHour, hourWidth, hourHeight, rowHeight, labelWidth, ampm, theme: themeOverrides, renderEvent, onPressEvent, onDragEvent, onPressCell, onCreateEvent, businessHours, dragStepMinutes, className, style, classNames, styles }: ResourceTimelineProps<T>): ReactElement;
|
|
716
729
|
//#endregion
|
|
717
730
|
export { Agenda, type AgendaProps, type AgendaSlot, type BusinessHours, Calendar, type CalendarEvent, type CalendarMode, type CalendarProps, type CalendarSlot, DatePicker, type DatePickerProps, type DatePickerSlot, type DateRange, DateRangePicker, type DateRangePickerProps, type DateSelectionConstraints, type DaySelectionState, type DomAgendaEvent, type DomAgendaEventArgs, type DomCalendarTheme, type DomMonthEvent, type DomMonthEventArgs, type DomRenderEvent, type DomRenderEventArgs, type EventAccessibilityLabelContext, type EventAccessibilityLabeler, type ICalEvent, type ICalendarEvent, type MonthGrid, type MonthGridDay, type MonthGridWeek, type MonthGridWeekday, MonthList, type MonthListProps, type MonthListSlot, MonthView, type MonthViewProps, type MonthViewSlot, type PositionedEvent, type RecurrenceFrequency, type RecurrenceRule, type ResolvedSlot, type Resource, type ResourceEventArgs, ResourceTimeline, type ResourceTimelineProps, type ResourceTimelineSlot, type SlotDefault, type SlotStyleProps, TimeGrid, type TimeGridMode, type TimeGridProps, type TimeGridSlot, type ToICalendarOptions, type UseDateRangeOptions, type UseMonthGridOptions, type WeekStartsOn, type WeekdayFormat, buildMonthGrid, darkDomTheme, daySelectionState, defaultDomTheme, expandRecurringEvents, getViewDays, isAllDayEvent, isDateSelectable, isRangeEndpoint, isWithinDateRange, layoutDayEvents, mergeDomTheme, nextDateRange, parseICalendar, toICalendar, useDateRange, useMonthGrid, weekdayFormatToken };
|
package/dist/index.js
CHANGED
|
@@ -2092,7 +2092,7 @@ function DefaultBar({ event, height, boxProps, theme }) {
|
|
|
2092
2092
|
* />
|
|
2093
2093
|
* ```
|
|
2094
2094
|
*/
|
|
2095
|
-
function ResourceTimeline({ date, orientation = "horizontal", resources, events, resourceId = (event) => event.resourceId, startHour = 0, endHour = 24, hourWidth = 80, hourHeight = 48, rowHeight = 56, labelWidth = 140, ampm = false, theme: themeOverrides, renderEvent, onPressEvent, onDragEvent, dragStepMinutes = 15, className, style, classNames, styles }) {
|
|
2095
|
+
function ResourceTimeline({ date, orientation = "horizontal", resources, events, resourceId = (event) => event.resourceId, startHour = 0, endHour = 24, hourWidth = 80, hourHeight = 48, rowHeight = 56, labelWidth = 140, ampm = false, theme: themeOverrides, renderEvent, onPressEvent, onDragEvent, onPressCell, onCreateEvent, businessHours, dragStepMinutes = 15, className, style, classNames, styles }) {
|
|
2096
2096
|
const theme = (0, react.useMemo)(() => mergeDomTheme(themeOverrides), [themeOverrides]);
|
|
2097
2097
|
const slot = createSlots({
|
|
2098
2098
|
classNames,
|
|
@@ -2171,6 +2171,69 @@ function ResourceTimeline({ date, orientation = "horizontal", resources, events,
|
|
|
2171
2171
|
applyDrag(null);
|
|
2172
2172
|
origin.current = null;
|
|
2173
2173
|
};
|
|
2174
|
+
const [createBox, setCreateBox] = (0, react.useState)(null);
|
|
2175
|
+
const createOrigin = (0, react.useRef)(null);
|
|
2176
|
+
const cellEnabled = !!onPressCell || !!onCreateEvent;
|
|
2177
|
+
const pxAlongAxis = (el, e) => {
|
|
2178
|
+
const rect = el.getBoundingClientRect();
|
|
2179
|
+
return vertical ? e.clientY - rect.top : e.clientX - rect.left;
|
|
2180
|
+
};
|
|
2181
|
+
const beginCreate = (e, resource) => {
|
|
2182
|
+
if (!cellEnabled || e.pointerType === "touch") return;
|
|
2183
|
+
if (e.target !== e.currentTarget || e.button > 0) return;
|
|
2184
|
+
const el = e.currentTarget;
|
|
2185
|
+
const startPx = pxAlongAxis(el, e);
|
|
2186
|
+
try {
|
|
2187
|
+
el.setPointerCapture?.(e.pointerId);
|
|
2188
|
+
} catch {}
|
|
2189
|
+
createOrigin.current = {
|
|
2190
|
+
el,
|
|
2191
|
+
resource,
|
|
2192
|
+
startPx
|
|
2193
|
+
};
|
|
2194
|
+
if (onCreateEvent) setCreateBox({
|
|
2195
|
+
resourceKey: resource.id,
|
|
2196
|
+
startPx,
|
|
2197
|
+
curPx: startPx
|
|
2198
|
+
});
|
|
2199
|
+
};
|
|
2200
|
+
const moveCreate = (e) => {
|
|
2201
|
+
const o = createOrigin.current;
|
|
2202
|
+
if (!o || !onCreateEvent) return;
|
|
2203
|
+
setCreateBox({
|
|
2204
|
+
resourceKey: o.resource.id,
|
|
2205
|
+
startPx: o.startPx,
|
|
2206
|
+
curPx: pxAlongAxis(o.el, e)
|
|
2207
|
+
});
|
|
2208
|
+
};
|
|
2209
|
+
const endCreate = (e) => {
|
|
2210
|
+
const o = createOrigin.current;
|
|
2211
|
+
if (!o) return;
|
|
2212
|
+
try {
|
|
2213
|
+
o.el.releasePointerCapture?.(e.pointerId);
|
|
2214
|
+
} catch {}
|
|
2215
|
+
const endPx = pxAlongAxis(o.el, e);
|
|
2216
|
+
if (Math.abs(endPx - o.startPx) > 4 && onCreateEvent) {
|
|
2217
|
+
const range = (0, _super_calendar_core.cellRangeFromDrag)(date, o.startPx, endPx, hourSize, startHour, dragStepMinutes);
|
|
2218
|
+
if (range) onCreateEvent(range.start, range.end, o.resource);
|
|
2219
|
+
} else if (onPressCell) {
|
|
2220
|
+
const at = (0, _super_calendar_core.cellRangeFromDrag)(date, o.startPx, o.startPx, hourSize, startHour, dragStepMinutes);
|
|
2221
|
+
if (at) onPressCell(at.start, o.resource);
|
|
2222
|
+
}
|
|
2223
|
+
createOrigin.current = null;
|
|
2224
|
+
setCreateBox(null);
|
|
2225
|
+
};
|
|
2226
|
+
const cancelCreate = () => {
|
|
2227
|
+
createOrigin.current = null;
|
|
2228
|
+
setCreateBox(null);
|
|
2229
|
+
};
|
|
2230
|
+
const trackInteractionProps = (resource) => cellEnabled ? {
|
|
2231
|
+
onPointerDown: (e) => beginCreate(e, resource),
|
|
2232
|
+
onPointerMove: moveCreate,
|
|
2233
|
+
onPointerUp: endCreate,
|
|
2234
|
+
onPointerCancel: cancelCreate
|
|
2235
|
+
} : null;
|
|
2236
|
+
const bandsFor = (resource) => businessHours ? (0, _super_calendar_core.closedHourBands)(date, (d) => businessHours(d, resource), startHour, endHour) : [];
|
|
2174
2237
|
const hours = (0, react.useMemo)(() => Array.from({ length: Math.max(0, endHour - startHour) }, (_, i) => startHour + i), [startHour, endHour]);
|
|
2175
2238
|
const trackWidth = (endHour - startHour) * hourWidth;
|
|
2176
2239
|
const byResource = (0, react.useMemo)(() => {
|
|
@@ -2267,85 +2330,124 @@ function ResourceTimeline({ date, orientation = "horizontal", resources, events,
|
|
|
2267
2330
|
themed: { borderLeft: `1px solid ${theme.gridLine}` }
|
|
2268
2331
|
}),
|
|
2269
2332
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2333
|
+
...trackInteractionProps(resource),
|
|
2270
2334
|
...slot("track", { base: {
|
|
2271
2335
|
position: "relative",
|
|
2272
2336
|
height: trackHeight
|
|
2273
2337
|
} }),
|
|
2274
|
-
children: [
|
|
2275
|
-
"
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
inset: 0,
|
|
2280
|
-
pointerEvents: "none"
|
|
2281
|
-
},
|
|
2282
|
-
themed: { backgroundImage: vGridLines }
|
|
2283
|
-
})
|
|
2284
|
-
}), positioned.map((pe, idx) => {
|
|
2285
|
-
const key = `${resource.id}:${idx}`;
|
|
2286
|
-
const active = drag?.key === key ? drag : null;
|
|
2287
|
-
const startH = active ? active.startHours : pe.startHours;
|
|
2288
|
-
const durH = active ? active.durationHours : pe.durationHours;
|
|
2289
|
-
const top = clamp(startH - startHour, 0, endHour - startHour) * hourHeight;
|
|
2290
|
-
const bottom = clamp(startH + durH - startHour, 0, endHour - startHour) * hourHeight;
|
|
2291
|
-
const height = Math.max(bottom - top, 2);
|
|
2292
|
-
const lanePct = 100 / pe.columns;
|
|
2293
|
-
const onPress = () => onPressEvent?.(pe.event);
|
|
2294
|
-
const args = {
|
|
2295
|
-
event: pe.event,
|
|
2296
|
-
width: 0,
|
|
2297
|
-
height,
|
|
2298
|
-
onPress
|
|
2299
|
-
};
|
|
2300
|
-
const draggable = !!onDragEvent;
|
|
2301
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2302
|
-
type: "button",
|
|
2303
|
-
onClick: draggable ? void 0 : onPress,
|
|
2304
|
-
"aria-label": pe.event.title,
|
|
2305
|
-
...dataState({ "data-dragging": !!active }),
|
|
2306
|
-
onPointerDown: draggable ? (e) => beginDrag(e, key, "move", pe.startHours, pe.durationHours) : void 0,
|
|
2307
|
-
onPointerMove: draggable ? moveDrag : void 0,
|
|
2308
|
-
onPointerUp: draggable ? (e) => endDrag(e, pe.event, onPress) : void 0,
|
|
2309
|
-
onPointerCancel: draggable ? cancelDrag : void 0,
|
|
2310
|
-
...slot("event", { base: {
|
|
2311
|
-
position: "absolute",
|
|
2312
|
-
top,
|
|
2313
|
-
height,
|
|
2314
|
-
left: `${pe.column * lanePct}%`,
|
|
2315
|
-
width: `${lanePct}%`,
|
|
2316
|
-
padding: 1,
|
|
2317
|
-
border: "none",
|
|
2318
|
-
background: "transparent",
|
|
2319
|
-
cursor: draggable ? "grab" : "pointer",
|
|
2320
|
-
touchAction: draggable ? "none" : "auto",
|
|
2321
|
-
font: "inherit",
|
|
2322
|
-
textAlign: "left",
|
|
2323
|
-
boxSizing: "border-box",
|
|
2324
|
-
zIndex: active ? 3 : 1,
|
|
2325
|
-
opacity: active ? .85 : 1
|
|
2326
|
-
} }),
|
|
2327
|
-
children: [Renderer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Renderer, { ...args }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultBar, {
|
|
2328
|
-
...args,
|
|
2329
|
-
theme,
|
|
2330
|
-
boxProps: slot("eventBox")
|
|
2331
|
-
}), draggable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2332
|
-
"aria-hidden": true,
|
|
2333
|
-
onPointerDown: (e) => {
|
|
2334
|
-
e.stopPropagation();
|
|
2335
|
-
beginDrag(e, key, "resize", pe.startHours, pe.durationHours);
|
|
2336
|
-
},
|
|
2337
|
-
style: {
|
|
2338
|
+
children: [
|
|
2339
|
+
bandsFor(resource).map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2340
|
+
"aria-hidden": true,
|
|
2341
|
+
...slot("businessHours", {
|
|
2342
|
+
base: {
|
|
2338
2343
|
position: "absolute",
|
|
2339
2344
|
left: 0,
|
|
2340
2345
|
right: 0,
|
|
2341
|
-
|
|
2342
|
-
height:
|
|
2343
|
-
|
|
2344
|
-
|
|
2346
|
+
top: (b.start - startHour) * hourHeight,
|
|
2347
|
+
height: (b.end - b.start) * hourHeight,
|
|
2348
|
+
pointerEvents: "none",
|
|
2349
|
+
zIndex: 0
|
|
2350
|
+
},
|
|
2351
|
+
themed: { background: theme.outsideHoursBackground }
|
|
2352
|
+
})
|
|
2353
|
+
}, b.start)),
|
|
2354
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2355
|
+
"aria-hidden": true,
|
|
2356
|
+
...slot("gridLines", {
|
|
2357
|
+
base: {
|
|
2358
|
+
position: "absolute",
|
|
2359
|
+
inset: 0,
|
|
2360
|
+
pointerEvents: "none"
|
|
2361
|
+
},
|
|
2362
|
+
themed: { backgroundImage: vGridLines }
|
|
2363
|
+
})
|
|
2364
|
+
}),
|
|
2365
|
+
positioned.map((pe, idx) => {
|
|
2366
|
+
const key = `${resource.id}:${idx}`;
|
|
2367
|
+
const active = drag?.key === key ? drag : null;
|
|
2368
|
+
const startH = active ? active.startHours : pe.startHours;
|
|
2369
|
+
const durH = active ? active.durationHours : pe.durationHours;
|
|
2370
|
+
const top = clamp(startH - startHour, 0, endHour - startHour) * hourHeight;
|
|
2371
|
+
const bottom = clamp(startH + durH - startHour, 0, endHour - startHour) * hourHeight;
|
|
2372
|
+
const height = Math.max(bottom - top, 2);
|
|
2373
|
+
const lanePct = 100 / pe.columns;
|
|
2374
|
+
const onPress = () => onPressEvent?.(pe.event);
|
|
2375
|
+
const args = {
|
|
2376
|
+
event: pe.event,
|
|
2377
|
+
width: 0,
|
|
2378
|
+
height,
|
|
2379
|
+
onPress
|
|
2380
|
+
};
|
|
2381
|
+
const draggable = !!onDragEvent;
|
|
2382
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2383
|
+
type: "button",
|
|
2384
|
+
onClick: draggable ? void 0 : onPress,
|
|
2385
|
+
"aria-label": pe.event.title,
|
|
2386
|
+
...dataState({ "data-dragging": !!active }),
|
|
2387
|
+
onPointerDown: draggable ? (e) => beginDrag(e, key, "move", pe.startHours, pe.durationHours) : void 0,
|
|
2388
|
+
onPointerMove: draggable ? moveDrag : void 0,
|
|
2389
|
+
onPointerUp: draggable ? (e) => endDrag(e, pe.event, onPress) : void 0,
|
|
2390
|
+
onPointerCancel: draggable ? cancelDrag : void 0,
|
|
2391
|
+
...slot("event", { base: {
|
|
2392
|
+
position: "absolute",
|
|
2393
|
+
top,
|
|
2394
|
+
height,
|
|
2395
|
+
left: `${pe.column * lanePct}%`,
|
|
2396
|
+
width: `${lanePct}%`,
|
|
2397
|
+
padding: 1,
|
|
2398
|
+
border: "none",
|
|
2399
|
+
background: "transparent",
|
|
2400
|
+
cursor: draggable ? "grab" : "pointer",
|
|
2401
|
+
touchAction: draggable ? "none" : "auto",
|
|
2402
|
+
font: "inherit",
|
|
2403
|
+
textAlign: "left",
|
|
2404
|
+
boxSizing: "border-box",
|
|
2405
|
+
zIndex: active ? 3 : 1,
|
|
2406
|
+
opacity: active ? .85 : 1
|
|
2407
|
+
} }),
|
|
2408
|
+
children: [Renderer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Renderer, { ...args }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultBar, {
|
|
2409
|
+
...args,
|
|
2410
|
+
theme,
|
|
2411
|
+
boxProps: slot("eventBox")
|
|
2412
|
+
}), draggable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2413
|
+
"aria-hidden": true,
|
|
2414
|
+
onPointerDown: (e) => {
|
|
2415
|
+
e.stopPropagation();
|
|
2416
|
+
beginDrag(e, key, "resize", pe.startHours, pe.durationHours);
|
|
2417
|
+
},
|
|
2418
|
+
style: {
|
|
2419
|
+
position: "absolute",
|
|
2420
|
+
left: 0,
|
|
2421
|
+
right: 0,
|
|
2422
|
+
bottom: 0,
|
|
2423
|
+
height: 8,
|
|
2424
|
+
cursor: "ns-resize",
|
|
2425
|
+
touchAction: "none"
|
|
2426
|
+
}
|
|
2427
|
+
}) : null]
|
|
2428
|
+
}, idx);
|
|
2429
|
+
}),
|
|
2430
|
+
createBox?.resourceKey === resource.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2431
|
+
"aria-hidden": true,
|
|
2432
|
+
...slot("createGhost", {
|
|
2433
|
+
base: {
|
|
2434
|
+
position: "absolute",
|
|
2435
|
+
left: 1,
|
|
2436
|
+
right: 1,
|
|
2437
|
+
top: Math.min(createBox.startPx, createBox.curPx),
|
|
2438
|
+
height: Math.max(Math.abs(createBox.curPx - createBox.startPx), 2),
|
|
2439
|
+
opacity: .7,
|
|
2440
|
+
pointerEvents: "none",
|
|
2441
|
+
zIndex: 2
|
|
2442
|
+
},
|
|
2443
|
+
themed: {
|
|
2444
|
+
background: theme.rangeBackground,
|
|
2445
|
+
border: `1px solid ${theme.selectedBackground}`,
|
|
2446
|
+
borderRadius: 6
|
|
2345
2447
|
}
|
|
2346
|
-
})
|
|
2347
|
-
}
|
|
2348
|
-
|
|
2448
|
+
})
|
|
2449
|
+
}) : null
|
|
2450
|
+
]
|
|
2349
2451
|
})
|
|
2350
2452
|
}, resource.id);
|
|
2351
2453
|
})]
|
|
@@ -2420,84 +2522,123 @@ function ResourceTimeline({ date, orientation = "horizontal", resources, events,
|
|
|
2420
2522
|
}),
|
|
2421
2523
|
children: resource.title ?? resource.id
|
|
2422
2524
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2525
|
+
...trackInteractionProps(resource),
|
|
2423
2526
|
...slot("track", { base: {
|
|
2424
2527
|
position: "relative",
|
|
2425
2528
|
width: trackWidth
|
|
2426
2529
|
} }),
|
|
2427
|
-
children: [
|
|
2428
|
-
"
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
inset: 0,
|
|
2433
|
-
pointerEvents: "none"
|
|
2434
|
-
},
|
|
2435
|
-
themed: { backgroundImage: gridLines }
|
|
2436
|
-
})
|
|
2437
|
-
}), positioned.map((pe, idx) => {
|
|
2438
|
-
const key = `${resource.id}:${idx}`;
|
|
2439
|
-
const active = drag?.key === key ? drag : null;
|
|
2440
|
-
const startH = active ? active.startHours : pe.startHours;
|
|
2441
|
-
const durH = active ? active.durationHours : pe.durationHours;
|
|
2442
|
-
const left = clamp(startH - startHour, 0, endHour - startHour) * hourWidth;
|
|
2443
|
-
const right = clamp(startH + durH - startHour, 0, endHour - startHour) * hourWidth;
|
|
2444
|
-
const width = Math.max(right - left, 2);
|
|
2445
|
-
const laneHeight = rowHeight / pe.columns;
|
|
2446
|
-
const onPress = () => onPressEvent?.(pe.event);
|
|
2447
|
-
const args = {
|
|
2448
|
-
event: pe.event,
|
|
2449
|
-
width,
|
|
2450
|
-
onPress
|
|
2451
|
-
};
|
|
2452
|
-
const draggable = !!onDragEvent;
|
|
2453
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2454
|
-
type: "button",
|
|
2455
|
-
onClick: draggable ? void 0 : onPress,
|
|
2456
|
-
"aria-label": pe.event.title,
|
|
2457
|
-
...dataState({ "data-dragging": !!active }),
|
|
2458
|
-
onPointerDown: draggable ? (e) => beginDrag(e, key, "move", pe.startHours, pe.durationHours) : void 0,
|
|
2459
|
-
onPointerMove: draggable ? moveDrag : void 0,
|
|
2460
|
-
onPointerUp: draggable ? (e) => endDrag(e, pe.event, onPress) : void 0,
|
|
2461
|
-
onPointerCancel: draggable ? cancelDrag : void 0,
|
|
2462
|
-
...slot("event", { base: {
|
|
2463
|
-
position: "absolute",
|
|
2464
|
-
left,
|
|
2465
|
-
width,
|
|
2466
|
-
top: pe.column * laneHeight,
|
|
2467
|
-
height: laneHeight,
|
|
2468
|
-
padding: 1,
|
|
2469
|
-
border: "none",
|
|
2470
|
-
background: "transparent",
|
|
2471
|
-
cursor: draggable ? "grab" : "pointer",
|
|
2472
|
-
touchAction: draggable ? "none" : "auto",
|
|
2473
|
-
font: "inherit",
|
|
2474
|
-
textAlign: "left",
|
|
2475
|
-
boxSizing: "border-box",
|
|
2476
|
-
zIndex: active ? 3 : 1,
|
|
2477
|
-
opacity: active ? .85 : 1
|
|
2478
|
-
} }),
|
|
2479
|
-
children: [Renderer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Renderer, { ...args }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultBar, {
|
|
2480
|
-
...args,
|
|
2481
|
-
theme,
|
|
2482
|
-
boxProps: slot("eventBox")
|
|
2483
|
-
}), draggable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2484
|
-
"aria-hidden": true,
|
|
2485
|
-
onPointerDown: (e) => {
|
|
2486
|
-
e.stopPropagation();
|
|
2487
|
-
beginDrag(e, key, "resize", pe.startHours, pe.durationHours);
|
|
2488
|
-
},
|
|
2489
|
-
style: {
|
|
2530
|
+
children: [
|
|
2531
|
+
bandsFor(resource).map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2532
|
+
"aria-hidden": true,
|
|
2533
|
+
...slot("businessHours", {
|
|
2534
|
+
base: {
|
|
2490
2535
|
position: "absolute",
|
|
2491
2536
|
top: 0,
|
|
2492
2537
|
bottom: 0,
|
|
2493
|
-
|
|
2494
|
-
width:
|
|
2495
|
-
|
|
2496
|
-
|
|
2538
|
+
left: (b.start - startHour) * hourWidth,
|
|
2539
|
+
width: (b.end - b.start) * hourWidth,
|
|
2540
|
+
pointerEvents: "none",
|
|
2541
|
+
zIndex: 0
|
|
2542
|
+
},
|
|
2543
|
+
themed: { background: theme.outsideHoursBackground }
|
|
2544
|
+
})
|
|
2545
|
+
}, b.start)),
|
|
2546
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2547
|
+
"aria-hidden": true,
|
|
2548
|
+
...slot("gridLines", {
|
|
2549
|
+
base: {
|
|
2550
|
+
position: "absolute",
|
|
2551
|
+
inset: 0,
|
|
2552
|
+
pointerEvents: "none"
|
|
2553
|
+
},
|
|
2554
|
+
themed: { backgroundImage: gridLines }
|
|
2555
|
+
})
|
|
2556
|
+
}),
|
|
2557
|
+
positioned.map((pe, idx) => {
|
|
2558
|
+
const key = `${resource.id}:${idx}`;
|
|
2559
|
+
const active = drag?.key === key ? drag : null;
|
|
2560
|
+
const startH = active ? active.startHours : pe.startHours;
|
|
2561
|
+
const durH = active ? active.durationHours : pe.durationHours;
|
|
2562
|
+
const left = clamp(startH - startHour, 0, endHour - startHour) * hourWidth;
|
|
2563
|
+
const right = clamp(startH + durH - startHour, 0, endHour - startHour) * hourWidth;
|
|
2564
|
+
const width = Math.max(right - left, 2);
|
|
2565
|
+
const laneHeight = rowHeight / pe.columns;
|
|
2566
|
+
const onPress = () => onPressEvent?.(pe.event);
|
|
2567
|
+
const args = {
|
|
2568
|
+
event: pe.event,
|
|
2569
|
+
width,
|
|
2570
|
+
onPress
|
|
2571
|
+
};
|
|
2572
|
+
const draggable = !!onDragEvent;
|
|
2573
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2574
|
+
type: "button",
|
|
2575
|
+
onClick: draggable ? void 0 : onPress,
|
|
2576
|
+
"aria-label": pe.event.title,
|
|
2577
|
+
...dataState({ "data-dragging": !!active }),
|
|
2578
|
+
onPointerDown: draggable ? (e) => beginDrag(e, key, "move", pe.startHours, pe.durationHours) : void 0,
|
|
2579
|
+
onPointerMove: draggable ? moveDrag : void 0,
|
|
2580
|
+
onPointerUp: draggable ? (e) => endDrag(e, pe.event, onPress) : void 0,
|
|
2581
|
+
onPointerCancel: draggable ? cancelDrag : void 0,
|
|
2582
|
+
...slot("event", { base: {
|
|
2583
|
+
position: "absolute",
|
|
2584
|
+
left,
|
|
2585
|
+
width,
|
|
2586
|
+
top: pe.column * laneHeight,
|
|
2587
|
+
height: laneHeight,
|
|
2588
|
+
padding: 1,
|
|
2589
|
+
border: "none",
|
|
2590
|
+
background: "transparent",
|
|
2591
|
+
cursor: draggable ? "grab" : "pointer",
|
|
2592
|
+
touchAction: draggable ? "none" : "auto",
|
|
2593
|
+
font: "inherit",
|
|
2594
|
+
textAlign: "left",
|
|
2595
|
+
boxSizing: "border-box",
|
|
2596
|
+
zIndex: active ? 3 : 1,
|
|
2597
|
+
opacity: active ? .85 : 1
|
|
2598
|
+
} }),
|
|
2599
|
+
children: [Renderer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Renderer, { ...args }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultBar, {
|
|
2600
|
+
...args,
|
|
2601
|
+
theme,
|
|
2602
|
+
boxProps: slot("eventBox")
|
|
2603
|
+
}), draggable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2604
|
+
"aria-hidden": true,
|
|
2605
|
+
onPointerDown: (e) => {
|
|
2606
|
+
e.stopPropagation();
|
|
2607
|
+
beginDrag(e, key, "resize", pe.startHours, pe.durationHours);
|
|
2608
|
+
},
|
|
2609
|
+
style: {
|
|
2610
|
+
position: "absolute",
|
|
2611
|
+
top: 0,
|
|
2612
|
+
bottom: 0,
|
|
2613
|
+
right: 0,
|
|
2614
|
+
width: 8,
|
|
2615
|
+
cursor: "ew-resize",
|
|
2616
|
+
touchAction: "none"
|
|
2617
|
+
}
|
|
2618
|
+
}) : null]
|
|
2619
|
+
}, idx);
|
|
2620
|
+
}),
|
|
2621
|
+
createBox?.resourceKey === resource.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2622
|
+
"aria-hidden": true,
|
|
2623
|
+
...slot("createGhost", {
|
|
2624
|
+
base: {
|
|
2625
|
+
position: "absolute",
|
|
2626
|
+
top: 1,
|
|
2627
|
+
bottom: 1,
|
|
2628
|
+
left: Math.min(createBox.startPx, createBox.curPx),
|
|
2629
|
+
width: Math.max(Math.abs(createBox.curPx - createBox.startPx), 2),
|
|
2630
|
+
opacity: .7,
|
|
2631
|
+
pointerEvents: "none",
|
|
2632
|
+
zIndex: 2
|
|
2633
|
+
},
|
|
2634
|
+
themed: {
|
|
2635
|
+
background: theme.rangeBackground,
|
|
2636
|
+
border: `1px solid ${theme.selectedBackground}`,
|
|
2637
|
+
borderRadius: 6
|
|
2497
2638
|
}
|
|
2498
|
-
})
|
|
2499
|
-
}
|
|
2500
|
-
|
|
2639
|
+
})
|
|
2640
|
+
}) : null
|
|
2641
|
+
]
|
|
2501
2642
|
})]
|
|
2502
2643
|
}, resource.id);
|
|
2503
2644
|
})]
|
package/dist/index.mjs
CHANGED
|
@@ -2091,7 +2091,7 @@ function DefaultBar({ event, height, boxProps, theme }) {
|
|
|
2091
2091
|
* />
|
|
2092
2092
|
* ```
|
|
2093
2093
|
*/
|
|
2094
|
-
function ResourceTimeline({ date, orientation = "horizontal", resources, events, resourceId = (event) => event.resourceId, startHour = 0, endHour = 24, hourWidth = 80, hourHeight = 48, rowHeight = 56, labelWidth = 140, ampm = false, theme: themeOverrides, renderEvent, onPressEvent, onDragEvent, dragStepMinutes = 15, className, style, classNames, styles }) {
|
|
2094
|
+
function ResourceTimeline({ date, orientation = "horizontal", resources, events, resourceId = (event) => event.resourceId, startHour = 0, endHour = 24, hourWidth = 80, hourHeight = 48, rowHeight = 56, labelWidth = 140, ampm = false, theme: themeOverrides, renderEvent, onPressEvent, onDragEvent, onPressCell, onCreateEvent, businessHours, dragStepMinutes = 15, className, style, classNames, styles }) {
|
|
2095
2095
|
const theme = useMemo(() => mergeDomTheme(themeOverrides), [themeOverrides]);
|
|
2096
2096
|
const slot = createSlots({
|
|
2097
2097
|
classNames,
|
|
@@ -2170,6 +2170,69 @@ function ResourceTimeline({ date, orientation = "horizontal", resources, events,
|
|
|
2170
2170
|
applyDrag(null);
|
|
2171
2171
|
origin.current = null;
|
|
2172
2172
|
};
|
|
2173
|
+
const [createBox, setCreateBox] = useState(null);
|
|
2174
|
+
const createOrigin = useRef(null);
|
|
2175
|
+
const cellEnabled = !!onPressCell || !!onCreateEvent;
|
|
2176
|
+
const pxAlongAxis = (el, e) => {
|
|
2177
|
+
const rect = el.getBoundingClientRect();
|
|
2178
|
+
return vertical ? e.clientY - rect.top : e.clientX - rect.left;
|
|
2179
|
+
};
|
|
2180
|
+
const beginCreate = (e, resource) => {
|
|
2181
|
+
if (!cellEnabled || e.pointerType === "touch") return;
|
|
2182
|
+
if (e.target !== e.currentTarget || e.button > 0) return;
|
|
2183
|
+
const el = e.currentTarget;
|
|
2184
|
+
const startPx = pxAlongAxis(el, e);
|
|
2185
|
+
try {
|
|
2186
|
+
el.setPointerCapture?.(e.pointerId);
|
|
2187
|
+
} catch {}
|
|
2188
|
+
createOrigin.current = {
|
|
2189
|
+
el,
|
|
2190
|
+
resource,
|
|
2191
|
+
startPx
|
|
2192
|
+
};
|
|
2193
|
+
if (onCreateEvent) setCreateBox({
|
|
2194
|
+
resourceKey: resource.id,
|
|
2195
|
+
startPx,
|
|
2196
|
+
curPx: startPx
|
|
2197
|
+
});
|
|
2198
|
+
};
|
|
2199
|
+
const moveCreate = (e) => {
|
|
2200
|
+
const o = createOrigin.current;
|
|
2201
|
+
if (!o || !onCreateEvent) return;
|
|
2202
|
+
setCreateBox({
|
|
2203
|
+
resourceKey: o.resource.id,
|
|
2204
|
+
startPx: o.startPx,
|
|
2205
|
+
curPx: pxAlongAxis(o.el, e)
|
|
2206
|
+
});
|
|
2207
|
+
};
|
|
2208
|
+
const endCreate = (e) => {
|
|
2209
|
+
const o = createOrigin.current;
|
|
2210
|
+
if (!o) return;
|
|
2211
|
+
try {
|
|
2212
|
+
o.el.releasePointerCapture?.(e.pointerId);
|
|
2213
|
+
} catch {}
|
|
2214
|
+
const endPx = pxAlongAxis(o.el, e);
|
|
2215
|
+
if (Math.abs(endPx - o.startPx) > 4 && onCreateEvent) {
|
|
2216
|
+
const range = cellRangeFromDrag(date, o.startPx, endPx, hourSize, startHour, dragStepMinutes);
|
|
2217
|
+
if (range) onCreateEvent(range.start, range.end, o.resource);
|
|
2218
|
+
} else if (onPressCell) {
|
|
2219
|
+
const at = cellRangeFromDrag(date, o.startPx, o.startPx, hourSize, startHour, dragStepMinutes);
|
|
2220
|
+
if (at) onPressCell(at.start, o.resource);
|
|
2221
|
+
}
|
|
2222
|
+
createOrigin.current = null;
|
|
2223
|
+
setCreateBox(null);
|
|
2224
|
+
};
|
|
2225
|
+
const cancelCreate = () => {
|
|
2226
|
+
createOrigin.current = null;
|
|
2227
|
+
setCreateBox(null);
|
|
2228
|
+
};
|
|
2229
|
+
const trackInteractionProps = (resource) => cellEnabled ? {
|
|
2230
|
+
onPointerDown: (e) => beginCreate(e, resource),
|
|
2231
|
+
onPointerMove: moveCreate,
|
|
2232
|
+
onPointerUp: endCreate,
|
|
2233
|
+
onPointerCancel: cancelCreate
|
|
2234
|
+
} : null;
|
|
2235
|
+
const bandsFor = (resource) => businessHours ? closedHourBands(date, (d) => businessHours(d, resource), startHour, endHour) : [];
|
|
2173
2236
|
const hours = useMemo(() => Array.from({ length: Math.max(0, endHour - startHour) }, (_, i) => startHour + i), [startHour, endHour]);
|
|
2174
2237
|
const trackWidth = (endHour - startHour) * hourWidth;
|
|
2175
2238
|
const byResource = useMemo(() => {
|
|
@@ -2266,85 +2329,124 @@ function ResourceTimeline({ date, orientation = "horizontal", resources, events,
|
|
|
2266
2329
|
themed: { borderLeft: `1px solid ${theme.gridLine}` }
|
|
2267
2330
|
}),
|
|
2268
2331
|
children: /* @__PURE__ */ jsxs("div", {
|
|
2332
|
+
...trackInteractionProps(resource),
|
|
2269
2333
|
...slot("track", { base: {
|
|
2270
2334
|
position: "relative",
|
|
2271
2335
|
height: trackHeight
|
|
2272
2336
|
} }),
|
|
2273
|
-
children: [
|
|
2274
|
-
"
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
inset: 0,
|
|
2279
|
-
pointerEvents: "none"
|
|
2280
|
-
},
|
|
2281
|
-
themed: { backgroundImage: vGridLines }
|
|
2282
|
-
})
|
|
2283
|
-
}), positioned.map((pe, idx) => {
|
|
2284
|
-
const key = `${resource.id}:${idx}`;
|
|
2285
|
-
const active = drag?.key === key ? drag : null;
|
|
2286
|
-
const startH = active ? active.startHours : pe.startHours;
|
|
2287
|
-
const durH = active ? active.durationHours : pe.durationHours;
|
|
2288
|
-
const top = clamp(startH - startHour, 0, endHour - startHour) * hourHeight;
|
|
2289
|
-
const bottom = clamp(startH + durH - startHour, 0, endHour - startHour) * hourHeight;
|
|
2290
|
-
const height = Math.max(bottom - top, 2);
|
|
2291
|
-
const lanePct = 100 / pe.columns;
|
|
2292
|
-
const onPress = () => onPressEvent?.(pe.event);
|
|
2293
|
-
const args = {
|
|
2294
|
-
event: pe.event,
|
|
2295
|
-
width: 0,
|
|
2296
|
-
height,
|
|
2297
|
-
onPress
|
|
2298
|
-
};
|
|
2299
|
-
const draggable = !!onDragEvent;
|
|
2300
|
-
return /* @__PURE__ */ jsxs("button", {
|
|
2301
|
-
type: "button",
|
|
2302
|
-
onClick: draggable ? void 0 : onPress,
|
|
2303
|
-
"aria-label": pe.event.title,
|
|
2304
|
-
...dataState({ "data-dragging": !!active }),
|
|
2305
|
-
onPointerDown: draggable ? (e) => beginDrag(e, key, "move", pe.startHours, pe.durationHours) : void 0,
|
|
2306
|
-
onPointerMove: draggable ? moveDrag : void 0,
|
|
2307
|
-
onPointerUp: draggable ? (e) => endDrag(e, pe.event, onPress) : void 0,
|
|
2308
|
-
onPointerCancel: draggable ? cancelDrag : void 0,
|
|
2309
|
-
...slot("event", { base: {
|
|
2310
|
-
position: "absolute",
|
|
2311
|
-
top,
|
|
2312
|
-
height,
|
|
2313
|
-
left: `${pe.column * lanePct}%`,
|
|
2314
|
-
width: `${lanePct}%`,
|
|
2315
|
-
padding: 1,
|
|
2316
|
-
border: "none",
|
|
2317
|
-
background: "transparent",
|
|
2318
|
-
cursor: draggable ? "grab" : "pointer",
|
|
2319
|
-
touchAction: draggable ? "none" : "auto",
|
|
2320
|
-
font: "inherit",
|
|
2321
|
-
textAlign: "left",
|
|
2322
|
-
boxSizing: "border-box",
|
|
2323
|
-
zIndex: active ? 3 : 1,
|
|
2324
|
-
opacity: active ? .85 : 1
|
|
2325
|
-
} }),
|
|
2326
|
-
children: [Renderer ? /* @__PURE__ */ jsx(Renderer, { ...args }) : /* @__PURE__ */ jsx(DefaultBar, {
|
|
2327
|
-
...args,
|
|
2328
|
-
theme,
|
|
2329
|
-
boxProps: slot("eventBox")
|
|
2330
|
-
}), draggable ? /* @__PURE__ */ jsx("span", {
|
|
2331
|
-
"aria-hidden": true,
|
|
2332
|
-
onPointerDown: (e) => {
|
|
2333
|
-
e.stopPropagation();
|
|
2334
|
-
beginDrag(e, key, "resize", pe.startHours, pe.durationHours);
|
|
2335
|
-
},
|
|
2336
|
-
style: {
|
|
2337
|
+
children: [
|
|
2338
|
+
bandsFor(resource).map((b) => /* @__PURE__ */ jsx("div", {
|
|
2339
|
+
"aria-hidden": true,
|
|
2340
|
+
...slot("businessHours", {
|
|
2341
|
+
base: {
|
|
2337
2342
|
position: "absolute",
|
|
2338
2343
|
left: 0,
|
|
2339
2344
|
right: 0,
|
|
2340
|
-
|
|
2341
|
-
height:
|
|
2342
|
-
|
|
2343
|
-
|
|
2345
|
+
top: (b.start - startHour) * hourHeight,
|
|
2346
|
+
height: (b.end - b.start) * hourHeight,
|
|
2347
|
+
pointerEvents: "none",
|
|
2348
|
+
zIndex: 0
|
|
2349
|
+
},
|
|
2350
|
+
themed: { background: theme.outsideHoursBackground }
|
|
2351
|
+
})
|
|
2352
|
+
}, b.start)),
|
|
2353
|
+
/* @__PURE__ */ jsx("div", {
|
|
2354
|
+
"aria-hidden": true,
|
|
2355
|
+
...slot("gridLines", {
|
|
2356
|
+
base: {
|
|
2357
|
+
position: "absolute",
|
|
2358
|
+
inset: 0,
|
|
2359
|
+
pointerEvents: "none"
|
|
2360
|
+
},
|
|
2361
|
+
themed: { backgroundImage: vGridLines }
|
|
2362
|
+
})
|
|
2363
|
+
}),
|
|
2364
|
+
positioned.map((pe, idx) => {
|
|
2365
|
+
const key = `${resource.id}:${idx}`;
|
|
2366
|
+
const active = drag?.key === key ? drag : null;
|
|
2367
|
+
const startH = active ? active.startHours : pe.startHours;
|
|
2368
|
+
const durH = active ? active.durationHours : pe.durationHours;
|
|
2369
|
+
const top = clamp(startH - startHour, 0, endHour - startHour) * hourHeight;
|
|
2370
|
+
const bottom = clamp(startH + durH - startHour, 0, endHour - startHour) * hourHeight;
|
|
2371
|
+
const height = Math.max(bottom - top, 2);
|
|
2372
|
+
const lanePct = 100 / pe.columns;
|
|
2373
|
+
const onPress = () => onPressEvent?.(pe.event);
|
|
2374
|
+
const args = {
|
|
2375
|
+
event: pe.event,
|
|
2376
|
+
width: 0,
|
|
2377
|
+
height,
|
|
2378
|
+
onPress
|
|
2379
|
+
};
|
|
2380
|
+
const draggable = !!onDragEvent;
|
|
2381
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
2382
|
+
type: "button",
|
|
2383
|
+
onClick: draggable ? void 0 : onPress,
|
|
2384
|
+
"aria-label": pe.event.title,
|
|
2385
|
+
...dataState({ "data-dragging": !!active }),
|
|
2386
|
+
onPointerDown: draggable ? (e) => beginDrag(e, key, "move", pe.startHours, pe.durationHours) : void 0,
|
|
2387
|
+
onPointerMove: draggable ? moveDrag : void 0,
|
|
2388
|
+
onPointerUp: draggable ? (e) => endDrag(e, pe.event, onPress) : void 0,
|
|
2389
|
+
onPointerCancel: draggable ? cancelDrag : void 0,
|
|
2390
|
+
...slot("event", { base: {
|
|
2391
|
+
position: "absolute",
|
|
2392
|
+
top,
|
|
2393
|
+
height,
|
|
2394
|
+
left: `${pe.column * lanePct}%`,
|
|
2395
|
+
width: `${lanePct}%`,
|
|
2396
|
+
padding: 1,
|
|
2397
|
+
border: "none",
|
|
2398
|
+
background: "transparent",
|
|
2399
|
+
cursor: draggable ? "grab" : "pointer",
|
|
2400
|
+
touchAction: draggable ? "none" : "auto",
|
|
2401
|
+
font: "inherit",
|
|
2402
|
+
textAlign: "left",
|
|
2403
|
+
boxSizing: "border-box",
|
|
2404
|
+
zIndex: active ? 3 : 1,
|
|
2405
|
+
opacity: active ? .85 : 1
|
|
2406
|
+
} }),
|
|
2407
|
+
children: [Renderer ? /* @__PURE__ */ jsx(Renderer, { ...args }) : /* @__PURE__ */ jsx(DefaultBar, {
|
|
2408
|
+
...args,
|
|
2409
|
+
theme,
|
|
2410
|
+
boxProps: slot("eventBox")
|
|
2411
|
+
}), draggable ? /* @__PURE__ */ jsx("span", {
|
|
2412
|
+
"aria-hidden": true,
|
|
2413
|
+
onPointerDown: (e) => {
|
|
2414
|
+
e.stopPropagation();
|
|
2415
|
+
beginDrag(e, key, "resize", pe.startHours, pe.durationHours);
|
|
2416
|
+
},
|
|
2417
|
+
style: {
|
|
2418
|
+
position: "absolute",
|
|
2419
|
+
left: 0,
|
|
2420
|
+
right: 0,
|
|
2421
|
+
bottom: 0,
|
|
2422
|
+
height: 8,
|
|
2423
|
+
cursor: "ns-resize",
|
|
2424
|
+
touchAction: "none"
|
|
2425
|
+
}
|
|
2426
|
+
}) : null]
|
|
2427
|
+
}, idx);
|
|
2428
|
+
}),
|
|
2429
|
+
createBox?.resourceKey === resource.id ? /* @__PURE__ */ jsx("div", {
|
|
2430
|
+
"aria-hidden": true,
|
|
2431
|
+
...slot("createGhost", {
|
|
2432
|
+
base: {
|
|
2433
|
+
position: "absolute",
|
|
2434
|
+
left: 1,
|
|
2435
|
+
right: 1,
|
|
2436
|
+
top: Math.min(createBox.startPx, createBox.curPx),
|
|
2437
|
+
height: Math.max(Math.abs(createBox.curPx - createBox.startPx), 2),
|
|
2438
|
+
opacity: .7,
|
|
2439
|
+
pointerEvents: "none",
|
|
2440
|
+
zIndex: 2
|
|
2441
|
+
},
|
|
2442
|
+
themed: {
|
|
2443
|
+
background: theme.rangeBackground,
|
|
2444
|
+
border: `1px solid ${theme.selectedBackground}`,
|
|
2445
|
+
borderRadius: 6
|
|
2344
2446
|
}
|
|
2345
|
-
})
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2447
|
+
})
|
|
2448
|
+
}) : null
|
|
2449
|
+
]
|
|
2348
2450
|
})
|
|
2349
2451
|
}, resource.id);
|
|
2350
2452
|
})]
|
|
@@ -2419,84 +2521,123 @@ function ResourceTimeline({ date, orientation = "horizontal", resources, events,
|
|
|
2419
2521
|
}),
|
|
2420
2522
|
children: resource.title ?? resource.id
|
|
2421
2523
|
}), /* @__PURE__ */ jsxs("div", {
|
|
2524
|
+
...trackInteractionProps(resource),
|
|
2422
2525
|
...slot("track", { base: {
|
|
2423
2526
|
position: "relative",
|
|
2424
2527
|
width: trackWidth
|
|
2425
2528
|
} }),
|
|
2426
|
-
children: [
|
|
2427
|
-
"
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
inset: 0,
|
|
2432
|
-
pointerEvents: "none"
|
|
2433
|
-
},
|
|
2434
|
-
themed: { backgroundImage: gridLines }
|
|
2435
|
-
})
|
|
2436
|
-
}), positioned.map((pe, idx) => {
|
|
2437
|
-
const key = `${resource.id}:${idx}`;
|
|
2438
|
-
const active = drag?.key === key ? drag : null;
|
|
2439
|
-
const startH = active ? active.startHours : pe.startHours;
|
|
2440
|
-
const durH = active ? active.durationHours : pe.durationHours;
|
|
2441
|
-
const left = clamp(startH - startHour, 0, endHour - startHour) * hourWidth;
|
|
2442
|
-
const right = clamp(startH + durH - startHour, 0, endHour - startHour) * hourWidth;
|
|
2443
|
-
const width = Math.max(right - left, 2);
|
|
2444
|
-
const laneHeight = rowHeight / pe.columns;
|
|
2445
|
-
const onPress = () => onPressEvent?.(pe.event);
|
|
2446
|
-
const args = {
|
|
2447
|
-
event: pe.event,
|
|
2448
|
-
width,
|
|
2449
|
-
onPress
|
|
2450
|
-
};
|
|
2451
|
-
const draggable = !!onDragEvent;
|
|
2452
|
-
return /* @__PURE__ */ jsxs("button", {
|
|
2453
|
-
type: "button",
|
|
2454
|
-
onClick: draggable ? void 0 : onPress,
|
|
2455
|
-
"aria-label": pe.event.title,
|
|
2456
|
-
...dataState({ "data-dragging": !!active }),
|
|
2457
|
-
onPointerDown: draggable ? (e) => beginDrag(e, key, "move", pe.startHours, pe.durationHours) : void 0,
|
|
2458
|
-
onPointerMove: draggable ? moveDrag : void 0,
|
|
2459
|
-
onPointerUp: draggable ? (e) => endDrag(e, pe.event, onPress) : void 0,
|
|
2460
|
-
onPointerCancel: draggable ? cancelDrag : void 0,
|
|
2461
|
-
...slot("event", { base: {
|
|
2462
|
-
position: "absolute",
|
|
2463
|
-
left,
|
|
2464
|
-
width,
|
|
2465
|
-
top: pe.column * laneHeight,
|
|
2466
|
-
height: laneHeight,
|
|
2467
|
-
padding: 1,
|
|
2468
|
-
border: "none",
|
|
2469
|
-
background: "transparent",
|
|
2470
|
-
cursor: draggable ? "grab" : "pointer",
|
|
2471
|
-
touchAction: draggable ? "none" : "auto",
|
|
2472
|
-
font: "inherit",
|
|
2473
|
-
textAlign: "left",
|
|
2474
|
-
boxSizing: "border-box",
|
|
2475
|
-
zIndex: active ? 3 : 1,
|
|
2476
|
-
opacity: active ? .85 : 1
|
|
2477
|
-
} }),
|
|
2478
|
-
children: [Renderer ? /* @__PURE__ */ jsx(Renderer, { ...args }) : /* @__PURE__ */ jsx(DefaultBar, {
|
|
2479
|
-
...args,
|
|
2480
|
-
theme,
|
|
2481
|
-
boxProps: slot("eventBox")
|
|
2482
|
-
}), draggable ? /* @__PURE__ */ jsx("span", {
|
|
2483
|
-
"aria-hidden": true,
|
|
2484
|
-
onPointerDown: (e) => {
|
|
2485
|
-
e.stopPropagation();
|
|
2486
|
-
beginDrag(e, key, "resize", pe.startHours, pe.durationHours);
|
|
2487
|
-
},
|
|
2488
|
-
style: {
|
|
2529
|
+
children: [
|
|
2530
|
+
bandsFor(resource).map((b) => /* @__PURE__ */ jsx("div", {
|
|
2531
|
+
"aria-hidden": true,
|
|
2532
|
+
...slot("businessHours", {
|
|
2533
|
+
base: {
|
|
2489
2534
|
position: "absolute",
|
|
2490
2535
|
top: 0,
|
|
2491
2536
|
bottom: 0,
|
|
2492
|
-
|
|
2493
|
-
width:
|
|
2494
|
-
|
|
2495
|
-
|
|
2537
|
+
left: (b.start - startHour) * hourWidth,
|
|
2538
|
+
width: (b.end - b.start) * hourWidth,
|
|
2539
|
+
pointerEvents: "none",
|
|
2540
|
+
zIndex: 0
|
|
2541
|
+
},
|
|
2542
|
+
themed: { background: theme.outsideHoursBackground }
|
|
2543
|
+
})
|
|
2544
|
+
}, b.start)),
|
|
2545
|
+
/* @__PURE__ */ jsx("div", {
|
|
2546
|
+
"aria-hidden": true,
|
|
2547
|
+
...slot("gridLines", {
|
|
2548
|
+
base: {
|
|
2549
|
+
position: "absolute",
|
|
2550
|
+
inset: 0,
|
|
2551
|
+
pointerEvents: "none"
|
|
2552
|
+
},
|
|
2553
|
+
themed: { backgroundImage: gridLines }
|
|
2554
|
+
})
|
|
2555
|
+
}),
|
|
2556
|
+
positioned.map((pe, idx) => {
|
|
2557
|
+
const key = `${resource.id}:${idx}`;
|
|
2558
|
+
const active = drag?.key === key ? drag : null;
|
|
2559
|
+
const startH = active ? active.startHours : pe.startHours;
|
|
2560
|
+
const durH = active ? active.durationHours : pe.durationHours;
|
|
2561
|
+
const left = clamp(startH - startHour, 0, endHour - startHour) * hourWidth;
|
|
2562
|
+
const right = clamp(startH + durH - startHour, 0, endHour - startHour) * hourWidth;
|
|
2563
|
+
const width = Math.max(right - left, 2);
|
|
2564
|
+
const laneHeight = rowHeight / pe.columns;
|
|
2565
|
+
const onPress = () => onPressEvent?.(pe.event);
|
|
2566
|
+
const args = {
|
|
2567
|
+
event: pe.event,
|
|
2568
|
+
width,
|
|
2569
|
+
onPress
|
|
2570
|
+
};
|
|
2571
|
+
const draggable = !!onDragEvent;
|
|
2572
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
2573
|
+
type: "button",
|
|
2574
|
+
onClick: draggable ? void 0 : onPress,
|
|
2575
|
+
"aria-label": pe.event.title,
|
|
2576
|
+
...dataState({ "data-dragging": !!active }),
|
|
2577
|
+
onPointerDown: draggable ? (e) => beginDrag(e, key, "move", pe.startHours, pe.durationHours) : void 0,
|
|
2578
|
+
onPointerMove: draggable ? moveDrag : void 0,
|
|
2579
|
+
onPointerUp: draggable ? (e) => endDrag(e, pe.event, onPress) : void 0,
|
|
2580
|
+
onPointerCancel: draggable ? cancelDrag : void 0,
|
|
2581
|
+
...slot("event", { base: {
|
|
2582
|
+
position: "absolute",
|
|
2583
|
+
left,
|
|
2584
|
+
width,
|
|
2585
|
+
top: pe.column * laneHeight,
|
|
2586
|
+
height: laneHeight,
|
|
2587
|
+
padding: 1,
|
|
2588
|
+
border: "none",
|
|
2589
|
+
background: "transparent",
|
|
2590
|
+
cursor: draggable ? "grab" : "pointer",
|
|
2591
|
+
touchAction: draggable ? "none" : "auto",
|
|
2592
|
+
font: "inherit",
|
|
2593
|
+
textAlign: "left",
|
|
2594
|
+
boxSizing: "border-box",
|
|
2595
|
+
zIndex: active ? 3 : 1,
|
|
2596
|
+
opacity: active ? .85 : 1
|
|
2597
|
+
} }),
|
|
2598
|
+
children: [Renderer ? /* @__PURE__ */ jsx(Renderer, { ...args }) : /* @__PURE__ */ jsx(DefaultBar, {
|
|
2599
|
+
...args,
|
|
2600
|
+
theme,
|
|
2601
|
+
boxProps: slot("eventBox")
|
|
2602
|
+
}), draggable ? /* @__PURE__ */ jsx("span", {
|
|
2603
|
+
"aria-hidden": true,
|
|
2604
|
+
onPointerDown: (e) => {
|
|
2605
|
+
e.stopPropagation();
|
|
2606
|
+
beginDrag(e, key, "resize", pe.startHours, pe.durationHours);
|
|
2607
|
+
},
|
|
2608
|
+
style: {
|
|
2609
|
+
position: "absolute",
|
|
2610
|
+
top: 0,
|
|
2611
|
+
bottom: 0,
|
|
2612
|
+
right: 0,
|
|
2613
|
+
width: 8,
|
|
2614
|
+
cursor: "ew-resize",
|
|
2615
|
+
touchAction: "none"
|
|
2616
|
+
}
|
|
2617
|
+
}) : null]
|
|
2618
|
+
}, idx);
|
|
2619
|
+
}),
|
|
2620
|
+
createBox?.resourceKey === resource.id ? /* @__PURE__ */ jsx("div", {
|
|
2621
|
+
"aria-hidden": true,
|
|
2622
|
+
...slot("createGhost", {
|
|
2623
|
+
base: {
|
|
2624
|
+
position: "absolute",
|
|
2625
|
+
top: 1,
|
|
2626
|
+
bottom: 1,
|
|
2627
|
+
left: Math.min(createBox.startPx, createBox.curPx),
|
|
2628
|
+
width: Math.max(Math.abs(createBox.curPx - createBox.startPx), 2),
|
|
2629
|
+
opacity: .7,
|
|
2630
|
+
pointerEvents: "none",
|
|
2631
|
+
zIndex: 2
|
|
2632
|
+
},
|
|
2633
|
+
themed: {
|
|
2634
|
+
background: theme.rangeBackground,
|
|
2635
|
+
border: `1px solid ${theme.selectedBackground}`,
|
|
2636
|
+
borderRadius: 6
|
|
2496
2637
|
}
|
|
2497
|
-
})
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2638
|
+
})
|
|
2639
|
+
}) : null
|
|
2640
|
+
]
|
|
2500
2641
|
})]
|
|
2501
2642
|
}, resource.id);
|
|
2502
2643
|
})]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@super-calendar/dom",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Virtualized month / week / day calendar and date picker for react-dom. No React Native, no react-native-web.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"calendar",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"access": "public"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@super-calendar/core": "2.
|
|
51
|
+
"@super-calendar/core": "2.5.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@legendapp/list": ">=3",
|
package/src/ResourceTimeline.tsx
CHANGED
|
@@ -8,6 +8,8 @@ import type {
|
|
|
8
8
|
import { useMemo, useRef, useState } from "react";
|
|
9
9
|
import {
|
|
10
10
|
type CalendarEvent,
|
|
11
|
+
cellRangeFromDrag,
|
|
12
|
+
closedHourBands,
|
|
11
13
|
eventTimeLabel,
|
|
12
14
|
formatHour,
|
|
13
15
|
layoutDayEvents,
|
|
@@ -28,8 +30,10 @@ export type ResourceTimelineSlot =
|
|
|
28
30
|
| "row"
|
|
29
31
|
| "track"
|
|
30
32
|
| "gridLines"
|
|
33
|
+
| "businessHours"
|
|
31
34
|
| "event"
|
|
32
|
-
| "eventBox"
|
|
35
|
+
| "eventBox"
|
|
36
|
+
| "createGhost";
|
|
33
37
|
|
|
34
38
|
/** A schedulable lane (room, person, machine) that events are grouped under. */
|
|
35
39
|
export interface Resource {
|
|
@@ -94,6 +98,16 @@ export interface ResourceTimelineProps<T = unknown> extends SlotStyleProps<Resou
|
|
|
94
98
|
* proposed new start/end. Return `false` to reject the drop (it snaps back).
|
|
95
99
|
*/
|
|
96
100
|
onDragEvent?: (event: CalendarEvent<T>, start: Date, end: Date) => void | boolean;
|
|
101
|
+
/** Tap empty lane space; called with the snapped time and the lane's resource. */
|
|
102
|
+
onPressCell?: (at: Date, resource: Resource) => void;
|
|
103
|
+
/** Drag empty lane space to create; called with the swept start/end and the lane's resource. */
|
|
104
|
+
onCreateEvent?: (start: Date, end: Date, resource: Resource) => void;
|
|
105
|
+
/**
|
|
106
|
+
* Shade the hours outside this window, per lane. Same shape as the Calendar's
|
|
107
|
+
* `businessHours` plus the lane's resource, so per-resource opening hours work
|
|
108
|
+
* (return `null` for a fully closed lane). A date-only function is accepted.
|
|
109
|
+
*/
|
|
110
|
+
businessHours?: (date: Date, resource: Resource) => { start: number; end: number } | null;
|
|
97
111
|
/** Snap dragged events to this many minutes (default 15). */
|
|
98
112
|
dragStepMinutes?: number;
|
|
99
113
|
/** Class applied to the root element. */
|
|
@@ -198,6 +212,9 @@ export function ResourceTimeline<T = unknown>({
|
|
|
198
212
|
renderEvent,
|
|
199
213
|
onPressEvent,
|
|
200
214
|
onDragEvent,
|
|
215
|
+
onPressCell,
|
|
216
|
+
onCreateEvent,
|
|
217
|
+
businessHours,
|
|
201
218
|
dragStepMinutes = 15,
|
|
202
219
|
className,
|
|
203
220
|
style,
|
|
@@ -285,6 +302,90 @@ export function ResourceTimeline<T = unknown>({
|
|
|
285
302
|
origin.current = null;
|
|
286
303
|
};
|
|
287
304
|
|
|
305
|
+
// Create-by-drag / tap on empty lane space, mirroring the time grid: mouse and
|
|
306
|
+
// pen sweep out a new event, and a tap without movement fires onPressCell. Touch
|
|
307
|
+
// is left free to scroll the board, so cell taps are pointer (mouse/pen) only.
|
|
308
|
+
const [createBox, setCreateBox] = useState<{
|
|
309
|
+
resourceKey: string;
|
|
310
|
+
startPx: number;
|
|
311
|
+
curPx: number;
|
|
312
|
+
} | null>(null);
|
|
313
|
+
const createOrigin = useRef<{ el: HTMLElement; resource: Resource; startPx: number } | null>(
|
|
314
|
+
null,
|
|
315
|
+
);
|
|
316
|
+
const cellEnabled = !!onPressCell || !!onCreateEvent;
|
|
317
|
+
const pxAlongAxis = (el: HTMLElement, e: ReactPointerEvent) => {
|
|
318
|
+
const rect = el.getBoundingClientRect();
|
|
319
|
+
return vertical ? e.clientY - rect.top : e.clientX - rect.left;
|
|
320
|
+
};
|
|
321
|
+
const beginCreate = (e: ReactPointerEvent, resource: Resource) => {
|
|
322
|
+
// Only from the lane background, primary button, never on an event bar.
|
|
323
|
+
if (!cellEnabled || e.pointerType === "touch") return;
|
|
324
|
+
if (e.target !== e.currentTarget || e.button > 0) return;
|
|
325
|
+
const el = e.currentTarget as HTMLElement;
|
|
326
|
+
const startPx = pxAlongAxis(el, e);
|
|
327
|
+
try {
|
|
328
|
+
el.setPointerCapture?.(e.pointerId);
|
|
329
|
+
} catch {
|
|
330
|
+
// Best-effort capture.
|
|
331
|
+
}
|
|
332
|
+
createOrigin.current = { el, resource, startPx };
|
|
333
|
+
// Only preview a ghost when a create can happen; in press-only mode
|
|
334
|
+
// (onPressCell without onCreateEvent) the tap still commits via endCreate.
|
|
335
|
+
if (onCreateEvent) setCreateBox({ resourceKey: resource.id, startPx, curPx: startPx });
|
|
336
|
+
};
|
|
337
|
+
const moveCreate = (e: ReactPointerEvent) => {
|
|
338
|
+
const o = createOrigin.current;
|
|
339
|
+
if (!o || !onCreateEvent) return;
|
|
340
|
+
setCreateBox({ resourceKey: o.resource.id, startPx: o.startPx, curPx: pxAlongAxis(o.el, e) });
|
|
341
|
+
};
|
|
342
|
+
const endCreate = (e: ReactPointerEvent) => {
|
|
343
|
+
const o = createOrigin.current;
|
|
344
|
+
if (!o) return;
|
|
345
|
+
try {
|
|
346
|
+
o.el.releasePointerCapture?.(e.pointerId);
|
|
347
|
+
} catch {
|
|
348
|
+
// Best-effort release.
|
|
349
|
+
}
|
|
350
|
+
const endPx = pxAlongAxis(o.el, e);
|
|
351
|
+
const moved = Math.abs(endPx - o.startPx) > 4;
|
|
352
|
+
if (moved && onCreateEvent) {
|
|
353
|
+
const range = cellRangeFromDrag(date, o.startPx, endPx, hourSize, startHour, dragStepMinutes);
|
|
354
|
+
if (range) onCreateEvent(range.start, range.end, o.resource);
|
|
355
|
+
} else if (onPressCell) {
|
|
356
|
+
const at = cellRangeFromDrag(
|
|
357
|
+
date,
|
|
358
|
+
o.startPx,
|
|
359
|
+
o.startPx,
|
|
360
|
+
hourSize,
|
|
361
|
+
startHour,
|
|
362
|
+
dragStepMinutes,
|
|
363
|
+
);
|
|
364
|
+
if (at) onPressCell(at.start, o.resource);
|
|
365
|
+
}
|
|
366
|
+
createOrigin.current = null;
|
|
367
|
+
setCreateBox(null);
|
|
368
|
+
};
|
|
369
|
+
// A gesture the browser cancels (scroll takeover, etc.) must not commit.
|
|
370
|
+
const cancelCreate = () => {
|
|
371
|
+
createOrigin.current = null;
|
|
372
|
+
setCreateBox(null);
|
|
373
|
+
};
|
|
374
|
+
const trackInteractionProps = (resource: Resource) =>
|
|
375
|
+
cellEnabled
|
|
376
|
+
? {
|
|
377
|
+
onPointerDown: (e: ReactPointerEvent) => beginCreate(e, resource),
|
|
378
|
+
onPointerMove: moveCreate,
|
|
379
|
+
onPointerUp: endCreate,
|
|
380
|
+
onPointerCancel: cancelCreate,
|
|
381
|
+
}
|
|
382
|
+
: null;
|
|
383
|
+
// The closed-hours bands to shade in a lane, resolved per resource.
|
|
384
|
+
const bandsFor = (resource: Resource) =>
|
|
385
|
+
businessHours
|
|
386
|
+
? closedHourBands(date, (d) => businessHours(d, resource), startHour, endHour)
|
|
387
|
+
: [];
|
|
388
|
+
|
|
288
389
|
const hours = useMemo(
|
|
289
390
|
() => Array.from({ length: Math.max(0, endHour - startHour) }, (_, i) => startHour + i),
|
|
290
391
|
[startHour, endHour],
|
|
@@ -384,7 +485,29 @@ export function ResourceTimeline<T = unknown>({
|
|
|
384
485
|
themed: { borderLeft: `1px solid ${theme.gridLine}` },
|
|
385
486
|
})}
|
|
386
487
|
>
|
|
387
|
-
<div
|
|
488
|
+
<div
|
|
489
|
+
{...trackInteractionProps(resource)}
|
|
490
|
+
{...slot("track", { base: { position: "relative", height: trackHeight } })}
|
|
491
|
+
>
|
|
492
|
+
{/* Closed-hours shade, behind the grid lines and events. */}
|
|
493
|
+
{bandsFor(resource).map((b) => (
|
|
494
|
+
<div
|
|
495
|
+
key={b.start}
|
|
496
|
+
aria-hidden
|
|
497
|
+
{...slot("businessHours", {
|
|
498
|
+
base: {
|
|
499
|
+
position: "absolute",
|
|
500
|
+
left: 0,
|
|
501
|
+
right: 0,
|
|
502
|
+
top: (b.start - startHour) * hourHeight,
|
|
503
|
+
height: (b.end - b.start) * hourHeight,
|
|
504
|
+
pointerEvents: "none",
|
|
505
|
+
zIndex: 0,
|
|
506
|
+
},
|
|
507
|
+
themed: { background: theme.outsideHoursBackground },
|
|
508
|
+
})}
|
|
509
|
+
/>
|
|
510
|
+
))}
|
|
388
511
|
<div
|
|
389
512
|
aria-hidden
|
|
390
513
|
{...slot("gridLines", {
|
|
@@ -472,6 +595,28 @@ export function ResourceTimeline<T = unknown>({
|
|
|
472
595
|
</button>
|
|
473
596
|
);
|
|
474
597
|
})}
|
|
598
|
+
{createBox?.resourceKey === resource.id ? (
|
|
599
|
+
<div
|
|
600
|
+
aria-hidden
|
|
601
|
+
{...slot("createGhost", {
|
|
602
|
+
base: {
|
|
603
|
+
position: "absolute",
|
|
604
|
+
left: 1,
|
|
605
|
+
right: 1,
|
|
606
|
+
top: Math.min(createBox.startPx, createBox.curPx),
|
|
607
|
+
height: Math.max(Math.abs(createBox.curPx - createBox.startPx), 2),
|
|
608
|
+
opacity: 0.7,
|
|
609
|
+
pointerEvents: "none",
|
|
610
|
+
zIndex: 2,
|
|
611
|
+
},
|
|
612
|
+
themed: {
|
|
613
|
+
background: theme.rangeBackground,
|
|
614
|
+
border: `1px solid ${theme.selectedBackground}`,
|
|
615
|
+
borderRadius: 6,
|
|
616
|
+
},
|
|
617
|
+
})}
|
|
618
|
+
/>
|
|
619
|
+
) : null}
|
|
475
620
|
</div>
|
|
476
621
|
</div>
|
|
477
622
|
);
|
|
@@ -544,7 +689,29 @@ export function ResourceTimeline<T = unknown>({
|
|
|
544
689
|
>
|
|
545
690
|
{resource.title ?? resource.id}
|
|
546
691
|
</div>
|
|
547
|
-
<div
|
|
692
|
+
<div
|
|
693
|
+
{...trackInteractionProps(resource)}
|
|
694
|
+
{...slot("track", { base: { position: "relative", width: trackWidth } })}
|
|
695
|
+
>
|
|
696
|
+
{/* Closed-hours shade, behind the grid lines and events. */}
|
|
697
|
+
{bandsFor(resource).map((b) => (
|
|
698
|
+
<div
|
|
699
|
+
key={b.start}
|
|
700
|
+
aria-hidden
|
|
701
|
+
{...slot("businessHours", {
|
|
702
|
+
base: {
|
|
703
|
+
position: "absolute",
|
|
704
|
+
top: 0,
|
|
705
|
+
bottom: 0,
|
|
706
|
+
left: (b.start - startHour) * hourWidth,
|
|
707
|
+
width: (b.end - b.start) * hourWidth,
|
|
708
|
+
pointerEvents: "none",
|
|
709
|
+
zIndex: 0,
|
|
710
|
+
},
|
|
711
|
+
themed: { background: theme.outsideHoursBackground },
|
|
712
|
+
})}
|
|
713
|
+
/>
|
|
714
|
+
))}
|
|
548
715
|
<div
|
|
549
716
|
aria-hidden
|
|
550
717
|
{...slot("gridLines", {
|
|
@@ -627,6 +794,28 @@ export function ResourceTimeline<T = unknown>({
|
|
|
627
794
|
</button>
|
|
628
795
|
);
|
|
629
796
|
})}
|
|
797
|
+
{createBox?.resourceKey === resource.id ? (
|
|
798
|
+
<div
|
|
799
|
+
aria-hidden
|
|
800
|
+
{...slot("createGhost", {
|
|
801
|
+
base: {
|
|
802
|
+
position: "absolute",
|
|
803
|
+
top: 1,
|
|
804
|
+
bottom: 1,
|
|
805
|
+
left: Math.min(createBox.startPx, createBox.curPx),
|
|
806
|
+
width: Math.max(Math.abs(createBox.curPx - createBox.startPx), 2),
|
|
807
|
+
opacity: 0.7,
|
|
808
|
+
pointerEvents: "none",
|
|
809
|
+
zIndex: 2,
|
|
810
|
+
},
|
|
811
|
+
themed: {
|
|
812
|
+
background: theme.rangeBackground,
|
|
813
|
+
border: `1px solid ${theme.selectedBackground}`,
|
|
814
|
+
borderRadius: 6,
|
|
815
|
+
},
|
|
816
|
+
})}
|
|
817
|
+
/>
|
|
818
|
+
) : null}
|
|
630
819
|
</div>
|
|
631
820
|
</div>
|
|
632
821
|
);
|