@linker-design-plus/timeline-track 2.0.20 → 2.0.22

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
@@ -1,6 +1,6 @@
1
1
  # @linker-design-plus/timeline-track
2
2
 
3
- 基于 TypeScript、Konva 和 Vue demo 的时间线编辑组件库,对外以 `TimelineManager` 为统一入口,提供多轨片段编辑、拖拽/拉伸/分割、撤销重做、选中态同步、封面渲染和预览挂载能力。
3
+ 基于 TypeScript、Konva 和 Vue demo 的时间线编辑组件库,对外以 `TimelineManager` 为统一入口,提供多轨片段编辑、拖拽/拉伸/分割、撤销重做、选中态同步、封面渲染和预览挂载能力。时间轴主体仍由 Konva 驱动,播放指针使用独立的 SVG/DOM 覆盖层渲染。
4
4
 
5
5
  ## 当前状态
6
6
 
@@ -188,7 +188,7 @@ flowchart TB
188
188
  FACADE --> TRACKS["Tracks\nTrackManager / Bridge / Collection"]
189
189
  FACADE --> PRESENTATION["Presentation\nTimelinePresentationAdapter"]
190
190
  FACADE --> PREVIEW["Preview\nDomPreviewBackend"]
191
- FACADE --> COMPONENTS["Konva Views\nTimeline / Track / Clip / Playhead / Panels"]
191
+ FACADE --> COMPONENTS["Presentation Views\nTimeline / Track / Clip / Panels / ManagedPlayhead"]
192
192
  ```
193
193
 
194
194
  当前最值得关注的热点:
@@ -202,7 +202,7 @@ flowchart TB
202
202
 
203
203
  ```text
204
204
  src/
205
- components/ Konva 组件与交互 helper
205
+ components/ 时间轴视图、SVG/DOM 覆盖层与交互 helper
206
206
  core/ stores / commands / controllers / facade / models
207
207
  utils/ 渲染、时间和日志工具
208
208
  styles/ 样式入口
@@ -13,6 +13,17 @@ export interface ClipConfigPanelTheme {
13
13
  buttonActiveBorder: string;
14
14
  buttonActiveText: string;
15
15
  }
16
+ export type ClipConfigPanelGroupKind = 'video' | 'audio' | 'text' | 'mixed' | 'empty';
17
+ export interface ClipConfigPanelSelectionState {
18
+ primaryClip: Clip | null;
19
+ selectedClips: Clip[];
20
+ selectionCount: number;
21
+ groupKind: ClipConfigPanelGroupKind;
22
+ supportsVoiceBatch: boolean;
23
+ supportsTextContentBatch: boolean;
24
+ supportsVisualBatch: boolean;
25
+ supportsVolumeBatch: boolean;
26
+ }
16
27
  export declare const defaultDarkTheme: ClipConfigPanelTheme;
17
28
  export interface ClipConfigPanelConfig {
18
29
  container: HTMLElement;
@@ -75,6 +86,7 @@ export declare class ClipConfigPanel {
75
86
  private readonly onGenerateVoice?;
76
87
  private readonly voiceCatalog;
77
88
  private currentClip;
89
+ private selectionState;
78
90
  private activeTab;
79
91
  private readonly iconCache;
80
92
  private pendingPreferredTab;
@@ -91,13 +103,15 @@ export declare class ClipConfigPanel {
91
103
  private previewingVoiceId;
92
104
  constructor(config: ClipConfigPanelConfig);
93
105
  setClip(clip: Clip | null): void;
106
+ setSelectionState(selectionState: ClipConfigPanelSelectionState): void;
94
107
  setPreferredTab(tab: 'voice' | null): void;
95
108
  setVoiceGenerationBusy(isBusy: boolean): void;
96
109
  destroy(): void;
97
110
  private render;
98
111
  private getAvailableTabs;
112
+ private getEmptyStateMessage;
99
113
  private resetVoiceState;
100
- private syncVoiceStateForClip;
114
+ private syncVoiceStateForSelection;
101
115
  private syncActiveTab;
102
116
  private supportsVoicePanel;
103
117
  private ensureVoiceFilters;
@@ -1,7 +1,7 @@
1
1
  import { type TemplateResult } from 'lit';
2
2
  import { type Clip, type VoiceOption } from '../../core/models';
3
3
  import { LitDomRenderer } from '../../core/renderers/domRenderer';
4
- import type { ClipConfigPanelTheme } from './ClipConfigPanel';
4
+ import type { ClipConfigPanelSelectionState, ClipConfigPanelTheme } from './ClipConfigPanel';
5
5
  export type ClipConfigPanelTabKey = 'visual' | 'audio' | 'text' | 'voice';
6
6
  export interface ClipConfigPanelCallbacks {
7
7
  onTabChange: (tab: ClipConfigPanelTabKey) => void;
@@ -18,9 +18,12 @@ export interface ClipConfigPanelCallbacks {
18
18
  }
19
19
  export interface ClipConfigPanelViewModel {
20
20
  clip: Clip | null;
21
+ selectionState: ClipConfigPanelSelectionState;
21
22
  activeTab: ClipConfigPanelTabKey;
23
+ availableTabs: ClipConfigPanelTabKey[];
22
24
  theme: ClipConfigPanelTheme;
23
- emptyStateIconSvg: string;
25
+ emptyStateMessage: string | null;
26
+ isTextContentEditable: boolean;
24
27
  loadingIconSvg: string;
25
28
  selectedIconSvg: string;
26
29
  textDraftContent: string;
@@ -107,7 +110,6 @@ export declare class ClipConfigPanelRenderer extends LitDomRenderer<ClipConfigPa
107
110
  private renderLabeledNumberInput;
108
111
  private getContainerStyle;
109
112
  private getSliderStyle;
110
- private getAvailableTabs;
111
113
  private supportsVoicePanel;
112
114
  private getTransform;
113
115
  private calculatePresetPosition;
@@ -1,6 +1,5 @@
1
1
  import Konva from 'konva';
2
2
  import { Clip as ClipType, ClipStateUpdate, TrackConfig, TimeMs, Theme, TrackType } from '../../core/models';
3
- import { type TrackPointerOperation } from './trackInteractionState';
4
3
  import type { MultiDragMoveRequest } from '../../core/tracks/timelineTrackBridge';
5
4
  export declare class Track {
6
5
  private static readonly DEFAULT_DRAG_ACTIVATION_THRESHOLD;
@@ -28,7 +27,6 @@ export declare class Track {
28
27
  private multiDragOriginalPositions;
29
28
  private promotedClipParents;
30
29
  private interactionState;
31
- private legacyInteractionSnapshot?;
32
30
  private isVisualUpdate;
33
31
  private onClipUpdate;
34
32
  private onClipAdd;
@@ -58,37 +56,8 @@ export declare class Track {
58
56
  private handleGlobalPointerMove;
59
57
  private handleGlobalPointerEnd;
60
58
  private handleWindowBlur;
61
- get hasDragMoved(): boolean;
62
- set hasDragMoved(value: boolean);
63
- get activePointerOperation(): TrackPointerOperation | null;
64
- set activePointerOperation(value: TrackPointerOperation | null);
65
- get originalClipsState(): ClipType[];
66
- set originalClipsState(value: ClipType[]);
67
- get nonDraggedClips(): ClipType[];
68
- set nonDraggedClips(value: ClipType[]);
69
- get snapCandidateClips(): ClipType[];
70
- set snapCandidateClips(value: ClipType[]);
71
- get dragStartY(): number;
72
- set dragStartY(value: number);
73
- get dragTargetTrackY(): number;
74
- set dragTargetTrackY(value: number);
75
- get crossTrackDragOffsetY(): number;
76
- set crossTrackDragOffsetY(value: number);
77
- get crossTrackDragStartX(): number;
78
- set crossTrackDragStartX(value: number);
79
- get dragStartScrollLeft(): number;
80
- set dragStartScrollLeft(value: number);
81
- get dragGestureStartClientX(): number | null;
82
- set dragGestureStartClientX(value: number | null);
83
- get dragGestureStartClientY(): number | null;
84
- set dragGestureStartClientY(value: number | null);
85
- get lastDragClientX(): number | null;
86
- set lastDragClientX(value: number | null);
87
- get lastDragClientY(): number | null;
88
- set lastDragClientY(value: number | null);
89
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);
90
60
  private initClips;
91
- private getLegacyInteractionSnapshot;
92
61
  private ensureDropPreviewGroup;
93
62
  private createDropPreviewRect;
94
63
  private showDropPreview;
@@ -113,8 +82,6 @@ export declare class Track {
113
82
  private finishPointerInteraction;
114
83
  private resolveDraggedClipForInteraction;
115
84
  private applyInteractionTransition;
116
- private resolveInteractionState;
117
- private syncLegacyInteractionMirror;
118
85
  private handleClipMoveEnd;
119
86
  private updateAllClips;
120
87
  private buildAndSendUpdates;
@@ -166,7 +133,7 @@ export declare class Track {
166
133
  private buildPreviewClip;
167
134
  private constrainResizePreviewToTrackBounds;
168
135
  private getResizeLeftBoundaryStart;
169
- private getResizeRightBoundaryEnd;
136
+ private computeResizeRightPushFollowers;
170
137
  private buildResizeLeftPreviewClip;
171
138
  private buildResizeRightPreviewClip;
172
139
  private applyClipSnap;
@@ -22,6 +22,8 @@ export interface TimelinePreviewBackendCallbacks {
22
22
  onAspectRatioChange?: (aspectRatio: PreviewAspectRatio) => void;
23
23
  onPreviewClipSelect?: (clipId: string) => void;
24
24
  onVisualTransformCommit?: (clipId: string, visualTransform: ClipVisualTransform) => void;
25
+ onTextFontSizeCommit?: (clipId: string, fontSize: number) => void;
26
+ onTextRotationCommit?: (clipId: string, rotation: number) => void;
25
27
  onPendingPreviewRetry?: () => void;
26
28
  onRuntimeError?: (error: unknown) => void;
27
29
  }
@@ -6,15 +6,24 @@ export interface PreviewTransformOverlayState {
6
6
  frameSize: PreviewFrameSize;
7
7
  baseRect: PreviewRect;
8
8
  displayRect: PreviewRect;
9
+ clipType?: 'text' | 'video';
10
+ currentFontSize?: number;
11
+ currentRotation?: number;
9
12
  }
10
13
  interface PreviewTransformOverlayCallbacks {
11
14
  onPreviewTransformChange?: (clipId: string, transform: ClipVisualTransform | null) => void;
12
15
  onPreviewTransformCommit?: (clipId: string, transform: ClipVisualTransform) => void;
16
+ onFontSizeChange?: (clipId: string, fontSize: number | null) => void;
17
+ onFontSizeCommit?: (clipId: string, fontSize: number) => void;
18
+ onRotationChange?: (clipId: string, rotation: number | null) => void;
19
+ onRotationCommit?: (clipId: string, rotation: number) => void;
13
20
  }
14
21
  export declare class PreviewTransformOverlay {
15
22
  private frameElement;
16
23
  private overlayElement;
17
24
  private boxElement;
25
+ private rotateHandleElement;
26
+ private rotateHandlePointerDownHandler;
18
27
  private readonly handleElements;
19
28
  private readonly handlePointerDownHandlers;
20
29
  private state;
@@ -28,6 +37,8 @@ export declare class PreviewTransformOverlay {
28
37
  attach(frameElement: HTMLElement): void;
29
38
  detach(): void;
30
39
  sync(state: PreviewTransformOverlayState | null): void;
40
+ private createRotateHandle;
41
+ private getRotateHandlePointerDown;
31
42
  private createHandle;
32
43
  private readonly handleMovePointerDown;
33
44
  private getHandlePointerDown;
@@ -3,6 +3,7 @@ export interface TimelineClipConfigControllerConfig {
3
3
  container: HTMLElement;
4
4
  theme: Theme;
5
5
  getPrimarySelectedClip: () => Clip | null;
6
+ getSelectedClips: () => Clip[];
6
7
  voiceCatalog: VoiceOption[];
7
8
  updateClip: (clipId: string, updates: Partial<Clip>) => void;
8
9
  onGenerateVoice?: (clip: Clip, voice: VoiceOption, followTextUpdates: boolean) => Promise<void>;
@@ -10,6 +11,7 @@ export interface TimelineClipConfigControllerConfig {
10
11
  export declare class TimelineClipConfigController {
11
12
  private panel;
12
13
  private config;
14
+ private currentSelectionState;
13
15
  private isInternalUpdate;
14
16
  init(config: TimelineClipConfigControllerConfig): void;
15
17
  update(): void;
@@ -17,5 +19,7 @@ export declare class TimelineClipConfigController {
17
19
  destroy(): void;
18
20
  setPreferredTab(tab: 'voice' | null): void;
19
21
  setVoiceGenerationBusy(isBusy: boolean): void;
22
+ private buildSelectionState;
23
+ private resolveUpdateTargets;
20
24
  private convertTheme;
21
25
  }
@@ -11,6 +11,8 @@ interface TimelinePreviewSessionCallbacks {
11
11
  onAspectRatioChange?: (aspectRatio: PreviewAspectRatio) => void;
12
12
  onPreviewClipSelect?: (clipId: string) => void;
13
13
  onVisualTransformCommit?: (clipId: string, visualTransform: ClipVisualTransform) => void;
14
+ onTextFontSizeCommit?: (clipId: string, fontSize: number) => void;
15
+ onTextRotationCommit?: (clipId: string, rotation: number) => void;
14
16
  onPendingPreviewRetry?: () => void;
15
17
  }
16
18
  interface TimelinePreviewSessionDependencies {
@@ -59,6 +61,8 @@ export declare class TimelinePreviewSession {
59
61
  private lastSettledSyncRequestId;
60
62
  private primarySelectedClipId;
61
63
  private transientVisualTransform;
64
+ private transientFontSize;
65
+ private transientRotation;
62
66
  private textPreviewDragState;
63
67
  private textPreviewFontStyleElement;
64
68
  private textPreviewFontSignature;
@@ -81,6 +81,7 @@ export declare class TimelineManager {
81
81
  private lastSteadyPlaybackPreviewSyncAt;
82
82
  private previewSyncInteractionMode;
83
83
  private readonly resourceCacheManager;
84
+ private voiceLinkedTextRegenerationTimer;
84
85
  constructor(config?: Partial<TimelineConfig>);
85
86
  private createPlaybackAttemptId;
86
87
  private refreshPlaybackAttempt;
@@ -176,7 +177,6 @@ export declare class TimelineManager {
176
177
  removeTrack(trackId: string): boolean;
177
178
  renameTrack(trackId: string, newName: string): boolean;
178
179
  getTracks(): any[];
179
- initTrackInfoPanel(container: HTMLElement): void;
180
180
  updateTrackInfoPanel(): void;
181
181
  muteTrack(trackId: string, isMuted: boolean): boolean;
182
182
  isTrackMuted(trackId: string): boolean;
@@ -357,6 +357,7 @@ export declare class TimelineManager {
357
357
  private relocateClipIfNeeded;
358
358
  private regenerateVoiceLinkedAudioClips;
359
359
  private getSelectedTextClipsForVoiceGeneration;
360
+ private getSelectedTtsAudioClipsForVoiceGeneration;
360
361
  private handleVoiceGenerateAction;
361
362
  private primeOrLoadClipThumbnails;
362
363
  private markClipThumbnailLoadCompleted;
@@ -575,5 +576,7 @@ export declare class TimelineManager {
575
576
  private resolveSelectedClipSnapshot;
576
577
  private resolveSelectionChangeData;
577
578
  private commitPreviewVisualTransform;
579
+ private commitPreviewTextFontSize;
580
+ private commitPreviewTextRotation;
578
581
  clearAllTracksAndClips(): void;
579
582
  }
@@ -1,6 +1,6 @@
1
1
  import type { Theme } from '../models/types';
2
2
  import { type TimelineManagerLayoutElements } from './TimelineManagerLayoutRenderer';
3
- export declare const TIMELINE_MANAGER_LEFT_PANEL_WIDTH = 200;
3
+ export declare const TIMELINE_MANAGER_LEFT_PANEL_WIDTH = 80;
4
4
  export declare const TIMELINE_MANAGER_SCROLLBAR_SIZE = 8;
5
5
  export type { TimelineManagerLayoutElements };
6
6
  export interface CreateTimelineManagerLayoutOptions {