@linker-design-plus/timeline-track 1.0.8 → 1.0.10

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
@@ -348,6 +348,12 @@ new TimelineManager(config?: Partial<TimelineConfig>)
348
348
  | `off(event, listener)` | 移除事件监听器 | `event`:事件类型,`listener`:事件监听器 | 无 |
349
349
  | `destroy()` | 销毁时间轴管理器 | 无 | 无 |
350
350
 
351
+ ### 交互事件分层规范
352
+
353
+ - 鼠标交互(时间轴拖拽、底部滑块拖拽、片段拖拽)采用统一分层策略,详见:
354
+ - `docs/interaction-model.md`
355
+ - 该文档用于约束后续重构,避免出现“移出画布中断拖拽”或“回到画布瞬移”等回归问题。
356
+
351
357
  #### 事件
352
358
 
353
359
  | 事件名 | 描述 | 数据 |
@@ -359,9 +365,13 @@ new TimelineManager(config?: Partial<TimelineConfig>)
359
365
  | `clip_removed` | 移除片段 | `{ clipId: string }` |
360
366
  | `clip_updated` | 更新片段 | `{ clip: Clip }` |
361
367
  | `clip_selected` | 选择片段 | `{ clip: Clip }` |
368
+ | `selected_clip_change` | 选中片段变化(订阅后会立即回调当前状态) | `{ clip: Clip \| null, hasSelectedClip: boolean }` |
362
369
  | `zoom_change` | 缩放比例变化 | `{ zoom: number }` |
363
370
  | `history_change` | 历史记录变更 | `{ canUndo: boolean, canRedo: boolean }` |
364
371
  | `track_duration_change` | 轨道总时长变化 | `{ duration: number }` |
372
+ | `buffering_state_change` | 视频缓冲状态变化 | `{ isBuffering: boolean }` |
373
+ | `can_play_change` | 是否可播放状态变化 | `{ canPlay: boolean }` |
374
+ | `source_loading_change` | 视频源加载状态变化 | `{ isLoading: boolean, pending: number }` |
365
375
 
366
376
  ### ClipConfig 接口
367
377
 
@@ -13,15 +13,26 @@ export declare class Timeline {
13
13
  private onZoomChange;
14
14
  private onScrollChange;
15
15
  private animationFrameId;
16
- private isZooming;
17
16
  private scrollbarHeight;
18
17
  private scrollbarY;
19
18
  private isScrollbarDragging;
20
19
  private scrollbarDragStartX;
21
20
  private scrollbarDragStartScrollLeft;
21
+ private hasBoundGlobalPointerListenersForDrag;
22
+ private isPointerInsideTimeline;
23
+ private lastPointerXInTimeline;
24
+ private handleGlobalPointerMove;
25
+ private handleGlobalPointerEnd;
26
+ private handleVisibilityChange;
22
27
  private leftPadding;
23
28
  constructor(stage: Konva.Stage, gridLayer: Konva.Layer, config: Partial<TimelineConfig>, onTimeChange: (time: TimeMs) => void, onZoomChange: (zoom: number) => void, onScrollChange: (scrollLeft: number) => void);
24
29
  private initEventListeners;
30
+ private updatePointerPosition;
31
+ private bindGlobalPointerListenersForDrag;
32
+ private unbindGlobalPointerListenersForDrag;
33
+ private isPointerSessionActive;
34
+ private finalizePointerInteraction;
35
+ private handlePointerMove;
25
36
  private handleZoom;
26
37
  private animateZoom;
27
38
  private animateHorizontalScroll;
@@ -64,6 +75,14 @@ export declare class Timeline {
64
75
  * 获取网格图层
65
76
  */
66
77
  getGridLayer(): Konva.Layer;
78
+ /**
79
+ * 鼠标当前是否位于时间轴区域内
80
+ */
81
+ hasPointerInTimeline(): boolean;
82
+ /**
83
+ * 获取时间轴内最近一次鼠标 X 坐标(相对 stage)
84
+ */
85
+ getPointerXInTimeline(): number | null;
67
86
  /**
68
87
  * 调整大小
69
88
  */
@@ -21,13 +21,21 @@ export declare class VideoTrack {
21
21
  private onClipSplit;
22
22
  private onClipSelect;
23
23
  private onTimeJump;
24
+ private onAutoScrollDuringDrag?;
25
+ private dragAutoScrollOffsetPx;
26
+ private hasBoundGlobalPointerListenersForDrag;
27
+ private handleGlobalPointerMove;
28
+ private handleGlobalPointerEnd;
24
29
  constructor(layer: Konva.Layer, config: TrackConfig, zoom: number, trackY: number, trackHeight: number, theme: Theme, onClipUpdate: (clip: ClipType, originalClip?: ClipType, clipUpdates?: Array<{
25
30
  clipId: string;
26
31
  newState: any;
27
32
  previousState: any;
28
- }>) => void, onClipAdd: (clip: ClipType) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: ClipType, clip2: ClipType) => void, onClipSelect: (clip: ClipType) => void, onTimeJump: (time: TimeMs) => void);
33
+ }>) => void, onClipAdd: (clip: ClipType) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: ClipType, clip2: ClipType) => void, onClipSelect: (clip: ClipType) => void, onTimeJump: (time: TimeMs) => void, onAutoScrollDuringDrag?: (targetScrollLeft: number) => void);
29
34
  private initClips;
30
35
  private initEventListeners;
36
+ private bindGlobalPointerListenersForDrag;
37
+ private unbindGlobalPointerListenersForDrag;
38
+ private handleVisibilityChange;
31
39
  /**
32
40
  * 处理轨道背景点击事件
33
41
  */
@@ -77,6 +85,9 @@ export declare class VideoTrack {
77
85
  * 处理舞台鼠标移动事件
78
86
  */
79
87
  private handleStageMouseMove;
88
+ private isDragSessionActive;
89
+ private handleDragMove;
90
+ private maybeAutoScrollDuringDrag;
80
91
  /**
81
92
  * 处理片段鼠标离开事件
82
93
  */
@@ -19,3 +19,4 @@ export declare const TIME_SCALE: {
19
19
  MAX_HEIGHT: number;
20
20
  };
21
21
  export declare const TIMELINE_LEFT_PADDING = 15;
22
+ export declare const MIN_CLIP_LINE_WIDTH = 6;
@@ -1,4 +1,4 @@
1
- import { TimelineConfig, Clip, ClipConfig, TimeMs, PlayState, Action, TimelineEvent, EventListener, VideoPreviewConfig, TimelineExportData, ThumbnailProvider } from './types';
1
+ import { TimelineConfig, Clip, ClipConfig, TimeMs, PlayState, Action, TimelineEvent, EventListener as TimelineEventListener, VideoPreviewConfig, TimelineExportData, ThumbnailProvider } from './types';
2
2
  export declare class TimelineManager {
3
3
  private timeline;
4
4
  private tracks;
@@ -20,6 +20,12 @@ export declare class TimelineManager {
20
20
  private lastTrackDuration;
21
21
  private thumbnailProvider;
22
22
  private canPlay;
23
+ private isBuffering;
24
+ private sourceLoadingCount;
25
+ private lastSelectedClipId;
26
+ private playStateBeforeBuffering;
27
+ private isPausingForBuffering;
28
+ private videoBufferingListeners;
23
29
  constructor(config?: Partial<TimelineConfig>);
24
30
  init(container: HTMLElement): void;
25
31
  play(): void;
@@ -29,6 +35,21 @@ export declare class TimelineManager {
29
35
  setCurrentTime(time: TimeMs): void;
30
36
  getCurrentTime(): TimeMs;
31
37
  setZoom(zoom: number): void;
38
+ /**
39
+ * 以游标(playhead)为中心设置缩放,用于外置 slider 等非幕布上的缩放操作
40
+ * 调整 scrollLeft 使游标在屏幕上的位置保持不变
41
+ */
42
+ setZoomCenteredOnPlayhead(zoom: number): void;
43
+ /**
44
+ * 外部缩放入口:
45
+ * - 鼠标在时间轴内:以鼠标位置为中心缩放
46
+ * - 鼠标不在时间轴内:回退为以游标(playhead)为中心缩放
47
+ */
48
+ setZoomByInteraction(zoom: number): void;
49
+ /**
50
+ * 以时间轴上的指定像素点为中心缩放,保持该像素对应时间不变
51
+ */
52
+ setZoomCenteredOnTimelinePointer(zoom: number, pointerX: number): void;
32
53
  getZoom(): number;
33
54
  /**
34
55
  * 设置播放倍速
@@ -45,6 +66,17 @@ export declare class TimelineManager {
45
66
  * @returns 是否可以播放
46
67
  */
47
68
  getCanPlay(): boolean;
69
+ /**
70
+ * 当前是否存在视频源加载任务(例如 addClip 期间读取视频时长)
71
+ */
72
+ isSourceLoading(): boolean;
73
+ /**
74
+ * 获取视频源加载状态
75
+ */
76
+ getSourceLoadingState(): {
77
+ isLoading: boolean;
78
+ pending: number;
79
+ };
48
80
  /**
49
81
  * 设置缩略图提供器
50
82
  * @param provider 缩略图提供器
@@ -68,6 +100,11 @@ export declare class TimelineManager {
68
100
  */
69
101
  refreshAllClipThumbnails(): Promise<boolean[]>;
70
102
  addClip(clipConfig: ClipConfig): Promise<string>;
103
+ /**
104
+ * 批量添加片段。批量模式下仅维护一段连续的视频源加载状态。
105
+ */
106
+ addClips(clipConfigs: ClipConfig[]): Promise<string[]>;
107
+ private addClipInternal;
71
108
  removeClip(clipId: string): void;
72
109
  updateClip(clipId: string, updates: Partial<Clip>): void;
73
110
  splitClip(clipId: string, time: TimeMs): void;
@@ -92,6 +129,7 @@ export declare class TimelineManager {
92
129
  private handleTimeChange;
93
130
  private handleZoomChange;
94
131
  private handleScrollChange;
132
+ private handleTrackAutoScrollRequest;
95
133
  private handleClipUpdate;
96
134
  private handleClipAdd;
97
135
  private handleClipRemove;
@@ -109,9 +147,12 @@ export declare class TimelineManager {
109
147
  private handleClipSelect;
110
148
  private handleActionUndo;
111
149
  private handleActionRedo;
112
- on(event: TimelineEvent, listener: EventListener): void;
113
- off(event: TimelineEvent, listener: EventListener): void;
150
+ on(event: TimelineEvent, listener: TimelineEventListener): void;
151
+ off(event: TimelineEvent, listener: TimelineEventListener): void;
114
152
  private emitEvent;
153
+ private emitSelectedClipChangeIfNeeded;
154
+ private beginSourceLoading;
155
+ private endSourceLoading;
115
156
  getPlayState(): PlayState;
116
157
  setDuration(duration: TimeMs): void;
117
158
  getDuration(): TimeMs;
@@ -168,10 +209,16 @@ export declare class TimelineManager {
168
209
  /** 清除历史堆栈 */
169
210
  clearHistory(): void;
170
211
  private handleVideoTimeUpdate;
212
+ private getLastClipByEndTime;
171
213
  private handlePlayStateChange;
172
214
  private handleClipChange;
173
215
  private findClipAtTime;
174
216
  private loadClipToVideo;
217
+ private attachVideoBufferingListeners;
218
+ private detachVideoBufferingListeners;
219
+ private enterBuffering;
220
+ private exitBuffering;
221
+ private clearBufferingStateForNoSource;
175
222
  /**
176
223
  * 检查轨道总时长是否变化,如果变化则触发事件并更新时间轴时长
177
224
  */
@@ -123,7 +123,7 @@ export interface HistoryState {
123
123
  past: Action[];
124
124
  future: Action[];
125
125
  }
126
- export type TimelineEvent = 'time_change' | 'play_state_change' | 'clip_added' | 'clip_removed' | 'clip_updated' | 'zoom_change' | 'history_change' | 'track_duration_change' | 'clip_selected' | 'speed_change' | 'can_play_change';
126
+ 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';
127
127
  export interface TimeChangeData {
128
128
  time: TimeMs;
129
129
  }
@@ -136,12 +136,23 @@ export interface ZoomChangeData {
136
136
  export interface ClipEventData {
137
137
  clip: Clip;
138
138
  }
139
+ export interface SelectedClipChangeData {
140
+ clip: Clip | null;
141
+ hasSelectedClip: boolean;
142
+ }
139
143
  export interface ClipRemovedEventData {
140
144
  clipId: string;
141
145
  }
142
146
  export interface CanPlayChangeData {
143
147
  canPlay: boolean;
144
148
  }
149
+ export interface BufferingStateChangeData {
150
+ isBuffering: boolean;
151
+ }
152
+ export interface SourceLoadingChangeData {
153
+ isLoading: boolean;
154
+ pending: number;
155
+ }
145
156
  export interface EventListener {
146
157
  (event: TimelineEvent, data?: any): void;
147
158
  }