@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.
- package/dist/EF_FRAMEGEN.js +17 -14
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_RENDERING.js.map +1 -1
- package/dist/canvas/EFCanvas.d.ts +9 -2
- package/dist/canvas/EFCanvas.js +14 -4
- package/dist/canvas/EFCanvas.js.map +1 -1
- package/dist/canvas/EFCanvasItem.d.ts +2 -2
- package/dist/canvas/overlays/SelectionOverlay.d.ts +10 -2
- package/dist/canvas/overlays/SelectionOverlay.js +5 -12
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
- package/dist/canvas/overlays/overlayState.js.map +1 -1
- package/dist/canvas/selection/SelectionController.js.map +1 -1
- package/dist/elements/EFAudio.d.ts +1 -11
- package/dist/elements/EFAudio.js +2 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +5 -9
- package/dist/elements/EFCaptions.js +34 -11
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +10 -8
- package/dist/elements/EFImage.js +117 -32
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +2 -2
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +15 -92
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +10 -11
- package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
- package/dist/elements/EFMedia/{AssetIdMediaEngine.js → FileMediaEngine.js} +44 -24
- package/dist/elements/EFMedia/FileMediaEngine.js.map +1 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +14 -13
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +12 -7
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/shared/timeoutUtils.js +44 -0
- package/dist/elements/EFMedia/shared/timeoutUtils.js.map +1 -0
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +4 -4
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +14 -8
- package/dist/elements/EFMedia.js +52 -19
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +2 -2
- package/dist/elements/EFPanZoom.js +1 -1
- package/dist/elements/EFPanZoom.js.map +1 -1
- package/dist/elements/EFSourceMixin.js +16 -8
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +5 -8
- package/dist/elements/EFSurface.js +4 -43
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +33 -8
- package/dist/elements/EFTemporal.js +92 -40
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +3 -0
- package/dist/elements/EFText.js +54 -21
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.js +8 -4
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +26 -43
- package/dist/elements/EFTimegroup.js +295 -314
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +44 -42
- package/dist/elements/EFVideo.js +259 -172
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +3 -8
- package/dist/elements/EFWaveform.js +18 -13
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.js.map +1 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/TargetController.d.ts +0 -3
- package/dist/elements/TargetController.js +12 -35
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/cloneFactoryRegistry.d.ts +14 -0
- package/dist/elements/cloneFactoryRegistry.js +15 -0
- package/dist/elements/cloneFactoryRegistry.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +8 -6
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/setupTemporalHierarchy.js +62 -0
- package/dist/elements/setupTemporalHierarchy.js.map +1 -0
- package/dist/elements/updateAnimations.js +62 -87
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +3 -2
- package/dist/getRenderInfo.js +20 -4
- package/dist/getRenderInfo.js.map +1 -1
- package/dist/gui/ContextMixin.js +68 -12
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +1 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +2 -2
- package/dist/gui/EFActiveRootTemporal.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +2 -2
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +2 -2
- package/dist/gui/EFDial.js +12 -9
- package/dist/gui/EFDial.js.map +1 -1
- package/dist/gui/EFFilmstrip.d.ts +2 -0
- package/dist/gui/EFFilmstrip.js +18 -10
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +28 -4
- package/dist/gui/EFFitScale.js +88 -26
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +2 -2
- package/dist/gui/EFFocusOverlay.js +3 -3
- package/dist/gui/EFFocusOverlay.js.map +1 -1
- package/dist/gui/EFOverlayItem.d.ts +2 -2
- package/dist/gui/EFOverlayLayer.d.ts +2 -2
- package/dist/gui/EFPause.d.ts +2 -2
- package/dist/gui/EFPause.js +1 -1
- package/dist/gui/EFPlay.d.ts +2 -2
- package/dist/gui/EFPlay.js +1 -1
- package/dist/gui/EFPreview.js +1 -1
- package/dist/gui/EFResizableBox.d.ts +2 -2
- package/dist/gui/EFResizableBox.js +5 -5
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +2 -2
- package/dist/gui/EFScrubber.js +8 -13
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +6 -2
- package/dist/gui/EFTimeDisplay.js +25 -7
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +2 -2
- package/dist/gui/EFTimelineRuler.js +3 -3
- package/dist/gui/EFTimelineRuler.js.map +1 -1
- package/dist/gui/EFToggleLoop.d.ts +2 -2
- package/dist/gui/EFToggleLoop.js +1 -1
- package/dist/gui/EFTogglePlay.d.ts +2 -2
- package/dist/gui/EFTogglePlay.js +1 -1
- package/dist/gui/EFTransformHandles.d.ts +2 -2
- package/dist/gui/EFTransformHandles.js +6 -6
- package/dist/gui/EFTransformHandles.js.map +1 -1
- package/dist/gui/EFWorkbench.d.ts +40 -36
- package/dist/gui/EFWorkbench.js +436 -822
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.js.map +1 -1
- package/dist/gui/PlaybackController.d.ts +3 -8
- package/dist/gui/PlaybackController.js +59 -56
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin.js.map +1 -1
- package/dist/gui/TargetOrContextMixin.js +43 -6
- package/dist/gui/TargetOrContextMixin.js.map +1 -1
- package/dist/gui/ef-theme.css +136 -0
- package/dist/gui/hierarchy/EFHierarchy.d.ts +2 -2
- package/dist/gui/hierarchy/EFHierarchy.js +14 -24
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
- package/dist/gui/hierarchy/EFHierarchyItem.js +22 -10
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
- package/dist/gui/icons.js.map +1 -1
- package/dist/gui/previewSettingsContext.d.ts +18 -0
- package/dist/gui/previewSettingsContext.js.map +1 -1
- package/dist/gui/theme.js +34 -0
- package/dist/gui/theme.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +2 -2
- package/dist/gui/timeline/EFTimeline.js +70 -52
- package/dist/gui/timeline/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/EFTimelineRow.d.ts +5 -3
- package/dist/gui/timeline/EFTimelineRow.js +55 -32
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
- package/dist/gui/timeline/TrimHandles.d.ts +23 -9
- package/dist/gui/timeline/TrimHandles.js +224 -51
- package/dist/gui/timeline/TrimHandles.js.map +1 -1
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
- package/dist/gui/timeline/timelineEditingContext.d.ts +34 -0
- package/dist/gui/timeline/timelineEditingContext.js +24 -0
- package/dist/gui/timeline/timelineEditingContext.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +2 -3
- package/dist/gui/timeline/tracks/CaptionsTrack.js +17 -75
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +52 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js +596 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TextTrack.d.ts +3 -2
- package/dist/gui/timeline/tracks/TextTrack.js +17 -43
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +3 -4
- package/dist/gui/timeline/tracks/TimegroupTrack.js +33 -23
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TrackItem.d.ts +7 -9
- package/dist/gui/timeline/tracks/TrackItem.js +18 -17
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.d.ts +3 -3
- package/dist/gui/timeline/tracks/VideoTrack.js +11 -14
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/gui/tree/EFTree.d.ts +2 -2
- package/dist/gui/tree/EFTree.js +8 -14
- package/dist/gui/tree/EFTree.js.map +1 -1
- package/dist/gui/tree/EFTreeItem.d.ts +2 -2
- package/dist/gui/tree/EFTreeItem.js +3 -3
- package/dist/gui/tree/EFTreeItem.js.map +1 -1
- package/dist/gui/tree/treeContext.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/dist/preview/AdaptiveResolutionTracker.js +3 -3
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
- package/dist/preview/FrameController.d.ts +2 -17
- package/dist/preview/FrameController.js +40 -63
- package/dist/preview/FrameController.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.d.ts +76 -0
- package/dist/preview/QualityUpgradeScheduler.js +158 -0
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -0
- package/dist/preview/RenderContext.d.ts +119 -1
- package/dist/preview/RenderContext.js +21 -3
- package/dist/preview/RenderContext.js.map +1 -1
- package/dist/preview/RenderProfiler.js.map +1 -1
- package/dist/preview/RenderStats.js +85 -0
- package/dist/preview/RenderStats.js.map +1 -0
- package/dist/preview/encoding/canvasEncoder.js +2 -52
- package/dist/preview/encoding/canvasEncoder.js.map +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
- package/dist/preview/encoding/workerEncoder.js.map +1 -1
- package/dist/preview/logger.js.map +1 -1
- package/dist/preview/previewSettings.d.ts +34 -0
- package/dist/preview/previewSettings.js +29 -17
- package/dist/preview/previewSettings.js.map +1 -1
- package/dist/preview/previewTypes.js +4 -4
- package/dist/preview/previewTypes.js.map +1 -1
- package/dist/preview/renderElementToCanvas.d.ts +44 -0
- package/dist/preview/renderElementToCanvas.js +72 -0
- package/dist/preview/renderElementToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.d.ts +134 -32
- package/dist/preview/renderTimegroupToCanvas.js +321 -146
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.types.d.ts +51 -0
- package/dist/preview/renderTimegroupToVideo.d.ts +20 -35
- package/dist/preview/renderTimegroupToVideo.js +94 -106
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.types.d.ts +42 -0
- package/dist/preview/renderVideoToVideo.js +286 -0
- package/dist/preview/renderVideoToVideo.js.map +1 -0
- package/dist/preview/renderers.d.ts +56 -0
- package/dist/preview/renderers.js +13 -1
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/ScaleConfig.js +74 -0
- package/dist/preview/rendering/ScaleConfig.js.map +1 -0
- package/dist/preview/rendering/inlineImages.d.ts +13 -0
- package/dist/preview/rendering/inlineImages.js +7 -44
- package/dist/preview/rendering/inlineImages.js.map +1 -1
- package/dist/preview/rendering/loadImage.d.ts +8 -0
- package/dist/preview/rendering/loadImage.js +22 -0
- package/dist/preview/rendering/loadImage.js.map +1 -0
- package/dist/preview/rendering/renderToImageNative.js +3 -3
- package/dist/preview/rendering/renderToImageNative.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
- package/dist/preview/statsTrackingStrategy.js +1 -101
- package/dist/preview/statsTrackingStrategy.js.map +1 -1
- package/dist/preview/workers/WorkerPool.js +0 -1
- package/dist/preview/workers/WorkerPool.js.map +1 -1
- package/dist/preview/workers/encoderWorkerInline.js +21 -54
- package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
- package/dist/render/EFRenderAPI.d.ts +2 -1
- package/dist/render/EFRenderAPI.js +12 -36
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/render/getRenderData.js +4 -4
- package/dist/render/getRenderData.js.map +1 -1
- package/dist/style.css +114 -163
- package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
- package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
- package/dist/transcoding/types/index.d.ts +1 -1
- package/dist/transcoding/utils/UrlGenerator.js +10 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -0
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +23 -1
- package/dist/utils/frameTime.js.map +1 -1
- package/package.json +45 -8
- package/scripts/build-css.js +8 -1
- package/test/setup.ts +0 -1
- package/test/useAssetMSW.ts +50 -0
- package/test/visualRegressionUtils.ts +23 -9
- package/tsdown.config.ts +6 -1
- package/dist/_virtual/rolldown_runtime.js +0 -27
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
- package/dist/elements/EFThumbnailStrip.d.ts +0 -167
- package/dist/elements/EFThumbnailStrip.js +0 -731
- package/dist/elements/EFThumbnailStrip.js.map +0 -1
- package/dist/elements/SessionThumbnailCache.js +0 -154
- package/dist/elements/SessionThumbnailCache.js.map +0 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
- package/dist/node_modules/react/cjs/react.development.js +0 -1521
- package/dist/node_modules/react/cjs/react.development.js.map +0 -1
- package/dist/node_modules/react/index.js +0 -13
- package/dist/node_modules/react/index.js.map +0 -1
- package/dist/node_modules/react/jsx-runtime.js +0 -13
- package/dist/node_modules/react/jsx-runtime.js.map +0 -1
- package/dist/preview/encoding/types.d.ts +0 -1
- package/dist/preview/renderTimegroupPreview.js +0 -686
- package/dist/preview/renderTimegroupPreview.js.map +0 -1
- package/dist/preview/rendering/renderToImage.d.ts +0 -2
- package/dist/preview/rendering/renderToImage.js +0 -95
- package/dist/preview/rendering/renderToImage.js.map +0 -1
- package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
- package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
- package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
- package/dist/preview/rendering/svgSerializer.js +0 -43
- package/dist/preview/rendering/svgSerializer.js.map +0 -1
- package/dist/preview/rendering/types.d.ts +0 -2
- package/dist/preview/thumbnailCacheSettings.js +0 -52
- package/dist/preview/thumbnailCacheSettings.js.map +0 -1
- package/dist/sandbox/PlaybackControls.d.ts +0 -1
- package/dist/sandbox/PlaybackControls.js +0 -10
- package/dist/sandbox/PlaybackControls.js.map +0 -1
- package/dist/sandbox/ScenarioRunner.d.ts +0 -1
- package/dist/sandbox/ScenarioRunner.js +0 -1
- package/dist/sandbox/defineSandbox.d.ts +0 -1
- package/dist/sandbox/index.d.ts +0 -3
- package/dist/sandbox/index.js +0 -2
- package/test/EFVideo.framegen.browsertest.ts +0 -80
- package/test/thumbnail-performance-test.html +0 -116
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFTreeItem.js","names":["EFTreeItem"],"sources":["../../../src/gui/tree/EFTreeItem.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement, nothing } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\n\nimport type { TreeItem, TreeContext } from \"./treeContext.js\";\nimport { treeContext } from \"./treeContext.js\";\n\n/**\n * Generic tree item component.\n
|
|
1
|
+
{"version":3,"file":"EFTreeItem.js","names":["EFTreeItem"],"sources":["../../../src/gui/tree/EFTreeItem.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement, nothing } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\n\nimport type { TreeItem, TreeContext } from \"./treeContext.js\";\nimport { treeContext } from \"./treeContext.js\";\n\n/**\n * Generic tree item component.\n *\n * Renders a single item in a tree with:\n * - Expand/collapse toggle for items with children\n * - Optional icon\n * - Label\n * - Recursive children rendering\n *\n * @fires tree-item-click - When item is clicked (for selection)\n */\n@customElement(\"ef-tree-item\")\nexport class EFTreeItem extends LitElement {\n static styles = css`\n :host {\n display: block;\n }\n\n .item-row {\n display: flex;\n align-items: center;\n height: var(--tree-item-height, 1.5rem);\n padding-left: var(--tree-item-padding-left, 0.5rem);\n padding-right: var(--tree-item-padding-right, 0.5rem);\n font-size: var(--tree-item-font-size, 0.75rem);\n cursor: pointer;\n user-select: none;\n color: var(--tree-text);\n }\n\n .item-row:hover {\n background: var(--tree-hover-bg);\n }\n\n .item-row[data-selected] {\n background: var(--tree-selected-bg);\n }\n\n .expand-icon {\n width: var(--tree-expand-icon-size, 1rem);\n height: var(--tree-expand-icon-size, 1rem);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n flex-shrink: 0;\n }\n\n .expand-icon svg {\n width: 0.75rem;\n height: 0.75rem;\n transition: transform 0.15s ease;\n }\n\n .expand-icon[data-expanded] svg {\n transform: rotate(90deg);\n }\n\n .icon {\n margin-right: var(--tree-icon-gap, 0.25rem);\n flex-shrink: 0;\n display: flex;\n align-items: center;\n }\n\n .label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex: 1;\n }\n\n .children {\n padding-left: var(--tree-indent, 1rem);\n }\n\n .children[data-collapsed] {\n display: none;\n }\n `;\n\n @consume({ context: treeContext, subscribe: true })\n treeContext?: TreeContext;\n\n @property({ type: Object, attribute: false })\n item!: TreeItem;\n\n @state()\n private localExpanded = true;\n\n get isSelected(): boolean {\n if (!this.treeContext || !this.item) return false;\n return this.treeContext.state.selectedId === this.item.id;\n }\n\n get isExpanded(): boolean {\n if (!this.treeContext || !this.item) return this.localExpanded;\n return this.treeContext.state.expandedIds.has(this.item.id);\n }\n\n get hasChildren(): boolean {\n return Boolean(this.item?.children && this.item.children.length > 0);\n }\n\n private handleClick(e: Event): void {\n e.stopPropagation();\n if (this.treeContext && this.item) {\n this.treeContext.actions.select(this.item.id);\n }\n }\n\n private handleExpandClick(e: Event): void {\n e.stopPropagation();\n if (this.treeContext && this.item) {\n this.treeContext.actions.toggleExpanded(this.item.id);\n } else {\n this.localExpanded = !this.localExpanded;\n }\n }\n\n render() {\n if (!this.item) return nothing;\n\n const expanded = this.isExpanded;\n\n return html`\n <div\n class=\"item-row\"\n ?data-selected=${this.isSelected}\n @click=${this.handleClick}\n >\n ${\n this.hasChildren\n ? html`\n <span\n class=\"expand-icon\"\n ?data-expanded=${expanded}\n @click=${this.handleExpandClick}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </span>\n `\n : html`<span class=\"expand-icon\"></span>`\n }\n ${\n this.item.icon\n ? html`<span class=\"icon\">${this.item.icon}</span>`\n : nothing\n }\n <span class=\"label\">${this.item.label}</span>\n </div>\n ${\n this.hasChildren\n ? html`\n <div class=\"children\" ?data-collapsed=${!expanded}>\n ${this.item.children!.map(\n (child) => html`<ef-tree-item .item=${child}></ef-tree-item>`,\n )}\n </div>\n `\n : nothing\n }\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-tree-item\": EFTreeItem;\n }\n}\n"],"mappings":";;;;;;;AAmBO,uBAAMA,qBAAmB,WAAW;;;uBA4EjB;;;gBA3ER,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6EnB,IAAI,aAAsB;AACxB,MAAI,CAAC,KAAK,eAAe,CAAC,KAAK,KAAM,QAAO;AAC5C,SAAO,KAAK,YAAY,MAAM,eAAe,KAAK,KAAK;;CAGzD,IAAI,aAAsB;AACxB,MAAI,CAAC,KAAK,eAAe,CAAC,KAAK,KAAM,QAAO,KAAK;AACjD,SAAO,KAAK,YAAY,MAAM,YAAY,IAAI,KAAK,KAAK,GAAG;;CAG7D,IAAI,cAAuB;AACzB,SAAO,QAAQ,KAAK,MAAM,YAAY,KAAK,KAAK,SAAS,SAAS,EAAE;;CAGtE,AAAQ,YAAY,GAAgB;AAClC,IAAE,iBAAiB;AACnB,MAAI,KAAK,eAAe,KAAK,KAC3B,MAAK,YAAY,QAAQ,OAAO,KAAK,KAAK,GAAG;;CAIjD,AAAQ,kBAAkB,GAAgB;AACxC,IAAE,iBAAiB;AACnB,MAAI,KAAK,eAAe,KAAK,KAC3B,MAAK,YAAY,QAAQ,eAAe,KAAK,KAAK,GAAG;MAErD,MAAK,gBAAgB,CAAC,KAAK;;CAI/B,SAAS;AACP,MAAI,CAAC,KAAK,KAAM,QAAO;EAEvB,MAAM,WAAW,KAAK;AAEtB,SAAO,IAAI;;;yBAGU,KAAK,WAAW;iBACxB,KAAK,YAAY;;UAGxB,KAAK,cACD,IAAI;;;iCAGe,SAAS;yBACjB,KAAK,kBAAkB;;;;;;gBAOlC,IAAI,oCACT;UAEC,KAAK,KAAK,OACN,IAAI,sBAAsB,KAAK,KAAK,KAAK,WACzC,QACL;8BACqB,KAAK,KAAK,MAAM;;QAGtC,KAAK,cACD,IAAI;oDACoC,CAAC,SAAS;gBAC9C,KAAK,KAAK,SAAU,KACnB,UAAU,IAAI,uBAAuB,MAAM,kBAC7C,CAAC;;cAGJ,QACL;;;;YAlFJ,QAAQ;CAAE,SAAS;CAAa,WAAW;CAAM,CAAC;YAGlD,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAO,CAAC;YAG5C,OAAO;yBA5ET,cAAc,eAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"treeContext.js","names":[],"sources":["../../../src/gui/tree/treeContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\nimport type { TemplateResult } from \"lit\";\n\n/**\n * Generic tree item data structure.\n * Used by ef-tree for data-driven tree rendering.\n */\nexport interface TreeItem {\n /** Unique identifier for this item */\n id: string;\n /** Display label */\n label: string;\n /** Optional icon (Lit TemplateResult or string) */\n icon?: TemplateResult | string;\n /** Child items (folders have children, leaves don't) */\n children?: TreeItem[];\n /** Arbitrary payload data */\n data?: unknown;\n}\n\n/**\n * Tree component state\n */\nexport interface TreeState {\n /** Currently selected item ID */\n selectedId: string | null;\n /** Set of expanded item IDs */\n expandedIds: Set<string>;\n}\n\n/**\n * Tree component actions\n */\nexport interface TreeActions {\n /** Select an item by ID */\n select: (id: string | null) => void;\n /** Toggle an item's expanded state */\n toggleExpanded: (id: string) => void;\n /** Set an item's expanded state explicitly */\n setExpanded: (id: string, expanded: boolean) => void;\n}\n\n/**\n * Tree context provided to tree items\n */\nexport interface TreeContext {\n state: TreeState;\n actions: TreeActions;\n}\n\n/**\n * Lit context for tree components\n */\nexport const treeContext = createContext<TreeContext>(\"ef-tree-context\");\n\n/**\n * Helper to collect all item IDs from a tree (for default expanded state)\n */\nexport function collectAllIds(items: TreeItem[]): Set<string> {\n const ids = new Set<string>();\n
|
|
1
|
+
{"version":3,"file":"treeContext.js","names":[],"sources":["../../../src/gui/tree/treeContext.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\nimport type { TemplateResult } from \"lit\";\n\n/**\n * Generic tree item data structure.\n * Used by ef-tree for data-driven tree rendering.\n */\nexport interface TreeItem {\n /** Unique identifier for this item */\n id: string;\n /** Display label */\n label: string;\n /** Optional icon (Lit TemplateResult or string) */\n icon?: TemplateResult | string;\n /** Child items (folders have children, leaves don't) */\n children?: TreeItem[];\n /** Arbitrary payload data */\n data?: unknown;\n}\n\n/**\n * Tree component state\n */\nexport interface TreeState {\n /** Currently selected item ID */\n selectedId: string | null;\n /** Set of expanded item IDs */\n expandedIds: Set<string>;\n}\n\n/**\n * Tree component actions\n */\nexport interface TreeActions {\n /** Select an item by ID */\n select: (id: string | null) => void;\n /** Toggle an item's expanded state */\n toggleExpanded: (id: string) => void;\n /** Set an item's expanded state explicitly */\n setExpanded: (id: string, expanded: boolean) => void;\n}\n\n/**\n * Tree context provided to tree items\n */\nexport interface TreeContext {\n state: TreeState;\n actions: TreeActions;\n}\n\n/**\n * Lit context for tree components\n */\nexport const treeContext = createContext<TreeContext>(\"ef-tree-context\");\n\n/**\n * Helper to collect all item IDs from a tree (for default expanded state)\n */\nexport function collectAllIds(items: TreeItem[]): Set<string> {\n const ids = new Set<string>();\n\n function traverse(item: TreeItem) {\n ids.add(item.id);\n if (item.children) {\n for (const child of item.children) {\n traverse(child);\n }\n }\n }\n\n for (const item of items) {\n traverse(item);\n }\n\n return ids;\n}\n"],"mappings":";;;;;;AAqDA,MAAa,cAAc,cAA2B,kBAAkB;;;;AAKxE,SAAgB,cAAc,OAAgC;CAC5D,MAAM,sBAAM,IAAI,KAAa;CAE7B,SAAS,SAAS,MAAgB;AAChC,MAAI,IAAI,KAAK,GAAG;AAChB,MAAI,KAAK,SACP,MAAK,MAAM,SAAS,KAAK,SACvB,UAAS,MAAM;;AAKrB,MAAK,MAAM,QAAQ,MACjB,UAAS,KAAK;AAGhB,QAAO"}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,8 +4,7 @@ import { TemporalMixinInterface, isEFTemporal } from "./elements/EFTemporal.js";
|
|
|
4
4
|
import { EFMedia } from "./elements/EFMedia.js";
|
|
5
5
|
import { ContainerInfo, getContainerInfoFromElement } from "./elements/ContainerInfo.js";
|
|
6
6
|
import { ElementPositionInfo, PositionInfoMixin, getPositionInfoFromElement } from "./elements/ElementPositionInfo.js";
|
|
7
|
-
import {
|
|
8
|
-
import { RenderProgress, RenderToVideoOptions } from "./preview/renderTimegroupToVideo.js";
|
|
7
|
+
import { RenderProgress, RenderToVideoOptions } from "./preview/renderTimegroupToVideo.types.js";
|
|
9
8
|
import { CanvasElementBounds, CanvasElementData, SelectionState } from "./canvas/api/types.js";
|
|
10
9
|
import { EFPanZoom, PanZoomTransform } from "./elements/EFPanZoom.js";
|
|
11
10
|
import { EFOverlayItem, OverlayItemPosition } from "./gui/EFOverlayItem.js";
|
|
@@ -13,17 +12,18 @@ import { EFOverlayLayer } from "./gui/EFOverlayLayer.js";
|
|
|
13
12
|
import { EFTransformHandles, TransformBounds } from "./gui/EFTransformHandles.js";
|
|
14
13
|
import { EFCanvas } from "./canvas/EFCanvas.js";
|
|
15
14
|
import { EFHierarchy } from "./gui/hierarchy/EFHierarchy.js";
|
|
16
|
-
import { EFTrimHandles, TrimChangeDetail } from "./gui/timeline/TrimHandles.js";
|
|
17
|
-
import { EFThumbnailStrip } from "./
|
|
15
|
+
import { EFTrimHandles, TrimChangeDetail, TrimValue } from "./gui/timeline/TrimHandles.js";
|
|
16
|
+
import { EFThumbnailStrip } from "./gui/timeline/tracks/EFThumbnailStrip.js";
|
|
18
17
|
import { EFAudio } from "./elements/EFAudio.js";
|
|
18
|
+
import { EFVideo } from "./elements/EFVideo.js";
|
|
19
19
|
import { EFCaptions, EFCaptionsActiveWord, EFCaptionsAfterActiveWord, EFCaptionsBeforeActiveWord, EFCaptionsSegment } from "./elements/EFCaptions.js";
|
|
20
20
|
import { EFTimelineRuler, calculateFrameIntervalMs, calculatePixelsPerFrame, quantizeToFrameTimeMs, shouldShowFrameMarkers } from "./gui/EFTimelineRuler.js";
|
|
21
21
|
import { EFTimeline } from "./gui/timeline/EFTimeline.js";
|
|
22
22
|
import { EFFilmstrip } from "./gui/EFFilmstrip.js";
|
|
23
|
-
import { EFFitScale } from "./gui/EFFitScale.js";
|
|
23
|
+
import { EFFitScale, ScaleInput, ScaleOutput, computeFitScale } from "./gui/EFFitScale.js";
|
|
24
24
|
import { EFWorkbench } from "./gui/EFWorkbench.js";
|
|
25
25
|
import { EFTimegroup } from "./elements/EFTimegroup.js";
|
|
26
|
-
import "./
|
|
26
|
+
import { CloneFactory, CloneFactoryResult, getCloneFactory, registerCloneFactory, unregisterCloneFactory } from "./elements/cloneFactoryRegistry.js";
|
|
27
27
|
import { elementNeedsFitScale, needsFitScale } from "./gui/FitScaleHelpers.js";
|
|
28
28
|
import { EFImage } from "./elements/EFImage.js";
|
|
29
29
|
import { EFTextSegment } from "./elements/EFTextSegment.js";
|
|
@@ -52,7 +52,9 @@ import { EFSurface } from "./elements/EFSurface.js";
|
|
|
52
52
|
import { EFCanvasItem } from "./canvas/EFCanvasItem.js";
|
|
53
53
|
import { CanvasAPI } from "./canvas/api/CanvasAPI.js";
|
|
54
54
|
import { SelectionModel } from "./canvas/selection/SelectionModel.js";
|
|
55
|
+
import { RenderInfo, RenderInfoSchema, getRenderInfo } from "./getRenderInfo.js";
|
|
55
56
|
import "./render/EFRenderAPI.js";
|
|
56
|
-
import { RenderInfo, getRenderInfo } from "./getRenderInfo.js";
|
|
57
57
|
import { getRenderData } from "./render/getRenderData.js";
|
|
58
|
-
|
|
58
|
+
import { CanvasPreviewOptions, CanvasPreviewResult, CaptureOptions, ContentReadyMode } from "./preview/renderTimegroupToCanvas.types.js";
|
|
59
|
+
import { RenderElementOptions, renderElementToCanvas } from "./preview/renderElementToCanvas.js";
|
|
60
|
+
export { type BoxBounds, CanvasAPI, type CanvasElementBounds, type CanvasElementData, type CanvasPreviewOptions, type CanvasPreviewResult, type CaptureOptions, type CloneFactory, type CloneFactoryResult, type ContainerInfo, type ContentReadyMode, type DialChangeDetail, EFActiveRootTemporal, EFAudio, EFAudioHierarchyItem, EFCanvas, EFCanvasItem, EFCaptions, EFCaptionsActiveWord, EFCaptionsActiveWordHierarchyItem, EFCaptionsAfterActiveWord, EFCaptionsBeforeActiveWord, EFCaptionsHierarchyItem, EFCaptionsSegment, EFConfiguration, EFControls, EFDial, EFFilmstrip, EFFitScale, EFFocusOverlay, EFHTMLHierarchyItem, EFHierarchy, EFHierarchyItem, EFImage, EFImageHierarchyItem, type EFMedia, EFOverlayItem, EFOverlayLayer, EFPanZoom, EFPause, EFPlay, EFPreview, EFResizableBox, EFScrubber, EFSurface, EFText, EFTextHierarchyItem, EFTextSegment, EFTextSegmentHierarchyItem, EFThumbnailStrip, EFTimeDisplay, EFTimegroup, EFTimegroupHierarchyItem, EFTimeline, EFTimelineRuler, EFToggleLoop, EFTogglePlay, EFTransformHandles, EFTree, EFTreeItem, EFTrimHandles, EFVideo, EFVideoHierarchyItem, EFWaveform, EFWaveformHierarchyItem, EFWorkbench, type ElementPositionInfo, type HierarchyActions, type HierarchyContext, type HierarchyState, type OverlayItemPosition, type PanZoomTransform, PositionInfoMixin, type RenderElementOptions, type RenderInfo, RenderInfoSchema, type RenderProgress, type RenderToVideoOptions, type ScaleInput, type ScaleOutput, SelectionModel, type SelectionState, type TemporalMixinInterface, type TraceContext, type TransformBounds, type TreeActions, type TreeContext, type TreeItem, type TreeState, type TrimChangeDetail, type TrimValue, calculateFrameIntervalMs, calculatePixelsPerFrame, collectAllIds, computeFitScale, elementNeedsFitScale, getCloneFactory, getContainerInfoFromElement, getCornerPoint, getOppositeCorner, getPositionInfoFromElement, getRenderData, getRenderInfo, hierarchyContext, isEFTemporal, needsFitScale, quantizeToFrameTimeMs, registerCloneFactory, renderElementToCanvas, rotatePoint, shouldShowFrameMarkers, treeContext, unregisterCloneFactory };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getCloneFactory, registerCloneFactory, unregisterCloneFactory } from "./elements/cloneFactoryRegistry.js";
|
|
1
2
|
import { isEFTemporal } from "./elements/EFTemporal.js";
|
|
2
3
|
import { EFConfiguration } from "./gui/EFConfiguration.js";
|
|
3
4
|
import "./elements/EFMedia.js";
|
|
@@ -19,15 +20,14 @@ import { EFWaveform } from "./elements/EFWaveform.js";
|
|
|
19
20
|
import { EFAudioHierarchyItem, EFCaptionsActiveWordHierarchyItem, EFCaptionsHierarchyItem, EFHTMLHierarchyItem, EFHierarchyItem, EFImageHierarchyItem, EFTextHierarchyItem, EFTextSegmentHierarchyItem, EFTimegroupHierarchyItem, EFVideoHierarchyItem, EFWaveformHierarchyItem } from "./gui/hierarchy/EFHierarchyItem.js";
|
|
20
21
|
import { EFHierarchy } from "./gui/hierarchy/EFHierarchy.js";
|
|
21
22
|
import { EFTrimHandles } from "./gui/timeline/TrimHandles.js";
|
|
22
|
-
import { EFThumbnailStrip } from "./
|
|
23
|
+
import { EFThumbnailStrip } from "./gui/timeline/tracks/EFThumbnailStrip.js";
|
|
23
24
|
import { EFTimelineRuler, calculateFrameIntervalMs, calculatePixelsPerFrame, quantizeToFrameTimeMs, shouldShowFrameMarkers } from "./gui/EFTimelineRuler.js";
|
|
24
25
|
import { EFTimeline } from "./gui/timeline/EFTimeline.js";
|
|
25
26
|
import { EFFilmstrip } from "./gui/EFFilmstrip.js";
|
|
26
|
-
import { EFFitScale } from "./gui/EFFitScale.js";
|
|
27
|
+
import { EFFitScale, computeFitScale } from "./gui/EFFitScale.js";
|
|
27
28
|
import { EFWorkbench } from "./gui/EFWorkbench.js";
|
|
28
29
|
import { EFPanZoom } from "./elements/EFPanZoom.js";
|
|
29
30
|
import { EFTimegroup } from "./elements/EFTimegroup.js";
|
|
30
|
-
import "./sandbox/index.js";
|
|
31
31
|
import { elementNeedsFitScale, needsFitScale } from "./gui/FitScaleHelpers.js";
|
|
32
32
|
import { EFPreview } from "./gui/EFPreview.js";
|
|
33
33
|
import { collectAllIds, treeContext } from "./gui/tree/treeContext.js";
|
|
@@ -49,13 +49,14 @@ import { CanvasAPI } from "./canvas/api/CanvasAPI.js";
|
|
|
49
49
|
import { EFCanvasItem } from "./canvas/EFCanvasItem.js";
|
|
50
50
|
import { EFOverlayItem } from "./gui/EFOverlayItem.js";
|
|
51
51
|
import "./EF_FRAMEGEN.js";
|
|
52
|
-
import {
|
|
52
|
+
import { RenderInfoSchema, getRenderInfo } from "./getRenderInfo.js";
|
|
53
53
|
import "./render/EFRenderAPI.js";
|
|
54
54
|
import { getRenderData } from "./render/getRenderData.js";
|
|
55
|
+
import { renderElementToCanvas } from "./preview/renderElementToCanvas.js";
|
|
55
56
|
|
|
56
57
|
//#region src/index.ts
|
|
57
58
|
if (typeof window !== "undefined") window.EF_REGISTERED = true;
|
|
58
59
|
|
|
59
60
|
//#endregion
|
|
60
|
-
export { CanvasAPI, EFActiveRootTemporal, EFAudio, EFAudioHierarchyItem, EFCanvas, EFCanvasItem, EFCaptions, EFCaptionsActiveWord, EFCaptionsActiveWordHierarchyItem, EFCaptionsAfterActiveWord, EFCaptionsBeforeActiveWord, EFCaptionsHierarchyItem, EFCaptionsSegment, EFConfiguration, EFControls, EFDial, EFFilmstrip, EFFitScale, EFFocusOverlay, EFHTMLHierarchyItem, EFHierarchy, EFHierarchyItem, EFImage, EFImageHierarchyItem, EFOverlayItem, EFOverlayLayer, EFPanZoom, EFPause, EFPlay, EFPreview, EFResizableBox, EFScrubber, EFSurface, EFText, EFTextHierarchyItem, EFTextSegment, EFTextSegmentHierarchyItem, EFThumbnailStrip, EFTimeDisplay, EFTimegroup, EFTimegroupHierarchyItem, EFTimeline, EFTimelineRuler, EFToggleLoop, EFTogglePlay, EFTransformHandles, EFTree, EFTreeItem, EFTrimHandles, EFVideo, EFVideoHierarchyItem, EFWaveform, EFWaveformHierarchyItem, EFWorkbench, PositionInfoMixin,
|
|
61
|
+
export { CanvasAPI, EFActiveRootTemporal, EFAudio, EFAudioHierarchyItem, EFCanvas, EFCanvasItem, EFCaptions, EFCaptionsActiveWord, EFCaptionsActiveWordHierarchyItem, EFCaptionsAfterActiveWord, EFCaptionsBeforeActiveWord, EFCaptionsHierarchyItem, EFCaptionsSegment, EFConfiguration, EFControls, EFDial, EFFilmstrip, EFFitScale, EFFocusOverlay, EFHTMLHierarchyItem, EFHierarchy, EFHierarchyItem, EFImage, EFImageHierarchyItem, EFOverlayItem, EFOverlayLayer, EFPanZoom, EFPause, EFPlay, EFPreview, EFResizableBox, EFScrubber, EFSurface, EFText, EFTextHierarchyItem, EFTextSegment, EFTextSegmentHierarchyItem, EFThumbnailStrip, EFTimeDisplay, EFTimegroup, EFTimegroupHierarchyItem, EFTimeline, EFTimelineRuler, EFToggleLoop, EFTogglePlay, EFTransformHandles, EFTree, EFTreeItem, EFTrimHandles, EFVideo, EFVideoHierarchyItem, EFWaveform, EFWaveformHierarchyItem, EFWorkbench, PositionInfoMixin, RenderInfoSchema, SelectionModel, calculateFrameIntervalMs, calculatePixelsPerFrame, collectAllIds, computeFitScale, elementNeedsFitScale, getCloneFactory, getContainerInfoFromElement, getCornerPoint, getOppositeCorner, getPositionInfoFromElement, getRenderData, getRenderInfo, hierarchyContext, isEFTemporal, needsFitScale, quantizeToFrameTimeMs, registerCloneFactory, renderElementToCanvas, rotatePoint, shouldShowFrameMarkers, treeContext, unregisterCloneFactory };
|
|
61
62
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import \"./elements/EFTimegroup.js\";\
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import \"./elements/EFTimegroup.js\";\n\nexport { EFTimegroup } from \"./elements/EFTimegroup.js\";\nexport {\n registerCloneFactory,\n unregisterCloneFactory,\n getCloneFactory,\n type CloneFactory,\n type CloneFactoryResult,\n} from \"./elements/cloneFactoryRegistry.js\";\nexport type { ContainerInfo } from \"./elements/ContainerInfo.js\";\nexport { getContainerInfoFromElement } from \"./elements/ContainerInfo.js\";\nexport type { ElementPositionInfo } from \"./elements/ElementPositionInfo.js\";\nexport {\n getPositionInfoFromElement,\n PositionInfoMixin,\n} from \"./elements/ElementPositionInfo.js\";\nexport { needsFitScale, elementNeedsFitScale } from \"./gui/FitScaleHelpers.js\";\n\nimport \"./elements/EFImage.js\";\n\nexport { EFImage } from \"./elements/EFImage.js\";\n\nimport \"./elements/EFMedia.js\";\n\nexport type { EFMedia } from \"./elements/EFMedia.js\";\n\nimport \"./elements/EFAudio.js\";\n\nexport { EFAudio } from \"./elements/EFAudio.js\";\n\nimport \"./elements/EFVideo.js\";\n\nexport { EFVideo } from \"./elements/EFVideo.js\";\n\nimport \"./elements/EFCaptions.js\";\n\nexport {\n EFCaptions,\n EFCaptionsActiveWord,\n EFCaptionsAfterActiveWord,\n EFCaptionsBeforeActiveWord,\n EFCaptionsSegment,\n} from \"./elements/EFCaptions.js\";\n\nimport \"./elements/EFText.js\";\nimport \"./elements/EFTextSegment.js\";\n\nexport { EFText } from \"./elements/EFText.js\";\nexport { EFTextSegment } from \"./elements/EFTextSegment.js\";\n\nimport \"./elements/EFWaveform.js\";\n\nexport { EFWaveform } from \"./elements/EFWaveform.js\";\n\nimport \"./elements/EFTemporal.js\";\n\nexport { isEFTemporal } from \"./elements/EFTemporal.js\";\nexport type { TemporalMixinInterface } from \"./elements/EFTemporal.js\";\n\nimport \"./gui/EFConfiguration.ts\";\n\nexport { EFConfiguration } from \"./gui/EFConfiguration.ts\";\n\nimport \"./gui/EFWorkbench.js\";\n\nexport { EFWorkbench } from \"./gui/EFWorkbench.js\";\n\nimport \"./gui/EFPreview.js\";\n\nexport { EFPreview } from \"./gui/EFPreview.js\";\n\nimport \"./gui/EFFilmstrip.js\";\n\nexport { EFFilmstrip } from \"./gui/EFFilmstrip.js\";\n\nimport \"./gui/hierarchy/EFHierarchy.js\";\nimport \"./gui/hierarchy/EFHierarchyItem.js\";\n\nexport { EFHierarchy } from \"./gui/hierarchy/EFHierarchy.js\";\nexport {\n EFHierarchyItem,\n EFTimegroupHierarchyItem,\n EFAudioHierarchyItem,\n EFVideoHierarchyItem,\n EFCaptionsHierarchyItem,\n EFCaptionsActiveWordHierarchyItem,\n EFTextHierarchyItem,\n EFTextSegmentHierarchyItem,\n EFWaveformHierarchyItem,\n EFImageHierarchyItem,\n EFHTMLHierarchyItem,\n} from \"./gui/hierarchy/EFHierarchyItem.js\";\nexport type {\n HierarchyState,\n HierarchyActions,\n HierarchyContext,\n} from \"./gui/hierarchy/hierarchyContext.js\";\nexport { hierarchyContext } from \"./gui/hierarchy/hierarchyContext.js\";\n\n// Generic tree component\nimport \"./gui/tree/EFTree.js\";\nimport \"./gui/tree/EFTreeItem.js\";\n\nexport { EFTree } from \"./gui/tree/EFTree.js\";\nexport { EFTreeItem } from \"./gui/tree/EFTreeItem.js\";\nexport type {\n TreeItem,\n TreeState,\n TreeActions,\n TreeContext,\n} from \"./gui/tree/treeContext.js\";\nexport { treeContext, collectAllIds } from \"./gui/tree/treeContext.js\";\n\nimport \"./gui/EFTogglePlay.js\";\n\nexport { EFTogglePlay } from \"./gui/EFTogglePlay.js\";\n\nimport \"./gui/EFPlay.js\";\n\nexport { EFPlay } from \"./gui/EFPlay.js\";\n\nimport \"./gui/EFPause.js\";\n\nexport { EFPause } from \"./gui/EFPause.js\";\n\nimport \"./gui/EFToggleLoop.js\";\n\nexport { EFToggleLoop } from \"./gui/EFToggleLoop.js\";\n\nimport \"./gui/EFScrubber.js\";\n\nexport { EFScrubber } from \"./gui/EFScrubber.js\";\n\nimport \"./gui/EFTimeDisplay.js\";\n\nexport { EFTimeDisplay } from \"./gui/EFTimeDisplay.js\";\n\nimport \"./gui/EFActiveRootTemporal.js\";\n\nexport { EFActiveRootTemporal } from \"./gui/EFActiveRootTemporal.js\";\n\nimport \"./gui/EFDial.js\";\n\nexport { type DialChangeDetail, EFDial } from \"./gui/EFDial.js\";\n\nimport \"./gui/EFControls.js\";\n\nexport { EFControls } from \"./gui/EFControls.js\";\n\nimport \"./gui/EFFocusOverlay.js\";\n\nexport { EFFocusOverlay } from \"./gui/EFFocusOverlay.js\";\n\nimport \"./gui/transformUtils.js\";\n\nexport {\n getCornerPoint,\n getOppositeCorner,\n rotatePoint,\n} from \"./gui/transformUtils.js\";\n\nimport \"./gui/EFTransformHandles.ts\";\n\nexport {\n type TransformBounds,\n EFTransformHandles,\n} from \"./gui/EFTransformHandles.ts\";\n\nimport \"./gui/EFResizableBox.ts\";\n\nexport { type BoxBounds, EFResizableBox } from \"./gui/EFResizableBox.ts\";\n\nimport \"./gui/EFFitScale.js\";\n\nexport {\n EFFitScale,\n computeFitScale,\n type ScaleInput,\n type ScaleOutput,\n} from \"./gui/EFFitScale.js\";\n\nimport \"./elements/EFSurface.ts\";\n\nexport { EFSurface } from \"./elements/EFSurface.ts\";\n\nimport \"./elements/EFPanZoom.js\";\n\nexport { EFPanZoom } from \"./elements/EFPanZoom.js\";\nexport type { PanZoomTransform } from \"./elements/EFPanZoom.js\";\n\nimport \"./canvas/EFCanvas.js\";\nimport \"./canvas/EFCanvasItem.js\";\n\nexport { EFCanvas } from \"./canvas/EFCanvas.js\";\nexport { EFCanvasItem } from \"./canvas/EFCanvasItem.js\";\nexport { CanvasAPI } from \"./canvas/api/CanvasAPI.js\";\nexport type {\n CanvasElementData,\n SelectionState,\n CanvasElementBounds,\n} from \"./canvas/api/types.js\";\nexport { SelectionModel } from \"./canvas/selection/SelectionModel.js\";\n\nimport \"./gui/EFOverlayLayer.ts\";\n\nexport { EFOverlayLayer } from \"./gui/EFOverlayLayer.ts\";\n\nimport \"./gui/EFOverlayItem.ts\";\n\nexport { EFOverlayItem } from \"./gui/EFOverlayItem.ts\";\nexport type { OverlayItemPosition } from \"./gui/EFOverlayItem.ts\";\n\nimport \"./gui/EFTimelineRuler.ts\";\n\nexport {\n EFTimelineRuler,\n quantizeToFrameTimeMs,\n calculateFrameIntervalMs,\n calculatePixelsPerFrame,\n shouldShowFrameMarkers,\n} from \"./gui/EFTimelineRuler.ts\";\n\nimport \"./gui/timeline/EFTimeline.js\";\nimport \"./gui/timeline/TrimHandles.js\";\nimport \"./gui/timeline/tracks/EFThumbnailStrip.js\";\n\nexport { EFTimeline } from \"./gui/timeline/EFTimeline.js\";\nexport {\n EFTrimHandles,\n type TrimValue,\n type TrimChangeDetail,\n} from \"./gui/timeline/TrimHandles.js\";\nexport { EFThumbnailStrip } from \"./gui/timeline/tracks/EFThumbnailStrip.js\";\n\nif (typeof window !== \"undefined\") {\n // @ts-expect-error\n window.EF_REGISTERED = true;\n}\n\nimport \"./EF_FRAMEGEN.js\";\n\n// Initialize render API\nimport \"./render/EFRenderAPI.js\";\n\nexport {\n getRenderInfo,\n RenderInfoSchema,\n type RenderInfo,\n} from \"./getRenderInfo.js\";\nexport { getRenderData } from \"./render/getRenderData.js\";\n// Export types only - actual render functions are loaded dynamically by EFTimegroup\nexport type {\n RenderToVideoOptions,\n RenderProgress,\n} from \"./preview/renderTimegroupToVideo.types.js\";\nexport type {\n ContentReadyMode,\n CaptureOptions,\n CanvasPreviewOptions,\n CanvasPreviewResult,\n} from \"./preview/renderTimegroupToCanvas.types.js\";\nexport type { TraceContext } from \"./otel/tracingHelpers.js\";\n\n// Element-to-canvas rendering\nexport {\n renderElementToCanvas,\n type RenderElementOptions,\n} from \"./preview/renderElementToCanvas.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2OA,IAAI,OAAO,WAAW,YAEpB,QAAO,gBAAgB"}
|
package/dist/node.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ import { ElementPositionInfo } from "./elements/ElementPositionInfo.js";
|
|
|
4
4
|
import { EFCaptions } from "./elements/EFCaptions.js";
|
|
5
5
|
import { EFTimegroup } from "./elements/EFTimegroup.js";
|
|
6
6
|
import { EFImage } from "./elements/EFImage.js";
|
|
7
|
-
import { RenderInfo, getRenderInfo } from "./getRenderInfo.js";
|
|
8
|
-
export { type ContainerInfo, type EFCaptions, type EFImage, type EFMedia, type EFTimegroup, type ElementPositionInfo, RenderInfo, getRenderInfo };
|
|
7
|
+
import { RenderInfo, RenderInfoSchema, getRenderInfo } from "./getRenderInfo.js";
|
|
8
|
+
export { type ContainerInfo, type EFCaptions, type EFImage, type EFMedia, type EFTimegroup, type ElementPositionInfo, type RenderInfo, RenderInfoSchema, getRenderInfo };
|
package/dist/node.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RenderInfoSchema, getRenderInfo } from "./getRenderInfo.js";
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { RenderInfoSchema, getRenderInfo };
|
|
@@ -29,7 +29,7 @@ const SCALE_STEPS = [
|
|
|
29
29
|
];
|
|
30
30
|
/**
|
|
31
31
|
* Timing thresholds
|
|
32
|
-
*
|
|
32
|
+
*
|
|
33
33
|
* Target: 30fps = 33.33ms per frame
|
|
34
34
|
* Tolerate down to 15fps (half target) before scaling down.
|
|
35
35
|
* Scale up when we have plenty of headroom.
|
|
@@ -73,7 +73,7 @@ var AdaptiveResolutionTracker = class {
|
|
|
73
73
|
if (this.pressureHistory.length > PRESSURE_HISTORY_SIZE) this.pressureHistory.shift();
|
|
74
74
|
}
|
|
75
75
|
});
|
|
76
|
-
this.pressureObserver.observe("cpu", { sampleInterval: 500 });
|
|
76
|
+
this.pressureObserver.observe("cpu", { sampleInterval: 500 }).catch(() => {});
|
|
77
77
|
} catch (e) {
|
|
78
78
|
logger.warn("[AdaptiveResolutionTracker] Failed to initialize PressureObserver:", e);
|
|
79
79
|
this.pressureObserver = null;
|
|
@@ -82,7 +82,7 @@ var AdaptiveResolutionTracker = class {
|
|
|
82
82
|
/**
|
|
83
83
|
* Record a frame's render time.
|
|
84
84
|
* Call this AFTER each render completes with how long the render took.
|
|
85
|
-
*
|
|
85
|
+
*
|
|
86
86
|
* @param renderTimeMs - How long the render() call took in milliseconds
|
|
87
87
|
* @param timestamp - Optional rAF timestamp for frame interval tracking (display only)
|
|
88
88
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdaptiveResolutionTracker.js","names":[],"sources":["../../src/preview/AdaptiveResolutionTracker.ts"],"sourcesContent":["/**\n * Adaptive Resolution Tracker\n * \n * Monitors actual render time to dynamically adjust preview resolution\n * for smooth playback without dropped frames.\n * \n * Key insight: We measure how long each render() takes, not rAF timing.\n * If renders consistently take longer than our frame budget, scale down.\n * If renders consistently have headroom, scale up.\n */\n\nimport { logger } from \"./logger.js\";\n\n/**\n * Available resolution scale steps for adaptive scaling.\n * Finer-grained steps (5% increments) for smoother adaptation.\n * Ordered from highest to lowest quality.\n */\nconst SCALE_STEPS = [\n 1.0, 0.95, 0.90, 0.85, 0.80, \n 0.75, 0.70, 0.65, 0.60, 0.55,\n 0.50, 0.45, 0.40, 0.35, 0.30,\n 0.25, 0.20, 0.15, 0.10\n] as const;\ntype ScaleStep = typeof SCALE_STEPS[number];\n\n/**\n * Compute Pressure API types (not yet in TypeScript lib)\n */\ntype PressureState = \"nominal\" | \"fair\" | \"serious\" | \"critical\";\n\ninterface PressureRecord {\n state: PressureState;\n time: number;\n}\n\ninterface PressureObserverCallback {\n (records: PressureRecord[]): void;\n}\n\ninterface PressureObserverOptions {\n sampleInterval?: number;\n}\n\ndeclare class PressureObserver {\n constructor(callback: PressureObserverCallback);\n observe(source: \"cpu\", options?: PressureObserverOptions): Promise<void>;\n unobserve(source: \"cpu\"): void;\n disconnect(): void;\n}\n\n/**\n * Timing thresholds\n * \n * Target: 30fps = 33.33ms per frame\n * Tolerate down to 15fps (half target) before scaling down.\n * Scale up when we have plenty of headroom.\n */\nconst TARGET_FRAME_TIME_MS = 33.33; // 30fps target\nconst SCALE_DOWN_THRESHOLD_MS = 66.67; // 15fps (half target) - only scale down if really struggling\nconst SCALE_UP_THRESHOLD_MS = 25; // If avg render time is well below target, consider scaling up\nconst ROLLING_WINDOW_SIZE = 30; // ~1 second of samples at 30fps\nconst MIN_SCALE_CHANGE_INTERVAL_MS = 2000; // Wait 2s between any scale changes\nconst SCALE_UP_STABILITY_SAMPLES = 60; // Need 60 samples (~2s) of good performance to scale up\n\n/** Size of the pressure history for histogram display */\nconst PRESSURE_HISTORY_SIZE = 60;\n\n/**\n * Tracks render time to recommend optimal preview resolution.\n */\nexport class AdaptiveResolutionTracker {\n private renderTimes: number[] = []; // Rolling window of render times (ms)\n private currentScaleIndex = 0; // Index into SCALE_STEPS (0 = highest quality)\n private lastScaleChangeTime = 0;\n private samplesAtCurrentScale = 0; // How many samples we've collected at current scale\n \n // Compute Pressure API\n private pressureObserver: PressureObserver | null = null;\n private pressureState: PressureState = \"nominal\";\n private pressureHistory: PressureState[] = [];\n \n // For display - track frame intervals separately from render times\n private lastFrameTime = 0;\n private frameIntervals: number[] = [];\n \n // Callbacks\n private onScaleChange?: (scale: ScaleStep) => void;\n \n constructor(options?: { onScaleChange?: (scale: ScaleStep) => void }) {\n this.onScaleChange = options?.onScaleChange;\n this.initPressureObserver();\n }\n \n /**\n * Initialize Compute Pressure API observer if available.\n */\n private initPressureObserver(): void {\n if (!(\"PressureObserver\" in globalThis)) {\n return;\n }\n \n try {\n this.pressureObserver = new PressureObserver((records) => {\n if (records.length > 0) {\n const latest = records[records.length - 1]!;\n this.pressureState = latest.state;\n \n this.pressureHistory.push(latest.state);\n if (this.pressureHistory.length > PRESSURE_HISTORY_SIZE) {\n this.pressureHistory.shift();\n }\n }\n });\n \n this.pressureObserver.observe(\"cpu\", { sampleInterval: 500 });\n } catch (e) {\n logger.warn(\"[AdaptiveResolutionTracker] Failed to initialize PressureObserver:\", e);\n this.pressureObserver = null;\n }\n }\n \n /**\n * Record a frame's render time.\n * Call this AFTER each render completes with how long the render took.\n * \n * @param renderTimeMs - How long the render() call took in milliseconds\n * @param timestamp - Optional rAF timestamp for frame interval tracking (display only)\n */\n recordFrame(renderTimeMs: number, timestamp?: number): void {\n // Track render times for adaptive decisions\n this.renderTimes.push(renderTimeMs);\n if (this.renderTimes.length > ROLLING_WINDOW_SIZE) {\n this.renderTimes.shift();\n }\n this.samplesAtCurrentScale++;\n \n // Track frame intervals for FPS display (separate from render time)\n if (timestamp !== undefined && this.lastFrameTime > 0) {\n const interval = timestamp - this.lastFrameTime;\n this.frameIntervals.push(interval);\n if (this.frameIntervals.length > ROLLING_WINDOW_SIZE) {\n this.frameIntervals.shift();\n }\n }\n if (timestamp !== undefined) {\n this.lastFrameTime = timestamp;\n }\n \n // Check if we should adjust scale\n this.checkForScaleAdjustment();\n }\n \n /**\n * Check if we should scale up or down based on render time trends.\n */\n private checkForScaleAdjustment(): void {\n if (this.renderTimes.length < 10) return; // Need some samples\n \n const now = performance.now();\n if (now - this.lastScaleChangeTime < MIN_SCALE_CHANGE_INTERVAL_MS) {\n return; // Rate limit changes\n }\n \n const avgRenderTime = this.renderTimes.reduce((a, b) => a + b, 0) / this.renderTimes.length;\n \n // Scale DOWN if we're consistently slow\n if (avgRenderTime > SCALE_DOWN_THRESHOLD_MS) {\n this.scaleDown(\"slow\");\n return;\n }\n \n // Scale DOWN if CPU pressure is high (proactive)\n if (this.pressureState === \"critical\" || this.pressureState === \"serious\") {\n this.scaleDown(\"pressure\");\n return;\n }\n \n // Scale UP if we have sustained headroom and CPU isn't under pressure\n // (we already returned above if pressure is serious/critical, but check again for clarity)\n const pressureOk = this.pressureState === \"nominal\" || this.pressureState === \"fair\";\n if (avgRenderTime < SCALE_UP_THRESHOLD_MS && \n this.samplesAtCurrentScale >= SCALE_UP_STABILITY_SAMPLES &&\n pressureOk) {\n this.scaleUp();\n }\n }\n \n /**\n * Decrease resolution (increase scale index).\n */\n private scaleDown(reason: \"slow\" | \"pressure\"): void {\n if (this.currentScaleIndex < SCALE_STEPS.length - 1) {\n this.currentScaleIndex++;\n this.lastScaleChangeTime = performance.now();\n this.samplesAtCurrentScale = 0;\n this.renderTimes = []; // Clear history at new scale\n \n const newScale = SCALE_STEPS[this.currentScaleIndex]!;\n logger.debug(`[AdaptiveResolutionTracker] Scaling DOWN to ${(newScale * 100).toFixed(0)}% (reason: ${reason})`);\n this.onScaleChange?.(newScale);\n }\n }\n \n /**\n * Increase resolution (decrease scale index).\n */\n private scaleUp(): void {\n if (this.currentScaleIndex > 0) {\n this.currentScaleIndex--;\n this.lastScaleChangeTime = performance.now();\n this.samplesAtCurrentScale = 0;\n this.renderTimes = []; // Clear history at new scale\n \n const newScale = SCALE_STEPS[this.currentScaleIndex]!;\n logger.debug(`[AdaptiveResolutionTracker] Scaling UP to ${(newScale * 100).toFixed(0)}% (reason: stable performance)`);\n this.onScaleChange?.(newScale);\n }\n }\n \n /**\n * Get the current recommended scale factor.\n */\n getRecommendedScale(): ScaleStep {\n return SCALE_STEPS[this.currentScaleIndex]!;\n }\n \n /**\n * Get current statistics for display.\n */\n getStats(): {\n currentScale: ScaleStep;\n avgRenderTime: number;\n fps: number;\n pressureState: PressureState;\n pressureHistory: PressureState[];\n samplesAtCurrentScale: number;\n canScaleUp: boolean;\n canScaleDown: boolean;\n headroom: number; // How much faster than target we're rendering (negative = behind)\n } {\n const avgRenderTime = this.renderTimes.length > 0\n ? this.renderTimes.reduce((a, b) => a + b, 0) / this.renderTimes.length\n : 0;\n \n // FPS based on frame intervals (how often we're called), not render time\n const avgFrameInterval = this.frameIntervals.length > 0\n ? this.frameIntervals.reduce((a, b) => a + b, 0) / this.frameIntervals.length\n : 16.67;\n const fps = avgFrameInterval > 0 ? 1000 / avgFrameInterval : 0;\n \n const now = performance.now();\n const timeSinceLastChange = now - this.lastScaleChangeTime;\n const canChange = timeSinceLastChange >= MIN_SCALE_CHANGE_INTERVAL_MS;\n \n const pressureOk = this.pressureState === \"nominal\" || this.pressureState === \"fair\";\n const canScaleUp = canChange && \n this.currentScaleIndex > 0 && \n avgRenderTime < SCALE_UP_THRESHOLD_MS &&\n this.samplesAtCurrentScale >= SCALE_UP_STABILITY_SAMPLES &&\n pressureOk;\n \n const canScaleDown = canChange && \n this.currentScaleIndex < SCALE_STEPS.length - 1;\n \n // Headroom: positive = we're faster than needed, negative = we're behind\n const headroom = TARGET_FRAME_TIME_MS - avgRenderTime;\n \n return {\n currentScale: this.getRecommendedScale(),\n avgRenderTime,\n fps,\n pressureState: this.pressureState,\n pressureHistory: [...this.pressureHistory],\n samplesAtCurrentScale: this.samplesAtCurrentScale,\n canScaleUp,\n canScaleDown,\n headroom,\n };\n }\n \n /**\n * Reset the tracker state.\n */\n reset(): void {\n this.lastFrameTime = 0;\n this.frameIntervals = [];\n this.renderTimes = [];\n this.currentScaleIndex = 0;\n this.lastScaleChangeTime = 0;\n this.samplesAtCurrentScale = 0;\n }\n \n /**\n * Initialize the tracker to start at a specific scale.\n */\n initializeAtScale(targetScale: number): void {\n let bestIndex = 0;\n for (let i = 0; i < SCALE_STEPS.length; i++) {\n if (SCALE_STEPS[i]! <= targetScale) {\n bestIndex = i;\n break;\n }\n }\n \n this.currentScaleIndex = bestIndex;\n this.lastFrameTime = 0;\n this.frameIntervals = [];\n this.renderTimes = [];\n this.lastScaleChangeTime = 0;\n this.samplesAtCurrentScale = 0;\n \n logger.debug(`[AdaptiveResolutionTracker] Initialized at scale ${(SCALE_STEPS[bestIndex]! * 100).toFixed(0)}%`);\n }\n \n /**\n * Clean up resources.\n */\n dispose(): void {\n if (this.pressureObserver) {\n try {\n this.pressureObserver.disconnect();\n } catch {\n // Ignore cleanup errors\n }\n this.pressureObserver = null;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdaptiveResolutionTracker.js","names":[],"sources":["../../src/preview/AdaptiveResolutionTracker.ts"],"sourcesContent":["/**\n * Adaptive Resolution Tracker\n *\n * Monitors actual render time to dynamically adjust preview resolution\n * for smooth playback without dropped frames.\n *\n * Key insight: We measure how long each render() takes, not rAF timing.\n * If renders consistently take longer than our frame budget, scale down.\n * If renders consistently have headroom, scale up.\n */\n\nimport { logger } from \"./logger.js\";\n\n/**\n * Available resolution scale steps for adaptive scaling.\n * Finer-grained steps (5% increments) for smoother adaptation.\n * Ordered from highest to lowest quality.\n */\nconst SCALE_STEPS = [\n 1.0, 0.95, 0.9, 0.85, 0.8, 0.75, 0.7, 0.65, 0.6, 0.55, 0.5, 0.45, 0.4, 0.35,\n 0.3, 0.25, 0.2, 0.15, 0.1,\n] as const;\ntype ScaleStep = (typeof SCALE_STEPS)[number];\n\n/**\n * Compute Pressure API types (not yet in TypeScript lib)\n */\ntype PressureState = \"nominal\" | \"fair\" | \"serious\" | \"critical\";\n\ninterface PressureRecord {\n state: PressureState;\n time: number;\n}\n\ninterface PressureObserverCallback {\n (records: PressureRecord[]): void;\n}\n\ninterface PressureObserverOptions {\n sampleInterval?: number;\n}\n\ndeclare class PressureObserver {\n constructor(callback: PressureObserverCallback);\n observe(source: \"cpu\", options?: PressureObserverOptions): Promise<void>;\n unobserve(source: \"cpu\"): void;\n disconnect(): void;\n}\n\n/**\n * Timing thresholds\n *\n * Target: 30fps = 33.33ms per frame\n * Tolerate down to 15fps (half target) before scaling down.\n * Scale up when we have plenty of headroom.\n */\nconst TARGET_FRAME_TIME_MS = 33.33; // 30fps target\nconst SCALE_DOWN_THRESHOLD_MS = 66.67; // 15fps (half target) - only scale down if really struggling\nconst SCALE_UP_THRESHOLD_MS = 25; // If avg render time is well below target, consider scaling up\nconst ROLLING_WINDOW_SIZE = 30; // ~1 second of samples at 30fps\nconst MIN_SCALE_CHANGE_INTERVAL_MS = 2000; // Wait 2s between any scale changes\nconst SCALE_UP_STABILITY_SAMPLES = 60; // Need 60 samples (~2s) of good performance to scale up\n\n/** Size of the pressure history for histogram display */\nconst PRESSURE_HISTORY_SIZE = 60;\n\n/**\n * Tracks render time to recommend optimal preview resolution.\n */\nexport class AdaptiveResolutionTracker {\n private renderTimes: number[] = []; // Rolling window of render times (ms)\n private currentScaleIndex = 0; // Index into SCALE_STEPS (0 = highest quality)\n private lastScaleChangeTime = 0;\n private samplesAtCurrentScale = 0; // How many samples we've collected at current scale\n\n // Compute Pressure API\n private pressureObserver: PressureObserver | null = null;\n private pressureState: PressureState = \"nominal\";\n private pressureHistory: PressureState[] = [];\n\n // For display - track frame intervals separately from render times\n private lastFrameTime = 0;\n private frameIntervals: number[] = [];\n\n // Callbacks\n private onScaleChange?: (scale: ScaleStep) => void;\n\n constructor(options?: { onScaleChange?: (scale: ScaleStep) => void }) {\n this.onScaleChange = options?.onScaleChange;\n this.initPressureObserver();\n }\n\n /**\n * Initialize Compute Pressure API observer if available.\n */\n private initPressureObserver(): void {\n if (!(\"PressureObserver\" in globalThis)) {\n return;\n }\n\n try {\n this.pressureObserver = new PressureObserver((records) => {\n if (records.length > 0) {\n const latest = records[records.length - 1]!;\n this.pressureState = latest.state;\n\n this.pressureHistory.push(latest.state);\n if (this.pressureHistory.length > PRESSURE_HISTORY_SIZE) {\n this.pressureHistory.shift();\n }\n }\n });\n\n this.pressureObserver\n .observe(\"cpu\", { sampleInterval: 500 })\n .catch(() => {\n // Ignore errors from observe (e.g., AbortError if disconnect called before observe resolves)\n });\n } catch (e) {\n logger.warn(\n \"[AdaptiveResolutionTracker] Failed to initialize PressureObserver:\",\n e,\n );\n this.pressureObserver = null;\n }\n }\n\n /**\n * Record a frame's render time.\n * Call this AFTER each render completes with how long the render took.\n *\n * @param renderTimeMs - How long the render() call took in milliseconds\n * @param timestamp - Optional rAF timestamp for frame interval tracking (display only)\n */\n recordFrame(renderTimeMs: number, timestamp?: number): void {\n // Track render times for adaptive decisions\n this.renderTimes.push(renderTimeMs);\n if (this.renderTimes.length > ROLLING_WINDOW_SIZE) {\n this.renderTimes.shift();\n }\n this.samplesAtCurrentScale++;\n\n // Track frame intervals for FPS display (separate from render time)\n if (timestamp !== undefined && this.lastFrameTime > 0) {\n const interval = timestamp - this.lastFrameTime;\n this.frameIntervals.push(interval);\n if (this.frameIntervals.length > ROLLING_WINDOW_SIZE) {\n this.frameIntervals.shift();\n }\n }\n if (timestamp !== undefined) {\n this.lastFrameTime = timestamp;\n }\n\n // Check if we should adjust scale\n this.checkForScaleAdjustment();\n }\n\n /**\n * Check if we should scale up or down based on render time trends.\n */\n private checkForScaleAdjustment(): void {\n if (this.renderTimes.length < 10) return; // Need some samples\n\n const now = performance.now();\n if (now - this.lastScaleChangeTime < MIN_SCALE_CHANGE_INTERVAL_MS) {\n return; // Rate limit changes\n }\n\n const avgRenderTime =\n this.renderTimes.reduce((a, b) => a + b, 0) / this.renderTimes.length;\n\n // Scale DOWN if we're consistently slow\n if (avgRenderTime > SCALE_DOWN_THRESHOLD_MS) {\n this.scaleDown(\"slow\");\n return;\n }\n\n // Scale DOWN if CPU pressure is high (proactive)\n if (this.pressureState === \"critical\" || this.pressureState === \"serious\") {\n this.scaleDown(\"pressure\");\n return;\n }\n\n // Scale UP if we have sustained headroom and CPU isn't under pressure\n // (we already returned above if pressure is serious/critical, but check again for clarity)\n const pressureOk =\n this.pressureState === \"nominal\" || this.pressureState === \"fair\";\n if (\n avgRenderTime < SCALE_UP_THRESHOLD_MS &&\n this.samplesAtCurrentScale >= SCALE_UP_STABILITY_SAMPLES &&\n pressureOk\n ) {\n this.scaleUp();\n }\n }\n\n /**\n * Decrease resolution (increase scale index).\n */\n private scaleDown(reason: \"slow\" | \"pressure\"): void {\n if (this.currentScaleIndex < SCALE_STEPS.length - 1) {\n this.currentScaleIndex++;\n this.lastScaleChangeTime = performance.now();\n this.samplesAtCurrentScale = 0;\n this.renderTimes = []; // Clear history at new scale\n\n const newScale = SCALE_STEPS[this.currentScaleIndex]!;\n logger.debug(\n `[AdaptiveResolutionTracker] Scaling DOWN to ${(newScale * 100).toFixed(0)}% (reason: ${reason})`,\n );\n this.onScaleChange?.(newScale);\n }\n }\n\n /**\n * Increase resolution (decrease scale index).\n */\n private scaleUp(): void {\n if (this.currentScaleIndex > 0) {\n this.currentScaleIndex--;\n this.lastScaleChangeTime = performance.now();\n this.samplesAtCurrentScale = 0;\n this.renderTimes = []; // Clear history at new scale\n\n const newScale = SCALE_STEPS[this.currentScaleIndex]!;\n logger.debug(\n `[AdaptiveResolutionTracker] Scaling UP to ${(newScale * 100).toFixed(0)}% (reason: stable performance)`,\n );\n this.onScaleChange?.(newScale);\n }\n }\n\n /**\n * Get the current recommended scale factor.\n */\n getRecommendedScale(): ScaleStep {\n return SCALE_STEPS[this.currentScaleIndex]!;\n }\n\n /**\n * Get current statistics for display.\n */\n getStats(): {\n currentScale: ScaleStep;\n avgRenderTime: number;\n fps: number;\n pressureState: PressureState;\n pressureHistory: PressureState[];\n samplesAtCurrentScale: number;\n canScaleUp: boolean;\n canScaleDown: boolean;\n headroom: number; // How much faster than target we're rendering (negative = behind)\n } {\n const avgRenderTime =\n this.renderTimes.length > 0\n ? this.renderTimes.reduce((a, b) => a + b, 0) / this.renderTimes.length\n : 0;\n\n // FPS based on frame intervals (how often we're called), not render time\n const avgFrameInterval =\n this.frameIntervals.length > 0\n ? this.frameIntervals.reduce((a, b) => a + b, 0) /\n this.frameIntervals.length\n : 16.67;\n const fps = avgFrameInterval > 0 ? 1000 / avgFrameInterval : 0;\n\n const now = performance.now();\n const timeSinceLastChange = now - this.lastScaleChangeTime;\n const canChange = timeSinceLastChange >= MIN_SCALE_CHANGE_INTERVAL_MS;\n\n const pressureOk =\n this.pressureState === \"nominal\" || this.pressureState === \"fair\";\n const canScaleUp =\n canChange &&\n this.currentScaleIndex > 0 &&\n avgRenderTime < SCALE_UP_THRESHOLD_MS &&\n this.samplesAtCurrentScale >= SCALE_UP_STABILITY_SAMPLES &&\n pressureOk;\n\n const canScaleDown =\n canChange && this.currentScaleIndex < SCALE_STEPS.length - 1;\n\n // Headroom: positive = we're faster than needed, negative = we're behind\n const headroom = TARGET_FRAME_TIME_MS - avgRenderTime;\n\n return {\n currentScale: this.getRecommendedScale(),\n avgRenderTime,\n fps,\n pressureState: this.pressureState,\n pressureHistory: [...this.pressureHistory],\n samplesAtCurrentScale: this.samplesAtCurrentScale,\n canScaleUp,\n canScaleDown,\n headroom,\n };\n }\n\n /**\n * Reset the tracker state.\n */\n reset(): void {\n this.lastFrameTime = 0;\n this.frameIntervals = [];\n this.renderTimes = [];\n this.currentScaleIndex = 0;\n this.lastScaleChangeTime = 0;\n this.samplesAtCurrentScale = 0;\n }\n\n /**\n * Initialize the tracker to start at a specific scale.\n */\n initializeAtScale(targetScale: number): void {\n let bestIndex = 0;\n for (let i = 0; i < SCALE_STEPS.length; i++) {\n if (SCALE_STEPS[i]! <= targetScale) {\n bestIndex = i;\n break;\n }\n }\n\n this.currentScaleIndex = bestIndex;\n this.lastFrameTime = 0;\n this.frameIntervals = [];\n this.renderTimes = [];\n this.lastScaleChangeTime = 0;\n this.samplesAtCurrentScale = 0;\n\n logger.debug(\n `[AdaptiveResolutionTracker] Initialized at scale ${(SCALE_STEPS[bestIndex]! * 100).toFixed(0)}%`,\n );\n }\n\n /**\n * Clean up resources.\n */\n dispose(): void {\n if (this.pressureObserver) {\n try {\n this.pressureObserver.disconnect();\n } catch {\n // Ignore cleanup errors\n }\n this.pressureObserver = null;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -82,7 +82,7 @@ declare class FrameController {
|
|
|
82
82
|
currentTimeMs: number;
|
|
83
83
|
});
|
|
84
84
|
/**
|
|
85
|
-
* Cancel any in-progress render operation.
|
|
85
|
+
* Cancel any in-progress render operation and reset deduplication state.
|
|
86
86
|
*/
|
|
87
87
|
abort(): void;
|
|
88
88
|
/**
|
|
@@ -103,21 +103,6 @@ declare class FrameController {
|
|
|
103
103
|
*/
|
|
104
104
|
get isRendering(): boolean;
|
|
105
105
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Interface for the legacy frameTask object.
|
|
108
|
-
* Used for backwards compatibility with code expecting the old Task-like API.
|
|
109
|
-
*/
|
|
110
|
-
interface FrameTask {
|
|
111
|
-
/**
|
|
112
|
-
* Run the frame task (prepare + render).
|
|
113
|
-
* @returns Promise that resolves when the task completes
|
|
114
|
-
*/
|
|
115
|
-
run(): Promise<void>;
|
|
116
|
-
/**
|
|
117
|
-
* Promise that resolves when the current task completes.
|
|
118
|
-
*/
|
|
119
|
-
taskComplete: Promise<void>;
|
|
120
|
-
}
|
|
121
106
|
//#endregion
|
|
122
|
-
export { FrameController, FrameRenderable, FrameState,
|
|
107
|
+
export { FrameController, FrameRenderable, FrameState, RenderFrameOptions };
|
|
123
108
|
//# sourceMappingURL=FrameController.d.ts.map
|
|
@@ -44,15 +44,27 @@ var FrameController = class {
|
|
|
44
44
|
#abortController = null;
|
|
45
45
|
#renderInProgress = false;
|
|
46
46
|
#pendingRenderTime = null;
|
|
47
|
+
#frameCount = 0;
|
|
48
|
+
#totalQueryMs = 0;
|
|
49
|
+
#totalPrepareMs = 0;
|
|
50
|
+
#totalRenderMs = 0;
|
|
51
|
+
#totalAnimsMs = 0;
|
|
52
|
+
/**
|
|
53
|
+
* Last successfully rendered time. Used for deduplication when multiple
|
|
54
|
+
* callers (e.g., PlaybackController RAF loop and canvas render loop)
|
|
55
|
+
* both try to render the same frame within one animation frame.
|
|
56
|
+
*/
|
|
57
|
+
#lastRenderedTimeMs = -1;
|
|
47
58
|
constructor(rootElement) {
|
|
48
59
|
this.#rootElement = rootElement;
|
|
49
60
|
}
|
|
50
61
|
/**
|
|
51
|
-
* Cancel any in-progress render operation.
|
|
62
|
+
* Cancel any in-progress render operation and reset deduplication state.
|
|
52
63
|
*/
|
|
53
64
|
abort() {
|
|
54
65
|
this.#abortController?.abort();
|
|
55
66
|
this.#abortController = null;
|
|
67
|
+
this.#lastRenderedTimeMs = -1;
|
|
56
68
|
}
|
|
57
69
|
/**
|
|
58
70
|
* Render a frame at the specified time.
|
|
@@ -68,6 +80,7 @@ var FrameController = class {
|
|
|
68
80
|
*/
|
|
69
81
|
async renderFrame(timeMs, options = {}) {
|
|
70
82
|
const { waitForLitUpdate = true, onAnimationsUpdate } = options;
|
|
83
|
+
if (timeMs === this.#lastRenderedTimeMs) return;
|
|
71
84
|
if (this.#renderInProgress) {
|
|
72
85
|
this.#pendingRenderTime = timeMs;
|
|
73
86
|
return;
|
|
@@ -81,19 +94,40 @@ var FrameController = class {
|
|
|
81
94
|
await this.#rootElement.updateComplete;
|
|
82
95
|
signal.throwIfAborted();
|
|
83
96
|
}
|
|
97
|
+
const tQuery = performance.now();
|
|
84
98
|
const elements = this.#queryVisibleElements(timeMs);
|
|
99
|
+
const queryMs = performance.now() - tQuery;
|
|
85
100
|
signal.throwIfAborted();
|
|
101
|
+
const tPrepare = performance.now();
|
|
86
102
|
const elementsNeedingPreparation = elements.filter((el) => el.getFrameState(timeMs).needsPreparation);
|
|
87
103
|
if (elementsNeedingPreparation.length > 0) {
|
|
88
104
|
await Promise.all(elementsNeedingPreparation.map((el) => el.prepareFrame(timeMs, signal)));
|
|
89
105
|
signal.throwIfAborted();
|
|
90
106
|
}
|
|
107
|
+
const prepareMs = performance.now() - tPrepare;
|
|
108
|
+
const tRender = performance.now();
|
|
91
109
|
const sortedElements = [...elements].sort((a, b) => a.getFrameState(timeMs).priority - b.getFrameState(timeMs).priority);
|
|
92
110
|
for (const element of sortedElements) {
|
|
93
111
|
signal.throwIfAborted();
|
|
94
112
|
element.renderFrame(timeMs);
|
|
95
113
|
}
|
|
114
|
+
const renderMs = performance.now() - tRender;
|
|
115
|
+
const tAnims = performance.now();
|
|
96
116
|
if (onAnimationsUpdate) onAnimationsUpdate(this.#rootElement);
|
|
117
|
+
const animsMs = performance.now() - tAnims;
|
|
118
|
+
this.#frameCount++;
|
|
119
|
+
this.#totalQueryMs += queryMs;
|
|
120
|
+
this.#totalPrepareMs += prepareMs;
|
|
121
|
+
this.#totalRenderMs += renderMs;
|
|
122
|
+
this.#totalAnimsMs += animsMs;
|
|
123
|
+
if (this.#frameCount % 60 === 0) {
|
|
124
|
+
this.#frameCount = 0;
|
|
125
|
+
this.#totalQueryMs = 0;
|
|
126
|
+
this.#totalPrepareMs = 0;
|
|
127
|
+
this.#totalRenderMs = 0;
|
|
128
|
+
this.#totalAnimsMs = 0;
|
|
129
|
+
}
|
|
130
|
+
this.#lastRenderedTimeMs = timeMs;
|
|
97
131
|
} finally {
|
|
98
132
|
this.#renderInProgress = false;
|
|
99
133
|
if (this.#pendingRenderTime !== null) {
|
|
@@ -106,12 +140,12 @@ var FrameController = class {
|
|
|
106
140
|
/**
|
|
107
141
|
* Query all visible FrameRenderable elements in the tree.
|
|
108
142
|
* Uses temporal visibility to filter out elements not visible at current time.
|
|
109
|
-
*
|
|
143
|
+
*
|
|
110
144
|
* IMPORTANT: For temporal elements, we use temporal visibility (startTimeMs/endTimeMs)
|
|
111
145
|
* instead of CSS visibility. This is because updateAnimations sets display:none on
|
|
112
146
|
* elements outside their time range, but that CSS state is from the PREVIOUS frame.
|
|
113
147
|
* When seeking, we need to evaluate visibility based on the NEW time, not stale CSS.
|
|
114
|
-
*
|
|
148
|
+
*
|
|
115
149
|
* @param timeMs - The time to use for visibility checks. This should be the target
|
|
116
150
|
* render time, not read from root element (which may be stale).
|
|
117
151
|
*/
|
|
@@ -125,11 +159,7 @@ var FrameController = class {
|
|
|
125
159
|
if (!(currentTimeMs >= startMs && currentTimeMs < endMs)) return;
|
|
126
160
|
if (isFrameRenderable(element)) result.push(element);
|
|
127
161
|
} else {
|
|
128
|
-
if (element instanceof HTMLElement)
|
|
129
|
-
if (element.style.display === "none") return;
|
|
130
|
-
const style = getComputedStyle(element);
|
|
131
|
-
if (style.display === "none" || style.visibility === "hidden") return;
|
|
132
|
-
}
|
|
162
|
+
if (element instanceof HTMLElement && element.style.display === "none") return;
|
|
133
163
|
if (isFrameRenderable(element)) result.push(element);
|
|
134
164
|
}
|
|
135
165
|
const children = this.#getChildrenIncludingSlotted(element);
|
|
@@ -153,7 +183,7 @@ var FrameController = class {
|
|
|
153
183
|
return assignedElements;
|
|
154
184
|
}
|
|
155
185
|
}
|
|
156
|
-
return
|
|
186
|
+
return element.children;
|
|
157
187
|
}
|
|
158
188
|
/**
|
|
159
189
|
* Check if a render is currently in progress.
|
|
@@ -162,60 +192,7 @@ var FrameController = class {
|
|
|
162
192
|
return this.#renderInProgress;
|
|
163
193
|
}
|
|
164
194
|
};
|
|
165
|
-
/**
|
|
166
|
-
* Create a backwards-compatible frameTask wrapper for a FrameRenderable element.
|
|
167
|
-
*
|
|
168
|
-
* This factory function creates a frameTask object that:
|
|
169
|
-
* - Manages its own AbortController for cancellation
|
|
170
|
-
* - Calls prepareFrame() then renderFrame() in sequence
|
|
171
|
-
* - Silently ignores AbortErrors (expected during cancellation)
|
|
172
|
-
* - Provides taskComplete promise for awaiting completion
|
|
173
|
-
*
|
|
174
|
-
* @param element - The element implementing FrameRenderable
|
|
175
|
-
* @param options - Optional configuration
|
|
176
|
-
* @returns A frameTask object compatible with legacy code
|
|
177
|
-
*
|
|
178
|
-
* @example
|
|
179
|
-
* ```typescript
|
|
180
|
-
* class MyElement extends LitElement implements FrameRenderable {
|
|
181
|
-
* frameTask = createFrameTaskWrapper(this, {
|
|
182
|
-
* getTimeMs: () => this.currentSourceTimeMs,
|
|
183
|
-
* });
|
|
184
|
-
*
|
|
185
|
-
* getFrameState(timeMs: number): FrameState { ... }
|
|
186
|
-
* async prepareFrame(timeMs: number, signal: AbortSignal): Promise<void> { ... }
|
|
187
|
-
* renderFrame(timeMs: number): void { ... }
|
|
188
|
-
* }
|
|
189
|
-
* ```
|
|
190
|
-
*/
|
|
191
|
-
function createFrameTaskWrapper(element, options = {}) {
|
|
192
|
-
let frameTaskPromise = Promise.resolve();
|
|
193
|
-
const getTimeMs = options.getTimeMs ?? (() => {
|
|
194
|
-
if ("desiredSeekTimeMs" in element && typeof element.desiredSeekTimeMs === "number") return element.desiredSeekTimeMs;
|
|
195
|
-
if ("ownCurrentTimeMs" in element && typeof element.ownCurrentTimeMs === "number") return element.ownCurrentTimeMs;
|
|
196
|
-
return 0;
|
|
197
|
-
});
|
|
198
|
-
const taskObj = {
|
|
199
|
-
run: () => {
|
|
200
|
-
const abortController = new AbortController();
|
|
201
|
-
const timeMs = getTimeMs();
|
|
202
|
-
frameTaskPromise = (async () => {
|
|
203
|
-
try {
|
|
204
|
-
await element.prepareFrame(timeMs, abortController.signal);
|
|
205
|
-
element.renderFrame(timeMs);
|
|
206
|
-
} catch (error) {
|
|
207
|
-
if (error instanceof DOMException && error.name === "AbortError") return;
|
|
208
|
-
throw error;
|
|
209
|
-
}
|
|
210
|
-
})();
|
|
211
|
-
taskObj.taskComplete = frameTaskPromise;
|
|
212
|
-
return frameTaskPromise;
|
|
213
|
-
},
|
|
214
|
-
taskComplete: Promise.resolve()
|
|
215
|
-
};
|
|
216
|
-
return taskObj;
|
|
217
|
-
}
|
|
218
195
|
|
|
219
196
|
//#endregion
|
|
220
|
-
export { FrameController, PRIORITY_AUDIO, PRIORITY_CAPTIONS, PRIORITY_DEFAULT, PRIORITY_IMAGE, PRIORITY_VIDEO, PRIORITY_WAVEFORM
|
|
197
|
+
export { FrameController, PRIORITY_AUDIO, PRIORITY_CAPTIONS, PRIORITY_DEFAULT, PRIORITY_IMAGE, PRIORITY_VIDEO, PRIORITY_WAVEFORM };
|
|
221
198
|
//# sourceMappingURL=FrameController.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FrameController.js","names":["#rootElement","#abortController","#renderInProgress","#pendingRenderTime","#queryVisibleElements","result: FrameRenderable[]","#getChildrenIncludingSlotted","assignedElements: Element[]","frameTaskPromise: Promise<void>","taskObj: FrameTask"],"sources":["../../src/preview/FrameController.ts"],"sourcesContent":["/**\n * FrameController: Centralized frame rendering control\n *\n * Replaces the distributed Lit Task hierarchy with a single control loop\n * that queries elements and coordinates rendering directly.\n *\n * Benefits over the previous Task-based system:\n * - Single abort controller instead of distributed abort handling\n * - Clear prepare → render phases\n * - All coordination visible in one place\n * - No Lit Task reactivity overhead\n */\n\nimport type { LitElement } from \"lit\";\n\n// ============================================================================\n// Priority Constants\n// ============================================================================\n// Lower numbers render first. Elements with dependencies should have higher\n// priority numbers than their dependencies.\n//\n// Example: Waveform depends on audio analysis data, so it renders after audio.\n// ============================================================================\n\n/**\n * Priority for video elements.\n * Video renders first as other elements may depend on video frames being ready.\n */\nexport const PRIORITY_VIDEO = 1;\n\n/**\n * Priority for captions elements.\n * Captions render after video so they can overlay correctly.\n */\nexport const PRIORITY_CAPTIONS = 2;\n\n/**\n * Priority for audio elements.\n * Audio renders after captions (no visual dependency, but keeps consistent ordering).\n */\nexport const PRIORITY_AUDIO = 3;\n\n/**\n * Priority for waveform elements.\n * Waveform renders after audio because it depends on audio analysis data.\n */\nexport const PRIORITY_WAVEFORM = 4;\n\n/**\n * Priority for image elements.\n * Images render with low priority as they're typically static.\n */\nexport const PRIORITY_IMAGE = 5;\n\n/**\n * Default priority for elements that don't specify one.\n * High number ensures custom elements render after standard elements.\n */\nexport const PRIORITY_DEFAULT = 100;\n\n/**\n * State returned by elements describing their readiness for a given time.\n */\nexport interface FrameState {\n /**\n * Whether async preparation is needed before rendering.\n * Examples: video needs to seek, captions need to load data.\n */\n needsPreparation: boolean;\n\n /**\n * Whether the element is ready to render synchronously.\n * True when all async work is complete and renderFrame() can be called.\n */\n isReady: boolean;\n\n /**\n * Rendering priority hint. Lower numbers render first.\n * Used to order render calls for elements with dependencies.\n * \n * Standard priorities:\n * - PRIORITY_VIDEO (1): Video elements\n * - PRIORITY_CAPTIONS (2): Caption overlays\n * - PRIORITY_AUDIO (3): Audio elements\n * - PRIORITY_WAVEFORM (4): Audio visualizers (depend on audio)\n * - PRIORITY_IMAGE (5): Static images\n * - PRIORITY_DEFAULT (100): Fallback for custom elements\n */\n priority: number;\n}\n\n/**\n * Interface that elements implement to participate in centralized frame rendering.\n * Elements keep their rendering logic local but expose a standardized interface.\n */\nexport interface FrameRenderable {\n /**\n * Query the element's readiness state for a given time.\n * Must be synchronous and cheap to call.\n */\n getFrameState(timeMs: number): FrameState;\n\n /**\n * Async preparation phase. Called when getFrameState().needsPreparation is true.\n * Performs any async work needed before rendering (seeking, loading, etc.).\n *\n * @param timeMs - The time to prepare for\n * @param signal - Abort signal for cancellation\n */\n prepareFrame(timeMs: number, signal: AbortSignal): Promise<void>;\n\n /**\n * Synchronous render phase. Called after all preparation is complete.\n * Performs the actual rendering (paint to canvas, update DOM, etc.).\n *\n * @param timeMs - The time to render\n */\n renderFrame(timeMs: number): void;\n}\n\n/**\n * Type guard to check if an element implements FrameRenderable.\n */\nexport function isFrameRenderable(element: unknown): element is FrameRenderable {\n return (\n typeof element === \"object\" &&\n element !== null &&\n \"getFrameState\" in element &&\n \"prepareFrame\" in element &&\n \"renderFrame\" in element &&\n typeof (element as FrameRenderable).getFrameState === \"function\" &&\n typeof (element as FrameRenderable).prepareFrame === \"function\" &&\n typeof (element as FrameRenderable).renderFrame === \"function\"\n );\n}\n\n/**\n * Options for FrameController.renderFrame()\n */\nexport interface RenderFrameOptions {\n /**\n * Whether to wait for Lit updateComplete before querying elements.\n * Default: true\n */\n waitForLitUpdate?: boolean;\n\n /**\n * Callback to update CSS animations after frame rendering completes.\n * Called with the root element after all elements have rendered.\n * This centralizes animation synchronization in one place.\n */\n onAnimationsUpdate?: (rootElement: Element) => void;\n}\n\n/**\n * Central controller for frame rendering.\n * Lives at the root timegroup and orchestrates all element rendering.\n */\nexport class FrameController {\n #rootElement: LitElement & { currentTimeMs: number };\n #abortController: AbortController | null = null;\n #renderInProgress = false;\n #pendingRenderTime: number | null = null;\n\n constructor(rootElement: LitElement & { currentTimeMs: number }) {\n this.#rootElement = rootElement;\n }\n\n /**\n * Cancel any in-progress render operation.\n */\n abort(): void {\n this.#abortController?.abort();\n this.#abortController = null;\n }\n\n /**\n * Render a frame at the specified time.\n *\n * This is the main entry point for frame rendering. It:\n * 1. Cancels any previous in-progress render\n * 2. Queries all visible FrameRenderable elements\n * 3. Runs preparation in parallel for elements that need it\n * 4. Runs render in priority order\n *\n * @param timeMs - The time in milliseconds to render\n * @param options - Optional configuration\n */\n async renderFrame(\n timeMs: number,\n options: RenderFrameOptions = {}\n ): Promise<void> {\n const { waitForLitUpdate = true, onAnimationsUpdate } = options;\n\n // If a render is in progress, queue this one\n if (this.#renderInProgress) {\n this.#pendingRenderTime = timeMs;\n return;\n }\n\n // Cancel any previous render operation\n this.#abortController?.abort();\n this.#abortController = new AbortController();\n const signal = this.#abortController.signal;\n\n this.#renderInProgress = true;\n\n try {\n // Wait for Lit to propagate time changes to children\n if (waitForLitUpdate) {\n await this.#rootElement.updateComplete;\n signal.throwIfAborted();\n }\n\n // Query all visible elements that implement FrameRenderable\n // Pass the timeMs parameter to use for visibility checks (root element's time may be stale)\n const elements = this.#queryVisibleElements(timeMs);\n signal.throwIfAborted();\n\n // Phase 1: Parallel preparation\n const elementsNeedingPreparation = elements.filter(\n (el) => el.getFrameState(timeMs).needsPreparation\n );\n\n if (elementsNeedingPreparation.length > 0) {\n await Promise.all(\n elementsNeedingPreparation.map((el) => el.prepareFrame(timeMs, signal))\n );\n signal.throwIfAborted();\n }\n\n // Phase 2: Sequential render by priority\n const sortedElements = [...elements].sort(\n (a, b) => a.getFrameState(timeMs).priority - b.getFrameState(timeMs).priority\n );\n\n for (const element of sortedElements) {\n signal.throwIfAborted();\n element.renderFrame(timeMs);\n }\n\n // Phase 3: Update CSS animations (centralized)\n if (onAnimationsUpdate) {\n onAnimationsUpdate(this.#rootElement);\n }\n } finally {\n this.#renderInProgress = false;\n\n // Process any queued render\n if (this.#pendingRenderTime !== null) {\n const pendingTime = this.#pendingRenderTime;\n this.#pendingRenderTime = null;\n // Don't await - fire and forget to avoid recursive waiting\n this.renderFrame(pendingTime, options).catch(() => {\n // Silently ignore errors from queued renders (likely aborted)\n });\n }\n }\n }\n\n /**\n * Query all visible FrameRenderable elements in the tree.\n * Uses temporal visibility to filter out elements not visible at current time.\n * \n * IMPORTANT: For temporal elements, we use temporal visibility (startTimeMs/endTimeMs)\n * instead of CSS visibility. This is because updateAnimations sets display:none on\n * elements outside their time range, but that CSS state is from the PREVIOUS frame.\n * When seeking, we need to evaluate visibility based on the NEW time, not stale CSS.\n * \n * @param timeMs - The time to use for visibility checks. This should be the target\n * render time, not read from root element (which may be stale).\n */\n #queryVisibleElements(timeMs: number): FrameRenderable[] {\n const result: FrameRenderable[] = [];\n const currentTimeMs = timeMs;\n\n const walk = (element: Element): void => {\n // For temporal elements (ef-timegroup, ef-video, etc.), use temporal visibility\n // instead of CSS visibility. CSS display:none may be stale from previous frame.\n const isTemporal = \"startTimeMs\" in element && \"endTimeMs\" in element;\n \n if (isTemporal) {\n // Temporal element: check time-based visibility\n // Use exclusive end (< not <=) to avoid overlap at boundaries\n const startMs = (element as { startTimeMs?: number }).startTimeMs ?? -Infinity;\n const endMs = (element as { endTimeMs?: number }).endTimeMs ?? Infinity;\n const isTemporallyVisible = currentTimeMs >= startMs && currentTimeMs < endMs;\n \n if (!isTemporallyVisible) {\n // Skip this element AND its children (children's times are relative to parent)\n return;\n }\n \n // Element is temporally visible - include if it implements FrameRenderable\n if (isFrameRenderable(element)) {\n result.push(element);\n }\n } else {\n // Non-temporal element: use CSS visibility\n if (element instanceof HTMLElement) {\n // Fast path: check inline display style\n if (element.style.display === \"none\") {\n return;\n }\n // Slow path: check computed style\n const style = getComputedStyle(element);\n if (style.display === \"none\" || style.visibility === \"hidden\") {\n return;\n }\n }\n\n // Check if this element implements FrameRenderable\n if (isFrameRenderable(element)) {\n result.push(element);\n }\n }\n\n // Walk children - handle both regular children and slotted content\n const children = this.#getChildrenIncludingSlotted(element);\n for (const child of children) {\n walk(child);\n }\n };\n\n walk(this.#rootElement);\n return result;\n }\n\n /**\n * Gets all child elements including slotted content for shadow DOM elements.\n * For elements with shadow DOM that contain slots, this returns the assigned\n * elements (slotted content) instead of just the shadow DOM children.\n */\n #getChildrenIncludingSlotted(element: Element): Element[] {\n // If element has shadowRoot with slots, get assigned elements\n if (element.shadowRoot) {\n const slots = element.shadowRoot.querySelectorAll('slot');\n if (slots.length > 0) {\n const assignedElements: Element[] = [];\n for (const slot of slots) {\n assignedElements.push(...slot.assignedElements());\n }\n // Also include shadow DOM children that aren't slots (for mixed content)\n for (const child of element.shadowRoot.children) {\n if (child.tagName !== 'SLOT') {\n assignedElements.push(child);\n }\n }\n return assignedElements;\n }\n }\n \n // Fallback to regular children\n return Array.from(element.children);\n }\n\n /**\n * Check if a render is currently in progress.\n */\n get isRendering(): boolean {\n return this.#renderInProgress;\n }\n}\n\n/**\n * Default frame state for elements that don't need special handling.\n * Use this for simple elements that are always ready.\n */\nexport const DEFAULT_FRAME_STATE: FrameState = {\n needsPreparation: false,\n isReady: true,\n priority: PRIORITY_DEFAULT,\n};\n\n/**\n * Helper to create a FrameRenderable mixin for elements.\n * Provides default implementations that can be overridden.\n */\nexport function createFrameRenderableMixin<T extends { new (...args: any[]): HTMLElement }>(\n Base: T\n) {\n return class FrameRenderableMixin extends Base implements FrameRenderable {\n getFrameState(_timeMs: number): FrameState {\n return DEFAULT_FRAME_STATE;\n }\n\n async prepareFrame(_timeMs: number, _signal: AbortSignal): Promise<void> {\n // Default: no preparation needed\n }\n\n renderFrame(_timeMs: number): void {\n // Default: no explicit render needed\n }\n };\n}\n\n// ============================================================================\n// Shared Frame Task Wrapper\n// ============================================================================\n// Creates a backwards-compatible frameTask object from a FrameRenderable element.\n// This eliminates duplicate boilerplate code across all temporal elements.\n// ============================================================================\n\n/**\n * Interface for the legacy frameTask object.\n * Used for backwards compatibility with code expecting the old Task-like API.\n */\nexport interface FrameTask {\n /**\n * Run the frame task (prepare + render).\n * @returns Promise that resolves when the task completes\n */\n run(): Promise<void>;\n\n /**\n * Promise that resolves when the current task completes.\n */\n taskComplete: Promise<void>;\n}\n\n/**\n * Options for creating a frame task wrapper.\n */\nexport interface FrameTaskWrapperOptions {\n /**\n * Function to get the current time in milliseconds.\n * Default uses element's ownCurrentTimeMs if available, otherwise 0.\n */\n getTimeMs?: () => number;\n}\n\n/**\n * Create a backwards-compatible frameTask wrapper for a FrameRenderable element.\n * \n * This factory function creates a frameTask object that:\n * - Manages its own AbortController for cancellation\n * - Calls prepareFrame() then renderFrame() in sequence\n * - Silently ignores AbortErrors (expected during cancellation)\n * - Provides taskComplete promise for awaiting completion\n * \n * @param element - The element implementing FrameRenderable\n * @param options - Optional configuration\n * @returns A frameTask object compatible with legacy code\n * \n * @example\n * ```typescript\n * class MyElement extends LitElement implements FrameRenderable {\n * frameTask = createFrameTaskWrapper(this, {\n * getTimeMs: () => this.currentSourceTimeMs,\n * });\n * \n * getFrameState(timeMs: number): FrameState { ... }\n * async prepareFrame(timeMs: number, signal: AbortSignal): Promise<void> { ... }\n * renderFrame(timeMs: number): void { ... }\n * }\n * ```\n */\nexport function createFrameTaskWrapper(\n element: FrameRenderable & { ownCurrentTimeMs?: number; desiredSeekTimeMs?: number },\n options: FrameTaskWrapperOptions = {}\n): FrameTask {\n let frameTaskPromise: Promise<void> = Promise.resolve();\n\n const getTimeMs = options.getTimeMs ?? (() => {\n // Try desiredSeekTimeMs first (video), then ownCurrentTimeMs, then 0\n if (\"desiredSeekTimeMs\" in element && typeof element.desiredSeekTimeMs === \"number\") {\n return element.desiredSeekTimeMs;\n }\n if (\"ownCurrentTimeMs\" in element && typeof element.ownCurrentTimeMs === \"number\") {\n return element.ownCurrentTimeMs;\n }\n return 0;\n });\n\n const taskObj: FrameTask = {\n run: () => {\n const abortController = new AbortController();\n const timeMs = getTimeMs();\n \n frameTaskPromise = (async () => {\n try {\n await element.prepareFrame(timeMs, abortController.signal);\n element.renderFrame(timeMs);\n } catch (error) {\n // Silently ignore AbortErrors - expected when task is cancelled\n if (error instanceof DOMException && error.name === \"AbortError\") {\n return;\n }\n throw error;\n }\n })();\n \n taskObj.taskComplete = frameTaskPromise;\n return frameTaskPromise;\n },\n taskComplete: Promise.resolve(),\n };\n return taskObj;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FrameController.js","names":["#rootElement","#abortController","#lastRenderedTimeMs","#renderInProgress","#pendingRenderTime","#queryVisibleElements","#frameCount","#totalQueryMs","#totalPrepareMs","#totalRenderMs","#totalAnimsMs","result: FrameRenderable[]","#getChildrenIncludingSlotted","assignedElements: Element[]"],"sources":["../../src/preview/FrameController.ts"],"sourcesContent":["/**\n * FrameController: Centralized frame rendering control\n *\n * Replaces the distributed Lit Task hierarchy with a single control loop\n * that queries elements and coordinates rendering directly.\n *\n * Benefits over the previous Task-based system:\n * - Single abort controller instead of distributed abort handling\n * - Clear prepare → render phases\n * - All coordination visible in one place\n * - No Lit Task reactivity overhead\n */\n\nimport type { LitElement } from \"lit\";\n\n// ============================================================================\n// Priority Constants\n// ============================================================================\n// Lower numbers render first. Elements with dependencies should have higher\n// priority numbers than their dependencies.\n//\n// Example: Waveform depends on audio analysis data, so it renders after audio.\n// ============================================================================\n\n/**\n * Priority for video elements.\n * Video renders first as other elements may depend on video frames being ready.\n */\nexport const PRIORITY_VIDEO = 1;\n\n/**\n * Priority for captions elements.\n * Captions render after video so they can overlay correctly.\n */\nexport const PRIORITY_CAPTIONS = 2;\n\n/**\n * Priority for audio elements.\n * Audio renders after captions (no visual dependency, but keeps consistent ordering).\n */\nexport const PRIORITY_AUDIO = 3;\n\n/**\n * Priority for waveform elements.\n * Waveform renders after audio because it depends on audio analysis data.\n */\nexport const PRIORITY_WAVEFORM = 4;\n\n/**\n * Priority for image elements.\n * Images render with low priority as they're typically static.\n */\nexport const PRIORITY_IMAGE = 5;\n\n/**\n * Default priority for elements that don't specify one.\n * High number ensures custom elements render after standard elements.\n */\nexport const PRIORITY_DEFAULT = 100;\n\n/**\n * State returned by elements describing their readiness for a given time.\n */\nexport interface FrameState {\n /**\n * Whether async preparation is needed before rendering.\n * Examples: video needs to seek, captions need to load data.\n */\n needsPreparation: boolean;\n\n /**\n * Whether the element is ready to render synchronously.\n * True when all async work is complete and renderFrame() can be called.\n */\n isReady: boolean;\n\n /**\n * Rendering priority hint. Lower numbers render first.\n * Used to order render calls for elements with dependencies.\n *\n * Standard priorities:\n * - PRIORITY_VIDEO (1): Video elements\n * - PRIORITY_CAPTIONS (2): Caption overlays\n * - PRIORITY_AUDIO (3): Audio elements\n * - PRIORITY_WAVEFORM (4): Audio visualizers (depend on audio)\n * - PRIORITY_IMAGE (5): Static images\n * - PRIORITY_DEFAULT (100): Fallback for custom elements\n */\n priority: number;\n}\n\n/**\n * Interface that elements implement to participate in centralized frame rendering.\n * Elements keep their rendering logic local but expose a standardized interface.\n */\nexport interface FrameRenderable {\n /**\n * Query the element's readiness state for a given time.\n * Must be synchronous and cheap to call.\n */\n getFrameState(timeMs: number): FrameState;\n\n /**\n * Async preparation phase. Called when getFrameState().needsPreparation is true.\n * Performs any async work needed before rendering (seeking, loading, etc.).\n *\n * @param timeMs - The time to prepare for\n * @param signal - Abort signal for cancellation\n */\n prepareFrame(timeMs: number, signal: AbortSignal): Promise<void>;\n\n /**\n * Synchronous render phase. Called after all preparation is complete.\n * Performs the actual rendering (paint to canvas, update DOM, etc.).\n *\n * @param timeMs - The time to render\n */\n renderFrame(timeMs: number): void;\n}\n\n/**\n * Type guard to check if an element implements FrameRenderable.\n */\nexport function isFrameRenderable(\n element: unknown,\n): element is FrameRenderable {\n return (\n typeof element === \"object\" &&\n element !== null &&\n \"getFrameState\" in element &&\n \"prepareFrame\" in element &&\n \"renderFrame\" in element &&\n typeof (element as FrameRenderable).getFrameState === \"function\" &&\n typeof (element as FrameRenderable).prepareFrame === \"function\" &&\n typeof (element as FrameRenderable).renderFrame === \"function\"\n );\n}\n\n/**\n * Options for FrameController.renderFrame()\n */\nexport interface RenderFrameOptions {\n /**\n * Whether to wait for Lit updateComplete before querying elements.\n * Default: true\n */\n waitForLitUpdate?: boolean;\n\n /**\n * Callback to update CSS animations after frame rendering completes.\n * Called with the root element after all elements have rendered.\n * This centralizes animation synchronization in one place.\n */\n onAnimationsUpdate?: (rootElement: Element) => void;\n}\n\n/**\n * Central controller for frame rendering.\n * Lives at the root timegroup and orchestrates all element rendering.\n */\nexport class FrameController {\n #rootElement: LitElement & { currentTimeMs: number };\n #abortController: AbortController | null = null;\n #renderInProgress = false;\n #pendingRenderTime: number | null = null;\n #frameCount = 0;\n #totalQueryMs = 0;\n #totalPrepareMs = 0;\n #totalRenderMs = 0;\n #totalAnimsMs = 0;\n\n /**\n * Last successfully rendered time. Used for deduplication when multiple\n * callers (e.g., PlaybackController RAF loop and canvas render loop)\n * both try to render the same frame within one animation frame.\n */\n #lastRenderedTimeMs: number = -1;\n\n constructor(rootElement: LitElement & { currentTimeMs: number }) {\n this.#rootElement = rootElement;\n }\n\n /**\n * Cancel any in-progress render operation and reset deduplication state.\n */\n abort(): void {\n this.#abortController?.abort();\n this.#abortController = null;\n // Reset deduplication state so next render goes through even if same time\n this.#lastRenderedTimeMs = -1;\n }\n\n /**\n * Render a frame at the specified time.\n *\n * This is the main entry point for frame rendering. It:\n * 1. Cancels any previous in-progress render\n * 2. Queries all visible FrameRenderable elements\n * 3. Runs preparation in parallel for elements that need it\n * 4. Runs render in priority order\n *\n * @param timeMs - The time in milliseconds to render\n * @param options - Optional configuration\n */\n async renderFrame(\n timeMs: number,\n options: RenderFrameOptions = {},\n ): Promise<void> {\n const { waitForLitUpdate = true, onAnimationsUpdate } = options;\n\n // Deduplicate: skip if we just rendered this exact time.\n // This prevents double-rendering when multiple RAF loops (e.g., PlaybackController\n // and canvas render loop) both call renderFrame() for the same frame.\n if (timeMs === this.#lastRenderedTimeMs) {\n return;\n }\n\n // If a render is in progress, queue this one\n if (this.#renderInProgress) {\n this.#pendingRenderTime = timeMs;\n return;\n }\n\n // Cancel any previous render operation\n this.#abortController?.abort();\n this.#abortController = new AbortController();\n const signal = this.#abortController.signal;\n\n this.#renderInProgress = true;\n\n try {\n if (waitForLitUpdate) {\n await this.#rootElement.updateComplete;\n signal.throwIfAborted();\n }\n\n const tQuery = performance.now();\n const elements = this.#queryVisibleElements(timeMs);\n const queryMs = performance.now() - tQuery;\n signal.throwIfAborted();\n\n const tPrepare = performance.now();\n const elementsNeedingPreparation = elements.filter(\n (el) => el.getFrameState(timeMs).needsPreparation,\n );\n\n if (elementsNeedingPreparation.length > 0) {\n await Promise.all(\n elementsNeedingPreparation.map((el) =>\n el.prepareFrame(timeMs, signal),\n ),\n );\n signal.throwIfAborted();\n }\n const prepareMs = performance.now() - tPrepare;\n\n const tRender = performance.now();\n const sortedElements = [...elements].sort(\n (a, b) =>\n a.getFrameState(timeMs).priority - b.getFrameState(timeMs).priority,\n );\n\n for (const element of sortedElements) {\n signal.throwIfAborted();\n element.renderFrame(timeMs);\n }\n const renderMs = performance.now() - tRender;\n\n const tAnims = performance.now();\n if (onAnimationsUpdate) {\n onAnimationsUpdate(this.#rootElement);\n }\n const animsMs = performance.now() - tAnims;\n\n this.#frameCount++;\n this.#totalQueryMs += queryMs;\n this.#totalPrepareMs += prepareMs;\n this.#totalRenderMs += renderMs;\n this.#totalAnimsMs += animsMs;\n\n if (this.#frameCount % 60 === 0) {\n this.#frameCount = 0;\n this.#totalQueryMs = 0;\n this.#totalPrepareMs = 0;\n this.#totalRenderMs = 0;\n this.#totalAnimsMs = 0;\n }\n\n this.#lastRenderedTimeMs = timeMs;\n } finally {\n this.#renderInProgress = false;\n\n // Process any queued render\n if (this.#pendingRenderTime !== null) {\n const pendingTime = this.#pendingRenderTime;\n this.#pendingRenderTime = null;\n // Don't await - fire and forget to avoid recursive waiting\n this.renderFrame(pendingTime, options).catch(() => {\n // Silently ignore errors from queued renders (likely aborted)\n });\n }\n }\n }\n\n /**\n * Query all visible FrameRenderable elements in the tree.\n * Uses temporal visibility to filter out elements not visible at current time.\n *\n * IMPORTANT: For temporal elements, we use temporal visibility (startTimeMs/endTimeMs)\n * instead of CSS visibility. This is because updateAnimations sets display:none on\n * elements outside their time range, but that CSS state is from the PREVIOUS frame.\n * When seeking, we need to evaluate visibility based on the NEW time, not stale CSS.\n *\n * @param timeMs - The time to use for visibility checks. This should be the target\n * render time, not read from root element (which may be stale).\n */\n #queryVisibleElements(timeMs: number): FrameRenderable[] {\n const result: FrameRenderable[] = [];\n const currentTimeMs = timeMs;\n\n const walk = (element: Element): void => {\n // For temporal elements (ef-timegroup, ef-video, etc.), use temporal visibility\n // instead of CSS visibility. CSS display:none may be stale from previous frame.\n const isTemporal = \"startTimeMs\" in element && \"endTimeMs\" in element;\n\n if (isTemporal) {\n // Temporal element: check time-based visibility\n // Use exclusive end (< not <=) to avoid overlap at boundaries\n const startMs =\n (element as { startTimeMs?: number }).startTimeMs ?? -Infinity;\n const endMs = (element as { endTimeMs?: number }).endTimeMs ?? Infinity;\n const isTemporallyVisible =\n currentTimeMs >= startMs && currentTimeMs < endMs;\n\n if (!isTemporallyVisible) {\n // Skip this element AND its children (children's times are relative to parent)\n return;\n }\n\n // Element is temporally visible - include if it implements FrameRenderable\n if (isFrameRenderable(element)) {\n result.push(element);\n }\n } else {\n // Non-temporal element: only check inline display style (fast path).\n // Skip getComputedStyle — it forces synchronous style recalc and is\n // unnecessary because FrameRenderable elements are always temporal.\n // We only walk non-temporal elements to reach temporal children.\n if (\n element instanceof HTMLElement &&\n element.style.display === \"none\"\n ) {\n return;\n }\n\n if (isFrameRenderable(element)) {\n result.push(element);\n }\n }\n\n // Walk children - handle both regular children and slotted content\n const children = this.#getChildrenIncludingSlotted(element);\n for (const child of children) {\n walk(child);\n }\n };\n\n walk(this.#rootElement);\n return result;\n }\n\n /**\n * Gets all child elements including slotted content for shadow DOM elements.\n * For elements with shadow DOM that contain slots, this returns the assigned\n * elements (slotted content) instead of just the shadow DOM children.\n */\n #getChildrenIncludingSlotted(element: Element): Iterable<Element> {\n // If element has shadowRoot with slots, get assigned elements\n if (element.shadowRoot) {\n const slots = element.shadowRoot.querySelectorAll(\"slot\");\n if (slots.length > 0) {\n const assignedElements: Element[] = [];\n for (const slot of slots) {\n assignedElements.push(...slot.assignedElements());\n }\n // Also include shadow DOM children that aren't slots (for mixed content)\n for (const child of element.shadowRoot.children) {\n if (child.tagName !== \"SLOT\") {\n assignedElements.push(child);\n }\n }\n return assignedElements;\n }\n }\n\n // Return HTMLCollection directly (iterable, no allocation)\n return element.children;\n }\n\n /**\n * Check if a render is currently in progress.\n */\n get isRendering(): boolean {\n return this.#renderInProgress;\n }\n}\n\n/**\n * Default frame state for elements that don't need special handling.\n * Use this for simple elements that are always ready.\n */\nexport const DEFAULT_FRAME_STATE: FrameState = {\n needsPreparation: false,\n isReady: true,\n priority: PRIORITY_DEFAULT,\n};\n\n/**\n * Helper to create a FrameRenderable mixin for elements.\n * Provides default implementations that can be overridden.\n */\nexport function createFrameRenderableMixin<\n T extends { new (...args: any[]): HTMLElement },\n>(Base: T) {\n return class FrameRenderableMixin extends Base implements FrameRenderable {\n getFrameState(_timeMs: number): FrameState {\n return DEFAULT_FRAME_STATE;\n }\n\n async prepareFrame(_timeMs: number, _signal: AbortSignal): Promise<void> {\n // Default: no preparation needed\n }\n\n renderFrame(_timeMs: number): void {\n // Default: no explicit render needed\n }\n };\n}\n"],"mappings":"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"}
|