1- import { useCallback , useEffect , useRef , useState } from 'react'
1+ import { useCallback , useEffect , useId , useRef , useState } from 'react'
22import { usePlayheadScrub } from './usePlayheadScrub'
33import c from './Timeline.module.css'
44import { TimelineTrack } from './TimelineTrack'
55import { TimelineManagerTrack } from '@/types'
6+ import { findTrackById } from '@/utils/findTrackById'
67
78export interface TimelineProps {
89 /** Timeline data */
910 timeline : {
1011 durationMs : number
1112 tracks : TimelineManagerTrack [ ]
1213 }
13- /** Current playhead position in milliseconds */
14- playheadPositionMs ?: number
15- /** Called when the user clicks on the track area to set the playhead */
16- onPlayheadChange ?: ( time : number ) => void
17- /** Called when a keyframe should be deleted */
18- onKeyframeDelete ?: ( keyframeId : string ) => void
19- /** Called when a keyframe should be inserted on a track at a given time */
20- onKeyframeInsert ?: ( trackId : string , time : number ) => void
14+ playheadPositionMs : number
15+ activeTimelineComponentId : string | null
16+ selectedTrackId : string | null
17+ setSelectedTrackId : ( trackId : string | null ) => void
18+ setActiveTimelineComponentId : ( id : string | null ) => void
19+ componentId ?: string
20+ onPlayheadChange : ( time : number ) => void
21+ onKeyframeDelete : ( keyframeId : string ) => void
22+ onKeyframeInsert : ( trackId : string , time : number ) => void
2123}
2224
2325export function Timeline ( {
2426 timeline,
2527 playheadPositionMs = 0 ,
28+ activeTimelineComponentId,
29+ selectedTrackId : _selectedTrackId ,
30+ setSelectedTrackId : _setSelectedTrackId ,
31+ componentId : _componentId ,
32+ setActiveTimelineComponentId,
2633 onPlayheadChange,
2734 onKeyframeDelete,
2835 onKeyframeInsert,
2936} : TimelineProps ) {
3037 const { durationMs, tracks } = timeline
3138 const rulerAreaRef = useRef < HTMLDivElement > ( null )
32- const [ selectedTrackId , setSelectedTrackId ] = useState < string | null > ( null )
33- const [ selectedKeyframes , setSelectedKeyframes ] = useState < string [ ] | null > ( null )
34-
35- const findTrackById = useCallback (
36- ( trackList : TimelineManagerTrack [ ] , trackId : string ) : TimelineManagerTrack | null => {
37- for ( const track of trackList ) {
38- if ( track . id === trackId ) {
39- return track
40- }
4139
42- if ( track . trackType === 'vector' ) {
43- const childTrack = findTrackById ( track . childTracks , trackId )
44- if ( childTrack ) {
45- return childTrack
46- }
47- }
48- }
40+ // To prevent clashing of keyboard events, we need to keep track of which component is the active one
41+ const fallbackId = useId ( )
42+ const componentId = _componentId ?? fallbackId
43+
44+ const isActiveComponent = componentId === activeTimelineComponentId
45+
46+ const [ _selectedKeyframes , _setSelectedKeyframes ] = useState < string [ ] | null > ( null )
47+
48+ const selectedKeyframes = isActiveComponent ? _selectedKeyframes : null
49+ const selectedTrackId = isActiveComponent ? _selectedTrackId : null
50+
51+ const setSelectedKeyframes = useCallback (
52+ ( keyframes : string [ ] | null ) => {
53+ _setSelectedKeyframes ( keyframes )
4954
50- return null
55+ setActiveTimelineComponentId ( componentId )
5156 } ,
52- [ ] ,
57+ [ componentId , setActiveTimelineComponentId ] ,
58+ )
59+
60+ const setSelectedTrackId = useCallback (
61+ ( trackId : string | null ) => {
62+ _setSelectedTrackId ( trackId )
63+
64+ setActiveTimelineComponentId ( componentId )
65+ } ,
66+ [ _setSelectedTrackId , componentId , setActiveTimelineComponentId ] ,
5367 )
5468
5569 useEffect ( ( ) => {
@@ -67,19 +81,21 @@ export function Timeline({
6781
6882 const handleKeyDown = ( e : KeyboardEvent ) => {
6983 const target = e . target as HTMLElement | null
70- if ( target ?. tagName === 'INPUT' || target ?. tagName === 'TEXTAREA' || target ?. isContentEditable ) {
84+ if (
85+ target ?. tagName === 'INPUT' ||
86+ target ?. tagName === 'TEXTAREA' ||
87+ target ?. isContentEditable
88+ ) {
7189 return
7290 }
7391 if ( e . key === 'x' && selectedKeyframes ) {
7492 selectedKeyframes . forEach ( ( keyframeId ) => {
75- onKeyframeDelete ?. ( keyframeId )
93+ onKeyframeDelete ( keyframeId )
7694 } )
7795
7896 setSelectedKeyframes ( null )
7997 }
8098 if ( e . key === 'i' && selectedTrackId ) {
81- if ( ! onKeyframeInsert ) return
82-
8399 const track = findTrackById ( tracks , selectedTrackId )
84100 if ( ! track ) return
85101
@@ -89,15 +105,17 @@ export function Timeline({
89105 }
90106 }
91107 window . addEventListener ( 'keydown' , handleKeyDown )
92- return ( ) => window . removeEventListener ( 'keydown' , handleKeyDown )
108+ return ( ) => {
109+ window . removeEventListener ( 'keydown' , handleKeyDown )
110+ }
93111 } , [
94112 selectedKeyframes ,
95113 onKeyframeDelete ,
96114 selectedTrackId ,
97115 playheadPositionMs ,
98116 onKeyframeInsert ,
99117 tracks ,
100- findTrackById ,
118+ setSelectedKeyframes ,
101119 ] )
102120
103121 usePlayheadScrub ( durationMs , rulerAreaRef , playheadPositionMs , onPlayheadChange )
0 commit comments