@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
@@ -1 +0,0 @@
1
- {"version":3,"file":"renderTimegroupPreview.js","names":["CSS_SAFE_DEFAULT_VALUES: Record<string, string | string[]>","removed: RemovedNodeInfo[]","cs: CSSStyleDeclaration","contentCs: CSSStyleDeclaration | undefined","srcMap: StylePropertyMapReadOnly","node: CloneNode","node","clone","clone: HTMLElement","syncState: SyncState","syncStats: SyncStats","rules: string[]"],"sources":["../../src/preview/renderTimegroupPreview.ts"],"sourcesContent":["/**\n * Canvas refresh fix:\n * Canvas pixels must be explicitly cleared and redrawn in syncNodeStyles\n * to ensure video frames are captured correctly during foreignObject rendering.\n * Without clearRect, stale frames from previous seeks may be serialized.\n * \n * See FOREIGNOBJECT_BUG_FIX.md for detailed explanation.\n */\n\nimport { logger } from \"./logger.js\";\n\n/**\n * Elements to skip entirely when building the preview.\n */\nconst SKIP_TAGS = new Set([\n \"EF-AUDIO\",\n \"EF-THUMBNAIL-STRIP\",\n \"EF-FILMSTRIP\",\n \"EF-TIMELINE\",\n \"EF-WORKBENCH\",\n \"SCRIPT\",\n \"STYLE\",\n]);\n\n/**\n * All CSS properties to sync (camelCase for style[] access).\n */\nconst SYNC_PROPERTIES = [\n \"display\", \"visibility\", \"opacity\",\n \"position\", \"top\", \"right\", \"bottom\", \"left\", \"zIndex\",\n \"width\", \"height\", \"minWidth\", \"minHeight\", \"maxWidth\", \"maxHeight\",\n \"flexGrow\", \"flexShrink\", \"flexBasis\", \"flexDirection\", \"flexWrap\",\n \"justifyContent\", \"alignItems\", \"alignContent\", \"alignSelf\", \"gap\",\n \"gridTemplate\", \"gridColumn\", \"gridRow\", \"gridArea\",\n \"margin\", \"padding\", \"boxSizing\",\n \"border\", \"borderTop\", \"borderRight\", \"borderBottom\", \"borderLeft\", \"borderRadius\",\n \"background\", \"color\", \"boxShadow\", \"filter\", \"backdropFilter\", \"clipPath\",\n \"fontFamily\", \"fontSize\", \"fontWeight\", \"fontStyle\", \"fontVariant\",\n \"textAlign\", \"textDecoration\", \"textTransform\",\n \"letterSpacing\", \"wordSpacing\", \"whiteSpace\", \"textOverflow\", \"lineHeight\",\n \"verticalAlign\",\n \"transform\", \"transformOrigin\", \"transformStyle\",\n \"perspective\", \"perspectiveOrigin\", \"backfaceVisibility\",\n \"cursor\", \"pointerEvents\", \"userSelect\", \"overflow\",\n] as const;\n\n/**\n * Kebab-case versions for computedStyleMap.get() - pre-computed for speed.\n */\nconst SYNC_PROPERTIES_KEBAB = SYNC_PROPERTIES.map(prop =>\n prop.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`)\n);\n\n/**\n * Feature detection: computedStyleMap is ~15% faster for style syncing.\n */\nconst HAS_COMPUTED_STYLE_MAP = typeof Element !== \"undefined\" && typeof Element.prototype.computedStyleMap === \"function\";\n\n/**\n * CSS initial/default values for SAFE-TO-SKIP properties.\n * Only includes NON-INHERITED properties where skipping the default\n * won't affect visual output.\n * \n * EXCLUDED (must always serialize):\n * - Inherited properties (color, font, text-*, visibility, cursor)\n * - Display (affects layout significantly)\n * - Properties where \"auto\" computes to a specific value\n * \n * INCLUDED (safe to skip):\n * - Transform/filter effects (none = no effect)\n * - Box shadows (none = no shadow)\n * - Borders when none/0 (no visual impact)\n * - backdrop-filter (none = no effect)\n */\nconst CSS_SAFE_DEFAULT_VALUES: Record<string, string | string[]> = {\n // Transforms & effects - safe to skip \"none\" (no visual impact)\n transform: \"none\",\n filter: \"none\",\n backdropFilter: \"none\",\n boxShadow: \"none\",\n \n // Borders - safe to skip when none/0\n border: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderTop: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderRight: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderBottom: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderLeft: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderRadius: [\"0px\", \"0\"],\n \n // Positioning - safe to skip \"static\"\n position: \"static\",\n \n // Z-index - \"auto\" is safe when position is static\n zIndex: \"auto\",\n \n // 3D transforms - safe to skip defaults\n transformStyle: \"flat\",\n perspective: \"none\",\n backfaceVisibility: \"visible\",\n};\n\n/**\n * Check if a value matches a safe-to-skip default.\n */\nfunction isDefaultValue(prop: string, value: string): boolean {\n const defaults = CSS_SAFE_DEFAULT_VALUES[prop];\n if (!defaults) return false;\n if (Array.isArray(defaults)) {\n return defaults.includes(value);\n }\n return defaults === value;\n}\n\n// Re-export temporal types from shared module\nexport {\n type TemporalElement,\n isTemporal,\n getTemporalBounds,\n isVisibleAtTime,\n} from \"./previewTypes.js\";\n\n// Import for internal use\nimport {\n getTemporalBounds,\n} from \"./previewTypes.js\";\n\n/**\n * Tree node representing a source/clone pair with children.\n * This replaces the flat array approach for cleaner recursive traversal.\n */\nexport interface CloneNode {\n source: Element;\n clone: HTMLElement;\n children: CloneNode[];\n isCanvasClone: boolean;\n /** Cached temporal bounds for this node */\n bounds: { startMs: number; endMs: number };\n /** Parent node reference for ancestor visibility checks */\n parent: CloneNode | null;\n}\n\n/** Tree-based sync state */\nexport interface CloneTree {\n root: CloneNode | null;\n}\n\n/** Sync state with tree structure and delta tracking */\nexport interface SyncState {\n tree: CloneTree;\n nodeCount: number; // Total number of nodes (for debugging/logging)\n /** Maps clone canvases to their original source elements (ef-video, ef-image, etc.) */\n canvasSourceMap: WeakMap<HTMLCanvasElement, Element>;\n /** Previous frame's visible set for delta tracking */\n previousVisibleSet: Set<CloneNode>;\n /** Current frame's visible set (updated by syncStyles) */\n currentVisibleSet: Set<CloneNode>;\n}\n\n/** Info needed to restore a removed node */\ninterface RemovedNodeInfo {\n node: CloneNode;\n parent: Node;\n nextSibling: Node | null;\n}\n\n/**\n * Remove hidden nodes from the clone DOM for serialization.\n * Returns info needed to restore them afterward.\n * \n * This physically removes non-visible nodes so they won't be serialized,\n * avoiding the cost of serializing hidden elements and their resources.\n */\nexport function removeHiddenNodesForSerialization(state: SyncState): RemovedNodeInfo[] {\n const removed: RemovedNodeInfo[] = [];\n const visibleSet = state.currentVisibleSet;\n \n // Traverse all nodes and remove those not in visible set\n function visit(node: CloneNode): void {\n // First recurse to children (before potentially removing this node)\n for (const child of node.children) {\n visit(child);\n }\n \n // If this node isn't visible, remove it from DOM\n if (!visibleSet.has(node)) {\n const parent = node.clone.parentNode;\n if (parent) {\n const nextSibling = node.clone.nextSibling;\n parent.removeChild(node.clone);\n removed.push({ node, parent, nextSibling });\n }\n }\n }\n \n if (state.tree.root) {\n visit(state.tree.root);\n }\n \n return removed;\n}\n\n/**\n * Restore previously removed hidden nodes to the clone DOM.\n * Must be called after serialization to maintain tree integrity for next frame.\n */\nexport function restoreHiddenNodes(removed: RemovedNodeInfo[]): void {\n // Restore in reverse order to maintain correct DOM positions\n for (let i = removed.length - 1; i >= 0; i--) {\n const { node, parent, nextSibling } = removed[i]!;\n if (nextSibling) {\n parent.insertBefore(node.clone, nextSibling);\n } else {\n parent.appendChild(node.clone);\n }\n }\n}\n\n/**\n * Get visible canvases from the current visible set.\n * Use this to skip encoding hidden canvases during serialization.\n */\nexport function getVisibleCanvases(state: SyncState): Set<HTMLCanvasElement> {\n const visibleCanvases = new Set<HTMLCanvasElement>();\n for (const node of state.currentVisibleSet) {\n if (node.clone instanceof HTMLCanvasElement) {\n visibleCanvases.add(node.clone);\n }\n }\n return visibleCanvases;\n}\n\n/**\n * Traverse all nodes in the clone tree, calling the callback for each.\n */\nexport function traverseCloneTree(state: SyncState, callback: (node: CloneNode) => void): void {\n function visit(node: CloneNode): void {\n callback(node);\n for (const child of node.children) {\n visit(child);\n }\n }\n if (state.tree.root) {\n visit(state.tree.root);\n }\n}\n\n/**\n * Unified CSS property sync for all elements (canvas clones and regular elements).\n * \n * Canvas clones use a limited property set matching the original implementation\n * to avoid dimension/layout issues. Regular elements use the full SYNC_PROPERTIES array.\n * \n * @param source - Source element to read styles from\n * @param clone - Clone element to write styles to\n * @param contentSource - Optional content element for width/height (canvas clones only)\n */\nfunction syncElementStyles(\n source: Element,\n clone: HTMLElement,\n contentSource?: Element,\n): void {\n const cloneStyle = clone.style as any;\n const tagName = (source as HTMLElement).tagName;\n const isCanvasClone = !!contentSource;\n \n // Canvas clones: Use exact property list from original implementation\n if (isCanvasClone) {\n let cs: CSSStyleDeclaration;\n let contentCs: CSSStyleDeclaration | undefined;\n \n try {\n cs = getComputedStyle(source);\n if (contentSource) {\n contentCs = getComputedStyle(contentSource);\n }\n } catch { return; }\n \n // Exact properties from original copyCanvasCloneStyles + syncNodeStyles\n cloneStyle.position = cs.position;\n cloneStyle.top = cs.top;\n cloneStyle.right = cs.right;\n cloneStyle.bottom = cs.bottom;\n cloneStyle.left = cs.left;\n cloneStyle.margin = cs.margin;\n cloneStyle.zIndex = cs.zIndex;\n cloneStyle.transform = cs.transform;\n cloneStyle.transformOrigin = cs.transformOrigin;\n cloneStyle.opacity = cs.opacity;\n cloneStyle.visibility = cs.visibility;\n cloneStyle.backfaceVisibility = cs.backfaceVisibility;\n cloneStyle.transformStyle = cs.transformStyle;\n \n // Visual properties (safe for canvas clones - don't affect dimensions)\n cloneStyle.background = cs.background;\n cloneStyle.color = cs.color;\n cloneStyle.boxShadow = cs.boxShadow;\n cloneStyle.filter = cs.filter;\n cloneStyle.backdropFilter = cs.backdropFilter;\n \n // Width/height from content source (shadow canvas/img)\n if (contentCs) {\n cloneStyle.width = contentCs.width;\n cloneStyle.height = contentCs.height;\n }\n \n cloneStyle.display = \"block\";\n cloneStyle.animation = \"none\";\n cloneStyle.transition = \"none\";\n \n return;\n }\n \n // Regular elements: full property sync from SYNC_PROPERTIES\n const propLen = SYNC_PROPERTIES.length;\n \n if (HAS_COMPUTED_STYLE_MAP) {\n let srcMap: StylePropertyMapReadOnly;\n \n try {\n srcMap = source.computedStyleMap();\n } catch { return; }\n \n for (let j = 0; j < propLen; j++) {\n const kebab = SYNC_PROPERTIES_KEBAB[j]!;\n const camel = SYNC_PROPERTIES[j]!;\n \n const srcVal = srcMap.get(kebab);\n if (!srcVal) continue;\n \n const strVal = srcVal.toString();\n \n if (camel === \"display\") {\n // For caption child elements, preserve display:none when explicitly set\n // (they use it to hide empty content, not for temporal visibility)\n const isCaptionChild = tagName && (\n tagName === 'EF-CAPTIONS-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-BEFORE-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-AFTER-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-SEGMENT'\n );\n const targetDisplay = (strVal === \"none\" && !isCaptionChild) ? \"block\" : strVal;\n cloneStyle.display = targetDisplay;\n continue;\n }\n \n // Skip clipPath - clones always have clipPath: none for rendering\n // (source may have clip-path: inset(100%) from proxy mode)\n if (camel === \"clipPath\") continue;\n \n // OPTIMIZATION: Skip default values to reduce serialized HTML size\n // If the computed value is the CSS default, don't set it as inline style\n if (isDefaultValue(camel, strVal)) {\n // Remove from inline style if it was previously set\n if (cloneStyle[camel]) cloneStyle[camel] = \"\";\n continue;\n }\n \n cloneStyle[camel] = strVal;\n }\n } else {\n let cs: CSSStyleDeclaration;\n \n try {\n cs = getComputedStyle(source);\n } catch { return; }\n \n const srcStyle = cs as any;\n \n for (const prop of SYNC_PROPERTIES) {\n const srcVal = srcStyle[prop];\n if (!srcVal) continue;\n \n if (prop === \"display\") {\n // For caption child elements, preserve display:none when explicitly set\n // (they use it to hide empty content, not for temporal visibility)\n const isCaptionChild = tagName && (\n tagName === 'EF-CAPTIONS-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-BEFORE-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-AFTER-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-SEGMENT'\n );\n const targetDisplay = (srcVal === \"none\" && !isCaptionChild) ? \"block\" : srcVal;\n cloneStyle.display = targetDisplay;\n continue;\n }\n \n // Skip clipPath - clones always have clipPath: none for rendering\n // (source may have clip-path: inset(100%) from proxy mode)\n if (prop === \"clipPath\") continue;\n \n // OPTIMIZATION: Skip default values to reduce serialized HTML size\n if (isDefaultValue(prop, srcVal)) {\n if (cloneStyle[prop]) cloneStyle[prop] = \"\";\n continue;\n }\n \n cloneStyle[prop] = srcVal;\n }\n }\n \n // Disable animations/transitions to prevent re-animation\n cloneStyle.animation = \"none\";\n cloneStyle.transition = \"none\";\n}\n\n/**\n * Refresh canvas pixel content from shadow DOM source.\n * Handles both shadow canvas and shadow img sources.\n */\nfunction refreshCanvasPixels(node: CloneNode): void {\n const { source, clone } = node;\n const canvas = clone as HTMLCanvasElement;\n const shadowCanvas = source.shadowRoot?.querySelector(\"canvas\");\n const shadowImg = source.shadowRoot?.querySelector(\"img\");\n \n if (shadowCanvas) {\n // Update buffer dimensions if needed\n if (canvas.width !== shadowCanvas.width) canvas.width = shadowCanvas.width;\n if (canvas.height !== shadowCanvas.height) canvas.height = shadowCanvas.height;\n \n // Copy pixels with explicit clear\n const ctx = canvas.getContext(\"2d\");\n if (ctx && shadowCanvas.width > 0 && shadowCanvas.height > 0) {\n try {\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.drawImage(shadowCanvas, 0, 0);\n } catch (e) {\n logger.warn(\"[refreshCanvasPixels] Canvas draw failed:\", e);\n }\n }\n } else if (shadowImg?.complete && shadowImg.naturalWidth > 0) {\n // Update buffer dimensions if needed\n if (canvas.width !== shadowImg.naturalWidth) canvas.width = shadowImg.naturalWidth;\n if (canvas.height !== shadowImg.naturalHeight) canvas.height = shadowImg.naturalHeight;\n \n // Copy pixels with explicit clear\n const ctx = canvas.getContext(\"2d\");\n if (ctx) {\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n try { ctx.drawImage(shadowImg, 0, 0); } catch {}\n }\n }\n}\n\n/**\n * Sync text content from light DOM to clone.\n */\nfunction syncTextContent(source: Element, clone: HTMLElement): void {\n const srcTextNode = source.childNodes[0];\n if (srcTextNode?.nodeType === Node.TEXT_NODE) {\n const srcText = srcTextNode.textContent || \"\";\n const cloneTextNode = clone.childNodes[0];\n \n if (cloneTextNode?.nodeType === Node.TEXT_NODE) {\n // Update existing text node\n if (cloneTextNode.textContent !== srcText) cloneTextNode.textContent = srcText;\n } else if (!clone.childNodes.length) {\n // Only create text node if clone has NO children (was empty when initially cloned)\n // Don't set textContent as it would delete element children!\n clone.appendChild(document.createTextNode(srcText));\n }\n }\n}\n\n/**\n * Sync input element value.\n */\nfunction syncInputValue(source: Element, clone: HTMLElement): void {\n if (source instanceof HTMLInputElement) {\n const srcVal = source.value;\n const cloneInput = clone as HTMLInputElement;\n if (cloneInput.value !== srcVal) {\n cloneInput.value = srcVal;\n cloneInput.setAttribute(\"value\", srcVal);\n }\n }\n}\n\n/**\n * Build clone tree structure with minimal overhead.\n * Caches temporal bounds on each node for visibility checks.\n * Optionally syncs styles in the same pass if timeMs is provided.\n */\nexport function buildCloneStructure(source: Element, timeMs?: number): {\n container: HTMLDivElement;\n syncState: SyncState;\n} {\n const container = document.createElement(\"div\");\n container.style.cssText = \"position:absolute;top:0;left:0;width:100%;height:100%\";\n \n let nodeCount = 0;\n const canvasSourceMap = new WeakMap<HTMLCanvasElement, Element>();\n \n function cloneElement(srcEl: Element, parentNode: CloneNode | null): CloneNode | null {\n if (SKIP_TAGS.has(srcEl.tagName)) return null;\n \n // Get temporal bounds upfront for indexing\n const bounds = getTemporalBounds(srcEl);\n \n // Canvas - copy pixels\n // NOTE: Raw canvases are always recopied (no caching) since we can't detect when their content changes.\n // Long-term solution: Create EFCanvas wrapper element to track modifications.\n if (srcEl instanceof HTMLCanvasElement) {\n const canvas = document.createElement(\"canvas\");\n // Use intrinsic buffer dimensions (not affected by zoom/transforms)\n canvas.width = srcEl.width;\n canvas.height = srcEl.height;\n const ctx = canvas.getContext(\"2d\");\n if (ctx) {\n try { ctx.drawImage(srcEl, 0, 0); } catch {}\n }\n \n // Set explicit CSS dimensions based on buffer size to avoid zoom-affected computed styles\n // This ensures the canvas renders at its natural size regardless of workspace zoom\n canvas.style.width = `${srcEl.width}px`;\n canvas.style.height = `${srcEl.height}px`;\n \n // Sync positioning/transform styles from source, but dimensions are already set above\n try {\n const cs = getComputedStyle(srcEl);\n canvas.style.position = cs.position;\n canvas.style.top = cs.top;\n canvas.style.right = cs.right;\n canvas.style.bottom = cs.bottom;\n canvas.style.left = cs.left;\n canvas.style.margin = cs.margin;\n canvas.style.zIndex = cs.zIndex;\n canvas.style.transform = cs.transform;\n canvas.style.transformOrigin = cs.transformOrigin;\n canvas.style.opacity = cs.opacity;\n canvas.style.visibility = cs.visibility;\n canvas.style.display = \"block\";\n } catch {}\n \n // Map clone canvas to source for RenderContext (though caching won't help here)\n canvasSourceMap.set(canvas, srcEl);\n \n const node: CloneNode = {\n source: srcEl,\n clone: canvas,\n children: [],\n isCanvasClone: true,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n return node;\n }\n \n // Custom elements with shadow canvas (e.g., ef-video, ef-image)\n const isCustom = srcEl.tagName.includes(\"-\");\n if (isCustom && srcEl.shadowRoot) {\n const shadowCanvas = srcEl.shadowRoot.querySelector(\"canvas\");\n if (shadowCanvas) {\n const clone = document.createElement(\"canvas\");\n clone.width = shadowCanvas.width || srcEl.clientWidth;\n clone.height = shadowCanvas.height || srcEl.clientHeight;\n // Check if the element actually has alpha channel before preserving it\n // ef-image tracks hasAlpha based on MIME type (JPEG=false, PNG/WebP=true)\n // ef-waveform always needs alpha for proper rendering\n if (srcEl.tagName === \"EF-WAVEFORM\") {\n clone.dataset.preserveAlpha = \"true\";\n } else if (srcEl.tagName === \"EF-IMAGE\") {\n const hasAlpha = \"hasAlpha\" in srcEl && (srcEl as any).hasAlpha;\n if (hasAlpha) {\n clone.dataset.preserveAlpha = \"true\";\n }\n console.log(`[buildCloneStructure] EF-IMAGE canvas: size=${clone.width}x${clone.height}, hasAlpha=${hasAlpha}, preserveAlpha=${hasAlpha ? 'PNG' : 'JPEG'}`);\n }\n \n const ctx = clone.getContext(\"2d\");\n if (ctx) {\n try { ctx.drawImage(shadowCanvas, 0, 0); } catch {}\n }\n \n // Copy initial CSS styles using unified sync\n // Pass shadowCanvas as contentSource for width/height\n try {\n syncElementStyles(srcEl, clone, shadowCanvas);\n } catch {}\n \n // Map clone canvas to source element for RenderContext caching\n canvasSourceMap.set(clone, srcEl);\n \n const node: CloneNode = {\n source: srcEl,\n clone,\n children: [],\n isCanvasClone: true,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n return node;\n }\n \n const shadowImg = srcEl.shadowRoot.querySelector(\"img\");\n if (shadowImg?.complete && shadowImg.naturalWidth > 0) {\n const clone = document.createElement(\"canvas\");\n clone.width = shadowImg.naturalWidth;\n clone.height = shadowImg.naturalHeight;\n // Check if the element actually has alpha channel before preserving it\n // For direct img elements, check the element's hasAlpha property\n if (srcEl.tagName === \"EF-IMAGE\") {\n const hasAlpha = \"hasAlpha\" in srcEl && (srcEl as any).hasAlpha;\n if (hasAlpha) {\n clone.dataset.preserveAlpha = \"true\";\n }\n }\n const ctx = clone.getContext(\"2d\");\n if (ctx) {\n try { ctx.drawImage(shadowImg, 0, 0); } catch {}\n }\n \n // Copy initial CSS styles using unified sync\n // Pass shadowImg as contentSource for width/height\n try {\n syncElementStyles(srcEl, clone, shadowImg);\n } catch {}\n \n // Map clone canvas to source element for RenderContext caching\n canvasSourceMap.set(clone, srcEl);\n \n const node: CloneNode = {\n source: srcEl,\n clone,\n children: [],\n isCanvasClone: true,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n return node;\n }\n }\n \n // Standard element clone\n // SVG elements need createElementNS, HTML elements use createElement\n let clone: HTMLElement;\n if (srcEl instanceof SVGElement) {\n clone = document.createElementNS(\"http://www.w3.org/2000/svg\", srcEl.tagName) as unknown as HTMLElement;\n } else {\n clone = document.createElement(isCustom ? \"div\" : srcEl.tagName.toLowerCase()) as HTMLElement;\n }\n \n // Copy attributes - OPTIMIZATION: Early exit if no attributes\n const attrs = srcEl.attributes;\n const attrLen = attrs.length;\n if (attrLen > 0) {\n for (let i = 0; i < attrLen; i++) {\n const attr = attrs[i]!;\n const name = attr.name.toLowerCase();\n if (name === \"id\" || name.startsWith(\"on\")) continue;\n if (isCustom && name !== \"class\" && !name.startsWith(\"data-\")) continue;\n try { clone.setAttribute(attr.name, attr.value); } catch {}\n }\n }\n \n if (srcEl instanceof HTMLImageElement && srcEl.src) {\n (clone as HTMLImageElement).src = srcEl.src;\n }\n if (srcEl instanceof HTMLInputElement) {\n (clone as HTMLInputElement).value = srcEl.value;\n }\n \n const node: CloneNode = {\n source: srcEl,\n clone,\n children: [],\n isCanvasClone: false,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n \n // Shadow DOM children - OPTIMIZATION: Early exit if no childNodes\n if (srcEl.shadowRoot) {\n const shadowChildren = srcEl.shadowRoot.childNodes;\n const shadowLen = shadowChildren.length;\n if (shadowLen > 0) {\n // For text segments, ALWAYS create a text node placeholder even if empty.\n // Caption elements now use light DOM, so they don't need special handling here.\n const isTextSegment = srcEl.tagName === 'EF-TEXT-SEGMENT';\n let hasTextNode = false;\n \n for (let i = 0; i < shadowLen; i++) {\n const child = shadowChildren[i]!;\n if (child.nodeType === Node.TEXT_NODE) {\n const text = child.textContent?.trim();\n // Always include text for text segments (even if whitespace-only, e.g., \" \")\n if (text || isTextSegment) {\n clone.appendChild(document.createTextNode(child.textContent || \"\"));\n hasTextNode = true;\n }\n } else if (child.nodeType === Node.ELEMENT_NODE) {\n const el = child as Element;\n if (el.tagName === \"STYLE\" || el.tagName === \"SLOT\") continue;\n const childNode = cloneElement(el, node);\n if (childNode) {\n node.children.push(childNode);\n clone.appendChild(childNode.clone);\n }\n }\n }\n \n // For text segments, ensure there's always a text node for syncStyles to update\n if (isTextSegment && !hasTextNode) {\n clone.appendChild(document.createTextNode(\"\"));\n }\n }\n }\n \n // Light DOM children - OPTIMIZATION: Use indexed loop for performance\n const lightChildren = srcEl.childNodes;\n const lightLen = lightChildren.length;\n for (let i = 0; i < lightLen; i++) {\n const child = lightChildren[i]!;\n if (child.nodeType === Node.TEXT_NODE) {\n const text = child.textContent?.trim();\n if (text) clone.appendChild(document.createTextNode(text));\n } else if (child.nodeType === Node.ELEMENT_NODE) {\n const childNode = cloneElement(child as Element, node);\n if (childNode) {\n node.children.push(childNode);\n clone.appendChild(childNode.clone);\n }\n }\n }\n \n return node;\n }\n \n const root = cloneElement(source, null);\n if (root) container.appendChild(root.clone);\n \n const syncState: SyncState = {\n tree: { root },\n nodeCount,\n canvasSourceMap,\n previousVisibleSet: new Set(),\n currentVisibleSet: new Set(),\n };\n \n // Sync styles in the same pass if timeMs is provided\n if (timeMs !== undefined && root) {\n syncStylesWithIndex(syncState, timeMs);\n }\n \n return {\n container,\n syncState,\n };\n}\n\n/**\n * Sync a single node's styles (extracted for reuse).\n * Now uses unified style syncing with clear separation of concerns:\n * 1. Canvas pixel refresh (if canvas clone)\n * 2. Unified CSS property sync (all elements)\n * 3. Content sync (text, input values)\n */\nfunction syncNodeStyles(node: CloneNode): void {\n const { source, clone, isCanvasClone } = node;\n \n // 1. Canvas-specific: Refresh pixel content from shadow DOM\n if (isCanvasClone) {\n refreshCanvasPixels(node);\n }\n \n // 2. Unified: Sync ALL CSS properties using SYNC_PROPERTIES array\n // For canvas clones, pass content source (shadow canvas/img) for width/height\n const contentSource = isCanvasClone\n ? (source.shadowRoot?.querySelector(\"canvas\") || source.shadowRoot?.querySelector(\"img\") || undefined)\n : undefined;\n syncElementStyles(source, clone, contentSource);\n \n // 3. Element-specific: Sync text content and input values\n syncTextContent(source, clone);\n syncInputValue(source, clone);\n}\n\n// Performance instrumentation counters\ninterface SyncStats {\n nodesVisited: number;\n nodesCulledByParent: number;\n nodesCulledByTemporal: number;\n nodesProcessed: number;\n nodesFullSync: number; // Newly visible nodes (full sync)\n nodesIncrementalSync: number; // Still visible nodes (incremental sync)\n nodesHidden: number; // Newly hidden nodes\n indexQueryTimeMs: number;\n syncTimeMs: number;\n}\n\nlet syncStats: SyncStats = {\n nodesVisited: 0,\n nodesCulledByParent: 0,\n nodesCulledByTemporal: 0,\n nodesProcessed: 0,\n nodesFullSync: 0,\n nodesIncrementalSync: 0,\n nodesHidden: 0,\n indexQueryTimeMs: 0,\n syncTimeMs: 0,\n};\n\n/**\n * Visibility delta between frames.\n * Used for incremental updates - only sync what changed.\n */\ninterface VisibilityDelta {\n nowVisible: Set<CloneNode>; // Need full style sync + show\n stillVisible: Set<CloneNode>; // Only sync animated properties (or skip if same time)\n nowHidden: Set<CloneNode>; // Just set display:none\n}\n\n/**\n * Compute visibility delta between previous and current frame.\n */\nfunction computeVisibilityDelta(\n previousSet: Set<CloneNode>,\n currentSet: Set<CloneNode>,\n): VisibilityDelta {\n const nowVisible = new Set<CloneNode>();\n const stillVisible = new Set<CloneNode>();\n const nowHidden = new Set<CloneNode>();\n \n // Find nodes that became visible or stayed visible\n for (const node of currentSet) {\n if (previousSet.has(node)) {\n stillVisible.add(node);\n } else {\n nowVisible.add(node);\n }\n }\n \n // Find nodes that became hidden\n for (const node of previousSet) {\n if (!currentSet.has(node)) {\n nowHidden.add(node);\n }\n }\n \n return { nowVisible, stillVisible, nowHidden };\n}\n\n/**\n * Build visible set by recursive traversal with bounds checking.\n * Queries fresh bounds from source elements each time - bounds are computed\n * dynamically by timegroups based on composition mode.\n */\nfunction buildVisibleSetRecursive(\n node: CloneNode,\n timeMs: number,\n visibleSet: Set<CloneNode>,\n): void {\n const { children, source } = node;\n \n // Get fresh bounds from source element (not cached - timegroup bounds are dynamic)\n const bounds = getTemporalBounds(source);\n \n // Check if this node is visible at current time\n const isVisible = timeMs >= bounds.startMs && timeMs <= bounds.endMs;\n \n if (isVisible) {\n visibleSet.add(node);\n // Recurse to children\n for (const child of children) {\n buildVisibleSetRecursive(child, timeMs, visibleSet);\n }\n }\n // If not visible, skip entire subtree\n}\n\n/**\n * Sync styles with recursive visibility check and delta tracking.\n * \n * DELTA TRACKING: Tracks visibility changes between frames to minimize work:\n * - nowVisible nodes: Full style sync + show\n * - stillVisible nodes: Incremental sync (source DOM may have changed)\n * - nowHidden nodes: Just hide (display:none)\n */\nfunction syncStylesWithIndex(state: SyncState, timeMs: number): void {\n const queryStart = performance.now();\n \n // Build the set of visible nodes by recursive traversal\n const visibleSet = new Set<CloneNode>();\n if (state.tree.root) {\n buildVisibleSetRecursive(state.tree.root, timeMs, visibleSet);\n }\n \n // Compute delta from previous frame\n const delta = computeVisibilityDelta(state.previousVisibleSet, visibleSet);\n \n syncStats.indexQueryTimeMs = performance.now() - queryStart;\n \n // Now traverse the tree but use the delta for O(1) sync decisions\n const syncStart = performance.now();\n if (state.tree.root) {\n syncNodeWithDelta(state.tree.root, visibleSet, delta);\n }\n syncStats.syncTimeMs = performance.now() - syncStart;\n \n // Update state for next frame and expose current visible set\n state.previousVisibleSet = visibleSet;\n state.currentVisibleSet = visibleSet;\n}\n\n/**\n * Sync a node using visibility delta for incremental updates.\n * \n * DELTA TRACKING optimization:\n * - nowVisible: Full style sync (element just appeared)\n * - stillVisible: Incremental sync (source DOM may have changed)\n * - nowHidden: Just hide the element\n * - Not in any set: Skip entirely (was already hidden)\n */\nfunction syncNodeWithDelta(\n node: CloneNode,\n visibleSet: Set<CloneNode>,\n delta: VisibilityDelta,\n): void {\n syncStats.nodesVisited++;\n \n const isVisible = visibleSet.has(node);\n \n if (!isVisible) {\n // Node is not visible - ALWAYS set display:none\n // This handles both \"just became hidden\" and \"initial build with node outside time range\"\n node.clone.style.display = \"none\";\n if (delta.nowHidden.has(node)) {\n syncStats.nodesHidden++;\n }\n // Already hidden nodes: skip (don't even recurse to children)\n syncStats.nodesCulledByTemporal++;\n return;\n }\n \n // Node is visible - determine sync strategy\n if (delta.nowVisible.has(node)) {\n // Just became visible - need full style sync\n syncNodeStyles(node);\n syncStats.nodesFullSync++;\n } else if (delta.stillVisible.has(node)) {\n // Was visible, still visible - still need to sync\n // Source DOM properties can change independently of time (input values, text, etc.)\n // TODO: Phase 5 could track property changes for smarter incremental sync\n syncNodeStyles(node);\n syncStats.nodesIncrementalSync++;\n }\n \n syncStats.nodesProcessed++;\n \n // Recurse to children\n for (const child of node.children) {\n syncNodeWithDelta(child, visibleSet, delta);\n }\n}\n\n/**\n * Legacy recursive sync (kept for comparison/fallback).\n * Returns early if the node is temporally culled, skipping ALL descendants.\n * @deprecated Use syncStylesWithIndex for better performance\n */\nexport function syncNodeRecursiveLegacy(node: CloneNode, timeMs: number): void {\n const { clone, children, bounds } = node;\n syncStats.nodesVisited++;\n \n // Temporal culling - check if this node is visible at current time\n // NOTE: Canvas clones now participate in temporal culling (lazy canvas copying).\n // Invalid bounds [0,0] are treated as [-Infinity, Infinity] by getTemporalBounds.\n {\n // OPTIMIZATION: Check if parent is already hidden to skip bounds computation\n const parent = clone.parentElement;\n if (parent instanceof HTMLElement) {\n // If parent has display:none, this element is already hidden - skip bounds check\n if (parent.style.display === \"none\") {\n clone.style.display = \"none\";\n syncStats.nodesCulledByParent++;\n return;\n }\n }\n \n // Use cached bounds from node instead of calling getTemporalBounds\n const { startMs, endMs } = bounds;\n if (timeMs < startMs || timeMs > endMs) {\n // Hide this element and BAIL OUT - skip all descendants automatically!\n clone.style.display = \"none\";\n syncStats.nodesCulledByTemporal++;\n return;\n }\n }\n \n // Sync this node's styles\n syncNodeStyles(node);\n syncStats.nodesProcessed++;\n \n // Recursively sync children\n for (const child of children) {\n syncNodeRecursiveLegacy(child, timeMs);\n }\n}\n\n/**\n * Sync all CSS properties from source elements to their clones.\n * Uses interval index for O(log n + k) visibility queries instead of O(n) traversal.\n * Uses delta tracking for incremental updates between frames.\n */\nexport function syncStyles(state: SyncState, timeMs: number): void {\n // Reset stats\n syncStats = {\n nodesVisited: 0,\n nodesCulledByParent: 0,\n nodesCulledByTemporal: 0,\n nodesProcessed: 0,\n nodesFullSync: 0,\n nodesIncrementalSync: 0,\n nodesHidden: 0,\n indexQueryTimeMs: 0,\n syncTimeMs: 0,\n };\n \n // Use interval-index-based sync with delta tracking\n syncStylesWithIndex(state, timeMs);\n}\n\n/**\n * Collect document styles for shadow DOM injection.\n */\nexport function collectDocumentStyles(): string {\n const rules: string[] = [];\n try {\n for (const sheet of document.styleSheets) {\n try {\n if (sheet.cssRules) {\n for (const rule of sheet.cssRules) {\n rules.push(rule.cssText);\n }\n }\n } catch {}\n }\n } catch {}\n return rules.join(\"\\n\");\n}\n\n\n// Backward-compatible aliases\nexport const syncStaticStyles = syncStyles;\nexport const syncAnimatedStyles = syncStyles;\n\n/**\n * Override clip-path, opacity, and optionally transform on the root clone element.\n * The source may have these properties set for proxy mode or workbench scaling.\n * \n * @param syncState - The sync state containing the clone tree\n * @param fullReset - If true, also resets opacity and transform (for capture operations)\n */\nexport function overrideRootCloneStyles(syncState: SyncState, fullReset: boolean = false): void {\n const rootClone = syncState.tree.root?.clone;\n if (!rootClone) return;\n \n rootClone.style.clipPath = \"none\";\n if (fullReset) {\n rootClone.style.opacity = \"1\";\n rootClone.style.transform = \"none\";\n }\n}\n\n/**\n * Create a live preview of a timegroup with a refresh function.\n * Used by EFWorkbench for the \"computed\" preview mode.\n * \n * @param source - The source timegroup to preview\n * @returns Object with preview container and refresh function\n */\nexport function renderTimegroupPreview(source: Element): {\n container: HTMLDivElement;\n refresh: (timeMs?: number) => void;\n} {\n const { container, syncState } = buildCloneStructure(source);\n \n // Initial style sync\n syncStyles(syncState, 0);\n \n return {\n container,\n refresh: (timeMs?: number) => {\n syncStyles(syncState, timeMs ?? 0);\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import "../RenderContext.js";
2
- import "./types.js";
@@ -1,95 +0,0 @@
1
- import { getEffectiveRenderMode } from "../renderers.js";
2
- import { defaultProfiler } from "../RenderProfiler.js";
3
- import { renderToImageNative } from "./renderToImageNative.js";
4
- import { inlineImages } from "./inlineImages.js";
5
- import { encodeCanvasesInParallel } from "../encoding/canvasEncoder.js";
6
- import { serializeToSvgDataUri } from "./renderToImageForeignObject.js";
7
-
8
- //#region src/preview/rendering/renderToImage.ts
9
- /**
10
- * Check if an element or any of its ancestors has display:none.
11
- * Used to skip encoding hidden canvases.
12
- */
13
- function isElementHidden(element) {
14
- let current = element;
15
- while (current) {
16
- if (current instanceof HTMLElement && current.style.display === "none") return true;
17
- current = current.parentElement;
18
- }
19
- return false;
20
- }
21
- /**
22
- * Load an image from a data URI. Returns a Promise that resolves when loaded.
23
- */
24
- function loadImageFromDataUri(dataUri) {
25
- const img = new Image();
26
- const imageLoadStart = performance.now();
27
- return new Promise((resolve, reject) => {
28
- img.onload = () => {
29
- defaultProfiler.addTime("imageLoad", performance.now() - imageLoadStart);
30
- resolve(img);
31
- };
32
- img.onerror = reject;
33
- img.src = dataUri;
34
- });
35
- }
36
- /**
37
- * Render HTML content to an image (or canvas) for drawing.
38
- *
39
- * Supports two rendering modes (configurable via previewSettings):
40
- * - "native": Chrome's experimental drawElementImage API (fastest when available)
41
- * - "foreignObject": SVG foreignObject serialization (fallback, works everywhere)
42
- *
43
- * @param container - The HTML element to render
44
- * @param width - Target width in logical pixels
45
- * @param height - Target height in logical pixels
46
- * @param options - Rendering options
47
- * @returns HTMLCanvasElement when using native, HTMLImageElement when using foreignObject
48
- */
49
- async function renderToImage(container, width, height, options) {
50
- if (getEffectiveRenderMode() === "native") return renderToImageNative(container, width, height, options);
51
- const allOriginalCanvases = Array.from(container.querySelectorAll("canvas"));
52
- const clone = container.cloneNode(true);
53
- const allClonedCanvases = Array.from(clone.querySelectorAll("canvas"));
54
- const visibleIndices = [];
55
- const visibleCanvases = [];
56
- for (let i = 0; i < allOriginalCanvases.length; i++) {
57
- const canvas = allOriginalCanvases[i];
58
- if (!isElementHidden(canvas)) {
59
- visibleIndices.push(i);
60
- visibleCanvases.push(canvas);
61
- }
62
- }
63
- const canvasScale = options?.canvasScale ?? 1;
64
- const canvasStart = performance.now();
65
- const encodedResults = await encodeCanvasesInParallel(visibleCanvases, {
66
- scale: canvasScale,
67
- renderContext: options?.renderContext,
68
- sourceMap: options?.sourceMap
69
- });
70
- for (let j = 0; j < visibleCanvases.length; j++) {
71
- const srcCanvas = visibleCanvases[j];
72
- const dstCanvas = allClonedCanvases[visibleIndices[j]];
73
- const encoded = encodedResults.find((r) => r.canvas === srcCanvas);
74
- if (!dstCanvas || !encoded) continue;
75
- try {
76
- const img = document.createElement("img");
77
- img.src = encoded.dataUrl;
78
- img.width = srcCanvas.width;
79
- img.height = srcCanvas.height;
80
- const style = dstCanvas.getAttribute("style");
81
- if (style) img.setAttribute("style", style);
82
- dstCanvas.parentNode?.replaceChild(img, dstCanvas);
83
- } catch {}
84
- }
85
- defaultProfiler.addTime("canvasEncode", performance.now() - canvasStart);
86
- const inlineStart = performance.now();
87
- await inlineImages(clone);
88
- defaultProfiler.addTime("inline", performance.now() - inlineStart);
89
- const { dataUri } = await serializeToSvgDataUri(clone, width, height);
90
- return loadImageFromDataUri(dataUri);
91
- }
92
-
93
- //#endregion
94
- export { loadImageFromDataUri, renderToImage };
95
- //# sourceMappingURL=renderToImage.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"renderToImage.js","names":["current: Element | null","visibleIndices: number[]","visibleCanvases: HTMLCanvasElement[]"],"sources":["../../../src/preview/rendering/renderToImage.ts"],"sourcesContent":["/**\n * Public rendering API facade.\n * Dispatches to native or foreignObject rendering paths based on settings.\n */\n\nimport type { ForeignObjectRenderOptions } from \"./types.js\";\nimport type { RenderContext } from \"../RenderContext.js\";\nimport { renderToImageNative } from \"./renderToImageNative.js\";\nimport { serializeToSvgDataUri } from \"./renderToImageForeignObject.js\";\nimport { inlineImages } from \"./inlineImages.js\";\nimport { getEffectiveRenderMode } from \"../renderers.js\";\nimport { encodeCanvasesInParallel } from \"../encoding/canvasEncoder.js\";\nimport { defaultProfiler } from \"../RenderProfiler.js\";\n\n/**\n * Check if an element or any of its ancestors has display:none.\n * Used to skip encoding hidden canvases.\n */\nfunction isElementHidden(element: Element): boolean {\n let current: Element | null = element;\n while (current) {\n if (current instanceof HTMLElement && current.style.display === \"none\") {\n return true;\n }\n current = current.parentElement;\n }\n return false;\n}\n\n/**\n * Load an image from a data URI. Returns a Promise that resolves when loaded.\n */\nexport function loadImageFromDataUri(dataUri: string): Promise<HTMLImageElement> {\n const img = new Image();\n const imageLoadStart = performance.now();\n \n return new Promise<HTMLImageElement>((resolve, reject) => {\n img.onload = () => {\n defaultProfiler.addTime(\"imageLoad\", performance.now() - imageLoadStart);\n resolve(img);\n };\n img.onerror = reject;\n img.src = dataUri;\n });\n}\n\n/**\n * Render HTML content to an image (or canvas) for drawing.\n * \n * Supports two rendering modes (configurable via previewSettings):\n * - \"native\": Chrome's experimental drawElementImage API (fastest when available)\n * - \"foreignObject\": SVG foreignObject serialization (fallback, works everywhere)\n * \n * @param container - The HTML element to render\n * @param width - Target width in logical pixels\n * @param height - Target height in logical pixels\n * @param options - Rendering options\n * @returns HTMLCanvasElement when using native, HTMLImageElement when using foreignObject\n */\nexport async function renderToImage(\n container: HTMLElement,\n width: number,\n height: number,\n options?: ForeignObjectRenderOptions,\n): Promise<HTMLImageElement | HTMLCanvasElement> {\n const renderMode = getEffectiveRenderMode();\n \n // Native HTML-in-Canvas API path (fastest, requires Chrome flag)\n if (renderMode === \"native\") {\n return renderToImageNative(container, width, height, options);\n }\n \n // Fallback: SVG foreignObject serialization\n // Clone the container first (don't modify original)\n // Note: cloneNode doesn't copy canvas pixels, so we encode from original canvases\n const allOriginalCanvases = Array.from(container.querySelectorAll(\"canvas\"));\n const clone = container.cloneNode(true) as HTMLElement;\n const allClonedCanvases = Array.from(clone.querySelectorAll(\"canvas\"));\n \n // Filter out hidden canvases - they have display:none and won't render anyway\n // Keep track of indices to match with cloned canvases\n const visibleIndices: number[] = [];\n const visibleCanvases: HTMLCanvasElement[] = [];\n for (let i = 0; i < allOriginalCanvases.length; i++) {\n const canvas = allOriginalCanvases[i]!;\n if (!isElementHidden(canvas)) {\n visibleIndices.push(i);\n visibleCanvases.push(canvas);\n }\n }\n \n // Encode visible original canvases\n // Pass through renderContext and sourceMap for caching\n const canvasScale = options?.canvasScale ?? 1;\n const canvasStart = performance.now();\n const encodedResults = await encodeCanvasesInParallel(visibleCanvases, { \n scale: canvasScale,\n renderContext: options?.renderContext,\n sourceMap: options?.sourceMap,\n });\n \n // Map encoded results to corresponding cloned canvases using tracked indices\n for (let j = 0; j < visibleCanvases.length; j++) {\n const srcCanvas = visibleCanvases[j]!;\n const originalIndex = visibleIndices[j]!;\n const dstCanvas = allClonedCanvases[originalIndex];\n const encoded = encodedResults.find((r) => r.canvas === srcCanvas);\n \n if (!dstCanvas || !encoded) continue;\n \n try {\n const img = document.createElement(\"img\");\n img.src = encoded.dataUrl;\n img.width = srcCanvas.width;\n img.height = srcCanvas.height;\n const style = dstCanvas.getAttribute(\"style\");\n if (style) img.setAttribute(\"style\", style);\n dstCanvas.parentNode?.replaceChild(img, dstCanvas);\n } catch {\n // Cross-origin or other error - skip\n }\n }\n defaultProfiler.addTime(\"canvasEncode\", performance.now() - canvasStart);\n\n // Inline external images in the clone\n const inlineStart = performance.now();\n await inlineImages(clone);\n defaultProfiler.addTime(\"inline\", performance.now() - inlineStart);\n\n // Use common serialization pipeline (no restore needed since we're working on a clone)\n const { dataUri } = await serializeToSvgDataUri(clone, width, height);\n \n // Load as image\n return loadImageFromDataUri(dataUri);\n}\n\n/**\n * Render a pre-built clone container to an image WITHOUT cloning it again.\n * This is the fast path for reusing clone structures across frames.\n * \n * Key difference from renderToImage:\n * - Does NOT call cloneNode (avoids expensive DOM duplication)\n * - Converts canvases to images in-place, then restores them after serialization\n * - Assumes the container already has refreshed canvas content\n * \n * @param container - Pre-built clone container with refreshed canvas content\n * @param width - Output width\n * @param height - Output height\n * @returns Promise resolving to an HTMLImageElement\n */\nexport async function renderToImageDirect(\n container: HTMLElement,\n width: number,\n height: number,\n options?: {\n renderContext?: RenderContext;\n sourceMap?: WeakMap<HTMLCanvasElement, Element>;\n canvasScale?: number;\n },\n): Promise<HTMLImageElement> {\n defaultProfiler.incrementRenderCount();\n \n // Use common serialization pipeline (modifies in-place, restores after)\n const { dataUri, restore } = await serializeToSvgDataUri(container, width, height, {\n inlineImages: true,\n logEarlyRenders: true,\n renderContext: options?.renderContext,\n sourceMap: options?.sourceMap,\n canvasScale: options?.canvasScale ?? 1,\n });\n restore();\n \n // Load as image\n const image = await loadImageFromDataUri(dataUri);\n \n // Log timing breakdown periodically\n defaultProfiler.shouldLogByFrameCount(100);\n \n return image;\n}\n\n/**\n * Prepare a frame's data URI without waiting for image load.\n * Returns the data URI asynchronously (after parallel canvas encoding and serialization) for pipelined loading.\n * The DOM is restored before this function returns.\n */\nexport async function prepareFrameDataUri(\n container: HTMLElement,\n width: number,\n height: number,\n): Promise<string> {\n defaultProfiler.incrementRenderCount();\n \n // Use common serialization pipeline (modifies in-place, restores after)\n const { dataUri, restore } = await serializeToSvgDataUri(container, width, height);\n restore();\n \n return dataUri;\n}\n"],"mappings":";;;;;;;;;;;;AAkBA,SAAS,gBAAgB,SAA2B;CAClD,IAAIA,UAA0B;AAC9B,QAAO,SAAS;AACd,MAAI,mBAAmB,eAAe,QAAQ,MAAM,YAAY,OAC9D,QAAO;AAET,YAAU,QAAQ;;AAEpB,QAAO;;;;;AAMT,SAAgB,qBAAqB,SAA4C;CAC/E,MAAM,MAAM,IAAI,OAAO;CACvB,MAAM,iBAAiB,YAAY,KAAK;AAExC,QAAO,IAAI,SAA2B,SAAS,WAAW;AACxD,MAAI,eAAe;AACjB,mBAAgB,QAAQ,aAAa,YAAY,KAAK,GAAG,eAAe;AACxE,WAAQ,IAAI;;AAEd,MAAI,UAAU;AACd,MAAI,MAAM;GACV;;;;;;;;;;;;;;;AAgBJ,eAAsB,cACpB,WACA,OACA,QACA,SAC+C;AAI/C,KAHmB,wBAAwB,KAGxB,SACjB,QAAO,oBAAoB,WAAW,OAAO,QAAQ,QAAQ;CAM/D,MAAM,sBAAsB,MAAM,KAAK,UAAU,iBAAiB,SAAS,CAAC;CAC5E,MAAM,QAAQ,UAAU,UAAU,KAAK;CACvC,MAAM,oBAAoB,MAAM,KAAK,MAAM,iBAAiB,SAAS,CAAC;CAItE,MAAMC,iBAA2B,EAAE;CACnC,MAAMC,kBAAuC,EAAE;AAC/C,MAAK,IAAI,IAAI,GAAG,IAAI,oBAAoB,QAAQ,KAAK;EACnD,MAAM,SAAS,oBAAoB;AACnC,MAAI,CAAC,gBAAgB,OAAO,EAAE;AAC5B,kBAAe,KAAK,EAAE;AACtB,mBAAgB,KAAK,OAAO;;;CAMhC,MAAM,cAAc,SAAS,eAAe;CAC5C,MAAM,cAAc,YAAY,KAAK;CACrC,MAAM,iBAAiB,MAAM,yBAAyB,iBAAiB;EACrE,OAAO;EACP,eAAe,SAAS;EACxB,WAAW,SAAS;EACrB,CAAC;AAGF,MAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;EAC/C,MAAM,YAAY,gBAAgB;EAElC,MAAM,YAAY,kBADI,eAAe;EAErC,MAAM,UAAU,eAAe,MAAM,MAAM,EAAE,WAAW,UAAU;AAElE,MAAI,CAAC,aAAa,CAAC,QAAS;AAE5B,MAAI;GACF,MAAM,MAAM,SAAS,cAAc,MAAM;AACzC,OAAI,MAAM,QAAQ;AAClB,OAAI,QAAQ,UAAU;AACtB,OAAI,SAAS,UAAU;GACvB,MAAM,QAAQ,UAAU,aAAa,QAAQ;AAC7C,OAAI,MAAO,KAAI,aAAa,SAAS,MAAM;AAC3C,aAAU,YAAY,aAAa,KAAK,UAAU;UAC5C;;AAIV,iBAAgB,QAAQ,gBAAgB,YAAY,KAAK,GAAG,YAAY;CAGxE,MAAM,cAAc,YAAY,KAAK;AACrC,OAAM,aAAa,MAAM;AACzB,iBAAgB,QAAQ,UAAU,YAAY,KAAK,GAAG,YAAY;CAGlE,MAAM,EAAE,YAAY,MAAM,sBAAsB,OAAO,OAAO,OAAO;AAGrE,QAAO,qBAAqB,QAAQ"}
@@ -1,163 +0,0 @@
1
- import { logger } from "../logger.js";
2
- import { defaultProfiler } from "../RenderProfiler.js";
3
- import { encodeBase64Fast } from "./svgSerializer.js";
4
- import { inlineImages } from "./inlineImages.js";
5
- import { encodeCanvasesInParallel } from "../encoding/canvasEncoder.js";
6
-
7
- //#region src/preview/rendering/renderToImageForeignObject.ts
8
- let _xmlSerializer = null;
9
- let _textEncoder = null;
10
- /**
11
- * Check if an element or any of its ancestors has display:none.
12
- * Used to skip encoding hidden canvases.
13
- */
14
- function isElementHidden(element) {
15
- let current = element;
16
- while (current) {
17
- if (current instanceof HTMLElement && current.style.display === "none") return true;
18
- current = current.parentElement;
19
- }
20
- return false;
21
- }
22
- const SVG_PREFIX = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"";
23
- const SVG_HEIGHT_PREFIX = "\" height=\"";
24
- const SVG_MIDDLE = "\"><foreignObject width=\"100%\" height=\"100%\">";
25
- const SVG_SUFFIX = "</foreignObject></svg>";
26
- const DATA_URI_PREFIX = "data:image/svg+xml;base64,";
27
- const WRAPPER_STYLE_BASE = "overflow:hidden;position:relative;";
28
- /**
29
- * Common SVG foreignObject serialization pipeline.
30
- * Handles canvas encoding, serialization, and base64 encoding.
31
- *
32
- * @param container - The HTML element to serialize
33
- * @param width - Output width
34
- * @param height - Output height
35
- * @param options - Serialization options
36
- * @returns Serialization result with data URI and restore function
37
- */
38
- async function serializeToSvgDataUri(container, width, height, options = {}) {
39
- const { canvasScale = 1, inlineImages: shouldInlineImages = false, logEarlyRenders = false, renderContext, sourceMap } = options;
40
- const canvasRestoreInfo = [];
41
- const canvasStart = performance.now();
42
- const visibleCanvases = Array.from(container.querySelectorAll("canvas")).filter((canvas) => !isElementHidden(canvas));
43
- const canvasSnapshots = [];
44
- const qualityMultiplier = 1.5;
45
- for (let i = 0; i < visibleCanvases.length; i++) {
46
- const canvas = visibleCanvases[i];
47
- if (canvas.width > 0 && canvas.height > 0) {
48
- let optimalScale = canvasScale;
49
- if (sourceMap) {
50
- const sourceElement = sourceMap.get(canvas);
51
- if (sourceElement) try {
52
- const computedStyle = getComputedStyle(sourceElement);
53
- const cssWidth = parseFloat(computedStyle.width) || canvas.width;
54
- const cssHeight = parseFloat(computedStyle.height) || canvas.height;
55
- const displayScaleX = cssWidth / canvas.width;
56
- const displayScaleY = cssHeight / canvas.height;
57
- const displayScale = Math.min(displayScaleX, displayScaleY);
58
- optimalScale = Math.min(1, displayScale * canvasScale * qualityMultiplier);
59
- console.log(`[serializeToSvg] Canvas ${canvas.width}x${canvas.height} -> CSS ${cssWidth.toFixed(0)}x${cssHeight.toFixed(0)}, displayScale=${displayScale.toFixed(3)}, videoScale=${canvasScale}, optimalScale=${optimalScale.toFixed(3)}`);
60
- } catch (e) {
61
- console.warn(`[serializeToSvg] Failed to get computed style for ${sourceElement.tagName}:`, e);
62
- }
63
- }
64
- const targetWidth = Math.max(1, Math.floor(canvas.width * optimalScale));
65
- const targetHeight = Math.max(1, Math.floor(canvas.height * optimalScale));
66
- const copy = document.createElement("canvas");
67
- copy.width = targetWidth;
68
- copy.height = targetHeight;
69
- if (canvas.dataset.preserveAlpha) copy.dataset.preserveAlpha = canvas.dataset.preserveAlpha;
70
- const ctx = copy.getContext("2d");
71
- if (ctx) ctx.drawImage(canvas, 0, 0, targetWidth, targetHeight);
72
- canvasSnapshots.push({
73
- original: canvas,
74
- copy
75
- });
76
- }
77
- }
78
- const snapshotCanvases = canvasSnapshots.map((s) => s.copy);
79
- let snapshotSourceMap;
80
- if (sourceMap) {
81
- snapshotSourceMap = /* @__PURE__ */ new WeakMap();
82
- for (const { original, copy } of canvasSnapshots) {
83
- const sourceElement = sourceMap.get(original);
84
- if (sourceElement) snapshotSourceMap.set(copy, sourceElement);
85
- }
86
- }
87
- const encodedWithOriginals = (await encodeCanvasesInParallel(snapshotCanvases, {
88
- scale: 1,
89
- renderContext,
90
- sourceMap: snapshotSourceMap
91
- })).map((result) => {
92
- const snapshot = canvasSnapshots.find((s) => s.copy === result.canvas);
93
- return {
94
- ...result,
95
- canvas: snapshot?.original ?? result.canvas
96
- };
97
- });
98
- for (const { canvas, dataUrl } of encodedWithOriginals) try {
99
- const img = document.createElement("img");
100
- img.src = dataUrl;
101
- img.width = canvas.width;
102
- img.height = canvas.height;
103
- const style = canvas.getAttribute("style");
104
- if (style) img.setAttribute("style", style);
105
- const parent = canvas.parentNode;
106
- if (parent) {
107
- const nextSibling = canvas.nextSibling;
108
- parent.replaceChild(img, canvas);
109
- canvasRestoreInfo.push({
110
- canvas,
111
- parent,
112
- nextSibling,
113
- img
114
- });
115
- }
116
- } catch {}
117
- defaultProfiler.addTime("canvasEncode", performance.now() - canvasStart);
118
- if (shouldInlineImages) {
119
- const inlineStart = performance.now();
120
- await inlineImages(container);
121
- defaultProfiler.addTime("inline", performance.now() - inlineStart);
122
- }
123
- const serializeStart = performance.now();
124
- const wrapperElement = document.createElement("div");
125
- wrapperElement.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
126
- wrapperElement.setAttribute("style", `width:${width}px;height:${height}px;${WRAPPER_STYLE_BASE}`);
127
- wrapperElement.appendChild(container);
128
- if (!_xmlSerializer) _xmlSerializer = new XMLSerializer();
129
- const perfStart = performance.now();
130
- const serialized = _xmlSerializer.serializeToString(wrapperElement);
131
- const serializeTime = performance.now() - perfStart;
132
- if (Math.random() < .01) {
133
- const elementCount = wrapperElement.querySelectorAll("*").length;
134
- console.log(`[serialize] elements=${elementCount}, time=${serializeTime.toFixed(1)}ms, size=${(serialized.length / 1024).toFixed(1)}KB`);
135
- }
136
- defaultProfiler.addTime("serialize", performance.now() - serializeStart);
137
- const restore = () => {
138
- const restoreStart = performance.now();
139
- if (container.parentNode === wrapperElement) wrapperElement.removeChild(container);
140
- for (const { canvas, parent, nextSibling, img } of canvasRestoreInfo) if (img.parentNode === parent) parent.replaceChild(canvas, img);
141
- else if (canvas.parentNode !== parent) if (nextSibling && nextSibling.parentNode === parent) parent.insertBefore(canvas, nextSibling);
142
- else parent.appendChild(canvas);
143
- defaultProfiler.addTime("restore", performance.now() - restoreStart);
144
- };
145
- if (logEarlyRenders && defaultProfiler.isEarlyRender(2)) logger.debug(`[serializeToSvgDataUri] FO serialized: ${serialized.length} chars`);
146
- const base64Start = performance.now();
147
- const svg = SVG_PREFIX + width + SVG_HEIGHT_PREFIX + height + SVG_MIDDLE + serialized + SVG_SUFFIX;
148
- if (!_textEncoder) _textEncoder = new TextEncoder();
149
- const utf8Bytes = _textEncoder.encode(svg);
150
- let base64;
151
- if (typeof Uint8Array.prototype.toBase64 === "function") base64 = utf8Bytes.toBase64();
152
- else base64 = encodeBase64Fast(utf8Bytes);
153
- const dataUri = DATA_URI_PREFIX + base64;
154
- defaultProfiler.addTime("base64", performance.now() - base64Start);
155
- return {
156
- dataUri,
157
- restore
158
- };
159
- }
160
-
161
- //#endregion
162
- export { serializeToSvgDataUri };
163
- //# sourceMappingURL=renderToImageForeignObject.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"renderToImageForeignObject.js","names":["_xmlSerializer: XMLSerializer | null","_textEncoder: TextEncoder | null","current: Element | null","canvasRestoreInfo: CanvasRestoreInfo[]","canvasSnapshots: { original: HTMLCanvasElement; copy: HTMLCanvasElement }[]","snapshotSourceMap: WeakMap<HTMLCanvasElement, Element> | undefined","base64: string"],"sources":["../../../src/preview/rendering/renderToImageForeignObject.ts"],"sourcesContent":["/**\n * SVG foreignObject rendering path with serialization.\n */\n\nimport type { SerializeToSvgOptions, SerializationResult, CanvasRestoreInfo } from \"./types.js\";\nimport { encodeBase64Fast } from \"./svgSerializer.js\";\nimport { inlineImages } from \"./inlineImages.js\";\nimport { encodeCanvasesInParallel } from \"../encoding/canvasEncoder.js\";\nimport { defaultProfiler } from \"../RenderProfiler.js\";\nimport { logger } from \"../logger.js\";\n\n// Reusable instances for better performance (avoid creating new instances every frame)\n// Note: wrapper element is NOT reused - each concurrent frame needs its own wrapper\nlet _xmlSerializer: XMLSerializer | null = null;\nlet _textEncoder: TextEncoder | null = null;\n\n/**\n * Check if an element or any of its ancestors has display:none.\n * Used to skip encoding hidden canvases.\n */\nfunction isElementHidden(element: Element): boolean {\n let current: Element | null = element;\n while (current) {\n if (current instanceof HTMLElement && current.style.display === \"none\") {\n return true;\n }\n current = current.parentElement;\n }\n return false;\n}\n\n// Pre-computed SVG constants\nconst SVG_PREFIX = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"';\nconst SVG_HEIGHT_PREFIX = '\" height=\"';\nconst SVG_MIDDLE = '\"><foreignObject width=\"100%\" height=\"100%\">';\nconst SVG_SUFFIX = '</foreignObject></svg>';\nconst DATA_URI_PREFIX = 'data:image/svg+xml;base64,';\n\n// Shared style string to reduce allocations\nconst WRAPPER_STYLE_BASE = \"overflow:hidden;position:relative;\";\n\n/**\n * Common SVG foreignObject serialization pipeline.\n * Handles canvas encoding, serialization, and base64 encoding.\n * \n * @param container - The HTML element to serialize\n * @param width - Output width\n * @param height - Output height\n * @param options - Serialization options\n * @returns Serialization result with data URI and restore function\n */\nexport async function serializeToSvgDataUri(\n container: HTMLElement,\n width: number,\n height: number,\n options: SerializeToSvgOptions = {},\n): Promise<SerializationResult> {\n const { \n canvasScale = 1, \n inlineImages: shouldInlineImages = false, \n logEarlyRenders = false,\n renderContext,\n sourceMap,\n } = options;\n \n // Store info for restoration (only used if modifying in-place)\n const canvasRestoreInfo: CanvasRestoreInfo[] = [];\n \n // Phase 1: Encode canvases to data URLs (parallel)\n // Filter out hidden canvases - they have display:none and won't render anyway\n const canvasStart = performance.now();\n const allCanvases = Array.from(container.querySelectorAll(\"canvas\"));\n const visibleCanvases = allCanvases.filter(canvas => !isElementHidden(canvas));\n \n // CRITICAL FIX: Synchronously copy canvas pixels BEFORE any async work.\n // This prevents race conditions where concurrent render tasks overwrite\n // the shared clone canvases while encoding is in progress.\n // \n // OPTIMIZATION: Calculate optimal encoding resolution based on:\n // 1. CSS display size (how big it actually appears)\n // 2. Video export scale (output resolution multiplier)\n // 3. Quality multiplier (for sharpness, default 1.5x)\n const canvasSnapshots: { original: HTMLCanvasElement; copy: HTMLCanvasElement }[] = [];\n const qualityMultiplier = 1.5; // Encode at 1.5x display size for quality\n \n for (let i = 0; i < visibleCanvases.length; i++) {\n const canvas = visibleCanvases[i]!;\n if (canvas.width > 0 && canvas.height > 0) {\n // Calculate optimal encoding scale\n let optimalScale = canvasScale; // Start with video export scale\n \n // If we have sourceMap, calculate based on CSS display size\n if (sourceMap) {\n const sourceElement = sourceMap.get(canvas);\n if (sourceElement) {\n try {\n const computedStyle = getComputedStyle(sourceElement);\n const cssWidth = parseFloat(computedStyle.width) || canvas.width;\n const cssHeight = parseFloat(computedStyle.height) || canvas.height;\n \n // Calculate how much smaller the display is vs natural size\n const displayScaleX = cssWidth / canvas.width;\n const displayScaleY = cssHeight / canvas.height;\n const displayScale = Math.min(displayScaleX, displayScaleY);\n \n // Combine display scale, video scale, and quality multiplier\n // Clamp to 1.0 max (never upscale beyond natural resolution)\n optimalScale = Math.min(1.0, displayScale * canvasScale * qualityMultiplier);\n \n console.log(`[serializeToSvg] Canvas ${canvas.width}x${canvas.height} -> CSS ${cssWidth.toFixed(0)}x${cssHeight.toFixed(0)}, displayScale=${displayScale.toFixed(3)}, videoScale=${canvasScale}, optimalScale=${optimalScale.toFixed(3)}`);\n } catch (e) {\n // Fallback to just video scale if we can't get computed style\n console.warn(`[serializeToSvg] Failed to get computed style for ${sourceElement.tagName}:`, e);\n }\n }\n }\n \n // Create snapshot at optimal resolution\n const targetWidth = Math.max(1, Math.floor(canvas.width * optimalScale));\n const targetHeight = Math.max(1, Math.floor(canvas.height * optimalScale));\n \n const copy = document.createElement(\"canvas\");\n copy.width = targetWidth;\n copy.height = targetHeight;\n \n // Copy dataset attributes (e.g., preserveAlpha)\n if (canvas.dataset.preserveAlpha) {\n copy.dataset.preserveAlpha = canvas.dataset.preserveAlpha;\n }\n \n const ctx = copy.getContext(\"2d\");\n if (ctx) {\n // drawImage with scaling is SYNCHRONOUS - pixels are copied and scaled immediately\n ctx.drawImage(canvas, 0, 0, targetWidth, targetHeight);\n }\n canvasSnapshots.push({ original: canvas, copy });\n }\n }\n \n // Encode from the snapshot copies (safe from concurrent overwrites)\n const snapshotCanvases = canvasSnapshots.map(s => s.copy);\n \n // Create a new sourceMap that maps snapshot canvases to their source elements\n // The original sourceMap maps original canvases -> source elements\n // We need snapshot canvases -> source elements for caching to work\n let snapshotSourceMap: WeakMap<HTMLCanvasElement, Element> | undefined;\n if (sourceMap) {\n snapshotSourceMap = new WeakMap();\n for (const { original, copy } of canvasSnapshots) {\n const sourceElement = sourceMap.get(original);\n if (sourceElement) {\n snapshotSourceMap.set(copy, sourceElement);\n }\n }\n }\n \n // Snapshots are already scaled to optimal resolution, so encode at 1.0 scale\n const encodedResults = await encodeCanvasesInParallel(snapshotCanvases, { \n scale: 1.0, // Already scaled during snapshot creation\n renderContext,\n sourceMap: snapshotSourceMap,\n });\n \n // Map encoded results back to original canvases for DOM replacement\n const encodedWithOriginals = encodedResults.map(result => {\n const snapshot = canvasSnapshots.find(s => s.copy === result.canvas);\n return {\n ...result,\n canvas: snapshot?.original ?? result.canvas,\n };\n });\n \n // Replace canvases with images\n for (const { canvas, dataUrl } of encodedWithOriginals) {\n try {\n const img = document.createElement(\"img\");\n img.src = dataUrl;\n img.width = canvas.width;\n img.height = canvas.height;\n const style = canvas.getAttribute(\"style\");\n if (style) img.setAttribute(\"style\", style);\n \n const parent = canvas.parentNode;\n if (parent) {\n const nextSibling = canvas.nextSibling;\n parent.replaceChild(img, canvas);\n canvasRestoreInfo.push({ canvas, parent, nextSibling, img });\n }\n } catch {\n // Cross-origin canvas - leave as-is\n }\n }\n defaultProfiler.addTime(\"canvasEncode\", performance.now() - canvasStart);\n \n // Phase 2: Inline external images (if requested)\n if (shouldInlineImages) {\n const inlineStart = performance.now();\n await inlineImages(container);\n defaultProfiler.addTime(\"inline\", performance.now() - inlineStart);\n }\n \n // Phase 3: Serialize to XHTML\n const serializeStart = performance.now();\n \n // Create fresh wrapper element for THIS frame (local variable for closure safety)\n // Multiple concurrent frames in video export each get their own wrapper\n const wrapperElement = document.createElement(\"div\");\n wrapperElement.setAttribute(\"xmlns\", \"http://www.w3.org/1999/xhtml\");\n wrapperElement.setAttribute(\"style\", `width:${width}px;height:${height}px;${WRAPPER_STYLE_BASE}`);\n wrapperElement.appendChild(container);\n \n if (!_xmlSerializer) {\n _xmlSerializer = new XMLSerializer();\n }\n \n // NOTE: Hidden element handling is now done by the caller via removeHiddenNodesForSerialization().\n // The caller physically removes hidden nodes from the clone tree BEFORE calling this function,\n // so hidden elements are never serialized at all - not just hidden with display:none.\n //\n // Benefits of removing before serialization:\n // - Hidden canvases are not encoded (saves encoding time and memory)\n // - Hidden elements are not serialized (smaller SVG, faster serialization)\n // - Hidden images are not inlined (saves fetch and encoding)\n // - The serialized output is smaller and faster to base64 encode\n \n // Serialize to XHTML string\n const perfStart = performance.now();\n const serialized = _xmlSerializer.serializeToString(wrapperElement);\n const serializeTime = performance.now() - perfStart;\n \n // Sample 1% of frames to avoid spam\n if (Math.random() < 0.01) {\n const elementCount = wrapperElement.querySelectorAll('*').length;\n console.log(`[serialize] elements=${elementCount}, time=${serializeTime.toFixed(1)}ms, size=${(serialized.length / 1024).toFixed(1)}KB`);\n }\n\n defaultProfiler.addTime(\"serialize\", performance.now() - serializeStart);\n \n // Prepare restore function (removes container from wrapper, restores canvases)\n // Must be robust against concurrent frame rendering where DOM state may change\n const restore = (): void => {\n const restoreStart = performance.now();\n \n // Guard: only remove if container is still a child of wrapper\n if (container.parentNode === wrapperElement) {\n wrapperElement.removeChild(container);\n }\n \n for (const { canvas, parent, nextSibling, img } of canvasRestoreInfo) {\n // Guard: only restore if img is still in expected position\n if (img.parentNode === parent) {\n // Use replaceChild which is atomic and safer than insertBefore + removeChild\n parent.replaceChild(canvas, img);\n } else if (canvas.parentNode !== parent) {\n // Canvas was never restored and img was moved/removed - try to restore canvas\n if (nextSibling && nextSibling.parentNode === parent) {\n parent.insertBefore(canvas, nextSibling);\n } else {\n parent.appendChild(canvas);\n }\n }\n }\n defaultProfiler.addTime(\"restore\", performance.now() - restoreStart);\n };\n \n // DEBUG: Log serialized HTML size for early renders\n if (logEarlyRenders && defaultProfiler.isEarlyRender(2)) {\n logger.debug(`[serializeToSvgDataUri] FO serialized: ${serialized.length} chars`);\n }\n \n // Phase 4: Create SVG and encode to base64\n const base64Start = performance.now();\n \n // Build SVG string with minimal allocations (concatenation is faster for small strings)\n const svg = SVG_PREFIX + width + SVG_HEIGHT_PREFIX + height + SVG_MIDDLE + serialized + SVG_SUFFIX;\n \n if (!_textEncoder) {\n _textEncoder = new TextEncoder();\n }\n const utf8Bytes = _textEncoder.encode(svg);\n \n let base64: string;\n if (typeof (Uint8Array.prototype as any).toBase64 === \"function\") {\n base64 = (utf8Bytes as any).toBase64();\n } else {\n base64 = encodeBase64Fast(utf8Bytes);\n }\n const dataUri = DATA_URI_PREFIX + base64;\n defaultProfiler.addTime(\"base64\", performance.now() - base64Start);\n \n return { dataUri, restore };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- import "./types.js";
@@ -1,43 +0,0 @@
1
- //#region src/preview/rendering/svgSerializer.ts
2
- /**
3
- * SVG serialization and base64 encoding utilities.
4
- */
5
- const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
6
- /**
7
- * Fast base64 encoding directly from Uint8Array.
8
- * Optimized with array buffer pre-allocation and batch string building.
9
- * ~2-3x faster than string concatenation approach.
10
- */
11
- function encodeBase64Fast(bytes) {
12
- const len = bytes.length;
13
- const outputLen = (len + 2) / 3 << 2;
14
- const result = new Array(outputLen);
15
- let i = 0;
16
- let outIndex = 0;
17
- const len3 = len - 2;
18
- while (i < len3) {
19
- const byte1 = bytes[i++];
20
- const byte2 = bytes[i++];
21
- const byte3 = bytes[i++];
22
- const bitmap = byte1 << 16 | byte2 << 8 | byte3;
23
- result[outIndex++] = BASE64_CHARS[bitmap >> 18 & 63];
24
- result[outIndex++] = BASE64_CHARS[bitmap >> 12 & 63];
25
- result[outIndex++] = BASE64_CHARS[bitmap >> 6 & 63];
26
- result[outIndex++] = BASE64_CHARS[bitmap & 63];
27
- }
28
- const remaining = len - i;
29
- if (remaining > 0) {
30
- const byte1 = bytes[i++];
31
- const byte2 = remaining > 1 ? bytes[i++] : 0;
32
- const bitmap = byte1 << 16 | byte2 << 8;
33
- result[outIndex++] = BASE64_CHARS[bitmap >> 18 & 63];
34
- result[outIndex++] = BASE64_CHARS[bitmap >> 12 & 63];
35
- result[outIndex++] = remaining > 1 ? BASE64_CHARS[bitmap >> 6 & 63] : "=";
36
- result[outIndex++] = "=";
37
- }
38
- return result.join("");
39
- }
40
-
41
- //#endregion
42
- export { encodeBase64Fast };
43
- //# sourceMappingURL=svgSerializer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"svgSerializer.js","names":[],"sources":["../../../src/preview/rendering/svgSerializer.ts"],"sourcesContent":["/**\n * SVG serialization and base64 encoding utilities.\n */\n\n// Pre-computed base64 lookup table as Uint8Array for faster indexing\nconst BASE64_CHARS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\n/**\n * Fast base64 encoding directly from Uint8Array.\n * Optimized with array buffer pre-allocation and batch string building.\n * ~2-3x faster than string concatenation approach.\n */\nexport function encodeBase64Fast(bytes: Uint8Array): string {\n const len = bytes.length;\n \n // Pre-calculate output size: 4 chars per 3 bytes, rounded up\n const outputLen = ((len + 2) / 3) << 2;\n const result = new Array(outputLen);\n \n let i = 0;\n let outIndex = 0;\n \n // Process 3 bytes at a time (produces 4 base64 chars)\n // Unrolled for better performance\n const len3 = len - 2;\n while (i < len3) {\n const byte1 = bytes[i++]!;\n const byte2 = bytes[i++]!;\n const byte3 = bytes[i++]!;\n \n const bitmap = (byte1 << 16) | (byte2 << 8) | byte3;\n \n result[outIndex++] = BASE64_CHARS[(bitmap >> 18) & 63];\n result[outIndex++] = BASE64_CHARS[(bitmap >> 12) & 63];\n result[outIndex++] = BASE64_CHARS[(bitmap >> 6) & 63];\n result[outIndex++] = BASE64_CHARS[bitmap & 63];\n }\n \n // Handle remaining bytes (1 or 2)\n const remaining = len - i;\n if (remaining > 0) {\n const byte1 = bytes[i++]!;\n const byte2 = remaining > 1 ? bytes[i++]! : 0;\n const bitmap = (byte1 << 16) | (byte2 << 8);\n \n result[outIndex++] = BASE64_CHARS[(bitmap >> 18) & 63];\n result[outIndex++] = BASE64_CHARS[(bitmap >> 12) & 63];\n result[outIndex++] = remaining > 1 ? BASE64_CHARS[(bitmap >> 6) & 63] : \"=\";\n result[outIndex++] = \"=\";\n }\n \n return result.join(\"\");\n}\n"],"mappings":";;;;AAKA,MAAM,eAAe;;;;;;AAOrB,SAAgB,iBAAiB,OAA2B;CAC1D,MAAM,MAAM,MAAM;CAGlB,MAAM,aAAc,MAAM,KAAK,KAAM;CACrC,MAAM,SAAS,IAAI,MAAM,UAAU;CAEnC,IAAI,IAAI;CACR,IAAI,WAAW;CAIf,MAAM,OAAO,MAAM;AACnB,QAAO,IAAI,MAAM;EACf,MAAM,QAAQ,MAAM;EACpB,MAAM,QAAQ,MAAM;EACpB,MAAM,QAAQ,MAAM;EAEpB,MAAM,SAAU,SAAS,KAAO,SAAS,IAAK;AAE9C,SAAO,cAAc,aAAc,UAAU,KAAM;AACnD,SAAO,cAAc,aAAc,UAAU,KAAM;AACnD,SAAO,cAAc,aAAc,UAAU,IAAK;AAClD,SAAO,cAAc,aAAa,SAAS;;CAI7C,MAAM,YAAY,MAAM;AACxB,KAAI,YAAY,GAAG;EACjB,MAAM,QAAQ,MAAM;EACpB,MAAM,QAAQ,YAAY,IAAI,MAAM,OAAQ;EAC5C,MAAM,SAAU,SAAS,KAAO,SAAS;AAEzC,SAAO,cAAc,aAAc,UAAU,KAAM;AACnD,SAAO,cAAc,aAAc,UAAU,KAAM;AACnD,SAAO,cAAc,YAAY,IAAI,aAAc,UAAU,IAAK,MAAM;AACxE,SAAO,cAAc;;AAGvB,QAAO,OAAO,KAAK,GAAG"}
@@ -1,2 +0,0 @@
1
- import "../RenderContext.js";
2
- import "../encoding/types.js";
@@ -1,52 +0,0 @@
1
- //#region src/preview/thumbnailCacheSettings.ts
2
- /**
3
- * Thumbnail cache settings module with localStorage persistence.
4
- * Manages configuration for the thumbnail cache system.
5
- */
6
- const STORAGE_KEY_MAX_SIZE = "ef-thumbnail-cache-max-size";
7
- /**
8
- * Default maximum cache size (number of items).
9
- */
10
- const DEFAULT_MAX_SIZE = 1e3;
11
- /**
12
- * Get the current thumbnail cache max size.
13
- * Defaults to 1000 if not set.
14
- */
15
- function getThumbnailCacheMaxSize() {
16
- try {
17
- const stored = localStorage.getItem(STORAGE_KEY_MAX_SIZE);
18
- if (stored !== null) {
19
- const parsed = parseInt(stored, 10);
20
- if (!isNaN(parsed) && parsed > 0) return parsed;
21
- }
22
- return DEFAULT_MAX_SIZE;
23
- } catch {
24
- return DEFAULT_MAX_SIZE;
25
- }
26
- }
27
- /**
28
- * Set the thumbnail cache max size.
29
- * Persists to localStorage and dispatches a change event.
30
- */
31
- function setThumbnailCacheMaxSize(size) {
32
- if (size <= 0) throw new Error("Cache size must be greater than 0");
33
- try {
34
- localStorage.setItem(STORAGE_KEY_MAX_SIZE, String(size));
35
- } catch {}
36
- window.dispatchEvent(new CustomEvent("ef-thumbnail-cache-settings-changed", { detail: { maxSize: size } }));
37
- }
38
- /**
39
- * Subscribe to thumbnail cache settings changes.
40
- * @returns Unsubscribe function
41
- */
42
- function onThumbnailCacheSettingsChanged(callback) {
43
- const handler = (event) => {
44
- callback(event.detail);
45
- };
46
- window.addEventListener("ef-thumbnail-cache-settings-changed", handler);
47
- return () => window.removeEventListener("ef-thumbnail-cache-settings-changed", handler);
48
- }
49
-
50
- //#endregion
51
- export { getThumbnailCacheMaxSize, onThumbnailCacheSettingsChanged, setThumbnailCacheMaxSize };
52
- //# sourceMappingURL=thumbnailCacheSettings.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"thumbnailCacheSettings.js","names":[],"sources":["../../src/preview/thumbnailCacheSettings.ts"],"sourcesContent":["/**\n * Thumbnail cache settings module with localStorage persistence.\n * Manages configuration for the thumbnail cache system.\n */\n\nconst STORAGE_KEY_MAX_SIZE = \"ef-thumbnail-cache-max-size\";\n\n/**\n * Default maximum cache size (number of items).\n */\nconst DEFAULT_MAX_SIZE = 1000;\n\n/**\n * Get the current thumbnail cache max size.\n * Defaults to 1000 if not set.\n */\nexport function getThumbnailCacheMaxSize(): number {\n try {\n const stored = localStorage.getItem(STORAGE_KEY_MAX_SIZE);\n if (stored !== null) {\n const parsed = parseInt(stored, 10);\n if (!isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n return DEFAULT_MAX_SIZE;\n } catch {\n return DEFAULT_MAX_SIZE;\n }\n}\n\n/**\n * Set the thumbnail cache max size.\n * Persists to localStorage and dispatches a change event.\n */\nexport function setThumbnailCacheMaxSize(size: number): void {\n if (size <= 0) {\n throw new Error(\"Cache size must be greater than 0\");\n }\n \n try {\n localStorage.setItem(STORAGE_KEY_MAX_SIZE, String(size));\n } catch {\n // localStorage not available\n }\n \n // Dispatch event so components can react to the change\n window.dispatchEvent(new CustomEvent(\"ef-thumbnail-cache-settings-changed\", {\n detail: { maxSize: size }\n }));\n}\n\n/**\n * Subscribe to thumbnail cache settings changes.\n * @returns Unsubscribe function\n */\nexport function onThumbnailCacheSettingsChanged(\n callback: (detail: ThumbnailCacheSettingsChangedDetail) => void\n): () => void {\n const handler = (event: Event) => {\n callback((event as CustomEvent).detail);\n };\n window.addEventListener(\"ef-thumbnail-cache-settings-changed\", handler);\n return () => window.removeEventListener(\"ef-thumbnail-cache-settings-changed\", handler);\n}\n\n/**\n * Detail object for thumbnail cache settings change events.\n */\nexport interface ThumbnailCacheSettingsChangedDetail {\n maxSize?: number;\n}\n"],"mappings":";;;;;AAKA,MAAM,uBAAuB;;;;AAK7B,MAAM,mBAAmB;;;;;AAMzB,SAAgB,2BAAmC;AACjD,KAAI;EACF,MAAM,SAAS,aAAa,QAAQ,qBAAqB;AACzD,MAAI,WAAW,MAAM;GACnB,MAAM,SAAS,SAAS,QAAQ,GAAG;AACnC,OAAI,CAAC,MAAM,OAAO,IAAI,SAAS,EAC7B,QAAO;;AAGX,SAAO;SACD;AACN,SAAO;;;;;;;AAQX,SAAgB,yBAAyB,MAAoB;AAC3D,KAAI,QAAQ,EACV,OAAM,IAAI,MAAM,oCAAoC;AAGtD,KAAI;AACF,eAAa,QAAQ,sBAAsB,OAAO,KAAK,CAAC;SAClD;AAKR,QAAO,cAAc,IAAI,YAAY,uCAAuC,EAC1E,QAAQ,EAAE,SAAS,MAAM,EAC1B,CAAC,CAAC;;;;;;AAOL,SAAgB,gCACd,UACY;CACZ,MAAM,WAAW,UAAiB;AAChC,WAAU,MAAsB,OAAO;;AAEzC,QAAO,iBAAiB,uCAAuC,QAAQ;AACvE,cAAa,OAAO,oBAAoB,uCAAuC,QAAQ"}
@@ -1 +0,0 @@
1
- import "../elements/EFTimegroup.js";
@@ -1,10 +0,0 @@
1
- import { __toESM } from "../_virtual/rolldown_runtime.js";
2
- import { require_react } from "../node_modules/react/index.js";
3
- import { require_jsx_runtime } from "../node_modules/react/jsx-runtime.js";
4
-
5
- //#region src/sandbox/PlaybackControls.tsx
6
- var import_react = /* @__PURE__ */ __toESM(require_react());
7
- var import_jsx_runtime = /* @__PURE__ */ __toESM(require_jsx_runtime());
8
-
9
- //#endregion
10
- //# sourceMappingURL=PlaybackControls.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PlaybackControls.js","names":[],"sources":["../../src/sandbox/PlaybackControls.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport type { EFTimegroup } from \"../elements/EFTimegroup.js\";\n\nexport interface PlaybackControlsProps {\n /**\n * The timegroup element to control.\n * If null, controls are disabled.\n */\n timegroup: EFTimegroup | null;\n \n /**\n * Playback mode:\n * - \"auto\": Play/pause with continuous scrubbing\n * - \"step\": Discrete keyframe navigation\n */\n mode?: \"auto\" | \"step\";\n \n /**\n * Callback when mode changes.\n */\n onModeChange?: (mode: \"auto\" | \"step\") => void;\n \n /**\n * Keyframes for step mode (array of time values in ms).\n * If not provided, step mode divides duration into 10 equal steps.\n */\n keyframes?: number[];\n}\n\nfunction formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000);\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n const centiseconds = Math.floor((ms % 1000) / 10);\n return `${minutes}:${seconds.toString().padStart(2, \"0\")}.${centiseconds.toString().padStart(2, \"0\")}`;\n}\n\nexport function PlaybackControls({\n timegroup,\n mode = \"auto\",\n onModeChange,\n keyframes,\n}: PlaybackControlsProps) {\n const [isPlaying, setIsPlaying] = useState(false);\n const [isLooping, setIsLooping] = useState(false);\n const [currentTimeMs, setCurrentTimeMs] = useState(0);\n const [durationMs, setDurationMs] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n const [currentStepIndex, setCurrentStepIndex] = useState(0);\n \n const scrubberRef = useRef<HTMLDivElement>(null);\n const updateIntervalRef = useRef<number | null>(null);\n\n // Calculate step keyframes\n const steps = React.useMemo(() => {\n if (keyframes && keyframes.length > 0) {\n return keyframes;\n }\n // Default: 11 steps (0%, 10%, 20%, ..., 100%)\n const stepCount = 11;\n const stepSize = durationMs / (stepCount - 1);\n return Array.from({ length: stepCount }, (_, i) => i * stepSize);\n }, [keyframes, durationMs]);\n\n // Update duration when timegroup changes\n useEffect(() => {\n if (timegroup) {\n setDurationMs(timegroup.durationMs);\n setCurrentTimeMs(timegroup.currentTimeMs);\n } else {\n setDurationMs(0);\n setCurrentTimeMs(0);\n }\n }, [timegroup]);\n\n // Update current time periodically\n useEffect(() => {\n if (!timegroup) return;\n\n const updateTime = () => {\n if (!isDragging) {\n setCurrentTimeMs(timegroup.currentTimeMs);\n setIsPlaying(timegroup.playbackController?.playing ?? false);\n }\n };\n\n updateIntervalRef.current = window.setInterval(updateTime, 50);\n\n return () => {\n if (updateIntervalRef.current) {\n window.clearInterval(updateIntervalRef.current);\n }\n };\n }, [timegroup, isDragging]);\n\n // Update step index when time changes in step mode\n useEffect(() => {\n if (mode === \"step\" && steps.length > 0) {\n // Find closest step\n let closestIndex = 0;\n const firstStep = steps[0];\n if (firstStep !== undefined) {\n let closestDiff = Math.abs(currentTimeMs - firstStep);\n for (let i = 1; i < steps.length; i++) {\n const step = steps[i];\n if (step !== undefined) {\n const diff = Math.abs(currentTimeMs - step);\n if (diff < closestDiff) {\n closestDiff = diff;\n closestIndex = i;\n }\n }\n }\n setCurrentStepIndex(closestIndex);\n }\n }\n }, [currentTimeMs, mode, steps]);\n\n const handlePlayPause = useCallback(async () => {\n if (!timegroup) return;\n \n if (isPlaying) {\n timegroup.pause();\n setIsPlaying(false);\n } else {\n // Handle AudioContext for mobile devices\n if (timegroup.playbackController) {\n try {\n const audioContext = new AudioContext({ latencyHint: \"playback\" });\n audioContext.resume();\n timegroup.playbackController.setPendingAudioContext(audioContext);\n } catch (error) {\n console.warn(\"Failed to create/resume AudioContext:\", error);\n }\n }\n await timegroup.play();\n setIsPlaying(true);\n }\n }, [timegroup, isPlaying]);\n\n const handleLoopToggle = useCallback(() => {\n if (!timegroup?.playbackController) return;\n \n const newLooping = !isLooping;\n // @ts-expect-error - loop property is read-only in types but writable at runtime\n timegroup.playbackController.loop = newLooping;\n setIsLooping(newLooping);\n }, [timegroup, isLooping]);\n\n const handleScrubStart = useCallback((e: React.MouseEvent | React.TouchEvent) => {\n if (!timegroup || !scrubberRef.current) return;\n \n setIsDragging(true);\n handleScrub(e);\n }, [timegroup]);\n\n const handleScrub = useCallback((e: React.MouseEvent | React.TouchEvent | MouseEvent | TouchEvent) => {\n if (!timegroup || !scrubberRef.current || !isDragging) return;\n \n const scrubber = scrubberRef.current;\n if (!scrubber) return;\n \n const rect = scrubber.getBoundingClientRect();\n const touches = \"touches\" in e ? e.touches : null;\n const clientX = touches && touches[0] \n ? touches[0].clientX \n : (e as MouseEvent).clientX;\n const x = clientX - rect.left;\n const percent = Math.max(0, Math.min(1, x / rect.width));\n const targetTimeMs = percent * durationMs;\n \n timegroup.currentTime = targetTimeMs / 1000;\n setCurrentTimeMs(targetTimeMs);\n }, [timegroup, durationMs, isDragging]);\n\n const handleScrubEnd = useCallback(() => {\n setIsDragging(false);\n }, []);\n\n // Global mouse/touch events for scrubbing\n useEffect(() => {\n if (isDragging) {\n const handleMove = (e: MouseEvent | TouchEvent) => handleScrub(e);\n const handleUp = () => handleScrubEnd();\n \n document.addEventListener(\"mousemove\", handleMove);\n document.addEventListener(\"mouseup\", handleUp);\n document.addEventListener(\"touchmove\", handleMove);\n document.addEventListener(\"touchend\", handleUp);\n \n return () => {\n document.removeEventListener(\"mousemove\", handleMove);\n document.removeEventListener(\"mouseup\", handleUp);\n document.removeEventListener(\"touchmove\", handleMove);\n document.removeEventListener(\"touchend\", handleUp);\n };\n }\n }, [isDragging, handleScrub, handleScrubEnd]);\n\n const handleStepPrevious = useCallback(() => {\n if (!timegroup || steps.length === 0) return;\n \n const newIndex = Math.max(0, currentStepIndex - 1);\n const step = steps[newIndex];\n if (step !== undefined) {\n timegroup.currentTime = step / 1000;\n setCurrentStepIndex(newIndex);\n setCurrentTimeMs(step);\n }\n }, [timegroup, steps, currentStepIndex]);\n\n const handleStepNext = useCallback(() => {\n if (!timegroup || steps.length === 0) return;\n \n const newIndex = Math.min(steps.length - 1, currentStepIndex + 1);\n const step = steps[newIndex];\n if (step !== undefined) {\n timegroup.currentTime = step / 1000;\n setCurrentStepIndex(newIndex);\n setCurrentTimeMs(step);\n }\n }, [timegroup, steps, currentStepIndex]);\n\n const progress = durationMs > 0 ? (currentTimeMs / durationMs) * 100 : 0;\n const isDisabled = !timegroup;\n\n return (\n <div style={styles.container}>\n {/* Mode Toggle */}\n <div style={styles.modeToggle}>\n <button\n style={{\n ...styles.modeButton,\n ...(mode === \"auto\" ? styles.modeButtonActive : {}),\n }}\n onClick={() => onModeChange?.(\"auto\")}\n disabled={isDisabled}\n >\n Auto\n </button>\n <button\n style={{\n ...styles.modeButton,\n ...(mode === \"step\" ? styles.modeButtonActive : {}),\n }}\n onClick={() => onModeChange?.(\"step\")}\n disabled={isDisabled}\n >\n Step\n </button>\n </div>\n\n {/* Controls */}\n <div style={styles.controls}>\n {mode === \"auto\" ? (\n <>\n {/* Play/Pause */}\n <button\n style={styles.button}\n onClick={handlePlayPause}\n disabled={isDisabled}\n title={isPlaying ? \"Pause\" : \"Play\"}\n >\n {isPlaying ? \"⏸\" : \"▶\"}\n </button>\n\n {/* Loop */}\n <button\n style={{\n ...styles.button,\n ...(isLooping ? styles.buttonActive : {}),\n }}\n onClick={handleLoopToggle}\n disabled={isDisabled}\n title=\"Loop\"\n >\n 🔁\n </button>\n </>\n ) : (\n <>\n {/* Previous Step */}\n <button\n style={styles.button}\n onClick={handleStepPrevious}\n disabled={isDisabled || currentStepIndex === 0}\n title=\"Previous Step\"\n >\n ⏮\n </button>\n\n {/* Step indicator */}\n <span style={styles.stepIndicator}>\n {currentStepIndex + 1} / {steps.length}\n </span>\n\n {/* Next Step */}\n <button\n style={styles.button}\n onClick={handleStepNext}\n disabled={isDisabled || currentStepIndex === steps.length - 1}\n title=\"Next Step\"\n >\n ⏭\n </button>\n </>\n )}\n </div>\n\n {/* Scrubber */}\n <div style={styles.scrubberContainer}>\n <span style={styles.time}>{formatTime(currentTimeMs)}</span>\n <div\n ref={scrubberRef}\n style={{\n ...styles.scrubberTrack,\n ...(isDragging ? styles.scrubberTrackDragging : {}),\n }}\n onMouseDown={handleScrubStart}\n onTouchStart={handleScrubStart}\n >\n <div\n style={{\n ...styles.scrubberProgress,\n width: `${progress}%`,\n }}\n >\n <div style={styles.scrubberHandle} />\n </div>\n </div>\n <span style={styles.time}>{formatTime(durationMs)}</span>\n </div>\n </div>\n );\n}\n\nconst styles: Record<string, React.CSSProperties> = {\n container: {\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"8px\",\n padding: \"8px\",\n background: \"#1f2937\",\n borderTop: \"1px solid #374151\",\n },\n modeToggle: {\n display: \"flex\",\n gap: \"4px\",\n justifyContent: \"center\",\n },\n modeButton: {\n padding: \"4px 12px\",\n fontSize: \"11px\",\n background: \"#374151\",\n border: \"1px solid #4b5563\",\n borderRadius: \"4px\",\n color: \"#d1d5db\",\n cursor: \"pointer\",\n },\n modeButtonActive: {\n background: \"#3b82f6\",\n borderColor: \"#3b82f6\",\n color: \"white\",\n },\n controls: {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"8px\",\n },\n button: {\n width: \"32px\",\n height: \"32px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n background: \"transparent\",\n border: \"none\",\n color: \"#e5e7eb\",\n fontSize: \"16px\",\n cursor: \"pointer\",\n borderRadius: \"4px\",\n },\n buttonActive: {\n color: \"#22c55e\",\n },\n stepIndicator: {\n fontSize: \"12px\",\n color: \"#9ca3af\",\n minWidth: \"50px\",\n textAlign: \"center\",\n },\n scrubberContainer: {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n },\n time: {\n fontSize: \"11px\",\n color: \"#9ca3af\",\n fontFamily: \"'SF Mono', 'Monaco', 'Consolas', monospace\",\n minWidth: \"60px\",\n },\n scrubberTrack: {\n flex: 1,\n height: \"4px\",\n background: \"rgba(255, 255, 255, 0.2)\",\n borderRadius: \"2px\",\n cursor: \"pointer\",\n position: \"relative\",\n },\n scrubberTrackDragging: {\n cursor: \"grabbing\",\n },\n scrubberProgress: {\n height: \"100%\",\n background: \"#3b82f6\",\n borderRadius: \"2px\",\n position: \"relative\",\n transition: \"width 0.05s linear\",\n },\n scrubberHandle: {\n position: \"absolute\",\n right: \"-6px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n width: \"12px\",\n height: \"12px\",\n background: \"white\",\n borderRadius: \"50%\",\n boxShadow: \"0 2px 4px rgba(0, 0, 0, 0.3)\",\n },\n};\n"],"mappings":""}
@@ -1 +0,0 @@
1
- import "./index.js";
@@ -1 +0,0 @@
1
- import { nothing } from "lit";
@@ -1 +0,0 @@
1
- import { TemplateResult } from "lit";