@super-calendar/dom 2.3.2 → 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 +94 -223
- package/dist/index.d.ts +94 -223
- package/dist/index.js +640 -139
- package/dist/index.mjs +643 -142
- package/package.json +2 -2
- package/src/Calendar.tsx +192 -50
- package/src/ResourceTimeline.tsx +406 -11
- package/src/TimeGrid.tsx +345 -72
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 {
|
|
@@ -42,16 +46,28 @@ export interface Resource {
|
|
|
42
46
|
/** Props passed to a custom resource-timeline event renderer. */
|
|
43
47
|
export interface ResourceEventArgs<T = unknown> {
|
|
44
48
|
event: CalendarEvent<T>;
|
|
45
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Pixel width of the event bar. In the vertical orientation the columns flex
|
|
51
|
+
* to the container, so this is 0 there; size against `height` instead.
|
|
52
|
+
*/
|
|
46
53
|
width: number;
|
|
54
|
+
/** Pixel height of the event bar; set in the vertical orientation. */
|
|
55
|
+
height?: number;
|
|
47
56
|
onPress: () => void;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
/** Props for {@link ResourceTimeline}. */
|
|
51
60
|
export interface ResourceTimelineProps<T = unknown> extends SlotStyleProps<ResourceTimelineSlot> {
|
|
52
|
-
/** The day to lay out along the
|
|
61
|
+
/** The day to lay out along the time axis. */
|
|
53
62
|
date: Date;
|
|
54
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Lay the day out along the horizontal axis with resources as rows (the
|
|
65
|
+
* default), or down the vertical axis with resources as columns, like the
|
|
66
|
+
* time grid. Vertical reads better on narrow screens: the columns share the
|
|
67
|
+
* width instead of the axis scrolling sideways.
|
|
68
|
+
*/
|
|
69
|
+
orientation?: "horizontal" | "vertical";
|
|
70
|
+
/** The resource lanes: rows when horizontal, columns when vertical. */
|
|
55
71
|
resources: Resource[];
|
|
56
72
|
/** Events; each is placed in the row named by `resourceId(event)`. */
|
|
57
73
|
events: CalendarEvent<T>[];
|
|
@@ -61,11 +77,13 @@ export interface ResourceTimelineProps<T = unknown> extends SlotStyleProps<Resou
|
|
|
61
77
|
startHour?: number;
|
|
62
78
|
/** Last hour shown, exclusive (default 24). */
|
|
63
79
|
endHour?: number;
|
|
64
|
-
/** Pixels per hour along the axis (default 80). */
|
|
80
|
+
/** Pixels per hour along the horizontal axis (default 80). Horizontal only. */
|
|
65
81
|
hourWidth?: number;
|
|
66
|
-
/**
|
|
82
|
+
/** Pixels per hour down the vertical axis (default 48). Vertical only. */
|
|
83
|
+
hourHeight?: number;
|
|
84
|
+
/** Height of each resource row in px (default 56). Horizontal only. */
|
|
67
85
|
rowHeight?: number;
|
|
68
|
-
/** Width of the left resource-label column in px (default 140). */
|
|
86
|
+
/** Width of the left resource-label column in px (default 140). Horizontal only. */
|
|
69
87
|
labelWidth?: number;
|
|
70
88
|
/** 12-hour AM/PM axis labels (default false). */
|
|
71
89
|
ampm?: boolean;
|
|
@@ -80,6 +98,16 @@ export interface ResourceTimelineProps<T = unknown> extends SlotStyleProps<Resou
|
|
|
80
98
|
* proposed new start/end. Return `false` to reject the drop (it snaps back).
|
|
81
99
|
*/
|
|
82
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;
|
|
83
111
|
/** Snap dragged events to this many minutes (default 15). */
|
|
84
112
|
dragStepMinutes?: number;
|
|
85
113
|
/** Class applied to the root element. */
|
|
@@ -90,6 +118,9 @@ export interface ResourceTimelineProps<T = unknown> extends SlotStyleProps<Resou
|
|
|
90
118
|
|
|
91
119
|
const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
|
|
92
120
|
|
|
121
|
+
// Hour-gutter width in the vertical orientation, matching the time grid's axis.
|
|
122
|
+
const GUTTER_WIDTH = 56;
|
|
123
|
+
|
|
93
124
|
type DragState = {
|
|
94
125
|
key: string;
|
|
95
126
|
kind: "move" | "resize";
|
|
@@ -100,9 +131,13 @@ type DragState = {
|
|
|
100
131
|
|
|
101
132
|
function DefaultBar<T>({
|
|
102
133
|
event,
|
|
134
|
+
height,
|
|
103
135
|
boxProps,
|
|
104
136
|
theme,
|
|
105
137
|
}: ResourceEventArgs<T> & { boxProps: ResolvedSlot; theme: DomCalendarTheme }) {
|
|
138
|
+
// Vertical bars gate the time line on their height (short slots show just the
|
|
139
|
+
// title); horizontal bars keep it always, as before.
|
|
140
|
+
const showTime = height != null ? height > 34 : true;
|
|
106
141
|
const time = eventTimeLabel({
|
|
107
142
|
mode: "day",
|
|
108
143
|
isAllDay: false,
|
|
@@ -139,7 +174,7 @@ function DefaultBar<T>({
|
|
|
139
174
|
>
|
|
140
175
|
{event.title}
|
|
141
176
|
</div>
|
|
142
|
-
{time ? <div style={{ opacity: 0.75, fontSize: 11 }}>{time}</div> : null}
|
|
177
|
+
{time && showTime ? <div style={{ opacity: 0.75, fontSize: 11 }}>{time}</div> : null}
|
|
143
178
|
</div>
|
|
144
179
|
);
|
|
145
180
|
}
|
|
@@ -162,12 +197,14 @@ function DefaultBar<T>({
|
|
|
162
197
|
*/
|
|
163
198
|
export function ResourceTimeline<T = unknown>({
|
|
164
199
|
date,
|
|
200
|
+
orientation = "horizontal",
|
|
165
201
|
resources,
|
|
166
202
|
events,
|
|
167
203
|
resourceId = (event) => (event as { resourceId?: string }).resourceId,
|
|
168
204
|
startHour = 0,
|
|
169
205
|
endHour = 24,
|
|
170
206
|
hourWidth = 80,
|
|
207
|
+
hourHeight = 48,
|
|
171
208
|
rowHeight = 56,
|
|
172
209
|
labelWidth = 140,
|
|
173
210
|
ampm = false,
|
|
@@ -175,6 +212,9 @@ export function ResourceTimeline<T = unknown>({
|
|
|
175
212
|
renderEvent,
|
|
176
213
|
onPressEvent,
|
|
177
214
|
onDragEvent,
|
|
215
|
+
onPressCell,
|
|
216
|
+
onCreateEvent,
|
|
217
|
+
businessHours,
|
|
178
218
|
dragStepMinutes = 15,
|
|
179
219
|
className,
|
|
180
220
|
style,
|
|
@@ -185,6 +225,9 @@ export function ResourceTimeline<T = unknown>({
|
|
|
185
225
|
const slot = createSlots<ResourceTimelineSlot>({ classNames, styles });
|
|
186
226
|
const Renderer = renderEvent;
|
|
187
227
|
const snapHours = dragStepMinutes / 60;
|
|
228
|
+
const vertical = orientation === "vertical";
|
|
229
|
+
// Pixels per hour along whichever axis carries the time.
|
|
230
|
+
const hourSize = vertical ? hourHeight : hourWidth;
|
|
188
231
|
|
|
189
232
|
// `drag` drives the visual; `dragRef` is the source of truth the pointer
|
|
190
233
|
// handlers read so they never see a stale closure between events.
|
|
@@ -210,13 +253,13 @@ export function ResourceTimeline<T = unknown>({
|
|
|
210
253
|
} catch {
|
|
211
254
|
// Pointer capture is best-effort.
|
|
212
255
|
}
|
|
213
|
-
origin.current = { x: e.clientX, startHours, durationHours };
|
|
256
|
+
origin.current = { x: vertical ? e.clientY : e.clientX, startHours, durationHours };
|
|
214
257
|
applyDrag({ key, kind, startHours, durationHours, moved: false });
|
|
215
258
|
};
|
|
216
259
|
const moveDrag = (e: ReactPointerEvent) => {
|
|
217
260
|
const d = dragRef.current;
|
|
218
261
|
if (!d || !origin.current) return;
|
|
219
|
-
const dHours = (e.clientX - origin.current.x) /
|
|
262
|
+
const dHours = ((vertical ? e.clientY : e.clientX) - origin.current.x) / hourSize;
|
|
220
263
|
const snap = (v: number) => Math.round(v / snapHours) * snapHours;
|
|
221
264
|
if (d.kind === "move") {
|
|
222
265
|
const startHours = clamp(
|
|
@@ -259,6 +302,90 @@ export function ResourceTimeline<T = unknown>({
|
|
|
259
302
|
origin.current = null;
|
|
260
303
|
};
|
|
261
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
|
+
|
|
262
389
|
const hours = useMemo(
|
|
263
390
|
() => Array.from({ length: Math.max(0, endHour - startHour) }, (_, i) => startHour + i),
|
|
264
391
|
[startHour, endHour],
|
|
@@ -275,6 +402,230 @@ export function ResourceTimeline<T = unknown>({
|
|
|
275
402
|
return map;
|
|
276
403
|
}, [resources, events, resourceId, date]);
|
|
277
404
|
|
|
405
|
+
if (vertical) {
|
|
406
|
+
// Time flows down like the time grid: hour gutter on the left, one flexed
|
|
407
|
+
// column per resource, so narrow screens share the width instead of
|
|
408
|
+
// scrolling sideways.
|
|
409
|
+
const trackHeight = (endHour - startHour) * hourHeight;
|
|
410
|
+
const vGridLines = `repeating-linear-gradient(to bottom, transparent 0, transparent ${hourHeight - 1}px, ${theme.gridLine} ${hourHeight - 1}px, ${theme.gridLine} ${hourHeight}px)`;
|
|
411
|
+
return (
|
|
412
|
+
<div
|
|
413
|
+
className={className}
|
|
414
|
+
style={{ fontFamily: theme.fontFamily, color: theme.text, overflowY: "auto", ...style }}
|
|
415
|
+
>
|
|
416
|
+
{/* Header: corner above the hour gutter + one label per resource column */}
|
|
417
|
+
<div
|
|
418
|
+
{...slot("header", {
|
|
419
|
+
// Sticky so the resource labels stay visible while the hours scroll
|
|
420
|
+
// (matching the native renderer's fixed header row).
|
|
421
|
+
base: { display: "flex", position: "sticky", top: 0, zIndex: 2 },
|
|
422
|
+
themed: { borderBottom: `1px solid ${theme.gridLine}`, background: theme.surface },
|
|
423
|
+
})}
|
|
424
|
+
>
|
|
425
|
+
<div {...slot("corner", { base: { width: GUTTER_WIDTH, flex: "none" } })} />
|
|
426
|
+
{resources.map((resource) => (
|
|
427
|
+
<div
|
|
428
|
+
key={resource.id}
|
|
429
|
+
{...slot("resourceLabel", {
|
|
430
|
+
base: {
|
|
431
|
+
flex: 1,
|
|
432
|
+
minWidth: 0,
|
|
433
|
+
padding: "6px 4px",
|
|
434
|
+
textAlign: "center",
|
|
435
|
+
overflow: "hidden",
|
|
436
|
+
textOverflow: "ellipsis",
|
|
437
|
+
whiteSpace: "nowrap",
|
|
438
|
+
boxSizing: "border-box",
|
|
439
|
+
},
|
|
440
|
+
themed: {
|
|
441
|
+
fontSize: 13,
|
|
442
|
+
fontWeight: 600,
|
|
443
|
+
borderLeft: `1px solid ${theme.gridLine}`,
|
|
444
|
+
},
|
|
445
|
+
})}
|
|
446
|
+
>
|
|
447
|
+
{resource.title ?? resource.id}
|
|
448
|
+
</div>
|
|
449
|
+
))}
|
|
450
|
+
</div>
|
|
451
|
+
<div style={{ display: "flex" }}>
|
|
452
|
+
<div
|
|
453
|
+
{...slot("timeAxis", {
|
|
454
|
+
base: {
|
|
455
|
+
position: "relative",
|
|
456
|
+
width: GUTTER_WIDTH,
|
|
457
|
+
flex: "none",
|
|
458
|
+
height: trackHeight,
|
|
459
|
+
},
|
|
460
|
+
})}
|
|
461
|
+
>
|
|
462
|
+
{hours.map((h) => (
|
|
463
|
+
<div
|
|
464
|
+
key={h}
|
|
465
|
+
{...slot("hourTick", {
|
|
466
|
+
base: {
|
|
467
|
+
position: "absolute",
|
|
468
|
+
top: Math.max(0, (h - startHour) * hourHeight - 6),
|
|
469
|
+
right: 6,
|
|
470
|
+
},
|
|
471
|
+
themed: { fontSize: 10, color: theme.textMuted },
|
|
472
|
+
})}
|
|
473
|
+
>
|
|
474
|
+
{formatHour(h, { ampm })}
|
|
475
|
+
</div>
|
|
476
|
+
))}
|
|
477
|
+
</div>
|
|
478
|
+
{resources.map((resource) => {
|
|
479
|
+
const positioned = byResource.get(resource.id) ?? [];
|
|
480
|
+
return (
|
|
481
|
+
<div
|
|
482
|
+
key={resource.id}
|
|
483
|
+
{...slot("row", {
|
|
484
|
+
base: { flex: 1, minWidth: 0 },
|
|
485
|
+
themed: { borderLeft: `1px solid ${theme.gridLine}` },
|
|
486
|
+
})}
|
|
487
|
+
>
|
|
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
|
+
))}
|
|
511
|
+
<div
|
|
512
|
+
aria-hidden
|
|
513
|
+
{...slot("gridLines", {
|
|
514
|
+
base: { position: "absolute", inset: 0, pointerEvents: "none" },
|
|
515
|
+
themed: { backgroundImage: vGridLines },
|
|
516
|
+
})}
|
|
517
|
+
/>
|
|
518
|
+
{positioned.map((pe, idx) => {
|
|
519
|
+
const key = `${resource.id}:${idx}`;
|
|
520
|
+
const active = drag?.key === key ? drag : null;
|
|
521
|
+
const startH = active ? active.startHours : pe.startHours;
|
|
522
|
+
const durH = active ? active.durationHours : pe.durationHours;
|
|
523
|
+
const top = clamp(startH - startHour, 0, endHour - startHour) * hourHeight;
|
|
524
|
+
const bottom =
|
|
525
|
+
clamp(startH + durH - startHour, 0, endHour - startHour) * hourHeight;
|
|
526
|
+
const height = Math.max(bottom - top, 2);
|
|
527
|
+
// Overlapping events share the column as side-by-side sub-lanes.
|
|
528
|
+
const lanePct = 100 / pe.columns;
|
|
529
|
+
const onPress = () => onPressEvent?.(pe.event);
|
|
530
|
+
const args: ResourceEventArgs<T> = {
|
|
531
|
+
event: pe.event,
|
|
532
|
+
width: 0,
|
|
533
|
+
height,
|
|
534
|
+
onPress,
|
|
535
|
+
};
|
|
536
|
+
const draggable = !!onDragEvent;
|
|
537
|
+
return (
|
|
538
|
+
<button
|
|
539
|
+
key={idx}
|
|
540
|
+
type="button"
|
|
541
|
+
onClick={draggable ? undefined : onPress}
|
|
542
|
+
aria-label={pe.event.title}
|
|
543
|
+
{...dataState({ "data-dragging": !!active })}
|
|
544
|
+
onPointerDown={
|
|
545
|
+
draggable
|
|
546
|
+
? (e) => beginDrag(e, key, "move", pe.startHours, pe.durationHours)
|
|
547
|
+
: undefined
|
|
548
|
+
}
|
|
549
|
+
onPointerMove={draggable ? moveDrag : undefined}
|
|
550
|
+
onPointerUp={draggable ? (e) => endDrag(e, pe.event, onPress) : undefined}
|
|
551
|
+
onPointerCancel={draggable ? cancelDrag : undefined}
|
|
552
|
+
{...slot("event", {
|
|
553
|
+
base: {
|
|
554
|
+
position: "absolute",
|
|
555
|
+
top,
|
|
556
|
+
height,
|
|
557
|
+
left: `${pe.column * lanePct}%`,
|
|
558
|
+
width: `${lanePct}%`,
|
|
559
|
+
padding: 1,
|
|
560
|
+
border: "none",
|
|
561
|
+
background: "transparent",
|
|
562
|
+
cursor: draggable ? "grab" : "pointer",
|
|
563
|
+
touchAction: draggable ? "none" : "auto",
|
|
564
|
+
font: "inherit",
|
|
565
|
+
textAlign: "left",
|
|
566
|
+
boxSizing: "border-box",
|
|
567
|
+
zIndex: active ? 3 : 1,
|
|
568
|
+
opacity: active ? 0.85 : 1,
|
|
569
|
+
},
|
|
570
|
+
})}
|
|
571
|
+
>
|
|
572
|
+
{Renderer ? (
|
|
573
|
+
<Renderer {...args} />
|
|
574
|
+
) : (
|
|
575
|
+
<DefaultBar {...args} theme={theme} boxProps={slot("eventBox")} />
|
|
576
|
+
)}
|
|
577
|
+
{draggable ? (
|
|
578
|
+
<span
|
|
579
|
+
aria-hidden
|
|
580
|
+
onPointerDown={(e) => {
|
|
581
|
+
e.stopPropagation();
|
|
582
|
+
beginDrag(e, key, "resize", pe.startHours, pe.durationHours);
|
|
583
|
+
}}
|
|
584
|
+
style={{
|
|
585
|
+
position: "absolute",
|
|
586
|
+
left: 0,
|
|
587
|
+
right: 0,
|
|
588
|
+
bottom: 0,
|
|
589
|
+
height: 8,
|
|
590
|
+
cursor: "ns-resize",
|
|
591
|
+
touchAction: "none",
|
|
592
|
+
}}
|
|
593
|
+
/>
|
|
594
|
+
) : null}
|
|
595
|
+
</button>
|
|
596
|
+
);
|
|
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}
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
);
|
|
623
|
+
})}
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
278
629
|
const gridLines = `repeating-linear-gradient(to right, transparent 0, transparent ${hourWidth - 1}px, ${theme.gridLine} ${hourWidth - 1}px, ${theme.gridLine} ${hourWidth}px)`;
|
|
279
630
|
|
|
280
631
|
return (
|
|
@@ -338,7 +689,29 @@ export function ResourceTimeline<T = unknown>({
|
|
|
338
689
|
>
|
|
339
690
|
{resource.title ?? resource.id}
|
|
340
691
|
</div>
|
|
341
|
-
<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
|
+
))}
|
|
342
715
|
<div
|
|
343
716
|
aria-hidden
|
|
344
717
|
{...slot("gridLines", {
|
|
@@ -421,6 +794,28 @@ export function ResourceTimeline<T = unknown>({
|
|
|
421
794
|
</button>
|
|
422
795
|
);
|
|
423
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}
|
|
424
819
|
</div>
|
|
425
820
|
</div>
|
|
426
821
|
);
|