@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":"EFScrubber.js","names":["EFScrubber"],"sources":["../../src/gui/EFScrubber.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport {\n customElement,\n eventOptions,\n property,\n state,\n} from \"lit/decorators.js\";\n\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport type { ControllableInterface } from \"./Controllable.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { efContext } from \"./efContext.js\";\nimport { playingContext } from \"./playingContext.js\";\nimport { TargetOrContextMixin } from \"./TargetOrContextMixin.js\";\nimport { quantizeToFrameTimeMs } from \"./EFTimelineRuler.js\";\n\nconst BASE_PIXELS_PER_SECOND = 100;\n\nfunction timeToPixels(\n timeMs: number,\n durationMs: number,\n containerWidth: number,\n zoomScale: number,\n): number {\n if (durationMs <= 0) return 0;\n const pixelsPerSecond = BASE_PIXELS_PER_SECOND * zoomScale;\n return (timeMs / 1000) * pixelsPerSecond;\n}\n\nfunction pixelsToTime(\n pixels: number,\n durationMs: number,\n containerWidth: number,\n zoomScale: number,\n): number {\n if (durationMs <= 0) return 0;\n const pixelsPerSecond = BASE_PIXELS_PER_SECOND * zoomScale;\n return (pixels / pixelsPerSecond) * 1000;\n}\n\n@customElement(\"ef-scrubber\")\nexport class EFScrubber extends TargetOrContextMixin(LitElement, efContext) {\n static styles = [\n css`\n :host {\n --ef-scrubber-height: 4px;\n --ef-scrubber-background: rgb(209 213 219);\n --ef-scrubber-progress-color: rgb(37 99 235);\n --ef-scrubber-handle-size: 12px;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n :host(.dark), :host-context(.dark) {\n --ef-scrubber-background: rgb(75 85 99);\n --ef-scrubber-progress-color: rgb(96 165 250);\n }\n \n :host([orientation=\"vertical\"]) {\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n pointer-events: auto;\n }\n\n .scrubber {\n width: 100%;\n height: var(--ef-scrubber-height);\n background: var(--ef-scrubber-background);\n position: relative;\n cursor: pointer;\n border-radius: 2px;\n touch-action: none;\n user-select: none;\n }\n\n :host([orientation=\"vertical\"]) .scrubber {\n width: 100%;\n height: 100%;\n background: transparent;\n cursor: ew-resize;\n }\n\n .progress {\n position: absolute;\n height: 100%;\n background: var(--ef-scrubber-progress-color);\n border-radius: 2px;\n }\n\n :host([orientation=\"vertical\"]) .progress {\n display: none;\n }\n\n .handle {\n position: absolute;\n width: var(--ef-scrubber-handle-size);\n height: var(--ef-scrubber-handle-size);\n background: var(--ef-scrubber-progress-color);\n border-radius: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n cursor: grab;\n }\n\n :host([orientation=\"vertical\"]) .handle {\n display: none;\n }\n\n .playhead {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--ef-scrubber-progress-color);\n pointer-events: auto;\n cursor: ew-resize;\n z-index: 30;\n }\n\n ::part(playhead) {\n z-index: 30;\n }\n\n .playhead-handle {\n position: absolute;\n top: 0;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 12px;\n height: 12px;\n background: var(--ef-scrubber-progress-color);\n border-radius: 50%;\n }\n\n .raw-preview {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: rgba(37, 99, 235, 0.2);\n pointer-events: none;\n z-index: 20;\n }\n\n /* Add CSS Shadow Parts */\n ::part(scrubber) { }\n ::part(progress) { }\n ::part(handle) { }\n `,\n ];\n\n @consume({ context: playingContext, subscribe: true })\n playing = false;\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = Number.NaN;\n\n @consume({ context: durationContext, subscribe: true })\n contextDurationMs = 0;\n\n @property({ type: String, attribute: \"orientation\" })\n orientation: \"horizontal\" | \"vertical\" = \"horizontal\";\n\n @property({ type: Number, attribute: \"current-time-ms\" })\n currentTimeMs = Number.NaN;\n\n @property({ type: Number, attribute: \"duration-ms\" })\n durationMs = 0;\n\n @property({ type: Number, attribute: \"zoom-scale\" })\n zoomScale = 1.0;\n\n @property({ type: Number, attribute: \"container-width\" })\n containerWidth = 0;\n\n @property({ type: Number, attribute: \"fps\" })\n fps?: number;\n\n @property({ type: Number, attribute: \"raw-scrub-time-ms\" })\n rawScrubTimeMs?: number | null;\n\n @property({ attribute: false })\n scrollContainerRef?: { current: HTMLElement | null };\n\n /**\n * Reference to the element that represents the actual track content area.\n * Used to calculate the offset between the scroll container and where tracks begin.\n */\n @property({ attribute: false })\n trackContentRef?: { current: HTMLElement | null };\n\n @property({ attribute: false })\n onSeek?: (time: number) => void;\n\n @property({ attribute: false })\n isScrubbingRef?: { current: boolean };\n\n get context(): ControllableInterface | null {\n return this.effectiveContext;\n }\n\n get effectiveCurrentTimeMs(): number {\n if (!Number.isNaN(this.currentTimeMs)) {\n return this.currentTimeMs;\n }\n if (!Number.isNaN(this.contextCurrentTimeMs)) {\n return this.contextCurrentTimeMs;\n }\n return 0;\n }\n\n get effectiveDurationMs(): number {\n return this.durationMs || this.contextDurationMs || 0;\n }\n\n get isTimelineMode(): boolean {\n return this.orientation === \"vertical\" && this.zoomScale > 0;\n }\n\n @state()\n private scrubProgress = 0;\n\n @state()\n private isMoving = false;\n\n private scrubberRef = createRef<HTMLElement>();\n private _scrubberElement?: HTMLElement;\n private capturedPointerId: number | null = null;\n\n private updateProgress(e: PointerEvent) {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (!scrubberEl) return;\n\n const duration = this.effectiveDurationMs;\n if (duration <= 0) return;\n\n if (this.isTimelineMode) {\n // Timeline mode: use pixel-based positioning with zoom\n const scrollContainer =\n this.scrollContainerRef?.current || scrubberEl.parentElement;\n if (!scrollContainer) return;\n\n const scrollContainerRect = scrollContainer.getBoundingClientRect();\n const scrollLeft = scrollContainer.scrollLeft || 0;\n\n // Calculate pixel offset dynamically from the track content element\n // This accounts for any hierarchy panel or other elements before the tracks\n let pixelOffset = 0;\n if (this.trackContentRef?.current) {\n const trackRect = this.trackContentRef.current.getBoundingClientRect();\n pixelOffset =\n trackRect.left -\n scrollContainerRect.left +\n scrollContainer.scrollLeft;\n }\n\n const x = e.clientX - scrollContainerRect.left - pixelOffset;\n const pixelPosition = scrollLeft + x;\n const effectiveWidth =\n this.containerWidth > 0\n ? this.containerWidth\n : scrollContainerRect.width;\n if (effectiveWidth <= 0) return;\n\n let rawTime = pixelsToTime(\n pixelPosition,\n duration,\n effectiveWidth,\n this.zoomScale,\n );\n rawTime = Math.max(0, Math.min(rawTime, duration));\n\n // Quantize to frame boundaries if FPS is provided, then clamp to duration\n let quantizedTime =\n this.fps && this.fps > 0\n ? quantizeToFrameTimeMs(rawTime, this.fps)\n : rawTime;\n quantizedTime = Math.max(0, Math.min(quantizedTime, duration));\n\n this.scrubProgress = quantizedTime / duration;\n\n if (this.onSeek) {\n this.onSeek(quantizedTime);\n } else {\n // Emit seek event for event listeners\n this.dispatchEvent(\n new CustomEvent(\"seek\", {\n detail: quantizedTime,\n bubbles: true,\n composed: true,\n }),\n );\n if (this.context) {\n this.context.currentTimeMs = quantizedTime;\n }\n }\n } else {\n // Horizontal mode: simple progress calculation\n const rect = scrubberEl.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const progress = Math.max(0, Math.min(1, x / rect.width));\n\n this.scrubProgress = progress;\n const timeMs = progress * duration;\n\n if (this.onSeek) {\n this.onSeek(timeMs);\n } else {\n // Emit seek event for event listeners\n this.dispatchEvent(\n new CustomEvent(\"seek\", {\n detail: timeMs,\n bubbles: true,\n composed: true,\n }),\n );\n if (this.context) {\n this.context.currentTimeMs = timeMs;\n }\n }\n }\n }\n\n @eventOptions({ passive: false, capture: false })\n private handlePointerDown(e: PointerEvent) {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (!scrubberEl) return;\n\n this.isMoving = true;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = true;\n }\n e.preventDefault();\n e.stopPropagation();\n this.capturedPointerId = e.pointerId;\n try {\n scrubberEl.setPointerCapture(e.pointerId);\n } catch (err) {\n // setPointerCapture may fail in some cases, continue anyway\n console.warn(\"Failed to set pointer capture:\", err);\n }\n this.updateProgress(e);\n }\n\n private boundHandlePointerMove = (e: PointerEvent) => {\n if (this.isMoving && e.pointerId === this.capturedPointerId) {\n e.preventDefault();\n e.stopPropagation();\n this.updateProgress(e);\n }\n };\n\n private boundHandlePointerUp = (e: PointerEvent) => {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (e.pointerId === this.capturedPointerId && scrubberEl) {\n e.preventDefault();\n e.stopPropagation();\n try {\n scrubberEl.releasePointerCapture(e.pointerId);\n } catch (_err) {\n // releasePointerCapture may fail if capture was already lost\n }\n this.capturedPointerId = null;\n this.isMoving = false;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = false;\n }\n }\n };\n\n private boundHandlePointerCancel = (e: PointerEvent) => {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (e.pointerId === this.capturedPointerId && scrubberEl) {\n try {\n scrubberEl.releasePointerCapture(e.pointerId);\n } catch (_err) {\n // releasePointerCapture may fail if capture was already lost\n }\n this.capturedPointerId = null;\n this.isMoving = false;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = false;\n }\n }\n };\n\n private boundHandleContextMenu = (e: Event) => {\n if (this.isMoving) {\n e.preventDefault();\n e.stopPropagation();\n }\n };\n\n render() {\n const duration = this.effectiveDurationMs;\n const currentTime = this.effectiveCurrentTimeMs;\n\n if (duration <= 0) {\n return html``;\n }\n\n if (this.isTimelineMode) {\n // Vertical timeline mode: render playhead line\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n const effectiveWidth =\n this.containerWidth > 0\n ? this.containerWidth\n : scrubberEl?.parentElement?.getBoundingClientRect().width || 0;\n\n const positionPixels =\n effectiveWidth > 0\n ? timeToPixels(currentTime, duration, effectiveWidth, this.zoomScale)\n : 0;\n\n const rawScrubPositionPixels =\n this.rawScrubTimeMs !== null &&\n this.rawScrubTimeMs !== undefined &&\n effectiveWidth > 0\n ? timeToPixels(\n this.rawScrubTimeMs,\n duration,\n effectiveWidth,\n this.zoomScale,\n )\n : null;\n\n return html`\n ${\n rawScrubPositionPixels !== null &&\n rawScrubPositionPixels !== positionPixels\n ? html`<div\n class=\"raw-preview\"\n style=\"left: ${rawScrubPositionPixels}px\"\n ></div>`\n : html``\n }\n <div\n ${ref(this.scrubberRef)}\n part=\"scrubber\"\n class=\"scrubber\"\n @pointerdown=${this.handlePointerDown}\n @contextmenu=${this.boundHandleContextMenu}\n >\n <div\n part=\"playhead\"\n class=\"playhead\"\n style=\"left: ${positionPixels}px\"\n @pointerdown=${this.handlePointerDown}\n >\n <div class=\"playhead-handle\"></div>\n </div>\n </div>\n `;\n } else {\n // Horizontal mode: render progress bar\n const currentProgress = duration > 0 ? currentTime / duration : 0;\n const displayProgress = this.isMoving\n ? this.scrubProgress\n : currentProgress;\n\n return html`\n <div\n ${ref(this.scrubberRef)}\n part=\"scrubber\"\n class=\"scrubber\"\n @pointerdown=${this.handlePointerDown}\n @contextmenu=${this.boundHandleContextMenu}\n >\n <div class=\"progress\" style=\"width: ${displayProgress * 100}%\"></div>\n <div class=\"handle\" style=\"left: ${displayProgress * 100}%\"></div>\n </div>\n `;\n }\n }\n\n connectedCallback() {\n super.connectedCallback();\n window.addEventListener(\n \"pointerup\",\n this.boundHandlePointerUp as EventListener,\n { passive: false },\n );\n window.addEventListener(\"pointermove\", this.boundHandlePointerMove, {\n passive: false,\n });\n window.addEventListener(\n \"pointercancel\",\n this.boundHandlePointerCancel as EventListener,\n { passive: false },\n );\n this.addEventListener(\"contextmenu\", this.boundHandleContextMenu, {\n passive: false,\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n window.removeEventListener(\n \"pointerup\",\n this.boundHandlePointerUp as EventListener,\n );\n window.removeEventListener(\"pointermove\", this.boundHandlePointerMove);\n window.removeEventListener(\n \"pointercancel\",\n this.boundHandlePointerCancel as EventListener,\n );\n this.removeEventListener(\"contextmenu\", this.boundHandleContextMenu);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-scrubber\": EFScrubber;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFScrubber.js","names":["EFScrubber"],"sources":["../../src/gui/EFScrubber.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport {\n customElement,\n eventOptions,\n property,\n state,\n} from \"lit/decorators.js\";\n\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport type { ControllableInterface } from \"./Controllable.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { efContext } from \"./efContext.js\";\nimport { playingContext } from \"./playingContext.js\";\nimport { TargetOrContextMixin } from \"./TargetOrContextMixin.js\";\nimport { quantizeToFrameTimeMs } from \"./EFTimelineRuler.js\";\n\nconst BASE_PIXELS_PER_SECOND = 100;\n\nfunction timeToPixels(\n timeMs: number,\n durationMs: number,\n _containerWidth: number,\n zoomScale: number,\n): number {\n if (durationMs <= 0) return 0;\n const pixelsPerSecond = BASE_PIXELS_PER_SECOND * zoomScale;\n return (timeMs / 1000) * pixelsPerSecond;\n}\n\nfunction pixelsToTime(\n pixels: number,\n durationMs: number,\n _containerWidth: number,\n zoomScale: number,\n): number {\n if (durationMs <= 0) return 0;\n const pixelsPerSecond = BASE_PIXELS_PER_SECOND * zoomScale;\n return (pixels / pixelsPerSecond) * 1000;\n}\n\n@customElement(\"ef-scrubber\")\nexport class EFScrubber extends TargetOrContextMixin(LitElement, efContext) {\n static styles = [\n css`\n :host {\n --ef-scrubber-height: 4px;\n --ef-scrubber-background: var(--ef-color-border, rgba(255, 255, 255, 0.2));\n --ef-scrubber-progress-color: var(--ef-color-primary, #fff);\n --ef-scrubber-handle-size: 12px;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n :host([orientation=\"vertical\"]) {\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n pointer-events: auto;\n }\n\n .scrubber {\n width: 100%;\n height: var(--ef-scrubber-height);\n background: var(--ef-scrubber-background);\n position: relative;\n cursor: pointer;\n border-radius: 2px;\n touch-action: none;\n user-select: none;\n }\n\n :host([orientation=\"vertical\"]) .scrubber {\n width: 100%;\n height: 100%;\n background: transparent;\n cursor: ew-resize;\n }\n\n .progress {\n position: absolute;\n height: 100%;\n background: var(--ef-scrubber-progress-color);\n border-radius: 2px;\n }\n\n :host([orientation=\"vertical\"]) .progress {\n display: none;\n }\n\n .handle {\n position: absolute;\n width: var(--ef-scrubber-handle-size);\n height: var(--ef-scrubber-handle-size);\n background: var(--ef-scrubber-progress-color);\n border-radius: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n cursor: grab;\n }\n\n :host([orientation=\"vertical\"]) .handle {\n display: none;\n }\n\n .playhead {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--ef-scrubber-progress-color);\n pointer-events: auto;\n cursor: ew-resize;\n z-index: 30;\n }\n\n ::part(playhead) {\n z-index: 30;\n }\n\n .playhead-handle {\n position: absolute;\n top: 0;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 12px;\n height: 12px;\n background: var(--ef-scrubber-progress-color);\n border-radius: 50%;\n }\n\n .raw-preview {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: color-mix(in srgb, var(--ef-color-primary) 20%, transparent);\n pointer-events: none;\n z-index: 20;\n }\n\n /* Add CSS Shadow Parts */\n ::part(scrubber) { }\n ::part(progress) { }\n ::part(handle) { }\n `,\n ];\n\n @consume({ context: playingContext, subscribe: true })\n playing = false;\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = Number.NaN;\n\n @consume({ context: durationContext, subscribe: true })\n contextDurationMs = 0;\n\n @property({ type: String, attribute: \"orientation\" })\n orientation: \"horizontal\" | \"vertical\" = \"horizontal\";\n\n @property({ type: Number, attribute: \"current-time-ms\" })\n currentTimeMs = Number.NaN;\n\n @property({ type: Number, attribute: \"duration-ms\" })\n durationMs = 0;\n\n @property({ type: Number, attribute: \"zoom-scale\" })\n zoomScale = 1.0;\n\n @property({ type: Number, attribute: \"container-width\" })\n containerWidth = 0;\n\n @property({ type: Number, attribute: \"fps\" })\n fps?: number;\n\n @property({ type: Number, attribute: \"raw-scrub-time-ms\" })\n rawScrubTimeMs?: number | null;\n\n @property({ attribute: false })\n scrollContainerRef?: { current: HTMLElement | null };\n\n /**\n * Reference to the element that represents the actual track content area.\n * Used to calculate the offset between the scroll container and where tracks begin.\n */\n @property({ attribute: false })\n trackContentRef?: { current: HTMLElement | null };\n\n @property({ attribute: false })\n onSeek?: (time: number) => void;\n\n @property({ attribute: false })\n isScrubbingRef?: { current: boolean };\n\n get context(): ControllableInterface | null {\n return this.effectiveContext;\n }\n\n get effectiveCurrentTimeMs(): number {\n if (!Number.isNaN(this.currentTimeMs)) {\n return this.currentTimeMs;\n }\n if (!Number.isNaN(this.contextCurrentTimeMs)) {\n return this.contextCurrentTimeMs;\n }\n return 0;\n }\n\n get effectiveDurationMs(): number {\n return this.durationMs || this.contextDurationMs || 0;\n }\n\n get isTimelineMode(): boolean {\n return this.orientation === \"vertical\" && this.zoomScale > 0;\n }\n\n @state()\n private scrubProgress = 0;\n\n @state()\n private isMoving = false;\n\n private scrubberRef = createRef<HTMLElement>();\n private _scrubberElement?: HTMLElement;\n private capturedPointerId: number | null = null;\n\n private updateProgress(e: PointerEvent) {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (!scrubberEl) return;\n\n const duration = this.effectiveDurationMs;\n if (duration <= 0) return;\n\n if (this.isTimelineMode) {\n // Timeline mode: use pixel-based positioning with zoom\n const scrollContainer =\n this.scrollContainerRef?.current || scrubberEl.parentElement;\n if (!scrollContainer) return;\n\n const scrollContainerRect = scrollContainer.getBoundingClientRect();\n const scrollLeft = scrollContainer.scrollLeft || 0;\n\n // Calculate pixel offset dynamically from the track content element\n // This accounts for any hierarchy panel or other elements before the tracks\n let pixelOffset = 0;\n if (this.trackContentRef?.current) {\n const trackRect = this.trackContentRef.current.getBoundingClientRect();\n pixelOffset =\n trackRect.left -\n scrollContainerRect.left +\n scrollContainer.scrollLeft;\n }\n\n const x = e.clientX - scrollContainerRect.left - pixelOffset;\n const pixelPosition = scrollLeft + x;\n const effectiveWidth =\n this.containerWidth > 0\n ? this.containerWidth\n : scrollContainerRect.width;\n if (effectiveWidth <= 0) return;\n\n let rawTime = pixelsToTime(\n pixelPosition,\n duration,\n effectiveWidth,\n this.zoomScale,\n );\n rawTime = Math.max(0, Math.min(rawTime, duration));\n\n // Quantize to frame boundaries if FPS is provided, then clamp to duration\n let quantizedTime =\n this.fps && this.fps > 0\n ? quantizeToFrameTimeMs(rawTime, this.fps)\n : rawTime;\n quantizedTime = Math.max(0, Math.min(quantizedTime, duration));\n\n this.scrubProgress = quantizedTime / duration;\n\n if (this.onSeek) {\n this.onSeek(quantizedTime);\n } else {\n // Emit seek event for event listeners\n this.dispatchEvent(\n new CustomEvent(\"seek\", {\n detail: quantizedTime,\n bubbles: true,\n composed: true,\n }),\n );\n if (this.context) {\n this.context.currentTimeMs = quantizedTime;\n }\n }\n } else {\n // Horizontal mode: simple progress calculation\n const rect = scrubberEl.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const progress = Math.max(0, Math.min(1, x / rect.width));\n\n this.scrubProgress = progress;\n const timeMs = progress * duration;\n\n if (this.onSeek) {\n this.onSeek(timeMs);\n } else {\n // Emit seek event for event listeners\n this.dispatchEvent(\n new CustomEvent(\"seek\", {\n detail: timeMs,\n bubbles: true,\n composed: true,\n }),\n );\n if (this.context) {\n this.context.currentTimeMs = timeMs;\n }\n }\n }\n }\n\n @eventOptions({ passive: false, capture: false })\n private handlePointerDown(e: PointerEvent) {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (!scrubberEl) return;\n\n this.isMoving = true;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = true;\n }\n e.preventDefault();\n e.stopPropagation();\n this.capturedPointerId = e.pointerId;\n try {\n scrubberEl.setPointerCapture(e.pointerId);\n } catch (err) {\n // setPointerCapture may fail in some cases, continue anyway\n console.warn(\"Failed to set pointer capture:\", err);\n }\n this.updateProgress(e);\n }\n\n private boundHandlePointerMove = (e: PointerEvent) => {\n if (this.isMoving && e.pointerId === this.capturedPointerId) {\n e.preventDefault();\n e.stopPropagation();\n this.updateProgress(e);\n }\n };\n\n private boundHandlePointerUp = (e: PointerEvent) => {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (e.pointerId === this.capturedPointerId && scrubberEl) {\n e.preventDefault();\n e.stopPropagation();\n try {\n scrubberEl.releasePointerCapture(e.pointerId);\n } catch (_err) {\n // releasePointerCapture may fail if capture was already lost\n }\n this.capturedPointerId = null;\n this.isMoving = false;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = false;\n }\n }\n };\n\n private boundHandlePointerCancel = (e: PointerEvent) => {\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n if (e.pointerId === this.capturedPointerId && scrubberEl) {\n try {\n scrubberEl.releasePointerCapture(e.pointerId);\n } catch (_err) {\n // releasePointerCapture may fail if capture was already lost\n }\n this.capturedPointerId = null;\n this.isMoving = false;\n if (this.isScrubbingRef) {\n this.isScrubbingRef.current = false;\n }\n }\n };\n\n private boundHandleContextMenu = (e: Event) => {\n if (this.isMoving) {\n e.preventDefault();\n e.stopPropagation();\n }\n };\n\n render() {\n const duration = this.effectiveDurationMs;\n const currentTime = this.effectiveCurrentTimeMs;\n\n if (duration <= 0) {\n return html``;\n }\n\n if (this.isTimelineMode) {\n // Vertical timeline mode: render playhead line\n const scrubberEl = this.scrubberRef.value || this._scrubberElement;\n const effectiveWidth =\n this.containerWidth > 0\n ? this.containerWidth\n : scrubberEl?.parentElement?.getBoundingClientRect().width || 0;\n\n const positionPixels =\n effectiveWidth > 0\n ? timeToPixels(currentTime, duration, effectiveWidth, this.zoomScale)\n : 0;\n\n const rawScrubPositionPixels =\n this.rawScrubTimeMs !== null &&\n this.rawScrubTimeMs !== undefined &&\n effectiveWidth > 0\n ? timeToPixels(\n this.rawScrubTimeMs,\n duration,\n effectiveWidth,\n this.zoomScale,\n )\n : null;\n\n return html`\n ${\n rawScrubPositionPixels !== null &&\n rawScrubPositionPixels !== positionPixels\n ? html`<div\n class=\"raw-preview\"\n style=\"left: ${rawScrubPositionPixels}px\"\n ></div>`\n : html``\n }\n <div\n ${ref(this.scrubberRef)}\n part=\"scrubber\"\n class=\"scrubber\"\n @pointerdown=${this.handlePointerDown}\n @contextmenu=${this.boundHandleContextMenu}\n >\n <div\n part=\"playhead\"\n class=\"playhead\"\n style=\"left: ${positionPixels}px\"\n @pointerdown=${this.handlePointerDown}\n >\n <div class=\"playhead-handle\"></div>\n </div>\n </div>\n `;\n } else {\n // Horizontal mode: render progress bar\n const currentProgress = duration > 0 ? currentTime / duration : 0;\n const displayProgress = this.isMoving\n ? this.scrubProgress\n : currentProgress;\n\n return html`\n <div\n ${ref(this.scrubberRef)}\n part=\"scrubber\"\n class=\"scrubber\"\n @pointerdown=${this.handlePointerDown}\n @contextmenu=${this.boundHandleContextMenu}\n >\n <div part=\"progress\" class=\"progress\" style=\"width: ${displayProgress * 100}%\"></div>\n <div part=\"handle\" class=\"handle\" style=\"left: ${displayProgress * 100}%\"></div>\n </div>\n `;\n }\n }\n\n connectedCallback() {\n super.connectedCallback();\n window.addEventListener(\n \"pointerup\",\n this.boundHandlePointerUp as EventListener,\n { passive: false },\n );\n window.addEventListener(\"pointermove\", this.boundHandlePointerMove, {\n passive: false,\n });\n window.addEventListener(\n \"pointercancel\",\n this.boundHandlePointerCancel as EventListener,\n { passive: false },\n );\n this.addEventListener(\"contextmenu\", this.boundHandleContextMenu, {\n passive: false,\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n window.removeEventListener(\n \"pointerup\",\n this.boundHandlePointerUp as EventListener,\n );\n window.removeEventListener(\"pointermove\", this.boundHandlePointerMove);\n window.removeEventListener(\n \"pointercancel\",\n this.boundHandlePointerCancel as EventListener,\n );\n this.removeEventListener(\"contextmenu\", this.boundHandleContextMenu);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-scrubber\": EFScrubber;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit20 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
4
|
import * as lit_html19 from "lit-html";
|
|
5
5
|
|
|
@@ -10,9 +10,13 @@ declare const EFTimeDisplay_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFTimeDisplay extends EFTimeDisplay_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit20.CSSResult;
|
|
14
14
|
currentTimeMs: number;
|
|
15
15
|
durationMs: number;
|
|
16
|
+
contextCurrentTimeMs: number;
|
|
17
|
+
contextDurationMs: number;
|
|
18
|
+
get effectiveCurrentTimeMs(): number;
|
|
19
|
+
get effectiveDurationMs(): number;
|
|
16
20
|
private formatTime;
|
|
17
21
|
render(): lit_html19.TemplateResult<1>;
|
|
18
22
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { efContext } from "./efContext.js";
|
|
1
2
|
import { currentTimeContext } from "./currentTimeContext.js";
|
|
2
3
|
import { durationContext } from "./durationContext.js";
|
|
3
4
|
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
|
|
4
|
-
import { efContext } from "./efContext.js";
|
|
5
5
|
import { TargetOrContextMixin } from "./TargetOrContextMixin.js";
|
|
6
6
|
import { consume } from "@lit/context";
|
|
7
7
|
import { LitElement, css, html } from "lit";
|
|
8
|
-
import { customElement } from "lit/decorators.js";
|
|
8
|
+
import { customElement, property } from "lit/decorators.js";
|
|
9
9
|
|
|
10
10
|
//#region src/gui/EFTimeDisplay.ts
|
|
11
11
|
let EFTimeDisplay = class EFTimeDisplay$1 extends TargetOrContextMixin(LitElement, efContext) {
|
|
@@ -13,6 +13,8 @@ let EFTimeDisplay = class EFTimeDisplay$1 extends TargetOrContextMixin(LitElemen
|
|
|
13
13
|
super(..._args);
|
|
14
14
|
this.currentTimeMs = NaN;
|
|
15
15
|
this.durationMs = 0;
|
|
16
|
+
this.contextCurrentTimeMs = NaN;
|
|
17
|
+
this.contextDurationMs = 0;
|
|
16
18
|
}
|
|
17
19
|
static {
|
|
18
20
|
this.styles = css`
|
|
@@ -21,21 +23,29 @@ let EFTimeDisplay = class EFTimeDisplay$1 extends TargetOrContextMixin(LitElemen
|
|
|
21
23
|
align-items: center;
|
|
22
24
|
font-family: var(--ef-font-family, system-ui);
|
|
23
25
|
font-size: var(--ef-font-size-xs, 0.75rem);
|
|
24
|
-
color: var(--ef-text-color,
|
|
26
|
+
color: var(--ef-text-color, var(--ef-color-text));
|
|
25
27
|
white-space: nowrap;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
::part(time) {}
|
|
29
31
|
`;
|
|
30
32
|
}
|
|
33
|
+
get effectiveCurrentTimeMs() {
|
|
34
|
+
if (!Number.isNaN(this.currentTimeMs)) return this.currentTimeMs;
|
|
35
|
+
if (!Number.isNaN(this.contextCurrentTimeMs)) return this.contextCurrentTimeMs;
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
get effectiveDurationMs() {
|
|
39
|
+
return this.durationMs || this.contextDurationMs || 0;
|
|
40
|
+
}
|
|
31
41
|
formatTime(ms) {
|
|
32
42
|
if (!Number.isFinite(ms) || ms < 0) return "0:00";
|
|
33
43
|
const totalSeconds = Math.floor(ms / 1e3);
|
|
34
44
|
return `${Math.floor(totalSeconds / 60)}:${(totalSeconds % 60).toString().padStart(2, "0")}`;
|
|
35
45
|
}
|
|
36
46
|
render() {
|
|
37
|
-
const currentTime = this.
|
|
38
|
-
const totalTime = this.
|
|
47
|
+
const currentTime = this.effectiveCurrentTimeMs;
|
|
48
|
+
const totalTime = this.effectiveDurationMs;
|
|
39
49
|
return html`
|
|
40
50
|
<span part="time">
|
|
41
51
|
${this.formatTime(currentTime)} / ${this.formatTime(totalTime)}
|
|
@@ -43,14 +53,22 @@ let EFTimeDisplay = class EFTimeDisplay$1 extends TargetOrContextMixin(LitElemen
|
|
|
43
53
|
`;
|
|
44
54
|
}
|
|
45
55
|
};
|
|
56
|
+
__decorate([property({
|
|
57
|
+
type: Number,
|
|
58
|
+
attribute: "current-time-ms"
|
|
59
|
+
})], EFTimeDisplay.prototype, "currentTimeMs", void 0);
|
|
60
|
+
__decorate([property({
|
|
61
|
+
type: Number,
|
|
62
|
+
attribute: "duration-ms"
|
|
63
|
+
})], EFTimeDisplay.prototype, "durationMs", void 0);
|
|
46
64
|
__decorate([consume({
|
|
47
65
|
context: currentTimeContext,
|
|
48
66
|
subscribe: true
|
|
49
|
-
})], EFTimeDisplay.prototype, "
|
|
67
|
+
})], EFTimeDisplay.prototype, "contextCurrentTimeMs", void 0);
|
|
50
68
|
__decorate([consume({
|
|
51
69
|
context: durationContext,
|
|
52
70
|
subscribe: true
|
|
53
|
-
})], EFTimeDisplay.prototype, "
|
|
71
|
+
})], EFTimeDisplay.prototype, "contextDurationMs", void 0);
|
|
54
72
|
EFTimeDisplay = __decorate([customElement("ef-time-display")], EFTimeDisplay);
|
|
55
73
|
|
|
56
74
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFTimeDisplay.js","names":["EFTimeDisplay"],"sources":["../../src/gui/EFTimeDisplay.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { efContext } from \"./efContext.js\";\nimport { TargetOrContextMixin } from \"./TargetOrContextMixin.js\";\n\n@customElement(\"ef-time-display\")\nexport class EFTimeDisplay extends TargetOrContextMixin(LitElement, efContext) {\n static styles = css`\n :host {\n display: inline-flex;\n align-items: center;\n font-family: var(--ef-font-family, system-ui);\n font-size: var(--ef-font-size-xs, 0.75rem);\n color: var(--ef-text-color,
|
|
1
|
+
{"version":3,"file":"EFTimeDisplay.js","names":["EFTimeDisplay"],"sources":["../../src/gui/EFTimeDisplay.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { currentTimeContext } from \"./currentTimeContext.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport { efContext } from \"./efContext.js\";\nimport { TargetOrContextMixin } from \"./TargetOrContextMixin.js\";\n\n@customElement(\"ef-time-display\")\nexport class EFTimeDisplay extends TargetOrContextMixin(LitElement, efContext) {\n static styles = css`\n :host {\n display: inline-flex;\n align-items: center;\n font-family: var(--ef-font-family, system-ui);\n font-size: var(--ef-font-size-xs, 0.75rem);\n color: var(--ef-text-color, var(--ef-color-text));\n white-space: nowrap;\n }\n\n ::part(time) {}\n `;\n\n @property({ type: Number, attribute: \"current-time-ms\" })\n currentTimeMs = Number.NaN;\n\n @property({ type: Number, attribute: \"duration-ms\" })\n durationMs = 0;\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = Number.NaN;\n\n @consume({ context: durationContext, subscribe: true })\n contextDurationMs = 0;\n\n get effectiveCurrentTimeMs(): number {\n if (!Number.isNaN(this.currentTimeMs)) return this.currentTimeMs;\n if (!Number.isNaN(this.contextCurrentTimeMs))\n return this.contextCurrentTimeMs;\n return 0;\n }\n\n get effectiveDurationMs(): number {\n return this.durationMs || this.contextDurationMs || 0;\n }\n\n private formatTime(ms: number): string {\n // Handle NaN, undefined, null, or negative values\n if (!Number.isFinite(ms) || ms < 0) {\n return \"0:00\";\n }\n\n const totalSeconds = Math.floor(ms / 1000);\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n return `${minutes}:${seconds.toString().padStart(2, \"0\")}`;\n }\n\n render() {\n const currentTime = this.effectiveCurrentTimeMs;\n const totalTime = this.effectiveDurationMs;\n\n return html`\n <span part=\"time\">\n ${this.formatTime(currentTime)} / ${this.formatTime(totalTime)}\n </span>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-time-display\": EFTimeDisplay;\n }\n}\n"],"mappings":";;;;;;;;;;AASO,0BAAMA,wBAAsB,qBAAqB,YAAY,UAAU,CAAC;;;uBAe7D;oBAGH;8BAGU;2BAGH;;;gBAvBJ,GAAG;;;;;;;;;;;;;CAyBnB,IAAI,yBAAiC;AACnC,MAAI,CAAC,OAAO,MAAM,KAAK,cAAc,CAAE,QAAO,KAAK;AACnD,MAAI,CAAC,OAAO,MAAM,KAAK,qBAAqB,CAC1C,QAAO,KAAK;AACd,SAAO;;CAGT,IAAI,sBAA8B;AAChC,SAAO,KAAK,cAAc,KAAK,qBAAqB;;CAGtD,AAAQ,WAAW,IAAoB;AAErC,MAAI,CAAC,OAAO,SAAS,GAAG,IAAI,KAAK,EAC/B,QAAO;EAGT,MAAM,eAAe,KAAK,MAAM,KAAK,IAAK;AAG1C,SAAO,GAFS,KAAK,MAAM,eAAe,GAAG,CAE3B,IADF,eAAe,IACF,UAAU,CAAC,SAAS,GAAG,IAAI;;CAG1D,SAAS;EACP,MAAM,cAAc,KAAK;EACzB,MAAM,YAAY,KAAK;AAEvB,SAAO,IAAI;;UAEL,KAAK,WAAW,YAAY,CAAC,KAAK,KAAK,WAAW,UAAU,CAAC;;;;;YAzCpE,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAmB,CAAC;YAGxD,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAe,CAAC;YAGpD,QAAQ;CAAE,SAAS;CAAoB,WAAW;CAAM,CAAC;YAGzD,QAAQ;CAAE,SAAS;CAAiB,WAAW;CAAM,CAAC;4BAxBxD,cAAc,kBAAkB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TimelineState } from "./timeline/timelineStateContext.js";
|
|
2
2
|
import * as lit33 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html31 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFTimelineRuler.d.ts
|
|
7
7
|
/**
|
|
@@ -59,7 +59,7 @@ declare class EFTimelineRuler extends LitElement {
|
|
|
59
59
|
private calculateLabelInterval;
|
|
60
60
|
private getVisibleLabels;
|
|
61
61
|
private renderCanvas;
|
|
62
|
-
render():
|
|
62
|
+
render(): lit_html31.TemplateResult<1>;
|
|
63
63
|
}
|
|
64
64
|
declare global {
|
|
65
65
|
interface HTMLElementTagNameMap {
|
|
@@ -96,7 +96,7 @@ let EFTimelineRuler = class EFTimelineRuler$1 extends LitElement {
|
|
|
96
96
|
position: absolute;
|
|
97
97
|
top: 50%;
|
|
98
98
|
font-size: 10px;
|
|
99
|
-
color:
|
|
99
|
+
color: var(--ef-color-text-muted);
|
|
100
100
|
font-family: ui-monospace, monospace;
|
|
101
101
|
white-space: nowrap;
|
|
102
102
|
pointer-events: none;
|
|
@@ -231,7 +231,7 @@ let EFTimelineRuler = class EFTimelineRuler$1 extends LitElement {
|
|
|
231
231
|
ctx.clearRect(0, 0, width, height);
|
|
232
232
|
const pixelsPerMs = this.pixelsPerMs;
|
|
233
233
|
const canvasLeft = viewport.left;
|
|
234
|
-
ctx.strokeStyle = "rgb(156, 163, 175)";
|
|
234
|
+
ctx.strokeStyle = getComputedStyle(this).getPropertyValue("--ef-color-text-muted").trim() || "rgb(156, 163, 175)";
|
|
235
235
|
ctx.lineWidth = 1;
|
|
236
236
|
const labelIntervalMs = this.calculateLabelInterval();
|
|
237
237
|
const visibleStartTimeMs = Math.max(0, canvasLeft / pixelsPerMs - labelIntervalMs);
|
|
@@ -251,7 +251,7 @@ let EFTimelineRuler = class EFTimelineRuler$1 extends LitElement {
|
|
|
251
251
|
}
|
|
252
252
|
const frameIntervalMs = 1e3 / this.fps;
|
|
253
253
|
if (frameIntervalMs * pixelsPerMs >= MIN_FRAME_SPACING_PX) {
|
|
254
|
-
ctx.strokeStyle = "rgb(107, 114, 128)";
|
|
254
|
+
ctx.strokeStyle = getComputedStyle(this).getPropertyValue("--ef-color-border-subtle").trim() || "rgb(107, 114, 128)";
|
|
255
255
|
ctx.lineWidth = 1;
|
|
256
256
|
const firstFrameIndex = Math.floor(visibleStartTimeMs / frameIntervalMs);
|
|
257
257
|
const lastFrameIndex = Math.ceil(visibleEndTimeMs / frameIntervalMs);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFTimelineRuler.js","names":["EFTimelineRuler","labels: VisibleLabel[]"],"sources":["../../src/gui/EFTimelineRuler.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport {\n timelineStateContext,\n type TimelineState,\n DEFAULT_PIXELS_PER_MS,\n} from \"./timeline/timelineStateContext.js\";\n\nconst MIN_LABEL_SPACING_PX = 80;\nconst MIN_FRAME_SPACING_PX = 5;\n\n/** Maximum canvas width for ruler virtualization */\nconst MAX_RULER_CANVAS_WIDTH = 2000;\n\n/** Buffer pixels on each side of viewport for smooth scrolling */\nconst RULER_CANVAS_BUFFER = 200;\n\n/**\n * Quantize a time value to the nearest frame boundary.\n * This ensures frame markers align perfectly with playhead position.\n */\nexport function quantizeToFrameTimeMs(timeMs: number, fps: number): number {\n if (!fps || fps <= 0) return timeMs;\n const frameDurationS = 1 / fps;\n const timeSeconds = timeMs / 1000;\n const quantizedSeconds =\n Math.round(timeSeconds / frameDurationS) * frameDurationS;\n return quantizedSeconds * 1000;\n}\n\n/**\n * Calculate the duration of a single frame in milliseconds.\n */\nexport function calculateFrameIntervalMs(fps: number): number {\n if (fps <= 0) return 1000 / 30; // fallback to 30fps\n return 1000 / fps;\n}\n\n/**\n * Calculate pixels per frame given frame interval and zoom scale.\n * @param frameIntervalMs Duration of one frame in ms\n * @param pixelsPerMs Current zoom level (pixels per millisecond)\n */\nexport function calculatePixelsPerFrame(\n frameIntervalMs: number,\n pixelsPerMs: number,\n): number {\n return frameIntervalMs * pixelsPerMs;\n}\n\n/**\n * Determine if frame markers should be visible at the current zoom level.\n * Frame markers appear when each frame is at least MIN_FRAME_SPACING_PX wide.\n */\nexport function shouldShowFrameMarkers(\n pixelsPerFrame: number,\n minSpacing: number = MIN_FRAME_SPACING_PX,\n): boolean {\n return pixelsPerFrame >= minSpacing;\n}\n\ninterface VisibleLabel {\n timeMs: number;\n viewportX: number;\n text: string;\n}\n\n@customElement(\"ef-timeline-ruler\")\nexport class EFTimelineRuler extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n height: 100%;\n pointer-events: none;\n }\n \n .ruler-container {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n \n .canvas-viewport {\n position: absolute;\n top: 0;\n height: 100%;\n /* left and width set via inline styles for virtualization */\n }\n \n canvas {\n display: block;\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n }\n \n .label {\n position: absolute;\n top: 50%;\n font-size: 10px;\n color: rgb(156, 163, 175);\n font-family: ui-monospace, monospace;\n white-space: nowrap;\n pointer-events: none;\n user-select: none;\n }\n `,\n ];\n\n @property({ type: Number, attribute: \"duration-ms\" })\n durationMs = 0;\n\n @consume({ context: durationContext, subscribe: true })\n contextDurationMs = 0;\n\n @consume({ context: timelineStateContext, subscribe: true })\n timelineState?: TimelineState;\n\n @property({ type: Number, attribute: \"fps\" })\n fps = 30;\n\n /** Full content width in pixels (for virtualization) */\n @property({ type: Number, attribute: \"content-width\" })\n contentWidth = 0;\n\n private containerRef = createRef<HTMLDivElement>();\n private canvasRef = createRef<HTMLCanvasElement>();\n private resizeObserver?: ResizeObserver;\n\n @state()\n private viewportWidth = 0;\n\n /** Canvas viewport left position for virtualization */\n @state()\n private _canvasViewportLeft = 0;\n\n /** Canvas viewport width for virtualization */\n @state()\n private _canvasViewportWidth = 0;\n\n /** Last rendered scroll position - for detecting scroll changes */\n private _lastRenderedScrollLeft = -1;\n\n /** Last rendered viewport width - for detecting viewport changes */\n private _lastRenderedViewportWidth = 0;\n\n get effectiveDurationMs(): number {\n return this.durationMs || this.contextDurationMs || 0;\n }\n\n get pixelsPerMs(): number {\n return this.timelineState?.pixelsPerMs ?? DEFAULT_PIXELS_PER_MS;\n }\n\n get scrollLeft(): number {\n return this.timelineState?.viewportScrollLeft ?? 0;\n }\n\n /**\n * Calculate canvas viewport bounds for virtualization.\n * Returns the left position and width of the canvas viewport.\n */\n private calculateCanvasViewport(): { left: number; width: number } {\n const totalWidth = this.contentWidth || this.viewportWidth;\n \n // If content is small enough, no virtualization needed\n if (totalWidth <= MAX_RULER_CANVAS_WIDTH) {\n return { left: 0, width: totalWidth };\n }\n\n // Get visible region from scroll position\n const viewportScrollLeft = this.scrollLeft;\n const viewportWidth = this.timelineState?.viewportWidth ?? this.viewportWidth;\n\n // Calculate canvas viewport with buffer for smooth scrolling\n const canvasLeft = Math.max(0, viewportScrollLeft - RULER_CANVAS_BUFFER);\n const canvasRight = Math.min(\n totalWidth,\n viewportScrollLeft + viewportWidth + RULER_CANVAS_BUFFER\n );\n\n // Cap canvas width at maximum\n let canvasWidth = canvasRight - canvasLeft;\n if (canvasWidth > MAX_RULER_CANVAS_WIDTH) {\n canvasWidth = MAX_RULER_CANVAS_WIDTH;\n }\n\n return { left: canvasLeft, width: canvasWidth };\n }\n\n connectedCallback() {\n super.connectedCallback();\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n }\n\n protected firstUpdated(): void {\n const container = this.containerRef.value;\n if (container) {\n this.resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (entry) {\n const width = entry.contentRect.width;\n if (width !== this.viewportWidth) {\n this.viewportWidth = width;\n }\n }\n });\n this.resizeObserver.observe(container);\n this.viewportWidth = container.clientWidth;\n }\n }\n\n protected updated(changedProperties: Map<string | number | symbol, unknown>): void {\n // Check if scroll position or viewport changed from context\n const currentScrollLeft = this.scrollLeft;\n const currentViewportWidth = this.timelineState?.viewportWidth ?? this.viewportWidth;\n \n // Check if scroll changed, viewport changed, or other relevant properties changed\n const scrollChanged = currentScrollLeft !== this._lastRenderedScrollLeft;\n const viewportChanged = currentViewportWidth !== this._lastRenderedViewportWidth;\n const pixelsPerMsChanged = changedProperties.has(\"timelineState\") || \n changedProperties.has(\"pixelsPerMs\");\n const contentWidthChanged = changedProperties.has(\"contentWidth\");\n const durationChanged = changedProperties.has(\"durationMs\") || \n changedProperties.has(\"contextDurationMs\");\n \n // Only render if something actually changed\n if (scrollChanged || viewportChanged || pixelsPerMsChanged || \n contentWidthChanged || durationChanged || \n this._lastRenderedScrollLeft < 0) {\n this.renderCanvas();\n this._lastRenderedScrollLeft = currentScrollLeft;\n this._lastRenderedViewportWidth = currentViewportWidth;\n }\n }\n\n private calculateLabelInterval(): number {\n const pixelsPerMs = this.pixelsPerMs;\n const pixelsPerSecond = pixelsPerMs * 1000;\n\n const intervals = [0.1, 0.25, 0.5, 1, 2, 5, 10, 30, 60];\n\n for (const intervalS of intervals) {\n const pixelsPerInterval = intervalS * pixelsPerSecond;\n if (pixelsPerInterval >= MIN_LABEL_SPACING_PX) {\n return intervalS * 1000;\n }\n }\n\n return 60000;\n }\n\n private getVisibleLabels(): VisibleLabel[] {\n const canvasWidth = this._canvasViewportWidth;\n if (canvasWidth <= 0) return [];\n\n const pixelsPerMs = this.pixelsPerMs;\n const canvasLeft = this._canvasViewportLeft;\n\n const intervalMs = this.calculateLabelInterval();\n\n // Generate labels for the canvas viewport range\n const visibleStartTimeMs = Math.max(\n 0,\n canvasLeft / pixelsPerMs - intervalMs,\n );\n const visibleEndTimeMs =\n (canvasLeft + canvasWidth) / pixelsPerMs + intervalMs;\n\n const firstLabelIndex = Math.floor(visibleStartTimeMs / intervalMs);\n const lastLabelIndex = Math.ceil(visibleEndTimeMs / intervalMs);\n\n const labels: VisibleLabel[] = [];\n\n for (let i = firstLabelIndex; i <= lastLabelIndex; i++) {\n const timeMs = i * intervalMs;\n if (timeMs < 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport (canvas is positioned at canvasLeft)\n const viewportX = absoluteX - canvasLeft;\n\n if (viewportX >= -50 && viewportX <= canvasWidth + 50) {\n const timeSeconds = timeMs / 1000;\n const text =\n timeSeconds % 1 === 0\n ? `${timeSeconds}s`\n : `${timeSeconds.toFixed(1)}s`;\n\n labels.push({ timeMs, viewportX, text });\n }\n }\n\n return labels;\n }\n\n private renderCanvas(): void {\n const canvas = this.canvasRef.value;\n const container = this.containerRef.value;\n if (!canvas || !container) return;\n\n // Calculate virtualized canvas viewport\n const viewport = this.calculateCanvasViewport();\n this._canvasViewportLeft = viewport.left;\n this._canvasViewportWidth = viewport.width;\n\n const width = viewport.width;\n const height = container.getBoundingClientRect().height;\n\n if (width <= 0 || height <= 0) return;\n\n const dpr = window.devicePixelRatio || 1;\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n ctx.scale(dpr, dpr);\n ctx.clearRect(0, 0, width, height);\n\n const pixelsPerMs = this.pixelsPerMs;\n const canvasLeft = viewport.left;\n\n // Time label ticks - more prominent (gray-400)\n ctx.strokeStyle = \"rgb(156, 163, 175)\";\n ctx.lineWidth = 1;\n\n const labelIntervalMs = this.calculateLabelInterval();\n // Fill the canvas viewport with ticks\n const visibleStartTimeMs = Math.max(\n 0,\n canvasLeft / pixelsPerMs - labelIntervalMs,\n );\n const visibleEndTimeMs =\n (canvasLeft + width) / pixelsPerMs + labelIntervalMs;\n\n const firstTickIndex = Math.floor(visibleStartTimeMs / labelIntervalMs);\n const lastTickIndex = Math.ceil(visibleEndTimeMs / labelIntervalMs);\n\n for (let i = firstTickIndex; i <= lastTickIndex; i++) {\n const timeMs = i * labelIntervalMs;\n if (timeMs < 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport\n const canvasX = absoluteX - canvasLeft;\n\n if (canvasX >= -1 && canvasX <= width + 1) {\n ctx.beginPath();\n ctx.moveTo(canvasX, 0);\n ctx.lineTo(canvasX, height * 0.4);\n ctx.stroke();\n }\n }\n\n const frameIntervalMs = 1000 / this.fps;\n const pixelsPerFrame = frameIntervalMs * pixelsPerMs;\n\n if (pixelsPerFrame >= MIN_FRAME_SPACING_PX) {\n // Frame markers should be lighter than background (gray-500) to be visible\n ctx.strokeStyle = \"rgb(107, 114, 128)\";\n ctx.lineWidth = 1;\n\n const firstFrameIndex = Math.floor(visibleStartTimeMs / frameIntervalMs);\n const lastFrameIndex = Math.ceil(visibleEndTimeMs / frameIntervalMs);\n\n for (let i = firstFrameIndex; i <= lastFrameIndex; i++) {\n const timeMs = i * frameIntervalMs;\n if (timeMs < 0) continue;\n\n if (timeMs % labelIntervalMs === 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport\n const canvasX = absoluteX - canvasLeft;\n\n if (canvasX >= -1 && canvasX <= width + 1) {\n ctx.beginPath();\n ctx.moveTo(canvasX, 0);\n ctx.lineTo(canvasX, height * 0.25);\n ctx.stroke();\n }\n }\n }\n }\n\n render() {\n const visibleLabels = this.getVisibleLabels();\n \n const canvasViewportStyles = styleMap({\n left: `${this._canvasViewportLeft}px`,\n width: `${this._canvasViewportWidth}px`,\n });\n\n return html`\n <div ${ref(this.containerRef)} class=\"ruler-container\">\n <div class=\"canvas-viewport\" style=${canvasViewportStyles}>\n <canvas ${ref(this.canvasRef)}></canvas>\n ${visibleLabels.map(\n ({ viewportX, text }) => html`\n <span \n class=\"label\" \n style=\"transform: translateX(${viewportX}px)\"\n >${text}</span>\n `,\n )}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timeline-ruler\": EFTimelineRuler;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFTimelineRuler.js","names":["EFTimelineRuler","labels: VisibleLabel[]"],"sources":["../../src/gui/EFTimelineRuler.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { durationContext } from \"./durationContext.js\";\nimport {\n timelineStateContext,\n type TimelineState,\n DEFAULT_PIXELS_PER_MS,\n} from \"./timeline/timelineStateContext.js\";\n\nconst MIN_LABEL_SPACING_PX = 80;\nconst MIN_FRAME_SPACING_PX = 5;\n\n/** Maximum canvas width for ruler virtualization */\nconst MAX_RULER_CANVAS_WIDTH = 2000;\n\n/** Buffer pixels on each side of viewport for smooth scrolling */\nconst RULER_CANVAS_BUFFER = 200;\n\n/**\n * Quantize a time value to the nearest frame boundary.\n * This ensures frame markers align perfectly with playhead position.\n */\nexport function quantizeToFrameTimeMs(timeMs: number, fps: number): number {\n if (!fps || fps <= 0) return timeMs;\n const frameDurationS = 1 / fps;\n const timeSeconds = timeMs / 1000;\n const quantizedSeconds =\n Math.round(timeSeconds / frameDurationS) * frameDurationS;\n return quantizedSeconds * 1000;\n}\n\n/**\n * Calculate the duration of a single frame in milliseconds.\n */\nexport function calculateFrameIntervalMs(fps: number): number {\n if (fps <= 0) return 1000 / 30; // fallback to 30fps\n return 1000 / fps;\n}\n\n/**\n * Calculate pixels per frame given frame interval and zoom scale.\n * @param frameIntervalMs Duration of one frame in ms\n * @param pixelsPerMs Current zoom level (pixels per millisecond)\n */\nexport function calculatePixelsPerFrame(\n frameIntervalMs: number,\n pixelsPerMs: number,\n): number {\n return frameIntervalMs * pixelsPerMs;\n}\n\n/**\n * Determine if frame markers should be visible at the current zoom level.\n * Frame markers appear when each frame is at least MIN_FRAME_SPACING_PX wide.\n */\nexport function shouldShowFrameMarkers(\n pixelsPerFrame: number,\n minSpacing: number = MIN_FRAME_SPACING_PX,\n): boolean {\n return pixelsPerFrame >= minSpacing;\n}\n\ninterface VisibleLabel {\n timeMs: number;\n viewportX: number;\n text: string;\n}\n\n@customElement(\"ef-timeline-ruler\")\nexport class EFTimelineRuler extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n height: 100%;\n pointer-events: none;\n }\n \n .ruler-container {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n \n .canvas-viewport {\n position: absolute;\n top: 0;\n height: 100%;\n /* left and width set via inline styles for virtualization */\n }\n \n canvas {\n display: block;\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n }\n \n .label {\n position: absolute;\n top: 50%;\n font-size: 10px;\n color: var(--ef-color-text-muted);\n font-family: ui-monospace, monospace;\n white-space: nowrap;\n pointer-events: none;\n user-select: none;\n }\n `,\n ];\n\n @property({ type: Number, attribute: \"duration-ms\" })\n durationMs = 0;\n\n @consume({ context: durationContext, subscribe: true })\n contextDurationMs = 0;\n\n @consume({ context: timelineStateContext, subscribe: true })\n timelineState?: TimelineState;\n\n @property({ type: Number, attribute: \"fps\" })\n fps = 30;\n\n /** Full content width in pixels (for virtualization) */\n @property({ type: Number, attribute: \"content-width\" })\n contentWidth = 0;\n\n private containerRef = createRef<HTMLDivElement>();\n private canvasRef = createRef<HTMLCanvasElement>();\n private resizeObserver?: ResizeObserver;\n\n @state()\n private viewportWidth = 0;\n\n /** Canvas viewport left position for virtualization */\n @state()\n private _canvasViewportLeft = 0;\n\n /** Canvas viewport width for virtualization */\n @state()\n private _canvasViewportWidth = 0;\n\n /** Last rendered scroll position - for detecting scroll changes */\n private _lastRenderedScrollLeft = -1;\n\n /** Last rendered viewport width - for detecting viewport changes */\n private _lastRenderedViewportWidth = 0;\n\n get effectiveDurationMs(): number {\n return this.durationMs || this.contextDurationMs || 0;\n }\n\n get pixelsPerMs(): number {\n return this.timelineState?.pixelsPerMs ?? DEFAULT_PIXELS_PER_MS;\n }\n\n get scrollLeft(): number {\n return this.timelineState?.viewportScrollLeft ?? 0;\n }\n\n /**\n * Calculate canvas viewport bounds for virtualization.\n * Returns the left position and width of the canvas viewport.\n */\n private calculateCanvasViewport(): { left: number; width: number } {\n const totalWidth = this.contentWidth || this.viewportWidth;\n\n // If content is small enough, no virtualization needed\n if (totalWidth <= MAX_RULER_CANVAS_WIDTH) {\n return { left: 0, width: totalWidth };\n }\n\n // Get visible region from scroll position\n const viewportScrollLeft = this.scrollLeft;\n const viewportWidth =\n this.timelineState?.viewportWidth ?? this.viewportWidth;\n\n // Calculate canvas viewport with buffer for smooth scrolling\n const canvasLeft = Math.max(0, viewportScrollLeft - RULER_CANVAS_BUFFER);\n const canvasRight = Math.min(\n totalWidth,\n viewportScrollLeft + viewportWidth + RULER_CANVAS_BUFFER,\n );\n\n // Cap canvas width at maximum\n let canvasWidth = canvasRight - canvasLeft;\n if (canvasWidth > MAX_RULER_CANVAS_WIDTH) {\n canvasWidth = MAX_RULER_CANVAS_WIDTH;\n }\n\n return { left: canvasLeft, width: canvasWidth };\n }\n\n connectedCallback() {\n super.connectedCallback();\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n }\n\n protected firstUpdated(): void {\n const container = this.containerRef.value;\n if (container) {\n this.resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (entry) {\n const width = entry.contentRect.width;\n if (width !== this.viewportWidth) {\n this.viewportWidth = width;\n }\n }\n });\n this.resizeObserver.observe(container);\n this.viewportWidth = container.clientWidth;\n }\n }\n\n protected updated(\n changedProperties: Map<string | number | symbol, unknown>,\n ): void {\n // Check if scroll position or viewport changed from context\n const currentScrollLeft = this.scrollLeft;\n const currentViewportWidth =\n this.timelineState?.viewportWidth ?? this.viewportWidth;\n\n // Check if scroll changed, viewport changed, or other relevant properties changed\n const scrollChanged = currentScrollLeft !== this._lastRenderedScrollLeft;\n const viewportChanged =\n currentViewportWidth !== this._lastRenderedViewportWidth;\n const pixelsPerMsChanged =\n changedProperties.has(\"timelineState\") ||\n changedProperties.has(\"pixelsPerMs\");\n const contentWidthChanged = changedProperties.has(\"contentWidth\");\n const durationChanged =\n changedProperties.has(\"durationMs\") ||\n changedProperties.has(\"contextDurationMs\");\n\n // Only render if something actually changed\n if (\n scrollChanged ||\n viewportChanged ||\n pixelsPerMsChanged ||\n contentWidthChanged ||\n durationChanged ||\n this._lastRenderedScrollLeft < 0\n ) {\n this.renderCanvas();\n this._lastRenderedScrollLeft = currentScrollLeft;\n this._lastRenderedViewportWidth = currentViewportWidth;\n }\n }\n\n private calculateLabelInterval(): number {\n const pixelsPerMs = this.pixelsPerMs;\n const pixelsPerSecond = pixelsPerMs * 1000;\n\n const intervals = [0.1, 0.25, 0.5, 1, 2, 5, 10, 30, 60];\n\n for (const intervalS of intervals) {\n const pixelsPerInterval = intervalS * pixelsPerSecond;\n if (pixelsPerInterval >= MIN_LABEL_SPACING_PX) {\n return intervalS * 1000;\n }\n }\n\n return 60000;\n }\n\n private getVisibleLabels(): VisibleLabel[] {\n const canvasWidth = this._canvasViewportWidth;\n if (canvasWidth <= 0) return [];\n\n const pixelsPerMs = this.pixelsPerMs;\n const canvasLeft = this._canvasViewportLeft;\n\n const intervalMs = this.calculateLabelInterval();\n\n // Generate labels for the canvas viewport range\n const visibleStartTimeMs = Math.max(\n 0,\n canvasLeft / pixelsPerMs - intervalMs,\n );\n const visibleEndTimeMs =\n (canvasLeft + canvasWidth) / pixelsPerMs + intervalMs;\n\n const firstLabelIndex = Math.floor(visibleStartTimeMs / intervalMs);\n const lastLabelIndex = Math.ceil(visibleEndTimeMs / intervalMs);\n\n const labels: VisibleLabel[] = [];\n\n for (let i = firstLabelIndex; i <= lastLabelIndex; i++) {\n const timeMs = i * intervalMs;\n if (timeMs < 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport (canvas is positioned at canvasLeft)\n const viewportX = absoluteX - canvasLeft;\n\n if (viewportX >= -50 && viewportX <= canvasWidth + 50) {\n const timeSeconds = timeMs / 1000;\n const text =\n timeSeconds % 1 === 0\n ? `${timeSeconds}s`\n : `${timeSeconds.toFixed(1)}s`;\n\n labels.push({ timeMs, viewportX, text });\n }\n }\n\n return labels;\n }\n\n private renderCanvas(): void {\n const canvas = this.canvasRef.value;\n const container = this.containerRef.value;\n if (!canvas || !container) return;\n\n // Calculate virtualized canvas viewport\n const viewport = this.calculateCanvasViewport();\n this._canvasViewportLeft = viewport.left;\n this._canvasViewportWidth = viewport.width;\n\n const width = viewport.width;\n const height = container.getBoundingClientRect().height;\n\n if (width <= 0 || height <= 0) return;\n\n const dpr = window.devicePixelRatio || 1;\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n ctx.scale(dpr, dpr);\n ctx.clearRect(0, 0, width, height);\n\n const pixelsPerMs = this.pixelsPerMs;\n const canvasLeft = viewport.left;\n\n // Time label ticks - more prominent\n ctx.strokeStyle =\n getComputedStyle(this).getPropertyValue(\"--ef-color-text-muted\").trim() ||\n \"rgb(156, 163, 175)\";\n ctx.lineWidth = 1;\n\n const labelIntervalMs = this.calculateLabelInterval();\n // Fill the canvas viewport with ticks\n const visibleStartTimeMs = Math.max(\n 0,\n canvasLeft / pixelsPerMs - labelIntervalMs,\n );\n const visibleEndTimeMs =\n (canvasLeft + width) / pixelsPerMs + labelIntervalMs;\n\n const firstTickIndex = Math.floor(visibleStartTimeMs / labelIntervalMs);\n const lastTickIndex = Math.ceil(visibleEndTimeMs / labelIntervalMs);\n\n for (let i = firstTickIndex; i <= lastTickIndex; i++) {\n const timeMs = i * labelIntervalMs;\n if (timeMs < 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport\n const canvasX = absoluteX - canvasLeft;\n\n if (canvasX >= -1 && canvasX <= width + 1) {\n ctx.beginPath();\n ctx.moveTo(canvasX, 0);\n ctx.lineTo(canvasX, height * 0.4);\n ctx.stroke();\n }\n }\n\n const frameIntervalMs = 1000 / this.fps;\n const pixelsPerFrame = frameIntervalMs * pixelsPerMs;\n\n if (pixelsPerFrame >= MIN_FRAME_SPACING_PX) {\n // Frame markers should be lighter than background to be visible\n ctx.strokeStyle =\n getComputedStyle(this)\n .getPropertyValue(\"--ef-color-border-subtle\")\n .trim() || \"rgb(107, 114, 128)\";\n ctx.lineWidth = 1;\n\n const firstFrameIndex = Math.floor(visibleStartTimeMs / frameIntervalMs);\n const lastFrameIndex = Math.ceil(visibleEndTimeMs / frameIntervalMs);\n\n for (let i = firstFrameIndex; i <= lastFrameIndex; i++) {\n const timeMs = i * frameIntervalMs;\n if (timeMs < 0) continue;\n\n if (timeMs % labelIntervalMs === 0) continue;\n\n const absoluteX = timeMs * pixelsPerMs;\n // Position relative to canvas viewport\n const canvasX = absoluteX - canvasLeft;\n\n if (canvasX >= -1 && canvasX <= width + 1) {\n ctx.beginPath();\n ctx.moveTo(canvasX, 0);\n ctx.lineTo(canvasX, height * 0.25);\n ctx.stroke();\n }\n }\n }\n }\n\n render() {\n const visibleLabels = this.getVisibleLabels();\n\n const canvasViewportStyles = styleMap({\n left: `${this._canvasViewportLeft}px`,\n width: `${this._canvasViewportWidth}px`,\n });\n\n return html`\n <div ${ref(this.containerRef)} class=\"ruler-container\">\n <div class=\"canvas-viewport\" style=${canvasViewportStyles}>\n <canvas ${ref(this.canvasRef)}></canvas>\n ${visibleLabels.map(\n ({ viewportX, text }) => html`\n <span \n class=\"label\" \n style=\"transform: translateX(${viewportX}px)\"\n >${text}</span>\n `,\n )}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timeline-ruler\": EFTimelineRuler;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit18 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
4
|
import * as lit_html17 from "lit-html";
|
|
5
5
|
|
|
@@ -10,7 +10,7 @@ declare const EFToggleLoop_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFToggleLoop extends EFToggleLoop_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit18.CSSResult[];
|
|
14
14
|
get context(): ControllableInterface | null;
|
|
15
15
|
render(): lit_html17.TemplateResult<1>;
|
|
16
16
|
}
|
package/dist/gui/EFToggleLoop.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
|
|
2
1
|
import { efContext } from "./efContext.js";
|
|
2
|
+
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
|
|
3
3
|
import { TargetOrContextMixin } from "./TargetOrContextMixin.js";
|
|
4
4
|
import { LitElement, css, html } from "lit";
|
|
5
5
|
import { customElement } from "lit/decorators.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit15 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
4
|
import * as lit_html14 from "lit-html";
|
|
5
5
|
|
|
@@ -10,7 +10,7 @@ declare const EFTogglePlay_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFTogglePlay extends EFTogglePlay_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit15.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
get efContext(): ControllableInterface | null;
|
|
16
16
|
connectedCallback(): void;
|
package/dist/gui/EFTogglePlay.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { efContext } from "./efContext.js";
|
|
1
2
|
import { playingContext } from "./playingContext.js";
|
|
2
3
|
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
|
|
3
4
|
import { isEFTemporal } from "../elements/EFTemporal.js";
|
|
4
|
-
import { efContext } from "./efContext.js";
|
|
5
5
|
import { attachContextRoot } from "../attachContextRoot.js";
|
|
6
6
|
import { TargetOrContextMixin } from "./TargetOrContextMixin.js";
|
|
7
7
|
import { consume } from "@lit/context";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PanZoomTransform } from "../elements/EFPanZoom.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit25 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
4
|
import * as lit_html23 from "lit-html";
|
|
5
5
|
|
|
@@ -49,7 +49,7 @@ declare class EFTransformHandles extends LitElement {
|
|
|
49
49
|
* Note: Not a @state() property to avoid re-renders during interaction.
|
|
50
50
|
*/
|
|
51
51
|
private initialBounds;
|
|
52
|
-
static styles:
|
|
52
|
+
static styles: lit25.CSSResult;
|
|
53
53
|
private resizeObserver?;
|
|
54
54
|
/**
|
|
55
55
|
* Single source of truth for zoom scale.
|
|
@@ -145,11 +145,11 @@ let EFTransformHandles = class EFTransformHandles$1 extends LitElement {
|
|
|
145
145
|
}
|
|
146
146
|
.overlay {
|
|
147
147
|
position: absolute;
|
|
148
|
-
border: 2px solid var(--ef-transform-handles-border-color,
|
|
148
|
+
border: 2px solid var(--ef-transform-handles-border-color, var(--ef-color-primary));
|
|
149
149
|
pointer-events: none;
|
|
150
150
|
}
|
|
151
151
|
.overlay.dragging {
|
|
152
|
-
border-color: var(--ef-transform-handles-dragging-border-color,
|
|
152
|
+
border-color: var(--ef-transform-handles-dragging-border-color, var(--ef-color-primary));
|
|
153
153
|
}
|
|
154
154
|
.drag-area {
|
|
155
155
|
position: absolute;
|
|
@@ -165,8 +165,8 @@ let EFTransformHandles = class EFTransformHandles$1 extends LitElement {
|
|
|
165
165
|
position: absolute;
|
|
166
166
|
width: 8px;
|
|
167
167
|
height: 8px;
|
|
168
|
-
background: var(--ef-transform-handles-handle-color,
|
|
169
|
-
border: 1px solid var(--ef-transform-handles-handle-border-color,
|
|
168
|
+
background: var(--ef-transform-handles-handle-color, var(--ef-color-bg-elevated));
|
|
169
|
+
border: 1px solid var(--ef-transform-handles-handle-border-color, var(--ef-color-primary));
|
|
170
170
|
pointer-events: auto;
|
|
171
171
|
/* Only capture pointer events, allow wheel events to pass through */
|
|
172
172
|
touch-action: none;
|
|
@@ -192,8 +192,8 @@ let EFTransformHandles = class EFTransformHandles$1 extends LitElement {
|
|
|
192
192
|
.rotate-handle-circle {
|
|
193
193
|
width: 24px;
|
|
194
194
|
height: 24px;
|
|
195
|
-
background: var(--ef-transform-handles-rotate-handle-color,
|
|
196
|
-
border: 2px solid
|
|
195
|
+
background: var(--ef-transform-handles-rotate-handle-color, var(--ef-color-success));
|
|
196
|
+
border: 2px solid var(--ef-color-bg-elevated);
|
|
197
197
|
border-radius: 50%;
|
|
198
198
|
display: flex;
|
|
199
199
|
align-items: center;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFTransformHandles.js","names":["EFTransformHandles","overlayStyles: Record<string, string>"],"sources":["../../src/gui/EFTransformHandles.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { panZoomTransformContext } from \"./panZoomTransformContext.js\";\nimport type { PanZoomTransform } from \"../elements/EFPanZoom.js\";\nimport {\n type ResizeHandle,\n calculateDragBounds,\n calculateResizeBounds,\n getResizeHandleCursor,\n} from \"./transformCalculations.js\";\nimport { getCornerPoint, getOppositeCorner } from \"./transformUtils.js\";\n\nconst DEFAULT_MIN_SIZE = 10;\n\nexport interface TransformBounds {\n x: number;\n y: number;\n width: number;\n height: number;\n rotation?: number;\n}\n\n/**\n * Interaction mode enumeration.\n * Only one mode can be active at a time (invariant).\n */\ntype InteractionMode = \"idle\" | \"dragging\" | \"resizing\" | \"rotating\";\n\n@customElement(\"ef-transform-handles\")\nexport class EFTransformHandles extends LitElement {\n @property({ type: Object })\n bounds: TransformBounds = { x: 0, y: 0, width: 100, height: 100 };\n\n @property({ type: Number })\n minSize = DEFAULT_MIN_SIZE;\n\n @property({ type: String })\n target?: string;\n\n @consume({ context: panZoomTransformContext, subscribe: true })\n panZoomTransformFromContext?: PanZoomTransform;\n\n @property({ type: Number, attribute: \"canvas-scale\" })\n canvasScale = 1;\n\n @property({ type: Boolean, attribute: \"enable-rotation\" })\n enableRotation = false;\n\n @property({ type: Boolean, attribute: \"enable-resize\" })\n enableResize = true;\n\n @property({ type: Boolean, attribute: \"corners-only\" })\n cornersOnly = false;\n\n @property({ type: Boolean, attribute: \"lock-aspect-ratio\" })\n lockAspectRatio = false;\n\n @property({ type: Boolean, attribute: \"enable-drag\" })\n enableDrag = true;\n\n @property({ type: Number, attribute: \"rotation-step\" })\n rotationStep?: number;\n\n /**\n * Current interaction mode.\n * Invariant: Only one mode active at a time.\n */\n @state()\n interactionMode: InteractionMode = \"idle\";\n\n /**\n * Active resize handle when in \"resizing\" mode.\n * Only valid when interactionMode === \"resizing\".\n */\n private activeResizeHandle: ResizeHandle | null = null;\n\n /**\n * Mouse start position for calculating deltas.\n * Only valid during active interaction.\n */\n private mouseStart: { x: number; y: number } | null = null;\n\n /**\n * Initial bounds at interaction start - NEVER mutated during interaction.\n * All calculations derive from this + mouse deltas.\n * Note: Not a @state() property to avoid re-renders during interaction.\n */\n private initialBounds: TransformBounds | null = null;\n\n static styles = css`\n :host {\n display: block;\n position: absolute;\n pointer-events: none;\n }\n .overlay {\n position: absolute;\n border: 2px solid var(--ef-transform-handles-border-color, #3b82f6);\n pointer-events: none;\n }\n .overlay.dragging {\n border-color: var(--ef-transform-handles-dragging-border-color, #2563eb);\n }\n .drag-area {\n position: absolute;\n inset: 0;\n cursor: move;\n pointer-events: none;\n }\n /* Only enable pointer events when actively dragging */\n .drag-area:active {\n pointer-events: auto;\n }\n .handle {\n position: absolute;\n width: 8px;\n height: 8px;\n background: var(--ef-transform-handles-handle-color, white);\n border: 1px solid var(--ef-transform-handles-handle-border-color, #3b82f6);\n pointer-events: auto;\n /* Only capture pointer events, allow wheel events to pass through */\n touch-action: none;\n }\n .handle.nw { top: -4px; left: -4px; }\n .handle.n { top: -4px; left: 50%; transform: translateX(-50%); }\n .handle.ne { top: -4px; right: -4px; }\n .handle.e { top: 50%; right: -4px; transform: translateY(-50%); }\n .handle.se { bottom: -4px; right: -4px; }\n .handle.s { bottom: -4px; left: 50%; transform: translateX(-50%); }\n .handle.sw { bottom: -4px; left: -4px; }\n .handle.w { top: 50%; left: -4px; transform: translateY(-50%); }\n .rotate-handle {\n position: absolute;\n top: -30px;\n left: 50%;\n transform: translateX(-50%);\n cursor: grab;\n pointer-events: auto;\n /* Only capture pointer events, allow wheel events to pass through */\n touch-action: none;\n }\n .rotate-handle-circle {\n width: 24px;\n height: 24px;\n background: var(--ef-transform-handles-rotate-handle-color, #10b981);\n border: 2px solid white;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .rotate-handle-circle span {\n font-size: 12px;\n color: white;\n }\n `;\n\n private resizeObserver?: ResizeObserver;\n\n /**\n * Single source of truth for zoom scale.\n * Priority: context > prop > 1.0\n */\n private getZoomScale(): number {\n return this.panZoomTransformFromContext?.scale ?? this.canvasScale ?? 1;\n }\n\n /**\n * Convert screen pixel bounds to canvas coordinates.\n */\n private screenToCanvas(bounds: TransformBounds): TransformBounds {\n const scale = this.getZoomScale();\n return {\n x: bounds.x / scale,\n y: bounds.y / scale,\n width: bounds.width / scale,\n height: bounds.height / scale,\n rotation: bounds.rotation,\n };\n }\n\n connectedCallback() {\n super.connectedCallback();\n this.resizeObserver = new ResizeObserver(() => {\n // Dimensions are read directly when needed\n });\n if (this.offsetParent) {\n this.resizeObserver.observe(this.offsetParent);\n }\n // Forward wheel events to parent panzoom so zoom works even when pointer is over handles\n // Wheel events should pass through, but we'll forward them to ensure panzoom receives them\n this.addEventListener(\n \"wheel\",\n (e: WheelEvent) => {\n // Only forward if not actively interacting with handles\n if (this.interactionMode === \"idle\") {\n // Find parent panzoom and forward the event\n const panZoom = this.closest(\"ef-pan-zoom\");\n if (panZoom) {\n // Create a new wheel event and dispatch it on panzoom\n const wheelEvent = new WheelEvent(\"wheel\", {\n bubbles: true,\n cancelable: true,\n clientX: e.clientX,\n clientY: e.clientY,\n deltaX: e.deltaX,\n deltaY: e.deltaY,\n deltaZ: e.deltaZ,\n deltaMode: e.deltaMode,\n ctrlKey: e.ctrlKey,\n metaKey: e.metaKey,\n shiftKey: e.shiftKey,\n altKey: e.altKey,\n });\n panZoom.dispatchEvent(wheelEvent);\n }\n }\n },\n { passive: true },\n );\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.resizeObserver?.disconnect();\n this.cleanup();\n }\n\n /**\n * Transition interaction mode state machine.\n * Ensures only one mode is active at a time (invariant).\n */\n private transitionInteractionMode(\n event:\n | \"mousedown-drag\"\n | \"mousedown-resize\"\n | \"mousedown-rotate\"\n | \"mouseup\",\n ): InteractionMode {\n if (event === \"mouseup\") {\n return \"idle\";\n }\n // Only allow transition from idle\n if (this.interactionMode !== \"idle\") {\n return this.interactionMode;\n }\n switch (event) {\n case \"mousedown-drag\":\n return \"dragging\";\n case \"mousedown-resize\":\n return \"resizing\";\n case \"mousedown-rotate\":\n return \"rotating\";\n default:\n return \"idle\";\n }\n }\n\n private handleMouseDown = (e: MouseEvent) => {\n if (!this.enableDrag) return;\n e.stopPropagation();\n this.interactionMode = this.transitionInteractionMode(\"mousedown-drag\");\n this.mouseStart = { x: e.clientX, y: e.clientY };\n this.initialBounds = { ...this.bounds };\n document.addEventListener(\"mousemove\", this.handleMouseMove);\n document.addEventListener(\"mouseup\", this.handleMouseUp);\n };\n\n private handleResizeMouseDown = (e: MouseEvent, handle: ResizeHandle) => {\n if (!this.enableResize) return;\n e.stopPropagation();\n e.preventDefault();\n this.interactionMode = this.transitionInteractionMode(\"mousedown-resize\");\n this.activeResizeHandle = handle;\n this.mouseStart = { x: e.clientX, y: e.clientY };\n // Store initial bounds as-is (in screen pixels) - we'll convert to canvas when calculating\n this.initialBounds = { ...this.bounds };\n document.addEventListener(\"mousemove\", this.handleMouseMove);\n document.addEventListener(\"mouseup\", this.handleMouseUp);\n };\n\n private handleRotateMouseDown = (e: MouseEvent) => {\n if (!this.enableRotation) return;\n e.stopPropagation();\n this.interactionMode = this.transitionInteractionMode(\"mousedown-rotate\");\n this.mouseStart = { x: e.clientX, y: e.clientY };\n this.initialBounds = { ...this.bounds };\n document.addEventListener(\"mousemove\", this.handleMouseMove);\n document.addEventListener(\"mouseup\", this.handleMouseUp);\n };\n\n /**\n * Dispatch bounds change event (one-way data flow).\n * Parent updates element, then reads element and updates handle bounds prop.\n * We don't modify this.bounds directly - always render from prop.\n */\n private dispatchBoundsChange(bounds: TransformBounds): void {\n this.dispatchEvent(\n new CustomEvent(\"bounds-change\", {\n detail: { bounds },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private handleMouseMove = (e: MouseEvent) => {\n if (!this.mouseStart || !this.initialBounds) return;\n\n // Calculate mouse deltas in viewport coordinates\n const screenDeltaX = e.clientX - this.mouseStart.x;\n const screenDeltaY = e.clientY - this.mouseStart.y;\n\n switch (this.interactionMode) {\n case \"dragging\": {\n const zoomScale = this.getZoomScale();\n const initialCanvas = this.screenToCanvas(this.initialBounds);\n\n const newPosition = calculateDragBounds(\n { x: initialCanvas.x, y: initialCanvas.y },\n screenDeltaX,\n screenDeltaY,\n zoomScale,\n );\n\n this.dispatchBoundsChange({\n ...newPosition,\n width: initialCanvas.width,\n height: initialCanvas.height,\n rotation: this.initialBounds.rotation,\n });\n break;\n }\n\n case \"resizing\": {\n if (!this.activeResizeHandle) return;\n\n const zoomScale = this.getZoomScale();\n const initialCanvas = this.screenToCanvas(this.initialBounds);\n const rotation = this.enableRotation\n ? (this.initialBounds.rotation ?? 0)\n : 0;\n\n // Calculate the fixed corner (opposite to handle being dragged)\n const oppositeCorner = getOppositeCorner(this.activeResizeHandle);\n const rotationRadians = (rotation * Math.PI) / 180;\n const fixedCorner = getCornerPoint(\n initialCanvas.x,\n initialCanvas.y,\n initialCanvas.width,\n initialCanvas.height,\n rotationRadians,\n oppositeCorner.x,\n oppositeCorner.y,\n );\n\n const newCanvasBounds = calculateResizeBounds(\n { width: initialCanvas.width, height: initialCanvas.height },\n { x: initialCanvas.x, y: initialCanvas.y },\n fixedCorner,\n this.activeResizeHandle,\n screenDeltaX,\n screenDeltaY,\n rotation,\n this.minSize / zoomScale,\n zoomScale,\n {\n lockAspectRatio: this.lockAspectRatio || e.shiftKey,\n resizeFromCenter: e.ctrlKey || e.metaKey,\n },\n );\n\n // Preserve rotation\n newCanvasBounds.rotation = this.initialBounds.rotation;\n this.dispatchBoundsChange(newCanvasBounds);\n break;\n }\n\n case \"rotating\": {\n // Calculate center in screen coordinates (bounds are overlay-relative)\n const overlayRect = this.offsetParent?.getBoundingClientRect() ?? {\n left: 0,\n top: 0,\n };\n const centerX =\n overlayRect.left +\n this.initialBounds.x +\n this.initialBounds.width / 2;\n const centerY =\n overlayRect.top +\n this.initialBounds.y +\n this.initialBounds.height / 2;\n\n // Calculate angle from mouse start to current position\n const startAngle =\n Math.atan2(this.mouseStart.y - centerY, this.mouseStart.x - centerX) *\n (180 / Math.PI) +\n 90;\n const currentAngle =\n Math.atan2(e.clientY - centerY, e.clientX - centerX) *\n (180 / Math.PI) +\n 90;\n\n // Normalize angle difference to [-180, 180] to avoid wrapping issues\n let deltaAngle = currentAngle - startAngle;\n while (deltaAngle > 180) deltaAngle -= 360;\n while (deltaAngle < -180) deltaAngle += 360;\n\n let newRotation = (this.initialBounds.rotation ?? 0) + deltaAngle;\n if (this.rotationStep !== undefined && this.rotationStep > 0) {\n newRotation =\n Math.round(newRotation / this.rotationStep) * this.rotationStep;\n }\n\n this.dispatchEvent(\n new CustomEvent(\"rotation-change\", {\n detail: { rotation: newRotation },\n bubbles: true,\n composed: true,\n }),\n );\n break;\n }\n\n case \"idle\":\n // No action needed\n break;\n }\n };\n\n private handleMouseUp = () => {\n this.cleanup();\n };\n\n private cleanup() {\n this.interactionMode = this.transitionInteractionMode(\"mouseup\");\n this.activeResizeHandle = null;\n this.mouseStart = null;\n this.initialBounds = null;\n document.removeEventListener(\"mousemove\", this.handleMouseMove);\n document.removeEventListener(\"mouseup\", this.handleMouseUp);\n }\n\n render() {\n // Always render from bounds prop (one-way data flow)\n // During interaction: dispatch events, parent updates element, parent updates handle bounds prop\n const currentBounds = this.bounds;\n const rotation = this.enableRotation ? (currentBounds.rotation ?? 0) : 0;\n\n const overlayStyles: Record<string, string> = {\n left: `${currentBounds.x}px`,\n top: `${currentBounds.y}px`,\n width: `${currentBounds.width}px`,\n height: `${currentBounds.height}px`,\n };\n\n if (this.enableRotation && rotation !== 0) {\n overlayStyles.transform = `rotate(${rotation}deg)`;\n overlayStyles.transformOrigin = \"center\";\n }\n\n const allHandles: ResizeHandle[] = [\n \"nw\",\n \"n\",\n \"ne\",\n \"e\",\n \"se\",\n \"s\",\n \"sw\",\n \"w\",\n ];\n const cornerHandles: ResizeHandle[] = [\"nw\", \"ne\", \"se\", \"sw\"];\n const handles = this.cornersOnly ? cornerHandles : allHandles;\n\n return html`\n <div\n class=\"overlay ${this.interactionMode === \"dragging\" ? \"dragging\" : \"\"}\"\n style=${styleMap(overlayStyles)}\n >\n ${\n this.enableDrag\n ? html`\n <div\n class=\"drag-area\"\n @mousedown=${this.handleMouseDown}\n ></div>\n `\n : \"\"\n }\n ${\n this.enableResize\n ? handles.map((handle) => {\n const rotation = this.enableRotation\n ? (currentBounds.rotation ?? 0)\n : 0;\n const cursor = getResizeHandleCursor(handle, rotation);\n return html`\n <div\n class=\"handle ${handle}\"\n style=${styleMap({ cursor })}\n @mousedown=${(e: MouseEvent) => this.handleResizeMouseDown(e, handle)}\n ></div>\n `;\n })\n : \"\"\n }\n ${\n this.enableRotation\n ? html`\n <div class=\"rotate-handle\" @mousedown=${this.handleRotateMouseDown}>\n <div class=\"rotate-handle-circle\">\n <span>↻</span>\n </div>\n </div>\n `\n : \"\"\n }\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-transform-handles\": EFTransformHandles;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFTransformHandles.js","names":["EFTransformHandles","overlayStyles: Record<string, string>"],"sources":["../../src/gui/EFTransformHandles.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, LitElement } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { panZoomTransformContext } from \"./panZoomTransformContext.js\";\nimport type { PanZoomTransform } from \"../elements/EFPanZoom.js\";\nimport {\n type ResizeHandle,\n calculateDragBounds,\n calculateResizeBounds,\n getResizeHandleCursor,\n} from \"./transformCalculations.js\";\nimport { getCornerPoint, getOppositeCorner } from \"./transformUtils.js\";\n\nconst DEFAULT_MIN_SIZE = 10;\n\nexport interface TransformBounds {\n x: number;\n y: number;\n width: number;\n height: number;\n rotation?: number;\n}\n\n/**\n * Interaction mode enumeration.\n * Only one mode can be active at a time (invariant).\n */\ntype InteractionMode = \"idle\" | \"dragging\" | \"resizing\" | \"rotating\";\n\n@customElement(\"ef-transform-handles\")\nexport class EFTransformHandles extends LitElement {\n @property({ type: Object })\n bounds: TransformBounds = { x: 0, y: 0, width: 100, height: 100 };\n\n @property({ type: Number })\n minSize = DEFAULT_MIN_SIZE;\n\n @property({ type: String })\n target?: string;\n\n @consume({ context: panZoomTransformContext, subscribe: true })\n panZoomTransformFromContext?: PanZoomTransform;\n\n @property({ type: Number, attribute: \"canvas-scale\" })\n canvasScale = 1;\n\n @property({ type: Boolean, attribute: \"enable-rotation\" })\n enableRotation = false;\n\n @property({ type: Boolean, attribute: \"enable-resize\" })\n enableResize = true;\n\n @property({ type: Boolean, attribute: \"corners-only\" })\n cornersOnly = false;\n\n @property({ type: Boolean, attribute: \"lock-aspect-ratio\" })\n lockAspectRatio = false;\n\n @property({ type: Boolean, attribute: \"enable-drag\" })\n enableDrag = true;\n\n @property({ type: Number, attribute: \"rotation-step\" })\n rotationStep?: number;\n\n /**\n * Current interaction mode.\n * Invariant: Only one mode active at a time.\n */\n @state()\n interactionMode: InteractionMode = \"idle\";\n\n /**\n * Active resize handle when in \"resizing\" mode.\n * Only valid when interactionMode === \"resizing\".\n */\n private activeResizeHandle: ResizeHandle | null = null;\n\n /**\n * Mouse start position for calculating deltas.\n * Only valid during active interaction.\n */\n private mouseStart: { x: number; y: number } | null = null;\n\n /**\n * Initial bounds at interaction start - NEVER mutated during interaction.\n * All calculations derive from this + mouse deltas.\n * Note: Not a @state() property to avoid re-renders during interaction.\n */\n private initialBounds: TransformBounds | null = null;\n\n static styles = css`\n :host {\n display: block;\n position: absolute;\n pointer-events: none;\n }\n .overlay {\n position: absolute;\n border: 2px solid var(--ef-transform-handles-border-color, var(--ef-color-primary));\n pointer-events: none;\n }\n .overlay.dragging {\n border-color: var(--ef-transform-handles-dragging-border-color, var(--ef-color-primary));\n }\n .drag-area {\n position: absolute;\n inset: 0;\n cursor: move;\n pointer-events: none;\n }\n /* Only enable pointer events when actively dragging */\n .drag-area:active {\n pointer-events: auto;\n }\n .handle {\n position: absolute;\n width: 8px;\n height: 8px;\n background: var(--ef-transform-handles-handle-color, var(--ef-color-bg-elevated));\n border: 1px solid var(--ef-transform-handles-handle-border-color, var(--ef-color-primary));\n pointer-events: auto;\n /* Only capture pointer events, allow wheel events to pass through */\n touch-action: none;\n }\n .handle.nw { top: -4px; left: -4px; }\n .handle.n { top: -4px; left: 50%; transform: translateX(-50%); }\n .handle.ne { top: -4px; right: -4px; }\n .handle.e { top: 50%; right: -4px; transform: translateY(-50%); }\n .handle.se { bottom: -4px; right: -4px; }\n .handle.s { bottom: -4px; left: 50%; transform: translateX(-50%); }\n .handle.sw { bottom: -4px; left: -4px; }\n .handle.w { top: 50%; left: -4px; transform: translateY(-50%); }\n .rotate-handle {\n position: absolute;\n top: -30px;\n left: 50%;\n transform: translateX(-50%);\n cursor: grab;\n pointer-events: auto;\n /* Only capture pointer events, allow wheel events to pass through */\n touch-action: none;\n }\n .rotate-handle-circle {\n width: 24px;\n height: 24px;\n background: var(--ef-transform-handles-rotate-handle-color, var(--ef-color-success));\n border: 2px solid var(--ef-color-bg-elevated);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .rotate-handle-circle span {\n font-size: 12px;\n color: white;\n }\n `;\n\n private resizeObserver?: ResizeObserver;\n\n /**\n * Single source of truth for zoom scale.\n * Priority: context > prop > 1.0\n */\n private getZoomScale(): number {\n return this.panZoomTransformFromContext?.scale ?? this.canvasScale ?? 1;\n }\n\n /**\n * Convert screen pixel bounds to canvas coordinates.\n */\n private screenToCanvas(bounds: TransformBounds): TransformBounds {\n const scale = this.getZoomScale();\n return {\n x: bounds.x / scale,\n y: bounds.y / scale,\n width: bounds.width / scale,\n height: bounds.height / scale,\n rotation: bounds.rotation,\n };\n }\n\n connectedCallback() {\n super.connectedCallback();\n this.resizeObserver = new ResizeObserver(() => {\n // Dimensions are read directly when needed\n });\n if (this.offsetParent) {\n this.resizeObserver.observe(this.offsetParent);\n }\n // Forward wheel events to parent panzoom so zoom works even when pointer is over handles\n // Wheel events should pass through, but we'll forward them to ensure panzoom receives them\n this.addEventListener(\n \"wheel\",\n (e: WheelEvent) => {\n // Only forward if not actively interacting with handles\n if (this.interactionMode === \"idle\") {\n // Find parent panzoom and forward the event\n const panZoom = this.closest(\"ef-pan-zoom\");\n if (panZoom) {\n // Create a new wheel event and dispatch it on panzoom\n const wheelEvent = new WheelEvent(\"wheel\", {\n bubbles: true,\n cancelable: true,\n clientX: e.clientX,\n clientY: e.clientY,\n deltaX: e.deltaX,\n deltaY: e.deltaY,\n deltaZ: e.deltaZ,\n deltaMode: e.deltaMode,\n ctrlKey: e.ctrlKey,\n metaKey: e.metaKey,\n shiftKey: e.shiftKey,\n altKey: e.altKey,\n });\n panZoom.dispatchEvent(wheelEvent);\n }\n }\n },\n { passive: true },\n );\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.resizeObserver?.disconnect();\n this.cleanup();\n }\n\n /**\n * Transition interaction mode state machine.\n * Ensures only one mode is active at a time (invariant).\n */\n private transitionInteractionMode(\n event:\n | \"mousedown-drag\"\n | \"mousedown-resize\"\n | \"mousedown-rotate\"\n | \"mouseup\",\n ): InteractionMode {\n if (event === \"mouseup\") {\n return \"idle\";\n }\n // Only allow transition from idle\n if (this.interactionMode !== \"idle\") {\n return this.interactionMode;\n }\n switch (event) {\n case \"mousedown-drag\":\n return \"dragging\";\n case \"mousedown-resize\":\n return \"resizing\";\n case \"mousedown-rotate\":\n return \"rotating\";\n default:\n return \"idle\";\n }\n }\n\n private handleMouseDown = (e: MouseEvent) => {\n if (!this.enableDrag) return;\n e.stopPropagation();\n this.interactionMode = this.transitionInteractionMode(\"mousedown-drag\");\n this.mouseStart = { x: e.clientX, y: e.clientY };\n this.initialBounds = { ...this.bounds };\n document.addEventListener(\"mousemove\", this.handleMouseMove);\n document.addEventListener(\"mouseup\", this.handleMouseUp);\n };\n\n private handleResizeMouseDown = (e: MouseEvent, handle: ResizeHandle) => {\n if (!this.enableResize) return;\n e.stopPropagation();\n e.preventDefault();\n this.interactionMode = this.transitionInteractionMode(\"mousedown-resize\");\n this.activeResizeHandle = handle;\n this.mouseStart = { x: e.clientX, y: e.clientY };\n // Store initial bounds as-is (in screen pixels) - we'll convert to canvas when calculating\n this.initialBounds = { ...this.bounds };\n document.addEventListener(\"mousemove\", this.handleMouseMove);\n document.addEventListener(\"mouseup\", this.handleMouseUp);\n };\n\n private handleRotateMouseDown = (e: MouseEvent) => {\n if (!this.enableRotation) return;\n e.stopPropagation();\n this.interactionMode = this.transitionInteractionMode(\"mousedown-rotate\");\n this.mouseStart = { x: e.clientX, y: e.clientY };\n this.initialBounds = { ...this.bounds };\n document.addEventListener(\"mousemove\", this.handleMouseMove);\n document.addEventListener(\"mouseup\", this.handleMouseUp);\n };\n\n /**\n * Dispatch bounds change event (one-way data flow).\n * Parent updates element, then reads element and updates handle bounds prop.\n * We don't modify this.bounds directly - always render from prop.\n */\n private dispatchBoundsChange(bounds: TransformBounds): void {\n this.dispatchEvent(\n new CustomEvent(\"bounds-change\", {\n detail: { bounds },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private handleMouseMove = (e: MouseEvent) => {\n if (!this.mouseStart || !this.initialBounds) return;\n\n // Calculate mouse deltas in viewport coordinates\n const screenDeltaX = e.clientX - this.mouseStart.x;\n const screenDeltaY = e.clientY - this.mouseStart.y;\n\n switch (this.interactionMode) {\n case \"dragging\": {\n const zoomScale = this.getZoomScale();\n const initialCanvas = this.screenToCanvas(this.initialBounds);\n\n const newPosition = calculateDragBounds(\n { x: initialCanvas.x, y: initialCanvas.y },\n screenDeltaX,\n screenDeltaY,\n zoomScale,\n );\n\n this.dispatchBoundsChange({\n ...newPosition,\n width: initialCanvas.width,\n height: initialCanvas.height,\n rotation: this.initialBounds.rotation,\n });\n break;\n }\n\n case \"resizing\": {\n if (!this.activeResizeHandle) return;\n\n const zoomScale = this.getZoomScale();\n const initialCanvas = this.screenToCanvas(this.initialBounds);\n const rotation = this.enableRotation\n ? (this.initialBounds.rotation ?? 0)\n : 0;\n\n // Calculate the fixed corner (opposite to handle being dragged)\n const oppositeCorner = getOppositeCorner(this.activeResizeHandle);\n const rotationRadians = (rotation * Math.PI) / 180;\n const fixedCorner = getCornerPoint(\n initialCanvas.x,\n initialCanvas.y,\n initialCanvas.width,\n initialCanvas.height,\n rotationRadians,\n oppositeCorner.x,\n oppositeCorner.y,\n );\n\n const newCanvasBounds = calculateResizeBounds(\n { width: initialCanvas.width, height: initialCanvas.height },\n { x: initialCanvas.x, y: initialCanvas.y },\n fixedCorner,\n this.activeResizeHandle,\n screenDeltaX,\n screenDeltaY,\n rotation,\n this.minSize / zoomScale,\n zoomScale,\n {\n lockAspectRatio: this.lockAspectRatio || e.shiftKey,\n resizeFromCenter: e.ctrlKey || e.metaKey,\n },\n );\n\n // Preserve rotation\n newCanvasBounds.rotation = this.initialBounds.rotation;\n this.dispatchBoundsChange(newCanvasBounds);\n break;\n }\n\n case \"rotating\": {\n // Calculate center in screen coordinates (bounds are overlay-relative)\n const overlayRect = this.offsetParent?.getBoundingClientRect() ?? {\n left: 0,\n top: 0,\n };\n const centerX =\n overlayRect.left +\n this.initialBounds.x +\n this.initialBounds.width / 2;\n const centerY =\n overlayRect.top +\n this.initialBounds.y +\n this.initialBounds.height / 2;\n\n // Calculate angle from mouse start to current position\n const startAngle =\n Math.atan2(this.mouseStart.y - centerY, this.mouseStart.x - centerX) *\n (180 / Math.PI) +\n 90;\n const currentAngle =\n Math.atan2(e.clientY - centerY, e.clientX - centerX) *\n (180 / Math.PI) +\n 90;\n\n // Normalize angle difference to [-180, 180] to avoid wrapping issues\n let deltaAngle = currentAngle - startAngle;\n while (deltaAngle > 180) deltaAngle -= 360;\n while (deltaAngle < -180) deltaAngle += 360;\n\n let newRotation = (this.initialBounds.rotation ?? 0) + deltaAngle;\n if (this.rotationStep !== undefined && this.rotationStep > 0) {\n newRotation =\n Math.round(newRotation / this.rotationStep) * this.rotationStep;\n }\n\n this.dispatchEvent(\n new CustomEvent(\"rotation-change\", {\n detail: { rotation: newRotation },\n bubbles: true,\n composed: true,\n }),\n );\n break;\n }\n\n case \"idle\":\n // No action needed\n break;\n }\n };\n\n private handleMouseUp = () => {\n this.cleanup();\n };\n\n private cleanup() {\n this.interactionMode = this.transitionInteractionMode(\"mouseup\");\n this.activeResizeHandle = null;\n this.mouseStart = null;\n this.initialBounds = null;\n document.removeEventListener(\"mousemove\", this.handleMouseMove);\n document.removeEventListener(\"mouseup\", this.handleMouseUp);\n }\n\n render() {\n // Always render from bounds prop (one-way data flow)\n // During interaction: dispatch events, parent updates element, parent updates handle bounds prop\n const currentBounds = this.bounds;\n const rotation = this.enableRotation ? (currentBounds.rotation ?? 0) : 0;\n\n const overlayStyles: Record<string, string> = {\n left: `${currentBounds.x}px`,\n top: `${currentBounds.y}px`,\n width: `${currentBounds.width}px`,\n height: `${currentBounds.height}px`,\n };\n\n if (this.enableRotation && rotation !== 0) {\n overlayStyles.transform = `rotate(${rotation}deg)`;\n overlayStyles.transformOrigin = \"center\";\n }\n\n const allHandles: ResizeHandle[] = [\n \"nw\",\n \"n\",\n \"ne\",\n \"e\",\n \"se\",\n \"s\",\n \"sw\",\n \"w\",\n ];\n const cornerHandles: ResizeHandle[] = [\"nw\", \"ne\", \"se\", \"sw\"];\n const handles = this.cornersOnly ? cornerHandles : allHandles;\n\n return html`\n <div\n class=\"overlay ${this.interactionMode === \"dragging\" ? \"dragging\" : \"\"}\"\n style=${styleMap(overlayStyles)}\n >\n ${\n this.enableDrag\n ? html`\n <div\n class=\"drag-area\"\n @mousedown=${this.handleMouseDown}\n ></div>\n `\n : \"\"\n }\n ${\n this.enableResize\n ? handles.map((handle) => {\n const rotation = this.enableRotation\n ? (currentBounds.rotation ?? 0)\n : 0;\n const cursor = getResizeHandleCursor(handle, rotation);\n return html`\n <div\n class=\"handle ${handle}\"\n style=${styleMap({ cursor })}\n @mousedown=${(e: MouseEvent) => this.handleResizeMouseDown(e, handle)}\n ></div>\n `;\n })\n : \"\"\n }\n ${\n this.enableRotation\n ? html`\n <div class=\"rotate-handle\" @mousedown=${this.handleRotateMouseDown}>\n <div class=\"rotate-handle-circle\">\n <span>↻</span>\n </div>\n </div>\n `\n : \"\"\n }\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-transform-handles\": EFTransformHandles;\n }\n}\n"],"mappings":"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"}
|