1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
"use client";
import { useElementSelection } from "@/timeline/hooks/element/use-element-selection";
import { TimelineElement } from "./timeline-element";
import type { TimelineTrack } from "@/timeline";
import type { TimelineElement as TimelineElementType } from "@/timeline";
import { TIMELINE_LAYERS } from "./layers";
import type { ElementDragView } from "@/timeline";
interface TimelineTrackContentProps {
track: TimelineTrack;
zoomLevel: number;
dragView: ElementDragView;
onResizeStart: (params: {
event: React.MouseEvent;
element: TimelineElementType;
track: TimelineTrack;
side: "left" | "right";
}) => void;
onElementMouseDown: (params: {
event: React.MouseEvent;
element: TimelineElementType;
track: TimelineTrack;
}) => void;
onElementClick: (params: {
event: React.MouseEvent;
element: TimelineElementType;
track: TimelineTrack;
}) => void;
onTrackMouseDown?: (event: React.MouseEvent) => void;
onTrackMouseUp?: (event: React.MouseEvent) => void;
shouldIgnoreClick?: () => boolean;
targetElementId?: string | null;
}
export function TimelineTrackContent({
track,
zoomLevel,
dragView,
onResizeStart,
onElementMouseDown,
onElementClick,
onTrackMouseDown,
onTrackMouseUp,
shouldIgnoreClick,
targetElementId = null,
}: TimelineTrackContentProps) {
const { isElementSelected } = useElementSelection();
return (
<div className="relative size-full">
<button
type="button"
className="absolute inset-0 m-0 size-full appearance-none border-0 bg-transparent p-0"
aria-label={`Select ${track.name} track`}
onMouseUp={(event) => {
if (shouldIgnoreClick?.()) return;
onTrackMouseUp?.(event);
}}
onMouseDown={(event) => {
event.preventDefault();
onTrackMouseDown?.(event);
}}
/>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- spatial gesture surface; the wrapping <button> handles keyboard track selection, this <div> only forwards background clicks for box-select / deselect. */}
<div
className="relative h-full min-w-full"
style={{ zIndex: TIMELINE_LAYERS.trackContent }}
onMouseUp={(event) => {
if (event.target !== event.currentTarget) return;
if (shouldIgnoreClick?.()) return;
onTrackMouseUp?.(event);
}}
onMouseDown={(event) => {
if (event.target !== event.currentTarget) return;
event.preventDefault();
onTrackMouseDown?.(event);
}}
>
{track.elements.length === 0 ? (
<div className="text-muted-foreground border-muted/30 pointer-events-none flex size-full items-center justify-center rounded-sm border-2 border-dashed text-xs" />
) : (
track.elements.map((element) => {
const isSelected = isElementSelected({
trackId: track.id,
elementId: element.id,
});
return (
<TimelineElement
key={element.id}
element={element}
track={track}
zoomLevel={zoomLevel}
isSelected={isSelected}
onResizeStart={({ event, element, side }) =>
onResizeStart({ event, element, track, side })
}
onElementMouseDown={({ event, element }) =>
onElementMouseDown({ event, element, track })
}
onElementClick={({ event, element }) =>
onElementClick({ event, element, track })
}
dragView={dragView}
isDropTarget={element.id === targetElementId}
/>
);
})
)}
</div>
</div>
);
}
|