@linker-design-plus/timeline-track 2.0.2 → 2.0.4

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/README.md CHANGED
@@ -120,7 +120,7 @@ await timeline.refreshAllClipThumbnails();
120
120
  - `setZoom(zoom)` / `getZoom()`
121
121
  - `setSpeed(speed)` / `getSpeed()`
122
122
  - `addClip(config)` / `addClips(configs)`
123
- - `updateClip(clipId, updates)` / `removeClip(clipId)`
123
+ - `updateClip(clipId, updates)` / `removeClip(clipId)` / `removeClipsByExternalId(externalId)`
124
124
  - `selectClip(clipId)` / `clearSelection()` / `getSelectedClip()`
125
125
  - `canSeparateClipAudio(clipId)` / `separateClipAudio(clipId)` / `canRestoreClipAudio(clipId)` / `restoreClipAudio(clipId)`
126
126
  - `splitCurrentClip()` / `removeClipGaps()` / `fitZoom()`
@@ -1,20 +1,33 @@
1
1
  import type { TimeMs, Theme } from '../../core/models/types';
2
2
  export declare class ManagedPlayhead {
3
+ private static readonly SVG_NS;
4
+ private static readonly VISUAL_WIDTH;
5
+ private static readonly HANDLE_HEIGHT;
6
+ private static readonly HANDLE_LINE_START;
7
+ private static readonly LINE_WIDTH;
8
+ private static readonly LINE_HIT_WIDTH;
9
+ private static readonly END_CAP_WIDTH;
10
+ private static readonly END_CAP_HEIGHT;
11
+ private static readonly AUTO_SCROLL_EDGE_THRESHOLD;
12
+ private static readonly AUTO_SCROLL_MAX_SPEED;
3
13
  private readonly container;
4
- private readonly lineElement;
5
- private readonly handleElement;
14
+ private readonly visualElement;
15
+ private readonly lineHitAreaElement;
6
16
  private readonly theme;
7
17
  private readonly onTimeChange;
18
+ private readonly onHorizontalAutoScroll?;
8
19
  private currentTime;
9
20
  private zoom;
10
21
  private scrollLeft;
11
22
  private height;
12
23
  private isDragging;
13
24
  private hasBoundGlobalPointerListeners;
25
+ private edgeAutoScrollAnimationFrameId;
26
+ private lastPointerClientX;
14
27
  private handleGlobalPointerMove;
15
28
  private handleGlobalPointerEnd;
16
29
  private handleVisibilityChange;
17
- constructor(container: HTMLElement, initialTime: TimeMs, zoom: number, height: number, theme: Theme, onTimeChange: (time: TimeMs, mode?: 'seek' | 'scrub') => void);
30
+ constructor(container: HTMLElement, initialTime: TimeMs, zoom: number, height: number, theme: Theme, onTimeChange: (time: TimeMs, mode?: 'seek' | 'scrub') => void, onHorizontalAutoScroll?: (nextScrollLeft: number) => number);
18
31
  setCurrentTime(time: TimeMs): void;
19
32
  setTime(time: TimeMs): void;
20
33
  setZoom(zoom: number): void;
@@ -26,5 +39,16 @@ export declare class ManagedPlayhead {
26
39
  private bindGlobalPointerListeners;
27
40
  private unbindGlobalPointerListeners;
28
41
  private updateTimeFromClientX;
42
+ private updateEdgeAutoScrollState;
43
+ private calculateHorizontalAutoScrollVelocity;
44
+ private startEdgeAutoScroll;
45
+ private stopEdgeAutoScroll;
46
+ private readonly handleEdgeAutoScrollFrame;
29
47
  private render;
48
+ private renderVisual;
49
+ private createHandleShadowPath;
50
+ private createHandleBadgePath;
51
+ private createGripLines;
52
+ private createMainLine;
53
+ private createEndCap;
30
54
  }
@@ -1,5 +1,6 @@
1
1
  import Konva from 'konva';
2
2
  import { Clip as ClipType, ClipStateUpdate, TrackConfig, TimeMs, Theme, TrackType } from '../../core/models';
3
+ import type { MultiDragMoveRequest } from '../../core/tracks/timelineTrackBridge';
3
4
  export declare class Track {
4
5
  private static readonly DEFAULT_DRAG_ACTIVATION_THRESHOLD;
5
6
  private static readonly DEFAULT_CLIP_SNAP_THRESHOLD;
@@ -7,7 +8,9 @@ export declare class Track {
7
8
  private static readonly AUTO_SCROLL_MAX_SPEED;
8
9
  private layer;
9
10
  private trackGroup;
11
+ private dragOverlayLayer;
10
12
  private dropPreviewGroup;
13
+ private dropPreviewClips;
11
14
  private config;
12
15
  private theme;
13
16
  private trackType;
@@ -19,6 +22,9 @@ export declare class Track {
19
22
  private trackHeight;
20
23
  private selectedClip;
21
24
  private hasSelectedClip;
25
+ private selectedClipIds;
26
+ private multiDragOriginalPositions;
27
+ private promotedClipParents;
22
28
  private interactionState;
23
29
  private isVisualUpdate;
24
30
  private onClipUpdate;
@@ -37,6 +43,11 @@ export declare class Track {
37
43
  private onClearDropPreview?;
38
44
  private onClearSelection?;
39
45
  private onSnapGuideChange?;
46
+ private onClipToggleSelection?;
47
+ private onSetSingleSelection?;
48
+ private getMultiDragClipIds?;
49
+ private onMultiDragMove?;
50
+ private onMultiDragInteractionEnd?;
40
51
  private resolveSnapTargetClips?;
41
52
  private readonly dragActivationThreshold;
42
53
  private enableClipSnap;
@@ -44,9 +55,10 @@ export declare class Track {
44
55
  private handleGlobalPointerMove;
45
56
  private handleGlobalPointerEnd;
46
57
  private handleWindowBlur;
47
- constructor(layer: Konva.Layer, config: TrackConfig, trackType: TrackType, zoom: number, trackY: number, trackHeight: number, theme: Theme, onClipUpdate: (clip: ClipType, originalClip?: ClipType, clipUpdates?: ClipStateUpdate[]) => void, onClipAdd: (clip: ClipType) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: ClipType, clip2: ClipType) => void, onClipSelect: (clip: ClipType) => void, onTimeJump: (time: TimeMs) => void, onHorizontalDragAutoScroll?: (nextScrollLeft: number) => number, onClipOverlap?: (clip: ClipType, currentTrackId: string) => void, onClipCrossTrackPreview?: (clip: ClipType, targetTrackY: number, currentTrackId: string) => 'self' | 'external' | 'clear', onClipCrossTrack?: (clip: ClipType, originalClip: ClipType | null, targetTrackY: number, currentTrackId: string) => boolean | void, onClearDropPreview?: () => void, onClearSelection?: () => void, onSnapGuideChange?: (guideTime: TimeMs | null) => void, dragActivationThreshold?: number, enableClipSnap?: boolean, clipSnapThreshold?: number);
58
+ constructor(layer: Konva.Layer, config: TrackConfig, trackType: TrackType, zoom: number, trackY: number, trackHeight: number, theme: Theme, onClipUpdate: (clip: ClipType, originalClip?: ClipType, clipUpdates?: ClipStateUpdate[]) => void, onClipAdd: (clip: ClipType) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: ClipType, clip2: ClipType) => void, onClipSelect: (clip: ClipType) => void, onTimeJump: (time: TimeMs) => void, onHorizontalDragAutoScroll?: (nextScrollLeft: number) => number, onClipOverlap?: (clip: ClipType, currentTrackId: string) => void, onClipCrossTrackPreview?: (clip: ClipType, targetTrackY: number, currentTrackId: string) => 'self' | 'external' | 'clear', onClipCrossTrack?: (clip: ClipType, originalClip: ClipType | null, targetTrackY: number, currentTrackId: string) => boolean | void, onClearDropPreview?: () => void, onClearSelection?: () => void, onSnapGuideChange?: (guideTime: TimeMs | null) => void, onClipToggleSelection?: (clipId: string) => void, onSetSingleSelection?: (clipId: string) => void, getMultiDragClipIds?: (clipId: string) => string[] | null, onMultiDragMove?: (request: MultiDragMoveRequest) => boolean | void, onMultiDragInteractionEnd?: () => void, dragActivationThreshold?: number, enableClipSnap?: boolean, clipSnapThreshold?: number);
48
59
  private initClips;
49
60
  private ensureDropPreviewGroup;
61
+ private createDropPreviewRect;
50
62
  private showDropPreview;
51
63
  private hideDropPreview;
52
64
  private initEventListeners;
@@ -55,6 +67,8 @@ export declare class Track {
55
67
  private handleVisibilityChange;
56
68
  private readonly handleEdgeAutoScrollFrame;
57
69
  private handleTrackBackgroundClick;
70
+ private handleTrackBackgroundMouseDown;
71
+ updateClipSelection(clipId: string, isSelected: boolean): void;
58
72
  private createClipGroup;
59
73
  private handleClipClick;
60
74
  private handleClipMouseDown;
@@ -85,6 +99,10 @@ export declare class Track {
85
99
  setScrollLeft(scrollLeft: number): void;
86
100
  private updateClipVisibility;
87
101
  setTrackY(trackY: number): void;
102
+ setTrackStackOrder(order: number): void;
103
+ setDragOverlayLayer(layer: Konva.Layer | null): void;
104
+ setClipsDragOverlayActive(clipIds: string[], isActive: boolean): void;
105
+ clearClipDragOverlay(): void;
88
106
  getTrackType(): TrackType;
89
107
  render(shouldBatchDraw?: boolean): void;
90
108
  private getTrackBackgroundFill;
@@ -94,12 +112,16 @@ export declare class Track {
94
112
  updateSelectionVisual(selectedClipId: string | null): void;
95
113
  selectClip(clipId: string): void;
96
114
  private applySelectionVisual;
115
+ private syncSelectionState;
97
116
  splitSelectedClip(time: TimeMs): void;
98
117
  removeClipGaps(): void;
99
118
  getTrackGroup(): Konva.Group;
100
119
  getId(): string;
120
+ updateClipPosition(clipId: string, newStartTime: TimeMs, isFinal: boolean, previewOffsetY?: number): void;
121
+ updateClipPositionFinal(clipId: string, newStartTime: TimeMs): void;
101
122
  setTrackHeight(height: number): void;
102
123
  showClipDropPreview(clip: ClipType): void;
124
+ showClipDropPreviews(clips: ClipType[]): void;
103
125
  clearClipDropPreview(): void;
104
126
  getRole(): 'primary' | 'normal';
105
127
  setClipSnapEnabled(enabled: boolean): void;
@@ -113,6 +135,9 @@ export declare class Track {
113
135
  private buildResizeRightPreviewClip;
114
136
  private applyClipSnap;
115
137
  private applyPreviewClipState;
138
+ private setClipDragOverlayActive;
139
+ private resolveClipRenderY;
140
+ private getPromotedClipParents;
116
141
  private updateSnapGuideLine;
117
142
  private updateEdgeAutoScrollState;
118
143
  private calculateHorizontalAutoScrollVelocity;
@@ -35,7 +35,21 @@ export interface DraggingResizeRightTrackInteractionState {
35
35
  kind: 'draggingResizeRight';
36
36
  context: TrackInteractionContext;
37
37
  }
38
- export type TrackInteractionState = IdleTrackInteractionState | PressedTrackInteractionState | DraggingMoveTrackInteractionState | DraggingResizeLeftTrackInteractionState | DraggingResizeRightTrackInteractionState;
38
+ export interface BoxSelectingTrackInteractionState {
39
+ kind: 'boxSelecting';
40
+ context: {
41
+ startClientX: number;
42
+ startClientY: number;
43
+ startLocalX: number;
44
+ startLocalY: number;
45
+ currentClientX: number;
46
+ currentClientY: number;
47
+ currentLocalX: number;
48
+ currentLocalY: number;
49
+ additive: boolean;
50
+ };
51
+ }
52
+ export type TrackInteractionState = IdleTrackInteractionState | PressedTrackInteractionState | DraggingMoveTrackInteractionState | DraggingResizeLeftTrackInteractionState | DraggingResizeRightTrackInteractionState | BoxSelectingTrackInteractionState;
39
53
  export type TrackInteractionEvent = {
40
54
  type: 'POINTER_DOWN';
41
55
  operation: TrackPointerOperation;
@@ -58,6 +72,21 @@ export type TrackInteractionEvent = {
58
72
  type: 'SCROLL_LEFT_CHANGED';
59
73
  } | {
60
74
  type: 'POINTER_UP' | 'VISIBILITY_HIDDEN' | 'WINDOW_BLUR' | 'CANCEL';
75
+ } | {
76
+ type: 'BOX_SELECT_START';
77
+ clientX: number;
78
+ clientY: number;
79
+ localX: number;
80
+ localY: number;
81
+ additive: boolean;
82
+ } | {
83
+ type: 'BOX_SELECT_MOVE';
84
+ clientX: number;
85
+ clientY: number;
86
+ localX: number;
87
+ localY: number;
88
+ } | {
89
+ type: 'BOX_SELECT_END';
61
90
  };
62
91
  export interface TrackInteractionTransitionEffects {
63
92
  bindGlobalListeners?: boolean;
@@ -77,3 +106,10 @@ export declare function getTrackInteractionContext(state: TrackInteractionState
77
106
  export declare function getTrackInteractionOperation(state: TrackInteractionState | null | undefined): TrackPointerOperation | null;
78
107
  export declare function getTrackInteractionCursor(state: TrackInteractionState | null | undefined): 'ew-resize' | 'grabbing' | null;
79
108
  export declare function transitionTrackInteraction(state: TrackInteractionState, event: TrackInteractionEvent): TrackInteractionTransitionResult;
109
+ export declare function isBoxSelecting(state: TrackInteractionState | null | undefined): boolean;
110
+ export declare function getBoxSelectRect(state: TrackInteractionState | null | undefined): {
111
+ x1: number;
112
+ y1: number;
113
+ x2: number;
114
+ y2: number;
115
+ } | null;
@@ -8,6 +8,7 @@ interface SyncTrackDurationChangeOptions {
8
8
  setTimelineDuration: (duration: TimeMs) => void;
9
9
  emitTrackDurationChange: (duration: TimeMs) => void;
10
10
  }
11
+ export declare function calculateTrackTimelineExtent(clips: Pick<Clip, 'startTime' | 'duration'>[]): TimeMs;
11
12
  export declare function calculateTrackTotalDuration(clips: Pick<Clip, 'startTime' | 'duration'>[]): TimeMs;
12
13
  export declare function calculateTimelineDuration(trackDuration: TimeMs): TimeMs;
13
14
  export declare function syncTrackDurationChange(options: SyncTrackDurationChangeOptions): TimeMs;
@@ -29,6 +29,7 @@ interface ClipMutationSideEffectHandlers {
29
29
  updateCanPlayState?: () => void;
30
30
  updateTrackInfoPanel?: () => void;
31
31
  handleClipChange?: () => void;
32
+ cleanupEmptyTracks?: () => void;
32
33
  }
33
34
  interface ClipMoveSideEffectHandlers {
34
35
  notifySelectionChange: () => void;
@@ -18,7 +18,13 @@ export declare class TimelineManager {
18
18
  private headerLayer;
19
19
  private backgroundLayer;
20
20
  private trackLayer;
21
+ private dragOverlayLayer;
21
22
  private snapGuideLayer;
23
+ private selectionBoxLayer;
24
+ private selectionBoxRect;
25
+ private isBoxSelecting;
26
+ private boxSelectStartX;
27
+ private boxSelectStartY;
22
28
  private resizeObserver;
23
29
  private rootContainer;
24
30
  private layout;
@@ -31,6 +37,7 @@ export declare class TimelineManager {
31
37
  private verticalScrollbar;
32
38
  private isExecutingHistoryAction;
33
39
  private lastTrackDuration;
40
+ private lastEffectiveTrackDuration;
34
41
  private thumbnailProvider;
35
42
  private canPlay;
36
43
  private sourceLoadingCount;
@@ -57,6 +64,9 @@ export declare class TimelineManager {
57
64
  private readonly rootWheelListener;
58
65
  private mountManager;
59
66
  private pendingDraftData;
67
+ private selectionStore;
68
+ private multiDragSession;
69
+ private clipRemovalBatchDepth;
60
70
  constructor(config?: Partial<TimelineConfig>);
61
71
  private getTimelineStore;
62
72
  private getTimelineCommands;
@@ -136,6 +146,7 @@ export declare class TimelineManager {
136
146
  private cleanupEmptyTracks;
137
147
  private clearAllTrackDropPreviews;
138
148
  private showClipDropPreview;
149
+ private showClipDropPreviews;
139
150
  private ensureTrackInsertionPreviewLine;
140
151
  private refreshTrackInsertionPreview;
141
152
  private showTrackInsertionPreview;
@@ -237,6 +248,7 @@ export declare class TimelineManager {
237
248
  addClips(clipConfigs: ClipConfig[]): Promise<string[]>;
238
249
  private addClipInternal;
239
250
  removeClip(clipId: string): void;
251
+ removeClipsByExternalId(externalId: string): boolean;
240
252
  updateClip(clipId: string, updates: Partial<Clip>): void;
241
253
  splitClip(clipId: string, time: TimeMs): void;
242
254
  splitCurrentClip(): void;
@@ -307,11 +319,22 @@ export declare class TimelineManager {
307
319
  * @param clipId 目标片段的 ID
308
320
  */
309
321
  private notifySelectionChange;
322
+ private syncPrimarySelectionFromSelectionStore;
310
323
  selectClip(clipId: string, clip?: any): void;
311
324
  /**
312
325
  * 清空所有轨道的选中状态
313
326
  */
314
327
  clearSelection(): void;
328
+ getSelectedClipIds(): string[];
329
+ addToSelection(clipId: string): void;
330
+ removeFromSelection(clipId: string): void;
331
+ toggleSelection(clipId: string): void;
332
+ isClipSelected(clipId: string): boolean;
333
+ deleteSelectedClips(): void;
334
+ separateSelectedClipsAudio(): void;
335
+ selectAllClips(): void;
336
+ private emitSelectionChangeEvent;
337
+ private updateAllTracksSelectionVisual;
315
338
  /**
316
339
  * 滚动到指定片段
317
340
  * @param clipId 目标片段的 ID
@@ -330,6 +353,11 @@ export declare class TimelineManager {
330
353
  * @returns 当前选中的 clip,如果没有则返回 null
331
354
  */
332
355
  getSelectedClip(): Clip | null;
356
+ getSelectedClips(): Clip[];
357
+ canDeleteSelectedClips(): boolean;
358
+ getSelectedClipAudioAction(): 'separate' | 'restore' | null;
359
+ canToggleSelectedClipsAudio(): boolean;
360
+ toggleSelectedClipsAudio(): Promise<boolean>;
333
361
  getCurrentActiveClips(): ActiveClipPlaybackInfo[];
334
362
  getCurrentPlaybackPlan(): ResolvedPlaybackPlan;
335
363
  getPlaybackPlanAtTime(time: TimeMs): ResolvedPlaybackPlan;
@@ -349,10 +377,13 @@ export declare class TimelineManager {
349
377
  * @returns 是否成功删除了选中的 clip
350
378
  */
351
379
  removeSelectedClip(): boolean;
380
+ removeSelectedClips(): boolean;
352
381
  canSeparateClipAudio(clipId: string): boolean;
353
382
  canRestoreClipAudio(clipId: string): boolean;
383
+ private resolvePreferredSeparatedAudioTrackId;
354
384
  separateClipAudio(clipId: string): Promise<string | null>;
355
385
  restoreClipAudio(clipId: string): boolean;
386
+ private applySelectedClipAudioAction;
356
387
  /**
357
388
  * 将片段从一个轨道移动到另一个轨道
358
389
  * @param clipId 要移动的片段 ID
@@ -362,17 +393,60 @@ export declare class TimelineManager {
362
393
  moveClipToTrack(clipId: string, targetTrackId: string): boolean;
363
394
  private moveClipToTrackWithHistorySnapshot;
364
395
  /**
365
- * 获取轨道内的总时长(包含空隙)
366
- * @returns 从0到最后一个clip结束时间的总时长(毫秒)
396
+ * 获取轨道内的有效总时长(去除首尾空白)
397
+ * @returns 最早 clip 起点到最晚 clip 终点的时长(毫秒)
367
398
  */
368
399
  getTrackTotalDuration(): TimeMs;
369
400
  destroy(): void;
370
401
  /** 清除历史堆栈 */
371
402
  clearHistory(): void;
372
403
  private handleClipChange;
404
+ private withClipRemovalBatch;
405
+ private resolveClipIdsForExternalIdRemoval;
406
+ private shouldClearSelectionForClipIds;
407
+ private resolveSelectedClipAtTime;
408
+ private handleClipToggleSelection;
409
+ private handleSetSingleSelection;
410
+ private getMultiDragClipIds;
411
+ private handleMultiDragMove;
412
+ private normalizeTrackGroupStackOrder;
413
+ private elevateMultiDragTrackGroups;
414
+ private resolveMultiDragSessionTrackIds;
415
+ private resetMultiDragSession;
416
+ private handleMultiDragInteractionEnd;
417
+ private syncMultiDragOverlayState;
418
+ private clearMultiDragOverlayState;
419
+ private ensureMultiDragSession;
420
+ private haveSameClipIds;
421
+ private restoreDraggedClipSnapshot;
422
+ private syncMultiDragPeerClips;
423
+ private previewMultiDragCrossTrack;
424
+ private finalizeMultiDragCrossTrack;
425
+ private resolveTrackIndexDelta;
426
+ private resolveMultiDragTargetTrackId;
427
+ private resolveMultiDragPreviewTargetTrackId;
428
+ private buildMovedClipSnapshot;
429
+ private appendPreviewClip;
430
+ private resolveCrossTrackMoveDestination;
431
+ private resolveCrossTrackPreviewDestination;
432
+ private ensureTrackIdByRelativeIndex;
433
+ private findTrackIdByRelativeIndex;
434
+ private getTrackIdsByType;
435
+ private updateClipPositionWithHistory;
436
+ private updateClipPositionSilently;
437
+ private ensureSelectionBoxRect;
438
+ private showSelectionBoxRect;
439
+ private hideSelectionBoxRect;
440
+ private handleStageMouseDown;
441
+ private handleBoxSelectMove;
442
+ private handleBoxSelectEnd;
443
+ private isPointOnClip;
444
+ private getClipsIntersectingBox;
373
445
  private addClipToTrack;
374
446
  private cloneTrackSnapshot;
375
447
  private ensureTrackFromHistorySnapshot;
448
+ private getTrackRestoreAnchor;
449
+ private resolveTrackInsertionFromRestoreAnchor;
376
450
  private resolveTrackIdByClipId;
377
451
  private findTrackByClipId;
378
452
  /**
@@ -1,4 +1,4 @@
1
- import { Action, Clip, ClipStateUpdate, HistoryState, MoveClipBetweenTracksAction, MoveClipAction, MultiClipUpdateAction, RemoveClipAction, RemoveGapsAction, RestoreClipAudioAction, ResizeClipAction, SeparateClipAudioAction, SetTimeAction, SplitClipAction, Track, UpdateClipAction, AddClipAction } from '../models/types';
1
+ import { Action, Clip, ClipStateUpdate, CompoundAction, HistoryState, MoveClipBetweenTracksAction, MoveClipAction, MultiClipUpdateAction, RemoveClipAction, RemoveGapsAction, RestoreClipAudioAction, ResizeClipAction, SeparateClipAudioAction, SetTimeAction, SplitClipAction, Track, TrackRestoreAnchor, UpdateClipAction, AddClipAction } from '../models/types';
2
2
  export declare class HistoryManager {
3
3
  private state;
4
4
  private maxHistorySize;
@@ -42,6 +42,7 @@ export declare class HistoryManager {
42
42
  * 创建移除片段操作
43
43
  */
44
44
  createRemoveClipAction(clip: Clip): RemoveClipAction;
45
+ createRemoveClipActionWithTrackSnapshot(clip: Clip, sourceTrackId: string | null, sourceTrackSnapshot: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null): RemoveClipAction;
45
46
  /**
46
47
  * 创建更新片段操作
47
48
  */
@@ -76,4 +77,5 @@ export declare class HistoryManager {
76
77
  createMoveClipBetweenTracksAction(clipId: string, sourceTrackId: string, targetTrackId: string, clipBefore: Clip, clipAfter: Clip, sourceTrackSnapshot: Track | null, targetTrackSnapshot: Track | null): MoveClipBetweenTracksAction;
77
78
  createSeparateClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): SeparateClipAudioAction;
78
79
  createRestoreClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): RestoreClipAudioAction;
80
+ createCompoundAction(actions: Action[], label?: string): CompoundAction;
79
81
  }
@@ -1,4 +1,4 @@
1
- import type { Action, Clip, Track } from '../models/types';
1
+ import type { Action, Clip, Track, TrackRestoreAnchor } from '../models/types';
2
2
  export interface TimelineHistoryExecutionTrack {
3
3
  addClip(clip: Clip): void;
4
4
  }
@@ -7,7 +7,7 @@ export interface TimelineHistoryExecutionTarget {
7
7
  addClipToTrack(trackId: string, clip: Clip): boolean;
8
8
  updateClip(clipId: string, updates: Partial<Clip>): void;
9
9
  moveClipToTrack(clipId: string, targetTrackId: string): boolean;
10
- ensureTrackFromHistorySnapshot(trackSnapshot: Track): string | null;
10
+ ensureTrackFromHistorySnapshot(trackSnapshot: Track, restoreAnchor?: TrackRestoreAnchor | null): string | null;
11
11
  removeClipGaps(): void;
12
12
  getClips(): Clip[];
13
13
  findTrackByClipId(clipId: string): TimelineHistoryExecutionTrack | null;
@@ -19,5 +19,10 @@ export declare class TimelineHistoryExecutor {
19
19
  constructor(target: TimelineHistoryExecutionTarget);
20
20
  executeUndo(action: Action): void;
21
21
  executeRedo(action: Action): void;
22
+ private executeUndoInternal;
23
+ private executeRedoInternal;
22
24
  private restoreMovedClip;
25
+ private normalizeRemoveClipActionData;
26
+ private resolveTrackIdForHistorySnapshot;
27
+ private resolveRestoreAnchor;
23
28
  }
@@ -1,15 +1,19 @@
1
1
  import { HistoryManager } from '../history/history';
2
- import type { Action, Clip, ClipStateUpdate, Track } from '../models/types';
2
+ import type { Action, Clip, ClipStateUpdate, Track, TrackRestoreAnchor } from '../models/types';
3
3
  export declare class TimelineHistoryRecorder {
4
4
  private readonly history;
5
5
  constructor(history: HistoryManager);
6
6
  recordAddClip(clip: Clip): Action;
7
- recordRemoveClip(clip: Clip): Action;
7
+ createRemoveClipAction(clip: Clip, sourceTrackId?: string | null, sourceTrackSnapshot?: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null): Action;
8
+ recordRemoveClip(clip: Clip, sourceTrackId?: string | null, sourceTrackSnapshot?: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null): Action;
8
9
  recordClipUpdate(clip: Clip, originalClip?: Clip, clipUpdates?: ClipStateUpdate[]): Action | null;
9
10
  recordSplitClip(clip1: Clip, clip2: Clip): Action;
10
11
  recordRemoveGaps(clipsBefore: Clip[]): Action | null;
11
12
  recordMoveClipBetweenTracks(clipId: string, sourceTrackId: string, targetTrackId: string, clipBefore: Clip, clipAfter: Clip, sourceTrackSnapshot: Track | null, targetTrackSnapshot: Track | null): Action;
12
13
  recordSeparateClipAudio(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
13
14
  recordRestoreClipAudio(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
15
+ createSeparateClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
16
+ createRestoreClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
17
+ recordCompoundAction(actions: Action[], label?: string): Action | null;
14
18
  private createClipUpdateAction;
15
19
  }
@@ -203,9 +203,18 @@ export interface AddClipAction {
203
203
  }
204
204
  export interface RemoveClipAction {
205
205
  type: 'remove_clip';
206
- data: Clip;
206
+ data: {
207
+ clip: Clip;
208
+ sourceTrackId: string | null;
209
+ sourceTrackSnapshot: Track | null;
210
+ sourceTrackRestoreAnchor: TrackRestoreAnchor | null;
211
+ };
207
212
  timestamp: number;
208
213
  }
214
+ export interface TrackRestoreAnchor {
215
+ previousTrackId: string | null;
216
+ nextTrackId: string | null;
217
+ }
209
218
  export interface UpdateClipAction {
210
219
  type: 'update_clip';
211
220
  data: {
@@ -299,7 +308,15 @@ export interface RestoreClipAudioAction {
299
308
  };
300
309
  timestamp: number;
301
310
  }
302
- export type Action = AddClipAction | RemoveClipAction | UpdateClipAction | SplitClipAction | MoveClipAction | ResizeClipAction | SetTimeAction | RemoveGapsAction | MultiClipUpdateAction | MoveClipBetweenTracksAction | SeparateClipAudioAction | RestoreClipAudioAction;
311
+ export interface CompoundAction {
312
+ type: 'compound';
313
+ data: {
314
+ actions: Action[];
315
+ label?: string;
316
+ };
317
+ timestamp: number;
318
+ }
319
+ export type Action = AddClipAction | RemoveClipAction | UpdateClipAction | SplitClipAction | MoveClipAction | ResizeClipAction | SetTimeAction | RemoveGapsAction | MultiClipUpdateAction | MoveClipBetweenTracksAction | SeparateClipAudioAction | RestoreClipAudioAction | CompoundAction;
303
320
  export type ActionType = Action['type'];
304
321
  export interface Position {
305
322
  x: number;
@@ -313,7 +330,7 @@ export interface HistoryState {
313
330
  past: Action[];
314
331
  future: Action[];
315
332
  }
316
- export type TimelineEvent = 'time_change' | 'play_state_change' | 'clip_added' | 'clip_removed' | 'clip_updated' | 'zoom_change' | 'history_change' | 'track_duration_change' | 'clip_selected' | 'selected_clip_change' | 'speed_change' | 'can_play_change' | 'buffering_state_change' | 'source_loading_change' | 'preview_aspect_ratio_change' | 'draft_loaded';
333
+ export type TimelineEvent = 'time_change' | 'play_state_change' | 'clip_added' | 'clip_removed' | 'clip_updated' | 'zoom_change' | 'history_change' | 'track_duration_change' | 'clip_selected' | 'selected_clip_change' | 'speed_change' | 'can_play_change' | 'buffering_state_change' | 'source_loading_change' | 'preview_aspect_ratio_change' | 'draft_loaded' | 'selection_change';
317
334
  export interface TimeChangeData {
318
335
  time: TimeMs;
319
336
  }
@@ -352,6 +369,10 @@ export interface DraftLoadedData {
352
369
  error: unknown;
353
370
  }>;
354
371
  }
372
+ export interface MultiSelectionChangeData {
373
+ selectedClipIds: string[];
374
+ previousSelectedClipIds: string[];
375
+ }
355
376
  export interface EventListener {
356
377
  (event: TimelineEvent, data?: any): void;
357
378
  }
@@ -17,6 +17,6 @@ export declare class TimelinePresentationAdapter {
17
17
  syncZoom(timeline: TimelineZoomView | null, playhead: TimelineZoomView | null, tracks: TimelineTrackCollection, zoom: number): void;
18
18
  syncScrollLeft(playhead: TimelineScrollView | null, tracks: TimelineTrackCollection, scrollLeft: number): void;
19
19
  syncScrollTop(panel: TimelineVerticalScrollView | null, scrollbar: TimelineVerticalScrollView | null, scrollTop: number): void;
20
- syncSelection(tracks: TimelineTrackCollection, selectedClipId: string | null): void;
20
+ syncSelection(tracks: TimelineTrackCollection, selectedClipId: string | null, selectedClipIds?: string[]): void;
21
21
  findSelectedClip(tracks: TimelineTrackCollection): Clip | null;
22
22
  }
@@ -1,7 +1,14 @@
1
1
  export declare class SelectionStore {
2
- private selectedClipId;
2
+ private selectedClipIds;
3
3
  getSelectedClipId(): string | null;
4
+ getSelectedClipIds(): string[];
5
+ addToSelection(clipId: string): void;
6
+ removeFromSelection(clipId: string): void;
7
+ toggleSelection(clipId: string): void;
8
+ setSelection(clipIds: string[]): void;
4
9
  setSelectedClipId(clipId: string | null): void;
5
10
  clear(): void;
6
11
  hasSelection(): boolean;
12
+ isSelected(clipId: string): boolean;
13
+ getSelectionCount(): number;
7
14
  }
@@ -1,3 +1,4 @@
1
1
  export { TimelineTrackBridge } from './timelineTrackBridge';
2
+ export type { MultiDragMoveRequest } from './timelineTrackBridge';
2
3
  export { TimelineTrackCollection } from './timelineTrackCollection';
3
4
  export { TrackManager } from './trackManager';
@@ -1,6 +1,14 @@
1
1
  import Konva from 'konva';
2
2
  import { Track } from '../../components/track/Track';
3
3
  import type { Clip, ClipStateUpdate, Theme, TimeMs, TrackConfig, TrackType } from '../models/types';
4
+ export interface MultiDragMoveRequest {
5
+ draggedClipId: string;
6
+ deltaTime: TimeMs;
7
+ targetTrackY: number;
8
+ currentTrackId: string;
9
+ crossTrackOffsetY: number;
10
+ isFinal: boolean;
11
+ }
4
12
  export interface TimelineTrackBridgeHandlers {
5
13
  onClipUpdate: (clip: Clip, originalClip?: Clip, clipUpdates?: ClipStateUpdate[]) => void;
6
14
  onClipAdd: (clip: Clip) => void;
@@ -15,6 +23,11 @@ export interface TimelineTrackBridgeHandlers {
15
23
  onClearDropPreview?: () => void;
16
24
  onClearSelection?: () => void;
17
25
  onSnapGuideChange?: (guideTime: TimeMs | null) => void;
26
+ onClipToggleSelection?: (clipId: string) => void;
27
+ onSetSingleSelection?: (clipId: string) => void;
28
+ getMultiDragClipIds?: (clipId: string) => string[] | null;
29
+ onMultiDragMove?: (request: MultiDragMoveRequest) => boolean | void;
30
+ onMultiDragInteractionEnd?: () => void;
18
31
  }
19
32
  export interface TimelineTrackBridgeCreateOptions {
20
33
  layer: Konva.Layer;
@@ -28,7 +41,7 @@ export interface TimelineTrackBridgeCreateOptions {
28
41
  enableClipSnap?: boolean;
29
42
  clipSnapThreshold?: number;
30
43
  }
31
- export type TimelineTrackConstructor<TTrack = Track> = new (layer: Konva.Layer, config: TrackConfig, trackType: TrackType, zoom: number, trackY: number, trackHeight: number, theme: Theme, onClipUpdate: (clip: Clip, originalClip?: Clip, clipUpdates?: ClipStateUpdate[]) => void, onClipAdd: (clip: Clip) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: Clip, clip2: Clip) => void, onClipSelect: (clip: Clip) => void, onTimeJump: (time: TimeMs) => void, onHorizontalDragAutoScroll?: (nextScrollLeft: number) => number, onClipOverlap?: (clip: Clip, currentTrackId: string) => void, onClipCrossTrackPreview?: (clip: Clip, targetTrackY: number, currentTrackId: string) => 'self' | 'external' | 'clear', onClipCrossTrack?: (clip: Clip, originalClip: Clip | null, targetTrackY: number, currentTrackId: string) => boolean | void, onClearDropPreview?: () => void, onClearSelection?: () => void, onSnapGuideChange?: (guideTime: TimeMs | null) => void, dragActivationThreshold?: number, enableClipSnap?: boolean, clipSnapThreshold?: number) => TTrack;
44
+ export type TimelineTrackConstructor<TTrack = Track> = new (layer: Konva.Layer, config: TrackConfig, trackType: TrackType, zoom: number, trackY: number, trackHeight: number, theme: Theme, onClipUpdate: (clip: Clip, originalClip?: Clip, clipUpdates?: ClipStateUpdate[]) => void, onClipAdd: (clip: Clip) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: Clip, clip2: Clip) => void, onClipSelect: (clip: Clip) => void, onTimeJump: (time: TimeMs) => void, onHorizontalDragAutoScroll?: (nextScrollLeft: number) => number, onClipOverlap?: (clip: Clip, currentTrackId: string) => void, onClipCrossTrackPreview?: (clip: Clip, targetTrackY: number, currentTrackId: string) => 'self' | 'external' | 'clear', onClipCrossTrack?: (clip: Clip, originalClip: Clip | null, targetTrackY: number, currentTrackId: string) => boolean | void, onClearDropPreview?: () => void, onClearSelection?: () => void, onSnapGuideChange?: (guideTime: TimeMs | null) => void, onClipToggleSelection?: (clipId: string) => void, onSetSingleSelection?: (clipId: string) => void, getMultiDragClipIds?: (clipId: string) => string[] | null, onMultiDragMove?: (request: MultiDragMoveRequest) => boolean | void, onMultiDragInteractionEnd?: () => void, dragActivationThreshold?: number, enableClipSnap?: boolean, clipSnapThreshold?: number) => TTrack;
32
45
  export declare class TimelineTrackBridge<TTrack = Track> {
33
46
  private readonly handlers;
34
47
  private readonly TrackCtor;
@@ -1,24 +1,32 @@
1
1
  import type { Clip, TimeMs } from '../models/types';
2
+ import type Konva from 'konva';
2
3
  import type { TimelineTrackView } from '../commands/timelineCommands';
3
4
  export interface TimelineTrackCollectionItem extends TimelineTrackView {
4
5
  getRole?(): 'primary' | 'normal';
5
6
  showClipDropPreview?(clip: Clip): void;
7
+ showClipDropPreviews?(clips: Clip[]): void;
6
8
  clearClipDropPreview?(): void;
7
9
  setCurrentTime?(time: TimeMs): void;
8
10
  setZoom?(zoom: number): void;
9
11
  setScrollLeft?(scrollLeft: number): void;
10
12
  setScrollTop?(scrollTop: number): void;
11
13
  updateClip?(clipId: string, updates: Partial<Clip>): void;
14
+ updateClipPosition?(clipId: string, newStartTime: TimeMs, isFinal: boolean, previewOffsetY?: number): void;
12
15
  removeClipGaps?(): void;
13
16
  updateSelectionVisual?(selectedClipId: string | null): void;
14
17
  clearSelection?(): void;
15
18
  getSelectedClip?(): Clip | null;
16
19
  selectClip?(clipId: string): void;
17
20
  setTrackY?(y: number): void;
21
+ setTrackStackOrder?(order: number): void;
18
22
  setTrackHeight?(height: number): void;
23
+ setDragOverlayLayer?(layer: Konva.Layer | null): void;
24
+ setClipsDragOverlayActive?(clipIds: string[], isActive: boolean): void;
25
+ clearClipDragOverlay?(): void;
19
26
  getTrackGroup?(): {
20
27
  destroy(): void;
21
28
  };
29
+ updateClipSelection?(clipId: string, isSelected: boolean): void;
22
30
  }
23
31
  export declare class TimelineTrackCollection {
24
32
  private readonly tracks;