@linker-design-plus/timeline-track 2.1.2 → 2.1.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
@@ -135,6 +135,7 @@ pnpm install
135
135
  pnpm dev
136
136
  pnpm build
137
137
  pnpm test
138
+ pnpm harness:test
138
139
  pnpm exec tsc -p tsconfig.json --noEmit
139
140
  ```
140
141
 
@@ -146,3 +147,4 @@ pnpm exec tsc -p tsconfig.json --noEmit
146
147
  - [docs/resource-cache.md](./docs/resource-cache.md): 预览资源缓存的能力边界与配置
147
148
  - [docs/interaction-model.md](./docs/interaction-model.md): 指针交互分层和拖拽约束
148
149
  - [docs/preview-playback-recovery-flow.md](./docs/preview-playback-recovery-flow.md): 预览媒体时钟驱动播放、buffering 阻塞与恢复续播流程
150
+ - [docs/testing-harness-roadmap.md](./docs/testing-harness-roadmap.md): Harness Engineering 导向的测试与诊断重构方案
@@ -35,6 +35,7 @@ export declare class Track {
35
35
  private onClipSelect;
36
36
  private onTimeJump;
37
37
  private onClipOverlap?;
38
+ private onPrimaryTrackMagnetMove?;
38
39
  private hasBoundGlobalPointerListenersForDrag;
39
40
  private hasLockedGlobalCursor;
40
41
  private edgeAutoScrollAnimationFrameId;
@@ -56,7 +57,7 @@ export declare class Track {
56
57
  private handleGlobalPointerMove;
57
58
  private handleGlobalPointerEnd;
58
59
  private handleWindowBlur;
59
- 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, originalClip?: ClipType | null) => 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);
60
+ 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, originalClip?: ClipType | null) => void, onPrimaryTrackMagnetMove?: (clip: ClipType, originalClip: ClipType | null, peerClips: ClipType[], currentTrackId: string) => boolean | 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);
60
61
  private initClips;
61
62
  private ensureDropPreviewGroup;
62
63
  private createDropPreviewRect;
@@ -83,6 +84,7 @@ export declare class Track {
83
84
  private resolveDraggedClipForInteraction;
84
85
  private applyInteractionTransition;
85
86
  private handleClipMoveEnd;
87
+ private compactPrimaryTrackClips;
86
88
  private updateAllClips;
87
89
  private buildAndSendUpdates;
88
90
  private updateHoverCursor;
@@ -12,6 +12,7 @@ export { TimelinePreviewRuntimeController } from './timelinePreviewRuntimeContro
12
12
  export { TimelinePreviewStateController, type TimelinePendingPreviewState } from './timelinePreviewStateController';
13
13
  export { TimelineKeyboardShortcutsController, type TimelineKeyboardShortcutsControllerCallbacks } from './timelineKeyboardShortcutsController';
14
14
  export * from './timelineSelectionController';
15
+ export { TimelineStructureSession, type TimelineStructureSessionSummary } from './timelineStructureSession';
15
16
  export * from './timelineTrackMutationController';
16
17
  export { TimelineClipConfigController } from './timelineClipConfigController';
17
18
  export { TimelineTrackInfoPanelController } from './timelineTrackInfoPanelController';
@@ -0,0 +1,16 @@
1
+ import type { TimeMs } from '../models/types';
2
+ export declare const PLAYBACK_VIEWPORT_FOLLOW_MARGIN_PX = 48;
3
+ export declare function getPlayheadScreenX(time: TimeMs, zoom: number, scrollLeft: number): number;
4
+ export interface PlaybackViewportFollowInput {
5
+ time: TimeMs;
6
+ zoom: number;
7
+ scrollLeft: number;
8
+ viewportWidth: number;
9
+ contentWidth: number;
10
+ margin?: number;
11
+ }
12
+ export declare function clampScrollLeft(scrollLeft: number, viewportWidth: number, contentWidth: number): number;
13
+ /**
14
+ * 播放边距跟随:播放头超出左右安全边距时返回目标 scrollLeft,否则返回 null。
15
+ */
16
+ export declare function resolveScrollLeftForPlaybackFollow(input: PlaybackViewportFollowInput): number | null;
@@ -204,6 +204,7 @@ export declare class TimelinePreviewSession {
204
204
  private ensureTextPreviewFontReady;
205
205
  private resetTextPreviewFontState;
206
206
  private refreshSlotVisualLayout;
207
+ private syncSlotFailureVisualState;
207
208
  private buildSelectedOverlayState;
208
209
  private getFrameSize;
209
210
  private getDocument;
@@ -0,0 +1,67 @@
1
+ import type { Clip, Track, TrackRestoreAnchor } from '../models/types';
2
+ import type { TimelineTrackCollection, TimelineTrackCollectionItem } from '../tracks/timelineTrackCollection';
3
+ import type { TimelineHistoryExecutionTarget } from '../history/timelineHistoryExecutor';
4
+ export interface TimelineStructureSessionEvent {
5
+ type: 'clip_added' | 'clip_removed' | 'clip_updated';
6
+ payload: Record<string, unknown>;
7
+ }
8
+ export interface TimelineStructureSessionSummary {
9
+ cleanupEmptyTracks: boolean;
10
+ durationDirty: boolean;
11
+ canPlayDirty: boolean;
12
+ selectionDirty: boolean;
13
+ trackLayoutDirty: boolean;
14
+ trackInfoDirty: boolean;
15
+ stageDirty: boolean;
16
+ previewDirty: boolean;
17
+ events: TimelineStructureSessionEvent[];
18
+ }
19
+ interface TimelineStructureSessionOptions {
20
+ trackCollection: TimelineTrackCollection;
21
+ ensureTrackFromHistorySnapshot: (trackSnapshot: Track, restoreAnchor?: TrackRestoreAnchor | null) => string | null;
22
+ removeClipGaps: () => void;
23
+ setPrimaryTrackMagnetEnabled: (enabled: boolean) => void;
24
+ loadClipThumbnails?: (clip: Clip) => Promise<boolean> | void;
25
+ addClipToTrack?: (trackId: string, clip: Clip) => boolean;
26
+ removeClip?: (clipId: string) => void;
27
+ removeClipFromTrack?: (trackId: string, clipId: string) => boolean;
28
+ updateClip?: (clipId: string, updates: Partial<Clip>) => void;
29
+ moveClipToTrack?: (clip: Clip, sourceTrackId: string, targetTrackId: string) => boolean;
30
+ getClips?: () => Clip[];
31
+ findTrackById?: (trackId: string) => TimelineTrackCollectionItem | null;
32
+ findTrackByClipId?: (clipId: string) => TimelineTrackCollectionItem | null;
33
+ getDefaultTrack?: () => TimelineTrackCollectionItem | null;
34
+ onCommit: (summary: TimelineStructureSessionSummary) => void;
35
+ }
36
+ export declare class TimelineStructureSession {
37
+ private readonly options;
38
+ private cleanupEmptyTracks;
39
+ private durationDirty;
40
+ private canPlayDirty;
41
+ private selectionDirty;
42
+ private trackLayoutDirty;
43
+ private trackInfoDirty;
44
+ private stageDirty;
45
+ private previewDirty;
46
+ private committed;
47
+ private readonly events;
48
+ constructor(options: TimelineStructureSessionOptions);
49
+ addClipToTrack(trackId: string, clip: Clip): boolean;
50
+ removeClip(clipId: string): void;
51
+ removeClipFromTrack(trackId: string, clipId: string): boolean;
52
+ updateClip(clipId: string, updates: Partial<Clip>): void;
53
+ moveClipToTrack(clipId: string, targetTrackId: string): boolean;
54
+ ensureTrackFromHistorySnapshot(trackSnapshot: Track, restoreAnchor?: TrackRestoreAnchor | null): string | null;
55
+ removeClipGaps(): void;
56
+ getClips(): Clip[];
57
+ findTrackByClipId(clipId: string): TimelineTrackCollectionItem | null;
58
+ getDefaultTrack(): TimelineTrackCollectionItem | null;
59
+ queueEvent(type: TimelineStructureSessionEvent['type'], payload: Record<string, unknown>): void;
60
+ markStructureMutation(): void;
61
+ markClipStateMutation(): void;
62
+ markTrackLayoutMutation(): void;
63
+ commit(): void;
64
+ createHistoryExecutionTarget(): TimelineHistoryExecutionTarget;
65
+ private resolveTrackId;
66
+ }
67
+ export {};
@@ -49,7 +49,6 @@ export declare class TimelineManager {
49
49
  private sourceLoadingCount;
50
50
  private timelineStore?;
51
51
  private timelineCommands?;
52
- private timelineHistoryExecutor?;
53
52
  private timelineHistoryRecorder?;
54
53
  private timelinePresentationAdapter?;
55
54
  private timelineTrackBridge?;
@@ -74,6 +73,7 @@ export declare class TimelineManager {
74
73
  private splitOperationDepth;
75
74
  private readonly diagnostics;
76
75
  private previewRuntimeState;
76
+ private failedPreviewClipIds?;
77
77
  private previewActiveClipIds;
78
78
  private previewClockSeekAlignmentTargetTime;
79
79
  private previewClockSeekAlignmentDeadlineMs;
@@ -86,6 +86,11 @@ export declare class TimelineManager {
86
86
  private voiceLinkedTextRegenerationTimer;
87
87
  private pendingVoiceLinkedTextRegeneration;
88
88
  private historyRecordingSuppressionDepth;
89
+ private activeStructureSession;
90
+ private structureSessionDepth;
91
+ private preserveEmptyTracksDepth;
92
+ private playbackViewportFollowSuspended;
93
+ private isProgrammaticScrollChange;
89
94
  constructor(config?: Partial<TimelineConfig>);
90
95
  private createPlaybackAttemptId;
91
96
  private refreshPlaybackAttempt;
@@ -121,13 +126,20 @@ export declare class TimelineManager {
121
126
  private getEventDispatcher;
122
127
  private getDefaultTrackForHistory;
123
128
  private getTimelineHistoryRecorder;
124
- private getTimelineHistoryExecutor;
129
+ private withStructureSession;
130
+ private getActiveStructureSession;
131
+ private withPreservedEmptyTracks;
132
+ private applyStructureSessionSummary;
125
133
  private withHistoryBoundary;
126
134
  private shouldSkipHistoryRecording;
127
135
  private withHistoryTransaction;
128
136
  private get selectedClipId();
129
137
  private set selectedClipId(value);
130
138
  private ensureConfigState;
139
+ private isPlaybackViewportFollowEnabled;
140
+ private suspendPlaybackViewportFollowIfUserScrolling;
141
+ private runProgrammaticScrollChange;
142
+ private followPlaybackViewportIfNeeded;
131
143
  private getCurrentTimeState;
132
144
  private getPlayStateState;
133
145
  private setPlayStateState;
@@ -188,7 +200,7 @@ export declare class TimelineManager {
188
200
  private retryPendingPreview;
189
201
  private handlePreviewBackendRuntimeError;
190
202
  init(container: HTMLElement): void;
191
- createTrack(type: TrackType, name?: string, insertionPlacement?: TrackInsertionPlacement, referenceTrackId?: string, roleOverride?: TrackEntity['role']): string;
203
+ createTrack(type: TrackType, name?: string, insertionPlacement?: TrackInsertionPlacement, referenceTrackId?: string, roleOverride?: TrackEntity['role'], explicitTrackId?: string): string;
192
204
  removeTrack(trackId: string): boolean;
193
205
  renameTrack(trackId: string, newName: string): boolean;
194
206
  getTracks(): any[];
@@ -198,6 +210,7 @@ export declare class TimelineManager {
198
210
  private calculateTrackY;
199
211
  private calculateTotalHeight;
200
212
  private updateAllTrackPositions;
213
+ private removeEmptyTracksFromStructure;
201
214
  private cleanupEmptyTracks;
202
215
  private clearAllTrackDropPreviews;
203
216
  private showClipDropPreview;
@@ -240,6 +253,17 @@ export declare class TimelineManager {
240
253
  getCurrentTime(): TimeMs;
241
254
  setEnableClipSnap(enabled: boolean): void;
242
255
  getEnableClipSnap(): boolean;
256
+ setEnablePrimaryTrackMagnet(enabled: boolean): void;
257
+ getEnablePrimaryTrackMagnet(): boolean;
258
+ private setPrimaryTrackMagnetEnabledFromHistory;
259
+ private setPrimaryTrackMagnetEnabledState;
260
+ private getPrimaryTrackId;
261
+ private isPrimaryTrackId;
262
+ private shouldNormalizePrimaryTrackForTrackIds;
263
+ private getStableStartOrderedClips;
264
+ private buildPrimaryTrackContinuousClips;
265
+ private buildPrimaryTrackMultiDragOrderedClips;
266
+ private normalizePrimaryTrackInSession;
243
267
  setZoom(zoom: number): void;
244
268
  /**
245
269
  * 以游标(playhead)为中心设置缩放,用于外置 slider 等非幕布上的缩放操作
@@ -320,6 +344,10 @@ export declare class TimelineManager {
320
344
  splitClip(clipId: string, time: TimeMs): void;
321
345
  splitCurrentClip(): void;
322
346
  getClips(): Clip[];
347
+ private syncTimelineClipMediaStateFromPreview;
348
+ private applyTimelineClipMediaStatus;
349
+ private clearTimelineClipMediaStatus;
350
+ private getFailedPreviewClipIds;
323
351
  private getExportComposition;
324
352
  private getExportCoverUrl;
325
353
  exportTimeline(): TimelineExportData;
@@ -404,6 +432,7 @@ export declare class TimelineManager {
404
432
  private emitSelectedClipChangeIfNeeded;
405
433
  private beginSourceLoading;
406
434
  private endSourceLoading;
435
+ private handlePrimaryTrackMagnetMove;
407
436
  private handleClipOverlap;
408
437
  private handleClipCrossTrackPreview;
409
438
  private handleClipCrossTrack;
@@ -575,6 +604,7 @@ export declare class TimelineManager {
575
604
  private resolveMultiDragOverlapTargetTrackId;
576
605
  private commitMultiDragPlacements;
577
606
  private finalizeMultiDragSameTrack;
607
+ private compactPrimaryTrackAfterMultiDrag;
578
608
  private previewMultiDragCrossTrack;
579
609
  private finalizeMultiDragCrossTrack;
580
610
  private resolveTrackIndexDelta;
@@ -597,7 +627,6 @@ export declare class TimelineManager {
597
627
  private isPointOnClip;
598
628
  private getClipsIntersectingBox;
599
629
  private resolveTrackRenderHeight;
600
- private addClipToTrack;
601
630
  private cloneTrackSnapshot;
602
631
  private ensureTrackFromHistorySnapshot;
603
632
  private getTrackRestoreAnchor;
@@ -1,4 +1,4 @@
1
- import { Action, Clip, ClipStateUpdate, CompoundAction, HistoryState, MoveClipBetweenTracksAction, MoveClipAction, MultiClipUpdateAction, RemoveClipAction, RemoveGapsAction, RestoreClipAudioAction, ResizeClipAction, SeparateClipAudioAction, SetTimeAction, SplitClipAction, Track, TrackRestoreAnchor, UpdateClipAction, AddClipAction } from '../models/types';
1
+ import { Action, Clip, ClipStateUpdate, CompoundAction, HistoryState, MoveClipBetweenTracksAction, MoveClipAction, MultiClipUpdateAction, RemoveClipAction, RemoveGapsAction, RestoreClipAudioAction, ResizeClipAction, SeparateClipAudioAction, SetPrimaryTrackMagnetAction, SetTimeAction, SplitClipAction, Track, TrackRestoreAnchor, TrackHistoryReference, UpdateClipAction, AddClipAction } from '../models/types';
2
2
  export declare class HistoryManager {
3
3
  private state;
4
4
  private maxHistorySize;
@@ -50,7 +50,7 @@ export declare class HistoryManager {
50
50
  /**
51
51
  * 创建分割片段操作
52
52
  */
53
- createSplitClipAction(originalClip: Clip, clip1: Clip, clip2: Clip): SplitClipAction;
53
+ createSplitClipAction(originalClip: Clip, clip1: Clip, clip2: Clip, sourceTrackReference?: Partial<TrackHistoryReference>): SplitClipAction;
54
54
  /**
55
55
  * 创建移动片段操作
56
56
  */
@@ -74,8 +74,9 @@ export declare class HistoryManager {
74
74
  /**
75
75
  * 创建跨轨道移动片段操作
76
76
  */
77
- createMoveClipBetweenTracksAction(clipId: string, sourceTrackId: string, targetTrackId: string, clipBefore: Clip, clipAfter: Clip, sourceTrackSnapshot: Track | null, targetTrackSnapshot: Track | null): MoveClipBetweenTracksAction;
77
+ createMoveClipBetweenTracksAction(clipId: string, sourceTrackId: string, targetTrackId: string, clipBefore: Clip, clipAfter: Clip, sourceTrackSnapshot: Track | null, targetTrackSnapshot: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null, targetTrackRestoreAnchor?: TrackRestoreAnchor | null): MoveClipBetweenTracksAction;
78
78
  createSeparateClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): SeparateClipAudioAction;
79
79
  createRestoreClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): RestoreClipAudioAction;
80
+ createSetPrimaryTrackMagnetAction(previousEnabled: boolean, nextEnabled: boolean): SetPrimaryTrackMagnetAction;
80
81
  createCompoundAction(actions: Action[], label?: string): CompoundAction;
81
82
  }
@@ -7,12 +7,14 @@ 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
+ setPrimaryTrackMagnetEnabled?(enabled: boolean): void;
10
11
  ensureTrackFromHistorySnapshot(trackSnapshot: Track, restoreAnchor?: TrackRestoreAnchor | null): string | null;
11
12
  removeClipGaps(): void;
12
13
  getClips(): Clip[];
13
14
  findTrackByClipId(clipId: string): TimelineHistoryExecutionTrack | null;
14
15
  getDefaultTrack(): TimelineHistoryExecutionTrack | null;
15
16
  loadClipThumbnails?(clip: Clip): Promise<boolean> | void;
17
+ withClipRemovalBatch?<T>(callback: () => T): T;
16
18
  }
17
19
  export declare class TimelineHistoryExecutor {
18
20
  private readonly target;
@@ -22,8 +24,11 @@ export declare class TimelineHistoryExecutor {
22
24
  private executeUndoInternal;
23
25
  private executeRedoInternal;
24
26
  private restoreMovedClip;
27
+ private withClipRemovalBatch;
25
28
  private normalizeRemoveClipActionData;
26
29
  private normalizeAddClipActionData;
30
+ private normalizeSplitClipActionData;
31
+ private normalizeMoveClipBetweenTracksActionData;
27
32
  private resolveTrackIdForHistorySnapshot;
28
33
  private resolveRestoreAnchor;
29
34
  }
@@ -9,13 +9,14 @@ export declare class TimelineHistoryRecorder {
9
9
  createRemoveClipAction(clip: Clip, sourceTrackId?: string | null, sourceTrackSnapshot?: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null): Action;
10
10
  recordRemoveClip(clip: Clip, sourceTrackId?: string | null, sourceTrackSnapshot?: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null): Action;
11
11
  recordClipUpdate(clip: Clip, originalClip?: Clip, clipUpdates?: ClipStateUpdate[]): Action | null;
12
- recordSplitClip(clip1: Clip, clip2: Clip): Action;
12
+ recordSplitClip(clip1: Clip, clip2: Clip, sourceTrackId?: string | null, sourceTrackSnapshot?: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null): Action;
13
13
  recordRemoveGaps(clipsBefore: Clip[]): Action | null;
14
- recordMoveClipBetweenTracks(clipId: string, sourceTrackId: string, targetTrackId: string, clipBefore: Clip, clipAfter: Clip, sourceTrackSnapshot: Track | null, targetTrackSnapshot: Track | null): Action;
14
+ recordMoveClipBetweenTracks(clipId: string, sourceTrackId: string, targetTrackId: string, clipBefore: Clip, clipAfter: Clip, sourceTrackSnapshot: Track | null, targetTrackSnapshot: Track | null, sourceTrackRestoreAnchor?: TrackRestoreAnchor | null, targetTrackRestoreAnchor?: TrackRestoreAnchor | null): Action;
15
15
  recordSeparateClipAudio(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
16
16
  recordRestoreClipAudio(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
17
17
  createSeparateClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
18
18
  createRestoreClipAudioAction(videoClipBefore: Clip, videoClipAfter: Clip, audioClip: Clip | null, audioTrackId: string | null): Action;
19
+ recordSetPrimaryTrackMagnet(previousEnabled: boolean, nextEnabled: boolean): Action;
19
20
  recordCompoundAction(actions: Action[], label?: string): Action | null;
20
21
  createClipUpdateAction(clip: Clip, originalClip?: Clip, clipUpdates?: ClipStateUpdate[]): Action | null;
21
22
  withTransaction<T>(label: string | undefined, callback: () => T): T;
@@ -76,6 +76,7 @@ export interface GenerateVoiceResult {
76
76
  voiceId: string;
77
77
  voiceName: string;
78
78
  }
79
+ export type ClipMediaStatus = 'ready' | 'failed';
79
80
  export interface ClipConfigVoicePanelOptions {
80
81
  voiceCatalog?: VoiceOption[];
81
82
  generateVoiceBatch?: (requests: GenerateVoiceRequest[]) => Promise<GenerateVoiceResult[]>;
@@ -105,6 +106,8 @@ export interface ClipConfig {
105
106
  ttsVoiceId?: string;
106
107
  ttsVoiceName?: string;
107
108
  ttsFollowTextUpdates?: boolean;
109
+ mediaStatus?: ClipMediaStatus;
110
+ mediaStatusMessage?: string | null;
108
111
  }
109
112
  export interface ClipEntity {
110
113
  id: string;
@@ -131,6 +134,8 @@ export interface ClipEntity {
131
134
  ttsVoiceId?: string;
132
135
  ttsVoiceName?: string;
133
136
  ttsFollowTextUpdates?: boolean;
137
+ mediaStatus?: ClipMediaStatus;
138
+ mediaStatusMessage?: string | null;
134
139
  }
135
140
  export interface ClipViewState {
136
141
  isDragging: boolean;
@@ -218,6 +223,7 @@ export interface TimelineConfig {
218
223
  speed?: number;
219
224
  dragActivationThreshold?: number;
220
225
  enableClipSnap?: boolean;
226
+ enablePrimaryTrackMagnet?: boolean;
221
227
  clipSnapThreshold?: number;
222
228
  thumbnailProvider?: ThumbnailProvider;
223
229
  previewBackend?: PreviewBackendType;
@@ -226,6 +232,8 @@ export interface TimelineConfig {
226
232
  textPreviewFont?: TextPreviewFontConfig | null;
227
233
  draftData?: TimelineExportData;
228
234
  keyboardShortcuts?: false | TimelineKeyboardShortcutsConfig;
235
+ /** 播放时自动横向滚动以保持播放头在可视安全区内;默认开启 */
236
+ playbackViewportFollow?: boolean;
229
237
  }
230
238
  export type PreviewAspectRatioMode = 'auto-first-added-video' | 'auto-first-video' | 'manual';
231
239
  export interface PreviewAspectRatio {
@@ -297,6 +305,11 @@ export interface TrackRestoreAnchor {
297
305
  previousTrackId: string | null;
298
306
  nextTrackId: string | null;
299
307
  }
308
+ export interface TrackHistoryReference {
309
+ trackId: string | null;
310
+ trackSnapshot: Track | null;
311
+ trackRestoreAnchor: TrackRestoreAnchor | null;
312
+ }
300
313
  export interface UpdateClipAction {
301
314
  type: 'update_clip';
302
315
  data: {
@@ -312,6 +325,9 @@ export interface SplitClipAction {
312
325
  originalClip: Clip;
313
326
  clip1: Clip;
314
327
  clip2: Clip;
328
+ sourceTrackId?: string | null;
329
+ sourceTrackSnapshot?: Track | null;
330
+ sourceTrackRestoreAnchor?: TrackRestoreAnchor | null;
315
331
  };
316
332
  timestamp: number;
317
333
  }
@@ -367,6 +383,8 @@ export interface MoveClipBetweenTracksAction {
367
383
  clipAfter: Clip;
368
384
  sourceTrackSnapshot: Track | null;
369
385
  targetTrackSnapshot: Track | null;
386
+ sourceTrackRestoreAnchor?: TrackRestoreAnchor | null;
387
+ targetTrackRestoreAnchor?: TrackRestoreAnchor | null;
370
388
  };
371
389
  timestamp: number;
372
390
  }
@@ -390,6 +408,14 @@ export interface RestoreClipAudioAction {
390
408
  };
391
409
  timestamp: number;
392
410
  }
411
+ export interface SetPrimaryTrackMagnetAction {
412
+ type: 'set_primary_track_magnet';
413
+ data: {
414
+ previousEnabled: boolean;
415
+ nextEnabled: boolean;
416
+ };
417
+ timestamp: number;
418
+ }
393
419
  export interface CompoundAction {
394
420
  type: 'compound';
395
421
  data: {
@@ -398,7 +424,7 @@ export interface CompoundAction {
398
424
  };
399
425
  timestamp: number;
400
426
  }
401
- export type Action = AddClipAction | RemoveClipAction | UpdateClipAction | SplitClipAction | MoveClipAction | ResizeClipAction | SetTimeAction | RemoveGapsAction | MultiClipUpdateAction | MoveClipBetweenTracksAction | SeparateClipAudioAction | RestoreClipAudioAction | CompoundAction;
427
+ export type Action = AddClipAction | RemoveClipAction | UpdateClipAction | SplitClipAction | MoveClipAction | ResizeClipAction | SetTimeAction | RemoveGapsAction | MultiClipUpdateAction | MoveClipBetweenTracksAction | SeparateClipAudioAction | RestoreClipAudioAction | SetPrimaryTrackMagnetAction | CompoundAction;
402
428
  export type ActionType = Action['type'];
403
429
  export interface Position {
404
430
  x: number;
@@ -10,6 +10,7 @@ export declare const SHARED_COLOR_TOKENS: {
10
10
  readonly videoClipBackground: "#1e5679";
11
11
  readonly textClipBackground: "#604c32";
12
12
  readonly audioClipBackground: "#254428";
13
+ readonly mediaClipFailedBackground: "#670014";
13
14
  readonly selectedTrackSurface: "#292929";
14
15
  readonly borderStrong: "#333333";
15
16
  readonly borderMedium: "#444444";
@@ -125,6 +126,8 @@ export declare const PLAYHEAD_DEFAULT_COLORS: {
125
126
  export declare const PREVIEW_PENDING_OVERLAY_COLORS: {
126
127
  readonly background: "rgba(0, 0, 0, 0.56)";
127
128
  readonly text: "#ffffff";
129
+ readonly failedBackground: "#670014";
130
+ readonly failedText: "#ffffff";
128
131
  readonly actionBackground: "#4c8dff";
129
132
  readonly actionText: "#ffffff";
130
133
  };
@@ -18,6 +18,7 @@ export interface TimelineTrackBridgeHandlers {
18
18
  onTimeJump: (time: TimeMs) => void;
19
19
  onHorizontalDragAutoScroll?: (nextScrollLeft: number) => number;
20
20
  onClipOverlap?: (clip: Clip, currentTrackId: string, originalClip?: Clip | null) => void;
21
+ onPrimaryTrackMagnetMove?: (clip: Clip, originalClip: Clip | null, peerClips: Clip[], currentTrackId: string) => boolean | void;
21
22
  onClipCrossTrackPreview?: (clip: Clip, targetTrackY: number, currentTrackId: string) => 'self' | 'external' | 'clear';
22
23
  onClipCrossTrack?: (clip: Clip, originalClip: Clip | null, targetTrackY: number, currentTrackId: string) => boolean | void;
23
24
  onClearDropPreview?: () => void;
@@ -41,7 +42,7 @@ export interface TimelineTrackBridgeCreateOptions {
41
42
  enableClipSnap?: boolean;
42
43
  clipSnapThreshold?: number;
43
44
  }
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, originalClip?: Clip | null) => 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;
45
+ 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, originalClip?: Clip | null) => void, onPrimaryTrackMagnetMove?: (clip: Clip, originalClip: Clip | null, peerClips: Clip[], currentTrackId: string) => boolean | 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;
45
46
  export declare class TimelineTrackBridge<TTrack = Track> {
46
47
  private readonly handlers;
47
48
  private readonly TrackCtor;
@@ -3,7 +3,7 @@ export declare class TrackManager {
3
3
  private tracks;
4
4
  private trackCounter;
5
5
  constructor(initialTracks?: Track[]);
6
- createTrack(type: TrackType, name?: string, insertionPlacement?: TrackInsertionPlacement, referenceTrackId?: string, roleOverride?: Track['role']): Track;
6
+ createTrack(type: TrackType, name?: string, insertionPlacement?: TrackInsertionPlacement, referenceTrackId?: string, roleOverride?: Track['role'], explicitTrackId?: string): Track;
7
7
  removeTrack(trackId: string): boolean;
8
8
  renameTrack(trackId: string, newName: string): boolean;
9
9
  getTracks(): Track[];