@editframe/elements 0.37.3-beta → 0.38.1

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.
Files changed (327) hide show
  1. package/dist/EF_FRAMEGEN.js +17 -14
  2. package/dist/EF_FRAMEGEN.js.map +1 -1
  3. package/dist/EF_RENDERING.js.map +1 -1
  4. package/dist/canvas/EFCanvas.d.ts +9 -2
  5. package/dist/canvas/EFCanvas.js +14 -4
  6. package/dist/canvas/EFCanvas.js.map +1 -1
  7. package/dist/canvas/EFCanvasItem.d.ts +2 -2
  8. package/dist/canvas/overlays/SelectionOverlay.d.ts +10 -2
  9. package/dist/canvas/overlays/SelectionOverlay.js +5 -12
  10. package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
  11. package/dist/canvas/overlays/overlayState.js.map +1 -1
  12. package/dist/canvas/selection/SelectionController.js.map +1 -1
  13. package/dist/elements/EFAudio.d.ts +1 -11
  14. package/dist/elements/EFAudio.js +2 -10
  15. package/dist/elements/EFAudio.js.map +1 -1
  16. package/dist/elements/EFCaptions.d.ts +5 -9
  17. package/dist/elements/EFCaptions.js +34 -11
  18. package/dist/elements/EFCaptions.js.map +1 -1
  19. package/dist/elements/EFImage.d.ts +10 -8
  20. package/dist/elements/EFImage.js +117 -32
  21. package/dist/elements/EFImage.js.map +1 -1
  22. package/dist/elements/EFMedia/AssetMediaEngine.js +2 -2
  23. package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
  24. package/dist/elements/EFMedia/BaseMediaEngine.js +15 -92
  25. package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
  26. package/dist/elements/EFMedia/BufferedSeekingInput.js +10 -11
  27. package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
  28. package/dist/elements/EFMedia/{AssetIdMediaEngine.js → FileMediaEngine.js} +44 -24
  29. package/dist/elements/EFMedia/FileMediaEngine.js.map +1 -0
  30. package/dist/elements/EFMedia/JitMediaEngine.js +14 -13
  31. package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
  32. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
  33. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
  34. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +12 -7
  35. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
  36. package/dist/elements/EFMedia/shared/timeoutUtils.js +44 -0
  37. package/dist/elements/EFMedia/shared/timeoutUtils.js.map +1 -0
  38. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +1 -1
  39. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
  40. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +4 -4
  41. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
  42. package/dist/elements/EFMedia.d.ts +14 -8
  43. package/dist/elements/EFMedia.js +52 -19
  44. package/dist/elements/EFMedia.js.map +1 -1
  45. package/dist/elements/EFPanZoom.d.ts +2 -2
  46. package/dist/elements/EFPanZoom.js +1 -1
  47. package/dist/elements/EFPanZoom.js.map +1 -1
  48. package/dist/elements/EFSourceMixin.js +16 -8
  49. package/dist/elements/EFSourceMixin.js.map +1 -1
  50. package/dist/elements/EFSurface.d.ts +5 -8
  51. package/dist/elements/EFSurface.js +4 -43
  52. package/dist/elements/EFSurface.js.map +1 -1
  53. package/dist/elements/EFTemporal.d.ts +33 -8
  54. package/dist/elements/EFTemporal.js +92 -40
  55. package/dist/elements/EFTemporal.js.map +1 -1
  56. package/dist/elements/EFText.d.ts +3 -0
  57. package/dist/elements/EFText.js +54 -21
  58. package/dist/elements/EFText.js.map +1 -1
  59. package/dist/elements/EFTextSegment.js +8 -4
  60. package/dist/elements/EFTextSegment.js.map +1 -1
  61. package/dist/elements/EFTimegroup.d.ts +26 -43
  62. package/dist/elements/EFTimegroup.js +295 -314
  63. package/dist/elements/EFTimegroup.js.map +1 -1
  64. package/dist/elements/EFVideo.d.ts +44 -42
  65. package/dist/elements/EFVideo.js +259 -172
  66. package/dist/elements/EFVideo.js.map +1 -1
  67. package/dist/elements/EFWaveform.d.ts +3 -8
  68. package/dist/elements/EFWaveform.js +18 -13
  69. package/dist/elements/EFWaveform.js.map +1 -1
  70. package/dist/elements/ElementPositionInfo.js.map +1 -1
  71. package/dist/elements/FetchMixin.js.map +1 -1
  72. package/dist/elements/TargetController.d.ts +0 -3
  73. package/dist/elements/TargetController.js +12 -35
  74. package/dist/elements/TargetController.js.map +1 -1
  75. package/dist/elements/TimegroupController.js.map +1 -1
  76. package/dist/elements/cloneFactoryRegistry.d.ts +14 -0
  77. package/dist/elements/cloneFactoryRegistry.js +15 -0
  78. package/dist/elements/cloneFactoryRegistry.js.map +1 -0
  79. package/dist/elements/renderTemporalAudio.js +8 -6
  80. package/dist/elements/renderTemporalAudio.js.map +1 -1
  81. package/dist/elements/setupTemporalHierarchy.js +62 -0
  82. package/dist/elements/setupTemporalHierarchy.js.map +1 -0
  83. package/dist/elements/updateAnimations.js +62 -87
  84. package/dist/elements/updateAnimations.js.map +1 -1
  85. package/dist/getRenderInfo.d.ts +3 -2
  86. package/dist/getRenderInfo.js +20 -4
  87. package/dist/getRenderInfo.js.map +1 -1
  88. package/dist/gui/ContextMixin.js +68 -12
  89. package/dist/gui/ContextMixin.js.map +1 -1
  90. package/dist/gui/Controllable.js +1 -1
  91. package/dist/gui/Controllable.js.map +1 -1
  92. package/dist/gui/EFActiveRootTemporal.d.ts +2 -2
  93. package/dist/gui/EFActiveRootTemporal.js.map +1 -1
  94. package/dist/gui/EFControls.d.ts +2 -2
  95. package/dist/gui/EFControls.js +2 -2
  96. package/dist/gui/EFControls.js.map +1 -1
  97. package/dist/gui/EFDial.d.ts +2 -2
  98. package/dist/gui/EFDial.js +12 -9
  99. package/dist/gui/EFDial.js.map +1 -1
  100. package/dist/gui/EFFilmstrip.d.ts +2 -0
  101. package/dist/gui/EFFilmstrip.js +18 -10
  102. package/dist/gui/EFFilmstrip.js.map +1 -1
  103. package/dist/gui/EFFitScale.d.ts +28 -4
  104. package/dist/gui/EFFitScale.js +88 -26
  105. package/dist/gui/EFFitScale.js.map +1 -1
  106. package/dist/gui/EFFocusOverlay.d.ts +2 -2
  107. package/dist/gui/EFFocusOverlay.js +3 -3
  108. package/dist/gui/EFFocusOverlay.js.map +1 -1
  109. package/dist/gui/EFOverlayItem.d.ts +2 -2
  110. package/dist/gui/EFOverlayLayer.d.ts +2 -2
  111. package/dist/gui/EFPause.d.ts +2 -2
  112. package/dist/gui/EFPause.js +1 -1
  113. package/dist/gui/EFPlay.d.ts +2 -2
  114. package/dist/gui/EFPlay.js +1 -1
  115. package/dist/gui/EFPreview.js +1 -1
  116. package/dist/gui/EFResizableBox.d.ts +2 -2
  117. package/dist/gui/EFResizableBox.js +5 -5
  118. package/dist/gui/EFResizableBox.js.map +1 -1
  119. package/dist/gui/EFScrubber.d.ts +2 -2
  120. package/dist/gui/EFScrubber.js +8 -13
  121. package/dist/gui/EFScrubber.js.map +1 -1
  122. package/dist/gui/EFTimeDisplay.d.ts +6 -2
  123. package/dist/gui/EFTimeDisplay.js +25 -7
  124. package/dist/gui/EFTimeDisplay.js.map +1 -1
  125. package/dist/gui/EFTimelineRuler.d.ts +2 -2
  126. package/dist/gui/EFTimelineRuler.js +3 -3
  127. package/dist/gui/EFTimelineRuler.js.map +1 -1
  128. package/dist/gui/EFToggleLoop.d.ts +2 -2
  129. package/dist/gui/EFToggleLoop.js +1 -1
  130. package/dist/gui/EFTogglePlay.d.ts +2 -2
  131. package/dist/gui/EFTogglePlay.js +1 -1
  132. package/dist/gui/EFTransformHandles.d.ts +2 -2
  133. package/dist/gui/EFTransformHandles.js +6 -6
  134. package/dist/gui/EFTransformHandles.js.map +1 -1
  135. package/dist/gui/EFWorkbench.d.ts +40 -36
  136. package/dist/gui/EFWorkbench.js +436 -822
  137. package/dist/gui/EFWorkbench.js.map +1 -1
  138. package/dist/gui/FitScaleHelpers.js.map +1 -1
  139. package/dist/gui/PlaybackController.d.ts +3 -8
  140. package/dist/gui/PlaybackController.js +59 -56
  141. package/dist/gui/PlaybackController.js.map +1 -1
  142. package/dist/gui/TWMixin.js +1 -1
  143. package/dist/gui/TWMixin.js.map +1 -1
  144. package/dist/gui/TargetOrContextMixin.js +43 -6
  145. package/dist/gui/TargetOrContextMixin.js.map +1 -1
  146. package/dist/gui/ef-theme.css +136 -0
  147. package/dist/gui/hierarchy/EFHierarchy.d.ts +2 -2
  148. package/dist/gui/hierarchy/EFHierarchy.js +14 -24
  149. package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
  150. package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
  151. package/dist/gui/hierarchy/EFHierarchyItem.js +22 -10
  152. package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
  153. package/dist/gui/icons.js.map +1 -1
  154. package/dist/gui/previewSettingsContext.d.ts +18 -0
  155. package/dist/gui/previewSettingsContext.js.map +1 -1
  156. package/dist/gui/theme.js +34 -0
  157. package/dist/gui/theme.js.map +1 -0
  158. package/dist/gui/timeline/EFTimeline.d.ts +2 -2
  159. package/dist/gui/timeline/EFTimeline.js +70 -52
  160. package/dist/gui/timeline/EFTimeline.js.map +1 -1
  161. package/dist/gui/timeline/EFTimelineRow.d.ts +5 -3
  162. package/dist/gui/timeline/EFTimelineRow.js +55 -32
  163. package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
  164. package/dist/gui/timeline/TrimHandles.d.ts +23 -9
  165. package/dist/gui/timeline/TrimHandles.js +224 -51
  166. package/dist/gui/timeline/TrimHandles.js.map +1 -1
  167. package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
  168. package/dist/gui/timeline/timelineEditingContext.d.ts +34 -0
  169. package/dist/gui/timeline/timelineEditingContext.js +24 -0
  170. package/dist/gui/timeline/timelineEditingContext.js.map +1 -0
  171. package/dist/gui/timeline/timelineStateContext.js.map +1 -1
  172. package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
  173. package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
  174. package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +2 -3
  175. package/dist/gui/timeline/tracks/CaptionsTrack.js +17 -75
  176. package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
  177. package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +52 -0
  178. package/dist/gui/timeline/tracks/EFThumbnailStrip.js +596 -0
  179. package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -0
  180. package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -1
  181. package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
  182. package/dist/gui/timeline/tracks/TextTrack.d.ts +3 -2
  183. package/dist/gui/timeline/tracks/TextTrack.js +17 -43
  184. package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
  185. package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +3 -4
  186. package/dist/gui/timeline/tracks/TimegroupTrack.js +33 -23
  187. package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
  188. package/dist/gui/timeline/tracks/TrackItem.d.ts +7 -9
  189. package/dist/gui/timeline/tracks/TrackItem.js +18 -17
  190. package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
  191. package/dist/gui/timeline/tracks/VideoTrack.d.ts +3 -3
  192. package/dist/gui/timeline/tracks/VideoTrack.js +11 -14
  193. package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
  194. package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -1
  195. package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
  196. package/dist/gui/timeline/tracks/waveformUtils.js +1 -1
  197. package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
  198. package/dist/gui/tree/EFTree.d.ts +2 -2
  199. package/dist/gui/tree/EFTree.js +8 -14
  200. package/dist/gui/tree/EFTree.js.map +1 -1
  201. package/dist/gui/tree/EFTreeItem.d.ts +2 -2
  202. package/dist/gui/tree/EFTreeItem.js +3 -3
  203. package/dist/gui/tree/EFTreeItem.js.map +1 -1
  204. package/dist/gui/tree/treeContext.js.map +1 -1
  205. package/dist/index.d.ts +10 -8
  206. package/dist/index.js +6 -5
  207. package/dist/index.js.map +1 -1
  208. package/dist/node.d.ts +2 -2
  209. package/dist/node.js +2 -2
  210. package/dist/preview/AdaptiveResolutionTracker.js +3 -3
  211. package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
  212. package/dist/preview/FrameController.d.ts +2 -17
  213. package/dist/preview/FrameController.js +40 -63
  214. package/dist/preview/FrameController.js.map +1 -1
  215. package/dist/preview/QualityUpgradeScheduler.d.ts +76 -0
  216. package/dist/preview/QualityUpgradeScheduler.js +158 -0
  217. package/dist/preview/QualityUpgradeScheduler.js.map +1 -0
  218. package/dist/preview/RenderContext.d.ts +119 -1
  219. package/dist/preview/RenderContext.js +21 -3
  220. package/dist/preview/RenderContext.js.map +1 -1
  221. package/dist/preview/RenderProfiler.js.map +1 -1
  222. package/dist/preview/RenderStats.js +85 -0
  223. package/dist/preview/RenderStats.js.map +1 -0
  224. package/dist/preview/encoding/canvasEncoder.js +2 -52
  225. package/dist/preview/encoding/canvasEncoder.js.map +1 -1
  226. package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
  227. package/dist/preview/encoding/workerEncoder.js.map +1 -1
  228. package/dist/preview/logger.js.map +1 -1
  229. package/dist/preview/previewSettings.d.ts +34 -0
  230. package/dist/preview/previewSettings.js +29 -17
  231. package/dist/preview/previewSettings.js.map +1 -1
  232. package/dist/preview/previewTypes.js +4 -4
  233. package/dist/preview/previewTypes.js.map +1 -1
  234. package/dist/preview/renderElementToCanvas.d.ts +44 -0
  235. package/dist/preview/renderElementToCanvas.js +72 -0
  236. package/dist/preview/renderElementToCanvas.js.map +1 -0
  237. package/dist/preview/renderTimegroupToCanvas.d.ts +134 -32
  238. package/dist/preview/renderTimegroupToCanvas.js +321 -146
  239. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  240. package/dist/preview/renderTimegroupToCanvas.types.d.ts +51 -0
  241. package/dist/preview/renderTimegroupToVideo.d.ts +20 -35
  242. package/dist/preview/renderTimegroupToVideo.js +94 -106
  243. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  244. package/dist/preview/renderTimegroupToVideo.types.d.ts +42 -0
  245. package/dist/preview/renderVideoToVideo.js +286 -0
  246. package/dist/preview/renderVideoToVideo.js.map +1 -0
  247. package/dist/preview/renderers.d.ts +56 -0
  248. package/dist/preview/renderers.js +13 -1
  249. package/dist/preview/renderers.js.map +1 -1
  250. package/dist/preview/rendering/ScaleConfig.js +74 -0
  251. package/dist/preview/rendering/ScaleConfig.js.map +1 -0
  252. package/dist/preview/rendering/inlineImages.d.ts +13 -0
  253. package/dist/preview/rendering/inlineImages.js +7 -44
  254. package/dist/preview/rendering/inlineImages.js.map +1 -1
  255. package/dist/preview/rendering/loadImage.d.ts +8 -0
  256. package/dist/preview/rendering/loadImage.js +22 -0
  257. package/dist/preview/rendering/loadImage.js.map +1 -0
  258. package/dist/preview/rendering/renderToImageNative.js +3 -3
  259. package/dist/preview/rendering/renderToImageNative.js.map +1 -1
  260. package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
  261. package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
  262. package/dist/preview/statsTrackingStrategy.js +1 -101
  263. package/dist/preview/statsTrackingStrategy.js.map +1 -1
  264. package/dist/preview/workers/WorkerPool.js +0 -1
  265. package/dist/preview/workers/WorkerPool.js.map +1 -1
  266. package/dist/preview/workers/encoderWorkerInline.js +21 -54
  267. package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
  268. package/dist/render/EFRenderAPI.d.ts +2 -1
  269. package/dist/render/EFRenderAPI.js +12 -36
  270. package/dist/render/EFRenderAPI.js.map +1 -1
  271. package/dist/render/getRenderData.js +4 -4
  272. package/dist/render/getRenderData.js.map +1 -1
  273. package/dist/style.css +114 -163
  274. package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
  275. package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
  276. package/dist/transcoding/types/index.d.ts +1 -1
  277. package/dist/transcoding/utils/UrlGenerator.js +10 -3
  278. package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
  279. package/dist/utils/LRUCache.js +1 -0
  280. package/dist/utils/LRUCache.js.map +1 -1
  281. package/dist/utils/frameTime.js +23 -1
  282. package/dist/utils/frameTime.js.map +1 -1
  283. package/package.json +45 -8
  284. package/scripts/build-css.js +8 -1
  285. package/test/setup.ts +0 -1
  286. package/test/useAssetMSW.ts +50 -0
  287. package/test/visualRegressionUtils.ts +23 -9
  288. package/tsdown.config.ts +6 -1
  289. package/dist/_virtual/rolldown_runtime.js +0 -27
  290. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
  291. package/dist/elements/EFThumbnailStrip.d.ts +0 -167
  292. package/dist/elements/EFThumbnailStrip.js +0 -731
  293. package/dist/elements/EFThumbnailStrip.js.map +0 -1
  294. package/dist/elements/SessionThumbnailCache.js +0 -154
  295. package/dist/elements/SessionThumbnailCache.js.map +0 -1
  296. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
  297. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
  298. package/dist/node_modules/react/cjs/react.development.js +0 -1521
  299. package/dist/node_modules/react/cjs/react.development.js.map +0 -1
  300. package/dist/node_modules/react/index.js +0 -13
  301. package/dist/node_modules/react/index.js.map +0 -1
  302. package/dist/node_modules/react/jsx-runtime.js +0 -13
  303. package/dist/node_modules/react/jsx-runtime.js.map +0 -1
  304. package/dist/preview/encoding/types.d.ts +0 -1
  305. package/dist/preview/renderTimegroupPreview.js +0 -686
  306. package/dist/preview/renderTimegroupPreview.js.map +0 -1
  307. package/dist/preview/rendering/renderToImage.d.ts +0 -2
  308. package/dist/preview/rendering/renderToImage.js +0 -95
  309. package/dist/preview/rendering/renderToImage.js.map +0 -1
  310. package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
  311. package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
  312. package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
  313. package/dist/preview/rendering/svgSerializer.js +0 -43
  314. package/dist/preview/rendering/svgSerializer.js.map +0 -1
  315. package/dist/preview/rendering/types.d.ts +0 -2
  316. package/dist/preview/thumbnailCacheSettings.js +0 -52
  317. package/dist/preview/thumbnailCacheSettings.js.map +0 -1
  318. package/dist/sandbox/PlaybackControls.d.ts +0 -1
  319. package/dist/sandbox/PlaybackControls.js +0 -10
  320. package/dist/sandbox/PlaybackControls.js.map +0 -1
  321. package/dist/sandbox/ScenarioRunner.d.ts +0 -1
  322. package/dist/sandbox/ScenarioRunner.js +0 -1
  323. package/dist/sandbox/defineSandbox.d.ts +0 -1
  324. package/dist/sandbox/index.d.ts +0 -3
  325. package/dist/sandbox/index.js +0 -2
  326. package/test/EFVideo.framegen.browsertest.ts +0 -80
  327. package/test/thumbnail-performance-test.html +0 -116
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EFThumbnailStrip.js","names":["#timestamps","EFThumbnailStrip","#timelineState","#thumbnailDimensions","#effectiveDurationMs","#effectivePixelsPerMs","#hostWidth","#targetController","#resizeObserver","#scheduleRender","#abortController","#cleanupTimegroupGenerator","#detachTargetListeners","#calculateThumbnailDimensions","#attachTargetListeners","#targetReadyStateHandler","#targetContentChangeHandler","thumbnails: ThumbnailDescriptor[]","#previousPixelsPerMs","#thumbnailPhase","#renderRequested","#renderThumbnails","#calculateVisibleThumbnails","#clearCanvas","#lastRequiredTimestamps","#updateVideoCapture","#updateTimegroupCapture","results: ThumbnailResult[]","#thumbnailCache","nearestTimeMs: number | null","#drawThumbnails","#getSourceTimeMs","#updateInProgress","#pendingTimestamps","#retryScheduled","#timegroupGenerator","#timegroupGeneratorAbort","#timegroupQueue","#timegroupClone","#previewContainer","#consumeTimegroupGenerator","#startTimegroupGenerator","updatePromises: Promise<any>[]","textUpdatePromises: Promise<any>[]","imagePromises: Promise<void>[]","#consumerRunning","#canvasContainer"],"sources":["../../../../src/gui/timeline/tracks/EFThumbnailStrip.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { createRef, ref, type Ref } from \"lit/directives/ref.js\";\n\nimport { EFTimegroup } from \"../../../elements/EFTimegroup.js\";\nimport { EFVideo } from \"../../../elements/EFVideo.js\";\nimport { TargetController } from \"../../../elements/TargetController.js\";\nimport { ThumbnailExtractor } from \"../../../elements/EFMedia/shared/ThumbnailExtractor.js\";\nimport type { BaseMediaEngine } from \"../../../elements/EFMedia/BaseMediaEngine.js\";\nimport {\n generateThumbnailsFromClone,\n type GeneratedThumbnail,\n type ThumbnailQueue,\n} from \"../../../preview/renderTimegroupToCanvas.js\";\n\nimport { quantizeToFrameTimeMs } from \"../../../utils/frameTime.js\";\nimport { TWMixin } from \"../../TWMixin.js\";\nimport {\n timelineStateContext,\n type TimelineState,\n} from \"../timelineStateContext.js\";\nimport {\n previewSettingsContext,\n type PreviewSettings,\n} from \"../../previewSettingsContext.js\";\n\n/** Padding for virtual rendering */\nconst VIRTUAL_RENDER_PADDING_PX = 100;\n\n/**\n * Mutable queue for timestamp generation.\n * Allows updating timestamps while generator is consuming them.\n */\nclass MutableTimestampQueue implements ThumbnailQueue {\n #timestamps: number[] = [];\n\n /** Replace entire queue with new timestamps (sorted) */\n reset(timestamps: number[]): void {\n this.#timestamps = [...timestamps].sort((a, b) => a - b);\n }\n\n /** Keep only these specific timestamps (maintains order) */\n retainOnly(timestamps: number[]): void {\n const keep = new Set(timestamps);\n this.#timestamps = this.#timestamps.filter((t) => keep.has(t));\n }\n\n /** Append timestamps to end (sorted) */\n append(timestamps: number[]): void {\n this.#timestamps.push(...[...timestamps].sort((a, b) => a - b));\n }\n\n /** Get next timestamp (removes from front) */\n shift(): number | undefined {\n return this.#timestamps.shift();\n }\n\n /** Get remaining timestamps without modifying queue */\n remaining(): number[] {\n return [...this.#timestamps];\n }\n\n /** Check if queue is empty */\n isEmpty(): boolean {\n return this.#timestamps.length === 0;\n }\n}\n\n/**\n * Descriptor for a thumbnail to render\n */\ninterface ThumbnailDescriptor {\n timeMs: number;\n x: number;\n width: number;\n height: number;\n}\n\n/**\n * Result of thumbnail rendering (canvas or error)\n */\ninterface ThumbnailResult {\n canvas: CanvasImageSource | null;\n error?: Error;\n}\n\n/**\n * Thumbnail strip component that renders thumbnails for video or timegroup elements.\n *\n * Features:\n * - Targets ef-video or root ef-timegroup via target attribute\n * - Batch video thumbnail extraction via ThumbnailExtractor\n * - Canvas rendering for timegroups at low resolution\n * - Viewport-based lazy loading with scroll calculation\n * - Fixed visual spacing (consistent at all zoom levels)\n * - Error indicators for failed thumbnails\n */\n@customElement(\"ef-thumbnail-strip\")\nexport class EFThumbnailStrip extends TWMixin(LitElement) {\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n\n .thumbnail-container {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n\n .error-message {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n padding: 8px;\n color: var(--ef-color-error);\n font-size: 12px;\n background: color-mix(in srgb, var(--ef-color-error) 10%, transparent);\n }\n\n canvas {\n position: absolute;\n image-rendering: pixelated;\n image-rendering: crisp-edges;\n }\n `,\n ];\n\n @property({ type: String })\n target = \"\";\n\n @property({ attribute: false })\n targetElement: Element | null = null;\n\n @property({ type: Number, attribute: \"thumbnail-height\" })\n thumbnailHeight = 24;\n\n @property({ type: Number, attribute: \"thumbnail-spacing-px\" })\n thumbnailSpacingPx = 48;\n\n @property({ type: Number, attribute: \"pixels-per-ms\" })\n pixelsPerMs: number | null = null;\n\n @property({ type: Boolean, attribute: \"use-intrinsic-duration\" })\n useIntrinsicDuration = false;\n\n @consume({ context: timelineStateContext, subscribe: true })\n @state()\n timelineState?: TimelineState;\n\n @consume({ context: previewSettingsContext, subscribe: true })\n @state()\n previewSettings?: PreviewSettings;\n\n @state()\n thumbnailDimensions = { width: 0, height: 0 };\n\n #targetController?: TargetController;\n #abortController: AbortController | null = null;\n #renderRequested = false;\n #canvasContainer: Ref<HTMLDivElement> = createRef();\n #lastRequiredTimestamps = \"\";\n #thumbnailCache = new Map<number, CanvasImageSource>();\n\n // Timegroup thumbnail generation state\n #timegroupQueue = new MutableTimestampQueue();\n #timegroupClone: {\n clone: EFTimegroup;\n container: HTMLElement;\n cleanup: () => void;\n } | null = null;\n #timegroupGenerator: AsyncGenerator<GeneratedThumbnail> | null = null;\n #timegroupGeneratorAbort: AbortController | null = null;\n #previewContainer: HTMLDivElement | null = null;\n #updateInProgress = false; // Lock to prevent concurrent updates\n #consumerRunning = false; // Lock to prevent concurrent consumers\n #pendingTimestamps = new Set<number>(); // Timestamps requested while update in progress\n #retryScheduled = false; // Flag to prevent duplicate retry schedules\n #thumbnailPhase: number = 0; // Phase offset for thumbnail grid\n #previousPixelsPerMs: number | null = null; // Track zoom changes\n #targetReadyStateHandler: (() => void) | null = null;\n #targetContentChangeHandler: (() => void) | null = null;\n #resizeObserver: ResizeObserver | null = null;\n #hostWidth = 0;\n\n /**\n * Check if target is valid (EFVideo or root EFTimegroup)\n */\n get isValidTarget(): boolean {\n const el = this.targetElement;\n if (!el) return false;\n\n if (el instanceof EFVideo) return true;\n\n if (el instanceof EFTimegroup) {\n // Only root timegroups\n return (el as any).isRootTimegroup === true;\n }\n\n return false;\n }\n\n get #timelineState(): TimelineState | undefined {\n return this.timelineState;\n }\n\n get #thumbnailDimensions() {\n return this.thumbnailDimensions;\n }\n\n get #effectiveDurationMs(): number {\n const element = this.targetElement;\n if (!element) return 0;\n if (this.useIntrinsicDuration) {\n return (\n (element as any).intrinsicDurationMs ?? (element as any).durationMs ?? 0\n );\n }\n return (element as any).durationMs ?? 0;\n }\n\n get #effectivePixelsPerMs(): number {\n if (this.#timelineState?.pixelsPerMs != null) {\n return this.#timelineState.pixelsPerMs;\n }\n if (this.pixelsPerMs != null) {\n return this.pixelsPerMs;\n }\n const durationMs = this.#effectiveDurationMs;\n if (this.#hostWidth > 0 && durationMs > 0) {\n return this.#hostWidth / durationMs;\n }\n return 0.04;\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n // Only use TargetController if target is set and targetElement is not directly set\n if (this.target && !this.targetElement) {\n this.#targetController = new TargetController(this);\n }\n this.#resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (!entry) return;\n const width = entry.contentRect.width;\n if (width !== this.#hostWidth) {\n this.#hostWidth = width;\n this.requestUpdate();\n this.#scheduleRender();\n }\n });\n this.#resizeObserver.observe(this);\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#abortController?.abort();\n this.#cleanupTimegroupGenerator();\n this.#detachTargetListeners(this.targetElement);\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = null;\n }\n\n protected willUpdate(\n changedProperties: Map<string | number | symbol, unknown>,\n ): void {\n super.willUpdate(changedProperties);\n\n // Create TargetController if target is set and targetElement is not directly set\n if (changedProperties.has(\"target\")) {\n if (this.target && !this.targetElement && !this.#targetController) {\n this.#targetController = new TargetController(this);\n }\n }\n\n // Recalculate thumbnail dimensions if target changed\n if (\n changedProperties.has(\"targetElement\") ||\n changedProperties.has(\"thumbnailHeight\")\n ) {\n this.thumbnailDimensions = this.#calculateThumbnailDimensions();\n }\n\n // Manage event listeners when target changes\n if (changedProperties.has(\"targetElement\")) {\n const oldTarget = changedProperties.get(\n \"targetElement\",\n ) as Element | null;\n this.#detachTargetListeners(oldTarget);\n this.#attachTargetListeners(this.targetElement);\n }\n }\n\n #attachTargetListeners(target: Element | null): void {\n if (!target) return;\n\n this.#targetReadyStateHandler = () => {\n this.requestUpdate();\n this.#scheduleRender();\n };\n this.#targetContentChangeHandler = () => {\n this.requestUpdate();\n this.#scheduleRender();\n };\n target.addEventListener(\"readystatechange\", this.#targetReadyStateHandler);\n target.addEventListener(\"contentchange\", this.#targetContentChangeHandler);\n\n // Late-subscriber: if the target already transitioned to \"ready\" before\n // we attached, the event was missed. The contentReadyState property\n // serves exactly this purpose — check it and render if needed.\n if ((target as any).contentReadyState === \"ready\") {\n this.requestUpdate();\n this.#scheduleRender();\n }\n }\n\n #detachTargetListeners(target: Element | null): void {\n if (!target) return;\n if (this.#targetReadyStateHandler) {\n target.removeEventListener(\n \"readystatechange\",\n this.#targetReadyStateHandler,\n );\n this.#targetReadyStateHandler = null;\n }\n if (this.#targetContentChangeHandler) {\n target.removeEventListener(\n \"contentchange\",\n this.#targetContentChangeHandler,\n );\n this.#targetContentChangeHandler = null;\n }\n }\n\n updated(changedProperties: Map<string | number | symbol, unknown>): void {\n super.updated(changedProperties);\n\n if (\n changedProperties.has(\"targetElement\") ||\n changedProperties.has(\"thumbnailSpacingPx\") ||\n changedProperties.has(\"pixelsPerMs\") ||\n changedProperties.has(\"thumbnailHeight\") ||\n changedProperties.has(\"timelineState\")\n ) {\n this.#scheduleRender();\n }\n }\n\n /**\n * Calculate thumbnail dimensions from target element's actual bounds\n */\n #calculateThumbnailDimensions(): { width: number; height: number } {\n const el = this.targetElement;\n if (!el) return { width: 0, height: 0 };\n\n // Get actual visible bounds from DOM\n const bounds = el.getBoundingClientRect();\n if (bounds.width === 0 || bounds.height === 0) {\n // Element not yet rendered or no size, use default aspect ratio\n return {\n width: this.thumbnailHeight * (16 / 9),\n height: this.thumbnailHeight,\n };\n }\n\n const aspectRatio = bounds.width / bounds.height;\n const width = Math.round(this.thumbnailHeight * aspectRatio);\n\n return { width, height: this.thumbnailHeight };\n }\n\n /**\n * Calculate visible thumbnails based on viewport\n */\n #calculateVisibleThumbnails(): ThumbnailDescriptor[] {\n if (!this.isValidTarget) return [];\n\n const element = this.targetElement;\n if (!element) return [];\n\n const scrollLeft = this.#timelineState?.viewportScrollLeft ?? 0;\n const viewportWidth =\n this.#timelineState?.viewportWidth ?? (this.#hostWidth || 800);\n const pixelsPerMs = this.#effectivePixelsPerMs;\n\n const durationMs = this.#effectiveDurationMs;\n if (durationMs === 0) return [];\n\n const trackWidthPx = durationMs * pixelsPerMs;\n\n // Get FPS for quantization\n const fps = (element as any).fps ?? 30;\n\n const visibleStartPx = scrollLeft - VIRTUAL_RENDER_PADDING_PX;\n const visibleEndPx = scrollLeft + viewportWidth + VIRTUAL_RENDER_PADDING_PX;\n\n const thumbnails: ThumbnailDescriptor[] = [];\n const { width, height } = this.#thumbnailDimensions;\n\n // Read minimum gap from CSS variable (--ef-thumbnail-gap, default 2px)\n const gapPx =\n parseFloat(\n getComputedStyle(this).getPropertyValue(\"--ef-thumbnail-gap\"),\n ) || 2;\n // Stride must be at least thumbnail width + gap to prevent overlap\n const thumbnailStride = Math.max(this.thumbnailSpacingPx, width + gapPx);\n\n // Detect zoom by checking if pixelsPerMs changed\n const isZoom =\n this.#previousPixelsPerMs !== null &&\n this.#previousPixelsPerMs !== pixelsPerMs;\n\n if (this.#previousPixelsPerMs === null) {\n // First render: align grid to track start (t=0)\n this.#thumbnailPhase = 0;\n } else if (isZoom) {\n // On zoom: snap a thumbnail to near the left edge of viewport\n // This prevents visual slip during zoom operations\n this.#thumbnailPhase = scrollLeft % thumbnailStride;\n } else if (scrollLeft < thumbnailStride) {\n // When scrolled near the start, realign to t=0 to avoid left gap\n this.#thumbnailPhase = 0;\n }\n // During normal scroll: phase unchanged, grid scrolls naturally with track\n\n this.#previousPixelsPerMs = pixelsPerMs;\n\n // Generate thumbnail grid anchored at phase offset\n // Each thumbnail is at absolute track position: phase + (i * stride)\n // This means grid is stable in track space (scrolls naturally)\n const startIndex = Math.max(\n 0,\n Math.floor((visibleStartPx - this.#thumbnailPhase) / thumbnailStride),\n );\n const endIndex = Math.ceil(\n (visibleEndPx - this.#thumbnailPhase) / thumbnailStride,\n );\n\n for (let i = startIndex; i <= endIndex; i++) {\n const thumbX = this.#thumbnailPhase + i * thumbnailStride;\n\n // Only include if within track bounds\n if (thumbX >= 0 && thumbX < trackWidthPx) {\n // Convert position to time (leading edge)\n const rawTimeMs = thumbX / pixelsPerMs;\n const timeMs = quantizeToFrameTimeMs(rawTimeMs, fps);\n\n if (timeMs >= 0 && timeMs < durationMs) {\n thumbnails.push({ timeMs, x: thumbX, width, height });\n }\n }\n }\n\n return thumbnails;\n }\n\n /**\n * Schedule thumbnail render on next frame\n */\n #scheduleRender(): void {\n if (this.#renderRequested) return;\n this.#renderRequested = true;\n\n requestAnimationFrame(() => {\n this.#renderRequested = false;\n this.#renderThumbnails();\n });\n }\n\n /**\n * Render thumbnails with cancellation support\n */\n async #renderThumbnails(): Promise<void> {\n // Cancel previous render\n this.#abortController?.abort();\n this.#abortController = new AbortController();\n const signal = this.#abortController.signal;\n\n const visibleThumbnails = this.#calculateVisibleThumbnails();\n if (visibleThumbnails.length === 0) {\n this.#clearCanvas();\n return;\n }\n\n // Check if required timestamps changed\n const requiredTimestamps = visibleThumbnails.map((t) => t.timeMs);\n const timestampsString = requiredTimestamps.join(\", \");\n if (timestampsString !== this.#lastRequiredTimestamps) {\n this.#lastRequiredTimestamps = timestampsString;\n\n // Update capture queue\n if (this.targetElement instanceof EFVideo) {\n this.#updateVideoCapture(requiredTimestamps, signal).catch((error) => {\n // Ignore AbortErrors - these are expected when renders are cancelled\n if (error instanceof DOMException && error.name === \"AbortError\") {\n return;\n }\n console.error(\"Thumbnail capture error:\", error);\n });\n } else if (this.targetElement instanceof EFTimegroup) {\n this.#updateTimegroupCapture(requiredTimestamps);\n }\n }\n\n if (signal.aborted) return;\n\n // Draw thumbnails - use nearest neighbor if exact timestamp not cached\n const maxNeighborDistanceMs = 3000; // Don't use thumbnails more than 3s away\n const results: ThumbnailResult[] = visibleThumbnails.map((t) => {\n let canvas = this.#thumbnailCache.get(t.timeMs);\n\n // If exact match not found, find nearest cached thumbnail\n if (!canvas) {\n let nearestTimeMs: number | null = null;\n let minDistance = Infinity;\n\n for (const cachedTimeMs of this.#thumbnailCache.keys()) {\n const distance = Math.abs(cachedTimeMs - t.timeMs);\n if (distance < minDistance && distance <= maxNeighborDistanceMs) {\n minDistance = distance;\n nearestTimeMs = cachedTimeMs;\n }\n }\n\n if (nearestTimeMs !== null) {\n canvas = this.#thumbnailCache.get(nearestTimeMs);\n }\n }\n\n return { canvas: canvas ?? null };\n });\n this.#drawThumbnails(visibleThumbnails, results);\n }\n\n /**\n * Update video thumbnail capture\n */\n async #updateVideoCapture(\n timestamps: number[],\n signal: AbortSignal,\n ): Promise<void> {\n const video = this.targetElement as EFVideo;\n if (!video) return;\n\n // Filter out cached timestamps\n const uncached = timestamps.filter((t) => !this.#thumbnailCache.has(t));\n if (uncached.length === 0) return;\n\n const mediaEngineTask = video.mediaEngineTask;\n if (!mediaEngineTask) return;\n\n const mediaEngine = await mediaEngineTask.taskComplete;\n if (!mediaEngine) return;\n\n const sourceTimestamps = uncached.map((t) => this.#getSourceTimeMs(t));\n\n const extractor = new ThumbnailExtractor(\n mediaEngine as unknown as BaseMediaEngine,\n );\n const scrubRendition = mediaEngine.videoRendition;\n if (!scrubRendition) return;\n\n const results = await extractor.extractThumbnails(\n sourceTimestamps,\n scrubRendition,\n video.durationMs ?? 0,\n signal,\n );\n\n // Store in cache and trigger redraw\n for (let i = 0; i < uncached.length; i++) {\n const thumbnail = results[i]?.thumbnail;\n const timestamp = uncached[i];\n if (thumbnail && timestamp !== undefined) {\n this.#thumbnailCache.set(timestamp, thumbnail);\n }\n }\n\n this.#scheduleRender();\n }\n\n /**\n * Update timegroup thumbnail capture using mutable queue\n */\n async #updateTimegroupCapture(timestamps: number[]): Promise<void> {\n const timegroup = this.targetElement as EFTimegroup;\n if (!timegroup) return;\n\n // Filter out cached timestamps\n const uncached = timestamps\n .filter((t) => !this.#thumbnailCache.has(t))\n .sort((a, b) => a - b);\n if (uncached.length === 0) {\n return;\n }\n\n // CRITICAL: If update already in progress, REPLACE pending (not add)\n // We only want the LATEST required timestamps, not a union of all previous ones\n if (this.#updateInProgress) {\n // Clear old pending and replace with latest\n this.#pendingTimestamps.clear();\n uncached.forEach((t) => this.#pendingTimestamps.add(t));\n\n // Schedule a retry (debounced via RAF)\n if (!this.#retryScheduled) {\n this.#retryScheduled = true;\n requestAnimationFrame(() => {\n this.#retryScheduled = false;\n if (this.#pendingTimestamps.size > 0) {\n const pending = Array.from(this.#pendingTimestamps);\n this.#pendingTimestamps.clear();\n this.#updateTimegroupCapture(pending);\n }\n });\n }\n return;\n }\n this.#updateInProgress = true;\n\n try {\n if (this.#timegroupGenerator) {\n // Generator is running - abort and reset queue to exactly what we need now\n // Abort in-flight capture\n this.#timegroupGeneratorAbort?.abort();\n\n // Create new abort controller for the updated queue\n this.#timegroupGeneratorAbort = new AbortController();\n\n // Reset queue to exactly what we need now\n this.#timegroupQueue.reset(uncached);\n } else if (this.#timegroupClone) {\n // Generator finished, restart with existing clone\n this.#timegroupQueue.reset(uncached);\n\n // Create new abort controller\n this.#timegroupGeneratorAbort = new AbortController();\n\n this.#timegroupGenerator = generateThumbnailsFromClone(\n this.#timegroupClone.clone,\n this.#previewContainer!,\n this.#timegroupQueue,\n {\n scale: 0.25,\n contentReadyMode: \"blocking\",\n blockingTimeoutMs: 1000,\n signal: this.#timegroupGeneratorAbort.signal,\n },\n );\n await this.#consumeTimegroupGenerator();\n } else {\n // No generator or clone, start fresh\n await this.#startTimegroupGenerator(timegroup, uncached);\n }\n } finally {\n this.#updateInProgress = false;\n\n // Check if there are pending timestamps that need processing\n // This happens when updates were skipped while this update was in progress\n if (this.#pendingTimestamps.size > 0 && !this.#retryScheduled) {\n this.#retryScheduled = true;\n requestAnimationFrame(() => {\n this.#retryScheduled = false;\n if (this.#pendingTimestamps.size > 0) {\n const pending = Array.from(this.#pendingTimestamps);\n this.#pendingTimestamps.clear();\n this.#updateTimegroupCapture(pending);\n }\n });\n }\n }\n }\n\n /**\n * Start timegroup thumbnail generator\n */\n async #startTimegroupGenerator(\n timegroup: EFTimegroup,\n timestamps: number[],\n ): Promise<void> {\n // Create render clone\n this.#timegroupClone = await timegroup.createRenderClone();\n\n // Use the original container from createRenderClone (already configured)\n this.#previewContainer = this.#timegroupClone.container as HTMLDivElement;\n\n // CRITICAL: Wait for Lit to process shadow DOM updates after moving to new container\n await this.#timegroupClone.clone.updateComplete;\n\n // Also wait for all nested Lit elements to update\n const litElements = this.#previewContainer.querySelectorAll(\"*\");\n const updatePromises: Promise<any>[] = [];\n for (const el of litElements) {\n if (\"updateComplete\" in el) {\n updatePromises.push((el as any).updateComplete);\n }\n }\n await Promise.all(updatePromises);\n\n // Wait AGAIN specifically for text segments (they may need to re-render after move)\n const textSegments =\n this.#previewContainer.querySelectorAll(\"ef-text-segment\");\n const textUpdatePromises: Promise<any>[] = [];\n for (const seg of textSegments) {\n if (\"updateComplete\" in seg) {\n textUpdatePromises.push((seg as any).updateComplete);\n }\n }\n await Promise.all(textUpdatePromises);\n\n // CRITICAL: Wait for ef-text to split text into segments\n // EFText.connectedCallback schedules splitText in requestAnimationFrame\n // We must wait for that RAF to fire before capturing\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // WARMUP: Do a seek to the first timestamp to \"prime\" the clone\n // Guard: clone may have been disposed during prior awaits\n if (!this.#timegroupClone) return;\n if (timestamps.length > 0) {\n await this.#timegroupClone.clone.seekForRender(timestamps[0]!);\n }\n\n // CRITICAL: Wait for fonts to load\n // Text won't render correctly if fonts aren't ready\n await document.fonts.ready;\n\n // CRITICAL: Wait for all images to load\n const images = this.#previewContainer.querySelectorAll(\"img\");\n const imagePromises: Promise<void>[] = [];\n for (const img of images) {\n if (!img.complete) {\n imagePromises.push(\n new Promise((resolve, _reject) => {\n img.onload = () => resolve();\n img.onerror = () => resolve(); // Don't block on errors\n // Timeout after 5s\n setTimeout(() => resolve(), 5000);\n }),\n );\n }\n }\n await Promise.all(imagePromises);\n\n // Guard: clone may have been disposed during prior awaits\n if (!this.#timegroupClone) return;\n\n // Initialize queue\n this.#timegroupQueue.reset(timestamps);\n\n // Create abort controller for this generator\n this.#timegroupGeneratorAbort = new AbortController();\n\n // Start generator using the fresh container\n this.#timegroupGenerator = generateThumbnailsFromClone(\n this.#timegroupClone.clone,\n this.#previewContainer,\n this.#timegroupQueue,\n {\n scale: 0.25,\n contentReadyMode: \"blocking\",\n blockingTimeoutMs: 1000,\n signal: this.#timegroupGeneratorAbort.signal,\n },\n );\n\n // Consume generator (CRITICAL: Must await to prevent concurrent consumers)\n await this.#consumeTimegroupGenerator();\n }\n\n /**\n * Consume generator and handle cleanup\n */\n async #consumeTimegroupGenerator(): Promise<void> {\n // CRITICAL: Prevent concurrent consumers\n if (this.#consumerRunning) {\n return;\n }\n this.#consumerRunning = true;\n\n if (!this.#timegroupGenerator) {\n this.#consumerRunning = false;\n return;\n }\n\n try {\n for await (const { timeMs, canvas } of this.#timegroupGenerator) {\n this.#thumbnailCache.set(timeMs, canvas);\n this.#scheduleRender();\n }\n } catch (err) {\n console.warn(\"Timegroup thumbnail generation error:\", err);\n } finally {\n // Generator finished, but keep clone alive for reuse\n this.#timegroupGenerator = null;\n this.#consumerRunning = false;\n }\n }\n\n /**\n * Cleanup timegroup generator and clone\n */\n #cleanupTimegroupGenerator(): void {\n // Abort any in-flight work\n this.#timegroupGeneratorAbort?.abort();\n this.#timegroupGeneratorAbort = null;\n\n this.#timegroupGenerator = null;\n\n // Remove preview container from DOM\n if (this.#previewContainer) {\n this.#previewContainer.remove();\n this.#previewContainer = null;\n }\n\n // Cleanup render clone\n if (this.#timegroupClone) {\n this.#timegroupClone.cleanup();\n this.#timegroupClone = null;\n }\n }\n\n /**\n * Clear all canvas elements\n */\n #clearCanvas(): void {\n const container = this.#canvasContainer.value;\n if (container) {\n container.innerHTML = \"\";\n }\n }\n\n /**\n * Translate composition time to source time for videos (handles trim)\n */\n #getSourceTimeMs(compositionTimeMs: number): number {\n if (this.useIntrinsicDuration) {\n return compositionTimeMs;\n }\n const el = this.targetElement;\n if (el instanceof EFVideo) {\n return compositionTimeMs + (el.sourceStartMs ?? 0);\n }\n return compositionTimeMs;\n }\n\n /**\n * Draw thumbnails to canvas elements\n */\n #drawThumbnails(\n thumbnails: ThumbnailDescriptor[],\n results: ThumbnailResult[],\n ): void {\n const container = this.#canvasContainer.value;\n if (!container) return;\n\n // Clear existing canvases\n container.innerHTML = \"\";\n\n for (let i = 0; i < thumbnails.length; i++) {\n const thumbnail = thumbnails[i];\n const result = results[i];\n\n if (!thumbnail) continue;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = thumbnail.width;\n canvas.height = thumbnail.height;\n canvas.style.left = `${thumbnail.x}px`;\n canvas.style.top = \"0\";\n canvas.style.width = `${thumbnail.width}px`;\n canvas.style.height = `${thumbnail.height}px`;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) continue;\n\n if (result?.canvas) {\n // Draw actual thumbnail\n ctx.drawImage(result.canvas, 0, 0, thumbnail.width, thumbnail.height);\n\n // Draw timestamp overlay if enabled\n if (this.previewSettings?.showThumbnailTimestamps) {\n ctx.fillStyle = \"rgba(0, 0, 0, 0.8)\";\n ctx.fillRect(2, 2, 95, 16);\n ctx.fillStyle = \"yellow\";\n ctx.font = \"11px monospace\";\n ctx.textAlign = \"left\";\n ctx.textBaseline = \"top\";\n ctx.fillText(`${Math.round(thumbnail.timeMs)}ms`, 5, 4);\n }\n } else {\n // Draw placeholder with timestamp text\n const bgColor =\n getComputedStyle(this)\n .getPropertyValue(\"--ef-color-bg-inset\")\n .trim() || \"rgba(100, 100, 100, 0.3)\";\n ctx.fillStyle = bgColor;\n ctx.fillRect(0, 0, thumbnail.width, thumbnail.height);\n\n const borderColor =\n getComputedStyle(this)\n .getPropertyValue(\"--ef-color-border-subtle\")\n .trim() || \"rgba(150, 150, 150, 0.5)\";\n ctx.strokeStyle = borderColor;\n ctx.lineWidth = 1;\n ctx.strokeRect(0, 0, thumbnail.width, thumbnail.height);\n\n ctx.fillStyle = \"white\";\n ctx.font = \"10px monospace\";\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(\n `${Math.round(thumbnail.timeMs)}ms`,\n thumbnail.width / 2,\n thumbnail.height / 2,\n );\n }\n\n container.appendChild(canvas);\n }\n }\n\n render() {\n // Error: No target specified (neither target string nor targetElement)\n if (!this.target && !this.targetElement) {\n return html`<div class=\"error-message\">No target specified</div>`;\n }\n\n // Error: Target element not found (when using target string)\n if (this.target && !this.targetElement) {\n return html`<div class=\"error-message\">\n Target element \"${this.target}\" not found\n </div>`;\n }\n\n // Error: Invalid target type\n if (!this.isValidTarget) {\n const elementType =\n (this.targetElement as any).tagName?.toLowerCase() || \"unknown\";\n return html`<div class=\"error-message\">\n Invalid target: \"${elementType}\" must be ef-video or root ef-timegroup\n </div>`;\n }\n\n // Calculate track width to clip thumbnails at track end\n const durationMs = this.#effectiveDurationMs;\n const pixelsPerMs = this.#effectivePixelsPerMs;\n const trackWidthPx = durationMs * pixelsPerMs;\n\n // Render canvas container with explicit width clipping\n return html`<div \n class=\"thumbnail-container\" \n style=\"max-width: ${trackWidthPx}px;\"\n ${ref(this.#canvasContainer)}\n ></div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-thumbnail-strip\": EFThumbnailStrip;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"HTMLTrack.js","names":["EFHTMLTrack"],"sources":["../../../../src/gui/timeline/tracks/HTMLTrack.ts"],"sourcesContent":["import { html } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\nimport { renderTrackChildren } from \"./renderTrackChildren.js\";\n\n@customElement(\"ef-html-track\")\nexport class EFHTMLTrack extends TrackItem {\n contents() {\n return html`\n <span>${this.element.tagName}</span>\n ${renderTrackChildren(\n Array.from(this.element.children || []),\n this.pixelsPerMs,\n this.hideSelectors,\n this.showSelectors,\n false,\n this.enableTrim,\n )}\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-html-track\": EFHTMLTrack;\n }\n}\n\n"],"mappings":";;;;;;;AAQO,wBAAMA,sBAAoB,UAAU;CACzC,WAAW;AACT,SAAO,IAAI;cACD,KAAK,QAAQ,QAAQ;QAC3B,oBACA,MAAM,KAAK,KAAK,QAAQ,YAAY,EAAE,CAAC,EACvC,KAAK,aACL,KAAK,eACL,KAAK,eACL,OACA,KAAK,WACN,CAAC;;;;0BAZP,cAAc,gBAAgB"}
1
+ {"version":3,"file":"HTMLTrack.js","names":["EFHTMLTrack"],"sources":["../../../../src/gui/timeline/tracks/HTMLTrack.ts"],"sourcesContent":["import { html } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\nimport { renderTrackChildren } from \"./renderTrackChildren.js\";\n\n@customElement(\"ef-html-track\")\nexport class EFHTMLTrack extends TrackItem {\n contents() {\n return html`\n <span>${this.element.tagName}</span>\n ${renderTrackChildren(\n Array.from(this.element.children || []),\n this.pixelsPerMs,\n this.hideSelectors,\n this.showSelectors,\n false,\n this.enableTrim,\n )}\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-html-track\": EFHTMLTrack;\n }\n}\n"],"mappings":";;;;;;;AAQO,wBAAMA,sBAAoB,UAAU;CACzC,WAAW;AACT,SAAO,IAAI;cACD,KAAK,QAAQ,QAAQ;QAC3B,oBACA,MAAM,KAAK,KAAK,QAAQ,YAAY,EAAE,CAAC,EACvC,KAAK,aACL,KAAK,eACL,KAAK,eACL,OACA,KAAK,WACN,CAAC;;;;0BAZP,cAAc,gBAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"ImageTrack.js","names":["EFImageTrack"],"sources":["../../../../src/gui/timeline/tracks/ImageTrack.ts"],"sourcesContent":["import { html, nothing } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { EFImage } from \"../../../elements/EFImage.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\n\n@customElement(\"ef-image-track\")\nexport class EFImageTrack extends TrackItem {\n contents() {\n const image = this.element as EFImage;\n if (!(image instanceof EFImage)) {\n return nothing;\n }\n\n // Try to get image src for thumbnail preview\n const src = image.getAttribute(\"src\") || (image as any).src;\n \n // Calculate track dimensions - show repeating thumbnails to fill the track\n const durationMs = image.durationMs ?? 0;\n const trackWidth = durationMs * this.pixelsPerMs;\n const thumbnailHeight = 18;\n const thumbnailWidth = Math.min(32, trackWidth);\n\n if (!src || thumbnailWidth < 8) {\n return nothing;\n }\n\n // Calculate how many thumbnails can fit\n const thumbnailCount = Math.max(1, Math.floor((trackWidth - 8) / (thumbnailWidth + 2)));\n\n return html`\n <div style=${styleMap({\n position: \"absolute\",\n left: \"4px\",\n right: \"4px\",\n top: \"2px\",\n bottom: \"2px\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"2px\",\n overflow: \"hidden\",\n })}>\n ${Array.from({ length: thumbnailCount }, () => html`\n <img\n src=\"${src}\"\n alt=\"\"\n style=${styleMap({\n height: `${thumbnailHeight}px`,\n width: `${thumbnailWidth}px`,\n objectFit: \"cover\",\n borderRadius: \"2px\",\n opacity: \"0.75\",\n pointerEvents: \"none\",\n flexShrink: \"0\",\n })}\n />\n `)}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-image-track\": EFImageTrack;\n }\n}\n\n"],"mappings":";;;;;;;;AASO,yBAAMA,uBAAqB,UAAU;CAC1C,WAAW;EACT,MAAM,QAAQ,KAAK;AACnB,MAAI,EAAE,iBAAiB,SACrB,QAAO;EAIT,MAAM,MAAM,MAAM,aAAa,MAAM,IAAK,MAAc;EAIxD,MAAM,cADa,MAAM,cAAc,KACP,KAAK;EACrC,MAAM,kBAAkB;EACxB,MAAM,iBAAiB,KAAK,IAAI,IAAI,WAAW;AAE/C,MAAI,CAAC,OAAO,iBAAiB,EAC3B,QAAO;EAIT,MAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,OAAO,aAAa,MAAM,iBAAiB,GAAG,CAAC;AAEvF,SAAO,IAAI;mBACI,SAAS;GACpB,UAAU;GACV,MAAM;GACN,OAAO;GACP,KAAK;GACL,QAAQ;GACR,SAAS;GACT,YAAY;GACZ,KAAK;GACL,UAAU;GACX,CAAC,CAAC;UACC,MAAM,KAAK,EAAE,QAAQ,gBAAgB,QAAQ,IAAI;;mBAExC,IAAI;;oBAEH,SAAS;GACf,QAAQ,GAAG,gBAAgB;GAC3B,OAAO,GAAG,eAAe;GACzB,WAAW;GACX,cAAc;GACd,SAAS;GACT,eAAe;GACf,YAAY;GACb,CAAC,CAAC;;UAEL,CAAC;;;;;2BAlDV,cAAc,iBAAiB"}
1
+ {"version":3,"file":"ImageTrack.js","names":["EFImageTrack"],"sources":["../../../../src/gui/timeline/tracks/ImageTrack.ts"],"sourcesContent":["import { html, nothing } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { EFImage } from \"../../../elements/EFImage.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\n\n@customElement(\"ef-image-track\")\nexport class EFImageTrack extends TrackItem {\n contents() {\n const image = this.element as EFImage;\n if (!(image instanceof EFImage)) {\n return nothing;\n }\n\n // Try to get image src for thumbnail preview\n const src = image.getAttribute(\"src\") || (image as any).src;\n\n // Calculate track dimensions - show repeating thumbnails to fill the track\n const durationMs = image.durationMs ?? 0;\n const trackWidth = durationMs * this.pixelsPerMs;\n const thumbnailHeight = 18;\n const thumbnailWidth = Math.min(32, trackWidth);\n\n if (!src || thumbnailWidth < 8) {\n return nothing;\n }\n\n // Calculate how many thumbnails can fit\n const thumbnailCount = Math.max(\n 1,\n Math.floor((trackWidth - 8) / (thumbnailWidth + 2)),\n );\n\n return html`\n <div style=${styleMap({\n position: \"absolute\",\n left: \"4px\",\n right: \"4px\",\n top: \"2px\",\n bottom: \"2px\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"2px\",\n overflow: \"hidden\",\n })}>\n ${Array.from(\n { length: thumbnailCount },\n () => html`\n <img\n src=\"${src}\"\n alt=\"\"\n style=${styleMap({\n height: `${thumbnailHeight}px`,\n width: `${thumbnailWidth}px`,\n objectFit: \"cover\",\n borderRadius: \"2px\",\n opacity: \"0.75\",\n pointerEvents: \"none\",\n flexShrink: \"0\",\n })}\n />\n `,\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-image-track\": EFImageTrack;\n }\n}\n"],"mappings":";;;;;;;;AASO,yBAAMA,uBAAqB,UAAU;CAC1C,WAAW;EACT,MAAM,QAAQ,KAAK;AACnB,MAAI,EAAE,iBAAiB,SACrB,QAAO;EAIT,MAAM,MAAM,MAAM,aAAa,MAAM,IAAK,MAAc;EAIxD,MAAM,cADa,MAAM,cAAc,KACP,KAAK;EACrC,MAAM,kBAAkB;EACxB,MAAM,iBAAiB,KAAK,IAAI,IAAI,WAAW;AAE/C,MAAI,CAAC,OAAO,iBAAiB,EAC3B,QAAO;EAIT,MAAM,iBAAiB,KAAK,IAC1B,GACA,KAAK,OAAO,aAAa,MAAM,iBAAiB,GAAG,CACpD;AAED,SAAO,IAAI;mBACI,SAAS;GACpB,UAAU;GACV,MAAM;GACN,OAAO;GACP,KAAK;GACL,QAAQ;GACR,SAAS;GACT,YAAY;GACZ,KAAK;GACL,UAAU;GACX,CAAC,CAAC;UACC,MAAM,KACN,EAAE,QAAQ,gBAAgB,QACpB,IAAI;;mBAED,IAAI;;oBAEH,SAAS;GACf,QAAQ,GAAG,gBAAgB;GAC3B,OAAO,GAAG,eAAe;GACzB,WAAW;GACX,cAAc;GACd,SAAS;GACT,eAAe;GACf,YAAY;GACb,CAAC,CAAC;;UAGN,CAAC;;;;;2BAxDT,cAAc,iBAAiB"}
@@ -1,11 +1,12 @@
1
1
  import { TrackItem } from "./TrackItem.js";
2
- import * as lit40 from "lit";
2
+ import * as lit42 from "lit";
3
3
  import { TemplateResult, nothing } from "lit";
4
4
 
5
5
  //#region src/gui/timeline/tracks/TextTrack.d.ts
6
6
  declare class EFTextTrack extends TrackItem {
7
7
  #private;
8
- static styles: lit40.CSSResult[];
8
+ contextCurrentTimeMs: number;
9
+ static styles: lit42.CSSResult[];
9
10
  render(): TemplateResult<1>;
10
11
  renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing;
11
12
  }
@@ -1,48 +1,18 @@
1
+ import { currentTimeContext } from "../../currentTimeContext.js";
1
2
  import { __decorate } from "../../../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
2
3
  import { TrackItem } from "./TrackItem.js";
3
4
  import { renderTrackChildren } from "./renderTrackChildren.js";
5
+ import { consume } from "@lit/context";
4
6
  import { css, html, nothing } from "lit";
5
7
  import { customElement } from "lit/decorators.js";
6
8
  import { styleMap } from "lit/directives/style-map.js";
7
9
 
8
10
  //#region src/gui/timeline/tracks/TextTrack.ts
9
- /**
10
- * Controller to ensure text track updates reactively during playback.
11
- */
12
- var TextTimeController = class TextTimeController {
13
- #animationFrameId;
14
- #lastTimeMs = -1;
15
- static #MIN_TIME_CHANGE_MS = 50;
16
- constructor(host) {
17
- this.host = host;
18
- this.host.addController(this);
19
- }
20
- hostConnected() {
21
- this.#startTimeUpdate();
22
- }
23
- hostDisconnected() {
24
- this.#stopTimeUpdate();
25
- }
26
- #startTimeUpdate() {
27
- const update = () => {
28
- const currentTimeMs = (this.host.element?.rootTimegroup)?.currentTimeMs || 0;
29
- if (Math.abs(currentTimeMs - this.#lastTimeMs) >= TextTimeController.#MIN_TIME_CHANGE_MS) {
30
- this.#lastTimeMs = currentTimeMs;
31
- this.host.requestUpdate();
32
- }
33
- this.#animationFrameId = requestAnimationFrame(update);
34
- };
35
- update();
36
- }
37
- #stopTimeUpdate() {
38
- if (this.#animationFrameId) {
39
- cancelAnimationFrame(this.#animationFrameId);
40
- this.#animationFrameId = void 0;
41
- }
42
- }
43
- };
44
11
  let EFTextTrack = class EFTextTrack$1 extends TrackItem {
45
- #timeController = new TextTimeController(this);
12
+ constructor(..._args) {
13
+ super(..._args);
14
+ this.contextCurrentTimeMs = 0;
15
+ }
46
16
  static {
47
17
  this.styles = [...TrackItem.styles, css`
48
18
  .text-segment-block {
@@ -54,11 +24,11 @@ let EFTextTrack = class EFTextTrack$1 extends TrackItem {
54
24
  padding: 0 4px;
55
25
  border-radius: 2px;
56
26
  overflow: hidden;
57
- background: rgba(249, 115, 22, 0.15);
27
+ background: color-mix(in srgb, var(--ef-color-type-text) 15%, transparent);
58
28
  }
59
29
 
60
30
  .text-segment-block.active {
61
- background: rgba(249, 115, 22, 0.35);
31
+ background: color-mix(in srgb, var(--ef-color-type-text) 35%, transparent);
62
32
  }
63
33
 
64
34
  .text-segment-block:hover {
@@ -66,7 +36,7 @@ let EFTextTrack = class EFTextTrack$1 extends TrackItem {
66
36
  overflow: visible;
67
37
  width: max-content !important;
68
38
  min-width: max-content;
69
- background: rgba(80, 50, 30, 0.95);
39
+ background: var(--ef-color-bg-elevated);
70
40
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
71
41
  }
72
42
 
@@ -75,7 +45,7 @@ let EFTextTrack = class EFTextTrack$1 extends TrackItem {
75
45
  white-space: nowrap;
76
46
  overflow: hidden;
77
47
  text-overflow: ellipsis;
78
- color: rgba(255, 255, 255, 0.85);
48
+ color: var(--ef-color-text);
79
49
  }
80
50
 
81
51
  .text-segment-block:hover .segment-text {
@@ -100,14 +70,14 @@ let EFTextTrack = class EFTextTrack$1 extends TrackItem {
100
70
  padding: 0 4px;
101
71
  overflow: hidden;
102
72
  border-radius: 2px;
103
- background: rgba(249, 115, 22, 0.1);
73
+ background: color-mix(in srgb, var(--ef-color-type-text) 10%, transparent);
104
74
  }
105
75
 
106
76
  .text-compact-block:hover {
107
77
  overflow: visible;
108
78
  z-index: 100;
109
79
  width: max-content;
110
- background: rgba(80, 50, 30, 0.95);
80
+ background: var(--ef-color-bg-elevated);
111
81
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
112
82
  }
113
83
  `];
@@ -170,7 +140,7 @@ let EFTextTrack = class EFTextTrack$1 extends TrackItem {
170
140
  */
171
141
  #renderSegments(segments, durationMs) {
172
142
  const text = this.element;
173
- const textLocalTimeMs = (text.rootTimegroup?.currentTimeMs || 0) - text.startTimeMs;
143
+ const textLocalTimeMs = (this.contextCurrentTimeMs || 0) - text.startTimeMs;
174
144
  return segments.map((segment, index) => {
175
145
  const staggerOffset = segment.staggerOffsetMs ?? 0;
176
146
  const isActive = textLocalTimeMs >= staggerOffset;
@@ -205,6 +175,10 @@ let EFTextTrack = class EFTextTrack$1 extends TrackItem {
205
175
  return renderTrackChildren(nonSegmentChildren, this.pixelsPerMs, this.hideSelectors, this.showSelectors, false, this.enableTrim);
206
176
  }
207
177
  };
178
+ __decorate([consume({
179
+ context: currentTimeContext,
180
+ subscribe: true
181
+ })], EFTextTrack.prototype, "contextCurrentTimeMs", void 0);
208
182
  EFTextTrack = __decorate([customElement("ef-text-track")], EFTextTrack);
209
183
  let EFTextSegmentTrack = class EFTextSegmentTrack$1 extends TrackItem {
210
184
  get textTrackStyles() {
@@ -1 +1 @@
1
- {"version":3,"file":"TextTrack.js","names":["#MIN_TIME_CHANGE_MS","host: EFTextTrack","#startTimeUpdate","#stopTimeUpdate","#lastTimeMs","#animationFrameId","EFTextTrack","#getTextContent","#canShowSegmentsIndividually","#renderSegments","#renderCompactText","EFTextSegmentTrack"],"sources":["../../../../src/gui/timeline/tracks/TextTrack.ts"],"sourcesContent":["import { css, html, nothing, type TemplateResult } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport type { ReactiveController } from \"lit\";\nimport { EFText } from \"../../../elements/EFText.js\";\nimport { EFTextSegment } from \"../../../elements/EFTextSegment.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\nimport { renderTrackChildren } from \"./renderTrackChildren.js\";\n\n/**\n * Controller to ensure text track updates reactively during playback.\n */\nclass TextTimeController implements ReactiveController {\n #animationFrameId?: number;\n #lastTimeMs = -1;\n static readonly #MIN_TIME_CHANGE_MS = 50;\n \n constructor(private host: EFTextTrack) {\n this.host.addController(this);\n }\n \n hostConnected(): void {\n this.#startTimeUpdate();\n }\n \n hostDisconnected(): void {\n this.#stopTimeUpdate();\n }\n \n #startTimeUpdate(): void {\n const update = () => {\n const text = this.host.element as EFText;\n const rootTimegroup = text?.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n \n // Update if time changed significantly\n const timeDelta = Math.abs(currentTimeMs - this.#lastTimeMs);\n if (timeDelta >= TextTimeController.#MIN_TIME_CHANGE_MS) {\n this.#lastTimeMs = currentTimeMs;\n this.host.requestUpdate();\n }\n \n this.#animationFrameId = requestAnimationFrame(update);\n };\n update();\n }\n \n #stopTimeUpdate(): void {\n if (this.#animationFrameId) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#animationFrameId = undefined;\n }\n }\n}\n\n@customElement(\"ef-text-track\")\nexport class EFTextTrack extends TrackItem {\n // Controller ensures real-time updates during playback\n #timeController = new TextTimeController(this);\n static styles = [\n ...TrackItem.styles,\n css`\n .text-segment-block {\n position: absolute;\n height: calc(100% - 4px);\n top: 2px;\n display: flex;\n align-items: center;\n padding: 0 4px;\n border-radius: 2px;\n overflow: hidden;\n background: rgba(249, 115, 22, 0.15);\n }\n \n .text-segment-block.active {\n background: rgba(249, 115, 22, 0.35);\n }\n \n .text-segment-block:hover {\n z-index: 100;\n overflow: visible;\n width: max-content !important;\n min-width: max-content;\n background: rgba(80, 50, 30, 0.95);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n \n .segment-text {\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: rgba(255, 255, 255, 0.85);\n }\n \n .text-segment-block:hover .segment-text {\n overflow: visible;\n text-overflow: clip;\n }\n \n .text-segment-block.active .segment-text {\n font-weight: 500;\n color: white;\n }\n \n /* Compact mode - single block for full text */\n .text-compact-block {\n position: absolute;\n left: 4px;\n right: 4px;\n top: 2px;\n bottom: 2px;\n display: flex;\n align-items: center;\n padding: 0 4px;\n overflow: hidden;\n border-radius: 2px;\n background: rgba(249, 115, 22, 0.1);\n }\n \n .text-compact-block:hover {\n overflow: visible;\n z-index: 100;\n width: max-content;\n background: rgba(80, 50, 30, 0.95);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n `,\n ];\n\n /**\n * Get the text content - from segments, direct text nodes, or element textContent\n */\n #getTextContent(segments: EFTextSegment[]): string {\n const text = this.element as EFText;\n \n // If there are segments, use their text\n if (segments.length > 0) {\n return segments.map(s => s.segmentText).join(\" \");\n }\n \n // Try direct text nodes (excluding templates and other elements)\n const directText = Array.from(text.childNodes)\n .filter(node => node.nodeType === Node.TEXT_NODE)\n .map(node => node.textContent?.trim())\n .filter(Boolean)\n .join(\" \");\n \n if (directText) return directText;\n \n // Ultimate fallback: use the element's full text content\n // (excluding script/style content but including nested text)\n const fullText = text.textContent?.trim() || \"\";\n return fullText;\n }\n\n /**\n * Check if segments can fit individually based on track width\n */\n #canShowSegmentsIndividually(segments: EFTextSegment[], trackWidthPx: number): boolean {\n if (segments.length === 0) return false;\n // Need at least 20px per segment\n return trackWidthPx >= segments.length * 20;\n }\n\n render() {\n const text = this.element as EFText;\n const segments = Array.from(text.querySelectorAll(\"ef-text-segment\")) as EFTextSegment[];\n const textContent = this.#getTextContent(segments);\n const durationMs = text.durationMs ?? 0;\n const trackWidthPx = durationMs * this.pixelsPerMs;\n const canShowSegments = this.#canShowSegmentsIndividually(segments, trackWidthPx);\n\n return html`<div style=${styleMap(this.gutterStyles)}>\n <div\n class=\"relative\"\n style=\"background-color: var(--filmstrip-bg);\"\n ?data-focused=${this.isFocused}\n @mouseenter=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = this.element;\n }\n }}\n @mouseleave=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = null;\n }\n }}\n >\n <div\n ?data-focused=${this.isFocused}\n class=\"relative mb-[1px] block h-[1.1rem] text-nowrap border text-sm overflow-visible\"\n style=${styleMap({\n ...this.trimPortionStyles,\n backgroundColor: this.isFocused\n ? \"var(--filmstrip-item-focused)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: \"var(--filmstrip-border)\",\n borderLeftColor: this.getElementTypeColor(),\n borderLeftWidth: \"3px\",\n })}\n >\n ${segments.length > 0 && canShowSegments\n ? this.#renderSegments(segments, durationMs)\n : this.#renderCompactText(textContent)}\n </div>\n </div>\n ${this.renderChildren()}\n </div>`;\n }\n\n /**\n * Render segments as positioned blocks (like captions)\n */\n #renderSegments(segments: EFTextSegment[], durationMs: number) {\n const text = this.element as EFText;\n const rootTimegroup = text.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const textLocalTimeMs = currentTimeMs - text.startTimeMs;\n\n return segments.map((segment, index) => {\n const staggerOffset = segment.staggerOffsetMs ?? 0;\n // Segment becomes active at its stagger offset\n const isActive = textLocalTimeMs >= staggerOffset;\n \n // Calculate segment width - distribute evenly or use stagger spacing\n const nextSegment = segments[index + 1];\n const nextStagger = nextSegment?.staggerOffsetMs ?? durationMs;\n const segmentWidthMs = nextStagger - staggerOffset;\n const segmentWidthPx = Math.max(this.pixelsPerMs * segmentWidthMs, 18);\n\n return html`<div\n class=\"text-segment-block ${isActive ? \"active\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * staggerOffset}px`,\n width: `${segmentWidthPx}px`,\n })}\n title=\"${segment.segmentText}\"\n >\n <span class=\"segment-text\">${segment.segmentText}</span>\n </div>`;\n });\n }\n\n /**\n * Render compact text (no segments or not enough space)\n */\n #renderCompactText(textContent: string) {\n if (!textContent) return nothing;\n \n return html`\n <div class=\"text-compact-block\">\n <span class=\"segment-text\">${textContent}</span>\n </div>\n `;\n }\n\n renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {\n const nonSegmentChildren = Array.from(this.element.children).filter(\n (child) => child.tagName?.toUpperCase() !== \"EF-TEXT-SEGMENT\"\n );\n \n if (nonSegmentChildren.length === 0) {\n return nothing;\n }\n \n return renderTrackChildren(\n nonSegmentChildren,\n this.pixelsPerMs,\n this.hideSelectors,\n this.showSelectors,\n false,\n this.enableTrim,\n );\n }\n}\n\n@customElement(\"ef-text-segment-track\")\nexport class EFTextSegmentTrack extends TrackItem {\n get textTrackStyles() {\n const parentText = this.element.closest(\"ef-text\") as EFText;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentText?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentText?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const segment = this.element as EFTextSegment;\n const parentText = segment.closest(\"ef-text\") as EFText;\n\n if (!parentText) {\n return html`<div style=${styleMap(this.textTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentText.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const textLocalTimeMs = currentTimeMs - parentText.startTimeMs;\n\n const isCurrentlyActive =\n textLocalTimeMs >= segment.segmentStartMs &&\n textLocalTimeMs < segment.segmentEndMs;\n\n return html`<div style=${styleMap(this.textTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n <div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * segment.segmentStartMs}px`,\n width: `${this.pixelsPerMs * (segment.segmentEndMs - segment.segmentStartMs)}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Segment: '${segment.segmentText}' (${segment.segmentStartMs}ms - ${segment.segmentEndMs}ms)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-caption-bg);\">${segment.segmentText}</span>` : \"\"}\n </div>\n </div>\n </div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text-track\": EFTextTrack;\n \"ef-text-segment-track\": EFTextSegmentTrack;\n }\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"TextTrack.js","names":["EFTextTrack","#getTextContent","#canShowSegmentsIndividually","#renderSegments","#renderCompactText","EFTextSegmentTrack"],"sources":["../../../../src/gui/timeline/tracks/TextTrack.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, nothing, type TemplateResult } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { EFText } from \"../../../elements/EFText.js\";\nimport { EFTextSegment } from \"../../../elements/EFTextSegment.js\";\nimport { currentTimeContext } from \"../../currentTimeContext.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\nimport { renderTrackChildren } from \"./renderTrackChildren.js\";\n\n@customElement(\"ef-text-track\")\nexport class EFTextTrack extends TrackItem {\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = 0;\n static styles = [\n ...TrackItem.styles,\n css`\n .text-segment-block {\n position: absolute;\n height: calc(100% - 4px);\n top: 2px;\n display: flex;\n align-items: center;\n padding: 0 4px;\n border-radius: 2px;\n overflow: hidden;\n background: color-mix(in srgb, var(--ef-color-type-text) 15%, transparent);\n }\n \n .text-segment-block.active {\n background: color-mix(in srgb, var(--ef-color-type-text) 35%, transparent);\n }\n \n .text-segment-block:hover {\n z-index: 100;\n overflow: visible;\n width: max-content !important;\n min-width: max-content;\n background: var(--ef-color-bg-elevated);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n \n .segment-text {\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--ef-color-text);\n }\n \n .text-segment-block:hover .segment-text {\n overflow: visible;\n text-overflow: clip;\n }\n \n .text-segment-block.active .segment-text {\n font-weight: 500;\n color: white;\n }\n \n /* Compact mode - single block for full text */\n .text-compact-block {\n position: absolute;\n left: 4px;\n right: 4px;\n top: 2px;\n bottom: 2px;\n display: flex;\n align-items: center;\n padding: 0 4px;\n overflow: hidden;\n border-radius: 2px;\n background: color-mix(in srgb, var(--ef-color-type-text) 10%, transparent);\n }\n \n .text-compact-block:hover {\n overflow: visible;\n z-index: 100;\n width: max-content;\n background: var(--ef-color-bg-elevated);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n `,\n ];\n\n /**\n * Get the text content - from segments, direct text nodes, or element textContent\n */\n #getTextContent(segments: EFTextSegment[]): string {\n const text = this.element as EFText;\n\n // If there are segments, use their text\n if (segments.length > 0) {\n return segments.map((s) => s.segmentText).join(\" \");\n }\n\n // Try direct text nodes (excluding templates and other elements)\n const directText = Array.from(text.childNodes)\n .filter((node) => node.nodeType === Node.TEXT_NODE)\n .map((node) => node.textContent?.trim())\n .filter(Boolean)\n .join(\" \");\n\n if (directText) return directText;\n\n // Ultimate fallback: use the element's full text content\n // (excluding script/style content but including nested text)\n const fullText = text.textContent?.trim() || \"\";\n return fullText;\n }\n\n /**\n * Check if segments can fit individually based on track width\n */\n #canShowSegmentsIndividually(\n segments: EFTextSegment[],\n trackWidthPx: number,\n ): boolean {\n if (segments.length === 0) return false;\n // Need at least 20px per segment\n return trackWidthPx >= segments.length * 20;\n }\n\n render() {\n const text = this.element as EFText;\n const segments = Array.from(\n text.querySelectorAll(\"ef-text-segment\"),\n ) as EFTextSegment[];\n const textContent = this.#getTextContent(segments);\n const durationMs = text.durationMs ?? 0;\n const trackWidthPx = durationMs * this.pixelsPerMs;\n const canShowSegments = this.#canShowSegmentsIndividually(\n segments,\n trackWidthPx,\n );\n\n return html`<div style=${styleMap(this.gutterStyles)}>\n <div\n class=\"relative\"\n style=\"background-color: var(--filmstrip-bg);\"\n ?data-focused=${this.isFocused}\n @mouseenter=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = this.element;\n }\n }}\n @mouseleave=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = null;\n }\n }}\n >\n <div\n ?data-focused=${this.isFocused}\n class=\"relative mb-[1px] block h-[1.1rem] text-nowrap border text-sm overflow-visible\"\n style=${styleMap({\n ...this.trimPortionStyles,\n backgroundColor: this.isFocused\n ? \"var(--filmstrip-item-focused)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: \"var(--filmstrip-border)\",\n borderLeftColor: this.getElementTypeColor(),\n borderLeftWidth: \"3px\",\n })}\n >\n ${\n segments.length > 0 && canShowSegments\n ? this.#renderSegments(segments, durationMs)\n : this.#renderCompactText(textContent)\n }\n </div>\n </div>\n ${this.renderChildren()}\n </div>`;\n }\n\n /**\n * Render segments as positioned blocks (like captions)\n */\n #renderSegments(segments: EFTextSegment[], durationMs: number) {\n const text = this.element as EFText;\n const currentTimeMs = this.contextCurrentTimeMs || 0;\n const textLocalTimeMs = currentTimeMs - text.startTimeMs;\n\n return segments.map((segment, index) => {\n const staggerOffset = segment.staggerOffsetMs ?? 0;\n // Segment becomes active at its stagger offset\n const isActive = textLocalTimeMs >= staggerOffset;\n\n // Calculate segment width - distribute evenly or use stagger spacing\n const nextSegment = segments[index + 1];\n const nextStagger = nextSegment?.staggerOffsetMs ?? durationMs;\n const segmentWidthMs = nextStagger - staggerOffset;\n const segmentWidthPx = Math.max(this.pixelsPerMs * segmentWidthMs, 18);\n\n return html`<div\n class=\"text-segment-block ${isActive ? \"active\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * staggerOffset}px`,\n width: `${segmentWidthPx}px`,\n })}\n title=\"${segment.segmentText}\"\n >\n <span class=\"segment-text\">${segment.segmentText}</span>\n </div>`;\n });\n }\n\n /**\n * Render compact text (no segments or not enough space)\n */\n #renderCompactText(textContent: string) {\n if (!textContent) return nothing;\n\n return html`\n <div class=\"text-compact-block\">\n <span class=\"segment-text\">${textContent}</span>\n </div>\n `;\n }\n\n renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {\n const nonSegmentChildren = Array.from(this.element.children).filter(\n (child) => child.tagName?.toUpperCase() !== \"EF-TEXT-SEGMENT\",\n );\n\n if (nonSegmentChildren.length === 0) {\n return nothing;\n }\n\n return renderTrackChildren(\n nonSegmentChildren,\n this.pixelsPerMs,\n this.hideSelectors,\n this.showSelectors,\n false,\n this.enableTrim,\n );\n }\n}\n\n@customElement(\"ef-text-segment-track\")\nexport class EFTextSegmentTrack extends TrackItem {\n get textTrackStyles() {\n const parentText = this.element.closest(\"ef-text\") as EFText;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentText?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentText?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const segment = this.element as EFTextSegment;\n const parentText = segment.closest(\"ef-text\") as EFText;\n\n if (!parentText) {\n return html`<div style=${styleMap(this.textTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentText.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const textLocalTimeMs = currentTimeMs - parentText.startTimeMs;\n\n const isCurrentlyActive =\n textLocalTimeMs >= segment.segmentStartMs &&\n textLocalTimeMs < segment.segmentEndMs;\n\n return html`<div style=${styleMap(this.textTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n <div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * segment.segmentStartMs}px`,\n width: `${this.pixelsPerMs * (segment.segmentEndMs - segment.segmentStartMs)}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Segment: '${segment.segmentText}' (${segment.segmentStartMs}ms - ${segment.segmentEndMs}ms)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-caption-bg);\">${segment.segmentText}</span>` : \"\"}\n </div>\n </div>\n </div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text-track\": EFTextTrack;\n \"ef-text-segment-track\": EFTextSegmentTrack;\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,6 @@
1
1
  import { TrackItem } from "./TrackItem.js";
2
- import "../../../elements/EFThumbnailStrip.js";
2
+ import "./EFThumbnailStrip.js";
3
3
  import * as lit41 from "lit";
4
- import { nothing } from "lit";
5
4
  import * as lit_html38 from "lit-html";
6
5
 
7
6
  //#region src/gui/timeline/tracks/TimegroupTrack.d.ts
@@ -30,10 +29,10 @@ declare class EFTimegroupTrack extends TrackItem {
30
29
  } | {
31
30
  height: string;
32
31
  width: string;
33
- minWidth: string;
34
32
  left: string;
35
33
  };
36
- contents(): lit_html38.TemplateResult<1> | typeof nothing;
34
+ private get modeLabel();
35
+ contents(): lit_html38.TemplateResult<1>;
37
36
  /**
38
37
  * Override render to use taller height for filmstrip rows
39
38
  */
@@ -1,8 +1,8 @@
1
1
  import { __decorate } from "../../../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
2
2
  import { TrackItem } from "./TrackItem.js";
3
- import "../../../elements/EFThumbnailStrip.js";
3
+ import "./EFThumbnailStrip.js";
4
4
  import { renderTrackChildren } from "./renderTrackChildren.js";
5
- import { css, html, nothing } from "lit";
5
+ import { css, html } from "lit";
6
6
  import { customElement, property } from "lit/decorators.js";
7
7
  import { styleMap } from "lit/directives/style-map.js";
8
8
 
@@ -36,16 +36,16 @@ let EFTimegroupTrack = class EFTimegroupTrack$1 extends TrackItem {
36
36
  .trim-container {
37
37
  background: linear-gradient(
38
38
  135deg,
39
- rgba(148, 163, 184, 0.1) 0%,
40
- rgba(148, 163, 184, 0.05) 100%
39
+ color-mix(in srgb, var(--ef-color-type-timegroup) 10%, transparent) 0%,
40
+ color-mix(in srgb, var(--ef-color-type-timegroup) 5%, transparent) 100%
41
41
  ) !important;
42
42
  }
43
43
 
44
44
  :host(:hover) .trim-container {
45
45
  background: linear-gradient(
46
46
  135deg,
47
- rgba(148, 163, 184, 0.15) 0%,
48
- rgba(148, 163, 184, 0.08) 100%
47
+ color-mix(in srgb, var(--ef-color-type-timegroup) 15%, transparent) 0%,
48
+ color-mix(in srgb, var(--ef-color-type-timegroup) 8%, transparent) 100%
49
49
  ) !important;
50
50
  }
51
51
  `];
@@ -65,26 +65,36 @@ let EFTimegroupTrack = class EFTimegroupTrack$1 extends TrackItem {
65
65
  */
66
66
  get trimPortionStyles() {
67
67
  const baseStyles = super.trimPortionStyles;
68
- if (this.shouldShowFilmstrip) {
69
- const durationMs = this.element.durationMs ?? 0;
70
- const calculatedWidth = this.pixelsPerMs * durationMs;
71
- const minWidth = Math.max(calculatedWidth, 500);
72
- return {
73
- ...baseStyles,
74
- height: `${FILMSTRIP_ROW_HEIGHT}px`,
75
- width: `${minWidth}px`,
76
- minWidth: `${minWidth}px`
77
- };
78
- }
68
+ if (this.shouldShowFilmstrip) return {
69
+ ...baseStyles,
70
+ height: `${FILMSTRIP_ROW_HEIGHT}px`
71
+ };
79
72
  return baseStyles;
80
73
  }
74
+ get modeLabel() {
75
+ const mode = this.element.mode || "fixed";
76
+ return {
77
+ fixed: "Fixed",
78
+ sequence: "Sequence",
79
+ contain: "Container",
80
+ fit: "Fit"
81
+ }[mode] || mode;
82
+ }
81
83
  contents() {
82
- if (this.shouldShowFilmstrip) {
83
- this.element.durationMs;
84
- if (!this.element.id) return nothing;
85
- return nothing;
86
- }
87
- if (this.skipChildren) return nothing;
84
+ if (this.shouldShowFilmstrip) return html`<ef-thumbnail-strip
85
+ .targetElement=${this.element}
86
+ thumbnail-height=${FILMSTRIP_ROW_HEIGHT}
87
+ thumbnail-spacing-px="96"
88
+ pixels-per-ms=${this.pixelsPerMs}
89
+ ></ef-thumbnail-strip>`;
90
+ if (this.skipChildren) return html`<span style="
91
+ font-size: 9px;
92
+ opacity: 0.5;
93
+ padding-left: 4px;
94
+ line-height: 14px;
95
+ pointer-events: none;
96
+ white-space: nowrap;
97
+ ">${this.modeLabel}</span>`;
88
98
  return html`${renderTrackChildren(Array.from(this.element.children || []), this.pixelsPerMs, this.hideSelectors, this.showSelectors, false, this.enableTrim)}`;
89
99
  }
90
100
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"TimegroupTrack.js","names":["EFTimegroupTrack"],"sources":["../../../../src/gui/timeline/tracks/TimegroupTrack.ts"],"sourcesContent":["import { css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\nimport { renderTrackChildren } from \"./renderTrackChildren.js\";\nimport \"../../../elements/EFThumbnailStrip.js\";\n\n/**\n * Check if a timegroup is a root timegroup (has no parent timegroup)\n * Uses the timegroup's own isRootTimegroup property for reliability\n */\nfunction isRootTimegroup(element: Element | null | undefined): boolean {\n // Handle null/undefined\n if (!element) {\n return false;\n }\n \n // Check if element has the isRootTimegroup property (most reliable)\n // EFTimegroup instances have this property that checks parentTimegroup\n const elem = element as any;\n if (typeof elem.isRootTimegroup === 'boolean') {\n return elem.isRootTimegroup;\n }\n \n // Alternative: check parentTimegroup property directly (EFTimegroup has this)\n if (elem.parentTimegroup !== undefined) {\n return !elem.parentTimegroup; // Root if no parent timegroup\n }\n \n // Fallback: check DOM parent tree (less reliable after DOM moves)\n let parent = element.parentElement;\n while (parent) {\n if (parent.tagName.toLowerCase() === \"ef-timegroup\") {\n return false;\n }\n parent = parent.parentElement;\n }\n return true;\n}\n\n/** Height for root timegroup filmstrip row */\nconst FILMSTRIP_ROW_HEIGHT = 48;\n\n@customElement(\"ef-timegroup-track\")\nexport class EFTimegroupTrack extends TrackItem {\n static styles = [\n ...TrackItem.styles,\n css`\n .trim-container {\n background: linear-gradient(\n 135deg,\n rgba(148, 163, 184, 0.1) 0%,\n rgba(148, 163, 184, 0.05) 100%\n ) !important;\n }\n \n :host(:hover) .trim-container {\n background: linear-gradient(\n 135deg,\n rgba(148, 163, 184, 0.15) 0%,\n rgba(148, 163, 184, 0.08) 100%\n ) !important;\n }\n `,\n ];\n\n /**\n * When true, children are not rendered (used in unified row architecture\n * where children get their own rows).\n */\n @property({ type: Boolean, attribute: \"skip-children\" })\n skipChildren = false;\n\n /**\n * When true, show filmstrip thumbnails for root timegroups\n * TODO: Re-enable when thumbnail strip performance is improved\n */\n @property({ type: Boolean, attribute: \"show-filmstrip\" })\n showFilmstrip = false;\n\n /**\n * Check if this track should show a filmstrip\n */\n private get shouldShowFilmstrip(): boolean {\n const skipChildren = this.skipChildren;\n const showFilmstrip = this.showFilmstrip;\n const hasId = !!this.element?.id;\n const isRoot = isRootTimegroup(this.element);\n \n return skipChildren && showFilmstrip && hasId && isRoot;\n }\n\n /**\n * Override trimPortionStyles to use taller height for filmstrip rows\n */\n override get trimPortionStyles() {\n const baseStyles = super.trimPortionStyles;\n if (this.shouldShowFilmstrip) {\n // Ensure minimum width for filmstrip tracks so thumbnail strip can render\n // even when duration is 0 (e.g., sequence mode timegroups before children load)\n const durationMs = this.element.durationMs ?? 0;\n const calculatedWidth = this.pixelsPerMs * durationMs;\n const minWidth = Math.max(calculatedWidth, 500); // Minimum 500px for thumbnail strip visibility\n \n return {\n ...baseStyles,\n height: `${FILMSTRIP_ROW_HEIGHT}px`,\n width: `${minWidth}px`,\n minWidth: `${minWidth}px`, // Ensure minimum width is enforced\n };\n }\n return baseStyles;\n }\n\n contents() {\n // Show filmstrip only for ROOT timegroups (no parent timegroup)\n const shouldShow = this.shouldShowFilmstrip;\n \n if (shouldShow) {\n // Don't set end-time-ms if duration is 0 - let thumbnail strip use target's duration directly\n // This allows the thumbnail strip to watch for duration changes and update automatically\n const durationMs = this.element.durationMs ?? 0;\n const elementId = this.element.id;\n \n if (!elementId) {\n return nothing;\n }\n \n return nothing;\n return html`\n <ef-thumbnail-strip\n target=\"${elementId}\"\n start-time-ms=\"0\"\n ${durationMs > 0 ? html`end-time-ms=\"${durationMs}\"` : nothing}\n pixels-per-ms=\"${this.pixelsPerMs}\"\n ></ef-thumbnail-strip>\n `;\n }\n\n // Mode info is now shown in the label, track is empty for non-root timegroups\n if (this.skipChildren) {\n return nothing;\n }\n // Wrap children in a fragment for consistent return type\n return html`${renderTrackChildren(\n Array.from(this.element.children || []),\n this.pixelsPerMs,\n this.hideSelectors,\n this.showSelectors,\n false,\n this.enableTrim,\n )}`;\n }\n\n /**\n * Override render to use taller height for filmstrip rows\n */\n override render() {\n // Use custom height for filmstrip, standard height otherwise\n const trackHeight = this.shouldShowFilmstrip \n ? `${FILMSTRIP_ROW_HEIGHT}px` \n : \"var(--timeline-track-height, 22px)\";\n\n return html`<div style=${styleMap(this.gutterStyles)}>\n <div\n style=\"background-color: var(--filmstrip-bg);\"\n ?data-focused=${this.isFocused}\n @mouseenter=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = this.element;\n }\n }}\n @mouseleave=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = null;\n }\n }}\n >\n <div\n ?data-focused=${this.isFocused}\n class=\"trim-container relative mb-0 block text-nowrap border text-sm\"\n style=${styleMap({\n ...this.trimPortionStyles,\n height: trackHeight,\n backgroundColor: this.isFocused\n ? \"var(--filmstrip-item-focused)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: this.shouldShowFilmstrip ? \"transparent\" : \"var(--filmstrip-border)\",\n })}\n >\n ${this.animations()}\n ${this.contents()}\n </div>\n </div>\n ${this.renderChildren()}\n </div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timegroup-track\": EFTimegroupTrack;\n }\n}\n\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAS,gBAAgB,SAA8C;AAErE,KAAI,CAAC,QACH,QAAO;CAKT,MAAM,OAAO;AACb,KAAI,OAAO,KAAK,oBAAoB,UAClC,QAAO,KAAK;AAId,KAAI,KAAK,oBAAoB,OAC3B,QAAO,CAAC,KAAK;CAIf,IAAI,SAAS,QAAQ;AACrB,QAAO,QAAQ;AACb,MAAI,OAAO,QAAQ,aAAa,KAAK,eACnC,QAAO;AAET,WAAS,OAAO;;AAElB,QAAO;;;AAIT,MAAM,uBAAuB;AAGtB,6BAAMA,2BAAyB,UAAU;;;sBA2B/B;uBAOC;;;gBAjCA,CACd,GAAG,UAAU,QACb,GAAG;;;;;;;;;;;;;;;;MAiBJ;;;;;CAmBD,IAAY,sBAA+B;EACzC,MAAM,eAAe,KAAK;EAC1B,MAAM,gBAAgB,KAAK;EAC3B,MAAM,QAAQ,CAAC,CAAC,KAAK,SAAS;EAC9B,MAAM,SAAS,gBAAgB,KAAK,QAAQ;AAE5C,SAAO,gBAAgB,iBAAiB,SAAS;;;;;CAMnD,IAAa,oBAAoB;EAC/B,MAAM,aAAa,MAAM;AACzB,MAAI,KAAK,qBAAqB;GAG5B,MAAM,aAAa,KAAK,QAAQ,cAAc;GAC9C,MAAM,kBAAkB,KAAK,cAAc;GAC3C,MAAM,WAAW,KAAK,IAAI,iBAAiB,IAAI;AAE/C,UAAO;IACL,GAAG;IACH,QAAQ,GAAG,qBAAqB;IAChC,OAAO,GAAG,SAAS;IACnB,UAAU,GAAG,SAAS;IACvB;;AAEH,SAAO;;CAGT,WAAW;AAIT,MAFmB,KAAK,qBAER;AAGK,QAAK,QAAQ;AAGhC,OAAI,CAFc,KAAK,QAAQ,GAG7B,QAAO;AAGT,UAAO;;AAYT,MAAI,KAAK,aACP,QAAO;AAGT,SAAO,IAAI,GAAG,oBACZ,MAAM,KAAK,KAAK,QAAQ,YAAY,EAAE,CAAC,EACvC,KAAK,aACL,KAAK,eACL,KAAK,eACL,OACA,KAAK,WACN;;;;;CAMH,AAAS,SAAS;EAEhB,MAAM,cAAc,KAAK,sBACrB,GAAG,qBAAqB,MACxB;AAEJ,SAAO,IAAI,cAAc,SAAS,KAAK,aAAa,CAAC;;;wBAGjC,KAAK,UAAU;4BACX;AAClB,OAAI,KAAK,aACP,MAAK,aAAa,iBAAiB,KAAK;IAE1C;4BACkB;AAClB,OAAI,KAAK,aACP,MAAK,aAAa,iBAAiB;IAErC;;;0BAGgB,KAAK,UAAU;;kBAEvB,SAAS;GACf,GAAG,KAAK;GACR,QAAQ;GACR,iBAAiB,KAAK,YAClB,kCACA;GACJ,aAAa,KAAK,sBAAsB,gBAAgB;GACzD,CAAC,CAAC;;YAED,KAAK,YAAY,CAAC;YAClB,KAAK,UAAU,CAAC;;;QAGpB,KAAK,gBAAgB,CAAC;;;;YA5H3B,SAAS;CAAE,MAAM;CAAS,WAAW;CAAiB,CAAC;YAOvD,SAAS;CAAE,MAAM;CAAS,WAAW;CAAkB,CAAC;+BAlC1D,cAAc,qBAAqB"}
1
+ {"version":3,"file":"TimegroupTrack.js","names":["EFTimegroupTrack"],"sources":["../../../../src/gui/timeline/tracks/TimegroupTrack.ts"],"sourcesContent":["import { css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\nimport { renderTrackChildren } from \"./renderTrackChildren.js\";\nimport \"./EFThumbnailStrip.js\";\n\n/**\n * Check if a timegroup is a root timegroup (has no parent timegroup)\n * Uses the timegroup's own isRootTimegroup property for reliability\n */\nfunction isRootTimegroup(element: Element | null | undefined): boolean {\n // Handle null/undefined\n if (!element) {\n return false;\n }\n\n // Check if element has the isRootTimegroup property (most reliable)\n // EFTimegroup instances have this property that checks parentTimegroup\n const elem = element as any;\n if (typeof elem.isRootTimegroup === \"boolean\") {\n return elem.isRootTimegroup;\n }\n\n // Alternative: check parentTimegroup property directly (EFTimegroup has this)\n if (elem.parentTimegroup !== undefined) {\n return !elem.parentTimegroup; // Root if no parent timegroup\n }\n\n // Fallback: check DOM parent tree (less reliable after DOM moves)\n let parent = element.parentElement;\n while (parent) {\n if (parent.tagName.toLowerCase() === \"ef-timegroup\") {\n return false;\n }\n parent = parent.parentElement;\n }\n return true;\n}\n\n/** Height for root timegroup filmstrip row */\nconst FILMSTRIP_ROW_HEIGHT = 48;\n\n@customElement(\"ef-timegroup-track\")\nexport class EFTimegroupTrack extends TrackItem {\n static styles = [\n ...TrackItem.styles,\n css`\n .trim-container {\n background: linear-gradient(\n 135deg,\n color-mix(in srgb, var(--ef-color-type-timegroup) 10%, transparent) 0%,\n color-mix(in srgb, var(--ef-color-type-timegroup) 5%, transparent) 100%\n ) !important;\n }\n \n :host(:hover) .trim-container {\n background: linear-gradient(\n 135deg,\n color-mix(in srgb, var(--ef-color-type-timegroup) 15%, transparent) 0%,\n color-mix(in srgb, var(--ef-color-type-timegroup) 8%, transparent) 100%\n ) !important;\n }\n `,\n ];\n\n /**\n * When true, children are not rendered (used in unified row architecture\n * where children get their own rows).\n */\n @property({ type: Boolean, attribute: \"skip-children\" })\n skipChildren = false;\n\n /**\n * When true, show filmstrip thumbnails for root timegroups\n * TODO: Re-enable when thumbnail strip performance is improved\n */\n @property({ type: Boolean, attribute: \"show-filmstrip\" })\n showFilmstrip = false;\n\n /**\n * Check if this track should show a filmstrip\n */\n private get shouldShowFilmstrip(): boolean {\n const skipChildren = this.skipChildren;\n const showFilmstrip = this.showFilmstrip;\n const hasId = !!this.element?.id;\n const isRoot = isRootTimegroup(this.element);\n\n return skipChildren && showFilmstrip && hasId && isRoot;\n }\n\n /**\n * Override trimPortionStyles to use taller height for filmstrip rows\n */\n override get trimPortionStyles() {\n const baseStyles = super.trimPortionStyles;\n if (this.shouldShowFilmstrip) {\n return {\n ...baseStyles,\n height: `${FILMSTRIP_ROW_HEIGHT}px`,\n };\n }\n return baseStyles;\n }\n\n private get modeLabel(): string {\n const mode = (this.element as any).mode || \"fixed\";\n const labels: Record<string, string> = {\n fixed: \"Fixed\",\n sequence: \"Sequence\",\n contain: \"Container\",\n fit: \"Fit\",\n };\n return labels[mode] || mode;\n }\n\n contents() {\n // Show filmstrip only for ROOT timegroups (no parent timegroup)\n const shouldShow = this.shouldShowFilmstrip;\n\n if (shouldShow) {\n return html`<ef-thumbnail-strip\n .targetElement=${this.element}\n thumbnail-height=${FILMSTRIP_ROW_HEIGHT}\n thumbnail-spacing-px=\"96\"\n pixels-per-ms=${this.pixelsPerMs}\n ></ef-thumbnail-strip>`;\n }\n\n if (this.skipChildren) {\n return html`<span style=\"\n font-size: 9px;\n opacity: 0.5;\n padding-left: 4px;\n line-height: 14px;\n pointer-events: none;\n white-space: nowrap;\n \">${this.modeLabel}</span>`;\n }\n // Wrap children in a fragment for consistent return type\n // Note: This hierarchical rendering path is only used in tests/sandboxes.\n // Production code always uses skipChildren=true with flat row architecture.\n return html`${renderTrackChildren(\n Array.from(this.element.children || []),\n this.pixelsPerMs,\n this.hideSelectors,\n this.showSelectors,\n false,\n this.enableTrim,\n )}`;\n }\n\n /**\n * Override render to use taller height for filmstrip rows\n */\n override render() {\n const trackHeight = this.shouldShowFilmstrip\n ? `${FILMSTRIP_ROW_HEIGHT}px`\n : \"var(--timeline-track-height, 22px)\";\n\n return html`<div style=${styleMap(this.gutterStyles)}>\n <div\n style=\"background-color: var(--filmstrip-bg);\"\n ?data-focused=${this.isFocused}\n @mouseenter=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = this.element;\n }\n }}\n @mouseleave=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = null;\n }\n }}\n >\n <div\n ?data-focused=${this.isFocused}\n class=\"trim-container relative mb-0 block text-nowrap border text-sm\"\n style=${styleMap({\n ...this.trimPortionStyles,\n height: trackHeight,\n backgroundColor: this.isFocused\n ? \"var(--filmstrip-item-focused)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: this.shouldShowFilmstrip\n ? \"transparent\"\n : \"var(--filmstrip-border)\",\n })}\n >\n ${this.animations()}\n ${this.contents()}\n </div>\n </div>\n ${this.renderChildren()}\n </div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timegroup-track\": EFTimegroupTrack;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAS,gBAAgB,SAA8C;AAErE,KAAI,CAAC,QACH,QAAO;CAKT,MAAM,OAAO;AACb,KAAI,OAAO,KAAK,oBAAoB,UAClC,QAAO,KAAK;AAId,KAAI,KAAK,oBAAoB,OAC3B,QAAO,CAAC,KAAK;CAIf,IAAI,SAAS,QAAQ;AACrB,QAAO,QAAQ;AACb,MAAI,OAAO,QAAQ,aAAa,KAAK,eACnC,QAAO;AAET,WAAS,OAAO;;AAElB,QAAO;;;AAIT,MAAM,uBAAuB;AAGtB,6BAAMA,2BAAyB,UAAU;;;sBA2B/B;uBAOC;;;gBAjCA,CACd,GAAG,UAAU,QACb,GAAG;;;;;;;;;;;;;;;;MAiBJ;;;;;CAmBD,IAAY,sBAA+B;EACzC,MAAM,eAAe,KAAK;EAC1B,MAAM,gBAAgB,KAAK;EAC3B,MAAM,QAAQ,CAAC,CAAC,KAAK,SAAS;EAC9B,MAAM,SAAS,gBAAgB,KAAK,QAAQ;AAE5C,SAAO,gBAAgB,iBAAiB,SAAS;;;;;CAMnD,IAAa,oBAAoB;EAC/B,MAAM,aAAa,MAAM;AACzB,MAAI,KAAK,oBACP,QAAO;GACL,GAAG;GACH,QAAQ,GAAG,qBAAqB;GACjC;AAEH,SAAO;;CAGT,IAAY,YAAoB;EAC9B,MAAM,OAAQ,KAAK,QAAgB,QAAQ;AAO3C,SANuC;GACrC,OAAO;GACP,UAAU;GACV,SAAS;GACT,KAAK;GACN,CACa,SAAS;;CAGzB,WAAW;AAIT,MAFmB,KAAK,oBAGtB,QAAO,IAAI;yBACQ,KAAK,QAAQ;2BACX,qBAAqB;;wBAExB,KAAK,YAAY;;AAIrC,MAAI,KAAK,aACP,QAAO,IAAI;;;;;;;UAOP,KAAK,UAAU;AAKrB,SAAO,IAAI,GAAG,oBACZ,MAAM,KAAK,KAAK,QAAQ,YAAY,EAAE,CAAC,EACvC,KAAK,aACL,KAAK,eACL,KAAK,eACL,OACA,KAAK,WACN;;;;;CAMH,AAAS,SAAS;EAChB,MAAM,cAAc,KAAK,sBACrB,GAAG,qBAAqB,MACxB;AAEJ,SAAO,IAAI,cAAc,SAAS,KAAK,aAAa,CAAC;;;wBAGjC,KAAK,UAAU;4BACX;AAClB,OAAI,KAAK,aACP,MAAK,aAAa,iBAAiB,KAAK;IAE1C;4BACkB;AAClB,OAAI,KAAK,aACP,MAAK,aAAa,iBAAiB;IAErC;;;0BAGgB,KAAK,UAAU;;kBAEvB,SAAS;GACf,GAAG,KAAK;GACR,QAAQ;GACR,iBAAiB,KAAK,YAClB,kCACA;GACJ,aAAa,KAAK,sBACd,gBACA;GACL,CAAC,CAAC;;YAED,KAAK,YAAY,CAAC;YAClB,KAAK,UAAU,CAAC;;;QAGpB,KAAK,gBAAgB,CAAC;;;;YA5H3B,SAAS;CAAE,MAAM;CAAS,WAAW;CAAiB,CAAC;YAOvD,SAAS;CAAE,MAAM;CAAS,WAAW;CAAkB,CAAC;+BAlC1D,cAAc,qBAAqB"}
@@ -1,7 +1,7 @@
1
1
  import { TemporalMixinInterface } from "../../../elements/EFTemporal.js";
2
2
  import { FocusContext } from "../../focusContext.js";
3
3
  import { TrimChangeDetail } from "../TrimHandles.js";
4
- import * as lit43 from "lit";
4
+ import * as lit44 from "lit";
5
5
  import { LitElement, PropertyValueMap, ReactiveController, TemplateResult, nothing } from "lit";
6
6
 
7
7
  //#region src/gui/timeline/tracks/TrackItem.d.ts
@@ -19,7 +19,7 @@ declare class ElementTrackController implements ReactiveController {
19
19
  }
20
20
  declare const TrackItem_base: typeof LitElement;
21
21
  declare class TrackItem extends TrackItem_base {
22
- static styles: lit43.CSSResult[];
22
+ static styles: lit44.CSSResult[];
23
23
  focusContext?: FocusContext;
24
24
  focusedElement?: HTMLElement | null;
25
25
  get isFocused(): boolean;
@@ -28,9 +28,13 @@ declare class TrackItem extends TrackItem_base {
28
28
  */
29
29
  protected getElementType(): "video" | "audio" | "image" | "text" | "timegroup" | "captions" | "unknown";
30
30
  /**
31
- * Get color for element type
31
+ * Get color for element type using shared theme utility
32
32
  */
33
33
  protected getElementTypeColor(): string;
34
+ /**
35
+ * @deprecated Use getElementTypeColor() instead
36
+ */
37
+ protected getElementTypeColorOld(): string;
34
38
  /**
35
39
  * Get icon for element type
36
40
  */
@@ -48,12 +52,6 @@ declare class TrackItem extends TrackItem_base {
48
52
  enableTrim: boolean;
49
53
  hideSelectors?: string[];
50
54
  showSelectors?: string[];
51
- /**
52
- * When true, positions the track at the element's absolute start time
53
- * (startTimeMs) rather than relative to parent (startTimeWithinParentMs).
54
- * Used for flat row architectures where each element gets its own row.
55
- */
56
- useAbsolutePosition: boolean;
57
55
  get gutterStyles(): {
58
56
  position: string;
59
57
  left: string;