@editframe/elements 0.30.1-beta.0 → 0.31.0-beta.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/EF_FRAMEGEN.d.ts +5 -0
- package/dist/EF_FRAMEGEN.js +20 -4
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_INTERACTIVE.js.map +1 -1
- package/dist/_virtual/rolldown_runtime.js +27 -0
- package/dist/canvas/EFCanvas.d.ts +311 -0
- package/dist/canvas/EFCanvas.js +1089 -0
- package/dist/canvas/EFCanvas.js.map +1 -0
- package/dist/canvas/EFCanvasItem.d.ts +55 -0
- package/dist/canvas/EFCanvasItem.js +72 -0
- package/dist/canvas/EFCanvasItem.js.map +1 -0
- package/dist/canvas/api/CanvasAPI.d.ts +115 -0
- package/dist/canvas/api/CanvasAPI.js +182 -0
- package/dist/canvas/api/CanvasAPI.js.map +1 -0
- package/dist/canvas/api/types.d.ts +42 -0
- package/dist/canvas/coordinateTransform.js +90 -0
- package/dist/canvas/coordinateTransform.js.map +1 -0
- package/dist/canvas/getElementBounds.js +40 -0
- package/dist/canvas/getElementBounds.js.map +1 -0
- package/dist/canvas/overlays/SelectionOverlay.js +265 -0
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -0
- package/dist/canvas/overlays/overlayState.js +153 -0
- package/dist/canvas/overlays/overlayState.js.map +1 -0
- package/dist/canvas/selection/SelectionController.js +105 -0
- package/dist/canvas/selection/SelectionController.js.map +1 -0
- package/dist/canvas/selection/SelectionModel.d.ts +98 -0
- package/dist/canvas/selection/SelectionModel.js +229 -0
- package/dist/canvas/selection/SelectionModel.js.map +1 -0
- package/dist/canvas/selection/selectionContext.d.ts +31 -0
- package/dist/canvas/selection/selectionContext.js +12 -0
- package/dist/canvas/selection/selectionContext.js.map +1 -0
- package/dist/elements/ContainerInfo.d.ts +29 -0
- package/dist/elements/ContainerInfo.js +30 -0
- package/dist/elements/ContainerInfo.js.map +1 -0
- package/dist/elements/EFAudio.d.ts +13 -3
- package/dist/elements/EFAudio.js +64 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +18 -16
- package/dist/elements/EFCaptions.js +110 -19
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +16 -6
- package/dist/elements/EFImage.js +79 -9
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +51 -4
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +125 -52
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +24 -6
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +12 -8
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +46 -7
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +98 -73
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +28 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +18 -6
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +8 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +31 -6
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +28 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +97 -72
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +25 -14
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +47 -16
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +37 -19
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +65 -21
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +8 -3
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +32 -9
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +33 -10
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +23 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +34 -10
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +31 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +31 -114
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +44 -8
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +18 -7
- package/dist/elements/EFMedia.js +23 -3
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +96 -0
- package/dist/elements/EFPanZoom.js +290 -0
- package/dist/elements/EFPanZoom.js.map +1 -0
- package/dist/elements/EFSourceMixin.js +7 -6
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +6 -6
- package/dist/elements/EFSurface.js +7 -2
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +2 -1
- package/dist/elements/EFTemporal.js +192 -71
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +5 -4
- package/dist/elements/EFText.js +102 -13
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.d.ts +32 -6
- package/dist/elements/EFTextSegment.js +53 -15
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFThumbnailStrip.d.ts +118 -56
- package/dist/elements/EFThumbnailStrip.js +522 -358
- package/dist/elements/EFThumbnailStrip.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +223 -27
- package/dist/elements/EFTimegroup.js +851 -148
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +42 -5
- package/dist/elements/EFVideo.js +165 -11
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +6 -6
- package/dist/elements/EFWaveform.js +2 -1
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.d.ts +35 -0
- package/dist/elements/ElementPositionInfo.js +49 -0
- package/dist/elements/ElementPositionInfo.js.map +1 -0
- package/dist/elements/FetchMixin.js +16 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/SessionThumbnailCache.js +152 -0
- package/dist/elements/SessionThumbnailCache.js.map +1 -0
- package/dist/elements/TargetController.js +3 -1
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js +9 -3
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/findRootTemporal.js +30 -0
- package/dist/elements/findRootTemporal.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +18 -5
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/updateAnimations.js +492 -109
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +2 -2
- package/dist/gui/ContextMixin.js +4 -2
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +74 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +50 -0
- package/dist/gui/EFActiveRootTemporal.js +94 -0
- package/dist/gui/EFActiveRootTemporal.js.map +1 -0
- package/dist/gui/EFConfiguration.d.ts +11 -5
- package/dist/gui/EFConfiguration.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +109 -13
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFFilmstrip.d.ts +11 -214
- package/dist/gui/EFFilmstrip.js +53 -1152
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +3 -3
- package/dist/gui/EFFitScale.js +39 -12
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFOverlayItem.d.ts +48 -0
- package/dist/gui/EFOverlayItem.js +97 -0
- package/dist/gui/EFOverlayItem.js.map +1 -0
- package/dist/gui/EFOverlayLayer.d.ts +70 -0
- package/dist/gui/EFOverlayLayer.js +104 -0
- package/dist/gui/EFOverlayLayer.js.map +1 -0
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPreview.d.ts +4 -4
- package/dist/gui/EFResizableBox.d.ts +12 -16
- package/dist/gui/EFResizableBox.js +109 -451
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +30 -5
- package/dist/gui/EFScrubber.js +224 -31
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.js +4 -1
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +71 -0
- package/dist/gui/EFTimelineRuler.js +320 -0
- package/dist/gui/EFTimelineRuler.js.map +1 -0
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTransformHandles.d.ts +91 -0
- package/dist/gui/EFTransformHandles.js +393 -0
- package/dist/gui/EFTransformHandles.js.map +1 -0
- package/dist/gui/EFWorkbench.d.ts +182 -4
- package/dist/gui/EFWorkbench.js +2067 -22
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.d.ts +31 -0
- package/dist/gui/FitScaleHelpers.js +41 -0
- package/dist/gui/FitScaleHelpers.js.map +1 -0
- package/dist/gui/PlaybackController.d.ts +2 -1
- package/dist/gui/PlaybackController.js +46 -15
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchy.d.ts +65 -0
- package/dist/gui/hierarchy/EFHierarchy.js +338 -0
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -0
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +118 -0
- package/dist/gui/hierarchy/EFHierarchyItem.js +551 -0
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -0
- package/dist/gui/hierarchy/hierarchyContext.d.ts +38 -0
- package/dist/gui/hierarchy/hierarchyContext.js +8 -0
- package/dist/gui/hierarchy/hierarchyContext.js.map +1 -0
- package/dist/gui/icons.js +34 -0
- package/dist/gui/icons.js.map +1 -0
- package/dist/gui/panZoomTransformContext.js +12 -0
- package/dist/gui/panZoomTransformContext.js.map +1 -0
- package/dist/gui/previewSettingsContext.js +12 -0
- package/dist/gui/previewSettingsContext.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +270 -0
- package/dist/gui/timeline/EFTimeline.js +1369 -0
- package/dist/gui/timeline/EFTimeline.js.map +1 -0
- package/dist/gui/timeline/EFTimelineRow.js +374 -0
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -0
- package/dist/gui/timeline/TrimHandles.d.ts +36 -0
- package/dist/gui/timeline/TrimHandles.js +204 -0
- package/dist/gui/timeline/TrimHandles.js.map +1 -0
- package/dist/gui/timeline/flattenHierarchy.js +31 -0
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.d.ts +26 -0
- package/dist/gui/timeline/timelineStateContext.js +42 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -0
- package/dist/gui/timeline/tracks/AudioTrack.js +264 -0
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/CaptionsTrack.js +595 -0
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js +19 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/ImageTrack.js +53 -0
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TextTrack.js +250 -0
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TimegroupTrack.js +143 -0
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/TrackItem.js +269 -0
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -0
- package/dist/gui/timeline/tracks/VideoTrack.js +265 -0
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/WaveformTrack.js +19 -0
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -0
- package/dist/gui/timeline/tracks/ensureTrackItemInit.js +1 -0
- package/dist/gui/timeline/tracks/preloadTracks.js +9 -0
- package/dist/gui/timeline/tracks/renderTrackChildren.js +119 -0
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -0
- package/dist/gui/timeline/tracks/waveformUtils.js +80 -0
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -0
- package/dist/gui/transformCalculations.js +217 -0
- package/dist/gui/transformCalculations.js.map +1 -0
- package/dist/gui/transformUtils.d.ts +37 -0
- package/dist/gui/transformUtils.js +77 -0
- package/dist/gui/transformUtils.js.map +1 -0
- package/dist/gui/tree/EFTree.d.ts +59 -0
- package/dist/gui/tree/EFTree.js +174 -0
- package/dist/gui/tree/EFTree.js.map +1 -0
- package/dist/gui/tree/EFTreeItem.d.ts +38 -0
- package/dist/gui/tree/EFTreeItem.js +146 -0
- package/dist/gui/tree/EFTreeItem.js.map +1 -0
- package/dist/gui/tree/treeContext.d.ts +60 -0
- package/dist/gui/tree/treeContext.js +23 -0
- package/dist/gui/tree/treeContext.js.map +1 -0
- package/dist/index.d.ts +32 -8
- package/dist/index.js +30 -6
- package/dist/index.js.map +1 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +688 -0
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +1 -0
- package/dist/node_modules/react/cjs/react.development.js +1521 -0
- package/dist/node_modules/react/cjs/react.development.js.map +1 -0
- package/dist/node_modules/react/index.js +13 -0
- package/dist/node_modules/react/index.js.map +1 -0
- package/dist/node_modules/react/jsx-runtime.js +13 -0
- package/dist/node_modules/react/jsx-runtime.js.map +1 -0
- package/dist/preview/AdaptiveResolutionTracker.js +228 -0
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -0
- package/dist/preview/RenderProfiler.js +135 -0
- package/dist/preview/RenderProfiler.js.map +1 -0
- package/dist/preview/previewSettings.js +131 -0
- package/dist/preview/previewSettings.js.map +1 -0
- package/dist/preview/previewTypes.js +64 -0
- package/dist/preview/previewTypes.js.map +1 -0
- package/dist/preview/renderTimegroupPreview.js +656 -0
- package/dist/preview/renderTimegroupPreview.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.d.ts +37 -0
- package/dist/preview/renderTimegroupToCanvas.js +840 -0
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToVideo.d.ts +39 -0
- package/dist/preview/renderTimegroupToVideo.js +274 -0
- package/dist/preview/renderTimegroupToVideo.js.map +1 -0
- package/dist/preview/renderers.js +16 -0
- package/dist/preview/renderers.js.map +1 -0
- package/dist/preview/statsTrackingStrategy.js +201 -0
- package/dist/preview/statsTrackingStrategy.js.map +1 -0
- package/dist/preview/thumbnailCacheSettings.js +52 -0
- package/dist/preview/thumbnailCacheSettings.js.map +1 -0
- package/dist/preview/workers/WorkerPool.js +178 -0
- package/dist/preview/workers/WorkerPool.js.map +1 -0
- package/dist/sandbox/PlaybackControls.js +10 -0
- package/dist/sandbox/PlaybackControls.js.map +1 -0
- package/dist/sandbox/ScenarioRunner.js +1 -0
- package/dist/sandbox/index.js +2 -0
- package/dist/style.css +66 -69
- package/dist/transcoding/types/index.d.ts +2 -1
- package/dist/transcoding/utils/UrlGenerator.d.ts +6 -1
- package/dist/transcoding/utils/UrlGenerator.js +12 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -375
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +14 -0
- package/dist/utils/frameTime.js.map +1 -0
- package/package.json +3 -3
- package/test/profilingPlugin.ts +223 -0
- package/test/recordReplayProxyPlugin.js +22 -27
- package/test/thumbnail-performance-test.html +116 -0
- package/test/visualRegressionUtils.ts +286 -0
- package/types.json +1 -1
- package/dist/elements/TimegroupController.d.ts +0 -18
- package/dist/msToTimeCode.js +0 -17
- package/dist/msToTimeCode.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EFTimeline.js","names":["EFTimeline","newState: TimelineState","state","#wheelHandler","#playbackSubscription","newTime: number","edgeScrollAnimationId: number | null","tracksScroll"],"sources":["../../../src/gui/timeline/EFTimeline.ts"],"sourcesContent":["import { consume, provide } from \"@lit/context\";\nimport {\n css,\n html,\n LitElement,\n nothing,\n type PropertyValues,\n type TemplateResult,\n} from \"lit\";\nimport {\n customElement,\n eventOptions,\n property,\n state,\n} from \"lit/decorators.js\";\nimport { createRef, ref, type Ref } from \"lit/directives/ref.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\n\n\nimport {\n isEFTemporal,\n type TemporalMixinInterface,\n} from \"../../elements/EFTemporal.js\";\nimport { EFTimegroup } from \"../../elements/EFTimegroup.js\";\nimport { findRootTemporal } from \"../../elements/findRootTemporal.js\";\nimport { TargetController } from \"../../elements/TargetController.js\";\nimport { selectionContext } from \"../../canvas/selection/selectionContext.js\";\nimport { targetTemporalContext } from \"../ContextMixin.js\";\nimport { currentTimeContext } from \"../currentTimeContext.js\";\nimport { durationContext } from \"../durationContext.js\";\nimport { loopContext, playingContext } from \"../playingContext.js\";\nimport type { EFCanvas } from \"../../canvas/EFCanvas.js\";\nimport { shouldRenderElement } from \"../hierarchy/EFHierarchyItem.js\";\nimport { TWMixin } from \"../TWMixin.js\";\nimport type {\n ControllableSubscription,\n} from \"../Controllable.js\";\nimport { createDirectTemporalSubscription } from \"../Controllable.js\";\n// NOTE: Track components (ef-audio-track, ef-video-track, etc.) are NOT imported here\n// to avoid circular dependencies with TrackItem. They must be registered before\n// EFTimeline is used. See preloadTracks.ts for the registration sequence.\nimport \"./tracks/preloadTracks.js\";\nimport type { TrimChangeDetail } from \"./TrimHandles.js\";\nimport { flattenHierarchy } from \"./flattenHierarchy.js\";\nimport \"./EFTimelineRow.js\";\nimport {\n timelineStateContext,\n type TimelineState,\n timeToPx,\n pxToTime,\n DEFAULT_PIXELS_PER_MS,\n pixelsPerMsToZoom,\n} from \"./timelineStateContext.js\";\nimport \"../EFTimelineRuler.js\";\nimport {\n quantizeToFrameTimeMs,\n calculateFrameIntervalMs,\n calculatePixelsPerFrame,\n shouldShowFrameMarkers,\n} from \"../EFTimelineRuler.js\";\nimport \"../../elements/EFThumbnailStrip.js\";\n\n// ============================================================================\n// TIMELINE STATE CONTEXT\n// ============================================================================\n\n/**\n * EFTimeline - Unified timeline component\n *\n * Core invariant: pixelsPerMs determines all positioning.\n * Everything else (ruler, tracks, playhead) derives from this single value.\n */\n@customElement(\"ef-timeline\")\nexport class EFTimeline extends TWMixin(LitElement) {\n static styles = [\n css`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n min-height: 100px;\n \n /* Layout coordination via CSS custom properties */\n --timeline-hierarchy-width: 200px;\n --timeline-row-height: 24px;\n --timeline-track-height: 24px;\n \n /* Theme */\n --timeline-bg: rgb(30 41 59);\n --timeline-border: rgb(71 85 105);\n --timeline-header-bg: rgb(51 65 85);\n --timeline-text: rgb(226 232 240);\n --timeline-ruler-bg: rgb(51 65 85);\n --timeline-track-bg: rgb(51 65 85);\n --timeline-track-hover: rgb(71 85 105);\n --timeline-playhead: rgb(239 68 68);\n }\n \n :host(.light) {\n --timeline-bg: rgb(241 245 249);\n --timeline-border: rgb(203 213 225);\n --timeline-header-bg: rgb(226 232 240);\n --timeline-text: rgb(30 41 59);\n --timeline-ruler-bg: rgb(226 232 240);\n --timeline-track-bg: rgb(226 232 240);\n --timeline-track-hover: rgb(203 213 225);\n --timeline-playhead: rgb(185 28 28);\n }\n \n .timeline-container {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n background: var(--timeline-bg);\n color: var(--timeline-text);\n overflow: hidden;\n }\n \n /* === HEADER / CONTROLS === */\n .header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n padding: 12px 16px;\n background: var(--timeline-header-bg);\n border-bottom: 1px solid var(--timeline-border);\n flex-shrink: 0;\n }\n \n .controls {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n \n .playback-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-right: 12px;\n border-right: 1px solid var(--timeline-border);\n }\n \n .control-btn {\n min-width: 32px;\n height: 32px;\n padding: 6px 10px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n color: inherit;\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n .control-btn:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.2);\n border-color: rgba(255, 255, 255, 0.3);\n }\n \n .control-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n \n .control-btn.active {\n background: rgba(59, 130, 246, 0.6);\n border-color: rgba(59, 130, 246, 0.8);\n }\n \n .time-display {\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 13px;\n font-weight: 500;\n padding: 6px 12px;\n background: rgba(0, 0, 0, 0.3);\n border-radius: 6px;\n letter-spacing: 0.5px;\n }\n \n .zoom-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n \n .zoom-btn {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n color: inherit;\n font-size: 18px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n \n .zoom-btn:hover {\n background: rgba(255, 255, 255, 0.2);\n border-color: rgba(255, 255, 255, 0.3);\n transform: scale(1.05);\n }\n \n .zoom-label {\n font-size: 12px;\n min-width: 48px;\n text-align: center;\n font-weight: 500;\n }\n \n /* === TIMELINE LAYOUT === */\n .timeline-area {\n flex: 1;\n display: flex;\n flex-direction: column;\n position: relative;\n overflow: hidden;\n }\n \n .ruler-row {\n display: flex;\n height: 24px;\n background: var(--timeline-ruler-bg);\n border-bottom: 1px solid var(--timeline-border);\n flex-shrink: 0;\n /* Sticky positioning for native scroll sync */\n position: sticky;\n top: 0;\n z-index: 10;\n }\n \n .ruler-spacer {\n width: var(--timeline-hierarchy-width);\n flex-shrink: 0;\n background: var(--timeline-header-bg);\n border-right: 1px solid var(--timeline-border);\n /* Sticky positioning to stay fixed during horizontal scroll */\n position: sticky;\n left: 0;\n z-index: 11;\n }\n \n .ruler-content {\n flex: 1;\n position: relative;\n overflow: hidden;\n cursor: ew-resize;\n }\n \n .ruler-content ef-timeline-ruler {\n width: 100%;\n height: 100%;\n }\n \n .ruler-playhead-handle {\n position: absolute;\n bottom: -6px;\n transform: translateX(-50%);\n width: 12px;\n height: 12px;\n background: var(--timeline-playhead);\n border-radius: 50%;\n pointer-events: auto;\n cursor: ew-resize;\n z-index: 101;\n }\n \n /* Thumbnail strip row */\n .thumbnail-row {\n display: flex;\n height: 48px;\n background: var(--timeline-bg);\n border-bottom: 1px solid var(--timeline-border);\n flex-shrink: 0;\n }\n \n .thumbnail-spacer {\n width: var(--timeline-hierarchy-width);\n flex-shrink: 0;\n background: var(--timeline-header-bg);\n border-right: 1px solid var(--timeline-border);\n }\n \n .thumbnail-content {\n flex: 1;\n position: relative;\n overflow: hidden;\n }\n \n .thumbnail-strip .thumbnail {\n flex-shrink: 0;\n border-radius: 2px;\n overflow: hidden;\n background: var(--timeline-track-bg);\n }\n \n .tracks-viewport {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n position: relative;\n }\n \n .tracks-scroll {\n flex: 1;\n overflow: auto;\n position: relative;\n background: var(--timeline-track-bg);\n }\n \n /* === TRACKS CONTENT uses grid to layer playhead over tracks === */\n .tracks-content {\n position: relative;\n min-height: 100%;\n display: grid;\n grid-template-columns: 1fr;\n grid-template-rows: 1fr;\n }\n \n .tracks-content > * {\n grid-area: 1 / 1;\n }\n \n .tracks-rows-layer {\n display: flex;\n flex-direction: column;\n }\n \n /* === PLAYHEAD (sticky layer that stays visible during vertical scroll) === */\n .playhead-layer {\n position: sticky;\n top: 0;\n height: 100%;\n pointer-events: none;\n /* Below sticky labels (z-index 10-11) but above tracks */\n z-index: 5;\n overflow: hidden;\n }\n \n .playhead {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--timeline-playhead);\n pointer-events: none;\n }\n \n .playhead-drag-target {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n width: 16px;\n cursor: ew-resize;\n pointer-events: auto;\n }\n \n /* === FRAME HIGHLIGHT === */\n .frame-highlight {\n position: absolute;\n top: 0;\n bottom: 0;\n background: rgba(59, 130, 246, 0.3);\n border-left: 2px solid rgba(59, 130, 246, 0.7);\n pointer-events: none;\n }\n \n .empty-state {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: rgba(148, 163, 184, 0.6);\n font-style: italic;\n }\n `,\n ];\n\n // ============================================================================\n // PROPERTIES\n // ============================================================================\n\n /**\n * Target element ID or \"selection\" to derive from canvas selection.\n * \n * - Empty string (default): Automatically derives target from canvas selection.\n * The timeline shows the root temporal element containing the currently selected element.\n * - \"selection\": Explicitly use selection-derived targeting (same as empty string).\n * - Element ID: Use the specified element as the target (must be a temporal element).\n * \n * When deriving from selection, the timeline automatically updates when selection changes.\n */\n @property({ type: String })\n target = \"\";\n\n /**\n * The core zoom value - pixels per millisecond.\n * All positioning derives from this single value.\n */\n @property({ type: Number, attribute: \"pixels-per-ms\" })\n pixelsPerMs = DEFAULT_PIXELS_PER_MS;\n\n @property({ type: Number, attribute: \"min-zoom\" })\n minZoom = 0.1;\n\n @property({ type: Number, attribute: \"max-zoom\" })\n maxZoom = 10;\n\n @property({ type: Boolean, attribute: \"enable-trim\" })\n enableTrim = false;\n\n @property({ type: Boolean, attribute: \"show-controls\" })\n showControls = true;\n\n @property({ type: Boolean, attribute: \"show-ruler\" })\n showRuler = true;\n\n @property({ type: Boolean, attribute: \"show-hierarchy\" })\n showHierarchy = true;\n\n @property({ type: Boolean, attribute: \"show-playhead\" })\n showPlayhead = true;\n\n @property({ type: Boolean, attribute: \"show-playback-controls\" })\n showPlaybackControls = true;\n\n @property({ type: Boolean, attribute: \"show-zoom-controls\" })\n showZoomControls = true;\n\n @property({ type: Boolean, attribute: \"show-time-display\" })\n showTimeDisplay = true;\n\n /**\n * Target temporal element ID for playback control.\n * Use this to specify which temporal element the timeline controls.\n * If not set and target is a canvas, derives from canvas selection.\n * \n * Examples:\n * - `control-target=\"timegroup-1\"` - control specific timegroup\n * - Empty: derive from canvas active selection\n */\n @property({ type: String, attribute: \"control-target\" })\n controlTarget = \"\";\n\n /**\n * CSS selectors for elements to hide in the timeline.\n * Comma-separated list of selectors (e.g., \"ef-waveform, .helper\").\n */\n @property({ type: String })\n hide = \"\";\n\n /**\n * CSS selectors for elements to show in the timeline.\n * When set, only matching elements are shown. Comma-separated list.\n */\n @property({ type: String })\n show = \"\";\n\n get hideSelectors(): string[] | undefined {\n if (!this.hide) return undefined;\n return this.hide\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n }\n\n get showSelectors(): string[] | undefined {\n if (!this.show) return undefined;\n return this.show\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n }\n\n // ============================================================================\n // STATE\n // ============================================================================\n\n /**\n * Target element for canvas-wide state (selection, highlight).\n * This should be set to the canvas element.\n */\n @state()\n private targetElement: HTMLElement | null = null;\n\n\n @state()\n private currentTimeMs = 0;\n\n @state()\n private isPlaying = false;\n\n @state()\n private isLooping = false;\n\n @state()\n private viewportScrollLeft = 0;\n\n @provide({ context: timelineStateContext })\n @state()\n private _timelineState: TimelineState = {\n pixelsPerMs: DEFAULT_PIXELS_PER_MS,\n currentTimeMs: 0,\n durationMs: 0,\n viewportScrollLeft: 0,\n viewportWidth: 800,\n seek: () => {},\n zoomIn: () => {},\n zoomOut: () => {},\n };\n\n private targetController?: TargetController;\n private tracksScrollRef: Ref<HTMLDivElement> = createRef();\n private containerRef: Ref<HTMLDivElement> = createRef();\n // Refs for direct DOM manipulation of playhead (bypasses Lit render cycle)\n private playheadRef: Ref<HTMLDivElement> = createRef();\n private playheadHandleRef: Ref<HTMLDivElement> = createRef();\n private frameHighlightRef: Ref<HTMLDivElement> = createRef();\n private animationFrameId?: number;\n private selectionChangeHandler?: () => void;\n private scrollHandler?: () => void;\n private keydownHandler?: (e: KeyboardEvent) => void;\n private isDraggingPlayhead = false;\n private targetObserver?: MutationObserver;\n private canvasActiveRootTemporalChangeHandler?: () => void;\n private resizeObserver?: ResizeObserver;\n private cachedViewportWidth = 800; // Cached to avoid layout thrashing\n private saveZoomScrollDebounceTimer: number | null = null;\n // Throttling for context updates (avoid cascading re-renders)\n private lastContextUpdateTime = 0;\n private static readonly CONTEXT_UPDATE_INTERVAL_MS = 100; // 10fps for context updates\n // Subscription to playback controller for playing state (avoids polling race conditions)\n #playbackSubscription: ControllableSubscription | null = null;\n // Wheel event handler bound reference for cleanup\n #wheelHandler: ((e: WheelEvent) => void) | null = null;\n\n // ============================================================================\n // CONTEXT PROVIDERS\n // ============================================================================\n\n @consume({ context: selectionContext, subscribe: true })\n selectionContext?: import(\"../../canvas/selection/selectionContext.js\").SelectionContext;\n\n @provide({ context: playingContext })\n get providedPlaying(): boolean {\n return this.isPlaying;\n }\n\n @provide({ context: loopContext })\n get providedLoop(): boolean {\n return this.isLooping;\n }\n\n @provide({ context: currentTimeContext })\n get providedCurrentTime(): number {\n return this.currentTimeMs;\n }\n\n @provide({ context: durationContext })\n get providedDuration(): number {\n return this.targetTemporal?.durationMs ?? 0;\n }\n\n @provide({ context: targetTemporalContext })\n get providedTargetTemporal(): TemporalMixinInterface | null {\n return this.targetTemporal;\n }\n\n /** Get timeline state (for external access) */\n get timelineState(): TimelineState {\n return this._timelineState;\n }\n\n /** Update timeline state when any constituent value changes */\n private updateTimelineState(): void {\n // Use cached viewport width to avoid layout thrashing\n // ResizeObserver updates cachedViewportWidth when container size changes\n const hierarchyWidth = this.showHierarchy ? EFTimeline.HIERARCHY_WIDTH : 0;\n const viewportWidth = this.cachedViewportWidth - hierarchyWidth;\n \n const newState: TimelineState = {\n pixelsPerMs: this.pixelsPerMs,\n currentTimeMs: this.currentTimeMs,\n durationMs: this.durationMs,\n viewportScrollLeft: this.viewportScrollLeft,\n viewportWidth,\n seek: (ms: number) => this.handleSeek(ms),\n zoomIn: () => this.handleZoomIn(),\n zoomOut: () => this.handleZoomOut(),\n };\n\n // Check which values changed\n const pixelsPerMsChanged = this._timelineState.pixelsPerMs !== newState.pixelsPerMs;\n const currentTimeMsChanged = this._timelineState.currentTimeMs !== newState.currentTimeMs;\n const durationMsChanged = this._timelineState.durationMs !== newState.durationMs;\n const viewportScrollLeftChanged = this._timelineState.viewportScrollLeft !== newState.viewportScrollLeft;\n const viewportWidthChanged = this._timelineState.viewportWidth !== newState.viewportWidth;\n \n // PERFORMANCE: During playback, scroll changes should NOT trigger context updates.\n // \n // Why this works:\n // 1. Context consumers (ruler, thumbnails) are INSIDE the scroll container\n // 2. They scroll natively with the container - no re-render needed for visual correctness\n // 3. They have pre-rendered buffers (RULER_CANVAS_BUFFER, VIRTUAL_RENDER_PADDING_PX)\n // 4. Virtualization updates can wait until playback pauses\n //\n // This prevents the cascade: scroll → context update → all consumers re-render\n // which was causing stuttering when playback and auto-scroll happened together.\n const scrollOnlyChange = viewportScrollLeftChanged && \n !pixelsPerMsChanged && !currentTimeMsChanged && !durationMsChanged && !viewportWidthChanged;\n \n const shouldSkipScrollUpdate = scrollOnlyChange && this.isPlaying;\n \n const hasRelevantChanges = \n pixelsPerMsChanged || currentTimeMsChanged || durationMsChanged || \n viewportWidthChanged || (viewportScrollLeftChanged && !shouldSkipScrollUpdate);\n \n if (hasRelevantChanges) {\n // Update state - this will trigger context updates to consumers\n this._timelineState = newState;\n // Explicitly request update to ensure consumers are notified\n this.requestUpdate();\n }\n }\n\n // ============================================================================\n // DERIVED STATE\n // ============================================================================\n\n /**\n * Get the target canvas element.\n * The canvas is the source of truth for selection and highlight state.\n */\n private getCanvas(): EFCanvas | null {\n // Use target element if it's a canvas\n if (this.targetElement && (this.targetElement as any).selectionContext) {\n return this.targetElement as EFCanvas;\n }\n // Fall back to finding a canvas by ID\n if (this.target) {\n const target = document.getElementById(this.target);\n if (target && (target as any).selectionContext) {\n return target as EFCanvas;\n }\n }\n // Fall back to first canvas in document\n return document.querySelector(\"ef-canvas\") as EFCanvas | null;\n }\n\n private getCanvasSelectionContext():\n | import(\"../../canvas/selection/selectionContext.js\").SelectionContext\n | undefined {\n if (this.selectionContext) return this.selectionContext;\n return this.getCanvas()?.selectionContext;\n }\n\n /**\n * Get the currently highlighted element from the canvas.\n */\n getHighlightedElement(): HTMLElement | null {\n return this.getCanvas()?.highlightedElement ?? null;\n }\n\n /**\n * Set the highlighted element on the canvas.\n * Called when user hovers a row in the timeline.\n */\n setHighlightedElement(element: HTMLElement | null): void {\n this.getCanvas()?.setHighlightedElement(element);\n }\n\n get targetTemporal(): TemporalMixinInterface | null {\n // If controlTarget is explicitly set, look it up directly\n if (this.controlTarget && this.controlTarget !== \"\" && this.controlTarget !== \"selection\") {\n const element = document.getElementById(this.controlTarget);\n if (element && isEFTemporal(element)) {\n return element as TemporalMixinInterface & HTMLElement;\n }\n // Log when controlTarget is set but element not found\n if (this.isPlaying) {\n console.warn(\n \"[EFTimeline] controlTarget set but element not found:\",\n this.controlTarget,\n \"isPlaying:\",\n this.isPlaying,\n );\n }\n }\n\n // If controlTarget is \"selection\" or empty, derive from canvas selection\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx) {\n const selectedIds = Array.from(selectionCtx.selectedIds);\n if (selectedIds.length > 0 && selectedIds[0]) {\n const element = document.getElementById(selectedIds[0]);\n if (element) {\n const rootTemporal = findRootTemporal(element);\n if (rootTemporal) return rootTemporal;\n }\n }\n // Log when selection exists but no temporal found\n if (this.isPlaying && selectedIds.length > 0) {\n console.warn(\n \"[EFTimeline] Selection found but no temporal element:\",\n selectedIds[0],\n \"isPlaying:\",\n this.isPlaying,\n );\n }\n }\n\n // Log when targetTemporal becomes null during playback\n if (this.isPlaying) {\n console.warn(\n \"[EFTimeline] targetTemporal is null during playback. controlTarget:\",\n this.controlTarget,\n \"target:\",\n this.target,\n \"hasSelectionContext:\",\n !!this.getCanvasSelectionContext(),\n );\n }\n\n return null;\n }\n\n get durationMs(): number {\n return this.targetTemporal?.durationMs ?? 0;\n }\n\n /** Content width in pixels (derived from duration and pixelsPerMs) */\n get contentWidthPx(): number {\n return timeToPx(this.durationMs, this.pixelsPerMs);\n }\n\n /** Current zoom as percentage (for display) */\n get zoomPercent(): number {\n return Math.round(pixelsPerMsToZoom(this.pixelsPerMs) * 100);\n }\n\n /** Derive fps from target temporal (defaults to 30) */\n get fps(): number {\n const target = this.targetTemporal;\n if (target && \"fps\" in target) {\n return (target as any).fps ?? 30;\n }\n return 30;\n }\n\n /** Whether frame markers should be visible at current zoom */\n get showFrameMarkers(): boolean {\n const frameIntervalMs = calculateFrameIntervalMs(this.fps);\n const pixelsPerFrame = calculatePixelsPerFrame(\n frameIntervalMs,\n this.pixelsPerMs,\n );\n return shouldShowFrameMarkers(pixelsPerFrame);\n }\n\n /**\n * Get the root timegroup ID for localStorage key generation.\n * Returns null if no root timegroup is found or it has no ID.\n */\n private getRootTimegroupId(): string | null {\n if (!this.targetTemporal) return null;\n \n const rootTemporal = findRootTemporal(this.targetTemporal as unknown as Element);\n \n if (rootTemporal instanceof EFTimegroup && rootTemporal.id) {\n return rootTemporal.id;\n }\n \n return null;\n }\n\n /**\n * Get localStorage key for timeline state (zoom and scroll).\n */\n private getTimelineStorageKey(): string | null {\n const rootId = this.getRootTimegroupId();\n return rootId ? `ef-timeline-${rootId}` : null;\n }\n\n /**\n * Save timeline zoom and scroll to localStorage.\n */\n private saveTimelineState(): void {\n const storageKey = this.getTimelineStorageKey();\n if (!storageKey) return;\n\n try {\n const state = {\n pixelsPerMs: this.pixelsPerMs,\n viewportScrollLeft: this.viewportScrollLeft,\n };\n localStorage.setItem(storageKey, JSON.stringify(state));\n } catch (error) {\n console.warn(\"Failed to save timeline state to localStorage\", error);\n }\n }\n\n /**\n * Restore timeline zoom and scroll from localStorage.\n */\n private restoreTimelineState(): void {\n const storageKey = this.getTimelineStorageKey();\n if (!storageKey) return;\n\n try {\n const stored = localStorage.getItem(storageKey);\n if (!stored) return;\n\n const state = JSON.parse(stored);\n if (typeof state.pixelsPerMs === \"number\" && state.pixelsPerMs > 0) {\n this.pixelsPerMs = Math.max(\n this.minZoom * DEFAULT_PIXELS_PER_MS,\n Math.min(this.maxZoom * DEFAULT_PIXELS_PER_MS, state.pixelsPerMs),\n );\n }\n if (typeof state.viewportScrollLeft === \"number\" && state.viewportScrollLeft >= 0) {\n // Restore scroll position after DOM is ready\n requestAnimationFrame(() => {\n const tracksScroll = this.tracksScrollRef.value;\n if (tracksScroll) {\n tracksScroll.scrollLeft = state.viewportScrollLeft;\n this.viewportScrollLeft = state.viewportScrollLeft;\n }\n });\n }\n } catch (error) {\n console.warn(\"Failed to restore timeline state from localStorage\", error);\n }\n }\n\n /**\n * Debounced save of timeline state to avoid excessive localStorage writes.\n */\n private debouncedSaveTimelineState(): void {\n if (this.saveZoomScrollDebounceTimer !== null) {\n clearTimeout(this.saveZoomScrollDebounceTimer);\n }\n this.saveZoomScrollDebounceTimer = window.setTimeout(() => {\n this.saveZoomScrollDebounceTimer = null;\n this.saveTimelineState();\n }, 200);\n }\n\n // ============================================================================\n // LIFECYCLE\n // ============================================================================\n\n connectedCallback(): void {\n super.connectedCallback();\n this.startTimeUpdate();\n this.setupSelectionListener();\n this.setupKeyboardListener();\n this.updateTimelineState();\n // Subscribe to playback controller when connected\n this.subscribeToPlaybackController();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.stopTimeUpdate();\n this.removeSelectionListener();\n this.removeScrollListener();\n this.removeKeyboardListener();\n this.targetObserver?.disconnect();\n this.resizeObserver?.disconnect();\n // Unsubscribe from playback controller\n this.unsubscribeFromPlaybackController();\n // Save state before disconnecting\n if (this.saveZoomScrollDebounceTimer !== null) {\n clearTimeout(this.saveZoomScrollDebounceTimer);\n this.saveZoomScrollDebounceTimer = null;\n }\n this.saveTimelineState();\n }\n\n /**\n * Setup MutationObserver to watch target element for ANY changes.\n * Re-registers when target changes.\n */\n private setupTargetObserver(): void {\n // Always disconnect from previous target first\n this.targetObserver?.disconnect();\n\n const target = this.targetTemporal;\n if (target && target instanceof Element) {\n this.targetObserver = new MutationObserver(() => this.requestUpdate());\n this.targetObserver.observe(target, {\n childList: true, // children added/removed\n subtree: true, // watch entire subtree\n attributes: true, // attribute changes\n });\n }\n }\n\n protected willUpdate(changedProperties: PropertyValues): void {\n // Setup TargetController for canvas target\n if (changedProperties.has(\"target\") && this.target && !this.targetController) {\n this.targetController = new TargetController(this as any);\n }\n\n // Retry setting up selection listener if not yet connected\n this.setupSelectionListener();\n\n // Always update timeline state - values may come from getters\n this.updateTimelineState();\n\n super.willUpdate(changedProperties);\n }\n\n protected firstUpdated(): void {\n // Set up ResizeObserver to cache viewport width without layout thrashing\n this.setupResizeObserver();\n }\n \n private setupResizeObserver(): void {\n const tracksScroll = this.tracksScrollRef.value;\n if (!tracksScroll) return;\n \n // Disconnect existing observer if any (shouldn't happen, but be safe)\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n \n // Initial measurement (only happens once on setup)\n // Use clientWidth for initial measurement as it's more reliable than contentRect\n // which might be 0 if element hasn't been laid out yet\n const initialWidth = tracksScroll.clientWidth;\n if (initialWidth > 0) {\n this.cachedViewportWidth = initialWidth;\n }\n \n this.resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n // Use contentRect to avoid triggering layout\n const width = entry.contentRect.width;\n if (width > 0) {\n this.cachedViewportWidth = width;\n // Update timeline state to propagate new viewport width to context consumers\n this.updateTimelineState();\n // Request update to trigger re-render with new dimensions\n this.requestUpdate();\n }\n }\n });\n this.resizeObserver.observe(tracksScroll);\n }\n\n protected updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n // Subscribe to playback controller when targetTemporal changes\n if (changedProperties.has(\"targetTemporal\") || changedProperties.has(\"controlTarget\")) {\n this.subscribeToPlaybackController();\n }\n\n // Restore timeline state when target changes\n if (changedProperties.has(\"targetTemporal\") || changedProperties.has(\"target\")) {\n // Wait for DOM to be ready before restoring scroll\n requestAnimationFrame(() => {\n this.restoreTimelineState();\n });\n }\n\n // Save timeline state when zoom or scroll changes\n if (changedProperties.has(\"pixelsPerMs\") || changedProperties.has(\"viewportScrollLeft\")) {\n this.debouncedSaveTimelineState();\n }\n\n // Re-register observer and listeners when target changes\n if (changedProperties.has(\"targetElement\") || changedProperties.has(\"target\")) {\n this.setupTargetObserver();\n // Reset selection listener to ensure we're listening to the right canvas\n this.removeSelectionListener();\n this.selectionChangeHandler = undefined;\n this.setupSelectionListener();\n }\n\n if (this.tracksScrollRef.value && !this.scrollHandler) {\n this.setupScrollListener();\n } else if (!this.tracksScrollRef.value && this.scrollHandler) {\n this.removeScrollListener();\n }\n\n // Set up ResizeObserver when tracks-scroll element becomes available\n // This handles the case where timeline initially has no target (empty state)\n // and then gets a target after selection, causing tracks-scroll to be rendered\n if (this.tracksScrollRef.value && !this.resizeObserver) {\n this.setupResizeObserver();\n // Update timeline state to propagate the newly measured viewport width\n this.updateTimelineState();\n } else if (!this.tracksScrollRef.value && this.resizeObserver) {\n this.resizeObserver.disconnect();\n this.resizeObserver = undefined;\n }\n }\n\n private setupSelectionListener(): void {\n // Don't set up if already set up\n if (this.selectionChangeHandler) {\n return;\n }\n\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx && \"addEventListener\" in selectionCtx) {\n this.selectionChangeHandler = () => this.requestUpdate();\n (selectionCtx as any).addEventListener(\n \"selectionchange\",\n this.selectionChangeHandler,\n );\n // Request update immediately to catch initial selection\n this.requestUpdate();\n }\n\n // Also listen to activeroottemporalchange from canvas for more reliable updates\n const canvas = this.getCanvas();\n if (canvas && !this.canvasActiveRootTemporalChangeHandler) {\n this.canvasActiveRootTemporalChangeHandler = () => {\n this.requestUpdate();\n };\n canvas.addEventListener(\n \"activeroottemporalchange\",\n this.canvasActiveRootTemporalChangeHandler,\n );\n }\n }\n\n private removeSelectionListener(): void {\n const selectionCtx = this.getCanvasSelectionContext();\n if (\n selectionCtx &&\n \"removeEventListener\" in selectionCtx &&\n this.selectionChangeHandler\n ) {\n (selectionCtx as any).removeEventListener(\n \"selectionchange\",\n this.selectionChangeHandler,\n );\n this.selectionChangeHandler = undefined;\n }\n\n // Remove activeroottemporalchange listener\n const canvas = document.querySelector(\"ef-canvas\");\n if (canvas && this.canvasActiveRootTemporalChangeHandler) {\n canvas.removeEventListener(\n \"activeroottemporalchange\",\n this.canvasActiveRootTemporalChangeHandler,\n );\n this.canvasActiveRootTemporalChangeHandler = undefined;\n }\n }\n\n private setupScrollListener(): void {\n if (this.tracksScrollRef.value) {\n this.scrollHandler = () => {\n if (this.tracksScrollRef.value) {\n const newScrollLeft = this.tracksScrollRef.value.scrollLeft;\n // Only update if scroll position actually changed\n if (newScrollLeft !== this.viewportScrollLeft) {\n this.viewportScrollLeft = newScrollLeft;\n // Update timeline state immediately to propagate to consumers\n this.updateTimelineState();\n // Save scroll position on scroll\n this.debouncedSaveTimelineState();\n }\n }\n };\n this.tracksScrollRef.value.addEventListener(\n \"scroll\",\n this.scrollHandler,\n { passive: true },\n );\n // Initialize scroll position\n this.scrollHandler();\n \n // Set up wheel listener for gestural zoom\n this.#wheelHandler = (e: WheelEvent) => this.handleWheel(e);\n this.tracksScrollRef.value.addEventListener(\n \"wheel\",\n this.#wheelHandler,\n { passive: false },\n );\n }\n }\n\n private removeScrollListener(): void {\n if (this.tracksScrollRef.value) {\n if (this.scrollHandler) {\n this.tracksScrollRef.value.removeEventListener(\n \"scroll\",\n this.scrollHandler,\n );\n }\n if (this.#wheelHandler) {\n this.tracksScrollRef.value.removeEventListener(\n \"wheel\",\n this.#wheelHandler,\n );\n this.#wheelHandler = null;\n }\n }\n }\n\n private setupKeyboardListener(): void {\n this.keydownHandler = (e: KeyboardEvent) => this.handleKeyDown(e);\n this.addEventListener(\"keydown\", this.keydownHandler);\n }\n\n private removeKeyboardListener(): void {\n if (this.keydownHandler) {\n this.removeEventListener(\"keydown\", this.keydownHandler);\n }\n }\n\n /** Margin from edge before auto-scroll kicks in during playback */\n private static readonly PLAYHEAD_MARGIN = 100;\n\n private lastPlayheadPx = 0;\n private isFollowingPlayhead = false;\n\n private startTimeUpdate(): void {\n const update = () => {\n if (this.targetTemporal) {\n // Skip time updates during thumbnail capture to prevent playhead jumping\n // Note: captureInProgress is a property that may exist on EFTimegroup\n const isCapturing = (this.targetTemporal as any).captureInProgress;\n if (!isCapturing) {\n const rawTime = this.targetTemporal.currentTimeMs ?? 0;\n const duration = this.targetTemporal.durationMs ?? 0;\n // Clamp time to valid range to prevent display issues\n const newTimeMs = Math.max(0, Math.min(rawTime, duration));\n \n // Update playhead position directly via DOM (bypasses Lit render cycle)\n this.updatePlayheadPositionDirect(newTimeMs);\n \n // Note: Playing state is now updated via subscription (subscribeToPlaybackController)\n // We still read it here as a fallback, but the subscription is the primary source\n // This ensures we catch state changes even if subscription hasn't been set up yet\n const newIsPlaying = this.targetTemporal.playing ?? false;\n const newIsLooping = this.targetTemporal.loop ?? false;\n \n // Only update if subscription hasn't already updated these values\n // This prevents race conditions between polling and subscription\n if (newIsPlaying !== this.isPlaying || newIsLooping !== this.isLooping) {\n // Only update if we don't have an active subscription (fallback mode)\n if (!this.#playbackSubscription) {\n const wasPlaying = this.isPlaying;\n this.isPlaying = newIsPlaying;\n this.isLooping = newIsLooping;\n \n // PERFORMANCE: When playback stops, force context update with current scroll\n // During playback, scroll-only changes don't trigger context updates (see updateTimelineState).\n // When stopping, we need to update context so consumers can do any deferred work.\n if (wasPlaying && !newIsPlaying) {\n // Force context update by calling updateTimelineState directly\n // (isPlaying is now false, so scroll changes will propagate)\n this.updateTimelineState();\n }\n }\n }\n \n // Update currentTimeMs for context consumers\n // - When NOT playing: always sync immediately (for seek responsiveness)\n // - When playing: throttle to 10fps to avoid cascading re-renders\n const now = performance.now();\n const shouldUpdateContext = !newIsPlaying || \n (now - this.lastContextUpdateTime >= EFTimeline.CONTEXT_UPDATE_INTERVAL_MS);\n \n if (shouldUpdateContext && this.currentTimeMs !== newTimeMs) {\n this.currentTimeMs = newTimeMs;\n this.lastContextUpdateTime = now;\n }\n\n // Auto-scroll to keep playhead visible (only when playing and not dragging)\n if (this.isPlaying && !this.isDraggingPlayhead) {\n this.followPlayhead(newTimeMs);\n } else {\n this.isFollowingPlayhead = false;\n }\n }\n } else {\n // Defensive check: if we were playing and lost targetTemporal, this is unexpected\n if (this.isPlaying) {\n console.warn(\n \"[EFTimeline] Lost targetTemporal during playback. Stopping playback state.\",\n \"controlTarget:\",\n this.controlTarget,\n \"target:\",\n this.target,\n );\n this.isPlaying = false;\n this.isLooping = false;\n // Force context update to notify consumers\n this.updateTimelineState();\n }\n // Continue polling to detect when target becomes available again\n }\n this.animationFrameId = requestAnimationFrame(update);\n };\n update();\n }\n \n /**\n * Update playhead position directly via DOM manipulation.\n * This bypasses the Lit render cycle for smooth 60fps playhead movement.\n */\n private updatePlayheadPositionDirect(timeMs: number): void {\n const hierarchyWidth = this.showHierarchy ? EFTimeline.HIERARCHY_WIDTH : 0;\n const playheadPx = timeToPx(timeMs, this.pixelsPerMs);\n const playheadLeft = hierarchyWidth + playheadPx;\n \n // Update main playhead\n const playhead = this.playheadRef.value;\n if (playhead) {\n playhead.style.left = `${playheadLeft - 1}px`;\n }\n \n // Update ruler playhead handle\n const handle = this.playheadHandleRef.value;\n if (handle) {\n handle.style.left = `${playheadPx}px`;\n }\n \n // Update frame highlight if visible\n if (this.showFrameMarkers && this.durationMs > 0) {\n const frameHighlight = this.frameHighlightRef.value;\n if (frameHighlight) {\n const fps = this.fps;\n const frameDurationMs = 1000 / fps;\n const frameStartMs = quantizeToFrameTimeMs(timeMs, fps);\n const frameEndMs = Math.min(frameStartMs + frameDurationMs, this.durationMs);\n const startPx = timeToPx(frameStartMs, this.pixelsPerMs);\n const widthPx = timeToPx(frameEndMs, this.pixelsPerMs) - startPx;\n \n if (widthPx > 0 && startPx >= 0) {\n frameHighlight.style.left = `${hierarchyWidth + startPx}px`;\n frameHighlight.style.width = `${widthPx}px`;\n frameHighlight.style.display = 'block';\n } else {\n frameHighlight.style.display = 'none';\n }\n }\n }\n }\n\n /**\n * Smooth playhead following - scrolls to keep playhead at a fixed screen position.\n * This eliminates jitter by scrolling exactly as much as the playhead moves.\n * \n * PERFORMANCE NOTE: We DO update viewportScrollLeft state here, but the context\n * cascade is prevented in updateTimelineState() during playback. This means:\n * - State stays in sync (for non-context consumers)\n * - But context consumers don't re-render during auto-scroll\n * - Components inside the scroll container scroll natively\n */\n private followPlayhead(currentTimeMs: number): void {\n const tracksScroll = this.tracksScrollRef.value;\n if (!tracksScroll) return;\n\n const hierarchyWidth = this.showHierarchy ? EFTimeline.HIERARCHY_WIDTH : 0;\n const playheadPx = timeToPx(currentTimeMs, this.pixelsPerMs);\n const viewportWidth = tracksScroll.clientWidth - hierarchyWidth;\n const maxScroll = tracksScroll.scrollWidth - tracksScroll.clientWidth;\n\n // Calculate where playhead is relative to visible area\n const playheadInViewport = playheadPx - tracksScroll.scrollLeft;\n const rightThreshold = viewportWidth - EFTimeline.PLAYHEAD_MARGIN;\n const leftThreshold = EFTimeline.PLAYHEAD_MARGIN;\n\n let newScrollLeft = tracksScroll.scrollLeft;\n let scrollChanged = false;\n\n if (this.isFollowingPlayhead) {\n // Already following - scroll by exactly the delta to keep playhead stationary on screen\n const delta = playheadPx - this.lastPlayheadPx;\n if (delta !== 0) {\n newScrollLeft = Math.max(0, Math.min(maxScroll, tracksScroll.scrollLeft + delta));\n tracksScroll.scrollLeft = newScrollLeft;\n scrollChanged = true;\n }\n } else if (playheadInViewport > rightThreshold) {\n // Playhead reached right threshold - start following from this exact position (no snap)\n this.isFollowingPlayhead = true;\n // No scroll change - just start tracking from current position\n } else if (playheadInViewport < leftThreshold && tracksScroll.scrollLeft > 0) {\n // Playhead at left edge and we can scroll left - scroll to show more\n newScrollLeft = Math.max(0, playheadPx - leftThreshold);\n tracksScroll.scrollLeft = newScrollLeft;\n scrollChanged = true;\n }\n\n // Update state (context cascade is prevented during playback in updateTimelineState)\n if (scrollChanged && this.viewportScrollLeft !== newScrollLeft) {\n this.viewportScrollLeft = newScrollLeft;\n }\n\n this.lastPlayheadPx = playheadPx;\n }\n\n private stopTimeUpdate(): void {\n if (this.animationFrameId) {\n cancelAnimationFrame(this.animationFrameId);\n }\n }\n\n /**\n * Subscribe to playback controller events for playing/loop state.\n * This avoids race conditions from polling when targetTemporal changes.\n */\n private subscribeToPlaybackController(): void {\n // Unsubscribe from previous controller\n this.unsubscribeFromPlaybackController();\n\n const temporal = this.targetTemporal;\n if (!temporal) {\n return;\n }\n\n // Wait for playbackController to be available if needed\n if (!temporal.playbackController) {\n (temporal as any).updateComplete?.then(() => {\n // Check again after async operation - temporal might have changed\n if (temporal === this.targetTemporal && temporal.playbackController) {\n this.#playbackSubscription = createDirectTemporalSubscription(\n temporal as TemporalMixinInterface & HTMLElement,\n {\n onPlayingChange: (value) => {\n // Only update if targetTemporal hasn't changed\n if (temporal === this.targetTemporal) {\n const wasPlaying = this.isPlaying;\n this.isPlaying = value;\n \n // When stopping, force context update\n if (wasPlaying && !value) {\n this.updateTimelineState();\n }\n }\n },\n onLoopChange: (value) => {\n if (temporal === this.targetTemporal) {\n this.isLooping = value;\n }\n },\n onCurrentTimeMsChange: () => {\n // We still poll for currentTimeMs to update playhead smoothly\n // This callback is here for completeness but doesn't need to do anything\n },\n onDurationMsChange: () => {\n // Duration changes are handled via other mechanisms\n },\n onTargetTemporalChange: () => {\n // Not used here\n },\n },\n );\n }\n });\n return;\n }\n\n // Subscribe immediately if controller is available\n this.#playbackSubscription = createDirectTemporalSubscription(\n temporal as TemporalMixinInterface & HTMLElement,\n {\n onPlayingChange: (value) => {\n // Only update if targetTemporal hasn't changed\n if (temporal === this.targetTemporal) {\n const wasPlaying = this.isPlaying;\n this.isPlaying = value;\n \n // When stopping, force context update\n if (wasPlaying && !value) {\n this.updateTimelineState();\n }\n }\n },\n onLoopChange: (value) => {\n if (temporal === this.targetTemporal) {\n this.isLooping = value;\n }\n },\n onCurrentTimeMsChange: () => {\n // We still poll for currentTimeMs to update playhead smoothly\n // This callback is here for completeness but doesn't need to do anything\n },\n onDurationMsChange: () => {\n // Duration changes are handled via other mechanisms\n },\n onTargetTemporalChange: () => {\n // Not used here\n },\n },\n );\n }\n\n /**\n * Unsubscribe from playback controller events.\n */\n private unsubscribeFromPlaybackController(): void {\n if (this.#playbackSubscription) {\n this.#playbackSubscription.unsubscribe();\n this.#playbackSubscription = null;\n }\n }\n\n // ============================================================================\n // EVENT HANDLERS\n // ============================================================================\n\n private handlePlay(): void {\n if (!this.targetTemporal) {\n console.warn(\n \"[EFTimeline] Cannot play: targetTemporal is null. controlTarget:\",\n this.controlTarget,\n \"target:\",\n this.target,\n );\n return;\n }\n this.targetTemporal.play();\n }\n\n private handlePause(): void {\n if (!this.targetTemporal) {\n console.warn(\n \"[EFTimeline] Cannot pause: targetTemporal is null. controlTarget:\",\n this.controlTarget,\n \"target:\",\n this.target,\n );\n return;\n }\n this.targetTemporal.pause();\n }\n\n private handleToggleLoop(): void {\n if (!this.targetTemporal) {\n console.warn(\n \"[EFTimeline] Cannot toggle loop: targetTemporal is null. controlTarget:\",\n this.controlTarget,\n \"target:\",\n this.target,\n );\n return;\n }\n this.targetTemporal.loop = !this.targetTemporal.loop;\n this.isLooping = this.targetTemporal.loop;\n }\n\n private handleZoomIn(): void {\n const currentZoom = pixelsPerMsToZoom(this.pixelsPerMs);\n const newZoom = Math.min(this.maxZoom, currentZoom * 1.25);\n this.pixelsPerMs = newZoom * DEFAULT_PIXELS_PER_MS;\n }\n\n private handleZoomOut(): void {\n const currentZoom = pixelsPerMsToZoom(this.pixelsPerMs);\n const newZoom = Math.max(this.minZoom, currentZoom / 1.25);\n this.pixelsPerMs = newZoom * DEFAULT_PIXELS_PER_MS;\n }\n\n /**\n * Handle wheel events for gestural zoom.\n * Cmd/Ctrl + wheel zooms toward the cursor position.\n * Without modifier, native scroll behavior is preserved.\n */\n private handleWheel(e: WheelEvent): void {\n const isZoom = e.metaKey || e.ctrlKey;\n \n if (!isZoom) {\n // Let native scroll handle it\n return;\n }\n \n // Prevent default to handle zoom ourselves\n e.preventDefault();\n e.stopPropagation();\n \n const tracksScroll = this.tracksScrollRef.value;\n if (!tracksScroll) return;\n \n const rect = tracksScroll.getBoundingClientRect();\n const hierarchyWidth = this.showHierarchy ? EFTimeline.HIERARCHY_WIDTH : 0;\n \n // Cursor X position relative to the track content area (excluding hierarchy)\n const cursorXInViewport = e.clientX - rect.left - hierarchyWidth;\n \n // If cursor is over the hierarchy panel, don't zoom\n if (cursorXInViewport < 0) return;\n \n // Calculate the time at cursor position before zoom\n const scrollLeft = tracksScroll.scrollLeft;\n const cursorXInContent = cursorXInViewport + scrollLeft;\n const timeAtCursor = pxToTime(cursorXInContent, this.pixelsPerMs);\n \n // Calculate zoom delta (same factor as EFPanZoom)\n const zoomFactor = e.deltaY > 0 ? 0.95 : 1.05;\n const currentZoom = pixelsPerMsToZoom(this.pixelsPerMs);\n const newZoom = Math.max(this.minZoom, Math.min(this.maxZoom, currentZoom * zoomFactor));\n const newPixelsPerMs = newZoom * DEFAULT_PIXELS_PER_MS;\n \n // Calculate new scroll position to keep timeAtCursor under the cursor\n const newCursorXInContent = timeToPx(timeAtCursor, newPixelsPerMs);\n const newScrollLeft = newCursorXInContent - cursorXInViewport;\n \n // Apply zoom\n this.pixelsPerMs = newPixelsPerMs;\n \n // Apply scroll position (clamp to valid range)\n const maxScroll = Math.max(0, timeToPx(this.durationMs, newPixelsPerMs) - (rect.width - hierarchyWidth));\n tracksScroll.scrollLeft = Math.max(0, Math.min(maxScroll, newScrollLeft));\n this.viewportScrollLeft = tracksScroll.scrollLeft;\n \n // Update timeline state\n this.updateTimelineState();\n }\n\n /**\n * Seek to a specific time, optionally quantizing to frame boundaries.\n * @param timeMs The raw time to seek to\n * @param snapToFrame Whether to quantize to the nearest frame boundary (default: true when frame markers visible)\n */\n private handleSeek(\n timeMs: number,\n snapToFrame: boolean = this.showFrameMarkers,\n ): void {\n if (!this.targetTemporal) {\n console.warn(\n \"[EFTimeline] Cannot seek: targetTemporal is null. controlTarget:\",\n this.controlTarget,\n \"target:\",\n this.target,\n );\n return;\n }\n\n let seekTime = timeMs;\n\n // Quantize to frame boundaries when snapping is enabled\n if (snapToFrame && this.fps > 0) {\n seekTime = quantizeToFrameTimeMs(seekTime, this.fps);\n }\n\n // Clamp to valid range\n const clampedTime = Math.max(0, Math.min(seekTime, this.durationMs));\n\n this.targetTemporal.currentTimeMs = clampedTime;\n this.currentTimeMs = clampedTime;\n }\n\n /**\n * Handle keyboard navigation for frame-by-frame or second-by-second movement.\n * - Arrow Left/Right: move by one frame\n * - Shift+Arrow Left/Right: move by one second\n */\n private handleKeyDown(e: KeyboardEvent): void {\n // Only handle arrow keys\n if (e.key !== \"ArrowLeft\" && e.key !== \"ArrowRight\") {\n return;\n }\n\n // Don't handle if user is typing in an input/textarea\n const activeElement = document.activeElement;\n if (\n activeElement?.tagName === \"INPUT\" ||\n activeElement?.tagName === \"TEXTAREA\" ||\n (activeElement as HTMLElement)?.isContentEditable\n ) {\n return;\n }\n\n e.preventDefault();\n\n const isShiftPressed = e.shiftKey;\n const isRightArrow = e.key === \"ArrowRight\";\n const fps = this.fps;\n const durationMs = this.durationMs;\n\n let newTime: number;\n\n if (isShiftPressed) {\n // Shift+arrow: move by 1 second (1000ms)\n const deltaMs = isRightArrow ? 1000 : -1000;\n newTime = this.currentTimeMs + deltaMs;\n } else {\n // Arrow: move by one frame\n const frameIntervalMs = fps > 0 ? 1000 / fps : 1000 / 30;\n const deltaMs = isRightArrow ? frameIntervalMs : -frameIntervalMs;\n newTime = this.currentTimeMs + deltaMs;\n\n // Quantize to frame boundaries\n newTime = quantizeToFrameTimeMs(newTime, fps);\n }\n\n // Clamp to bounds\n newTime = Math.max(0, Math.min(newTime, durationMs));\n\n // Seek to new time\n this.handleSeek(newTime);\n }\n\n @eventOptions({ passive: false })\n private handleRulerPointerDown(e: PointerEvent): void {\n const target = e.currentTarget as HTMLElement;\n const rect = target.getBoundingClientRect();\n // Since ruler-content is inside scroll container and moves with scroll,\n // rect.left already accounts for scroll position. No need to add scrollLeft.\n const x = e.clientX - rect.left;\n const timeMs = pxToTime(x, this.pixelsPerMs);\n this.handleSeek(timeMs);\n this.startPlayheadDrag(e);\n e.preventDefault();\n }\n\n @eventOptions({ passive: false })\n private handlePlayheadPointerDown(e: PointerEvent): void {\n e.stopPropagation();\n this.startPlayheadDrag(e);\n }\n\n /** Hierarchy panel width - must match CSS --timeline-hierarchy-width */\n private static readonly HIERARCHY_WIDTH = 200;\n\n @eventOptions({ passive: false })\n private handleTracksPointerDown(e: PointerEvent): void {\n // Only seek on direct clicks (not on track items)\n if (\n e.target === e.currentTarget ||\n (e.target as HTMLElement).classList.contains(\"tracks-content\")\n ) {\n const target = e.currentTarget as HTMLElement;\n const rect = target.getBoundingClientRect();\n const scrollLeft = target.scrollLeft;\n const hierarchyWidth = this.showHierarchy ? EFTimeline.HIERARCHY_WIDTH : 0;\n const x = e.clientX - rect.left + scrollLeft - hierarchyWidth;\n if (x >= 0) {\n const timeMs = pxToTime(x, this.pixelsPerMs);\n this.handleSeek(timeMs);\n this.startPlayheadDrag(e);\n }\n e.preventDefault();\n }\n }\n\n /** Edge scroll zone width in pixels */\n private static readonly EDGE_SCROLL_ZONE = 50;\n /** Base scroll speed in pixels per frame */\n private static readonly EDGE_SCROLL_SPEED = 8;\n\n private startPlayheadDrag(e: PointerEvent): void {\n this.isDraggingPlayhead = true;\n // Sync scroll state immediately to prevent offset\n const tracksScroll = this.tracksScrollRef.value;\n if (tracksScroll) {\n this.viewportScrollLeft = tracksScroll.scrollLeft;\n }\n const hierarchyWidth = this.showHierarchy ? EFTimeline.HIERARCHY_WIDTH : 0;\n let lastClientX = e.clientX;\n let edgeScrollAnimationId: number | null = null;\n\n const updatePlayheadFromMouse = () => {\n const tracksScroll = this.tracksScrollRef.value;\n if (!tracksScroll) return;\n const rect = tracksScroll.getBoundingClientRect();\n const scrollLeft = tracksScroll.scrollLeft;\n const x = lastClientX - rect.left + scrollLeft - hierarchyWidth;\n const timeMs = pxToTime(Math.max(0, x), this.pixelsPerMs);\n this.handleSeek(Math.min(timeMs, this.durationMs));\n };\n\n const edgeScrollLoop = () => {\n if (!this.isDraggingPlayhead) return;\n const tracksScroll = this.tracksScrollRef.value;\n if (!tracksScroll) return;\n\n const rect = tracksScroll.getBoundingClientRect();\n const trackAreaLeft = rect.left + hierarchyWidth;\n const trackAreaRight = rect.right;\n const trackAreaWidth = trackAreaRight - trackAreaLeft;\n\n // Calculate distance from edges (relative to track area, not full scroll container)\n const distanceFromLeft = lastClientX - trackAreaLeft;\n const distanceFromRight = trackAreaRight - lastClientX;\n\n let scrollDelta = 0;\n\n if (distanceFromLeft < EFTimeline.EDGE_SCROLL_ZONE && distanceFromLeft >= 0) {\n // Near left edge - scroll left (faster as you get closer to edge)\n const intensity = 1 - distanceFromLeft / EFTimeline.EDGE_SCROLL_ZONE;\n scrollDelta = -EFTimeline.EDGE_SCROLL_SPEED * intensity;\n } else if (distanceFromRight < EFTimeline.EDGE_SCROLL_ZONE && distanceFromRight >= 0) {\n // Near right edge - scroll right\n const intensity = 1 - distanceFromRight / EFTimeline.EDGE_SCROLL_ZONE;\n scrollDelta = EFTimeline.EDGE_SCROLL_SPEED * intensity;\n } else if (lastClientX < trackAreaLeft) {\n // Beyond left edge - scroll left at max speed\n scrollDelta = -EFTimeline.EDGE_SCROLL_SPEED;\n } else if (lastClientX > trackAreaRight) {\n // Beyond right edge - scroll right at max speed\n scrollDelta = EFTimeline.EDGE_SCROLL_SPEED;\n }\n\n if (scrollDelta !== 0) {\n // Clamp scroll to valid range\n const maxScroll = tracksScroll.scrollWidth - tracksScroll.clientWidth;\n const newScrollLeft = Math.max(0, Math.min(maxScroll, tracksScroll.scrollLeft + scrollDelta));\n tracksScroll.scrollLeft = newScrollLeft;\n this.viewportScrollLeft = newScrollLeft; // Sync immediately\n \n // Update playhead position based on current mouse and new scroll\n updatePlayheadFromMouse();\n }\n\n edgeScrollAnimationId = requestAnimationFrame(edgeScrollLoop);\n };\n\n const onMove = (moveEvent: PointerEvent) => {\n if (!this.isDraggingPlayhead) return;\n lastClientX = moveEvent.clientX;\n updatePlayheadFromMouse();\n };\n\n const onUp = () => {\n this.isDraggingPlayhead = false;\n if (edgeScrollAnimationId) {\n cancelAnimationFrame(edgeScrollAnimationId);\n }\n window.removeEventListener(\"pointermove\", onMove);\n window.removeEventListener(\"pointerup\", onUp);\n };\n\n window.addEventListener(\"pointermove\", onMove);\n window.addEventListener(\"pointerup\", onUp);\n \n // Start edge scroll detection loop\n edgeScrollAnimationId = requestAnimationFrame(edgeScrollLoop);\n }\n\n\n // ============================================================================\n // RENDERING - CONTROLS\n // ============================================================================\n\n private formatTime(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n const frames = Math.floor((ms % 1000) / (1000 / 30));\n return `${minutes}:${remainingSeconds.toString().padStart(2, \"0\")}:${frames.toString().padStart(2, \"0\")}`;\n }\n\n private renderPlaybackControls() {\n if (!this.showPlaybackControls) return nothing;\n return html`\n <div class=\"playback-controls\">\n ${\n this.isPlaying\n ? html`<button class=\"control-btn\" @click=${this.handlePause} title=\"Pause\">⏸</button>`\n : html`<button class=\"control-btn\" @click=${this.handlePlay} title=\"Play\">▶</button>`\n }\n <button class=\"control-btn ${this.isLooping ? \"active\" : \"\"}\" @click=${this.handleToggleLoop} title=\"Loop\">🔁</button>\n </div>\n `;\n }\n\n private renderTimeDisplay() {\n if (!this.showTimeDisplay) return nothing;\n return html`<span class=\"time-display\">${this.formatTime(this.currentTimeMs)} / ${this.formatTime(this.durationMs)}</span>`;\n }\n\n private renderZoomControls() {\n if (!this.showZoomControls) return nothing;\n return html`\n <div class=\"zoom-controls\">\n <button class=\"zoom-btn\" @click=${this.handleZoomOut} title=\"Zoom out\">−</button>\n <span class=\"zoom-label\">${this.zoomPercent}%</span>\n <button class=\"zoom-btn\" @click=${this.handleZoomIn} title=\"Zoom in\">+</button>\n </div>\n `;\n }\n\n /**\n * Calculate frame highlight bounds (semantics).\n * Returns null if frame markers aren't visible or duration is invalid.\n */\n private calculateFrameHighlightBounds(): {\n startPx: number;\n widthPx: number;\n } | null {\n if (!this.showFrameMarkers || this.durationMs <= 0) {\n return null;\n }\n\n const fps = this.fps;\n const frameDurationMs = 1000 / fps;\n const frameStartMs = quantizeToFrameTimeMs(this.currentTimeMs, fps);\n const frameEndMs = Math.min(\n frameStartMs + frameDurationMs,\n this.durationMs,\n );\n\n const startPx = timeToPx(frameStartMs, this.pixelsPerMs);\n const endPx = timeToPx(frameEndMs, this.pixelsPerMs);\n const widthPx = endPx - startPx;\n\n if (widthPx <= 0 || startPx < 0) {\n return null;\n }\n\n return { startPx, widthPx };\n }\n\n /**\n * Render frame highlight (mechanism).\n * Shows the current frame as a rectangle to indicate frames have duration.\n * Only rendered when frame markers are visible (zoom level high enough).\n */\n private renderFrameHighlight() {\n // Only render when frame markers should be visible\n if (!this.showFrameMarkers) {\n return nothing;\n }\n \n const bounds = this.calculateFrameHighlightBounds();\n if (!bounds) return nothing;\n \n const hierarchyWidth = this.showHierarchy ? EFTimeline.HIERARCHY_WIDTH : 0;\n\n return html`\n <div \n ${ref(this.frameHighlightRef)}\n class=\"frame-highlight\" \n style=${styleMap({\n left: `${hierarchyWidth + bounds.startPx}px`,\n width: `${bounds.widthPx}px`,\n })}\n ></div>\n `;\n }\n\n private renderControls() {\n if (!this.showControls) return nothing;\n return html`\n <div class=\"header\">\n <div class=\"controls\">\n ${this.renderPlaybackControls()}\n ${this.renderTimeDisplay()}\n </div>\n ${this.renderZoomControls()}\n </div>\n `;\n }\n\n // ============================================================================\n // RENDERING - TRACKS\n // ============================================================================\n\n private handleTrimChange(e: CustomEvent<TrimChangeDetail>): void {\n const { elementId, type, newValueMs } = e.detail;\n const element = this.targetElement?.querySelector(`#${elementId}`) as TemporalMixinInterface & HTMLElement;\n if (element) {\n if (type === \"start\") {\n element.trimStartMs = newValueMs;\n } else {\n element.trimEndMs = newValueMs;\n }\n }\n }\n\n /**\n * Handle row hover events - update canvas highlighted element.\n */\n private handleRowHover(e: CustomEvent<{ element: Element | null }>): void {\n // Update canvas highlight (source of truth)\n this.setHighlightedElement(e.detail.element as HTMLElement | null);\n }\n\n /**\n * Handle row selection events - update selection context.\n */\n private handleRowSelect(\n e: CustomEvent<{ elementId: string; element: Element }>,\n ): void {\n const selectionCtx = this.getCanvasSelectionContext();\n if (selectionCtx && e.detail.elementId) {\n selectionCtx.select(e.detail.elementId);\n }\n }\n\n /**\n * Render timeline rows using flattened hierarchy.\n * Each row is a unified component with both label and track.\n */\n private renderRows(target: TemporalMixinInterface & HTMLElement): TemplateResult {\n const rows = flattenHierarchy(target as unknown as TemporalMixinInterface & Element).filter((row) =>\n shouldRenderElement(row.element, this.hideSelectors, this.showSelectors),\n );\n\n const selectionCtx = this.getCanvasSelectionContext();\n // Create new Set to break reference equality - ensures Lit detects changes\n const selectedIds = new Set(selectionCtx?.selectedIds ?? []);\n const highlightedElement = this.getHighlightedElement();\n\n return html`\n <div\n class=\"tracks-rows\"\n @track-trim-change=${this.handleTrimChange}\n @row-hover=${this.handleRowHover}\n @row-select=${this.handleRowSelect}\n >\n ${repeat(\n rows,\n // Key function: use element ID or element itself for stable identity\n (row) => {\n const el = row.element as unknown as HTMLElement;\n return (el && el.id) ? el.id : row.element;\n },\n (row) => html`\n <ef-timeline-row\n .element=${row.element}\n depth=${row.depth}\n pixels-per-ms=${this.pixelsPerMs}\n ?enable-trim=${this.enableTrim}\n .hideSelectors=${this.hideSelectors}\n .showSelectors=${this.showSelectors}\n .highlightedElement=${highlightedElement}\n .selectedIds=${selectedIds}\n ></ef-timeline-row>\n `,\n )}\n </div>\n `;\n }\n\n // ============================================================================\n // MAIN RENDER\n // ============================================================================\n\n render() {\n const target = this.targetTemporal;\n\n if (!target) {\n return html`\n <div class=\"timeline-container\">\n ${this.renderControls()}\n <div class=\"empty-state\">No target element selected</div>\n </div>\n `;\n }\n\n const playheadPx = timeToPx(this.currentTimeMs, this.pixelsPerMs);\n\n const hierarchyWidth = this.showHierarchy ? EFTimeline.HIERARCHY_WIDTH : 0;\n // Playhead position includes hierarchy offset since it's inside scroll container\n const playheadLeft = hierarchyWidth + playheadPx;\n\n return html`\n <div class=\"timeline-container\" tabindex=\"0\" ${ref(this.containerRef)}>\n ${this.renderControls()}\n <div class=\"timeline-area\">\n <!-- Tracks Viewport - Single scrollable container -->\n <div class=\"tracks-viewport\">\n <div \n class=\"tracks-scroll\" \n ${ref(this.tracksScrollRef)} \n @pointerdown=${this.handleTracksPointerDown}\n >\n <!-- Ruler Row - Inside scroll container with sticky positioning -->\n ${\n this.showRuler\n ? html`\n <div class=\"ruler-row\" style=\"width: ${this.contentWidthPx + hierarchyWidth}px;\">\n ${this.showHierarchy ? html`<div class=\"ruler-spacer\"></div>` : nothing}\n <div \n class=\"ruler-content\" \n @pointerdown=${this.handleRulerPointerDown}\n >\n <ef-timeline-ruler\n duration-ms=${this.durationMs}\n fps=${this.fps}\n content-width=${this.contentWidthPx}\n ></ef-timeline-ruler>\n ${this.showPlayhead ? html`\n <div \n ${ref(this.playheadHandleRef)}\n class=\"ruler-playhead-handle\" \n style=\"left: ${playheadPx}px;\"\n @pointerdown=${this.handlePlayheadPointerDown}\n ></div>\n ` : nothing}\n </div>\n </div>\n `\n : nothing\n }\n <div class=\"tracks-content\" style=\"min-width: ${this.contentWidthPx + hierarchyWidth}px;\">\n <!-- Track rows layer -->\n <div class=\"tracks-rows-layer\">\n ${this.renderRows(target as unknown as TemporalMixinInterface & HTMLElement)}\n </div>\n \n <!-- Playhead layer - sticky to stay visible during vertical scroll -->\n <div class=\"playhead-layer\">\n ${this.renderFrameHighlight()}\n ${this.showPlayhead ? html`\n <div ${ref(this.playheadRef)} class=\"playhead\" style=\"left: ${playheadLeft - 1}px;\">\n <div class=\"playhead-drag-target\" @pointerdown=${this.handlePlayheadPointerDown}></div>\n </div>\n ` : nothing}\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timeline\": EFTimeline;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
|
|
2
|
+
import { isEFTemporal } from "../../elements/EFTemporal.js";
|
|
3
|
+
import { TWMixin } from "../TWMixin2.js";
|
|
4
|
+
import { EFAudio } from "../../elements/EFAudio.js";
|
|
5
|
+
import { EFVideo } from "../../elements/EFVideo.js";
|
|
6
|
+
import { EFCaptions } from "../../elements/EFCaptions.js";
|
|
7
|
+
import { EFImage } from "../../elements/EFImage.js";
|
|
8
|
+
import { EFText } from "../../elements/EFText.js";
|
|
9
|
+
import { ICONS, phosphorIcon } from "../icons.js";
|
|
10
|
+
import { renderTrackChildren } from "./tracks/renderTrackChildren.js";
|
|
11
|
+
import { EFTimegroup } from "../../elements/EFTimegroup.js";
|
|
12
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
13
|
+
import { customElement, property } from "lit/decorators.js";
|
|
14
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
15
|
+
|
|
16
|
+
//#region src/gui/timeline/EFTimelineRow.ts
|
|
17
|
+
const INDENT_PX = 16;
|
|
18
|
+
let EFTimelineRow = class EFTimelineRow$1 extends TWMixin(LitElement) {
|
|
19
|
+
constructor(..._args) {
|
|
20
|
+
super(..._args);
|
|
21
|
+
this.depth = 0;
|
|
22
|
+
this.pixelsPerMs = .04;
|
|
23
|
+
this.enableTrim = false;
|
|
24
|
+
this.highlightedElement = null;
|
|
25
|
+
this.selectedIds = /* @__PURE__ */ new Set();
|
|
26
|
+
this.handleMouseEnter = () => {
|
|
27
|
+
this.dispatchEvent(new CustomEvent("row-hover", {
|
|
28
|
+
detail: { element: this.element },
|
|
29
|
+
bubbles: true,
|
|
30
|
+
composed: true
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
33
|
+
this.handleMouseLeave = () => {
|
|
34
|
+
this.dispatchEvent(new CustomEvent("row-hover", {
|
|
35
|
+
detail: { element: null },
|
|
36
|
+
bubbles: true,
|
|
37
|
+
composed: true
|
|
38
|
+
}));
|
|
39
|
+
};
|
|
40
|
+
this.handleClick = (e) => {
|
|
41
|
+
e.stopPropagation();
|
|
42
|
+
const elementId = this.element?.id;
|
|
43
|
+
if (elementId) this.dispatchEvent(new CustomEvent("row-select", {
|
|
44
|
+
detail: {
|
|
45
|
+
elementId,
|
|
46
|
+
element: this.element
|
|
47
|
+
},
|
|
48
|
+
bubbles: true,
|
|
49
|
+
composed: true
|
|
50
|
+
}));
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
static {
|
|
54
|
+
this.styles = [css`
|
|
55
|
+
:host {
|
|
56
|
+
display: flex;
|
|
57
|
+
min-height: var(--timeline-row-height, 28px);
|
|
58
|
+
border-bottom: 1px solid rgba(71, 85, 105, 0.4);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Root timegroup row with filmstrip - taller to show thumbnails */
|
|
62
|
+
:host(.root-timegroup) {
|
|
63
|
+
min-height: 52px;
|
|
64
|
+
height: 52px;
|
|
65
|
+
/* Sticky at top below ruler (ruler is 24px) */
|
|
66
|
+
position: sticky;
|
|
67
|
+
top: 24px;
|
|
68
|
+
/* Higher z-index than regular row labels (z-index: 8) so everything scrolls underneath */
|
|
69
|
+
z-index: 15;
|
|
70
|
+
background: var(--timeline-bg, rgb(30 41 59));
|
|
71
|
+
border-bottom: 1px solid rgba(71, 85, 105, 0.6);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Root timegroup label needs higher z-index to stay above other labels when scrolling */
|
|
75
|
+
:host(.root-timegroup) .row-label {
|
|
76
|
+
z-index: 16;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Hover state - this row is directly hovered */
|
|
80
|
+
:host(.hovered) {
|
|
81
|
+
background: rgba(59, 130, 246, 0.1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Ancestor hovered - a descendant of this row is hovered */
|
|
85
|
+
:host(.ancestor-hovered) {
|
|
86
|
+
background: rgba(59, 130, 246, 0.05);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Descendant hovered - an ancestor of this row is hovered */
|
|
90
|
+
:host(.descendant-hovered) {
|
|
91
|
+
background: rgba(59, 130, 246, 0.03);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Selected state */
|
|
95
|
+
:host(.selected) {
|
|
96
|
+
background: rgba(59, 130, 246, 0.2);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
:host(.selected) .row-label {
|
|
100
|
+
font-weight: 500;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Ancestor has selected descendant */
|
|
104
|
+
:host(.ancestor-selected) {
|
|
105
|
+
background: rgba(59, 130, 246, 0.1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.row-label {
|
|
109
|
+
position: sticky;
|
|
110
|
+
left: 0;
|
|
111
|
+
/* Lower z-index so labels scroll underneath the sticky root timegroup row (z-index: 15) */
|
|
112
|
+
z-index: 8;
|
|
113
|
+
width: var(--timeline-hierarchy-width, 200px);
|
|
114
|
+
flex-shrink: 0;
|
|
115
|
+
background: rgb(38, 50, 68);
|
|
116
|
+
border-right: 1px solid rgba(71, 85, 105, 0.5);
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
font-size: 11px;
|
|
120
|
+
white-space: nowrap;
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
text-overflow: ellipsis;
|
|
123
|
+
color: rgba(226, 232, 240, 0.9);
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
transition: background-color 0.1s ease;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.row-label:hover {
|
|
129
|
+
background: rgb(51, 65, 85);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:host(.hovered) .row-label {
|
|
133
|
+
background: rgb(55, 90, 150);
|
|
134
|
+
color: white;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
:host(.selected) .row-label {
|
|
138
|
+
background: rgb(45, 85, 140);
|
|
139
|
+
color: white;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.row-track {
|
|
143
|
+
flex: 1;
|
|
144
|
+
position: relative;
|
|
145
|
+
min-width: 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
:host(:first-child) .row-track::before {
|
|
149
|
+
display: none;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Grouping indicator for nested elements */
|
|
153
|
+
.row-track::after {
|
|
154
|
+
content: "";
|
|
155
|
+
position: absolute;
|
|
156
|
+
left: -12px;
|
|
157
|
+
top: 0;
|
|
158
|
+
bottom: 0;
|
|
159
|
+
width: 2px;
|
|
160
|
+
background: var(--timeline-border, rgb(71 85 105));
|
|
161
|
+
opacity: 0.2;
|
|
162
|
+
z-index: 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
:host(:first-child) .row-track::after {
|
|
166
|
+
display: none;
|
|
167
|
+
}
|
|
168
|
+
`];
|
|
169
|
+
}
|
|
170
|
+
get isHovered() {
|
|
171
|
+
return this.highlightedElement === this.element;
|
|
172
|
+
}
|
|
173
|
+
get isSelected() {
|
|
174
|
+
const elementId = this.element?.id;
|
|
175
|
+
return elementId ? this.selectedIds.has(elementId) : false;
|
|
176
|
+
}
|
|
177
|
+
get isAncestorSelected() {
|
|
178
|
+
if (!this.element) return false;
|
|
179
|
+
const elementAsHTMLElement = this.element;
|
|
180
|
+
for (const selectedId of this.selectedIds) {
|
|
181
|
+
const selectedElement = document.getElementById(selectedId);
|
|
182
|
+
if (selectedElement && elementAsHTMLElement.contains(selectedElement) && selectedElement !== elementAsHTMLElement) return true;
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
get isAncestorHovered() {
|
|
187
|
+
if (!this.highlightedElement || !this.element) return false;
|
|
188
|
+
return this.element !== this.highlightedElement && this.element.contains(this.highlightedElement);
|
|
189
|
+
}
|
|
190
|
+
get isDescendantHovered() {
|
|
191
|
+
if (!this.highlightedElement || !this.element) return false;
|
|
192
|
+
return this.element !== this.highlightedElement && this.highlightedElement.contains(this.element);
|
|
193
|
+
}
|
|
194
|
+
updated(changedProperties) {
|
|
195
|
+
super.updated(changedProperties);
|
|
196
|
+
if (changedProperties.has("highlightedElement") || changedProperties.has("element")) {
|
|
197
|
+
this.classList.toggle("hovered", this.isHovered);
|
|
198
|
+
this.classList.toggle("ancestor-hovered", this.isAncestorHovered);
|
|
199
|
+
this.classList.toggle("descendant-hovered", this.isDescendantHovered);
|
|
200
|
+
}
|
|
201
|
+
if (changedProperties.has("selectedIds") || changedProperties.has("element")) {
|
|
202
|
+
this.classList.toggle("selected", this.isSelected);
|
|
203
|
+
this.classList.toggle("ancestor-selected", this.isAncestorSelected);
|
|
204
|
+
}
|
|
205
|
+
if (changedProperties.has("element")) {
|
|
206
|
+
const isRoot = this.element instanceof EFTimegroup && this.element.isRootTimegroup;
|
|
207
|
+
this.classList.toggle("root-timegroup", isRoot);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
getElementType(element) {
|
|
211
|
+
if (element instanceof EFVideo) return "video";
|
|
212
|
+
if (element instanceof EFAudio) return "audio";
|
|
213
|
+
if (element instanceof EFImage) return "image";
|
|
214
|
+
if (element instanceof EFText) return "text";
|
|
215
|
+
if (element instanceof EFCaptions) return "captions";
|
|
216
|
+
if (element instanceof EFTimegroup) return "timegroup";
|
|
217
|
+
return "unknown";
|
|
218
|
+
}
|
|
219
|
+
getElementTypeColor(type) {
|
|
220
|
+
const colors = {
|
|
221
|
+
video: "rgb(59, 130, 246)",
|
|
222
|
+
audio: "rgb(34, 197, 94)",
|
|
223
|
+
image: "rgb(168, 85, 247)",
|
|
224
|
+
text: "rgb(249, 115, 22)",
|
|
225
|
+
captions: "rgb(34, 197, 94)",
|
|
226
|
+
timegroup: "rgb(148, 163, 184)",
|
|
227
|
+
unknown: "rgb(148, 163, 184)"
|
|
228
|
+
};
|
|
229
|
+
return colors[type] || colors.unknown;
|
|
230
|
+
}
|
|
231
|
+
getElementIcon(type) {
|
|
232
|
+
const iconMap = {
|
|
233
|
+
video: phosphorIcon(ICONS.filmStrip, 14),
|
|
234
|
+
audio: phosphorIcon(ICONS.speakerHigh, 14),
|
|
235
|
+
image: phosphorIcon(ICONS.image, 14),
|
|
236
|
+
text: phosphorIcon(ICONS.textT, 14),
|
|
237
|
+
captions: phosphorIcon(ICONS.subtitles, 14),
|
|
238
|
+
timegroup: phosphorIcon(ICONS.filmSlate, 14),
|
|
239
|
+
unknown: phosphorIcon(ICONS.code, 14)
|
|
240
|
+
};
|
|
241
|
+
return iconMap[type] ?? iconMap.unknown;
|
|
242
|
+
}
|
|
243
|
+
getElementLabel(element) {
|
|
244
|
+
const id = element.id || "";
|
|
245
|
+
const type = this.getElementType(element);
|
|
246
|
+
if (id && !id.includes("-") && !id.match(/^\d+$/)) return id;
|
|
247
|
+
const parent = element.parentElement;
|
|
248
|
+
if (parent) {
|
|
249
|
+
const siblings = Array.from(parent.children).filter((child) => this.getElementType(child) === type);
|
|
250
|
+
const index = siblings.indexOf(element) + 1;
|
|
251
|
+
const label = {
|
|
252
|
+
video: "Video",
|
|
253
|
+
audio: "Audio",
|
|
254
|
+
image: "Image",
|
|
255
|
+
text: "Text",
|
|
256
|
+
captions: "Captions",
|
|
257
|
+
timegroup: "Composition",
|
|
258
|
+
unknown: "Layer"
|
|
259
|
+
}[type] || "Layer";
|
|
260
|
+
if (siblings.length === 1) return label;
|
|
261
|
+
return `${label} ${index}`;
|
|
262
|
+
}
|
|
263
|
+
return type.charAt(0).toUpperCase() + type.slice(1);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get additional detail text for the label (mode, preview, etc.)
|
|
267
|
+
*/
|
|
268
|
+
getElementDetail(element) {
|
|
269
|
+
if (element instanceof EFTimegroup) {
|
|
270
|
+
const mode = element.mode || "fixed";
|
|
271
|
+
return {
|
|
272
|
+
fixed: "Fixed",
|
|
273
|
+
sequence: "Sequence",
|
|
274
|
+
contain: "Container"
|
|
275
|
+
}[mode] || mode;
|
|
276
|
+
}
|
|
277
|
+
if (element instanceof EFText) {
|
|
278
|
+
const textContent = Array.from(element.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => node.textContent?.trim()).filter(Boolean).join(" ");
|
|
279
|
+
if (textContent) return textContent.length > 20 ? textContent.slice(0, 20) + "..." : textContent;
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
renderTrack() {
|
|
284
|
+
if (!this.element || !isEFTemporal(this.element)) return nothing;
|
|
285
|
+
if (this.element instanceof EFTimegroup) {
|
|
286
|
+
const showFilmstrip = this.element.isRootTimegroup;
|
|
287
|
+
return html`<ef-timegroup-track
|
|
288
|
+
.element=${this.element}
|
|
289
|
+
pixels-per-ms=${this.pixelsPerMs}
|
|
290
|
+
?enable-trim=${this.enableTrim}
|
|
291
|
+
?skip-children=${true}
|
|
292
|
+
?show-filmstrip=${showFilmstrip}
|
|
293
|
+
.hideSelectors=${this.hideSelectors}
|
|
294
|
+
.showSelectors=${this.showSelectors}
|
|
295
|
+
></ef-timegroup-track>`;
|
|
296
|
+
}
|
|
297
|
+
return html`${renderTrackChildren([this.element], this.pixelsPerMs, this.hideSelectors, this.showSelectors, true, this.enableTrim, true)}`;
|
|
298
|
+
}
|
|
299
|
+
connectedCallback() {
|
|
300
|
+
super.connectedCallback();
|
|
301
|
+
this.addEventListener("mouseenter", this.handleMouseEnter);
|
|
302
|
+
this.addEventListener("mouseleave", this.handleMouseLeave);
|
|
303
|
+
this.addEventListener("click", this.handleClick);
|
|
304
|
+
}
|
|
305
|
+
disconnectedCallback() {
|
|
306
|
+
super.disconnectedCallback();
|
|
307
|
+
this.removeEventListener("mouseenter", this.handleMouseEnter);
|
|
308
|
+
this.removeEventListener("mouseleave", this.handleMouseLeave);
|
|
309
|
+
this.removeEventListener("click", this.handleClick);
|
|
310
|
+
}
|
|
311
|
+
render() {
|
|
312
|
+
if (!this.element) return nothing;
|
|
313
|
+
const type = this.getElementType(this.element);
|
|
314
|
+
const label = this.getElementLabel(this.element);
|
|
315
|
+
const detail = this.getElementDetail(this.element);
|
|
316
|
+
const typeColor = this.getElementTypeColor(type);
|
|
317
|
+
const icon = this.getElementIcon(type);
|
|
318
|
+
return html`
|
|
319
|
+
<div
|
|
320
|
+
class="row-label"
|
|
321
|
+
style=${styleMap({
|
|
322
|
+
paddingLeft: `${this.depth * INDENT_PX}px`,
|
|
323
|
+
borderLeftColor: typeColor,
|
|
324
|
+
borderLeftWidth: "3px",
|
|
325
|
+
borderLeftStyle: "solid"
|
|
326
|
+
})}
|
|
327
|
+
>
|
|
328
|
+
<span style="color: ${typeColor}; opacity: 0.9; margin-right: 6px; flex-shrink: 0;">
|
|
329
|
+
${icon}
|
|
330
|
+
</span>
|
|
331
|
+
<span style="flex-shrink: 0;">${label}</span>
|
|
332
|
+
${detail ? html`
|
|
333
|
+
<span style="margin-left: 6px; opacity: 0.6; font-size: 10px; overflow: hidden; text-overflow: ellipsis;">
|
|
334
|
+
${detail}
|
|
335
|
+
</span>
|
|
336
|
+
` : nothing}
|
|
337
|
+
</div>
|
|
338
|
+
<div class="row-track">${this.renderTrack()}</div>
|
|
339
|
+
`;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
__decorate([property({
|
|
343
|
+
type: Object,
|
|
344
|
+
attribute: false
|
|
345
|
+
})], EFTimelineRow.prototype, "element", void 0);
|
|
346
|
+
__decorate([property({ type: Number })], EFTimelineRow.prototype, "depth", void 0);
|
|
347
|
+
__decorate([property({
|
|
348
|
+
type: Number,
|
|
349
|
+
attribute: "pixels-per-ms"
|
|
350
|
+
})], EFTimelineRow.prototype, "pixelsPerMs", void 0);
|
|
351
|
+
__decorate([property({
|
|
352
|
+
type: Boolean,
|
|
353
|
+
attribute: "enable-trim"
|
|
354
|
+
})], EFTimelineRow.prototype, "enableTrim", void 0);
|
|
355
|
+
__decorate([property({
|
|
356
|
+
type: Array,
|
|
357
|
+
attribute: false
|
|
358
|
+
})], EFTimelineRow.prototype, "hideSelectors", void 0);
|
|
359
|
+
__decorate([property({
|
|
360
|
+
type: Array,
|
|
361
|
+
attribute: false
|
|
362
|
+
})], EFTimelineRow.prototype, "showSelectors", void 0);
|
|
363
|
+
__decorate([property({
|
|
364
|
+
type: Object,
|
|
365
|
+
attribute: false
|
|
366
|
+
})], EFTimelineRow.prototype, "highlightedElement", void 0);
|
|
367
|
+
__decorate([property({
|
|
368
|
+
type: Object,
|
|
369
|
+
attribute: false
|
|
370
|
+
})], EFTimelineRow.prototype, "selectedIds", void 0);
|
|
371
|
+
EFTimelineRow = __decorate([customElement("ef-timeline-row")], EFTimelineRow);
|
|
372
|
+
|
|
373
|
+
//#endregion
|
|
374
|
+
//# sourceMappingURL=EFTimelineRow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EFTimelineRow.js","names":["EFTimelineRow","colors: Record<string, string>","iconMap: Record<string, TemplateResult>"],"sources":["../../../src/gui/timeline/EFTimelineRow.ts"],"sourcesContent":["import {\n css,\n html,\n LitElement,\n nothing,\n type PropertyValues,\n type TemplateResult,\n} from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\n\nimport {\n isEFTemporal,\n type TemporalMixinInterface,\n} from \"../../elements/EFTemporal.js\";\nimport { EFTimegroup } from \"../../elements/EFTimegroup.js\";\nimport { EFVideo } from \"../../elements/EFVideo.js\";\nimport { EFAudio } from \"../../elements/EFAudio.js\";\nimport { EFImage } from \"../../elements/EFImage.js\";\nimport { EFText } from \"../../elements/EFText.js\";\nimport { EFCaptions } from \"../../elements/EFCaptions.js\";\nimport { TWMixin } from \"../TWMixin.js\";\nimport { renderTrackChildren } from \"./tracks/renderTrackChildren.js\";\nimport { phosphorIcon, ICONS } from \"../icons.js\";\n// NOTE: Track components (ef-timegroup-track, etc.) are NOT imported here\n// to avoid circular dependencies with TrackItem. They must be registered before\n// EFTimelineRow is used. See preloadTracks.ts for the registration sequence.\n\nconst INDENT_PX = 16;\n\n/**\n * EFTimelineRow - A unified timeline row containing both label and track\n *\n * This component renders a single row in the timeline with:\n * - A sticky label on the left (stays fixed during horizontal scroll)\n * - Track content on the right (scrolls horizontally with the timeline)\n *\n * Heights are determined by content, not hardcoded.\n */\n@customElement(\"ef-timeline-row\")\nexport class EFTimelineRow extends TWMixin(LitElement) {\n static styles = [\n css`\n :host {\n display: flex;\n min-height: var(--timeline-row-height, 28px);\n border-bottom: 1px solid rgba(71, 85, 105, 0.4);\n }\n\n /* Root timegroup row with filmstrip - taller to show thumbnails */\n :host(.root-timegroup) {\n min-height: 52px;\n height: 52px;\n /* Sticky at top below ruler (ruler is 24px) */\n position: sticky;\n top: 24px;\n /* Higher z-index than regular row labels (z-index: 8) so everything scrolls underneath */\n z-index: 15;\n background: var(--timeline-bg, rgb(30 41 59));\n border-bottom: 1px solid rgba(71, 85, 105, 0.6);\n }\n\n /* Root timegroup label needs higher z-index to stay above other labels when scrolling */\n :host(.root-timegroup) .row-label {\n z-index: 16;\n }\n\n /* Hover state - this row is directly hovered */\n :host(.hovered) {\n background: rgba(59, 130, 246, 0.1);\n }\n\n /* Ancestor hovered - a descendant of this row is hovered */\n :host(.ancestor-hovered) {\n background: rgba(59, 130, 246, 0.05);\n }\n\n /* Descendant hovered - an ancestor of this row is hovered */\n :host(.descendant-hovered) {\n background: rgba(59, 130, 246, 0.03);\n }\n\n /* Selected state */\n :host(.selected) {\n background: rgba(59, 130, 246, 0.2);\n }\n \n :host(.selected) .row-label {\n font-weight: 500;\n }\n\n /* Ancestor has selected descendant */\n :host(.ancestor-selected) {\n background: rgba(59, 130, 246, 0.1);\n }\n\n .row-label {\n position: sticky;\n left: 0;\n /* Lower z-index so labels scroll underneath the sticky root timegroup row (z-index: 15) */\n z-index: 8;\n width: var(--timeline-hierarchy-width, 200px);\n flex-shrink: 0;\n background: rgb(38, 50, 68);\n border-right: 1px solid rgba(71, 85, 105, 0.5);\n display: flex;\n align-items: center;\n font-size: 11px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: rgba(226, 232, 240, 0.9);\n cursor: pointer;\n transition: background-color 0.1s ease;\n }\n\n .row-label:hover {\n background: rgb(51, 65, 85);\n }\n\n :host(.hovered) .row-label {\n background: rgb(55, 90, 150);\n color: white;\n }\n\n :host(.selected) .row-label {\n background: rgb(45, 85, 140);\n color: white;\n }\n\n .row-track {\n flex: 1;\n position: relative;\n min-width: 0;\n }\n \n :host(:first-child) .row-track::before {\n display: none;\n }\n \n /* Grouping indicator for nested elements */\n .row-track::after {\n content: \"\";\n position: absolute;\n left: -12px;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--timeline-border, rgb(71 85 105));\n opacity: 0.2;\n z-index: 0;\n }\n \n :host(:first-child) .row-track::after {\n display: none;\n }\n `,\n ];\n\n @property({ type: Object, attribute: false })\n element!: TemporalMixinInterface & Element;\n\n @property({ type: Number })\n depth = 0;\n\n @property({ type: Number, attribute: \"pixels-per-ms\" })\n pixelsPerMs = 0.04;\n\n @property({ type: Boolean, attribute: \"enable-trim\" })\n enableTrim = false;\n\n @property({ type: Array, attribute: false })\n hideSelectors?: string[];\n\n @property({ type: Array, attribute: false })\n showSelectors?: string[];\n\n /**\n * The currently highlighted element from canvas (source of truth).\n * Passed from parent timeline which reads it from canvas.\n */\n @property({ type: Object, attribute: false })\n highlightedElement: Element | null = null;\n\n @property({ type: Object, attribute: false })\n selectedIds: ReadonlySet<string> = new Set();\n\n // Derived interaction states (computed on-demand)\n private get isHovered(): boolean {\n return this.highlightedElement === this.element;\n }\n\n private get isSelected(): boolean {\n const elementId = (this.element as unknown as HTMLElement)?.id;\n return elementId ? this.selectedIds.has(elementId) : false;\n }\n\n private get isAncestorSelected(): boolean {\n if (!this.element) return false;\n // Check if this element contains any selected element\n const elementAsHTMLElement = this.element as unknown as HTMLElement;\n for (const selectedId of this.selectedIds) {\n const selectedElement = document.getElementById(selectedId);\n if (\n selectedElement &&\n elementAsHTMLElement.contains(selectedElement) &&\n selectedElement !== elementAsHTMLElement\n ) {\n return true;\n }\n }\n return false;\n }\n\n private get isAncestorHovered(): boolean {\n if (!this.highlightedElement || !this.element) return false;\n // This row's element contains the highlighted element (highlighted is a descendant)\n return (\n this.element !== this.highlightedElement &&\n this.element.contains(this.highlightedElement)\n );\n }\n\n private get isDescendantHovered(): boolean {\n if (!this.highlightedElement || !this.element) return false;\n // The highlighted element contains this row's element (highlighted is an ancestor)\n return (\n this.element !== this.highlightedElement &&\n this.highlightedElement.contains(this.element)\n );\n }\n\n protected updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n // Update host classes based on interaction state\n if (\n changedProperties.has(\"highlightedElement\") ||\n changedProperties.has(\"element\")\n ) {\n this.classList.toggle(\"hovered\", this.isHovered);\n this.classList.toggle(\"ancestor-hovered\", this.isAncestorHovered);\n this.classList.toggle(\"descendant-hovered\", this.isDescendantHovered);\n }\n\n // Update selection classes\n if (\n changedProperties.has(\"selectedIds\") ||\n changedProperties.has(\"element\")\n ) {\n this.classList.toggle(\"selected\", this.isSelected);\n this.classList.toggle(\"ancestor-selected\", this.isAncestorSelected);\n }\n\n // Update root timegroup class for filmstrip rows\n if (changedProperties.has(\"element\")) {\n const isRoot = this.element instanceof EFTimegroup && this.element.isRootTimegroup;\n this.classList.toggle(\"root-timegroup\", isRoot);\n }\n }\n\n private handleMouseEnter = (): void => {\n this.dispatchEvent(\n new CustomEvent(\"row-hover\", {\n detail: { element: this.element },\n bubbles: true,\n composed: true,\n }),\n );\n };\n\n private handleMouseLeave = (): void => {\n this.dispatchEvent(\n new CustomEvent(\"row-hover\", {\n detail: { element: null },\n bubbles: true,\n composed: true,\n }),\n );\n };\n\n private handleClick = (e: Event): void => {\n e.stopPropagation();\n const elementId = (this.element as unknown as HTMLElement)?.id;\n if (elementId) {\n this.dispatchEvent(\n new CustomEvent(\"row-select\", {\n detail: { elementId, element: this.element },\n bubbles: true,\n composed: true,\n }),\n );\n }\n };\n\n private getElementType(element: Element): string {\n if (element instanceof EFVideo) return \"video\";\n if (element instanceof EFAudio) return \"audio\";\n if (element instanceof EFImage) return \"image\";\n if (element instanceof EFText) return \"text\";\n if (element instanceof EFCaptions) return \"captions\";\n if (element instanceof EFTimegroup) return \"timegroup\";\n return \"unknown\";\n }\n\n private getElementTypeColor(type: string): string {\n const colors: Record<string, string> = {\n video: \"rgb(59, 130, 246)\", // Blue\n audio: \"rgb(34, 197, 94)\", // Green\n image: \"rgb(168, 85, 247)\", // Purple\n text: \"rgb(249, 115, 22)\", // Orange\n captions: \"rgb(34, 197, 94)\", // Green (like audio/subtitles)\n timegroup: \"rgb(148, 163, 184)\", // Gray\n unknown: \"rgb(148, 163, 184)\",\n };\n return colors[type] || colors.unknown!;\n }\n\n private getElementIcon(type: string): TemplateResult {\n const iconMap: Record<string, TemplateResult> = {\n video: phosphorIcon(ICONS.filmStrip, 14),\n audio: phosphorIcon(ICONS.speakerHigh, 14),\n image: phosphorIcon(ICONS.image, 14),\n text: phosphorIcon(ICONS.textT, 14),\n captions: phosphorIcon(ICONS.subtitles, 14),\n timegroup: phosphorIcon(ICONS.filmSlate, 14),\n unknown: phosphorIcon(ICONS.code, 14),\n };\n return iconMap[type] ?? iconMap.unknown!;\n }\n\n private getElementLabel(element: Element): string {\n const id = element.id || \"\";\n const type = this.getElementType(element);\n \n // If element has a meaningful ID (not auto-generated), use it\n if (id && !id.includes(\"-\") && !id.match(/^\\d+$/)) {\n return id;\n }\n \n // For auto-generated IDs, create a friendly name based on type\n // Count siblings of same type to generate \"Video 1\", \"Video 2\", etc.\n const parent = element.parentElement;\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (child) => this.getElementType(child) === type\n );\n const index = siblings.indexOf(element) + 1;\n const typeLabels: Record<string, string> = {\n video: \"Video\",\n audio: \"Audio\",\n image: \"Image\",\n text: \"Text\",\n captions: \"Captions\",\n timegroup: \"Composition\",\n unknown: \"Layer\",\n };\n const label = typeLabels[type] || \"Layer\";\n \n // If there's only one of this type, don't add number\n if (siblings.length === 1) {\n return label;\n }\n return `${label} ${index}`;\n }\n \n // Fallback: capitalize the type\n return type.charAt(0).toUpperCase() + type.slice(1);\n }\n\n /**\n * Get additional detail text for the label (mode, preview, etc.)\n */\n private getElementDetail(element: Element): string | null {\n if (element instanceof EFTimegroup) {\n const mode = element.mode || \"fixed\";\n const modeLabels: Record<string, string> = {\n fixed: \"Fixed\",\n sequence: \"Sequence\",\n contain: \"Container\",\n };\n return modeLabels[mode] || mode;\n }\n if (element instanceof EFText) {\n // Get text preview\n const textContent = Array.from(element.childNodes)\n .filter(node => node.nodeType === Node.TEXT_NODE)\n .map(node => node.textContent?.trim())\n .filter(Boolean)\n .join(\" \");\n if (textContent) {\n return textContent.length > 20 ? textContent.slice(0, 20) + \"...\" : textContent;\n }\n }\n return null;\n }\n\n private renderTrack(): TemplateResult | typeof nothing {\n if (!this.element || !isEFTemporal(this.element)) return nothing;\n\n // For timegroups, use skip-children since children get their own rows\n if (this.element instanceof EFTimegroup) {\n // Show filmstrip for root timegroups (no parent timegroup)\n // Use the timegroup's own isRootTimegroup property for reliability\n const showFilmstrip = this.element.isRootTimegroup;\n return html`<ef-timegroup-track\n .element=${this.element}\n pixels-per-ms=${this.pixelsPerMs}\n ?enable-trim=${this.enableTrim}\n ?skip-children=${true}\n ?show-filmstrip=${showFilmstrip}\n .hideSelectors=${this.hideSelectors}\n .showSelectors=${this.showSelectors}\n ></ef-timegroup-track>`;\n }\n\n return html`${renderTrackChildren(\n [this.element as unknown as Element],\n this.pixelsPerMs,\n this.hideSelectors,\n this.showSelectors,\n true, // skipRootFiltering - the row itself handles filtering\n this.enableTrim,\n true, // useAbsolutePosition - flat row architecture needs absolute positioning\n )}`;\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener(\"mouseenter\", this.handleMouseEnter);\n this.addEventListener(\"mouseleave\", this.handleMouseLeave);\n this.addEventListener(\"click\", this.handleClick);\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener(\"mouseenter\", this.handleMouseEnter);\n this.removeEventListener(\"mouseleave\", this.handleMouseLeave);\n this.removeEventListener(\"click\", this.handleClick);\n }\n\n render() {\n if (!this.element) return nothing;\n\n const type = this.getElementType(this.element);\n const label = this.getElementLabel(this.element);\n const detail = this.getElementDetail(this.element);\n const typeColor = this.getElementTypeColor(type);\n const icon = this.getElementIcon(type);\n const indentPx = this.depth * INDENT_PX;\n\n return html`\n <div\n class=\"row-label\"\n style=${styleMap({ \n paddingLeft: `${indentPx}px`,\n borderLeftColor: typeColor,\n borderLeftWidth: \"3px\",\n borderLeftStyle: \"solid\",\n })}\n >\n <span style=\"color: ${typeColor}; opacity: 0.9; margin-right: 6px; flex-shrink: 0;\">\n ${icon}\n </span>\n <span style=\"flex-shrink: 0;\">${label}</span>\n ${detail ? html`\n <span style=\"margin-left: 6px; opacity: 0.6; font-size: 10px; overflow: hidden; text-overflow: ellipsis;\">\n ${detail}\n </span>\n ` : nothing}\n </div>\n <div class=\"row-track\">${this.renderTrack()}</div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timeline-row\": EFTimelineRow;\n }\n}\n\n"],"mappings":"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"}
|