@editframe/elements 0.33.0-beta → 0.34.6-beta

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 (251) hide show
  1. package/dist/EF_FRAMEGEN.js +5 -3
  2. package/dist/EF_FRAMEGEN.js.map +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.94.0 → _@oxc-project_runtime@0.95.0}/helpers/decorate.js +1 -1
  4. package/dist/canvas/EFCanvas.d.ts +7 -4
  5. package/dist/canvas/EFCanvas.js +1 -1
  6. package/dist/canvas/EFCanvasItem.d.ts +4 -4
  7. package/dist/canvas/EFCanvasItem.js +1 -1
  8. package/dist/canvas/overlays/SelectionOverlay.d.ts +95 -0
  9. package/dist/canvas/overlays/SelectionOverlay.js +1 -1
  10. package/dist/canvas/selection/SelectionController.js +7 -11
  11. package/dist/canvas/selection/SelectionController.js.map +1 -1
  12. package/dist/elements/EFAudio.d.ts +25 -7
  13. package/dist/elements/EFAudio.js +31 -61
  14. package/dist/elements/EFAudio.js.map +1 -1
  15. package/dist/elements/EFCaptions.d.ts +65 -52
  16. package/dist/elements/EFCaptions.js +186 -400
  17. package/dist/elements/EFCaptions.js.map +1 -1
  18. package/dist/elements/EFImage.d.ts +34 -6
  19. package/dist/elements/EFImage.js +114 -79
  20. package/dist/elements/EFImage.js.map +1 -1
  21. package/dist/elements/EFMedia/AssetIdMediaEngine.js +17 -17
  22. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
  23. package/dist/elements/EFMedia/AssetMediaEngine.js +41 -25
  24. package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
  25. package/dist/elements/EFMedia/BaseMediaEngine.js +4 -4
  26. package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
  27. package/dist/elements/EFMedia/BufferedSeekingInput.js +1 -1
  28. package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
  29. package/dist/elements/EFMedia/JitMediaEngine.js +31 -17
  30. package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
  31. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
  32. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
  33. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +17 -9
  34. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
  35. package/dist/elements/EFMedia.d.ts +66 -20
  36. package/dist/elements/EFMedia.js +412 -30
  37. package/dist/elements/EFMedia.js.map +1 -1
  38. package/dist/elements/EFPanZoom.d.ts +4 -4
  39. package/dist/elements/EFPanZoom.js +1 -1
  40. package/dist/elements/EFSourceMixin.js +43 -15
  41. package/dist/elements/EFSourceMixin.js.map +1 -1
  42. package/dist/elements/EFSurface.d.ts +23 -10
  43. package/dist/elements/EFSurface.js +64 -22
  44. package/dist/elements/EFSurface.js.map +1 -1
  45. package/dist/elements/EFTemporal.d.ts +8 -2
  46. package/dist/elements/EFTemporal.js +42 -31
  47. package/dist/elements/EFTemporal.js.map +1 -1
  48. package/dist/elements/EFText.d.ts +5 -4
  49. package/dist/elements/EFText.js +11 -2
  50. package/dist/elements/EFText.js.map +1 -1
  51. package/dist/elements/EFTextSegment.d.ts +4 -4
  52. package/dist/elements/EFTextSegment.js +1 -1
  53. package/dist/elements/EFThumbnailStrip.d.ts +4 -4
  54. package/dist/elements/EFThumbnailStrip.js +1 -1
  55. package/dist/elements/EFTimegroup.d.ts +22 -8
  56. package/dist/elements/EFTimegroup.js +203 -115
  57. package/dist/elements/EFTimegroup.js.map +1 -1
  58. package/dist/elements/EFVideo.d.ts +57 -20
  59. package/dist/elements/EFVideo.js +324 -72
  60. package/dist/elements/EFVideo.js.map +1 -1
  61. package/dist/elements/EFWaveform.d.ts +33 -7
  62. package/dist/elements/EFWaveform.js +103 -59
  63. package/dist/elements/EFWaveform.js.map +1 -1
  64. package/dist/elements/renderTemporalAudio.js +14 -3
  65. package/dist/elements/renderTemporalAudio.js.map +1 -1
  66. package/dist/getRenderInfo.d.ts +2 -2
  67. package/dist/gui/ContextMixin.js +1 -1
  68. package/dist/gui/Controllable.d.ts +2 -0
  69. package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
  70. package/dist/gui/EFActiveRootTemporal.js +1 -1
  71. package/dist/gui/EFConfiguration.d.ts +4 -4
  72. package/dist/gui/EFConfiguration.js +1 -1
  73. package/dist/gui/EFControls.d.ts +2 -2
  74. package/dist/gui/EFControls.js +1 -1
  75. package/dist/gui/EFDial.d.ts +4 -4
  76. package/dist/gui/EFDial.js +1 -1
  77. package/dist/gui/EFFilmstrip.d.ts +3 -2
  78. package/dist/gui/EFFilmstrip.js +1 -1
  79. package/dist/gui/EFFitScale.js +1 -1
  80. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  81. package/dist/gui/EFFocusOverlay.js +1 -1
  82. package/dist/gui/EFOverlayItem.d.ts +4 -4
  83. package/dist/gui/EFOverlayItem.js +1 -1
  84. package/dist/gui/EFOverlayLayer.d.ts +4 -4
  85. package/dist/gui/EFOverlayLayer.js +1 -1
  86. package/dist/gui/EFPause.d.ts +4 -4
  87. package/dist/gui/EFPause.js +1 -1
  88. package/dist/gui/EFPlay.d.ts +4 -4
  89. package/dist/gui/EFPlay.js +1 -1
  90. package/dist/gui/EFPreview.d.ts +4 -4
  91. package/dist/gui/EFPreview.js +1 -1
  92. package/dist/gui/EFResizableBox.d.ts +4 -4
  93. package/dist/gui/EFResizableBox.js +1 -1
  94. package/dist/gui/EFScrubber.d.ts +4 -4
  95. package/dist/gui/EFScrubber.js +1 -1
  96. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  97. package/dist/gui/EFTimeDisplay.js +1 -1
  98. package/dist/gui/EFTimelineRuler.d.ts +4 -4
  99. package/dist/gui/EFTimelineRuler.js +1 -1
  100. package/dist/gui/EFToggleLoop.d.ts +4 -4
  101. package/dist/gui/EFToggleLoop.js +1 -1
  102. package/dist/gui/EFTogglePlay.d.ts +4 -4
  103. package/dist/gui/EFTogglePlay.js +1 -1
  104. package/dist/gui/EFTransformHandles.d.ts +4 -4
  105. package/dist/gui/EFTransformHandles.js +1 -1
  106. package/dist/gui/EFWorkbench.d.ts +5 -4
  107. package/dist/gui/EFWorkbench.js +1 -1
  108. package/dist/gui/PlaybackController.d.ts +10 -2
  109. package/dist/gui/PlaybackController.js +52 -30
  110. package/dist/gui/PlaybackController.js.map +1 -1
  111. package/dist/gui/TWMixin.js +1 -1
  112. package/dist/gui/TWMixin.js.map +1 -1
  113. package/dist/gui/TargetOrContextMixin.js +1 -1
  114. package/dist/gui/hierarchy/EFHierarchy.d.ts +4 -4
  115. package/dist/gui/hierarchy/EFHierarchy.js +1 -1
  116. package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
  117. package/dist/gui/hierarchy/EFHierarchyItem.js +1 -1
  118. package/dist/gui/timeline/EFTimeline.d.ts +6 -2
  119. package/dist/gui/timeline/EFTimeline.js +1 -1
  120. package/dist/gui/timeline/EFTimelineRow.d.ts +57 -0
  121. package/dist/gui/timeline/EFTimelineRow.js +1 -1
  122. package/dist/gui/timeline/TrimHandles.d.ts +4 -4
  123. package/dist/gui/timeline/TrimHandles.js +1 -1
  124. package/dist/gui/timeline/tracks/AudioTrack.d.ts +2 -0
  125. package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
  126. package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +58 -0
  127. package/dist/gui/timeline/tracks/CaptionsTrack.js +1 -1
  128. package/dist/gui/timeline/tracks/HTMLTrack.d.ts +13 -0
  129. package/dist/gui/timeline/tracks/HTMLTrack.js +1 -1
  130. package/dist/gui/timeline/tracks/ImageTrack.d.ts +14 -0
  131. package/dist/gui/timeline/tracks/ImageTrack.js +1 -1
  132. package/dist/gui/timeline/tracks/TextTrack.d.ts +26 -0
  133. package/dist/gui/timeline/tracks/TextTrack.js +1 -1
  134. package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +47 -0
  135. package/dist/gui/timeline/tracks/TimegroupTrack.js +4 -12
  136. package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
  137. package/dist/gui/timeline/tracks/TrackItem.d.ts +81 -0
  138. package/dist/gui/timeline/tracks/TrackItem.js +1 -1
  139. package/dist/gui/timeline/tracks/VideoTrack.d.ts +25 -0
  140. package/dist/gui/timeline/tracks/VideoTrack.js +1 -1
  141. package/dist/gui/timeline/tracks/WaveformTrack.d.ts +14 -0
  142. package/dist/gui/timeline/tracks/WaveformTrack.js +1 -1
  143. package/dist/gui/timeline/tracks/ensureTrackItemInit.d.ts +1 -0
  144. package/dist/gui/timeline/tracks/preloadTracks.d.ts +9 -0
  145. package/dist/gui/tree/EFTree.d.ts +5 -4
  146. package/dist/gui/tree/EFTree.js +1 -1
  147. package/dist/gui/tree/EFTreeItem.d.ts +4 -4
  148. package/dist/gui/tree/EFTreeItem.js +1 -1
  149. package/dist/index.d.ts +4 -1
  150. package/dist/preview/AdaptiveResolutionTracker.js +6 -14
  151. package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
  152. package/dist/preview/FrameController.d.ts +123 -0
  153. package/dist/preview/FrameController.js +216 -0
  154. package/dist/preview/FrameController.js.map +1 -0
  155. package/dist/preview/RenderContext.d.ts +1 -0
  156. package/dist/preview/RenderContext.js +193 -0
  157. package/dist/preview/RenderContext.js.map +1 -0
  158. package/dist/preview/encoding/canvasEncoder.js +166 -0
  159. package/dist/preview/encoding/canvasEncoder.js.map +1 -0
  160. package/dist/preview/encoding/mainThreadEncoder.js +39 -0
  161. package/dist/preview/encoding/mainThreadEncoder.js.map +1 -0
  162. package/dist/preview/encoding/types.d.ts +1 -0
  163. package/dist/preview/encoding/workerEncoder.js +58 -0
  164. package/dist/preview/encoding/workerEncoder.js.map +1 -0
  165. package/dist/preview/logger.js +41 -0
  166. package/dist/preview/logger.js.map +1 -0
  167. package/dist/preview/previewTypes.js +11 -10
  168. package/dist/preview/previewTypes.js.map +1 -1
  169. package/dist/preview/renderTimegroupPreview.js +259 -236
  170. package/dist/preview/renderTimegroupPreview.js.map +1 -1
  171. package/dist/preview/renderTimegroupToCanvas.d.ts +5 -0
  172. package/dist/preview/renderTimegroupToCanvas.js +100 -489
  173. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  174. package/dist/preview/renderTimegroupToVideo.d.ts +1 -0
  175. package/dist/preview/renderTimegroupToVideo.js +80 -22
  176. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  177. package/dist/preview/renderers.js.map +1 -1
  178. package/dist/preview/rendering/inlineImages.js +56 -0
  179. package/dist/preview/rendering/inlineImages.js.map +1 -0
  180. package/dist/preview/rendering/renderToImage.d.ts +1 -0
  181. package/dist/preview/rendering/renderToImage.js +120 -0
  182. package/dist/preview/rendering/renderToImage.js.map +1 -0
  183. package/dist/preview/rendering/renderToImageForeignObject.js +135 -0
  184. package/dist/preview/rendering/renderToImageForeignObject.js.map +1 -0
  185. package/dist/preview/rendering/renderToImageNative.d.ts +1 -0
  186. package/dist/preview/rendering/renderToImageNative.js +129 -0
  187. package/dist/preview/rendering/renderToImageNative.js.map +1 -0
  188. package/dist/preview/rendering/svgSerializer.js +43 -0
  189. package/dist/preview/rendering/svgSerializer.js.map +1 -0
  190. package/dist/preview/rendering/types.d.ts +2 -0
  191. package/dist/preview/statsTrackingStrategy.js +3 -1
  192. package/dist/preview/statsTrackingStrategy.js.map +1 -1
  193. package/dist/preview/workers/WorkerPool.js +8 -57
  194. package/dist/preview/workers/WorkerPool.js.map +1 -1
  195. package/dist/render/EFRenderAPI.d.ts +35 -0
  196. package/dist/render/EFRenderAPI.js +1 -0
  197. package/dist/render/EFRenderAPI.js.map +1 -1
  198. package/dist/sandbox/PlaybackControls.d.ts +1 -0
  199. package/dist/sandbox/ScenarioRunner.d.ts +1 -0
  200. package/dist/sandbox/defineSandbox.d.ts +1 -0
  201. package/dist/sandbox/index.d.ts +3 -0
  202. package/dist/style.css +3 -0
  203. package/dist/transcoding/types/index.d.ts +6 -3
  204. package/package.json +2 -3
  205. package/test/EFVideo.framegen.browsertest.ts +8 -1
  206. package/test/profilingPlugin.ts +1 -3
  207. package/test/setup.ts +23 -1
  208. package/dist/EF_INTERACTIVE.js +0 -7
  209. package/dist/EF_INTERACTIVE.js.map +0 -1
  210. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +0 -50
  211. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts +0 -12
  212. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +0 -104
  213. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +0 -1
  214. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +0 -168
  215. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +0 -1
  216. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +0 -46
  217. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +0 -1
  218. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +0 -49
  219. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +0 -1
  220. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +0 -30
  221. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +0 -1
  222. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +0 -49
  223. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +0 -1
  224. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +0 -47
  225. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +0 -1
  226. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +0 -140
  227. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +0 -1
  228. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +0 -13
  229. package/dist/elements/EFMedia/shared/BufferUtils.js +0 -86
  230. package/dist/elements/EFMedia/shared/BufferUtils.js.map +0 -1
  231. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +0 -17
  232. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +0 -90
  233. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +0 -1
  234. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +0 -80
  235. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +0 -1
  236. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +0 -49
  237. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +0 -1
  238. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +0 -58
  239. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +0 -1
  240. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +0 -71
  241. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +0 -1
  242. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +0 -52
  243. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +0 -1
  244. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +0 -50
  245. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +0 -1
  246. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +0 -109
  247. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +0 -1
  248. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +0 -12
  249. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +0 -97
  250. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +0 -1
  251. package/dist/elements/SampleBuffer.d.ts +0 -19
@@ -1,3 +1,4 @@
1
+ import { logger } from "./logger.js";
1
2
  import { getTemporalBounds, isTemporal, isVisibleAtTime } from "./previewTypes.js";
2
3
 
3
4
  //#region src/preview/renderTimegroupPreview.ts
@@ -77,15 +78,6 @@ const SYNC_PROPERTIES = [
77
78
  "userSelect",
78
79
  "overflow"
79
80
  ];
80
- /** Set version of SYNC_PROPERTIES for O(1) validation lookups */
81
- const SYNC_PROPERTIES_SET = new Set(SYNC_PROPERTIES);
82
- /**
83
- * Validation state for property change detection.
84
- * Reset via resetPropertyValidation() at start of export.
85
- */
86
- let _propertyValidationEnabled = false;
87
- let _validationFrameCount = 0;
88
- let _baselineSnapshot = null;
89
81
  /**
90
82
  * Kebab-case versions for computedStyleMap.get() - pre-computed for speed.
91
83
  */
@@ -162,24 +154,90 @@ function isDefaultValue(prop, value) {
162
154
  return defaults === value;
163
155
  }
164
156
  /**
157
+ * Remove hidden nodes from the clone DOM for serialization.
158
+ * Returns info needed to restore them afterward.
159
+ *
160
+ * This physically removes non-visible nodes so they won't be serialized,
161
+ * avoiding the cost of serializing hidden elements and their resources.
162
+ */
163
+ function removeHiddenNodesForSerialization(state) {
164
+ const removed = [];
165
+ const visibleSet = state.currentVisibleSet;
166
+ function visit(node) {
167
+ for (const child of node.children) visit(child);
168
+ if (!visibleSet.has(node)) {
169
+ const parent = node.clone.parentNode;
170
+ if (parent) {
171
+ const nextSibling = node.clone.nextSibling;
172
+ parent.removeChild(node.clone);
173
+ removed.push({
174
+ node,
175
+ parent,
176
+ nextSibling
177
+ });
178
+ }
179
+ }
180
+ }
181
+ if (state.tree.root) visit(state.tree.root);
182
+ return removed;
183
+ }
184
+ /**
185
+ * Restore previously removed hidden nodes to the clone DOM.
186
+ * Must be called after serialization to maintain tree integrity for next frame.
187
+ */
188
+ function restoreHiddenNodes(removed) {
189
+ for (let i = removed.length - 1; i >= 0; i--) {
190
+ const { node, parent, nextSibling } = removed[i];
191
+ if (nextSibling) parent.insertBefore(node.clone, nextSibling);
192
+ else parent.appendChild(node.clone);
193
+ }
194
+ }
195
+ /**
196
+ * Helper to copy styles from host and content elements to a canvas clone.
197
+ * Reduces code duplication for shadow canvas and shadow img cases.
198
+ */
199
+ function copyCanvasCloneStyles(clone, hostCs, contentCs) {
200
+ const s = clone.style;
201
+ s.position = hostCs.position;
202
+ s.top = hostCs.top;
203
+ s.right = hostCs.right;
204
+ s.bottom = hostCs.bottom;
205
+ s.left = hostCs.left;
206
+ s.margin = hostCs.margin;
207
+ s.zIndex = hostCs.zIndex;
208
+ s.transform = hostCs.transform;
209
+ s.transformOrigin = hostCs.transformOrigin;
210
+ s.opacity = hostCs.opacity;
211
+ s.visibility = hostCs.visibility;
212
+ s.width = contentCs.width;
213
+ s.height = contentCs.height;
214
+ s.display = "block";
215
+ s.animation = "none";
216
+ s.transition = "none";
217
+ }
218
+ /**
165
219
  * Build clone tree structure with minimal overhead.
220
+ * Caches temporal bounds on each node for visibility checks.
166
221
  * Optionally syncs styles in the same pass if timeMs is provided.
167
222
  */
168
223
  function buildCloneStructure(source, timeMs) {
169
224
  const container = document.createElement("div");
170
225
  container.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%";
171
- const legacyPairs = [];
172
- function cloneElement(srcEl) {
226
+ let nodeCount = 0;
227
+ const canvasSourceMap = /* @__PURE__ */ new WeakMap();
228
+ function cloneElement(srcEl, parentNode) {
173
229
  if (SKIP_TAGS.has(srcEl.tagName)) return null;
230
+ const bounds = getTemporalBounds(srcEl);
174
231
  if (srcEl instanceof SVGElement) {
175
232
  const node$1 = {
176
233
  source: srcEl,
177
234
  clone: srcEl.cloneNode(true),
178
235
  children: [],
179
236
  isCanvasClone: false,
180
- styleCache: /* @__PURE__ */ new Map()
237
+ bounds,
238
+ parent: parentNode
181
239
  };
182
- legacyPairs.push([srcEl, node$1.clone]);
240
+ nodeCount++;
183
241
  return node$1;
184
242
  }
185
243
  if (srcEl instanceof HTMLCanvasElement) {
@@ -204,33 +262,18 @@ function buildCloneStructure(source, timeMs) {
204
262
  ctx.drawImage(shadowCanvas, 0, 0);
205
263
  } catch {}
206
264
  try {
207
- const canvasCs = getComputedStyle(shadowCanvas);
208
- const hostCs = getComputedStyle(srcEl);
209
- clone$1.style.position = hostCs.position;
210
- clone$1.style.top = hostCs.top;
211
- clone$1.style.right = hostCs.right;
212
- clone$1.style.bottom = hostCs.bottom;
213
- clone$1.style.left = hostCs.left;
214
- clone$1.style.margin = hostCs.margin;
215
- clone$1.style.zIndex = hostCs.zIndex;
216
- clone$1.style.transform = hostCs.transform;
217
- clone$1.style.transformOrigin = hostCs.transformOrigin;
218
- clone$1.style.opacity = hostCs.opacity;
219
- clone$1.style.visibility = hostCs.visibility;
220
- clone$1.style.width = canvasCs.width;
221
- clone$1.style.height = canvasCs.height;
222
- clone$1.style.display = "block";
223
- clone$1.style.animation = "none";
224
- clone$1.style.transition = "none";
265
+ copyCanvasCloneStyles(clone$1, getComputedStyle(srcEl), getComputedStyle(shadowCanvas));
225
266
  } catch {}
267
+ canvasSourceMap.set(clone$1, srcEl);
226
268
  const node$1 = {
227
269
  source: srcEl,
228
270
  clone: clone$1,
229
271
  children: [],
230
272
  isCanvasClone: true,
231
- styleCache: /* @__PURE__ */ new Map()
273
+ bounds,
274
+ parent: parentNode
232
275
  };
233
- legacyPairs.push([srcEl, clone$1]);
276
+ nodeCount++;
234
277
  return node$1;
235
278
  }
236
279
  const shadowImg = srcEl.shadowRoot.querySelector("img");
@@ -244,38 +287,26 @@ function buildCloneStructure(source, timeMs) {
244
287
  ctx.drawImage(shadowImg, 0, 0);
245
288
  } catch {}
246
289
  try {
247
- const imgCs = getComputedStyle(shadowImg);
248
- const hostCs = getComputedStyle(srcEl);
249
- clone$1.style.position = hostCs.position;
250
- clone$1.style.top = hostCs.top;
251
- clone$1.style.right = hostCs.right;
252
- clone$1.style.bottom = hostCs.bottom;
253
- clone$1.style.left = hostCs.left;
254
- clone$1.style.margin = hostCs.margin;
255
- clone$1.style.zIndex = hostCs.zIndex;
256
- clone$1.style.transform = hostCs.transform;
257
- clone$1.style.transformOrigin = hostCs.transformOrigin;
258
- clone$1.style.opacity = hostCs.opacity;
259
- clone$1.style.visibility = hostCs.visibility;
260
- clone$1.style.width = imgCs.width;
261
- clone$1.style.height = imgCs.height;
262
- clone$1.style.display = "block";
263
- clone$1.style.animation = "none";
264
- clone$1.style.transition = "none";
290
+ copyCanvasCloneStyles(clone$1, getComputedStyle(srcEl), getComputedStyle(shadowImg));
265
291
  } catch {}
292
+ canvasSourceMap.set(clone$1, srcEl);
266
293
  const node$1 = {
267
294
  source: srcEl,
268
295
  clone: clone$1,
269
296
  children: [],
270
297
  isCanvasClone: true,
271
- styleCache: /* @__PURE__ */ new Map()
298
+ bounds,
299
+ parent: parentNode
272
300
  };
273
- legacyPairs.push([srcEl, clone$1]);
301
+ nodeCount++;
274
302
  return node$1;
275
303
  }
276
304
  }
277
305
  const clone = document.createElement(isCustom ? "div" : srcEl.tagName.toLowerCase());
278
- for (const attr of srcEl.attributes) {
306
+ const attrs = srcEl.attributes;
307
+ const attrLen = attrs.length;
308
+ if (attrLen > 0) for (let i = 0; i < attrLen; i++) {
309
+ const attr = attrs[i];
279
310
  const name = attr.name.toLowerCase();
280
311
  if (name === "id" || name.startsWith("on")) continue;
281
312
  if (isCustom && name !== "class" && !name.startsWith("data-")) continue;
@@ -290,51 +321,65 @@ function buildCloneStructure(source, timeMs) {
290
321
  clone,
291
322
  children: [],
292
323
  isCanvasClone: false,
293
- styleCache: /* @__PURE__ */ new Map()
324
+ bounds,
325
+ parent: parentNode
294
326
  };
295
- legacyPairs.push([srcEl, clone]);
327
+ nodeCount++;
296
328
  if (srcEl.shadowRoot) {
297
- const isCaptionElement = srcEl.tagName === "EF-CAPTIONS-ACTIVE-WORD" || srcEl.tagName === "EF-CAPTIONS-BEFORE-ACTIVE-WORD" || srcEl.tagName === "EF-CAPTIONS-AFTER-ACTIVE-WORD" || srcEl.tagName === "EF-CAPTIONS-SEGMENT";
298
- const isTextSegment = srcEl.tagName === "EF-TEXT-SEGMENT";
299
- let hasTextNode = false;
300
- for (const child of srcEl.shadowRoot.childNodes) if (child.nodeType === Node.TEXT_NODE) {
301
- if (child.textContent?.trim() || isCaptionElement || isTextSegment) {
302
- clone.appendChild(document.createTextNode(child.textContent || ""));
303
- hasTextNode = true;
329
+ const shadowChildren = srcEl.shadowRoot.childNodes;
330
+ const shadowLen = shadowChildren.length;
331
+ if (shadowLen > 0) {
332
+ const isTextSegment = srcEl.tagName === "EF-TEXT-SEGMENT";
333
+ let hasTextNode = false;
334
+ for (let i = 0; i < shadowLen; i++) {
335
+ const child = shadowChildren[i];
336
+ if (child.nodeType === Node.TEXT_NODE) {
337
+ if (child.textContent?.trim() || isTextSegment) {
338
+ clone.appendChild(document.createTextNode(child.textContent || ""));
339
+ hasTextNode = true;
340
+ }
341
+ } else if (child.nodeType === Node.ELEMENT_NODE) {
342
+ const el = child;
343
+ if (el.tagName === "STYLE" || el.tagName === "SLOT") continue;
344
+ const childNode = cloneElement(el, node);
345
+ if (childNode) {
346
+ node.children.push(childNode);
347
+ clone.appendChild(childNode.clone);
348
+ }
349
+ }
304
350
  }
351
+ if (isTextSegment && !hasTextNode) clone.appendChild(document.createTextNode(""));
352
+ }
353
+ }
354
+ const lightChildren = srcEl.childNodes;
355
+ const lightLen = lightChildren.length;
356
+ for (let i = 0; i < lightLen; i++) {
357
+ const child = lightChildren[i];
358
+ if (child.nodeType === Node.TEXT_NODE) {
359
+ const text = child.textContent?.trim();
360
+ if (text) clone.appendChild(document.createTextNode(text));
305
361
  } else if (child.nodeType === Node.ELEMENT_NODE) {
306
- const el = child;
307
- if (el.tagName === "STYLE" || el.tagName === "SLOT") continue;
308
- const childNode = cloneElement(el);
362
+ const childNode = cloneElement(child, node);
309
363
  if (childNode) {
310
364
  node.children.push(childNode);
311
365
  clone.appendChild(childNode.clone);
312
366
  }
313
367
  }
314
- if (isCaptionElement && !hasTextNode) clone.appendChild(document.createTextNode(""));
315
- }
316
- for (const child of srcEl.childNodes) if (child.nodeType === Node.TEXT_NODE) {
317
- const text = child.textContent?.trim();
318
- if (text) clone.appendChild(document.createTextNode(text));
319
- } else if (child.nodeType === Node.ELEMENT_NODE) {
320
- const childNode = cloneElement(child);
321
- if (childNode) {
322
- node.children.push(childNode);
323
- clone.appendChild(childNode.clone);
324
- }
325
368
  }
326
369
  return node;
327
370
  }
328
- const root = cloneElement(source);
371
+ const root = cloneElement(source, null);
329
372
  if (root) container.appendChild(root.clone);
330
373
  const syncState = {
331
374
  tree: { root },
332
- nodeCount: legacyPairs.length
375
+ nodeCount,
376
+ canvasSourceMap,
377
+ previousVisibleSet: /* @__PURE__ */ new Set(),
378
+ currentVisibleSet: /* @__PURE__ */ new Set()
333
379
  };
334
- if (timeMs !== void 0 && root) syncNodeRecursive(root, timeMs);
380
+ if (timeMs !== void 0 && root) syncStylesWithIndex(syncState, timeMs);
335
381
  return {
336
382
  container,
337
- pairs: legacyPairs,
338
383
  syncState
339
384
  };
340
385
  }
@@ -355,42 +400,27 @@ function syncNodeStyles(node) {
355
400
  ctx.clearRect(0, 0, canvas.width, canvas.height);
356
401
  ctx.drawImage(shadowCanvas, 0, 0);
357
402
  } catch (e) {
358
- console.warn("[syncNodeStyles] Canvas draw failed:", e);
403
+ logger.warn("[syncNodeStyles] Canvas draw failed:", e);
359
404
  }
360
405
  try {
361
406
  const canvasCs = getComputedStyle(shadowCanvas);
362
407
  const hostCs = getComputedStyle(source);
363
408
  const s = canvas.style;
364
- const srcWidth = canvasCs.width;
365
- const srcHeight = canvasCs.height;
366
- const srcPosition = hostCs.position;
367
- const srcTop = hostCs.top;
368
- const srcLeft = hostCs.left;
369
- const srcRight = hostCs.right;
370
- const srcBottom = hostCs.bottom;
371
- const srcMargin = hostCs.margin;
372
- const srcTransform = hostCs.transform;
373
- const srcTransformOrigin = hostCs.transformOrigin;
374
- const srcOpacity = hostCs.opacity;
375
- const srcVisibility = hostCs.visibility;
376
- const srcZIndex = hostCs.zIndex;
377
- const srcBackfaceVisibility = hostCs.backfaceVisibility;
378
- const srcTransformStyle = hostCs.transformStyle;
379
- if (s.position !== srcPosition) s.position = srcPosition;
380
- if (s.top !== srcTop) s.top = srcTop;
381
- if (s.left !== srcLeft) s.left = srcLeft;
382
- if (s.right !== srcRight) s.right = srcRight;
383
- if (s.bottom !== srcBottom) s.bottom = srcBottom;
384
- if (s.margin !== srcMargin) s.margin = srcMargin;
385
- if (s.transform !== srcTransform) s.transform = srcTransform;
386
- if (s.transformOrigin !== srcTransformOrigin) s.transformOrigin = srcTransformOrigin;
387
- if (s.opacity !== srcOpacity) s.opacity = srcOpacity;
388
- if (s.visibility !== srcVisibility) s.visibility = srcVisibility;
389
- if (s.zIndex !== srcZIndex) s.zIndex = srcZIndex;
390
- if (s.width !== srcWidth) s.width = srcWidth;
391
- if (s.height !== srcHeight) s.height = srcHeight;
392
- if (s.backfaceVisibility !== srcBackfaceVisibility) s.backfaceVisibility = srcBackfaceVisibility;
393
- if (s.transformStyle !== srcTransformStyle) s.transformStyle = srcTransformStyle;
409
+ s.position = hostCs.position;
410
+ s.top = hostCs.top;
411
+ s.left = hostCs.left;
412
+ s.right = hostCs.right;
413
+ s.bottom = hostCs.bottom;
414
+ s.margin = hostCs.margin;
415
+ s.transform = hostCs.transform;
416
+ s.transformOrigin = hostCs.transformOrigin;
417
+ s.opacity = hostCs.opacity;
418
+ s.visibility = hostCs.visibility;
419
+ s.zIndex = hostCs.zIndex;
420
+ s.width = canvasCs.width;
421
+ s.height = canvasCs.height;
422
+ s.backfaceVisibility = hostCs.backfaceVisibility;
423
+ s.transformStyle = hostCs.transformStyle;
394
424
  } catch {}
395
425
  } else if (shadowImg?.complete && shadowImg.naturalWidth > 0) {
396
426
  if (canvas.width !== shadowImg.naturalWidth) canvas.width = shadowImg.naturalWidth;
@@ -406,42 +436,27 @@ function syncNodeStyles(node) {
406
436
  const imgCs = getComputedStyle(shadowImg);
407
437
  const hostCs = getComputedStyle(source);
408
438
  const s = canvas.style;
409
- const srcWidth = imgCs.width;
410
- const srcHeight = imgCs.height;
411
- const srcPosition = hostCs.position;
412
- const srcTop = hostCs.top;
413
- const srcLeft = hostCs.left;
414
- const srcRight = hostCs.right;
415
- const srcBottom = hostCs.bottom;
416
- const srcMargin = hostCs.margin;
417
- const srcTransform = hostCs.transform;
418
- const srcTransformOrigin = hostCs.transformOrigin;
419
- const srcOpacity = hostCs.opacity;
420
- const srcVisibility = hostCs.visibility;
421
- const srcZIndex = hostCs.zIndex;
422
- const srcBackfaceVisibility = hostCs.backfaceVisibility;
423
- const srcTransformStyle = hostCs.transformStyle;
424
- if (s.position !== srcPosition) s.position = srcPosition;
425
- if (s.top !== srcTop) s.top = srcTop;
426
- if (s.left !== srcLeft) s.left = srcLeft;
427
- if (s.right !== srcRight) s.right = srcRight;
428
- if (s.bottom !== srcBottom) s.bottom = srcBottom;
429
- if (s.margin !== srcMargin) s.margin = srcMargin;
430
- if (s.transform !== srcTransform) s.transform = srcTransform;
431
- if (s.transformOrigin !== srcTransformOrigin) s.transformOrigin = srcTransformOrigin;
432
- if (s.opacity !== srcOpacity) s.opacity = srcOpacity;
433
- if (s.visibility !== srcVisibility) s.visibility = srcVisibility;
434
- if (s.zIndex !== srcZIndex) s.zIndex = srcZIndex;
435
- if (s.width !== srcWidth) s.width = srcWidth;
436
- if (s.height !== srcHeight) s.height = srcHeight;
437
- if (s.backfaceVisibility !== srcBackfaceVisibility) s.backfaceVisibility = srcBackfaceVisibility;
438
- if (s.transformStyle !== srcTransformStyle) s.transformStyle = srcTransformStyle;
439
+ s.position = hostCs.position;
440
+ s.top = hostCs.top;
441
+ s.left = hostCs.left;
442
+ s.right = hostCs.right;
443
+ s.bottom = hostCs.bottom;
444
+ s.margin = hostCs.margin;
445
+ s.transform = hostCs.transform;
446
+ s.transformOrigin = hostCs.transformOrigin;
447
+ s.opacity = hostCs.opacity;
448
+ s.visibility = hostCs.visibility;
449
+ s.zIndex = hostCs.zIndex;
450
+ s.width = imgCs.width;
451
+ s.height = imgCs.height;
452
+ s.backfaceVisibility = hostCs.backfaceVisibility;
453
+ s.transformStyle = hostCs.transformStyle;
439
454
  } catch {}
440
455
  }
441
456
  }
442
457
  const cloneStyle = clone.style;
443
- const { styleCache } = node;
444
458
  const propLen = SYNC_PROPERTIES.length;
459
+ const tagName = source.tagName;
445
460
  if (HAS_COMPUTED_STYLE_MAP) {
446
461
  let srcMap;
447
462
  try {
@@ -455,10 +470,8 @@ function syncNodeStyles(node) {
455
470
  const srcVal = srcMap.get(kebab);
456
471
  if (!srcVal) continue;
457
472
  const strVal = srcVal.toString();
458
- if (styleCache.get(camel) === strVal) continue;
459
- styleCache.set(camel, strVal);
460
473
  if (camel === "display") {
461
- cloneStyle.display = strVal === "none" ? "block" : strVal;
474
+ cloneStyle.display = strVal === "none" && !(tagName && (tagName === "EF-CAPTIONS-ACTIVE-WORD" || tagName === "EF-CAPTIONS-BEFORE-ACTIVE-WORD" || tagName === "EF-CAPTIONS-AFTER-ACTIVE-WORD" || tagName === "EF-CAPTIONS-SEGMENT")) ? "block" : strVal;
462
475
  continue;
463
476
  }
464
477
  if (camel === "clipPath") continue;
@@ -478,10 +491,8 @@ function syncNodeStyles(node) {
478
491
  const srcStyle = cs;
479
492
  for (const prop of SYNC_PROPERTIES) {
480
493
  const srcVal = srcStyle[prop];
481
- if (styleCache.get(prop) === srcVal) continue;
482
- styleCache.set(prop, srcVal);
483
494
  if (prop === "display") {
484
- cloneStyle.display = srcVal === "none" ? "block" : srcVal;
495
+ cloneStyle.display = srcVal === "none" && !(tagName && (tagName === "EF-CAPTIONS-ACTIVE-WORD" || tagName === "EF-CAPTIONS-BEFORE-ACTIVE-WORD" || tagName === "EF-CAPTIONS-AFTER-ACTIVE-WORD" || tagName === "EF-CAPTIONS-SEGMENT")) ? "block" : srcVal;
485
496
  continue;
486
497
  }
487
498
  if (prop === "clipPath") continue;
@@ -492,30 +503,15 @@ function syncNodeStyles(node) {
492
503
  cloneStyle[prop] = srcVal;
493
504
  }
494
505
  }
495
- if (cloneStyle.animation !== "none") cloneStyle.animation = "none";
496
- if (cloneStyle.transition !== "none") cloneStyle.transition = "none";
506
+ cloneStyle.animation = "none";
507
+ cloneStyle.transition = "none";
497
508
  const srcTextNode = source.childNodes[0];
498
- const cloneTextNode = clone.childNodes[0];
499
- if (srcTextNode?.nodeType === Node.TEXT_NODE && cloneTextNode?.nodeType === Node.TEXT_NODE) {
509
+ if (srcTextNode?.nodeType === Node.TEXT_NODE) {
500
510
  const srcText = srcTextNode.textContent || "";
501
- if (cloneTextNode.textContent !== srcText) cloneTextNode.textContent = srcText;
502
- }
503
- const tagName = source.tagName;
504
- if (tagName && (tagName === "EF-CAPTIONS-ACTIVE-WORD" || tagName === "EF-CAPTIONS-BEFORE-ACTIVE-WORD" || tagName === "EF-CAPTIONS-AFTER-ACTIVE-WORD" || tagName === "EF-CAPTIONS-SEGMENT")) {
505
- const srcShadowRoot = source.shadowRoot;
506
- if (srcShadowRoot && !srcTextNode) {
507
- let srcShadowText = "";
508
- for (const srcChild of srcShadowRoot.childNodes) if (srcChild.nodeType === Node.TEXT_NODE) srcShadowText += srcChild.textContent || "";
509
- let cloneTextNode$1 = null;
510
- for (const cloneChild of clone.childNodes) if (cloneChild.nodeType === Node.TEXT_NODE) {
511
- cloneTextNode$1 = cloneChild;
512
- break;
513
- }
514
- if (!cloneTextNode$1) {
515
- cloneTextNode$1 = document.createTextNode(srcShadowText);
516
- clone.appendChild(cloneTextNode$1);
517
- } else if (cloneTextNode$1.textContent !== srcShadowText) cloneTextNode$1.textContent = srcShadowText;
518
- }
511
+ const cloneTextNode = clone.childNodes[0];
512
+ if (cloneTextNode?.nodeType === Node.TEXT_NODE) {
513
+ if (cloneTextNode.textContent !== srcText) cloneTextNode.textContent = srcText;
514
+ } else if (!clone.childNodes.length) clone.appendChild(document.createTextNode(srcText));
519
515
  }
520
516
  if (source instanceof HTMLInputElement) {
521
517
  const srcVal = source.value;
@@ -526,84 +522,111 @@ function syncNodeStyles(node) {
526
522
  }
527
523
  }
528
524
  }
525
+ let syncStats = {
526
+ nodesVisited: 0,
527
+ nodesCulledByParent: 0,
528
+ nodesCulledByTemporal: 0,
529
+ nodesProcessed: 0,
530
+ nodesFullSync: 0,
531
+ nodesIncrementalSync: 0,
532
+ nodesHidden: 0,
533
+ indexQueryTimeMs: 0,
534
+ syncTimeMs: 0
535
+ };
529
536
  /**
530
- * Recursively sync a node and its children.
531
- * Returns early if the node is temporally culled, skipping ALL descendants.
537
+ * Compute visibility delta between previous and current frame.
532
538
  */
533
- function syncNodeRecursive(node, timeMs) {
534
- const { source, clone, children, isCanvasClone, styleCache } = node;
535
- if (!isCanvasClone) {
536
- const { startMs, endMs } = getTemporalBounds(source);
537
- if (timeMs < startMs || timeMs > endMs) {
538
- clone.style.display = "none";
539
- styleCache.delete("display");
540
- return;
541
- }
542
- }
543
- syncNodeStyles(node);
544
- for (const child of children) syncNodeRecursive(child, timeMs);
539
+ function computeVisibilityDelta(previousSet, currentSet) {
540
+ const nowVisible = /* @__PURE__ */ new Set();
541
+ const stillVisible = /* @__PURE__ */ new Set();
542
+ const nowHidden = /* @__PURE__ */ new Set();
543
+ for (const node of currentSet) if (previousSet.has(node)) stillVisible.add(node);
544
+ else nowVisible.add(node);
545
+ for (const node of previousSet) if (!currentSet.has(node)) nowHidden.add(node);
546
+ return {
547
+ nowVisible,
548
+ stillVisible,
549
+ nowHidden
550
+ };
545
551
  }
546
552
  /**
547
- * Sync all CSS properties from source elements to their clones.
548
- * Uses recursive tree traversal with early bailout for temporal culling.
553
+ * Build visible set by recursive traversal with bounds checking.
554
+ * Queries fresh bounds from source elements each time - bounds are computed
555
+ * dynamically by timegroups based on composition mode.
549
556
  */
550
- function syncStyles(state, timeMs) {
551
- if (state.tree.root) syncNodeRecursive(state.tree.root, timeMs);
552
- if (_propertyValidationEnabled) validatePropertyChanges(state);
557
+ function buildVisibleSetRecursive(node, timeMs, visibleSet) {
558
+ const { children, source } = node;
559
+ const bounds = getTemporalBounds(source);
560
+ if (timeMs >= bounds.startMs && timeMs <= bounds.endMs) {
561
+ visibleSet.add(node);
562
+ for (const child of children) buildVisibleSetRecursive(child, timeMs, visibleSet);
563
+ }
553
564
  }
554
565
  /**
555
- * Captures a snapshot of all CSS properties for all source elements.
566
+ * Sync styles with recursive visibility check and delta tracking.
567
+ *
568
+ * DELTA TRACKING: Tracks visibility changes between frames to minimize work:
569
+ * - nowVisible nodes: Full style sync + show
570
+ * - stillVisible nodes: Incremental sync (source DOM may have changed)
571
+ * - nowHidden nodes: Just hide (display:none)
556
572
  */
557
- function capturePropertySnapshot(state) {
558
- const snapshot = /* @__PURE__ */ new Map();
559
- function captureNode(node) {
560
- const props = /* @__PURE__ */ new Map();
561
- try {
562
- const cs = getComputedStyle(node.source);
563
- for (const prop of SYNC_PROPERTIES) props.set(prop, cs[prop] ?? "");
564
- } catch {}
565
- snapshot.set(node.source, props);
566
- for (const child of node.children) captureNode(child);
567
- }
568
- if (state.tree.root) captureNode(state.tree.root);
569
- return snapshot;
573
+ function syncStylesWithIndex(state, timeMs) {
574
+ const queryStart = performance.now();
575
+ const visibleSet = /* @__PURE__ */ new Set();
576
+ if (state.tree.root) buildVisibleSetRecursive(state.tree.root, timeMs, visibleSet);
577
+ const delta = computeVisibilityDelta(state.previousVisibleSet, visibleSet);
578
+ syncStats.indexQueryTimeMs = performance.now() - queryStart;
579
+ const syncStart = performance.now();
580
+ if (state.tree.root) syncNodeWithDelta(state.tree.root, visibleSet, delta);
581
+ syncStats.syncTimeMs = performance.now() - syncStart;
582
+ state.previousVisibleSet = visibleSet;
583
+ state.currentVisibleSet = visibleSet;
570
584
  }
571
585
  /**
572
- * Validates that only expected properties have changed since baseline.
573
- * Throws if any unexpected property changed.
586
+ * Sync a node using visibility delta for incremental updates.
587
+ *
588
+ * DELTA TRACKING optimization:
589
+ * - nowVisible: Full style sync (element just appeared)
590
+ * - stillVisible: Incremental sync (source DOM may have changed)
591
+ * - nowHidden: Just hide the element
592
+ * - Not in any set: Skip entirely (was already hidden)
574
593
  */
575
- function validatePropertyChanges(state) {
576
- _validationFrameCount++;
577
- if (_baselineSnapshot === null) {
578
- _baselineSnapshot = capturePropertySnapshot(state);
594
+ function syncNodeWithDelta(node, visibleSet, delta) {
595
+ syncStats.nodesVisited++;
596
+ if (!visibleSet.has(node)) {
597
+ node.clone.style.display = "none";
598
+ if (delta.nowHidden.has(node)) syncStats.nodesHidden++;
599
+ syncStats.nodesCulledByTemporal++;
579
600
  return;
580
601
  }
581
- if (_validationFrameCount % 30 !== 0) return;
582
- const currentSnapshot = capturePropertySnapshot(state);
583
- const violations = [];
584
- for (const [element, baselineProps] of _baselineSnapshot) {
585
- const currentProps = currentSnapshot.get(element);
586
- if (!currentProps) continue;
587
- for (const [prop, baselineValue] of baselineProps) {
588
- const currentValue = currentProps.get(prop) ?? "";
589
- if (baselineValue === currentValue) continue;
590
- if (!SYNC_PROPERTIES_SET.has(prop)) {
591
- const elementId = element.id || element.tagName;
592
- violations.push(`[${elementId}] ${prop}: "${baselineValue}" → "${currentValue}"`);
593
- }
594
- }
595
- }
596
- if (violations.length > 0) {
597
- const message = [
598
- `\n⚠️ UNEXPECTED PROPERTY CHANGES at frame ${_validationFrameCount}:`,
599
- `Properties not in SYNC_PROPERTIES changed during export.`,
600
- ``,
601
- `Violations (${violations.length}):`,
602
- ...violations.slice(0, 20).map((v) => ` ${v}`),
603
- violations.length > 20 ? ` ... and ${violations.length - 20} more` : ""
604
- ].join("\n");
605
- throw new Error(message);
602
+ if (delta.nowVisible.has(node)) {
603
+ syncNodeStyles(node);
604
+ syncStats.nodesFullSync++;
605
+ } else if (delta.stillVisible.has(node)) {
606
+ syncNodeStyles(node);
607
+ syncStats.nodesIncrementalSync++;
606
608
  }
609
+ syncStats.nodesProcessed++;
610
+ for (const child of node.children) syncNodeWithDelta(child, visibleSet, delta);
611
+ }
612
+ /**
613
+ * Sync all CSS properties from source elements to their clones.
614
+ * Uses interval index for O(log n + k) visibility queries instead of O(n) traversal.
615
+ * Uses delta tracking for incremental updates between frames.
616
+ */
617
+ function syncStyles(state, timeMs) {
618
+ syncStats = {
619
+ nodesVisited: 0,
620
+ nodesCulledByParent: 0,
621
+ nodesCulledByTemporal: 0,
622
+ nodesProcessed: 0,
623
+ nodesFullSync: 0,
624
+ nodesIncrementalSync: 0,
625
+ nodesHidden: 0,
626
+ indexQueryTimeMs: 0,
627
+ syncTimeMs: 0
628
+ };
629
+ syncStylesWithIndex(state, timeMs);
607
630
  }
608
631
  /**
609
632
  * Collect document styles for shadow DOM injection.
@@ -652,5 +675,5 @@ function renderTimegroupPreview(source) {
652
675
  }
653
676
 
654
677
  //#endregion
655
- export { buildCloneStructure, collectDocumentStyles, overrideRootCloneStyles, renderTimegroupPreview, syncStyles };
678
+ export { buildCloneStructure, collectDocumentStyles, overrideRootCloneStyles, removeHiddenNodesForSerialization, renderTimegroupPreview, restoreHiddenNodes, syncStyles };
656
679
  //# sourceMappingURL=renderTimegroupPreview.js.map