@aicut/core 0.4.3 → 0.6.0

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/dist/index.d.ts CHANGED
@@ -1,5 +1,180 @@
1
- import { M as Ms, T as Track, C as Clip, a as MediaSource, P as Project, b as Theme, L as Locale } from './types-C95koNwJ.js';
2
- export { f as formatLabel, l as localeEn, c as localeZh, m as mergeLocale } from './types-C95koNwJ.js';
1
+ import { P as PlaybackEngine, a as PlaybackEngineOptions, b as PlaybackEngineFactory } from './types-BbZjOQLz.js';
2
+ import { M as Ms, P as Project, T as Track, C as Clip, a as MediaSource, b as Theme, K as KeyframeProp, E as EasingKind } from './types-CmS-UIEr.js';
3
+ export { c as Keyframe } from './types-CmS-UIEr.js';
4
+ import { L as Locale } from './i18n-B24k4XVG.js';
5
+ export { f as formatLabel, l as localeEn, a as localeZh, m as mergeLocale } from './i18n-B24k4XVG.js';
6
+
7
+ declare class HtmlVideoEngine implements PlaybackEngine {
8
+ private host;
9
+ private mount;
10
+ private sources;
11
+ private project;
12
+ private currentClipId;
13
+ private playing;
14
+ private timeMs;
15
+ private rafHandle;
16
+ private lastFrameTs;
17
+ /** Permanent rAF that positions the active wrapper at the output
18
+ * rect + pushes keyframe transform onto the inner video via CSS. */
19
+ private transformRaf;
20
+ /** Public event hooks — set by Editor. */
21
+ onTimeUpdate?: (ms: Ms) => void;
22
+ onEnded?: () => void;
23
+ onError?: (err: Error) => void;
24
+ onReady?: () => void;
25
+ onSourceMetadata?: (sourceId: string, durationMs: Ms) => void;
26
+ constructor(opts: PlaybackEngineOptions);
27
+ setProject(next: Project): void;
28
+ play(): void;
29
+ pause(): void;
30
+ isPlaying(): boolean;
31
+ getTime(): Ms;
32
+ seek(timeMs: Ms): void;
33
+ /**
34
+ * The OUTPUT frame — the fixed stage the rendered video is clipped
35
+ * to. Independent of the keyframe transform. Used by the overlay to
36
+ * draw the dashed border at a stable position.
37
+ */
38
+ getOutputFrameRect(): {
39
+ x: number;
40
+ y: number;
41
+ w: number;
42
+ h: number;
43
+ } | null;
44
+ /**
45
+ * The CONTENT frame — where the transformed video pixels actually
46
+ * land. Equal to the output frame when transform is identity; may
47
+ * extend outside (zoom in) or fit inside (zoom out) when not.
48
+ */
49
+ getFrameRect(): {
50
+ x: number;
51
+ y: number;
52
+ w: number;
53
+ h: number;
54
+ } | null;
55
+ /** Untransformed contain-letterbox rect — the OUTPUT frame. */
56
+ private baseFrameRect;
57
+ /**
58
+ * Permanent rAF that (a) sizes + positions the active wrapper to
59
+ * the output frame, and (b) writes the keyframe transform onto the
60
+ * inner video. Negligible cost — three style writes per frame max.
61
+ */
62
+ private startTransformLoop;
63
+ private applyTransforms;
64
+ destroy(): void;
65
+ private syncSources;
66
+ private activate;
67
+ private seekVideoToClipOffset;
68
+ private clipById;
69
+ /**
70
+ * Find the clip whose timeline range contains `timeMs`, searching
71
+ * across ALL video tracks. If multiple tracks have a clip at this
72
+ * moment, the lowest-index track wins.
73
+ */
74
+ private clipAtTime;
75
+ /** Earliest clip starting at-or-after `timeMs` across all video tracks. */
76
+ private nextClipAfterTime;
77
+ /** Max clip end across all video tracks. */
78
+ private totalDuration;
79
+ private startTickLoop;
80
+ private stopTickLoop;
81
+ private advance;
82
+ }
83
+ /** Factory shorthand for `Editor.create({ playbackEngine })`. */
84
+ declare const htmlVideoEngineFactory: PlaybackEngineFactory;
85
+
86
+ interface CanvasCompositorEngineOptions extends PlaybackEngineOptions {
87
+ /**
88
+ * Show the corner HUD ("engine: canvas compositor • t=… • frames
89
+ * painted: …"). Off by default — production hosts get a clean canvas
90
+ * with no chrome painted on top. Turn on in development / demos to
91
+ * see who's drawing and what the current state is.
92
+ */
93
+ debug?: boolean;
94
+ }
95
+ /**
96
+ * Reference second engine — demonstrates that the `PlaybackEngine`
97
+ * surface really is engine-agnostic. Same `<video>`-based decode as
98
+ * `HtmlVideoEngine` (so it works in every browser, no WebCodecs gate),
99
+ * but rendering happens via `ctx.drawImage(video, …)` on a single
100
+ * canvas instead of the browser painting the video element itself.
101
+ *
102
+ * Why ship it: it's tangible proof that a host can swap the rendering
103
+ * surface for compositing / shaders / overlays / capture-to-canvas
104
+ * without touching Editor internals. It's also the natural stepping
105
+ * stone toward a WebGL or WebCodecs path (those replace the decoder
106
+ * but keep the canvas-blit pattern).
107
+ *
108
+ * Limits: same as `HtmlVideoEngine` (one source visible at a time,
109
+ * seek snaps to the browser's keyframe pipeline). The point is the
110
+ * surface, not new capability.
111
+ */
112
+ declare class CanvasCompositorEngine implements PlaybackEngine {
113
+ private host;
114
+ private mount;
115
+ private canvas;
116
+ private ctx;
117
+ /** Only created when constructed with `debug: true`. */
118
+ private badge;
119
+ private videos;
120
+ private project;
121
+ private currentClipId;
122
+ private playing;
123
+ private timeMs;
124
+ private rafHandle;
125
+ private lastFrameTs;
126
+ private paintedFrames;
127
+ /** Output frame rect (no transform) — fixed bounds. */
128
+ private lastOutputRect;
129
+ /** Post-transform content rect. */
130
+ private lastFrameRect;
131
+ onTimeUpdate?: (ms: Ms) => void;
132
+ onEnded?: () => void;
133
+ onError?: (err: Error) => void;
134
+ onReady?: () => void;
135
+ onSourceMetadata?: (sourceId: string, durationMs: Ms) => void;
136
+ constructor(opts: CanvasCompositorEngineOptions);
137
+ setProject(next: Project): void;
138
+ play(): void;
139
+ pause(): void;
140
+ isPlaying(): boolean;
141
+ getTime(): Ms;
142
+ seek(timeMs: Ms): void;
143
+ destroy(): void;
144
+ private syncSources;
145
+ private activate;
146
+ private seekVideoToClipOffset;
147
+ private clipById;
148
+ private clipAtTime;
149
+ private nextClipAfterTime;
150
+ private totalDuration;
151
+ private resizeCanvas;
152
+ private startTickLoop;
153
+ private stopTickLoop;
154
+ private advance;
155
+ /**
156
+ * One paint per rAF — clears the canvas, draws the current active
157
+ * video frame letterboxed to fit, then refreshes the HUD. Done
158
+ * unconditionally (not just on `playing`) so the HUD frame counter
159
+ * and the seek preview both update when paused.
160
+ */
161
+ private paint;
162
+ getOutputFrameRect(): {
163
+ x: number;
164
+ y: number;
165
+ w: number;
166
+ h: number;
167
+ } | null;
168
+ getFrameRect(): {
169
+ x: number;
170
+ y: number;
171
+ w: number;
172
+ h: number;
173
+ } | null;
174
+ private updateBadge;
175
+ }
176
+ /** Factory shorthand for `Editor.create({ playbackEngine })`. */
177
+ declare const canvasCompositorEngineFactory: PlaybackEngineFactory;
3
178
 
4
179
  interface EditorOptions {
5
180
  /** Host element to mount the editor into. Will be wiped on init. */
@@ -20,6 +195,63 @@ interface EditorOptions {
20
195
  * Call `editor.setLocale(...)` to switch at runtime.
21
196
  */
22
197
  locale?: Partial<Locale>;
198
+ /**
199
+ * Optional factory for a custom playback engine. Receives the
200
+ * editor's preview host element + the initial project, returns
201
+ * anything satisfying `PlaybackEngine`. Defaults to the built-in
202
+ * `HtmlVideoEngine` (one hidden `<video>` per source, swap on
203
+ * boundaries). Hosts that need frame-accurate editing, multi-track
204
+ * compositing, transitions, etc. pass a `WebCodecsEngine` factory
205
+ * (v0.6+) or their own.
206
+ */
207
+ playbackEngine?: PlaybackEngineFactory;
208
+ /**
209
+ * Pixel height of each track row in the timeline (default 56). Lower
210
+ * values (~32–40) shrink the timeline footprint for small viewports
211
+ * where the default crowds out the preview. Reasonable range:
212
+ * [28, 96]. Applied process-wide via `setTimelineMetrics` — multi-
213
+ * editor mounts share the value.
214
+ */
215
+ trackHeight?: number;
216
+ /**
217
+ * Pixel height of the timeline ruler / time-label strip (default 24).
218
+ * Pair with `trackHeight` to compact the whole timeline. Reasonable
219
+ * range: [18, 36].
220
+ */
221
+ rulerHeight?: number;
222
+ /**
223
+ * Pixel height of the whole bottom timeline area (default 240). The
224
+ * canvas inside fills 100% of this and shows a vertical scrollbar
225
+ * when there are more tracks than fit. Lower this (~120–180) on
226
+ * small viewports so the preview takes more of the editor's height.
227
+ * Reasonable range: [120, 480].
228
+ */
229
+ timelineHeight?: number;
230
+ /**
231
+ * Per-clip keyframe animation (X / Y / Scale). Off by default; flip
232
+ * `enabled: true` to surface keyframe markers on the timeline and
233
+ * route the canvas / WebCodecs engines through `getEffectiveTransform`
234
+ * when painting. Data is preserved either way — disabling just hides
235
+ * the editing UI and renders identity transforms.
236
+ *
237
+ * HtmlVideoEngine cannot apply per-frame transforms (it shows a raw
238
+ * `<video>`), so keyframes are silently ignored on that engine
239
+ * regardless of this flag. Swap to `CanvasCompositorEngine` or
240
+ * `WebCodecsEngine` for live preview.
241
+ */
242
+ keyframes?: {
243
+ enabled?: boolean;
244
+ };
245
+ /**
246
+ * Show the |◀ / ▶| "jump to clip start / end" toolbar buttons and
247
+ * bind the I / O keyboard shortcuts. Off by default — hosts opt in
248
+ * the same way they do for keyframes. When off the buttons are
249
+ * completely hidden (display: none) so they don't take up toolbar
250
+ * space, and the I / O keys fall through to the page.
251
+ */
252
+ clipEdgeNav?: {
253
+ enabled?: boolean;
254
+ };
23
255
  }
24
256
  interface EditorEventMap {
25
257
  /** Emitted whenever the project mutates. */
@@ -47,6 +279,23 @@ interface EditorEventMap {
47
279
  selectionChange: {
48
280
  clipId: string | null;
49
281
  };
282
+ /** Currently selected keyframe (parent clip + keyframe id), or null.
283
+ * Selecting a keyframe also selects its parent clip — listeners
284
+ * watching `selectionChange` get notified independently. */
285
+ keyframeSelectionChange: {
286
+ target: {
287
+ clipId: string;
288
+ keyframeId: string;
289
+ } | null;
290
+ };
291
+ /** Keyframe-mode toggle changed (Editor.setKeyframesEnabled). */
292
+ keyframesEnabledChange: {
293
+ enabled: boolean;
294
+ };
295
+ /** Jump-to-clip-edge nav toggle changed (Editor.setClipEdgeNavEnabled). */
296
+ clipEdgeNavEnabledChange: {
297
+ enabled: boolean;
298
+ };
50
299
  /** Zoom (px/sec) changed. */
51
300
  scaleChange: {
52
301
  pxPerSec: number;
@@ -123,10 +372,131 @@ interface EditorApi {
123
372
  setSnap(snap: boolean): void;
124
373
  getSelection(): string | null;
125
374
  setSelection(clipId: string | null): void;
375
+ isKeyframesEnabled(): boolean;
376
+ setKeyframesEnabled(enabled: boolean): void;
377
+ isClipEdgeNavEnabled(): boolean;
378
+ setClipEdgeNavEnabled(enabled: boolean): void;
379
+ /** Screen-space CSS-pixel rect of the active rendered frame, post
380
+ * transform, relative to the editor preview. Null when none. */
381
+ getActiveFrameRect(): {
382
+ x: number;
383
+ y: number;
384
+ w: number;
385
+ h: number;
386
+ } | null;
387
+ /** Output frame rect (fixed bounds, no transform). The overlay
388
+ * draws the dashed border here. */
389
+ getActiveOutputFrameRect(): {
390
+ x: number;
391
+ y: number;
392
+ w: number;
393
+ h: number;
394
+ } | null;
395
+ /**
396
+ * Upsert a per-property keyframe at the given clip-local time. If a
397
+ * keyframe for the same `prop` already exists within ~1 frame of
398
+ * `time` it gets its value updated; else a new one is appended.
399
+ * Returns the keyframe's id, or null when the clip can't be found.
400
+ *
401
+ * Defaults: `time` = playhead in clip-local coords; `value` = the
402
+ * currently interpolated value for that prop (so adding doesn't
403
+ * cause a visible jump).
404
+ */
405
+ addKeyframe(clipId: string, prop: KeyframeProp, opts?: {
406
+ time?: Ms;
407
+ value?: number;
408
+ }): string | null;
409
+ removeKeyframe(clipId: string, keyframeId: string): boolean;
410
+ moveKeyframe(clipId: string, keyframeId: string, timeMs: Ms): boolean;
411
+ /** Change one keyframe's value (single number, since each kf is
412
+ * per-property). */
413
+ setKeyframeValue(clipId: string, keyframeId: string, value: number): boolean;
414
+ /**
415
+ * Change one keyframe's outgoing easing curve. Shapes only the
416
+ * segment from this kf to the NEXT kf in time on the same prop.
417
+ * The kf's value is untouched.
418
+ */
419
+ setKeyframeEasing(clipId: string, keyframeId: string, easing: EasingKind): boolean;
420
+ /**
421
+ * Batch-set the outgoing easing on every kf at one moment in time
422
+ * (within the 16ms tolerance that the rest of the API uses) on a
423
+ * single clip. Mirrors the panel's "one dropdown for the moment"
424
+ * UX so all three props (panX / panY / scale) at the selected
425
+ * moment animate with the same curve. Single history entry.
426
+ */
427
+ setKeyframesEasingAtTime(clipId: string, timeMs: Ms, easing: EasingKind): boolean;
428
+ /**
429
+ * CapCut-style auto-record: write `value` for `prop` at the playhead.
430
+ * - If the prop already has keyframes → upsert a keyframe at the
431
+ * playhead with this value.
432
+ * - If the prop has no keyframes → just update the static base
433
+ * (panX / panY / scale on the clip) so the visual changes
434
+ * without committing the user to an animation track yet.
435
+ * Returns true if the project changed.
436
+ */
437
+ setValueAtPlayhead(clipId: string, prop: KeyframeProp, value: number): boolean;
438
+ getSelectedKeyframe(): {
439
+ clipId: string;
440
+ keyframeId: string;
441
+ } | null;
442
+ setSelectedKeyframe(target: {
443
+ clipId: string;
444
+ keyframeId: string;
445
+ } | null): void;
446
+ /**
447
+ * Toolbar-style toggle. If ANY keyframe exists at the playhead time
448
+ * on the selected clip, remove every keyframe at that time (all
449
+ * props). Otherwise, capture one keyframe per prop (panX, panY,
450
+ * scale) at the playhead with the currently interpolated values.
451
+ * Returns true when the project changed.
452
+ */
453
+ toggleKeyframeAtPlayhead(): boolean;
454
+ /**
455
+ * Clear every keyframe AND every static transform value on a clip,
456
+ * restoring the identity pose (panX=0, panY=0, scale=1). Single
457
+ * history entry. */
458
+ resetClipTransform(clipId: string): boolean;
459
+ /**
460
+ * Pin all three transform props (panX, panY, scale) to identity
461
+ * (0, 0, 1) at one specific clip-local time. Upserts on each prop —
462
+ * keyframes that already exist at that time get their values
463
+ * overwritten; props with no kf there get one added. Single history
464
+ * entry. Used by the panel's Reset button when a keyframe is selected.
465
+ */
466
+ resetKeyframesAtTime(clipId: string, timeMs: Ms): boolean;
467
+ /**
468
+ * Move the playhead to a clip edge. "end" intentionally lands 1ms
469
+ * INSIDE the clip (clipEnd - 1) so the playhead remains inside the
470
+ * clip — that lets the user immediately press the keyframe button
471
+ * (or the I/O shortcut) and have it find the right clip + drop a
472
+ * keyframe at clip-local time `duration - 1ms`. Without the -1ms
473
+ * offset the playhead lands on the seam and `toggleKeyframeAtPlayhead`
474
+ * picks the next clip (or none at all).
475
+ * Returns true when the seek actually moved.
476
+ */
477
+ seekToClipEdge(clipId: string, edge: "start" | "end"): boolean;
478
+ /** Convenience for the toolbar: act on the currently-selected clip. */
479
+ seekToSelectedClipEdge(edge: "start" | "end"): boolean;
126
480
  canUndo(): boolean;
127
481
  canRedo(): boolean;
128
482
  undo(): boolean;
129
483
  redo(): boolean;
484
+ /**
485
+ * Open a "drag session". While open, every internal `pushHistory`
486
+ * call captures the pre-session snapshot ONCE — subsequent calls
487
+ * during the same session are no-ops. The session commits a single
488
+ * history entry on `endInteraction()` (or is dropped entirely if
489
+ * the project ended up unchanged). Nestable: nested begin/end pairs
490
+ * count by depth and only the outermost commits.
491
+ *
492
+ * Hosts call this around continuous gestures (drag the preview
493
+ * overlay, scrub a numeric slider, wheel-zoom) so a single user
494
+ * gesture becomes ONE undo entry instead of 30-100. Without this,
495
+ * each pointermove of an overlay drag pushes its own history entry
496
+ * and the user has to mash Cmd+Z that many times to fully undo.
497
+ */
498
+ beginInteraction(): void;
499
+ endInteraction(): void;
130
500
  /**
131
501
  * Bookend slot at the very left of the top toolbar — host appends
132
502
  * its own controls (e.g. an aspect-ratio dropdown). Empty by default
@@ -165,6 +535,13 @@ declare class Editor implements EditorApi {
165
535
  private bus;
166
536
  private history;
167
537
  private selectedClipId;
538
+ private selectedKeyframe;
539
+ private keyframesEnabled;
540
+ private clipEdgeNavEnabled;
541
+ /** Drag-session bookkeeping for ripple-merge undo. See
542
+ * beginInteraction / endInteraction docs on EditorApi. */
543
+ private interactionDepth;
544
+ private interactionStartSnapshot;
168
545
  private pxPerSec;
169
546
  private snap;
170
547
  private locale;
@@ -252,10 +629,74 @@ declare class Editor implements EditorApi {
252
629
  snapMs(timeMs: Ms, ignoreClipId?: string | null): Ms;
253
630
  getSelection(): string | null;
254
631
  setSelection(clipId: string | null): void;
632
+ isKeyframesEnabled(): boolean;
633
+ /**
634
+ * Screen-space CSS-pixel rect of the actively painted frame
635
+ * (post-transform), relative to the editor's preview element.
636
+ * Null when no clip is active, the engine doesn't expose
637
+ * `getFrameRect`, or the rect isn't computed yet. Used by the
638
+ * library's keyframe-editing overlay.
639
+ */
640
+ getActiveFrameRect(): {
641
+ x: number;
642
+ y: number;
643
+ w: number;
644
+ h: number;
645
+ } | null;
646
+ /**
647
+ * Screen-space CSS-pixel rect of the OUTPUT FRAME (the fixed
648
+ * stage that clips the rendered video). Different from
649
+ * `getActiveFrameRect` which includes the keyframe transform —
650
+ * this one stays put as the user drags / scales the content.
651
+ * Used by the overlay to anchor the dashed border + drag body.
652
+ */
653
+ getActiveOutputFrameRect(): {
654
+ x: number;
655
+ y: number;
656
+ w: number;
657
+ h: number;
658
+ } | null;
659
+ setKeyframesEnabled(enabled: boolean): void;
660
+ isClipEdgeNavEnabled(): boolean;
661
+ setClipEdgeNavEnabled(enabled: boolean): void;
662
+ addKeyframe(clipId: string, prop: KeyframeProp, opts?: {
663
+ time?: Ms;
664
+ value?: number;
665
+ }): string | null;
666
+ removeKeyframe(clipId: string, keyframeId: string): boolean;
667
+ moveKeyframe(clipId: string, keyframeId: string, timeMs: Ms): boolean;
668
+ setKeyframeValue(clipId: string, keyframeId: string, value: number): boolean;
669
+ setKeyframeEasing(clipId: string, keyframeId: string, easing: EasingKind): boolean;
670
+ setKeyframesEasingAtTime(clipId: string, timeMs: Ms, easing: EasingKind): boolean;
671
+ setValueAtPlayhead(clipId: string, prop: KeyframeProp, value: number): boolean;
672
+ getSelectedKeyframe(): {
673
+ clipId: string;
674
+ keyframeId: string;
675
+ } | null;
676
+ resetClipTransform(clipId: string): boolean;
677
+ resetKeyframesAtTime(clipId: string, timeMs: Ms): boolean;
678
+ seekToClipEdge(clipId: string, edge: "start" | "end"): boolean;
679
+ seekToSelectedClipEdge(edge: "start" | "end"): boolean;
680
+ toggleKeyframeAtPlayhead(): boolean;
681
+ setSelectedKeyframe(target: {
682
+ clipId: string;
683
+ keyframeId: string;
684
+ } | null): void;
255
685
  canUndo(): boolean;
256
686
  canRedo(): boolean;
257
687
  undo(): boolean;
258
688
  redo(): boolean;
689
+ beginInteraction(): void;
690
+ endInteraction(): void;
691
+ /**
692
+ * Selections (clipId + selectedKeyframe) live OUTSIDE the project
693
+ * snapshot, so undo / redo can leave them pointing at ids that no
694
+ * longer exist. Defend against dangling refs by clearing anything
695
+ * the restored project doesn't actually contain — and emit the
696
+ * paired change events so panels / overlays hide cleanly instead
697
+ * of holding zombie references.
698
+ */
699
+ private reconcileSelectionsWithProject;
259
700
  on<K extends EditorEventName>(event: K, handler: (payload: EditorEventMap[K]) => void): () => void;
260
701
  off<K extends EditorEventName>(event: K, handler: (payload: EditorEventMap[K]) => void): void;
261
702
  destroy(): void;
@@ -267,6 +708,32 @@ declare class Editor implements EditorApi {
267
708
  private handleSourceMetadata;
268
709
  }
269
710
 
711
+ /**
712
+ * The transform a clip's content is rendered with at a given moment.
713
+ * Engines apply this INSIDE a fixed output frame: `panX` / `panY`
714
+ * translate the content (in CSS px), `scale` resizes it around the
715
+ * output frame's center. Anything pushed outside the frame is
716
+ * clipped. Identity = `{ panX: 0, panY: 0, scale: 1 }`.
717
+ */
718
+ interface EffectiveTransform {
719
+ panX: number;
720
+ panY: number;
721
+ scale: number;
722
+ }
723
+ /** Identity transform — no pan, no scaling (content fills the output frame). */
724
+ declare const IDENTITY_TRANSFORM: EffectiveTransform;
725
+ /** True when a transform is effectively identity (within FP slop). */
726
+ declare function isIdentityTransform(t: EffectiveTransform): boolean;
727
+
728
+ /**
729
+ * Effective transform = all three properties evaluated together. The
730
+ * engine applies this to the content inside the fixed output frame:
731
+ * scale around frame center, then translate by (panX, panY).
732
+ */
733
+ declare function getEffectiveTransform(clip: Clip, localMs: Ms): EffectiveTransform;
734
+ /** Same as `getEffectiveTransform` but takes timeline-absolute time. */
735
+ declare function getTransformAtTimelineTime(clip: Clip, timelineMs: Ms): EffectiveTransform;
736
+
270
737
  declare function createEmptyProject(): Project;
271
738
  /**
272
739
  * Defensive normalization — ensures clips on each track are sorted by
@@ -282,10 +749,30 @@ declare function normalizeProject(project: Project): Project;
282
749
  */
283
750
  declare function createId(prefix?: string): string;
284
751
 
285
- /** Visual constants — kept here so draw + hit-test share one source of truth. */
286
- declare const TRACK_HEIGHT = 56;
287
- declare const RULER_HEIGHT = 24;
752
+ /** Visual constants — kept here so draw + hit-test share one source of truth.
753
+ * The two row-height values are `let` rather than `const` so hosts can
754
+ * shrink the timeline footprint for small screens via
755
+ * `setTimelineMetrics(...)`. ES module live bindings mean every importer
756
+ * sees the updated value automatically. */
757
+ declare let TRACK_HEIGHT: number;
758
+ declare let RULER_HEIGHT: number;
288
759
  declare const HEADER_WIDTH = 96;
760
+ /**
761
+ * Override the default timeline row + ruler heights. Process-wide — call
762
+ * before / during editor construction. Useful when the editor is mounted
763
+ * in a small viewport (e.g. side-by-side panel on a laptop) and the
764
+ * default 56px tracks crowd out the preview.
765
+ *
766
+ * Reasonable ranges: trackHeight ∈ [28, 96], rulerHeight ∈ [18, 36].
767
+ * Anything smaller leaves no room for the clip label or the time ticks.
768
+ *
769
+ * Multi-editor mounts share these values. If you have two editors with
770
+ * different needs, agree on the smaller one or remount on switch.
771
+ */
772
+ declare function setTimelineMetrics(opts: {
773
+ trackHeight?: number;
774
+ rulerHeight?: number;
775
+ }): void;
289
776
 
290
777
  /**
291
778
  * Public options for the standalone `Timeline` component. The class
@@ -334,6 +821,23 @@ interface TimelineOptions {
334
821
  }) => void;
335
822
  onResizeClip?: (clipId: string, edits: Partial<Pick<Clip, "in" | "out" | "start">>) => void;
336
823
  onChange?: (project: Project) => void;
824
+ /**
825
+ * Hosts wire these to forward keyframe edits to the Editor. The
826
+ * Timeline only paints + hit-tests; mutation goes through these
827
+ * callbacks so the Editor can push history + emit events.
828
+ */
829
+ onSelectKeyframe?: (target: {
830
+ clipId: string;
831
+ keyframeId: string;
832
+ } | null) => void;
833
+ onMoveKeyframe?: (clipId: string, keyframeId: string, timeMs: Ms) => void;
834
+ /** Host-driven state mirror — Editor passes these on every render
835
+ * via `Timeline.setKeyframeState`. */
836
+ keyframesEnabled?: boolean;
837
+ selectedKeyframe?: {
838
+ clipId: string;
839
+ keyframeId: string;
840
+ } | null;
337
841
  /**
338
842
  * Lets the host predict where a drop will actually land — used to
339
843
  * keep the drag-ghost visual honest. The Editor wires this to its
@@ -382,6 +886,8 @@ declare class Timeline {
382
886
  private readOnly;
383
887
  private autoFitEnabled;
384
888
  private locale;
889
+ private keyframesEnabled;
890
+ private selectedKeyframe;
385
891
  private scrollLeft;
386
892
  private scrollTop;
387
893
  private viewportWidth;
@@ -400,6 +906,7 @@ declare class Timeline {
400
906
  private scrollbarDrag;
401
907
  private hoveredClipId;
402
908
  private hoveredTrackIndex;
909
+ private hoveredKeyframe;
403
910
  private hoverCursor;
404
911
  private dropTargetTrackIndex;
405
912
  private snapX;
@@ -496,6 +1003,15 @@ declare class Timeline {
496
1003
  private maybeContinueFade;
497
1004
  private maybeAutoFit;
498
1005
  private buildDrawState;
1006
+ /** Host-pushed state — Editor calls this when its keyframe mode
1007
+ * changes or when a keyframe is selected/deselected externally. */
1008
+ setKeyframeState(state: {
1009
+ enabled?: boolean;
1010
+ selected?: {
1011
+ clipId: string;
1012
+ keyframeId: string;
1013
+ } | null;
1014
+ }): void;
499
1015
  private readStyle;
500
1016
  private attachPointer;
501
1017
  private onPointerDown;
@@ -524,6 +1040,7 @@ declare class Timeline {
524
1040
  */
525
1041
  private maybeStartDragAutoScroll;
526
1042
  private onPointerUp;
1043
+ private attachKeyboard;
527
1044
  private attachWheel;
528
1045
  private attachResize;
529
1046
  private localCoords;
@@ -532,4 +1049,4 @@ declare class Timeline {
532
1049
  private applySnap;
533
1050
  }
534
1051
 
535
- export { Clip, Editor, type EditorApi, type EditorEventMap, type EditorEventName, type EditorOptions, HEADER_WIDTH, Locale, MediaSource, Ms, Project, RULER_HEIGHT, TRACK_HEIGHT, Theme, Timeline, type TimelineOptions, Track, createEmptyProject, createId, normalizeProject };
1052
+ export { CanvasCompositorEngine, type CanvasCompositorEngineOptions, Clip, EasingKind, Editor, type EditorApi, type EditorEventMap, type EditorEventName, type EditorOptions, type EffectiveTransform, HEADER_WIDTH, HtmlVideoEngine, IDENTITY_TRANSFORM, KeyframeProp, Locale, MediaSource, Ms, PlaybackEngine, PlaybackEngineFactory, PlaybackEngineOptions, Project, RULER_HEIGHT, TRACK_HEIGHT, Theme, Timeline, type TimelineOptions, Track, canvasCompositorEngineFactory, createEmptyProject, createId, getEffectiveTransform, getTransformAtTimelineTime, htmlVideoEngineFactory, isIdentityTransform, normalizeProject, setTimelineMetrics };