import { SvelteSet } from 'svelte/reactivity'; import { type BinItem, type ClipTransition, type ProjectBackground, type TextClipStyle, type TimelineAspectRatio, type TimelineClip, type TimelineFps, type TimelineMarker, type TimelineProject } from '../types/timeline.js'; import { PlaybackEngine } from './playback.js'; import { type Gap, type OpFailReason } from './ops.js'; export type TimelineEditorDeps = { /** Debounced project-out; the host owns persistence. */ emitChange: (project: TimelineProject) => void; /** Surface an aborted operation (toast etc.). */ notify?: (reason: OpFailReason) => void; /** CapCut-style magnetic first track (host/prop controlled). */ magneticMainTrack?: () => boolean; /** Debounce window (ms) for emitChange; 0 fires synchronously. */ emitDebounceMs?: () => number; }; export type DragState = { clipIds: string[]; primaryId: string; /** Snapped horizontal delta, frames. */ dxF: number; /** Cosmetic vertical float, px. */ dyPx: number; targetTrackId: string | null; insert: boolean; duplicate: boolean; }; export declare class TimelineEditorStore { #private; project: TimelineProject; /** Float seconds for smooth playback; quantized to frames at every edit. */ playhead: number; playing: boolean; snapping: boolean; loopRange: boolean; followPlayhead: boolean; selectedClipIds: SvelteSet; activeClipId: string | null; selectedGap: Gap | null; dragState: DragState | null; /** Bumped when the user explicitly asks to see a clip's options (e.g. taps a * clip without dragging on mobile). The host editor watches this to open the * inspector sheet — decoupled from selection so a drag doesn't trigger it. */ inspectorRequestSeq: number; canUndo: boolean; canRedo: boolean; readonly fps: 24 | 25 | 30 | 50 | 60; readonly playheadF: number; readonly durationF: number; readonly duration: number; readonly clipsByTrack: Map; readonly activeClip: TimelineClip | null; readonly selectedClips: TimelineClip[]; /** Link partners of the selection — highlighted but NOT selected. */ readonly linkedHighlightIds: Set; readonly hasClipboard: boolean; readonly engine: PlaybackEngine; constructor(project: TimelineProject, deps: TimelineEditorDeps); destroy(): void; seek(t: number): void; seekFrame(frame: number): void; stepFrames(delta: number): void; play(): void; pause(): void; togglePlay(): void; selectClip(clipId: string, additive: boolean): void; selectGap(trackId: string, frame: number): void; clearSelection(): void; /** Signal that the active clip's options should be shown (mobile sheet). */ requestInspector(): void; addTrack(name: string): void; removeTrack(trackId: string): void; renameTrack(trackId: string, name: string): void; moveTrack(trackId: string, toIndex: number): void; setTrackHeight(trackId: string, height: number): void; toggleTrackMuted(trackId: string): void; toggleTrackSolo(trackId: string): void; toggleTrackHidden(trackId: string): void; toggleTrackLocked(trackId: string): void; addBinItems(items: BinItem[]): void; removeBinItem(itemId: string): void; addClipFromBin(item: BinItem, trackId: string, atF: number, insert: boolean): void; addTextClip(trackId: string, atF: number, text: string): void; replaceClipFromBin(clipId: string, item: BinItem): boolean; beginDrag(primaryId: string, duplicate: boolean): void; updateDrag(patch: Partial>): void; commitDrag(): void; /** Shift all selected (unlocked) clips by delta frames, clamped at 0. */ nudgeSelected(deltaF: number): void; trimClip(clipId: string, edge: 'left' | 'right', targetF: number): void; rollEdit(leftId: string, rightId: string, deltaF: number): void; slipEdit(clipId: string, deltaF: number): void; splitAtPlayhead(): void; splitAllAtPlayhead(): void; deleteSelected(): void; rippleDeleteSelected(): void; closeSelectedGap(allTracks: boolean): void; deleteRange(): void; copySelection(): void; cutSelection(): void; paste(): void; duplicateSelected(): void; detachAudio(clipId: string): void; toggleLink(clipId: string): void; groupSelection(): void; ungroupSelection(): void; toggleClipLock(clipId: string): void; setClipVolume(clipId: string, volume: number): void; setClipFade(clipId: string, edge: 'in' | 'out', frames: number): void; /** Set or clear one side of a clip's enter/exit transition (media or text). * Pass `null` to remove that side; clearing both drops `animation` entirely. */ setClipAnimation(clipId: string, side: 'in' | 'out', transition: ClipTransition | null): void; addCrossfade(leftId: string, rightId: string): void; updateTextClip(clipId: string, patch: { text?: string; style?: Partial; }): void; addMarkerAtPlayhead(): void; updateMarker(id: string, patch: Partial>): void; removeMarker(id: string): void; jumpToMarker(direction: 1 | -1): void; setRangeIn(): void; setRangeOut(): void; clearRange(): void; setAspectRatio(aspectRatio: TimelineAspectRatio): void; setFps(fps: TimelineFps): void; setBackground(background: ProjectBackground | null): void; setZoom(pxPerSecond: number): void; renameProject(name: string): void; /** Candidate snap frames: clip edges, markers, the playhead, and 0. */ snapCandidatesF(excludeIds: ReadonlySet): number[]; undo(): void; redo(): void; beginGesture(): void; endGesture(): void; flush(): void; } export declare function setTimelineEditor(store: TimelineEditorStore): TimelineEditorStore; export declare function useTimelineEditor(): TimelineEditorStore;