@editframe/elements 0.30.1-beta.0 → 0.31.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/dist/EF_FRAMEGEN.d.ts +5 -0
  2. package/dist/EF_FRAMEGEN.js +20 -4
  3. package/dist/EF_FRAMEGEN.js.map +1 -1
  4. package/dist/EF_INTERACTIVE.js.map +1 -1
  5. package/dist/_virtual/rolldown_runtime.js +27 -0
  6. package/dist/canvas/EFCanvas.d.ts +311 -0
  7. package/dist/canvas/EFCanvas.js +1089 -0
  8. package/dist/canvas/EFCanvas.js.map +1 -0
  9. package/dist/canvas/EFCanvasItem.d.ts +55 -0
  10. package/dist/canvas/EFCanvasItem.js +72 -0
  11. package/dist/canvas/EFCanvasItem.js.map +1 -0
  12. package/dist/canvas/api/CanvasAPI.d.ts +115 -0
  13. package/dist/canvas/api/CanvasAPI.js +182 -0
  14. package/dist/canvas/api/CanvasAPI.js.map +1 -0
  15. package/dist/canvas/api/types.d.ts +42 -0
  16. package/dist/canvas/coordinateTransform.js +90 -0
  17. package/dist/canvas/coordinateTransform.js.map +1 -0
  18. package/dist/canvas/getElementBounds.js +40 -0
  19. package/dist/canvas/getElementBounds.js.map +1 -0
  20. package/dist/canvas/overlays/SelectionOverlay.js +265 -0
  21. package/dist/canvas/overlays/SelectionOverlay.js.map +1 -0
  22. package/dist/canvas/overlays/overlayState.js +153 -0
  23. package/dist/canvas/overlays/overlayState.js.map +1 -0
  24. package/dist/canvas/selection/SelectionController.js +105 -0
  25. package/dist/canvas/selection/SelectionController.js.map +1 -0
  26. package/dist/canvas/selection/SelectionModel.d.ts +98 -0
  27. package/dist/canvas/selection/SelectionModel.js +229 -0
  28. package/dist/canvas/selection/SelectionModel.js.map +1 -0
  29. package/dist/canvas/selection/selectionContext.d.ts +31 -0
  30. package/dist/canvas/selection/selectionContext.js +12 -0
  31. package/dist/canvas/selection/selectionContext.js.map +1 -0
  32. package/dist/elements/ContainerInfo.d.ts +29 -0
  33. package/dist/elements/ContainerInfo.js +30 -0
  34. package/dist/elements/ContainerInfo.js.map +1 -0
  35. package/dist/elements/EFAudio.d.ts +13 -3
  36. package/dist/elements/EFAudio.js +64 -10
  37. package/dist/elements/EFAudio.js.map +1 -1
  38. package/dist/elements/EFCaptions.d.ts +18 -16
  39. package/dist/elements/EFCaptions.js +110 -19
  40. package/dist/elements/EFCaptions.js.map +1 -1
  41. package/dist/elements/EFImage.d.ts +16 -6
  42. package/dist/elements/EFImage.js +79 -9
  43. package/dist/elements/EFImage.js.map +1 -1
  44. package/dist/elements/EFMedia/AssetIdMediaEngine.js +51 -4
  45. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
  46. package/dist/elements/EFMedia/AssetMediaEngine.js +125 -52
  47. package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
  48. package/dist/elements/EFMedia/BaseMediaEngine.js +24 -6
  49. package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
  50. package/dist/elements/EFMedia/JitMediaEngine.js +12 -8
  51. package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
  52. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +46 -7
  53. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +1 -1
  54. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +98 -73
  55. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +1 -1
  56. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +28 -5
  57. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +1 -1
  58. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +18 -6
  59. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +1 -1
  60. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +8 -2
  61. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +1 -1
  62. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +31 -6
  63. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +1 -1
  64. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +28 -5
  65. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +1 -1
  66. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +97 -72
  67. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +1 -1
  68. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -1
  69. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
  70. package/dist/elements/EFMedia/shared/BufferUtils.js +1 -1
  71. package/dist/elements/EFMedia/shared/BufferUtils.js.map +1 -1
  72. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +25 -14
  73. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
  74. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +47 -16
  75. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +1 -1
  76. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +37 -19
  77. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
  78. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +65 -21
  79. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
  80. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +8 -3
  81. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +1 -1
  82. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +32 -9
  83. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +1 -1
  84. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +33 -10
  85. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +1 -1
  86. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +23 -8
  87. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +1 -1
  88. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +34 -10
  89. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +1 -1
  90. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +31 -8
  91. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +1 -1
  92. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +31 -114
  93. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +1 -1
  94. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +44 -8
  95. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +1 -1
  96. package/dist/elements/EFMedia.d.ts +18 -7
  97. package/dist/elements/EFMedia.js +23 -3
  98. package/dist/elements/EFMedia.js.map +1 -1
  99. package/dist/elements/EFPanZoom.d.ts +96 -0
  100. package/dist/elements/EFPanZoom.js +290 -0
  101. package/dist/elements/EFPanZoom.js.map +1 -0
  102. package/dist/elements/EFSourceMixin.js +7 -6
  103. package/dist/elements/EFSourceMixin.js.map +1 -1
  104. package/dist/elements/EFSurface.d.ts +6 -6
  105. package/dist/elements/EFSurface.js +7 -2
  106. package/dist/elements/EFSurface.js.map +1 -1
  107. package/dist/elements/EFTemporal.d.ts +2 -1
  108. package/dist/elements/EFTemporal.js +192 -71
  109. package/dist/elements/EFTemporal.js.map +1 -1
  110. package/dist/elements/EFText.d.ts +5 -4
  111. package/dist/elements/EFText.js +102 -13
  112. package/dist/elements/EFText.js.map +1 -1
  113. package/dist/elements/EFTextSegment.d.ts +32 -6
  114. package/dist/elements/EFTextSegment.js +53 -15
  115. package/dist/elements/EFTextSegment.js.map +1 -1
  116. package/dist/elements/EFThumbnailStrip.d.ts +118 -56
  117. package/dist/elements/EFThumbnailStrip.js +522 -358
  118. package/dist/elements/EFThumbnailStrip.js.map +1 -1
  119. package/dist/elements/EFTimegroup.d.ts +223 -27
  120. package/dist/elements/EFTimegroup.js +851 -148
  121. package/dist/elements/EFTimegroup.js.map +1 -1
  122. package/dist/elements/EFVideo.d.ts +42 -5
  123. package/dist/elements/EFVideo.js +165 -11
  124. package/dist/elements/EFVideo.js.map +1 -1
  125. package/dist/elements/EFWaveform.d.ts +6 -6
  126. package/dist/elements/EFWaveform.js +2 -1
  127. package/dist/elements/EFWaveform.js.map +1 -1
  128. package/dist/elements/ElementPositionInfo.d.ts +35 -0
  129. package/dist/elements/ElementPositionInfo.js +49 -0
  130. package/dist/elements/ElementPositionInfo.js.map +1 -0
  131. package/dist/elements/FetchMixin.js +16 -1
  132. package/dist/elements/FetchMixin.js.map +1 -1
  133. package/dist/elements/SessionThumbnailCache.js +152 -0
  134. package/dist/elements/SessionThumbnailCache.js.map +1 -0
  135. package/dist/elements/TargetController.js +3 -1
  136. package/dist/elements/TargetController.js.map +1 -1
  137. package/dist/elements/TimegroupController.js +9 -3
  138. package/dist/elements/TimegroupController.js.map +1 -1
  139. package/dist/elements/findRootTemporal.js +30 -0
  140. package/dist/elements/findRootTemporal.js.map +1 -0
  141. package/dist/elements/renderTemporalAudio.js +18 -5
  142. package/dist/elements/renderTemporalAudio.js.map +1 -1
  143. package/dist/elements/updateAnimations.js +492 -109
  144. package/dist/elements/updateAnimations.js.map +1 -1
  145. package/dist/getRenderInfo.d.ts +2 -2
  146. package/dist/gui/ContextMixin.js +4 -2
  147. package/dist/gui/ContextMixin.js.map +1 -1
  148. package/dist/gui/Controllable.js +74 -1
  149. package/dist/gui/Controllable.js.map +1 -1
  150. package/dist/gui/EFActiveRootTemporal.d.ts +50 -0
  151. package/dist/gui/EFActiveRootTemporal.js +94 -0
  152. package/dist/gui/EFActiveRootTemporal.js.map +1 -0
  153. package/dist/gui/EFConfiguration.d.ts +11 -5
  154. package/dist/gui/EFConfiguration.js.map +1 -1
  155. package/dist/gui/EFControls.d.ts +2 -2
  156. package/dist/gui/EFControls.js +109 -13
  157. package/dist/gui/EFControls.js.map +1 -1
  158. package/dist/gui/EFDial.d.ts +4 -4
  159. package/dist/gui/EFFilmstrip.d.ts +11 -214
  160. package/dist/gui/EFFilmstrip.js +53 -1152
  161. package/dist/gui/EFFilmstrip.js.map +1 -1
  162. package/dist/gui/EFFitScale.d.ts +3 -3
  163. package/dist/gui/EFFitScale.js +39 -12
  164. package/dist/gui/EFFitScale.js.map +1 -1
  165. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  166. package/dist/gui/EFOverlayItem.d.ts +48 -0
  167. package/dist/gui/EFOverlayItem.js +97 -0
  168. package/dist/gui/EFOverlayItem.js.map +1 -0
  169. package/dist/gui/EFOverlayLayer.d.ts +70 -0
  170. package/dist/gui/EFOverlayLayer.js +104 -0
  171. package/dist/gui/EFOverlayLayer.js.map +1 -0
  172. package/dist/gui/EFPause.d.ts +4 -4
  173. package/dist/gui/EFPlay.d.ts +4 -4
  174. package/dist/gui/EFPreview.d.ts +4 -4
  175. package/dist/gui/EFResizableBox.d.ts +12 -16
  176. package/dist/gui/EFResizableBox.js +109 -451
  177. package/dist/gui/EFResizableBox.js.map +1 -1
  178. package/dist/gui/EFScrubber.d.ts +30 -5
  179. package/dist/gui/EFScrubber.js +224 -31
  180. package/dist/gui/EFScrubber.js.map +1 -1
  181. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  182. package/dist/gui/EFTimeDisplay.js +4 -1
  183. package/dist/gui/EFTimeDisplay.js.map +1 -1
  184. package/dist/gui/EFTimelineRuler.d.ts +71 -0
  185. package/dist/gui/EFTimelineRuler.js +320 -0
  186. package/dist/gui/EFTimelineRuler.js.map +1 -0
  187. package/dist/gui/EFToggleLoop.d.ts +4 -4
  188. package/dist/gui/EFTogglePlay.d.ts +4 -4
  189. package/dist/gui/EFTransformHandles.d.ts +91 -0
  190. package/dist/gui/EFTransformHandles.js +393 -0
  191. package/dist/gui/EFTransformHandles.js.map +1 -0
  192. package/dist/gui/EFWorkbench.d.ts +182 -4
  193. package/dist/gui/EFWorkbench.js +2067 -22
  194. package/dist/gui/EFWorkbench.js.map +1 -1
  195. package/dist/gui/FitScaleHelpers.d.ts +31 -0
  196. package/dist/gui/FitScaleHelpers.js +41 -0
  197. package/dist/gui/FitScaleHelpers.js.map +1 -0
  198. package/dist/gui/PlaybackController.d.ts +2 -1
  199. package/dist/gui/PlaybackController.js +46 -15
  200. package/dist/gui/PlaybackController.js.map +1 -1
  201. package/dist/gui/TWMixin.js +1 -1
  202. package/dist/gui/TWMixin.js.map +1 -1
  203. package/dist/gui/hierarchy/EFHierarchy.d.ts +65 -0
  204. package/dist/gui/hierarchy/EFHierarchy.js +338 -0
  205. package/dist/gui/hierarchy/EFHierarchy.js.map +1 -0
  206. package/dist/gui/hierarchy/EFHierarchyItem.d.ts +118 -0
  207. package/dist/gui/hierarchy/EFHierarchyItem.js +551 -0
  208. package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -0
  209. package/dist/gui/hierarchy/hierarchyContext.d.ts +38 -0
  210. package/dist/gui/hierarchy/hierarchyContext.js +8 -0
  211. package/dist/gui/hierarchy/hierarchyContext.js.map +1 -0
  212. package/dist/gui/icons.js +34 -0
  213. package/dist/gui/icons.js.map +1 -0
  214. package/dist/gui/panZoomTransformContext.js +12 -0
  215. package/dist/gui/panZoomTransformContext.js.map +1 -0
  216. package/dist/gui/previewSettingsContext.js +12 -0
  217. package/dist/gui/previewSettingsContext.js.map +1 -0
  218. package/dist/gui/timeline/EFTimeline.d.ts +270 -0
  219. package/dist/gui/timeline/EFTimeline.js +1369 -0
  220. package/dist/gui/timeline/EFTimeline.js.map +1 -0
  221. package/dist/gui/timeline/EFTimelineRow.js +374 -0
  222. package/dist/gui/timeline/EFTimelineRow.js.map +1 -0
  223. package/dist/gui/timeline/TrimHandles.d.ts +36 -0
  224. package/dist/gui/timeline/TrimHandles.js +204 -0
  225. package/dist/gui/timeline/TrimHandles.js.map +1 -0
  226. package/dist/gui/timeline/flattenHierarchy.js +31 -0
  227. package/dist/gui/timeline/flattenHierarchy.js.map +1 -0
  228. package/dist/gui/timeline/timelineStateContext.d.ts +26 -0
  229. package/dist/gui/timeline/timelineStateContext.js +42 -0
  230. package/dist/gui/timeline/timelineStateContext.js.map +1 -0
  231. package/dist/gui/timeline/tracks/AudioTrack.js +264 -0
  232. package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -0
  233. package/dist/gui/timeline/tracks/CaptionsTrack.js +595 -0
  234. package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -0
  235. package/dist/gui/timeline/tracks/HTMLTrack.js +19 -0
  236. package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -0
  237. package/dist/gui/timeline/tracks/ImageTrack.js +53 -0
  238. package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -0
  239. package/dist/gui/timeline/tracks/TextTrack.js +250 -0
  240. package/dist/gui/timeline/tracks/TextTrack.js.map +1 -0
  241. package/dist/gui/timeline/tracks/TimegroupTrack.js +143 -0
  242. package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -0
  243. package/dist/gui/timeline/tracks/TrackItem.js +269 -0
  244. package/dist/gui/timeline/tracks/TrackItem.js.map +1 -0
  245. package/dist/gui/timeline/tracks/VideoTrack.js +265 -0
  246. package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -0
  247. package/dist/gui/timeline/tracks/WaveformTrack.js +19 -0
  248. package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -0
  249. package/dist/gui/timeline/tracks/ensureTrackItemInit.js +1 -0
  250. package/dist/gui/timeline/tracks/preloadTracks.js +9 -0
  251. package/dist/gui/timeline/tracks/renderTrackChildren.js +119 -0
  252. package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -0
  253. package/dist/gui/timeline/tracks/waveformUtils.js +80 -0
  254. package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -0
  255. package/dist/gui/transformCalculations.js +217 -0
  256. package/dist/gui/transformCalculations.js.map +1 -0
  257. package/dist/gui/transformUtils.d.ts +37 -0
  258. package/dist/gui/transformUtils.js +77 -0
  259. package/dist/gui/transformUtils.js.map +1 -0
  260. package/dist/gui/tree/EFTree.d.ts +59 -0
  261. package/dist/gui/tree/EFTree.js +174 -0
  262. package/dist/gui/tree/EFTree.js.map +1 -0
  263. package/dist/gui/tree/EFTreeItem.d.ts +38 -0
  264. package/dist/gui/tree/EFTreeItem.js +146 -0
  265. package/dist/gui/tree/EFTreeItem.js.map +1 -0
  266. package/dist/gui/tree/treeContext.d.ts +60 -0
  267. package/dist/gui/tree/treeContext.js +23 -0
  268. package/dist/gui/tree/treeContext.js.map +1 -0
  269. package/dist/index.d.ts +32 -8
  270. package/dist/index.js +30 -6
  271. package/dist/index.js.map +1 -1
  272. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +688 -0
  273. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +1 -0
  274. package/dist/node_modules/react/cjs/react.development.js +1521 -0
  275. package/dist/node_modules/react/cjs/react.development.js.map +1 -0
  276. package/dist/node_modules/react/index.js +13 -0
  277. package/dist/node_modules/react/index.js.map +1 -0
  278. package/dist/node_modules/react/jsx-runtime.js +13 -0
  279. package/dist/node_modules/react/jsx-runtime.js.map +1 -0
  280. package/dist/preview/AdaptiveResolutionTracker.js +228 -0
  281. package/dist/preview/AdaptiveResolutionTracker.js.map +1 -0
  282. package/dist/preview/RenderProfiler.js +135 -0
  283. package/dist/preview/RenderProfiler.js.map +1 -0
  284. package/dist/preview/previewSettings.js +131 -0
  285. package/dist/preview/previewSettings.js.map +1 -0
  286. package/dist/preview/previewTypes.js +64 -0
  287. package/dist/preview/previewTypes.js.map +1 -0
  288. package/dist/preview/renderTimegroupPreview.js +656 -0
  289. package/dist/preview/renderTimegroupPreview.js.map +1 -0
  290. package/dist/preview/renderTimegroupToCanvas.d.ts +37 -0
  291. package/dist/preview/renderTimegroupToCanvas.js +840 -0
  292. package/dist/preview/renderTimegroupToCanvas.js.map +1 -0
  293. package/dist/preview/renderTimegroupToVideo.d.ts +39 -0
  294. package/dist/preview/renderTimegroupToVideo.js +274 -0
  295. package/dist/preview/renderTimegroupToVideo.js.map +1 -0
  296. package/dist/preview/renderers.js +16 -0
  297. package/dist/preview/renderers.js.map +1 -0
  298. package/dist/preview/statsTrackingStrategy.js +201 -0
  299. package/dist/preview/statsTrackingStrategy.js.map +1 -0
  300. package/dist/preview/thumbnailCacheSettings.js +52 -0
  301. package/dist/preview/thumbnailCacheSettings.js.map +1 -0
  302. package/dist/preview/workers/WorkerPool.js +178 -0
  303. package/dist/preview/workers/WorkerPool.js.map +1 -0
  304. package/dist/sandbox/PlaybackControls.js +10 -0
  305. package/dist/sandbox/PlaybackControls.js.map +1 -0
  306. package/dist/sandbox/ScenarioRunner.js +1 -0
  307. package/dist/sandbox/index.js +2 -0
  308. package/dist/style.css +66 -69
  309. package/dist/transcoding/types/index.d.ts +2 -1
  310. package/dist/transcoding/utils/UrlGenerator.d.ts +6 -1
  311. package/dist/transcoding/utils/UrlGenerator.js +12 -3
  312. package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
  313. package/dist/utils/LRUCache.js +1 -375
  314. package/dist/utils/LRUCache.js.map +1 -1
  315. package/dist/utils/frameTime.js +14 -0
  316. package/dist/utils/frameTime.js.map +1 -0
  317. package/package.json +3 -3
  318. package/test/profilingPlugin.ts +223 -0
  319. package/test/recordReplayProxyPlugin.js +22 -27
  320. package/test/thumbnail-performance-test.html +116 -0
  321. package/test/visualRegressionUtils.ts +286 -0
  322. package/types.json +1 -1
  323. package/dist/elements/TimegroupController.d.ts +0 -18
  324. package/dist/msToTimeCode.js +0 -17
  325. package/dist/msToTimeCode.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ElementPositionInfo.js","names":[],"sources":["../../src/elements/ElementPositionInfo.ts"],"sourcesContent":["import type { LitElement } from \"lit\";\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\n\n/**\n * Element position information interface.\n * Provides computed position, bounds, and transform information for elements.\n */\nexport interface ElementPositionInfo {\n /**\n * The bounding rectangle of the element in screen coordinates.\n */\n bounds: DOMRect;\n\n /**\n * The computed transform string (e.g., \"translate(10px, 20px) rotate(45deg)\").\n */\n transform: string;\n\n /**\n * The rotation angle in degrees, extracted from the transform.\n */\n rotation: number;\n}\n\n/**\n * Mixin that adds getPositionInfo() method to LitElement.\n * Elements can use this to expose their position information.\n */\nexport function PositionInfoMixin<T extends Constructor<LitElement>>(superClass: T) {\n class PositionInfoElement extends superClass {\n\n /**\n * Get position information for this element.\n * Returns computed bounds, transform, and rotation.\n *\n * @public\n */\n getPositionInfo(): ElementPositionInfo | null {\n return getPositionInfoFromElement(this as unknown as HTMLElement);\n }\n }\n\n return PositionInfoElement as T;\n}\n\n/**\n * Helper function to get position info from any HTMLElement.\n * Reads computed styles and bounding rect to determine position.\n */\nexport function getPositionInfoFromElement(\n element: HTMLElement | null,\n): ElementPositionInfo | null {\n if (!element) {\n return null;\n }\n\n const rect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n const transform = computedStyle.transform;\n\n // Parse rotation from transform matrix\n let rotation = 0;\n if (transform && transform !== \"none\") {\n // Transform matrix format: matrix(a, b, c, d, e, f)\n // Rotation = atan2(b, a) * (180 / Math.PI)\n const matrixMatch = transform.match(/matrix\\(([^)]+)\\)/);\n if (matrixMatch && matrixMatch[1]) {\n const values = matrixMatch[1].split(\",\").map((v) => parseFloat(v.trim()));\n if (values.length >= 4) {\n const a = values[0]!;\n const b = values[1]!;\n rotation = Math.atan2(b, a) * (180 / Math.PI);\n }\n }\n }\n\n return {\n bounds: rect,\n transform: transform || \"none\",\n rotation,\n };\n}\n"],"mappings":";;;;;AA6BA,SAAgB,kBAAqD,YAAe;CAClF,MAAM,4BAA4B,WAAW;;;;;;;EAQ3C,kBAA8C;AAC5C,UAAO,2BAA2B,KAA+B;;;AAIrE,QAAO;;;;;;AAOT,SAAgB,2BACd,SAC4B;AAC5B,KAAI,CAAC,QACH,QAAO;CAGT,MAAM,OAAO,QAAQ,uBAAuB;CAE5C,MAAM,YADgB,OAAO,iBAAiB,QAAQ,CACtB;CAGhC,IAAI,WAAW;AACf,KAAI,aAAa,cAAc,QAAQ;EAGrC,MAAM,cAAc,UAAU,MAAM,oBAAoB;AACxD,MAAI,eAAe,YAAY,IAAI;GACjC,MAAM,SAAS,YAAY,GAAG,MAAM,IAAI,CAAC,KAAK,MAAM,WAAW,EAAE,MAAM,CAAC,CAAC;AACzE,OAAI,OAAO,UAAU,GAAG;IACtB,MAAM,IAAI,OAAO;IACjB,MAAM,IAAI,OAAO;AACjB,eAAW,KAAK,MAAM,GAAG,EAAE,IAAI,MAAM,KAAK;;;;AAKhD,QAAO;EACL,QAAQ;EACR,WAAW,aAAa;EACxB;EACD"}
@@ -18,10 +18,25 @@ function FetchMixin(superClass) {
18
18
  }
19
19
  }
20
20
  return fetchPromise.catch((error) => {
21
+ const isAbortError = error instanceof Error && (error.name === "AbortError" || error.message.includes("signal is aborted") || error.message.includes("The user aborted a request"));
22
+ const isDisconnected = !this.isConnected;
23
+ const isNavigationAbort = isDisconnected && error instanceof TypeError && error.message === "Failed to fetch";
24
+ if (isAbortError || isNavigationAbort || isDisconnected) throw error;
21
25
  console.error("FetchMixin fetch error", url, error, window.location.href);
22
26
  const enhancedError = new (error instanceof Error ? error.constructor : Error)(`Failed to fetch: ${url}. Original error: ${error instanceof Error ? error.message : String(error)}`);
23
27
  if (error instanceof Error) {
24
- enhancedError.name = error.name;
28
+ try {
29
+ enhancedError.name = error.name;
30
+ } catch {
31
+ try {
32
+ Object.defineProperty(enhancedError, "name", {
33
+ value: error.name,
34
+ writable: true,
35
+ enumerable: false,
36
+ configurable: true
37
+ });
38
+ } catch {}
39
+ }
25
40
  enhancedError.stack = error.stack;
26
41
  Object.assign(enhancedError, error);
27
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FetchMixin.js","names":["fetchPromise: Promise<Response>"],"sources":["../../src/elements/FetchMixin.ts"],"sourcesContent":["import type { LitElement } from \"lit\";\n\nexport declare class FetchMixinInterface {\n fetch: typeof fetch;\n}\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\nexport function FetchMixin<T extends Constructor<LitElement>>(superClass: T) {\n class FetchElement extends superClass {\n fetch = (url: string, init?: RequestInit): Promise<Response> => {\n try {\n let fetchPromise: Promise<Response>;\n\n // Look for context providers up the DOM tree\n const workbench = this.closest(\"ef-workbench\") as any;\n if (workbench?.fetch) {\n fetchPromise = workbench.fetch(url, init);\n } else {\n const preview = this.closest(\"ef-preview\") as any;\n if (preview?.fetch) {\n fetchPromise = preview.fetch(url, init);\n } else {\n const configuration = this.closest(\"ef-configuration\") as any;\n if (configuration?.fetch) {\n fetchPromise = configuration.fetch(url, init);\n } else {\n // Fallback to window.fetch\n fetchPromise = window.fetch(url, init);\n }\n }\n }\n\n // Wrap the promise to catch rejections and log the URL\n // Return the promise chain so errors are logged but still propagate\n return fetchPromise.catch((error) => {\n console.error(\n \"FetchMixin fetch error\",\n url,\n error,\n window.location.href,\n );\n // Create a new error with the URL in the message, preserving the original error type\n const ErrorConstructor =\n error instanceof Error ? error.constructor : Error;\n const enhancedError = new (ErrorConstructor as typeof Error)(\n `Failed to fetch: ${url}. Original error: ${error instanceof Error ? error.message : String(error)}`,\n );\n // Preserve the original error's properties\n if (error instanceof Error) {\n enhancedError.name = error.name;\n enhancedError.stack = error.stack;\n // Copy any additional properties from the original error\n Object.assign(enhancedError, error);\n }\n throw enhancedError;\n });\n } catch (error) {\n console.error(\"FetchMixin error (synchronous)\", url, error);\n throw error;\n }\n };\n }\n\n return FetchElement as Constructor<FetchMixinInterface> & T;\n}\n"],"mappings":";AAOA,SAAgB,WAA8C,YAAe;CAC3E,MAAM,qBAAqB,WAAW;;;iBAC3B,KAAa,SAA0C;AAC9D,QAAI;KACF,IAAIA;KAGJ,MAAM,YAAY,KAAK,QAAQ,eAAe;AAC9C,SAAI,WAAW,MACb,gBAAe,UAAU,MAAM,KAAK,KAAK;UACpC;MACL,MAAM,UAAU,KAAK,QAAQ,aAAa;AAC1C,UAAI,SAAS,MACX,gBAAe,QAAQ,MAAM,KAAK,KAAK;WAClC;OACL,MAAM,gBAAgB,KAAK,QAAQ,mBAAmB;AACtD,WAAI,eAAe,MACjB,gBAAe,cAAc,MAAM,KAAK,KAAK;WAG7C,gBAAe,OAAO,MAAM,KAAK,KAAK;;;AAO5C,YAAO,aAAa,OAAO,UAAU;AACnC,cAAQ,MACN,0BACA,KACA,OACA,OAAO,SAAS,KACjB;MAID,MAAM,gBAAgB,KADpB,iBAAiB,QAAQ,MAAM,cAAc,OAE7C,oBAAoB,IAAI,oBAAoB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACnG;AAED,UAAI,iBAAiB,OAAO;AAC1B,qBAAc,OAAO,MAAM;AAC3B,qBAAc,QAAQ,MAAM;AAE5B,cAAO,OAAO,eAAe,MAAM;;AAErC,YAAM;OACN;aACK,OAAO;AACd,aAAQ,MAAM,kCAAkC,KAAK,MAAM;AAC3D,WAAM;;;;;AAKZ,QAAO"}
1
+ {"version":3,"file":"FetchMixin.js","names":["fetchPromise: Promise<Response>"],"sources":["../../src/elements/FetchMixin.ts"],"sourcesContent":["import type { LitElement } from \"lit\";\n\nexport declare class FetchMixinInterface {\n fetch: typeof fetch;\n}\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\nexport function FetchMixin<T extends Constructor<LitElement>>(superClass: T) {\n class FetchElement extends superClass {\n fetch = (url: string, init?: RequestInit): Promise<Response> => {\n try {\n let fetchPromise: Promise<Response>;\n\n // Look for context providers up the DOM tree\n const workbench = this.closest(\"ef-workbench\") as any;\n if (workbench?.fetch) {\n fetchPromise = workbench.fetch(url, init);\n } else {\n const preview = this.closest(\"ef-preview\") as any;\n if (preview?.fetch) {\n fetchPromise = preview.fetch(url, init);\n } else {\n const configuration = this.closest(\"ef-configuration\") as any;\n if (configuration?.fetch) {\n fetchPromise = configuration.fetch(url, init);\n } else {\n // Fallback to window.fetch\n fetchPromise = window.fetch(url, init);\n }\n }\n }\n\n // Wrap the promise to catch rejections and log the URL\n // Return the promise chain so errors are logged but still propagate\n return fetchPromise.catch((error) => {\n // Don't log AbortError - these are intentional request cancellations\n const isAbortError = \n error instanceof Error && (\n error.name === \"AbortError\" ||\n error.message.includes(\"signal is aborted\") ||\n error.message.includes(\"The user aborted a request\")\n );\n \n // Don't log errors if element is disconnected from DOM\n // This happens during scenario transitions when elements are removed mid-fetch\n // The browser throws TypeError: \"Failed to fetch\" when the page navigates\n const isDisconnected = !this.isConnected;\n \n // Also suppress \"Failed to fetch\" TypeError when disconnected\n // These occur when the browser aborts a request due to page navigation,\n // but doesn't throw a proper AbortError\n const isNavigationAbort = isDisconnected && \n error instanceof TypeError && \n error.message === \"Failed to fetch\";\n \n // For AbortErrors, navigation aborts, and disconnected elements,\n // re-throw the original error without enhancement to preserve error type\n if (isAbortError || isNavigationAbort || isDisconnected) {\n throw error;\n }\n \n // Log unexpected errors\n console.error(\n \"FetchMixin fetch error\",\n url,\n error,\n window.location.href,\n );\n \n // Create a new error with the URL in the message, preserving the original error type\n const ErrorConstructor =\n error instanceof Error ? error.constructor : Error;\n const enhancedError = new (ErrorConstructor as typeof Error)(\n `Failed to fetch: ${url}. Original error: ${error instanceof Error ? error.message : String(error)}`,\n );\n // Preserve the original error's properties\n if (error instanceof Error) {\n // Some error types (like DOMException) have read-only name property\n // Use try-catch to handle cases where name cannot be set\n try {\n enhancedError.name = error.name;\n } catch {\n // If name is read-only, use Object.defineProperty as fallback\n try {\n Object.defineProperty(enhancedError, \"name\", {\n value: error.name,\n writable: true,\n enumerable: false,\n configurable: true,\n });\n } catch {\n // If that also fails, just skip setting name\n }\n }\n enhancedError.stack = error.stack;\n // Copy any additional properties from the original error\n Object.assign(enhancedError, error);\n }\n throw enhancedError;\n });\n } catch (error) {\n console.error(\"FetchMixin error (synchronous)\", url, error);\n throw error;\n }\n };\n }\n\n return FetchElement as Constructor<FetchMixinInterface> & T;\n}\n"],"mappings":";AAOA,SAAgB,WAA8C,YAAe;CAC3E,MAAM,qBAAqB,WAAW;;;iBAC3B,KAAa,SAA0C;AAC9D,QAAI;KACF,IAAIA;KAGJ,MAAM,YAAY,KAAK,QAAQ,eAAe;AAC9C,SAAI,WAAW,MACb,gBAAe,UAAU,MAAM,KAAK,KAAK;UACpC;MACL,MAAM,UAAU,KAAK,QAAQ,aAAa;AAC1C,UAAI,SAAS,MACX,gBAAe,QAAQ,MAAM,KAAK,KAAK;WAClC;OACL,MAAM,gBAAgB,KAAK,QAAQ,mBAAmB;AACtD,WAAI,eAAe,MACjB,gBAAe,cAAc,MAAM,KAAK,KAAK;WAG7C,gBAAe,OAAO,MAAM,KAAK,KAAK;;;AAO5C,YAAO,aAAa,OAAO,UAAU;MAEnC,MAAM,eACJ,iBAAiB,UACf,MAAM,SAAS,gBACf,MAAM,QAAQ,SAAS,oBAAoB,IAC3C,MAAM,QAAQ,SAAS,6BAA6B;MAMxD,MAAM,iBAAiB,CAAC,KAAK;MAK7B,MAAM,oBAAoB,kBACxB,iBAAiB,aACjB,MAAM,YAAY;AAIpB,UAAI,gBAAgB,qBAAqB,eACvC,OAAM;AAIR,cAAQ,MACN,0BACA,KACA,OACA,OAAO,SAAS,KACjB;MAKD,MAAM,gBAAgB,KADpB,iBAAiB,QAAQ,MAAM,cAAc,OAE7C,oBAAoB,IAAI,oBAAoB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACnG;AAED,UAAI,iBAAiB,OAAO;AAG1B,WAAI;AACF,sBAAc,OAAO,MAAM;eACrB;AAEN,YAAI;AACF,gBAAO,eAAe,eAAe,QAAQ;UAC3C,OAAO,MAAM;UACb,UAAU;UACV,YAAY;UACZ,cAAc;UACf,CAAC;gBACI;;AAIV,qBAAc,QAAQ,MAAM;AAE5B,cAAO,OAAO,eAAe,MAAM;;AAErC,YAAM;OACN;aACK,OAAO;AACd,aAAQ,MAAM,kCAAkC,KAAK,MAAM;AAC3D,WAAM;;;;;AAKZ,QAAO"}
@@ -0,0 +1,152 @@
1
+ //#region src/elements/SessionThumbnailCache.ts
2
+ /**
3
+ * Quantize timestamp to 30fps frame boundaries for consistent caching.
4
+ * This eliminates cache misses from floating point precision differences.
5
+ */
6
+ function quantizeTimestamp(timeMs) {
7
+ const frameIntervalMs = 1e3 / 30;
8
+ return Math.round(timeMs / frameIntervalMs) * frameIntervalMs;
9
+ }
10
+ /**
11
+ * Generate cache key for thumbnail image data.
12
+ * Format: "rootId:elementId:quantizedTimeMs"
13
+ */
14
+ function getCacheKey(rootId, elementId, timeMs) {
15
+ return `${rootId}:${elementId}:${quantizeTimestamp(timeMs)}`;
16
+ }
17
+ /**
18
+ * Parse a cache key back into its components.
19
+ */
20
+ function parseCacheKey(key) {
21
+ const parts = key.split(":");
22
+ if (parts.length < 3) return null;
23
+ const timeMs = Number.parseFloat(parts.pop());
24
+ const rootId = parts[0];
25
+ parts.shift();
26
+ const elementId = parts.join(":");
27
+ if (Number.isNaN(timeMs)) return null;
28
+ return {
29
+ rootId,
30
+ elementId,
31
+ timeMs
32
+ };
33
+ }
34
+ /**
35
+ * Session thumbnail cache - simple Map with time-range invalidation.
36
+ */
37
+ var SessionThumbnailCache = class {
38
+ constructor(maxSize = 1e3) {
39
+ this.cache = /* @__PURE__ */ new Map();
40
+ this.maxSize = maxSize;
41
+ }
42
+ /**
43
+ * Check if a thumbnail exists in cache.
44
+ */
45
+ has(key) {
46
+ return this.cache.has(key);
47
+ }
48
+ /**
49
+ * Get a thumbnail from cache.
50
+ */
51
+ get(key) {
52
+ return this.cache.get(key)?.imageData;
53
+ }
54
+ /**
55
+ * Store a thumbnail in cache.
56
+ */
57
+ set(key, imageData, timeMs, elementId) {
58
+ if (this.cache.size >= this.maxSize) {
59
+ const firstKey = this.cache.keys().next().value;
60
+ if (firstKey) this.cache.delete(firstKey);
61
+ }
62
+ this.cache.set(key, {
63
+ imageData,
64
+ timeMs,
65
+ elementId
66
+ });
67
+ }
68
+ /**
69
+ * Invalidate all thumbnails for a specific element.
70
+ */
71
+ invalidateElement(rootId, elementId) {
72
+ const prefix = `${rootId}:${elementId}:`;
73
+ let count = 0;
74
+ for (const key of this.cache.keys()) if (key.startsWith(prefix)) {
75
+ this.cache.delete(key);
76
+ count++;
77
+ }
78
+ return count;
79
+ }
80
+ /**
81
+ * Invalidate thumbnails within a time range for a specific element.
82
+ * Used when content changes at specific times.
83
+ */
84
+ invalidateTimeRange(rootId, elementId, startTimeMs, endTimeMs) {
85
+ const prefix = `${rootId}:${elementId}:`;
86
+ let count = 0;
87
+ for (const [key, entry] of this.cache.entries()) if (key.startsWith(prefix)) {
88
+ if (entry.timeMs >= startTimeMs && entry.timeMs <= endTimeMs) {
89
+ this.cache.delete(key);
90
+ count++;
91
+ }
92
+ }
93
+ return count;
94
+ }
95
+ /**
96
+ * Invalidate all thumbnails that overlap with a given time range.
97
+ * This is more aggressive - invalidates any thumbnail that might show content from the range.
98
+ */
99
+ invalidateOverlappingRange(rootId, startTimeMs, endTimeMs) {
100
+ let count = 0;
101
+ for (const [key, entry] of this.cache.entries()) {
102
+ const parsed = parseCacheKey(key);
103
+ if (!parsed || parsed.rootId !== rootId) continue;
104
+ if (entry.timeMs >= startTimeMs && entry.timeMs <= endTimeMs) {
105
+ this.cache.delete(key);
106
+ count++;
107
+ }
108
+ }
109
+ return count;
110
+ }
111
+ /**
112
+ * Clear entire cache.
113
+ */
114
+ async clear() {
115
+ this.cache.clear();
116
+ }
117
+ /**
118
+ * Get current cache size.
119
+ */
120
+ get size() {
121
+ return this.cache.size;
122
+ }
123
+ /**
124
+ * Set maximum cache size.
125
+ */
126
+ setMaxSize(maxSize) {
127
+ this.maxSize = maxSize;
128
+ while (this.cache.size > this.maxSize) {
129
+ const firstKey = this.cache.keys().next().value;
130
+ if (firstKey) this.cache.delete(firstKey);
131
+ else break;
132
+ }
133
+ }
134
+ /**
135
+ * Get cache statistics.
136
+ */
137
+ async getStats() {
138
+ let totalSizeBytes = 0;
139
+ for (const entry of this.cache.values()) totalSizeBytes += entry.imageData.width * entry.imageData.height * 4;
140
+ return {
141
+ itemCount: this.cache.size,
142
+ totalSizeBytes,
143
+ maxSize: this.maxSize
144
+ };
145
+ }
146
+ };
147
+ const sessionThumbnailCache = new SessionThumbnailCache();
148
+ globalThis.debugSessionThumbnailCache = sessionThumbnailCache;
149
+
150
+ //#endregion
151
+ export { getCacheKey, sessionThumbnailCache };
152
+ //# sourceMappingURL=SessionThumbnailCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionThumbnailCache.js","names":[],"sources":["../../src/elements/SessionThumbnailCache.ts"],"sourcesContent":["/**\n * Session-only thumbnail cache - simple Map-based storage.\n * \n * DESIGN: Memory-only storage for thumbnails during the session.\n * - Fast synchronous access\n * - Automatic invalidation on time-range changes\n * - No persistence between sessions (refresh clears cache)\n * - No workers, no IndexedDB complexity\n */\n\nimport type { ThumbnailCacheStats } from \"./thumbnailCache.js\";\n\n/** Cache key format: \"rootId:elementId:quantizedTimeMs\" */\ntype CacheKey = string;\n\n/** Cache entry with metadata for invalidation */\ninterface CacheEntry {\n imageData: ImageData;\n timeMs: number; // Original (non-quantized) time for invalidation checks\n elementId: string; // Element identifier for scoped invalidation\n}\n\n/**\n * Quantize timestamp to 30fps frame boundaries for consistent caching.\n * This eliminates cache misses from floating point precision differences.\n */\nexport function quantizeTimestamp(timeMs: number): number {\n const frameIntervalMs = 1000 / 30; // 33.33ms at 30fps\n return Math.round(timeMs / frameIntervalMs) * frameIntervalMs;\n}\n\n/**\n * Generate cache key for thumbnail image data.\n * Format: \"rootId:elementId:quantizedTimeMs\"\n */\nexport function getCacheKey(rootId: string, elementId: string, timeMs: number): string {\n const quantizedTimeMs = quantizeTimestamp(timeMs);\n return `${rootId}:${elementId}:${quantizedTimeMs}`;\n}\n\n/**\n * Parse a cache key back into its components.\n */\nfunction parseCacheKey(key: CacheKey): { rootId: string; elementId: string; timeMs: number } | null {\n const parts = key.split(\":\");\n if (parts.length < 3) return null;\n \n // Last part is always timeMs\n const timeMs = Number.parseFloat(parts.pop()!);\n // Second to last is elementId (may contain colons in URLs)\n // Actually, we need to be smarter - rootId is first, elementId is middle, timeMs is last\n // For URL-based elementIds like \"http://example.com/video.mp4\", we need to handle colons\n \n // Simpler approach: rootId is first part, timeMs is last, everything else is elementId\n const rootId = parts[0]!;\n parts.shift(); // Remove rootId\n const elementId = parts.join(\":\"); // Rejoin middle parts as elementId\n \n if (Number.isNaN(timeMs)) return null;\n return { rootId, elementId, timeMs };\n}\n\n/**\n * Session thumbnail cache - simple Map with time-range invalidation.\n */\nexport class SessionThumbnailCache {\n private cache: Map<CacheKey, CacheEntry> = new Map();\n private maxSize: number;\n\n constructor(maxSize = 1000) {\n this.maxSize = maxSize;\n }\n\n /**\n * Check if a thumbnail exists in cache.\n */\n has(key: CacheKey): boolean {\n return this.cache.has(key);\n }\n\n /**\n * Get a thumbnail from cache.\n */\n get(key: CacheKey): ImageData | undefined {\n return this.cache.get(key)?.imageData;\n }\n\n /**\n * Store a thumbnail in cache.\n */\n set(key: CacheKey, imageData: ImageData, timeMs: number, elementId: string): void {\n // Enforce max size with simple FIFO eviction\n if (this.cache.size >= this.maxSize) {\n const firstKey = this.cache.keys().next().value;\n if (firstKey) {\n this.cache.delete(firstKey);\n }\n }\n\n this.cache.set(key, { imageData, timeMs, elementId });\n }\n\n /**\n * Invalidate all thumbnails for a specific element.\n */\n invalidateElement(rootId: string, elementId: string): number {\n const prefix = `${rootId}:${elementId}:`;\n let count = 0;\n \n for (const key of this.cache.keys()) {\n if (key.startsWith(prefix)) {\n this.cache.delete(key);\n count++;\n }\n }\n \n return count;\n }\n\n /**\n * Invalidate thumbnails within a time range for a specific element.\n * Used when content changes at specific times.\n */\n invalidateTimeRange(rootId: string, elementId: string, startTimeMs: number, endTimeMs: number): number {\n const prefix = `${rootId}:${elementId}:`;\n let count = 0;\n \n for (const [key, entry] of this.cache.entries()) {\n if (key.startsWith(prefix)) {\n // Check if this thumbnail's time falls within the invalidated range\n if (entry.timeMs >= startTimeMs && entry.timeMs <= endTimeMs) {\n this.cache.delete(key);\n count++;\n }\n }\n }\n \n return count;\n }\n\n /**\n * Invalidate all thumbnails that overlap with a given time range.\n * This is more aggressive - invalidates any thumbnail that might show content from the range.\n */\n invalidateOverlappingRange(rootId: string, startTimeMs: number, endTimeMs: number): number {\n let count = 0;\n \n for (const [key, entry] of this.cache.entries()) {\n const parsed = parseCacheKey(key);\n if (!parsed || parsed.rootId !== rootId) continue;\n \n // Invalidate if the thumbnail's time overlaps with the changed range\n if (entry.timeMs >= startTimeMs && entry.timeMs <= endTimeMs) {\n this.cache.delete(key);\n count++;\n }\n }\n \n return count;\n }\n\n /**\n * Clear entire cache.\n */\n async clear(): Promise<void> {\n this.cache.clear();\n }\n\n /**\n * Get current cache size.\n */\n get size(): number {\n return this.cache.size;\n }\n\n /**\n * Set maximum cache size.\n */\n setMaxSize(maxSize: number): void {\n this.maxSize = maxSize;\n \n // Evict entries if over new limit\n while (this.cache.size > this.maxSize) {\n const firstKey = this.cache.keys().next().value;\n if (firstKey) {\n this.cache.delete(firstKey);\n } else {\n break;\n }\n }\n }\n\n /**\n * Get cache statistics.\n */\n async getStats(): Promise<ThumbnailCacheStats> {\n // Calculate total size by summing all ImageData sizes\n let totalSizeBytes = 0;\n for (const entry of this.cache.values()) {\n // ImageData size = width * height * 4 bytes (RGBA)\n totalSizeBytes += entry.imageData.width * entry.imageData.height * 4;\n }\n\n return {\n itemCount: this.cache.size,\n totalSizeBytes,\n maxSize: this.maxSize,\n };\n }\n}\n\n// Global singleton cache instance for all thumbnail strips\nexport const sessionThumbnailCache = new SessionThumbnailCache();\n\n// Export for debugging\n(globalThis as any).debugSessionThumbnailCache = sessionThumbnailCache;\n"],"mappings":";;;;;AA0BA,SAAgB,kBAAkB,QAAwB;CACxD,MAAM,kBAAkB,MAAO;AAC/B,QAAO,KAAK,MAAM,SAAS,gBAAgB,GAAG;;;;;;AAOhD,SAAgB,YAAY,QAAgB,WAAmB,QAAwB;AAErF,QAAO,GAAG,OAAO,GAAG,UAAU,GADN,kBAAkB,OAAO;;;;;AAOnD,SAAS,cAAc,KAA6E;CAClG,MAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,KAAI,MAAM,SAAS,EAAG,QAAO;CAG7B,MAAM,SAAS,OAAO,WAAW,MAAM,KAAK,CAAE;CAM9C,MAAM,SAAS,MAAM;AACrB,OAAM,OAAO;CACb,MAAM,YAAY,MAAM,KAAK,IAAI;AAEjC,KAAI,OAAO,MAAM,OAAO,CAAE,QAAO;AACjC,QAAO;EAAE;EAAQ;EAAW;EAAQ;;;;;AAMtC,IAAa,wBAAb,MAAmC;CAIjC,YAAY,UAAU,KAAM;+BAHe,IAAI,KAAK;AAIlD,OAAK,UAAU;;;;;CAMjB,IAAI,KAAwB;AAC1B,SAAO,KAAK,MAAM,IAAI,IAAI;;;;;CAM5B,IAAI,KAAsC;AACxC,SAAO,KAAK,MAAM,IAAI,IAAI,EAAE;;;;;CAM9B,IAAI,KAAe,WAAsB,QAAgB,WAAyB;AAEhF,MAAI,KAAK,MAAM,QAAQ,KAAK,SAAS;GACnC,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,SACF,MAAK,MAAM,OAAO,SAAS;;AAI/B,OAAK,MAAM,IAAI,KAAK;GAAE;GAAW;GAAQ;GAAW,CAAC;;;;;CAMvD,kBAAkB,QAAgB,WAA2B;EAC3D,MAAM,SAAS,GAAG,OAAO,GAAG,UAAU;EACtC,IAAI,QAAQ;AAEZ,OAAK,MAAM,OAAO,KAAK,MAAM,MAAM,CACjC,KAAI,IAAI,WAAW,OAAO,EAAE;AAC1B,QAAK,MAAM,OAAO,IAAI;AACtB;;AAIJ,SAAO;;;;;;CAOT,oBAAoB,QAAgB,WAAmB,aAAqB,WAA2B;EACrG,MAAM,SAAS,GAAG,OAAO,GAAG,UAAU;EACtC,IAAI,QAAQ;AAEZ,OAAK,MAAM,CAAC,KAAK,UAAU,KAAK,MAAM,SAAS,CAC7C,KAAI,IAAI,WAAW,OAAO,EAExB;OAAI,MAAM,UAAU,eAAe,MAAM,UAAU,WAAW;AAC5D,SAAK,MAAM,OAAO,IAAI;AACtB;;;AAKN,SAAO;;;;;;CAOT,2BAA2B,QAAgB,aAAqB,WAA2B;EACzF,IAAI,QAAQ;AAEZ,OAAK,MAAM,CAAC,KAAK,UAAU,KAAK,MAAM,SAAS,EAAE;GAC/C,MAAM,SAAS,cAAc,IAAI;AACjC,OAAI,CAAC,UAAU,OAAO,WAAW,OAAQ;AAGzC,OAAI,MAAM,UAAU,eAAe,MAAM,UAAU,WAAW;AAC5D,SAAK,MAAM,OAAO,IAAI;AACtB;;;AAIJ,SAAO;;;;;CAMT,MAAM,QAAuB;AAC3B,OAAK,MAAM,OAAO;;;;;CAMpB,IAAI,OAAe;AACjB,SAAO,KAAK,MAAM;;;;;CAMpB,WAAW,SAAuB;AAChC,OAAK,UAAU;AAGf,SAAO,KAAK,MAAM,OAAO,KAAK,SAAS;GACrC,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,SACF,MAAK,MAAM,OAAO,SAAS;OAE3B;;;;;;CAQN,MAAM,WAAyC;EAE7C,IAAI,iBAAiB;AACrB,OAAK,MAAM,SAAS,KAAK,MAAM,QAAQ,CAErC,mBAAkB,MAAM,UAAU,QAAQ,MAAM,UAAU,SAAS;AAGrE,SAAO;GACL,WAAW,KAAK,MAAM;GACtB;GACA,SAAS,KAAK;GACf;;;AAKL,MAAa,wBAAwB,IAAI,uBAAuB;AAGhE,AAAC,WAAmB,6BAA6B"}
@@ -100,7 +100,9 @@ var TargetController = class {
100
100
  if (this.currentTargetString) this.registry.subscribe(this.currentTargetString, this.registryCallback);
101
101
  }
102
102
  updateTarget() {
103
- const newTarget = this.registry.get(this.host.target);
103
+ if (!this.host.target) return;
104
+ let newTarget = this.registry.get(this.host.target);
105
+ if (!newTarget) newTarget = document.getElementById(this.host.target);
104
106
  if (this.host.targetElement !== newTarget) {
105
107
  this.disconnectFromTarget();
106
108
  this.host.targetElement = newTarget ?? null;
@@ -1 +1 @@
1
- {"version":3,"file":"TargetController.js","names":["#registry","host: LitElement"],"sources":["../../src/elements/TargetController.ts"],"sourcesContent":["import { LitElement, type ReactiveController } from \"lit\";\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\n\n// Symbol to identify elements that can be targeted\nconst EF_TARGETABLE = Symbol(\"EF_TARGETABLE\");\n\nclass TargetRegistry {\n private idMap = new Map<string, LitElement>();\n private callbacks = new Map<\n string,\n Set<(target: LitElement | undefined) => void>\n >();\n\n subscribe(id: string, callback: (target: LitElement | undefined) => void) {\n this.callbacks.set(id, this.callbacks.get(id) ?? new Set());\n this.callbacks.get(id)?.add(callback);\n }\n\n unsubscribe(\n id: string | null,\n callback: (target: LitElement | undefined) => void,\n ) {\n if (id === null) {\n return;\n }\n this.callbacks.get(id)?.delete(callback);\n if (this.callbacks.get(id)?.size === 0) {\n this.callbacks.delete(id);\n }\n }\n\n get(id: string) {\n return this.idMap.get(id);\n }\n\n register(id: string, target: LitElement) {\n this.idMap.set(id, target);\n for (const callback of this.callbacks.get(id) ?? []) {\n callback(target);\n }\n }\n\n unregister(id: string, target: LitElement) {\n if (this.idMap.get(id) !== target) {\n // Avoid unregistering a target that is not the current target\n return;\n }\n for (const callback of this.callbacks.get(id) ?? []) {\n callback(undefined);\n }\n this.idMap.delete(id);\n this.callbacks.delete(id);\n }\n}\n\n// Map of root nodes to their target registries\nconst documentRegistries = new WeakMap<Node, TargetRegistry>();\n\nconst getRegistry = (root: Node) => {\n let registry = documentRegistries.get(root);\n if (!registry) {\n registry = new TargetRegistry();\n documentRegistries.set(root, registry);\n }\n return registry;\n};\n\nexport declare class TargetableMixinInterface {\n id: string;\n}\n\nexport const isEFTargetable = (obj: any): obj is TargetableMixinInterface =>\n obj[EF_TARGETABLE];\n\nexport const EFTargetable = <T extends Constructor<LitElement>>(\n superClass: T,\n) => {\n class TargetableElement extends superClass {\n #registry: TargetRegistry | null = null;\n\n static get observedAttributes(): string[] {\n // Get parent's observed attributes\n const parentAttributes = (superClass as any).observedAttributes || [];\n // Add 'id' if not already present\n return [...new Set([...parentAttributes, \"id\"])];\n }\n\n private updateRegistry(oldValue: string, newValue: string) {\n if (!this.#registry) return;\n if (oldValue === newValue) return;\n\n if (oldValue) {\n this.#registry.unregister(oldValue, this);\n }\n if (newValue) {\n this.#registry.register(newValue, this);\n }\n }\n\n connectedCallback() {\n super.connectedCallback();\n this.#registry = getRegistry(this.getRootNode());\n const initialId = this.getAttribute(\"id\");\n if (initialId) {\n this.updateRegistry(\"\", initialId);\n }\n }\n\n attributeChangedCallback(\n name: string,\n old: string | null,\n value: string | null,\n ) {\n super.attributeChangedCallback(name, old, value);\n if (name === \"id\") {\n this.updateRegistry(old ?? \"\", value ?? \"\");\n }\n }\n\n disconnectedCallback() {\n if (this.#registry) {\n this.updateRegistry(this.id, \"\");\n this.#registry = null;\n }\n super.disconnectedCallback();\n }\n }\n\n Object.defineProperty(TargetableElement.prototype, EF_TARGETABLE, {\n value: true,\n });\n\n return TargetableElement as T;\n};\n\nclass TargetUpdateController implements ReactiveController {\n constructor(private host: LitElement) {}\n\n hostConnected() {\n this.host.requestUpdate();\n }\n\n hostDisconnected() {\n this.host.requestUpdate();\n }\n\n hostUpdate() {\n this.host.requestUpdate();\n }\n}\n\nexport class TargetController implements ReactiveController {\n private host: LitElement & { targetElement: Element | null; target: string };\n private targetController: ReactiveController | null = null;\n private currentTargetString: string | null = null;\n\n constructor(\n host: LitElement & { targetElement: Element | null; target: string },\n ) {\n this.host = host;\n this.host.addController(this);\n this.currentTargetString = this.host.target;\n if (this.currentTargetString) {\n this.registry.subscribe(this.currentTargetString, this.registryCallback);\n }\n }\n\n private registryCallback = (target: LitElement | undefined) => {\n this.host.targetElement = target ?? null;\n };\n\n private updateTarget() {\n const newTarget = this.registry.get(this.host.target);\n if (this.host.targetElement !== newTarget) {\n this.disconnectFromTarget();\n this.host.targetElement = newTarget ?? (null as Element | null);\n this.connectToTarget();\n this.host.requestUpdate(\"targetElement\");\n }\n }\n\n private connectToTarget() {\n if (this.host.targetElement instanceof LitElement) {\n this.targetController = new TargetUpdateController(this.host);\n this.host.targetElement.addController(this.targetController);\n }\n }\n\n private disconnectFromTarget() {\n if (\n this.host.targetElement instanceof LitElement &&\n this.targetController\n ) {\n this.host.targetElement.removeController(this.targetController);\n this.targetController = null;\n }\n }\n\n private get registry() {\n const root = this.host.getRootNode();\n return getRegistry(root);\n }\n\n hostDisconnected() {\n this.disconnectFromTarget();\n }\n\n hostConnected() {\n this.updateTarget();\n }\n\n hostUpdate() {\n if (this.currentTargetString !== this.host.target) {\n this.registry.unsubscribe(\n this.currentTargetString,\n this.registryCallback,\n );\n this.registry.subscribe(this.host.target, this.registryCallback);\n this.updateTarget();\n this.currentTargetString = this.host.target;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TargetController.js","names":["#registry","host: LitElement"],"sources":["../../src/elements/TargetController.ts"],"sourcesContent":["import { LitElement, type ReactiveController } from \"lit\";\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\n\n// Symbol to identify elements that can be targeted\nconst EF_TARGETABLE = Symbol(\"EF_TARGETABLE\");\n\nclass TargetRegistry {\n private idMap = new Map<string, LitElement>();\n private callbacks = new Map<\n string,\n Set<(target: LitElement | undefined) => void>\n >();\n\n subscribe(id: string, callback: (target: LitElement | undefined) => void) {\n this.callbacks.set(id, this.callbacks.get(id) ?? new Set());\n this.callbacks.get(id)?.add(callback);\n }\n\n unsubscribe(\n id: string | null,\n callback: (target: LitElement | undefined) => void,\n ) {\n if (id === null) {\n return;\n }\n this.callbacks.get(id)?.delete(callback);\n if (this.callbacks.get(id)?.size === 0) {\n this.callbacks.delete(id);\n }\n }\n\n get(id: string) {\n return this.idMap.get(id);\n }\n\n register(id: string, target: LitElement) {\n this.idMap.set(id, target);\n for (const callback of this.callbacks.get(id) ?? []) {\n callback(target);\n }\n }\n\n unregister(id: string, target: LitElement) {\n if (this.idMap.get(id) !== target) {\n // Avoid unregistering a target that is not the current target\n return;\n }\n for (const callback of this.callbacks.get(id) ?? []) {\n callback(undefined);\n }\n this.idMap.delete(id);\n this.callbacks.delete(id);\n }\n}\n\n// Map of root nodes to their target registries\nconst documentRegistries = new WeakMap<Node, TargetRegistry>();\n\nconst getRegistry = (root: Node) => {\n let registry = documentRegistries.get(root);\n if (!registry) {\n registry = new TargetRegistry();\n documentRegistries.set(root, registry);\n }\n return registry;\n};\n\nexport declare class TargetableMixinInterface {\n id: string;\n}\n\nexport const isEFTargetable = (obj: any): obj is TargetableMixinInterface =>\n obj[EF_TARGETABLE];\n\nexport const EFTargetable = <T extends Constructor<LitElement>>(\n superClass: T,\n) => {\n class TargetableElement extends superClass {\n #registry: TargetRegistry | null = null;\n\n static get observedAttributes(): string[] {\n // Get parent's observed attributes\n const parentAttributes = (superClass as any).observedAttributes || [];\n // Add 'id' if not already present\n return [...new Set([...parentAttributes, \"id\"])];\n }\n\n private updateRegistry(oldValue: string, newValue: string) {\n if (!this.#registry) return;\n if (oldValue === newValue) return;\n\n if (oldValue) {\n this.#registry.unregister(oldValue, this);\n }\n if (newValue) {\n this.#registry.register(newValue, this);\n }\n }\n\n connectedCallback() {\n super.connectedCallback();\n this.#registry = getRegistry(this.getRootNode());\n const initialId = this.getAttribute(\"id\");\n if (initialId) {\n this.updateRegistry(\"\", initialId);\n }\n }\n\n attributeChangedCallback(\n name: string,\n old: string | null,\n value: string | null,\n ) {\n super.attributeChangedCallback(name, old, value);\n if (name === \"id\") {\n this.updateRegistry(old ?? \"\", value ?? \"\");\n }\n }\n\n disconnectedCallback() {\n if (this.#registry) {\n this.updateRegistry(this.id, \"\");\n this.#registry = null;\n }\n super.disconnectedCallback();\n }\n }\n\n Object.defineProperty(TargetableElement.prototype, EF_TARGETABLE, {\n value: true,\n });\n\n return TargetableElement as T;\n};\n\nclass TargetUpdateController implements ReactiveController {\n constructor(private host: LitElement) {}\n\n hostConnected() {\n this.host.requestUpdate();\n }\n\n hostDisconnected() {\n this.host.requestUpdate();\n }\n\n hostUpdate() {\n this.host.requestUpdate();\n }\n}\n\nexport class TargetController implements ReactiveController {\n private host: LitElement & { targetElement: Element | null; target: string };\n private targetController: ReactiveController | null = null;\n private currentTargetString: string | null = null;\n\n constructor(\n host: LitElement & { targetElement: Element | null; target: string },\n ) {\n this.host = host;\n this.host.addController(this);\n this.currentTargetString = this.host.target;\n if (this.currentTargetString) {\n this.registry.subscribe(this.currentTargetString, this.registryCallback);\n }\n }\n\n private registryCallback = (target: LitElement | undefined) => {\n this.host.targetElement = target ?? null;\n };\n\n private updateTarget() {\n // Only look up by ID if target string is non-empty\n // This preserves direct object bindings via .targetElement=${obj}\n if (!this.host.target) {\n return;\n }\n\n // First try the local registry (same root node)\n let newTarget = this.registry.get(this.host.target);\n\n // Fall back to document.getElementById for cross-shadow-root targeting\n if (!newTarget) {\n newTarget = document.getElementById(this.host.target) as LitElement | undefined;\n }\n\n if (this.host.targetElement !== newTarget) {\n this.disconnectFromTarget();\n this.host.targetElement = newTarget ?? (null as Element | null);\n this.connectToTarget();\n this.host.requestUpdate(\"targetElement\");\n }\n }\n\n private connectToTarget() {\n if (this.host.targetElement instanceof LitElement) {\n this.targetController = new TargetUpdateController(this.host);\n this.host.targetElement.addController(this.targetController);\n }\n }\n\n private disconnectFromTarget() {\n if (\n this.host.targetElement instanceof LitElement &&\n this.targetController\n ) {\n this.host.targetElement.removeController(this.targetController);\n this.targetController = null;\n }\n }\n\n private get registry() {\n const root = this.host.getRootNode();\n return getRegistry(root);\n }\n\n hostDisconnected() {\n this.disconnectFromTarget();\n }\n\n hostConnected() {\n this.updateTarget();\n }\n\n hostUpdate() {\n if (this.currentTargetString !== this.host.target) {\n this.registry.unsubscribe(\n this.currentTargetString,\n this.registryCallback,\n );\n this.registry.subscribe(this.host.target, this.registryCallback);\n this.updateTarget();\n this.currentTargetString = this.host.target;\n }\n }\n}\n"],"mappings":"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"}
@@ -12,9 +12,15 @@ var TimegroupController = class {
12
12
  this.host.removeController(this);
13
13
  }
14
14
  hostUpdated() {
15
- this.child.requestUpdate();
16
- const newChildTimeMs = this.host.currentTimeMs - (this.child.startTimeMs ?? 0);
17
- this.child.currentTimeMs = newChildTimeMs;
15
+ Promise.resolve().then(() => {
16
+ if ("mode" in this.child && "isRootTimegroup" in this.child) {
17
+ this.child.requestUpdate();
18
+ return;
19
+ }
20
+ this.child.requestUpdate();
21
+ const newChildTimeMs = this.host.currentTimeMs - (this.child.startTimeMs ?? 0);
22
+ this.child.currentTimeMs = newChildTimeMs;
23
+ });
18
24
  }
19
25
  };
20
26
 
@@ -1 +1 @@
1
- {"version":3,"file":"TimegroupController.js","names":["host: EFTimegroup","child: { currentTimeMs: number; startTimeMs?: number } & LitElement"],"sources":["../../src/elements/TimegroupController.ts"],"sourcesContent":["import type { LitElement, ReactiveController } from \"lit\";\nimport type { EFTimegroup } from \"./EFTimegroup.js\";\n\nexport class TimegroupController implements ReactiveController {\n constructor(\n private host: EFTimegroup,\n private child: { currentTimeMs: number; startTimeMs?: number } & LitElement,\n ) {\n this.host.addController(this);\n }\n\n remove() {\n this.host.removeController(this);\n }\n\n hostDisconnected(): void {\n this.host.removeController(this);\n }\n\n hostUpdated(): void {\n this.child.requestUpdate();\n const newChildTimeMs =\n this.host.currentTimeMs - (this.child.startTimeMs ?? 0);\n this.child.currentTimeMs = newChildTimeMs;\n }\n}\n"],"mappings":";AAGA,IAAa,sBAAb,MAA+D;CAC7D,YACE,AAAQA,MACR,AAAQC,OACR;EAFQ;EACA;AAER,OAAK,KAAK,cAAc,KAAK;;CAG/B,SAAS;AACP,OAAK,KAAK,iBAAiB,KAAK;;CAGlC,mBAAyB;AACvB,OAAK,KAAK,iBAAiB,KAAK;;CAGlC,cAAoB;AAClB,OAAK,MAAM,eAAe;EAC1B,MAAM,iBACJ,KAAK,KAAK,iBAAiB,KAAK,MAAM,eAAe;AACvD,OAAK,MAAM,gBAAgB"}
1
+ {"version":3,"file":"TimegroupController.js","names":["host: EFTimegroup","child: { currentTimeMs: number; startTimeMs?: number } & LitElement"],"sources":["../../src/elements/TimegroupController.ts"],"sourcesContent":["import type { LitElement, ReactiveController } from \"lit\";\nimport type { EFTimegroup } from \"./EFTimegroup.js\";\n\nexport class TimegroupController implements ReactiveController {\n constructor(\n private host: EFTimegroup,\n private child: { currentTimeMs: number; startTimeMs?: number } & LitElement,\n ) {\n this.host.addController(this);\n }\n\n remove() {\n this.host.removeController(this);\n }\n\n hostDisconnected(): void {\n this.host.removeController(this);\n }\n\n hostUpdated(): void {\n // Defer update to avoid Lit warning about scheduling updates after update completed\n // This batches updates and prevents cascading update cycles\n Promise.resolve().then(() => {\n // Skip setting currentTimeMs for timegroup children - they compute ownCurrentTimeMs\n // from the root timegroup via EFTemporal. Setting it directly causes an infinite loop\n // because the @property decorator on currentTime triggers reactive updates.\n if ('mode' in this.child && 'isRootTimegroup' in this.child) {\n // Child is a timegroup - just request update, don't set currentTimeMs\n this.child.requestUpdate();\n return;\n }\n\n this.child.requestUpdate();\n const newChildTimeMs =\n this.host.currentTimeMs - (this.child.startTimeMs ?? 0);\n this.child.currentTimeMs = newChildTimeMs;\n });\n }\n}\n"],"mappings":";AAGA,IAAa,sBAAb,MAA+D;CAC7D,YACE,AAAQA,MACR,AAAQC,OACR;EAFQ;EACA;AAER,OAAK,KAAK,cAAc,KAAK;;CAG/B,SAAS;AACP,OAAK,KAAK,iBAAiB,KAAK;;CAGlC,mBAAyB;AACvB,OAAK,KAAK,iBAAiB,KAAK;;CAGlC,cAAoB;AAGlB,UAAQ,SAAS,CAAC,WAAW;AAI3B,OAAI,UAAU,KAAK,SAAS,qBAAqB,KAAK,OAAO;AAE3D,SAAK,MAAM,eAAe;AAC1B;;AAGF,QAAK,MAAM,eAAe;GAC1B,MAAM,iBACJ,KAAK,KAAK,iBAAiB,KAAK,MAAM,eAAe;AACvD,QAAK,MAAM,gBAAgB;IAC3B"}
@@ -0,0 +1,30 @@
1
+ import { isEFTemporal } from "./EFTemporal.js";
2
+
3
+ //#region src/elements/findRootTemporal.ts
4
+ /**
5
+ * Finds the outermost temporal element in the DOM ancestry of the given element.
6
+ *
7
+ * Walks up from the element toward the document root, collecting all temporal
8
+ * elements (ef-timegroup, ef-video, ef-audio) in the ancestry, and returns
9
+ * the outermost one (closest to document root).
10
+ *
11
+ * This is a pure function - no caching, no stored state. Called fresh whenever
12
+ * the root temporal is needed.
13
+ *
14
+ * @param element - The element to start searching from
15
+ * @returns The outermost temporal element, or null if none found
16
+ */
17
+ function findRootTemporal(element) {
18
+ if (!element) return null;
19
+ let current = element;
20
+ let outermostTemporal = null;
21
+ while (current && current !== document) {
22
+ if (current instanceof HTMLElement && isEFTemporal(current)) outermostTemporal = current;
23
+ current = current.parentNode;
24
+ }
25
+ return outermostTemporal;
26
+ }
27
+
28
+ //#endregion
29
+ export { findRootTemporal };
30
+ //# sourceMappingURL=findRootTemporal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findRootTemporal.js","names":["current: Node | null","outermostTemporal: (TemporalMixinInterface & HTMLElement) | null"],"sources":["../../src/elements/findRootTemporal.ts"],"sourcesContent":["import { isEFTemporal, type TemporalMixinInterface } from \"./EFTemporal.js\";\n\n/**\n * Finds the outermost temporal element in the DOM ancestry of the given element.\n *\n * Walks up from the element toward the document root, collecting all temporal\n * elements (ef-timegroup, ef-video, ef-audio) in the ancestry, and returns\n * the outermost one (closest to document root).\n *\n * This is a pure function - no caching, no stored state. Called fresh whenever\n * the root temporal is needed.\n *\n * @param element - The element to start searching from\n * @returns The outermost temporal element, or null if none found\n */\nexport function findRootTemporal(\n element: Element | null,\n): (TemporalMixinInterface & HTMLElement) | null {\n if (!element) {\n return null;\n }\n\n let current: Node | null = element;\n let outermostTemporal: (TemporalMixinInterface & HTMLElement) | null = null;\n\n // Walk up the DOM tree toward document root\n while (current && current !== document) {\n // Check if current node is a temporal element\n if (current instanceof HTMLElement && isEFTemporal(current)) {\n outermostTemporal = current as TemporalMixinInterface & HTMLElement;\n }\n current = current.parentNode;\n }\n\n return outermostTemporal;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,SAAgB,iBACd,SAC+C;AAC/C,KAAI,CAAC,QACH,QAAO;CAGT,IAAIA,UAAuB;CAC3B,IAAIC,oBAAmE;AAGvE,QAAO,WAAW,YAAY,UAAU;AAEtC,MAAI,mBAAmB,eAAe,aAAa,QAAQ,CACzD,qBAAoB;AAEtB,YAAU,QAAQ;;AAGpB,QAAO"}
@@ -1,11 +1,14 @@
1
1
  //#region src/elements/renderTemporalAudio.ts
2
- async function renderTemporalAudio(host, fromMs, toMs) {
2
+ async function renderTemporalAudio(host, fromMs, toMs, signal) {
3
3
  const aacFrames = 48e3 * ((toMs - fromMs) / 1e3) / 1024;
4
4
  const contextSize = Math.round(aacFrames) * 1024;
5
5
  if (contextSize <= 0) throw new Error(`Duration must be greater than 0 when rendering audio. ${contextSize}ms`);
6
+ signal?.throwIfAborted();
6
7
  const audioContext = new OfflineAudioContext(2, contextSize, 48e3);
7
- if (host.waitForMediaDurations) await host.waitForMediaDurations();
8
- const abortController = new AbortController();
8
+ if (host.waitForMediaDurations) {
9
+ await host.waitForMediaDurations();
10
+ signal?.throwIfAborted();
11
+ }
9
12
  await Promise.all(host.getMediaElements().map(async (mediaElement) => {
10
13
  if (mediaElement.mute) return;
11
14
  const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
@@ -17,10 +20,20 @@ async function renderTemporalAudio(host, fromMs, toMs) {
17
20
  const sourceInMs = mediaElement.sourceInMs || mediaElement.trimStartMs || 0;
18
21
  const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;
19
22
  const mediaSourceToMs = mediaLocalToMs + sourceInMs;
20
- const audio = await mediaElement.fetchAudioSpanningTime(mediaSourceFromMs, mediaSourceToMs, abortController.signal);
23
+ signal?.throwIfAborted();
24
+ const audio = await mediaElement.fetchAudioSpanningTime(mediaSourceFromMs, mediaSourceToMs, signal);
21
25
  if (!audio) return;
22
26
  const bufferSource = audioContext.createBufferSource();
23
- bufferSource.buffer = await audioContext.decodeAudioData(await audio.blob.arrayBuffer());
27
+ let decodedBuffer;
28
+ try {
29
+ const arrayBuffer = await audio.blob.arrayBuffer();
30
+ if (arrayBuffer.byteLength < 100) return;
31
+ decodedBuffer = await audioContext.decodeAudioData(arrayBuffer);
32
+ } catch (decodeError) {
33
+ if (decodeError instanceof Error && decodeError.message.includes("Unable to decode audio data")) return;
34
+ throw decodeError;
35
+ }
36
+ bufferSource.buffer = decodedBuffer;
24
37
  bufferSource.connect(audioContext.destination);
25
38
  const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
26
39
  const offsetInBufferMs = mediaSourceFromMs - audio.startMs;
@@ -1 +1 @@
1
- {"version":3,"file":"renderTemporalAudio.js","names":[],"sources":["../../src/elements/renderTemporalAudio.ts"],"sourcesContent":["import type { EFMedia } from \"./EFMedia.js\";\n\ninterface TemporalAudioHost {\n startTimeMs: number;\n endTimeMs: number;\n durationMs: number;\n getMediaElements(): EFMedia[];\n waitForMediaDurations?(): Promise<void>;\n}\n\nexport async function renderTemporalAudio(\n host: TemporalAudioHost,\n fromMs: number,\n toMs: number,\n): Promise<AudioBuffer> {\n const durationMs = toMs - fromMs;\n const duration = durationMs / 1000;\n const exactSamples = 48000 * duration;\n const aacFrames = exactSamples / 1024;\n const alignedFrames = Math.round(aacFrames);\n const contextSize = alignedFrames * 1024;\n\n if (contextSize <= 0) {\n throw new Error(\n `Duration must be greater than 0 when rendering audio. ${contextSize}ms`,\n );\n }\n\n const audioContext = new OfflineAudioContext(2, contextSize, 48000);\n\n if (host.waitForMediaDurations) {\n await host.waitForMediaDurations();\n }\n\n const abortController = new AbortController();\n\n await Promise.all(\n host.getMediaElements().map(async (mediaElement) => {\n if (mediaElement.mute) {\n return;\n }\n\n const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;\n const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;\n const mediaOverlaps = mediaStartsBeforeEnd && mediaEndsAfterStart;\n if (!mediaOverlaps) {\n return;\n }\n\n const mediaLocalFromMs = Math.max(0, fromMs - mediaElement.startTimeMs);\n const mediaLocalToMs = Math.min(\n mediaElement.endTimeMs - mediaElement.startTimeMs,\n toMs - mediaElement.startTimeMs,\n );\n\n if (mediaLocalFromMs >= mediaLocalToMs) {\n return;\n }\n\n const sourceInMs =\n mediaElement.sourceInMs || mediaElement.trimStartMs || 0;\n const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;\n const mediaSourceToMs = mediaLocalToMs + sourceInMs;\n\n const audio = await mediaElement.fetchAudioSpanningTime(\n mediaSourceFromMs,\n mediaSourceToMs,\n abortController.signal,\n );\n if (!audio) {\n return;\n }\n\n const bufferSource = audioContext.createBufferSource();\n bufferSource.buffer = await audioContext.decodeAudioData(\n await audio.blob.arrayBuffer(),\n );\n bufferSource.connect(audioContext.destination);\n\n const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);\n\n const requestedSourceFromMs = mediaSourceFromMs;\n const actualSourceStartMs = audio.startMs;\n const offsetInBufferMs = requestedSourceFromMs - actualSourceStartMs;\n\n const safeOffsetMs = Math.max(0, offsetInBufferMs);\n\n const requestedDurationMs = mediaSourceToMs - mediaSourceFromMs;\n const availableAudioMs = audio.endMs - audio.startMs;\n const actualDurationMs = Math.min(\n requestedDurationMs,\n availableAudioMs - safeOffsetMs,\n );\n\n if (actualDurationMs <= 0) {\n return;\n }\n\n bufferSource.start(\n ctxStartMs / 1000,\n safeOffsetMs / 1000,\n actualDurationMs / 1000,\n );\n }),\n );\n\n return audioContext.startRendering();\n}\n"],"mappings":";AAUA,eAAsB,oBACpB,MACA,QACA,MACsB;CAItB,MAAM,YADe,SAFF,OAAO,UACI,OAEG;CAEjC,MAAM,cADgB,KAAK,MAAM,UAAU,GACP;AAEpC,KAAI,eAAe,EACjB,OAAM,IAAI,MACR,yDAAyD,YAAY,IACtE;CAGH,MAAM,eAAe,IAAI,oBAAoB,GAAG,aAAa,KAAM;AAEnE,KAAI,KAAK,sBACP,OAAM,KAAK,uBAAuB;CAGpC,MAAM,kBAAkB,IAAI,iBAAiB;AAE7C,OAAM,QAAQ,IACZ,KAAK,kBAAkB,CAAC,IAAI,OAAO,iBAAiB;AAClD,MAAI,aAAa,KACf;EAGF,MAAM,uBAAuB,aAAa,eAAe;EACzD,MAAM,sBAAsB,aAAa,aAAa;AAEtD,MAAI,EADkB,wBAAwB,qBAE5C;EAGF,MAAM,mBAAmB,KAAK,IAAI,GAAG,SAAS,aAAa,YAAY;EACvE,MAAM,iBAAiB,KAAK,IAC1B,aAAa,YAAY,aAAa,aACtC,OAAO,aAAa,YACrB;AAED,MAAI,oBAAoB,eACtB;EAGF,MAAM,aACJ,aAAa,cAAc,aAAa,eAAe;EACzD,MAAM,oBAAoB,mBAAmB;EAC7C,MAAM,kBAAkB,iBAAiB;EAEzC,MAAM,QAAQ,MAAM,aAAa,uBAC/B,mBACA,iBACA,gBAAgB,OACjB;AACD,MAAI,CAAC,MACH;EAGF,MAAM,eAAe,aAAa,oBAAoB;AACtD,eAAa,SAAS,MAAM,aAAa,gBACvC,MAAM,MAAM,KAAK,aAAa,CAC/B;AACD,eAAa,QAAQ,aAAa,YAAY;EAE9C,MAAM,aAAa,KAAK,IAAI,GAAG,aAAa,cAAc,OAAO;EAIjE,MAAM,mBAFwB,oBACF,MAAM;EAGlC,MAAM,eAAe,KAAK,IAAI,GAAG,iBAAiB;EAElD,MAAM,sBAAsB,kBAAkB;EAC9C,MAAM,mBAAmB,MAAM,QAAQ,MAAM;EAC7C,MAAM,mBAAmB,KAAK,IAC5B,qBACA,mBAAmB,aACpB;AAED,MAAI,oBAAoB,EACtB;AAGF,eAAa,MACX,aAAa,KACb,eAAe,KACf,mBAAmB,IACpB;GACD,CACH;AAED,QAAO,aAAa,gBAAgB"}
1
+ {"version":3,"file":"renderTemporalAudio.js","names":[],"sources":["../../src/elements/renderTemporalAudio.ts"],"sourcesContent":["import type { EFMedia } from \"./EFMedia.js\";\n\ninterface TemporalAudioHost {\n startTimeMs: number;\n endTimeMs: number;\n durationMs: number;\n getMediaElements(): EFMedia[];\n waitForMediaDurations?(): Promise<void>;\n}\n\nexport async function renderTemporalAudio(\n host: TemporalAudioHost,\n fromMs: number,\n toMs: number,\n signal?: AbortSignal,\n): Promise<AudioBuffer> {\n const durationMs = toMs - fromMs;\n const duration = durationMs / 1000;\n const exactSamples = 48000 * duration;\n const aacFrames = exactSamples / 1024;\n const alignedFrames = Math.round(aacFrames);\n const contextSize = alignedFrames * 1024;\n\n if (contextSize <= 0) {\n throw new Error(\n `Duration must be greater than 0 when rendering audio. ${contextSize}ms`,\n );\n }\n\n // Check abort before starting\n signal?.throwIfAborted();\n\n const audioContext = new OfflineAudioContext(2, contextSize, 48000);\n\n if (host.waitForMediaDurations) {\n await host.waitForMediaDurations();\n // Check abort after potentially slow operation\n signal?.throwIfAborted();\n }\n\n await Promise.all(\n host.getMediaElements().map(async (mediaElement) => {\n if (mediaElement.mute) {\n return;\n }\n\n const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;\n const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;\n const mediaOverlaps = mediaStartsBeforeEnd && mediaEndsAfterStart;\n if (!mediaOverlaps) {\n return;\n }\n\n const mediaLocalFromMs = Math.max(0, fromMs - mediaElement.startTimeMs);\n const mediaLocalToMs = Math.min(\n mediaElement.endTimeMs - mediaElement.startTimeMs,\n toMs - mediaElement.startTimeMs,\n );\n\n if (mediaLocalFromMs >= mediaLocalToMs) {\n return;\n }\n\n const sourceInMs =\n mediaElement.sourceInMs || mediaElement.trimStartMs || 0;\n const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;\n const mediaSourceToMs = mediaLocalToMs + sourceInMs;\n\n // Check abort before processing each media element\n signal?.throwIfAborted();\n \n const audio = await mediaElement.fetchAudioSpanningTime(\n mediaSourceFromMs,\n mediaSourceToMs,\n signal,\n );\n if (!audio) {\n return;\n }\n\n const bufferSource = audioContext.createBufferSource();\n \n // Decode audio data with error handling for invalid/incomplete audio\n let decodedBuffer;\n try {\n const arrayBuffer = await audio.blob.arrayBuffer();\n // Skip if buffer is too small to be valid audio\n if (arrayBuffer.byteLength < 100) {\n return;\n }\n decodedBuffer = await audioContext.decodeAudioData(arrayBuffer);\n } catch (decodeError) {\n // Unable to decode audio data - skip this segment silently\n // This can happen with corrupted/incomplete audio segments\n if (decodeError instanceof Error && \n decodeError.message.includes(\"Unable to decode audio data\")) {\n return;\n }\n throw decodeError;\n }\n \n bufferSource.buffer = decodedBuffer;\n bufferSource.connect(audioContext.destination);\n\n const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);\n\n const requestedSourceFromMs = mediaSourceFromMs;\n const actualSourceStartMs = audio.startMs;\n const offsetInBufferMs = requestedSourceFromMs - actualSourceStartMs;\n\n const safeOffsetMs = Math.max(0, offsetInBufferMs);\n\n const requestedDurationMs = mediaSourceToMs - mediaSourceFromMs;\n const availableAudioMs = audio.endMs - audio.startMs;\n const actualDurationMs = Math.min(\n requestedDurationMs,\n availableAudioMs - safeOffsetMs,\n );\n\n if (actualDurationMs <= 0) {\n return;\n }\n\n bufferSource.start(\n ctxStartMs / 1000,\n safeOffsetMs / 1000,\n actualDurationMs / 1000,\n );\n }),\n );\n\n return audioContext.startRendering();\n}\n"],"mappings":";AAUA,eAAsB,oBACpB,MACA,QACA,MACA,QACsB;CAItB,MAAM,YADe,SAFF,OAAO,UACI,OAEG;CAEjC,MAAM,cADgB,KAAK,MAAM,UAAU,GACP;AAEpC,KAAI,eAAe,EACjB,OAAM,IAAI,MACR,yDAAyD,YAAY,IACtE;AAIH,SAAQ,gBAAgB;CAExB,MAAM,eAAe,IAAI,oBAAoB,GAAG,aAAa,KAAM;AAEnE,KAAI,KAAK,uBAAuB;AAC9B,QAAM,KAAK,uBAAuB;AAElC,UAAQ,gBAAgB;;AAG1B,OAAM,QAAQ,IACZ,KAAK,kBAAkB,CAAC,IAAI,OAAO,iBAAiB;AAClD,MAAI,aAAa,KACf;EAGF,MAAM,uBAAuB,aAAa,eAAe;EACzD,MAAM,sBAAsB,aAAa,aAAa;AAEtD,MAAI,EADkB,wBAAwB,qBAE5C;EAGF,MAAM,mBAAmB,KAAK,IAAI,GAAG,SAAS,aAAa,YAAY;EACvE,MAAM,iBAAiB,KAAK,IAC1B,aAAa,YAAY,aAAa,aACtC,OAAO,aAAa,YACrB;AAED,MAAI,oBAAoB,eACtB;EAGF,MAAM,aACJ,aAAa,cAAc,aAAa,eAAe;EACzD,MAAM,oBAAoB,mBAAmB;EAC7C,MAAM,kBAAkB,iBAAiB;AAGzC,UAAQ,gBAAgB;EAExB,MAAM,QAAQ,MAAM,aAAa,uBAC/B,mBACA,iBACA,OACD;AACD,MAAI,CAAC,MACH;EAGF,MAAM,eAAe,aAAa,oBAAoB;EAGtD,IAAI;AACJ,MAAI;GACF,MAAM,cAAc,MAAM,MAAM,KAAK,aAAa;AAElD,OAAI,YAAY,aAAa,IAC3B;AAEF,mBAAgB,MAAM,aAAa,gBAAgB,YAAY;WACxD,aAAa;AAGpB,OAAI,uBAAuB,SACvB,YAAY,QAAQ,SAAS,8BAA8B,CAC7D;AAEF,SAAM;;AAGR,eAAa,SAAS;AACtB,eAAa,QAAQ,aAAa,YAAY;EAE9C,MAAM,aAAa,KAAK,IAAI,GAAG,aAAa,cAAc,OAAO;EAIjE,MAAM,mBAFwB,oBACF,MAAM;EAGlC,MAAM,eAAe,KAAK,IAAI,GAAG,iBAAiB;EAElD,MAAM,sBAAsB,kBAAkB;EAC9C,MAAM,mBAAmB,MAAM,QAAQ,MAAM;EAC7C,MAAM,mBAAmB,KAAK,IAC5B,qBACA,mBAAmB,aACpB;AAED,MAAI,oBAAoB,EACtB;AAGF,eAAa,MACX,aAAa,KACb,eAAe,KACf,mBAAmB,IACpB;GACD,CACH;AAED,QAAO,aAAa,gBAAgB"}