import { type ReactNode } from "react"; import type { GsapAnimation } from "@hyperframes/core/gsap-parser"; import type { MusicBeatAnalysis } from "@hyperframes/core/beats"; import type { TimelineElement, KeyframeCacheEntry } from "../store/playerStore"; import type { TimelineKeyframeTarget } from "./timelineKeyframeIdentity"; import type { TimelineTheme } from "./timelineTheme"; import type { TrackVisualStyle } from "./timelineIcons"; import type { DraggedClipState, ResizingClipState, BlockedClipState } from "./useTimelineClipDrag"; import type { TimelineClipIndex, TimelineTimeRange } from "../lib/timelineClipIndex"; import type { TimelineRowGeometry } from "./timelineLayout"; import type { TimelineVirtualRow } from "./useTimelineVirtualRows"; import type { TimelineLogicalRow } from "./timelineKeyboardNavigation"; import type { TimelineClipRenderContext } from "./TimelineTypes"; /** * Props shared by the scroll container ({@link import("./TimelineCanvas")}) and * the lane renderer ({@link import("./TimelineLanes")}). TimelineCanvas passes * these straight through via spread, so they are declared once here and both * prop types compose from this base — no duplicated prop list. Kept in its own * module so neither side has to import the other just to name the contract. */ export interface TimelineLaneBaseProps { pps: number; contentOrigin: number; contentGutter: number; trackContentWidth: number; theme: TimelineTheme; displayTrackOrder: number[]; rowHeights: readonly number[]; rowGeometry: TimelineRowGeometry; virtualRows: readonly TimelineVirtualRow[]; logicalRows: readonly TimelineLogicalRow[]; focusedTargetId: string | null; rowsVirtualized: boolean; clipIndex: TimelineClipIndex; renderTimeRange: TimelineTimeRange; visibleTimeRange: TimelineTimeRange; pinnedClipIdentities: ReadonlySet; trackOrder: number[]; tracks: [number, TimelineElement[]][]; trackStyles: Map; laneCounts: ReadonlyMap; selectedElementId: string | null; selectedElementIds: Set; hoveredClip: string | null; draggedClip: DraggedClipState | null; blockedClipRef: React.RefObject; suppressClickRef: React.RefObject; scrollRef: React.RefObject; renderClipContent?: ( element: TimelineElement, style: { clip: string; label: string }, context: TimelineClipRenderContext, ) => ReactNode; renderClipOverlay?: (element: TimelineElement) => ReactNode; onDrillDown?: (element: TimelineElement) => void; onSelectElement?: (element: TimelineElement | null) => void; setHoveredClip: (key: string | null) => void; setShowPopover: (v: boolean) => void; setRangeSelection: (v: null) => void; setResizingClip: (v: ResizingClipState | null) => void; setDraggedClip: (v: DraggedClipState | null) => void; setSelectedElementId: (id: string | null) => void; shiftClickClipRef: React.RefObject<{ element: TimelineElement; anchorX: number; anchorY: number; } | null>; getPreviewElement: (element: TimelineElement) => TimelineElement; getTrackStyle: (tag: string) => TrackVisualStyle; keyframeCache?: Map; gsapAnimations: Map; selectedKeyframes: Set; currentTime: number; onSeek?: (time: number) => void; onSelectSegment?: (elementId: string, target: TimelineKeyframeTarget) => void; onClickKeyframe?: (element: TimelineElement, target: TimelineKeyframeTarget) => void; onShiftClickKeyframe?: (elementId: string, target: TimelineKeyframeTarget) => void; onContextMenuKeyframe?: ( e: React.MouseEvent, elementId: string, target: TimelineKeyframeTarget, ) => void; onMoveKeyframe?: ( elementId: string, keyframe: TimelineKeyframeTarget, toClipPercentage: number, propertyGroup?: string, tweenPercentage?: number, animationId?: string, ) => Promise; onContextMenuClip?: (e: React.MouseEvent, element: TimelineElement) => void; /** * Right-click on EMPTY lane space (not on a clip — those preventDefault * before this fires — not the gutter/ruler, not below the lanes). `time` is * the timeline time (seconds) under the pointer on that lane. */ onContextMenuLane?: (e: React.MouseEvent, track: number, time: number) => void; beatAnalysis?: MusicBeatAnalysis | null; }