@@ -10,7 +10,11 @@ import { defaultTimelineTheme } from "./timelineTheme";
1010import { TRACK_H , getTimelineRowGeometry } from "./timelineLayout" ;
1111import { createTimelineClipIndex } from "../lib/timelineClipIndex" ;
1212import { buildTimelineLogicalRows } from "./timelineKeyboardNavigation" ;
13- import { usePlayerStore , type TimelineElement } from "../store/playerStore" ;
13+ import {
14+ usePlayerStore ,
15+ type KeyframeCacheEntry ,
16+ type TimelineElement ,
17+ } from "../store/playerStore" ;
1418import type { MultiDragPreviewInput } from "./timelineMultiDragPreview" ;
1519import type { TimelineEditCallbacks } from "./timelineCallbacks" ;
1620import type { DraggedClipState , BlockedClipState } from "./useTimelineClipDrag" ;
@@ -73,6 +77,7 @@ function positionTween(id: string): GsapAnimation {
7377
7478interface RenderLanesOptions {
7579 elements ?: TimelineElement [ ] ;
80+ keyframeCache ?: Map < string , KeyframeCacheEntry > ;
7681 animations ?: Map < string , GsapAnimation [ ] > ;
7782 expandedClipIds ?: string [ ] ;
7883 selectedElementIds ?: Set < string > ;
@@ -172,6 +177,7 @@ function renderLanes(options: RenderLanesOptions = {}): {
172177 getPreviewElement = { ( el ) => el }
173178 getTrackStyle = { getTrackStyle }
174179 gsapAnimations = { gsapAnimations }
180+ keyframeCache = { next . keyframeCache }
175181 selectedKeyframes = { new Set ( ) }
176182 currentTime = { 0 }
177183 onContextMenuLane = { next . onContextMenuLane }
@@ -399,18 +405,37 @@ describe("TimelineLanes disclosure target", () => {
399405 draggedPreviewStart : 0.5 ,
400406 selectedKeys : selectedElementIds ,
401407 } ;
402- const view = renderLanes ( { elements, selectedElementIds } ) ;
408+ const keyframeCache = new Map < string , KeyframeCacheEntry > ( [
409+ [
410+ "clip-a" ,
411+ {
412+ format : "percentage" ,
413+ keyframes : [
414+ { percentage : 0 , properties : { x : 0 } } ,
415+ { percentage : 100 , properties : { x : 50 } } ,
416+ ] ,
417+ } ,
418+ ] ,
419+ ] ) ;
420+ const view = renderLanes ( { elements, selectedElementIds, keyframeCache } ) ;
403421 const clipA = view . host . querySelector < HTMLElement > ( '[data-el-id="clip-a"]' ) ;
422+ const diamonds = ( ) =>
423+ view . host
424+ . querySelector ( '[data-el-id="clip-a"] ~ div button[aria-label*="keyframe at"]' )
425+ ?. closest < HTMLElement > ( "div.pointer-events-none" ) ;
404426 expect ( clipA ) . not . toBeNull ( ) ;
405427 expect ( clipA ?. parentElement ) . toBe (
406428 view . host . querySelector ( '[data-el-id="clip-b"]' ) ?. parentElement ,
407429 ) ;
408430
409- view . rerender ( { elements, selectedElementIds, multiDragPreview : dragging } ) ;
431+ expect ( diamonds ( ) ) . not . toBeNull ( ) ;
432+ view . rerender ( { elements, selectedElementIds, keyframeCache, multiDragPreview : dragging } ) ;
410433 expect ( view . host . querySelector ( '[data-el-id="clip-a"]' ) ) . toBe ( clipA ) ;
411434 expect ( clipA ?. style . transform ) . toMatch ( / ^ t r a n s l a t e X \( [ 1 - 9 ] / ) ;
435+ // Its keyframe diamonds ride with it.
436+ expect ( diamonds ( ) ?. style . transform ) . toBe ( clipA ?. style . transform ) ;
412437
413- view . rerender ( { elements, selectedElementIds } ) ;
438+ view . rerender ( { elements, selectedElementIds, keyframeCache } ) ;
414439 expect ( view . host . querySelector ( '[data-el-id="clip-a"]' ) ) . toBe ( clipA ) ;
415440 expect ( clipA ?. style . transform ) . toBe ( "" ) ;
416441 act ( ( ) => view . root . unmount ( ) ) ;
@@ -437,9 +462,11 @@ describe("TimelineLanes disclosure target", () => {
437462 } ) ;
438463
439464 const before = ariaControlsTarget ( view . host ) ;
440- const beforeLane = before ?. querySelector ( "[data-timeline-property-lane]" ) ;
465+ const beforeLane = before ?. querySelector < HTMLElement > ( "[data-timeline-property-lane]" ) ;
441466 expect ( before ) . not . toBeNull ( ) ;
442467 expect ( beforeLane ) . not . toBeNull ( ) ;
468+ const startOffset = beforeLane ?. style . transform ;
469+ expect ( startOffset ) . toMatch ( / ^ t r a n s l a t e X \( [ 1 - 9 ] / ) ;
443470
444471 view . rerender ( {
445472 elements,
@@ -452,6 +479,9 @@ describe("TimelineLanes disclosure target", () => {
452479 // Node identity, not just presence: a remount replaces these nodes.
453480 expect ( ariaControlsTarget ( view . host ) ) . toBe ( before ) ;
454481 expect ( before ?. querySelector ( "[data-timeline-property-lane]" ) ) . toBe ( beforeLane ) ;
482+ // The lanes ride with the formation, not just the clip.
483+ expect ( beforeLane ?. style . transform ) . toMatch ( / ^ t r a n s l a t e X \( [ 1 - 9 ] / ) ;
484+ expect ( beforeLane ?. style . transform ) . not . toBe ( startOffset ) ;
455485 act ( ( ) => view . root . unmount ( ) ) ;
456486 } ) ;
457487} ) ;
0 commit comments