@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":"EFTemporal.js","names":["isTimegroupCalculatingDurationFn:\n | ((timegroup: EFTimegroup | undefined) => boolean)\n | null","fallbackFn: (timegroup: EFTimegroup | undefined) => boolean","assignedElements: Element[]","temporalCache: Map<Element, TemporalMixinInterface[]>","host: EFTimegroup","temporal: TemporalMixinInterface & LitElement","#lastKnownTimeMs","taskObj: { run(): void | Promise<void>; taskComplete: Promise<void> }","#frameTaskPromise","#parentTimegroup","#ownCurrentTimeController","#rootTimegroupLocked","#loop","#parentTemporal","#offsetMs","#currentTimeMs"],"sources":["../../src/elements/EFTemporal.ts"],"sourcesContent":["import { consume, createContext } from \"@lit/context\";\nimport type { LitElement, ReactiveController } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { PlaybackController } from \"../gui/PlaybackController.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport type { EFTimegroup } from \"./EFTimegroup.js\";\n// Lazy import to break circular dependency: EFTemporal -> EFTimegroup -> EFMedia -> EFTemporal\n// isTimegroupCalculatingDuration is only used at runtime in a getter, so we can import it lazily\n// Use a module-level variable that gets set when EFTimegroup module loads\nlet isTimegroupCalculatingDurationFn:\n | ((timegroup: EFTimegroup | undefined) => boolean)\n | null = null;\n\n// This function will be called by EFTimegroup when it loads to register the function\nexport const registerIsTimegroupCalculatingDuration = (\n fn: (timegroup: EFTimegroup | undefined) => boolean,\n) => {\n isTimegroupCalculatingDurationFn = fn;\n};\n\nconst getIsTimegroupCalculatingDuration = (): ((\n timegroup: EFTimegroup | undefined,\n) => boolean) => {\n if (isTimegroupCalculatingDurationFn) {\n return isTimegroupCalculatingDurationFn as (\n timegroup: EFTimegroup | undefined,\n ) => boolean;\n }\n\n // If not registered yet, try to import synchronously (only works if module is already loaded)\n // This is a fallback for cases where EFTimegroup hasn't called registerIsTimegroupCalculatingDuration\n // In practice, EFTimegroup will call registerIsTimegroupCalculatingDuration when it loads\n let fallbackFn: (timegroup: EFTimegroup | undefined) => boolean = () => false;\n try {\n // Access the function via a global or try to get it from the module cache\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const efTimegroupModule = (globalThis as any).__EFTimegroupModule;\n if (efTimegroupModule?.isTimegroupCalculatingDuration) {\n fallbackFn = efTimegroupModule.isTimegroupCalculatingDuration;\n }\n } catch {\n // Use default fallback\n }\n isTimegroupCalculatingDurationFn = fallbackFn;\n return fallbackFn;\n};\n\nexport const timegroupContext = createContext<EFTimegroup>(\n Symbol(\"timeGroupContext\"),\n);\n\n// ============================================================================\n// Core Concept 1: Temporal Role\n// ============================================================================\n// A temporal element is either a root (controls its own playback) or a child\n// (delegates playback to its root timegroup). This is the fundamental invariant.\n// ============================================================================\n\ntype TemporalRole = \"root\" | \"child\";\n\nfunction determineTemporalRole(\n parentTimegroup: EFTimegroup | undefined,\n): TemporalRole {\n return parentTimegroup === undefined ? \"root\" : \"child\";\n}\n\n// ============================================================================\n// Core Concept 2: Duration Source\n// ============================================================================\n// Duration comes from one of three sources: intrinsic (media-based),\n// explicit (attribute), or inherited (from parent). This determines the base\n// duration before any modifications.\n// ============================================================================\n\ntype DurationSource = \"intrinsic\" | \"explicit\" | \"inherited\";\n\ninterface DurationSourceResult {\n source: DurationSource;\n baseDurationMs: number;\n}\n\nfunction determineDurationSource(\n intrinsicDurationMs: number | undefined,\n explicitDurationMs: number | undefined,\n parentDurationMs: number | undefined,\n): DurationSourceResult {\n if (intrinsicDurationMs !== undefined) {\n return { source: \"intrinsic\", baseDurationMs: intrinsicDurationMs };\n }\n if (explicitDurationMs !== undefined) {\n return { source: \"explicit\", baseDurationMs: explicitDurationMs };\n }\n if (parentDurationMs !== undefined) {\n return { source: \"inherited\", baseDurationMs: parentDurationMs };\n }\n return { source: \"inherited\", baseDurationMs: 0 };\n}\n\n// ============================================================================\n// Core Concept 3: Duration Modification Strategy\n// ============================================================================\n// Duration can be modified by trimming (removing from edges) or source\n// manipulation (selecting a portion of the source). These are mutually\n// exclusive strategies.\n// ============================================================================\n\ntype DurationModificationStrategy = \"none\" | \"trimming\" | \"source-manipulation\";\n\ninterface DurationModificationState {\n strategy: DurationModificationStrategy;\n trimStartMs: number | undefined;\n trimEndMs: number | undefined;\n sourceInMs: number | undefined;\n sourceOutMs: number | undefined;\n}\n\nfunction determineDurationModificationStrategy(\n trimStartMs: number | undefined,\n trimEndMs: number | undefined,\n sourceInMs: number | undefined,\n sourceOutMs: number | undefined,\n): DurationModificationState {\n if (trimStartMs !== undefined || trimEndMs !== undefined) {\n return {\n strategy: \"trimming\",\n trimStartMs,\n trimEndMs,\n sourceInMs: undefined,\n sourceOutMs: undefined,\n };\n }\n if (sourceInMs !== undefined || sourceOutMs !== undefined) {\n return {\n strategy: \"source-manipulation\",\n trimStartMs: undefined,\n trimEndMs: undefined,\n sourceInMs,\n sourceOutMs,\n };\n }\n return {\n strategy: \"none\",\n trimStartMs: undefined,\n trimEndMs: undefined,\n sourceInMs: undefined,\n sourceOutMs: undefined,\n };\n}\n\nfunction evaluateModifiedDuration(\n baseDurationMs: number,\n modification: DurationModificationState,\n): number {\n if (baseDurationMs === 0) {\n return 0;\n }\n\n switch (modification.strategy) {\n case \"trimming\": {\n const trimmedDurationMs =\n baseDurationMs -\n (modification.trimStartMs ?? 0) -\n (modification.trimEndMs ?? 0);\n return Math.max(0, trimmedDurationMs);\n }\n case \"source-manipulation\": {\n const sourceInMs = modification.sourceInMs ?? 0;\n const sourceOutMs = modification.sourceOutMs ?? baseDurationMs;\n if (sourceInMs >= sourceOutMs) {\n return 0;\n }\n return sourceOutMs - sourceInMs;\n }\n case \"none\":\n return baseDurationMs;\n }\n}\n\n// ============================================================================\n// Core Concept 4: Start Time Calculation Strategy\n// ============================================================================\n// Start time is calculated differently based on parent timegroup mode:\n// - Sequence mode: based on previous sibling\n// - Other modes: based on parent start + offset\n// ============================================================================\n\ntype StartTimeStrategy = \"sequence\" | \"offset\";\n\nfunction determineStartTimeStrategy(\n parentTimegroup: EFTimegroup | undefined,\n): StartTimeStrategy {\n if (!parentTimegroup) {\n return \"offset\";\n }\n return parentTimegroup.mode === \"sequence\" ? \"sequence\" : \"offset\";\n}\n\nfunction evaluateStartTimeForSequence(\n _element: TemporalMixinInterface & HTMLElement,\n parentTimegroup: EFTimegroup,\n siblingTemporals: TemporalMixinInterface[],\n ownIndex: number,\n): number {\n if (ownIndex === 0) {\n return parentTimegroup.startTimeMs;\n }\n const previous = siblingTemporals[ownIndex - 1];\n if (!previous) {\n throw new Error(\"Previous temporal element not found\");\n }\n return previous.startTimeMs + previous.durationMs - parentTimegroup.overlapMs;\n}\n\nfunction evaluateStartTimeForOffset(\n parentTimegroup: EFTimegroup,\n offsetMs: number,\n): number {\n return parentTimegroup.startTimeMs + offsetMs;\n}\n\nfunction evaluateStartTime(\n element: TemporalMixinInterface & HTMLElement,\n parentTimegroup: EFTimegroup | undefined,\n offsetMs: number,\n getSiblingTemporals: (parent: EFTimegroup) => TemporalMixinInterface[],\n): number {\n if (!parentTimegroup) {\n return 0;\n }\n\n const strategy = determineStartTimeStrategy(parentTimegroup);\n switch (strategy) {\n case \"sequence\": {\n const siblingTemporals = getSiblingTemporals(parentTimegroup);\n const ownIndex = siblingTemporals.indexOf(element);\n if (ownIndex === -1) {\n return 0;\n }\n return evaluateStartTimeForSequence(\n element,\n parentTimegroup,\n siblingTemporals,\n ownIndex,\n );\n }\n case \"offset\":\n return evaluateStartTimeForOffset(parentTimegroup, offsetMs);\n }\n}\n\n// ============================================================================\n// Core Concept 5: Current Time Source\n// ============================================================================\n// Current time comes from one of three sources: playback controller (root\n// elements), root timegroup (child elements), or local storage (fallback).\n// ============================================================================\n\ntype CurrentTimeSource = \"playback-controller\" | \"root-timegroup\" | \"local\";\n\ninterface CurrentTimeSourceResult {\n source: CurrentTimeSource;\n timeMs: number;\n}\n\nfunction determineCurrentTimeSource(\n playbackController: PlaybackController | undefined,\n rootTimegroup: EFTimegroup | undefined,\n isRootTimegroup: boolean,\n localTimeMs: number,\n startTimeMs: number,\n durationMs: number,\n): CurrentTimeSourceResult {\n if (playbackController) {\n const timeMs = Math.min(\n Math.max(0, playbackController.currentTimeMs),\n durationMs,\n );\n return { source: \"playback-controller\", timeMs };\n }\n\n if (rootTimegroup && !isRootTimegroup) {\n const timeMs = Math.min(\n Math.max(0, rootTimegroup.currentTimeMs - startTimeMs),\n durationMs,\n );\n return { source: \"root-timegroup\", timeMs };\n }\n\n const timeMs = Math.min(Math.max(0, localTimeMs), durationMs);\n return { source: \"local\", timeMs };\n}\n\nexport declare class TemporalMixinInterface {\n playbackController?: PlaybackController;\n playing: boolean;\n loop: boolean;\n play(): void;\n pause(): void;\n\n get hasOwnDuration(): boolean;\n /**\n * Whether the element has a duration set as an attribute.\n */\n get hasExplicitDuration(): boolean;\n\n get sourceStartMs(): number;\n\n /**\n * Used to trim the start of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `trimstart=\"10s\"` is equivalent to `trimstart=\"10000ms\"`.\n *\n * @domAttribute \"trimstart\"\n */\n get trimStartMs(): number | undefined;\n\n /**\n * Used to trim the end of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `trimend=\"10s\"` is equivalent to `trimend=\"10000ms\"`.\n *\n * @domAttribute \"trimend\"\n */\n get trimEndMs(): number;\n\n set trimStartMs(value: number | undefined);\n set trimEndMs(value: number | undefined);\n set trimstart(value: string | undefined);\n set trimend(value: string | undefined);\n\n /**\n * The source in time of the element.\n *\n * This is an amount of time to trim off the beginning of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `sourcein=\"10s\"` is equivalent to `sourcein=\"10000ms\"`.\n *\n * If the sourcein time is greater than the duration of the media, the media\n * will not be played.\n *\n * If the media is 20 seconds long, and the `sourcein` value is set to `10s`, the\n * media will play for 10 seconds, starting at the 10 second mark.\n *\n * Can be used in conjunction with `sourceout` to create a trimmed media.\n *\n * @domAttribute \"sourcein\"\n */\n get sourceInMs(): number | undefined;\n\n /**\n * The source out time of the element.\n *\n * This is the point in time in the media that will be treated as the end of\n * the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `sourceout=\"10s\"` is equivalent to `sourceout=\"10000ms\"`.\n *\n * If the sourceout time is greater than the duration of the media, the media\n * will play until the end of the media.\n *\n * If the media is 20 seconds long, and the `sourceout` value is set to `10s`,\n * the media will play for 10 seconds, starting at zero seconds and ending at\n * the 10 second mark.\n *\n * Can be used in conjunction with `sourcein` to create a trimmed media.\n *\n * @domAttribute \"sourceout\"\n */\n get sourceOutMs(): number | undefined;\n\n set sourceInMs(value: number | undefined);\n set sourceOutMs(value: number | undefined);\n set sourcein(value: string | undefined);\n set sourceout(value: string | undefined);\n\n /**\n * @domAttribute \"duration\"\n */\n get durationMs(): number;\n\n get explicitDurationMs(): number | undefined;\n\n get intrinsicDurationMs(): number | undefined;\n\n /**\n * The start time of the element within its root timegroup in milliseconds.\n *\n * This is an absolute time according to the highest scoped timegroup the media element is contained within.\n *\n * The calculated value will depend on the mode of the timegroup and the offset of the media element.\n *\n * If the parent time group is in `sequence` mode, the start time will be the\n * start time of the previous sibling element plus the previous sibling's duration\n * minus the overlap of the previous sibling and the current sibling.\n *\n * If the parent time group is in `contain` or `fixed` mode, the start time will be\n * the start time of the parent time group plus the offset of the media element.\n */\n get startTimeMs(): number;\n /**\n * The end time of the element within its root timegroup in milliseconds.\n *\n * This is an absolute time according to the highest scoped timegroup the media\n * element is contained within. Computed by adding the media's duration to its\n * start time.\n *\n * If the media element has been trimmed, its end time will be calculated according it\n * its trimmed duration, not its original duration.\n */\n get endTimeMs(): number;\n /**\n * The start time of the element within its parent timegroup in milliseconds.\n *\n * This is a relative time according to the closest timegroup the media element\n * is contained within. Unless the media element has been given any kind of specific offset\n * it is common for this time to be zero.\n */\n get startTimeWithinParentMs(): number;\n\n /**\n * The current time of the element in milliseconds.\n *\n * This is a relative time according to the closest timegroup the media element\n * is contained within.\n *\n * This is suitable for determining the percentage of the media that has been\n * played.\n */\n get ownCurrentTimeMs(): number;\n\n /**\n * Element's current time for progress calculation.\n * For timegroups: their timeline currentTimeMs\n * For other temporal elements: their ownCurrentTimeMs\n */\n get currentTimeMs(): number;\n set currentTimeMs(value: number);\n /**\n * The current time of the element in milliseconds, adjusted for trimming.\n *\n * This is suitable for mapping to internal media time codes for audio/video\n * elements.\n *\n * For example, if the media has a `sourcein` value of 10s, when `ownCurrentTimeMs` is 0s,\n * `currentSourceTimeMs` will be 10s.\n *\n * sourcein=10s sourceout=10s\n * / / /\n * |--------|=================|---------|\n * ^\n * |_\n * currentSourceTimeMs === 10s\n * |_\n * ownCurrentTimeMs === 0s\n */\n get currentSourceTimeMs(): number;\n\n set duration(value: string);\n get duration(): string;\n\n /**\n * The offset of the element within its parent timegroup in milliseconds.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `offset=\"10s\"` is equivalent to `offset=\"10000ms\"`.\n *\n * This can be used to create a negative or positive offset for the start time of the media.\n *\n * This will change the start time of the media relative to it's otherwise normal start time.\n *\n * The duration of the element, or it's parent, or the start and end time of it's temporal siblings will not\n * be affected by this offset.\n *\n * @domAttribute \"offset\"\n */\n set offset(value: string);\n get offset(): string;\n\n /**\n * A convenience property for getting the nearest containing timegroup of the media element.\n */\n parentTimegroup?: EFTimegroup;\n\n /**\n * A convenience property for getting the root timegroup of the media element.\n */\n rootTimegroup?: EFTimegroup;\n\n /**\n * Frame task interface. Can be a Lit Task or a simple object with run() and taskComplete.\n * @deprecated Prefer using FrameRenderable interface via FrameController.\n */\n frameTask: { run(): void | Promise<void>; taskComplete: Promise<unknown> };\n\n didBecomeRoot(): void;\n didBecomeChild(): void;\n\n updateComplete: Promise<boolean>;\n}\n\nexport const isEFTemporal = (obj: any): obj is TemporalMixinInterface =>\n obj[EF_TEMPORAL];\n\nconst EF_TEMPORAL = Symbol(\"EF_TEMPORAL\");\n\nexport const deepGetTemporalElements = (\n element: Element,\n temporals: Array<TemporalMixinInterface & HTMLElement> = [],\n) => {\n // Get children to walk - handle both regular children and slotted content\n const children = getChildrenIncludingSlotted(element);\n \n for (const child of children) {\n if (isEFTemporal(child)) {\n temporals.push(child as TemporalMixinInterface & HTMLElement);\n }\n deepGetTemporalElements(child, temporals);\n }\n return temporals;\n};\n\n/**\n * Gets all child elements including slotted content for shadow DOM elements.\n * For elements with shadow DOM that contain slots, this returns the assigned\n * elements (slotted content) instead of just the shadow DOM children.\n */\nconst getChildrenIncludingSlotted = (element: Element): Element[] => {\n // If element has shadowRoot with slots, get assigned elements\n if (element.shadowRoot) {\n const slots = element.shadowRoot.querySelectorAll('slot');\n if (slots.length > 0) {\n const assignedElements: Element[] = [];\n for (const slot of slots) {\n assignedElements.push(...slot.assignedElements());\n }\n // Also include shadow DOM children that aren't slots (for mixed content)\n for (const child of element.shadowRoot.children) {\n if (child.tagName !== 'SLOT') {\n assignedElements.push(child);\n }\n }\n return assignedElements;\n }\n }\n \n // Fallback to regular children\n return Array.from(element.children);\n};\n\nexport const deepGetElementsWithFrameTasks = (\n element: Element,\n elements: Array<TemporalMixinInterface & HTMLElement> = [],\n) => {\n // Get children to walk - handle both regular children and slotted content\n const children = getChildrenIncludingSlotted(element);\n \n for (const child of children) {\n // Check for frameTask with run method (works with both Lit Task and our compatibility wrapper)\n const hasFrameTask = \"frameTask\" in child && \n child.frameTask != null && \n typeof (child.frameTask as any).run === \"function\";\n if (hasFrameTask) {\n elements.push(child as TemporalMixinInterface & HTMLElement);\n }\n deepGetElementsWithFrameTasks(child, elements);\n }\n return elements;\n};\n\nlet temporalCache: Map<Element, TemporalMixinInterface[]>;\nlet temporalCacheResetScheduled = false;\nexport const resetTemporalCache = () => {\n temporalCache = new Map();\n if (\n typeof requestAnimationFrame !== \"undefined\" &&\n !temporalCacheResetScheduled\n ) {\n temporalCacheResetScheduled = true;\n requestAnimationFrame(() => {\n temporalCacheResetScheduled = false;\n resetTemporalCache();\n });\n }\n};\nresetTemporalCache();\n\nexport const shallowGetTemporalElements = (\n element: Element,\n temporals: TemporalMixinInterface[] = [],\n) => {\n const cachedResult = temporalCache.get(element);\n if (cachedResult) {\n return cachedResult;\n }\n // Get children to walk - handle both regular children and slotted content\n const children = getChildrenIncludingSlotted(element);\n \n for (const child of children) {\n if (isEFTemporal(child)) {\n temporals.push(child);\n } else {\n shallowGetTemporalElements(child, temporals);\n }\n }\n temporalCache.set(element, temporals);\n return temporals;\n};\n\nexport class OwnCurrentTimeController implements ReactiveController {\n #lastKnownTimeMs: number | undefined = undefined;\n \n constructor(\n private host: EFTimegroup,\n private temporal: TemporalMixinInterface & LitElement,\n ) {\n host.addController(this);\n }\n\n hostUpdated() {\n // CRITICAL FIX: Only trigger child updates when root's currentTimeMs actually changes.\n // Previously, this fired on EVERY root update, causing 40+ child updates per root update.\n // With nested timegroups, this created cascading updates that locked up the main thread.\n const currentTimeMs = this.host.currentTimeMs;\n if (this.#lastKnownTimeMs === currentTimeMs) {\n return; // Time hasn't changed, no need to update children\n }\n this.#lastKnownTimeMs = currentTimeMs;\n \n // Defer update using setTimeout(0) to avoid Lit warning about scheduling updates after update completed.\n // This batches updates and ensures we're completely outside any Lit update cycle.\n // Using setTimeout instead of Promise.resolve() because microtasks run before Lit's\n // change detection completes.\n setTimeout(() => {\n this.temporal.requestUpdate(\"ownCurrentTimeMs\");\n }, 0);\n }\n\n remove() {\n this.host.removeController(this);\n }\n}\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\n\nlet startTimeMsCache = new WeakMap<Element, number>();\nlet startTimeMsCacheResetScheduled = false;\nconst resetStartTimeMsCache = () => {\n startTimeMsCache = new WeakMap();\n if (\n typeof requestAnimationFrame !== \"undefined\" &&\n !startTimeMsCacheResetScheduled\n ) {\n startTimeMsCacheResetScheduled = true;\n requestAnimationFrame(() => {\n startTimeMsCacheResetScheduled = false;\n resetStartTimeMsCache();\n });\n }\n};\nresetStartTimeMsCache();\n\nexport const flushStartTimeMsCache = () => {\n startTimeMsCache = new WeakMap();\n};\n\nexport const EFTemporal = <T extends Constructor<LitElement>>(\n superClass: T,\n) => {\n class TemporalMixinClass extends superClass {\n #ownCurrentTimeController?: OwnCurrentTimeController;\n\n #parentTimegroup?: EFTimegroup;\n #rootTimegroupLocked = false; // When true, rootTimegroup won't be auto-recalculated\n \n @consume({ context: timegroupContext, subscribe: true })\n set parentTimegroup(value: EFTimegroup | undefined) {\n const oldParent = this.#parentTimegroup;\n const oldRole = determineTemporalRole(oldParent);\n const newRole = determineTemporalRole(value);\n\n this.#parentTimegroup = value;\n\n this.#ownCurrentTimeController?.remove();\n // Only auto-calculate rootTimegroup if it hasn't been locked\n // (locked means it was manually set, e.g., for render clones)\n if (!this.#rootTimegroupLocked) {\n this.rootTimegroup = this.getRootTimegroup();\n }\n if (this.rootTimegroup) {\n this.#ownCurrentTimeController = new OwnCurrentTimeController(\n this.rootTimegroup,\n this as InstanceType<Constructor<TemporalMixinInterface> & T>,\n );\n }\n\n // Only trigger callbacks if role actually changed\n if (oldRole !== newRole) {\n if (newRole === \"root\") {\n this.didBecomeRoot();\n } else {\n this.didBecomeChild();\n }\n }\n }\n \n /**\n * Lock the rootTimegroup to prevent auto-recalculation.\n * Used for render clones where the root must be fixed.\n * @internal\n */\n lockRootTimegroup() {\n this.#rootTimegroupLocked = true;\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#ownCurrentTimeController?.remove();\n\n if (this.playbackController) {\n this.playbackController.remove();\n this.playbackController = undefined;\n }\n\n // Clean up tracked animations to prevent memory leaks\n // Use dynamic import to avoid circular dependency with updateAnimations\n import(\"./updateAnimations.js\").then(({ cleanupTrackedAnimations }) => {\n cleanupTrackedAnimations(this);\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n this.#ownCurrentTimeController?.remove();\n\n // Root detection: Check DOM structure to determine if this is truly a root.\n // \n // We can't rely on Lit Context (parentTimegroup) because context propagates\n // asynchronously during update cycles. Children may complete their first update\n // before ancestors have provided context, causing them to incorrectly think\n // they're roots.\n //\n // Instead, we check if there's an ancestor ef-timegroup in the DOM. This is\n // reliable because DOM structure is established synchronously at connection time.\n // \n // If there's NO ancestor timegroup, this is a true root → create PlaybackController.\n // If there IS an ancestor, wait for context to propagate (handled by parentTimegroup setter).\n // Note: closest() includes self, so we check from parentElement to find true ancestors.\n const hasAncestorTimegroup = this.parentElement?.closest('ef-timegroup') != null;\n \n if (!hasAncestorTimegroup && !this.playbackController) {\n // True root: no ancestor timegroup in DOM\n // Defer slightly to allow element to fully initialize\n this.updateComplete.then(() => {\n if (!this.isConnected) return;\n if (!this.playbackController) {\n this.didBecomeRoot();\n }\n });\n }\n // For elements WITH ancestors, the parentTimegroup setter will be called\n // when Lit Context propagates, and if the role changes, didBecomeRoot/didBecomeChild\n // will be called appropriately.\n }\n\n get parentTimegroup() {\n return this.#parentTimegroup;\n }\n\n playbackController?: PlaybackController;\n\n get playing(): boolean {\n if (!this.playbackController) {\n return false;\n }\n return this.playbackController.playing;\n }\n\n set playing(value: boolean) {\n if (!this.playbackController) {\n console.warn(\"Cannot set playing on non-root temporal element\", this);\n return;\n }\n this.playbackController.setPlaying(value);\n }\n\n play(): void {\n if (!this.playbackController) {\n console.warn(\"play() called on non-root temporal element\", this);\n return;\n }\n this.playbackController.play();\n }\n\n pause(): void {\n if (!this.playbackController) {\n console.warn(\"pause() called on non-root temporal element\", this);\n return;\n }\n this.playbackController.pause();\n }\n\n @property({ type: Boolean, reflect: true, attribute: \"loop\" })\n get loop(): boolean {\n return this.playbackController?.loop ?? this.#loop;\n }\n\n set loop(value: boolean) {\n const oldValue = this.#loop;\n this.#loop = value;\n if (this.playbackController) {\n this.playbackController.setLoop(value);\n }\n this.requestUpdate(\"loop\", oldValue);\n }\n\n @property({\n type: String,\n attribute: \"offset\",\n converter: durationConverter,\n })\n private _offsetMs = 0;\n\n @property({\n type: Number,\n attribute: \"duration\",\n converter: durationConverter,\n })\n private _durationMs?: number;\n\n set duration(value: string | undefined) {\n if (value !== undefined) {\n this.setAttribute(\"duration\", value);\n } else {\n this.removeAttribute(\"duration\");\n }\n }\n\n @property({\n type: Number,\n attribute: \"trimstart\",\n converter: durationConverter,\n })\n private _trimStartMs: number | undefined = undefined;\n\n get trimStartMs() {\n if (this._trimStartMs === undefined) {\n return undefined;\n }\n return Math.min(\n Math.max(this._trimStartMs, 0),\n this.intrinsicDurationMs ?? 0,\n );\n }\n\n set trimStartMs(value: number | undefined) {\n this._trimStartMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"trimend\",\n converter: durationConverter,\n })\n private _trimEndMs: number | undefined = undefined;\n\n get trimEndMs() {\n if (this._trimEndMs === undefined) {\n return undefined;\n }\n return Math.min(this._trimEndMs, this.intrinsicDurationMs ?? 0);\n }\n\n set trimEndMs(value: number | undefined) {\n this._trimEndMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"sourcein\",\n converter: durationConverter,\n })\n private _sourceInMs: number | undefined = undefined;\n\n get sourceInMs() {\n if (this._sourceInMs === undefined) {\n return undefined;\n }\n return Math.max(this._sourceInMs, 0);\n }\n\n set sourceInMs(value: number | undefined) {\n this._sourceInMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"sourceout\",\n converter: durationConverter,\n })\n private _sourceOutMs: number | undefined = undefined;\n\n get sourceOutMs() {\n if (this._sourceOutMs === undefined) {\n return undefined;\n }\n if (\n this.intrinsicDurationMs &&\n this._sourceOutMs > this.intrinsicDurationMs\n ) {\n return this.intrinsicDurationMs;\n }\n return Math.max(this._sourceOutMs, 0);\n }\n\n set sourceOutMs(value: number | undefined) {\n this._sourceOutMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"startoffset\",\n converter: durationConverter,\n })\n private _startOffsetMs = 0;\n public get startOffsetMs(): number {\n return this._startOffsetMs;\n }\n\n @state()\n rootTimegroup?: EFTimegroup = this.getRootTimegroup();\n\n private getRootTimegroup(): EFTimegroup | undefined {\n let parent =\n this.tagName === \"EF-TIMEGROUP\" ? this : this.parentTimegroup;\n while (parent?.parentTimegroup) {\n parent = parent.parentTimegroup;\n }\n return parent as EFTimegroup | undefined;\n }\n\n get hasExplicitDuration() {\n return this._durationMs !== undefined;\n }\n\n get explicitDurationMs() {\n if (this.hasExplicitDuration) {\n return this._durationMs;\n }\n return undefined;\n }\n\n get hasOwnDuration() {\n return this.intrinsicDurationMs !== undefined || this.hasExplicitDuration;\n }\n\n get intrinsicDurationMs() {\n return undefined;\n }\n\n get durationMs() {\n // Prevent infinite loops: don't call parent.durationMs if parent is currently calculating\n // Lazy import to break circular dependency: EFTemporal -> EFTimegroup -> EFMedia -> EFTemporal\n const isTimegroupCalculatingDuration =\n getIsTimegroupCalculatingDuration();\n const parentDurationMs = isTimegroupCalculatingDuration(\n this.parentTimegroup,\n )\n ? undefined\n : this.parentTimegroup?.durationMs;\n const durationSource = determineDurationSource(\n this.intrinsicDurationMs,\n this._durationMs,\n parentDurationMs,\n );\n\n const modification = determineDurationModificationStrategy(\n this.trimStartMs,\n this.trimEndMs,\n this.sourceInMs,\n this.sourceOutMs,\n );\n\n return evaluateModifiedDuration(\n durationSource.baseDurationMs,\n modification,\n );\n }\n\n get sourceStartMs() {\n return this.trimStartMs ?? this.sourceInMs ?? 0;\n }\n\n #offsetMs() {\n return this._offsetMs || 0;\n }\n\n #parentTemporal() {\n let parent = this.parentElement;\n while (parent && !isEFTemporal(parent)) {\n parent = parent.parentElement;\n }\n return parent;\n }\n\n /**\n * The start time of the element within its parent timegroup.\n */\n get startTimeWithinParentMs() {\n const parent = this.#parentTemporal();\n if (!parent) {\n return 0;\n }\n return this.startTimeMs - parent.startTimeMs;\n }\n\n #loop = false;\n\n get startTimeMs(): number {\n const cachedStartTime = startTimeMsCache.get(this);\n if (cachedStartTime !== undefined) {\n return cachedStartTime;\n }\n\n const startTime = evaluateStartTime(\n this as InstanceType<Constructor<TemporalMixinInterface> & T>,\n this.parentTimegroup,\n this.#offsetMs(),\n (parent) => shallowGetTemporalElements(parent),\n );\n\n startTimeMsCache.set(this, startTime);\n return startTime;\n }\n\n get endTimeMs(): number {\n return this.startTimeMs + this.durationMs;\n }\n\n #currentTimeMs = 0;\n\n /**\n * The current time of the element within itself.\n * Compare with `currentTimeMs` to see the current time with respect to the root timegroup\n */\n get ownCurrentTimeMs(): number {\n const timeSource = determineCurrentTimeSource(\n this.playbackController,\n this.rootTimegroup,\n this.rootTimegroup === (this as any as EFTimegroup),\n this.#currentTimeMs,\n this.startTimeMs,\n this.durationMs,\n );\n return timeSource.timeMs;\n }\n\n /**\n * Element's current time for progress calculation.\n * Non-timegroup temporal elements use their local time (ownCurrentTimeMs)\n */\n get currentTimeMs() {\n return this.ownCurrentTimeMs;\n }\n\n set currentTimeMs(value: number) {\n const role = determineTemporalRole(this.parentTimegroup);\n\n // Apply current time based on role\n switch (role) {\n case \"root\":\n if (this.playbackController) {\n this.playbackController.currentTime = value / 1000;\n } else {\n this.#currentTimeMs = value;\n this.requestUpdate(\"currentTimeMs\");\n }\n break;\n case \"child\":\n if (\n this.rootTimegroup &&\n this.rootTimegroup !== (this as any as EFTimegroup)\n ) {\n this.rootTimegroup.currentTimeMs = value;\n } else {\n this.#currentTimeMs = value;\n this.requestUpdate(\"currentTimeMs\");\n }\n break;\n }\n }\n\n /**\n * Used to calculate the internal currentTimeMs of the element. This is useful\n * for mapping to internal media time codes for audio/video elements.\n */\n get currentSourceTimeMs() {\n const leadingTrimMs = this.sourceInMs || this.trimStartMs || 0;\n return this.ownCurrentTimeMs + leadingTrimMs;\n }\n\n /**\n * @deprecated Use FrameRenderable interface via FrameController instead.\n * This is a compatibility wrapper - base class just waits for updateComplete.\n */\n #frameTaskPromise: Promise<void> = Promise.resolve();\n \n frameTask = (() => {\n const self = this;\n const taskObj: { run(): void | Promise<void>; taskComplete: Promise<void> } = {\n run: () => {\n self.#frameTaskPromise = self.updateComplete.then(() => {});\n taskObj.taskComplete = self.#frameTaskPromise;\n return self.#frameTaskPromise;\n },\n taskComplete: Promise.resolve(),\n };\n return taskObj;\n })();\n\n didBecomeRoot() {\n // Never create PlaybackController for render clones\n // Render clones need direct time control via seekForRender\n if ((this as any).closest?.('.ef-render-clone-container')) {\n return;\n }\n \n if (!this.playbackController) {\n this.playbackController = new PlaybackController(this as any);\n if (this.#loop) {\n this.playbackController.setLoop(this.#loop);\n }\n }\n }\n\n didBecomeChild() {\n if (this.playbackController) {\n this.playbackController.remove();\n this.playbackController = undefined;\n }\n }\n }\n\n Object.defineProperty(TemporalMixinClass.prototype, EF_TEMPORAL, {\n value: true,\n });\n\n return TemporalMixinClass as unknown as Constructor<TemporalMixinInterface> &\n T;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFTemporal.js","names":["isTimegroupCalculatingDurationFn:\n | ((timegroup: EFTimegroup | undefined) => boolean)\n | null","fallbackFn: (timegroup: EFTimegroup | undefined) => boolean","elements: Array<TemporalMixinInterface & HTMLElement>","assignedElements: Element[]","temporalCache: Map<Element, TemporalMixinInterface[]>","host: EFTimegroup","temporal: TemporalMixinInterface & LitElement","#lastKnownTimeMs","#contentReadyState","state","#parentTimegroup","#ownCurrentTimeController","#rootTimegroupLocked","#loop","#parentTemporal","#offsetMs","#currentTimeMs"],"sources":["../../src/elements/EFTemporal.ts"],"sourcesContent":["import { consume, createContext } from \"@lit/context\";\nimport type { LitElement, ReactiveController } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { PlaybackController } from \"../gui/PlaybackController.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport type { EFTimegroup } from \"./EFTimegroup.js\";\n// Lazy import to break circular dependency: EFTemporal -> EFTimegroup -> EFMedia -> EFTemporal\n// isTimegroupCalculatingDuration is only used at runtime in a getter, so we can import it lazily\n// Use a module-level variable that gets set when EFTimegroup module loads\nlet isTimegroupCalculatingDurationFn:\n | ((timegroup: EFTimegroup | undefined) => boolean)\n | null = null;\n\n// This function will be called by EFTimegroup when it loads to register the function\nexport const registerIsTimegroupCalculatingDuration = (\n fn: (timegroup: EFTimegroup | undefined) => boolean,\n) => {\n isTimegroupCalculatingDurationFn = fn;\n};\n\nconst getIsTimegroupCalculatingDuration = (): ((\n timegroup: EFTimegroup | undefined,\n) => boolean) => {\n if (isTimegroupCalculatingDurationFn) {\n return isTimegroupCalculatingDurationFn as (\n timegroup: EFTimegroup | undefined,\n ) => boolean;\n }\n\n // If not registered yet, try to import synchronously (only works if module is already loaded)\n // This is a fallback for cases where EFTimegroup hasn't called registerIsTimegroupCalculatingDuration\n // In practice, EFTimegroup will call registerIsTimegroupCalculatingDuration when it loads\n let fallbackFn: (timegroup: EFTimegroup | undefined) => boolean = () => false;\n try {\n // Access the function via a global or try to get it from the module cache\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const efTimegroupModule = (globalThis as any).__EFTimegroupModule;\n if (efTimegroupModule?.isTimegroupCalculatingDuration) {\n fallbackFn = efTimegroupModule.isTimegroupCalculatingDuration;\n }\n } catch {\n // Use default fallback\n }\n isTimegroupCalculatingDurationFn = fallbackFn;\n return fallbackFn;\n};\n\nexport const timegroupContext = createContext<EFTimegroup>(\n Symbol(\"timeGroupContext\"),\n);\n\n// ============================================================================\n// Core Concept 1: Temporal Role\n// ============================================================================\n// A temporal element is either a root (controls its own playback) or a child\n// (delegates playback to its root timegroup). This is the fundamental invariant.\n// ============================================================================\n\n// ============================================================================\n// Core Concept 0: Content Readiness Protocol\n// ============================================================================\n// Every temporal element exposes a contentReadyState property and two events:\n// readystatechange — fires on state transitions (idle, loading, ready, error)\n// contentchange — fires when renderable output is invalidated\n//\n// The property solves late-subscriber: consumers check it on subscribe.\n// Events are non-bubbling: containers (timegroups) explicitly aggregate.\n// ============================================================================\n\nexport type ContentReadyState = \"idle\" | \"loading\" | \"ready\" | \"error\";\nexport type ContentChangeReason = \"source\" | \"bounds\" | \"structure\" | \"content\";\n\ntype TemporalRole = \"root\" | \"child\";\n\nfunction determineTemporalRole(\n parentTimegroup: EFTimegroup | undefined,\n): TemporalRole {\n return parentTimegroup === undefined ? \"root\" : \"child\";\n}\n\n// ============================================================================\n// Core Concept 2: Duration Source\n// ============================================================================\n// Duration comes from one of three sources: intrinsic (media-based),\n// explicit (attribute), or inherited (from parent). This determines the base\n// duration before any modifications.\n// ============================================================================\n\ntype DurationSource = \"intrinsic\" | \"explicit\" | \"inherited\";\n\ninterface DurationSourceResult {\n source: DurationSource;\n baseDurationMs: number;\n}\n\nfunction determineDurationSource(\n intrinsicDurationMs: number | undefined,\n explicitDurationMs: number | undefined,\n parentDurationMs: number | undefined,\n): DurationSourceResult {\n if (intrinsicDurationMs !== undefined) {\n return { source: \"intrinsic\", baseDurationMs: intrinsicDurationMs };\n }\n if (explicitDurationMs !== undefined) {\n return { source: \"explicit\", baseDurationMs: explicitDurationMs };\n }\n if (parentDurationMs !== undefined) {\n return { source: \"inherited\", baseDurationMs: parentDurationMs };\n }\n return { source: \"inherited\", baseDurationMs: 0 };\n}\n\n// ============================================================================\n// Core Concept 3: Duration Modification Strategy\n// ============================================================================\n// Duration can be modified by trimming (removing from edges) or source\n// manipulation (selecting a portion of the source). These are mutually\n// exclusive strategies.\n// ============================================================================\n\ntype DurationModificationStrategy = \"none\" | \"trimming\" | \"source-manipulation\";\n\ninterface DurationModificationState {\n strategy: DurationModificationStrategy;\n trimStartMs: number | undefined;\n trimEndMs: number | undefined;\n sourceInMs: number | undefined;\n sourceOutMs: number | undefined;\n}\n\nfunction determineDurationModificationStrategy(\n trimStartMs: number | undefined,\n trimEndMs: number | undefined,\n sourceInMs: number | undefined,\n sourceOutMs: number | undefined,\n): DurationModificationState {\n if (trimStartMs !== undefined || trimEndMs !== undefined) {\n return {\n strategy: \"trimming\",\n trimStartMs,\n trimEndMs,\n sourceInMs: undefined,\n sourceOutMs: undefined,\n };\n }\n if (sourceInMs !== undefined || sourceOutMs !== undefined) {\n return {\n strategy: \"source-manipulation\",\n trimStartMs: undefined,\n trimEndMs: undefined,\n sourceInMs,\n sourceOutMs,\n };\n }\n return {\n strategy: \"none\",\n trimStartMs: undefined,\n trimEndMs: undefined,\n sourceInMs: undefined,\n sourceOutMs: undefined,\n };\n}\n\nfunction evaluateModifiedDuration(\n baseDurationMs: number,\n modification: DurationModificationState,\n): number {\n if (baseDurationMs === 0) {\n return 0;\n }\n\n switch (modification.strategy) {\n case \"trimming\": {\n const trimmedDurationMs =\n baseDurationMs -\n (modification.trimStartMs ?? 0) -\n (modification.trimEndMs ?? 0);\n return Math.max(0, trimmedDurationMs);\n }\n case \"source-manipulation\": {\n const sourceInMs = modification.sourceInMs ?? 0;\n const sourceOutMs = modification.sourceOutMs ?? baseDurationMs;\n if (sourceInMs >= sourceOutMs) {\n return 0;\n }\n return sourceOutMs - sourceInMs;\n }\n case \"none\":\n return baseDurationMs;\n }\n}\n\n// ============================================================================\n// Core Concept 4: Start Time Calculation Strategy\n// ============================================================================\n// Start time is calculated differently based on parent timegroup mode:\n// - Sequence mode: based on previous sibling\n// - Other modes: based on parent start + offset\n// ============================================================================\n\ntype StartTimeStrategy = \"sequence\" | \"offset\";\n\nfunction determineStartTimeStrategy(\n parentTimegroup: EFTimegroup | undefined,\n): StartTimeStrategy {\n if (!parentTimegroup) {\n return \"offset\";\n }\n return parentTimegroup.mode === \"sequence\" ? \"sequence\" : \"offset\";\n}\n\nfunction evaluateStartTimeForSequence(\n _element: TemporalMixinInterface & HTMLElement,\n parentTimegroup: EFTimegroup,\n siblingTemporals: TemporalMixinInterface[],\n ownIndex: number,\n): number {\n if (ownIndex === 0) {\n return parentTimegroup.startTimeMs;\n }\n const previous = siblingTemporals[ownIndex - 1];\n if (!previous) {\n throw new Error(\"Previous temporal element not found\");\n }\n return previous.startTimeMs + previous.durationMs - parentTimegroup.overlapMs;\n}\n\nfunction evaluateStartTimeForOffset(\n parentTimegroup: EFTimegroup,\n offsetMs: number,\n): number {\n return parentTimegroup.startTimeMs + offsetMs;\n}\n\nfunction evaluateStartTime(\n element: TemporalMixinInterface & HTMLElement,\n parentTimegroup: EFTimegroup | undefined,\n offsetMs: number,\n getSiblingTemporals: (parent: EFTimegroup) => TemporalMixinInterface[],\n): number {\n if (!parentTimegroup) {\n return 0;\n }\n\n const strategy = determineStartTimeStrategy(parentTimegroup);\n switch (strategy) {\n case \"sequence\": {\n const siblingTemporals = getSiblingTemporals(parentTimegroup);\n const ownIndex = siblingTemporals.indexOf(element);\n if (ownIndex === -1) {\n return 0;\n }\n return evaluateStartTimeForSequence(\n element,\n parentTimegroup,\n siblingTemporals,\n ownIndex,\n );\n }\n case \"offset\":\n return evaluateStartTimeForOffset(parentTimegroup, offsetMs);\n }\n}\n\n// ============================================================================\n// Core Concept 5: Current Time Source\n// ============================================================================\n// Current time comes from one of three sources: playback controller (root\n// elements), root timegroup (child elements), or local storage (fallback).\n// ============================================================================\n\ntype CurrentTimeSource = \"playback-controller\" | \"root-timegroup\" | \"local\";\n\ninterface CurrentTimeSourceResult {\n source: CurrentTimeSource;\n timeMs: number;\n}\n\nfunction determineCurrentTimeSource(\n playbackController: PlaybackController | undefined,\n rootTimegroup: EFTimegroup | undefined,\n isRootTimegroup: boolean,\n localTimeMs: number,\n startTimeMs: number,\n durationMs: number,\n): CurrentTimeSourceResult {\n if (playbackController) {\n const timeMs = Math.min(\n Math.max(0, playbackController.currentTimeMs),\n durationMs,\n );\n return { source: \"playback-controller\", timeMs };\n }\n\n if (rootTimegroup && !isRootTimegroup) {\n const timeMs = Math.min(\n Math.max(0, rootTimegroup.currentTimeMs - startTimeMs),\n durationMs,\n );\n return { source: \"root-timegroup\", timeMs };\n }\n\n const timeMs = Math.min(Math.max(0, localTimeMs), durationMs);\n return { source: \"local\", timeMs };\n}\n\nexport declare class TemporalMixinInterface {\n playbackController?: PlaybackController;\n playing: boolean;\n loop: boolean;\n play(): void;\n pause(): void;\n\n get hasOwnDuration(): boolean;\n /**\n * Whether the element has a duration set as an attribute.\n */\n get hasExplicitDuration(): boolean;\n\n get sourceStartMs(): number;\n\n /**\n * Used to trim the start of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `trimstart=\"10s\"` is equivalent to `trimstart=\"10000ms\"`.\n *\n * @domAttribute \"trimstart\"\n */\n get trimStartMs(): number | undefined;\n\n /**\n * Used to trim the end of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `trimend=\"10s\"` is equivalent to `trimend=\"10000ms\"`.\n *\n * @domAttribute \"trimend\"\n */\n get trimEndMs(): number;\n\n set trimStartMs(value: number | undefined);\n set trimEndMs(value: number | undefined);\n set trimstart(value: string | undefined);\n set trimend(value: string | undefined);\n\n /**\n * The source in time of the element.\n *\n * This is an amount of time to trim off the beginning of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `sourcein=\"10s\"` is equivalent to `sourcein=\"10000ms\"`.\n *\n * If the sourcein time is greater than the duration of the media, the media\n * will not be played.\n *\n * If the media is 20 seconds long, and the `sourcein` value is set to `10s`, the\n * media will play for 10 seconds, starting at the 10 second mark.\n *\n * Can be used in conjunction with `sourceout` to create a trimmed media.\n *\n * @domAttribute \"sourcein\"\n */\n get sourceInMs(): number | undefined;\n\n /**\n * The source out time of the element.\n *\n * This is the point in time in the media that will be treated as the end of\n * the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `sourceout=\"10s\"` is equivalent to `sourceout=\"10000ms\"`.\n *\n * If the sourceout time is greater than the duration of the media, the media\n * will play until the end of the media.\n *\n * If the media is 20 seconds long, and the `sourceout` value is set to `10s`,\n * the media will play for 10 seconds, starting at zero seconds and ending at\n * the 10 second mark.\n *\n * Can be used in conjunction with `sourcein` to create a trimmed media.\n *\n * @domAttribute \"sourceout\"\n */\n get sourceOutMs(): number | undefined;\n\n set sourceInMs(value: number | undefined);\n set sourceOutMs(value: number | undefined);\n set sourcein(value: string | undefined);\n set sourceout(value: string | undefined);\n\n /**\n * @domAttribute \"duration\"\n */\n get durationMs(): number;\n\n get explicitDurationMs(): number | undefined;\n\n get intrinsicDurationMs(): number | undefined;\n\n /**\n * The start time of the element within its root timegroup in milliseconds.\n *\n * This is an absolute time according to the highest scoped timegroup the media element is contained within.\n *\n * The calculated value will depend on the mode of the timegroup and the offset of the media element.\n *\n * If the parent time group is in `sequence` mode, the start time will be the\n * start time of the previous sibling element plus the previous sibling's duration\n * minus the overlap of the previous sibling and the current sibling.\n *\n * If the parent time group is in `contain` or `fixed` mode, the start time will be\n * the start time of the parent time group plus the offset of the media element.\n */\n get startTimeMs(): number;\n /**\n * The end time of the element within its root timegroup in milliseconds.\n *\n * This is an absolute time according to the highest scoped timegroup the media\n * element is contained within. Computed by adding the media's duration to its\n * start time.\n *\n * If the media element has been trimmed, its end time will be calculated according it\n * its trimmed duration, not its original duration.\n */\n get endTimeMs(): number;\n /**\n * The start time of the element within its parent timegroup in milliseconds.\n *\n * This is a relative time according to the closest timegroup the media element\n * is contained within. Unless the media element has been given any kind of specific offset\n * it is common for this time to be zero.\n */\n get startTimeWithinParentMs(): number;\n\n /**\n * The current time of the element in milliseconds.\n *\n * This is a relative time according to the closest timegroup the media element\n * is contained within.\n *\n * This is suitable for determining the percentage of the media that has been\n * played.\n */\n get ownCurrentTimeMs(): number;\n\n /**\n * Set the base local time (ms) used by ownCurrentTimeMs when no playback\n * controller is present. Used by seekForRender() on render clones.\n * @internal\n */\n _setLocalTimeMs(value: number): void;\n\n /**\n * Element's current time for progress calculation.\n * For timegroups: their timeline currentTimeMs\n * For other temporal elements: their ownCurrentTimeMs\n */\n get currentTimeMs(): number;\n set currentTimeMs(value: number);\n /**\n * The current time of the element in milliseconds, adjusted for trimming.\n *\n * This is suitable for mapping to internal media time codes for audio/video\n * elements.\n *\n * For example, if the media has a `sourcein` value of 10s, when `ownCurrentTimeMs` is 0s,\n * `currentSourceTimeMs` will be 10s.\n *\n * sourcein=10s sourceout=10s\n * / / /\n * |--------|=================|---------|\n * ^\n * |_\n * currentSourceTimeMs === 10s\n * |_\n * ownCurrentTimeMs === 0s\n */\n get currentSourceTimeMs(): number;\n\n set duration(value: string);\n get duration(): string;\n\n /**\n * The offset of the element within its parent timegroup in milliseconds.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `offset=\"10s\"` is equivalent to `offset=\"10000ms\"`.\n *\n * This can be used to create a negative or positive offset for the start time of the media.\n *\n * This will change the start time of the media relative to it's otherwise normal start time.\n *\n * The duration of the element, or it's parent, or the start and end time of it's temporal siblings will not\n * be affected by this offset.\n *\n * @domAttribute \"offset\"\n */\n set offset(value: string);\n get offset(): string;\n\n /**\n * A convenience property for getting the nearest containing timegroup of the media element.\n */\n parentTimegroup?: EFTimegroup;\n\n /**\n * A convenience property for getting the root timegroup of the media element.\n */\n rootTimegroup?: EFTimegroup;\n\n didBecomeRoot(): void;\n didBecomeChild(): void;\n\n /**\n * The readiness state of this element's content.\n * \"idle\" — no content / not connected\n * \"loading\" — async resources are loading\n * \"ready\" — element can render / extract frames\n * \"error\" — resource loading failed\n *\n * @domAttribute \"content-ready-state\"\n */\n contentReadyState: ContentReadyState;\n\n /**\n * Transition to a new readiness state.\n * Dispatches a non-bubbling `readystatechange` CustomEvent if the state changed.\n */\n setContentReadyState(state: ContentReadyState): void;\n\n /**\n * Dispatch a non-bubbling `contentchange` CustomEvent.\n * Signals that cached renderable output is stale.\n */\n emitContentChange(reason: ContentChangeReason): void;\n\n /**\n * Whether this element should auto-transition to \"ready\" after first update.\n * Override to return false for elements with async loading (EFMedia, EFCaptions).\n */\n shouldAutoReady(): boolean;\n\n updateComplete: Promise<boolean>;\n}\n\nexport const isEFTemporal = (obj: any): obj is TemporalMixinInterface =>\n obj[EF_TEMPORAL];\n\nconst EF_TEMPORAL = Symbol(\"EF_TEMPORAL\");\n\nexport interface TemporalCollectionResult {\n /** Temporal elements to process (visible + pruned roots). */\n elements: Array<TemporalMixinInterface & HTMLElement>;\n /** Temporal elements whose subtrees were pruned (invisible containers). */\n pruned: Set<TemporalMixinInterface & HTMLElement>;\n}\n\nexport const deepGetTemporalElements = (\n element: Element,\n timeMs?: number,\n): TemporalCollectionResult => {\n const elements: Array<TemporalMixinInterface & HTMLElement> = [];\n const pruned = new Set<TemporalMixinInterface & HTMLElement>();\n\n const walk = (el: Element) => {\n const children = getChildrenIncludingSlotted(el);\n\n for (const child of children) {\n if (isEFTemporal(child)) {\n const temporal = child as TemporalMixinInterface & HTMLElement;\n elements.push(temporal);\n\n // Prune: if a time was provided and this temporal element is outside\n // its time range, skip its entire subtree. The caller is responsible\n // for setting display:none on these pruned roots; their children are\n // hidden by containment and never visited.\n if (timeMs !== undefined) {\n const startMs = temporal.startTimeMs;\n const endMs = temporal.endTimeMs;\n if (endMs > startMs && (timeMs < startMs || timeMs >= endMs)) {\n pruned.add(temporal);\n continue; // skip subtree\n }\n }\n }\n walk(child);\n }\n };\n\n walk(element);\n return { elements, pruned };\n};\n\n/**\n * Gets all child elements including slotted content for shadow DOM elements.\n * For elements with shadow DOM that contain slots, this returns the assigned\n * elements (slotted content) instead of just the shadow DOM children.\n */\nconst getChildrenIncludingSlotted = (element: Element): Element[] => {\n // If element has shadowRoot with slots, get assigned elements\n if (element.shadowRoot) {\n const slots = element.shadowRoot.querySelectorAll(\"slot\");\n if (slots.length > 0) {\n const assignedElements: Element[] = [];\n for (const slot of slots) {\n assignedElements.push(...slot.assignedElements());\n }\n // Also include shadow DOM children that aren't slots (for mixed content)\n for (const child of element.shadowRoot.children) {\n if (child.tagName !== \"SLOT\") {\n assignedElements.push(child);\n }\n }\n return assignedElements;\n }\n }\n\n // Fallback to regular children\n return Array.from(element.children);\n};\n\nlet temporalCache: Map<Element, TemporalMixinInterface[]>;\nlet temporalCacheResetScheduled = false;\nexport const resetTemporalCache = () => {\n temporalCache = new Map();\n if (\n typeof requestAnimationFrame !== \"undefined\" &&\n !temporalCacheResetScheduled\n ) {\n temporalCacheResetScheduled = true;\n requestAnimationFrame(() => {\n temporalCacheResetScheduled = false;\n resetTemporalCache();\n });\n }\n};\nresetTemporalCache();\n\nexport const shallowGetTemporalElements = (\n element: Element,\n temporals: TemporalMixinInterface[] = [],\n) => {\n const cachedResult = temporalCache.get(element);\n if (cachedResult) {\n return cachedResult;\n }\n // Get children to walk - handle both regular children and slotted content\n const children = getChildrenIncludingSlotted(element);\n\n for (const child of children) {\n if (isEFTemporal(child)) {\n temporals.push(child);\n } else {\n shallowGetTemporalElements(child, temporals);\n }\n }\n temporalCache.set(element, temporals);\n return temporals;\n};\n\nexport class OwnCurrentTimeController implements ReactiveController {\n #lastKnownTimeMs: number | undefined = undefined;\n\n constructor(\n private host: EFTimegroup,\n private temporal: TemporalMixinInterface & LitElement,\n ) {\n host.addController(this);\n }\n\n hostUpdated() {\n // CRITICAL FIX: Only trigger child updates when root's currentTimeMs actually changes.\n // Previously, this fired on EVERY root update, causing 40+ child updates per root update.\n // With nested timegroups, this created cascading updates that locked up the main thread.\n const currentTimeMs = this.host.currentTimeMs;\n if (this.#lastKnownTimeMs === currentTimeMs) {\n return; // Time hasn't changed, no need to update children\n }\n this.#lastKnownTimeMs = currentTimeMs;\n\n // Defer update via queueMicrotask to avoid Lit warning about scheduling\n // updates during hostUpdated. Unlike setTimeout(0) this fires as a microtask,\n // so it resolves between await points without yielding a full macrotask turn\n // (eliminating 4-16ms dead time per frame in the render pipeline).\n queueMicrotask(() => {\n this.temporal.requestUpdate(\"ownCurrentTimeMs\");\n });\n }\n\n remove() {\n this.host.removeController(this);\n }\n}\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\n\nlet startTimeMsCache = new WeakMap<Element, number>();\nlet startTimeMsCacheResetScheduled = false;\nconst resetStartTimeMsCache = () => {\n startTimeMsCache = new WeakMap();\n if (\n typeof requestAnimationFrame !== \"undefined\" &&\n !startTimeMsCacheResetScheduled\n ) {\n startTimeMsCacheResetScheduled = true;\n requestAnimationFrame(() => {\n startTimeMsCacheResetScheduled = false;\n resetStartTimeMsCache();\n });\n }\n};\nresetStartTimeMsCache();\n\nexport const flushStartTimeMsCache = () => {\n startTimeMsCache = new WeakMap();\n};\n\nexport const EFTemporal = <T extends Constructor<LitElement>>(\n superClass: T,\n) => {\n class TemporalMixinClass extends superClass {\n // ---- Content Readiness Protocol ----\n\n #contentReadyState: ContentReadyState = \"idle\";\n\n @property({ type: String, reflect: true, attribute: \"content-ready-state\" })\n get contentReadyState(): ContentReadyState {\n return this.#contentReadyState;\n }\n\n set contentReadyState(value: ContentReadyState) {\n this.setContentReadyState(value);\n }\n\n setContentReadyState(state: ContentReadyState): void {\n if (state === this.#contentReadyState) return;\n const old = this.#contentReadyState;\n this.#contentReadyState = state;\n this.requestUpdate(\"contentReadyState\", old);\n this.dispatchEvent(\n new CustomEvent(\"readystatechange\", {\n detail: { state },\n bubbles: false,\n composed: false,\n }),\n );\n }\n\n emitContentChange(reason: ContentChangeReason): void {\n this.dispatchEvent(\n new CustomEvent(\"contentchange\", {\n detail: { reason },\n bubbles: false,\n composed: false,\n }),\n );\n }\n\n shouldAutoReady(): boolean {\n return true;\n }\n\n // ---- End Content Readiness Protocol ----\n\n #ownCurrentTimeController?: OwnCurrentTimeController;\n\n #parentTimegroup?: EFTimegroup;\n #rootTimegroupLocked = false; // When true, rootTimegroup won't be auto-recalculated\n\n @consume({ context: timegroupContext, subscribe: true })\n set parentTimegroup(value: EFTimegroup | undefined) {\n const oldParent = this.#parentTimegroup;\n const oldRole = determineTemporalRole(oldParent);\n const newRole = determineTemporalRole(value);\n\n this.#parentTimegroup = value;\n\n this.#ownCurrentTimeController?.remove();\n // Only auto-calculate rootTimegroup if it hasn't been locked\n // (locked means it was manually set, e.g., for render clones)\n if (!this.#rootTimegroupLocked) {\n this.rootTimegroup = this.getRootTimegroup();\n }\n if (this.rootTimegroup) {\n this.#ownCurrentTimeController = new OwnCurrentTimeController(\n this.rootTimegroup,\n this as InstanceType<Constructor<TemporalMixinInterface> & T>,\n );\n }\n\n // Only trigger callbacks if role actually changed\n if (oldRole !== newRole) {\n if (newRole === \"root\") {\n this.didBecomeRoot();\n } else {\n this.didBecomeChild();\n }\n }\n }\n\n /**\n * Lock the rootTimegroup to prevent auto-recalculation.\n * Used for render clones where the root must be fixed.\n * @internal\n */\n lockRootTimegroup() {\n this.#rootTimegroupLocked = true;\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n\n // Skip teardown when being moved for canvas preview capture.\n // The element is temporarily reparented to a capture canvas for native\n // rendering but must retain its PlaybackController and animation state.\n if ((this as any).canvasPreviewActive) return;\n\n this.#ownCurrentTimeController?.remove();\n\n if (this.playbackController) {\n this.playbackController.remove();\n this.playbackController = undefined;\n }\n\n // Clean up tracked animations to prevent memory leaks\n // Use dynamic import to avoid circular dependency with updateAnimations\n import(\"./updateAnimations.js\").then(({ cleanupTrackedAnimations }) => {\n cleanupTrackedAnimations(this);\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n\n // Skip re-initialization when being moved for canvas preview capture.\n // The element is temporarily reparented to a capture canvas for native\n // rendering — root detection and PlaybackController creation must be\n // skipped to avoid an infinite wrapWithWorkbench → initCanvasMode loop.\n if ((this as any).canvasPreviewActive) return;\n\n this.#ownCurrentTimeController?.remove();\n\n // Root detection: Check DOM structure to determine if this is truly a root.\n //\n // We can't rely on Lit Context (parentTimegroup) because context propagates\n // asynchronously during update cycles. Children may complete their first update\n // before ancestors have provided context, causing them to incorrectly think\n // they're roots.\n //\n // Instead, we check if there's an ancestor ef-timegroup in the DOM. This is\n // reliable because DOM structure is established synchronously at connection time.\n //\n // If there's NO ancestor timegroup, this is a true root → create PlaybackController.\n // If there IS an ancestor, wait for context to propagate (handled by parentTimegroup setter).\n // Note: closest() includes self, so we check from parentElement to find true ancestors.\n const hasAncestorTimegroup =\n this.parentElement?.closest(\"ef-timegroup\") != null;\n\n if (!hasAncestorTimegroup && !this.playbackController) {\n // True root: no ancestor timegroup in DOM\n // Defer slightly to allow element to fully initialize\n this.updateComplete.then(() => {\n if (!this.isConnected) return;\n if (!this.playbackController) {\n this.didBecomeRoot();\n }\n });\n }\n // For elements WITH ancestors, the parentTimegroup setter will be called\n // when Lit Context propagates, and if the role changes, didBecomeRoot/didBecomeChild\n // will be called appropriately.\n\n // Default readiness: trivially-ready elements (no async loading)\n // transition to \"ready\" after first update. Subclasses with async\n // loading (EFMedia, EFCaptions) override shouldAutoReady() to return\n // false and manage their own readiness lifecycle.\n if (this.shouldAutoReady()) {\n this.updateComplete.then(() => {\n if (!this.isConnected) return;\n if (this.#contentReadyState === \"idle\") {\n this.setContentReadyState(\"ready\");\n }\n });\n }\n }\n\n get parentTimegroup() {\n return this.#parentTimegroup;\n }\n\n playbackController?: PlaybackController;\n\n get playing(): boolean {\n if (!this.playbackController) {\n return false;\n }\n return this.playbackController.playing;\n }\n\n set playing(value: boolean) {\n if (!this.playbackController) {\n console.warn(\"Cannot set playing on non-root temporal element\", this);\n return;\n }\n this.playbackController.setPlaying(value);\n }\n\n play(): void {\n if (!this.playbackController) {\n console.warn(\"play() called on non-root temporal element\", this);\n return;\n }\n this.playbackController.play();\n }\n\n pause(): void {\n if (!this.playbackController) {\n console.warn(\"pause() called on non-root temporal element\", this);\n return;\n }\n this.playbackController.pause();\n }\n\n @property({ type: Boolean, reflect: true, attribute: \"loop\" })\n get loop(): boolean {\n return this.playbackController?.loop ?? this.#loop;\n }\n\n set loop(value: boolean) {\n const oldValue = this.#loop;\n this.#loop = value;\n if (this.playbackController) {\n this.playbackController.setLoop(value);\n }\n this.requestUpdate(\"loop\", oldValue);\n }\n\n @property({\n type: String,\n attribute: \"offset\",\n converter: durationConverter,\n })\n private _offsetMs = 0;\n\n @property({\n type: Number,\n attribute: \"duration\",\n converter: durationConverter,\n })\n private _durationMs?: number;\n\n set duration(value: string | undefined) {\n if (value !== undefined) {\n this.setAttribute(\"duration\", value);\n } else {\n this.removeAttribute(\"duration\");\n }\n }\n\n @property({\n type: Number,\n attribute: \"trimstart\",\n converter: durationConverter,\n })\n private _trimStartMs: number | undefined = undefined;\n\n get trimStartMs() {\n if (this._trimStartMs === undefined) {\n return undefined;\n }\n return Math.min(\n Math.max(this._trimStartMs, 0),\n this.intrinsicDurationMs ?? 0,\n );\n }\n\n set trimStartMs(value: number | undefined) {\n this._trimStartMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"trimend\",\n converter: durationConverter,\n })\n private _trimEndMs: number | undefined = undefined;\n\n get trimEndMs() {\n if (this._trimEndMs === undefined) {\n return undefined;\n }\n return Math.min(this._trimEndMs, this.intrinsicDurationMs ?? 0);\n }\n\n set trimEndMs(value: number | undefined) {\n this._trimEndMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"sourcein\",\n converter: durationConverter,\n })\n private _sourceInMs: number | undefined = undefined;\n\n get sourceInMs() {\n if (this._sourceInMs === undefined) {\n return undefined;\n }\n return Math.max(this._sourceInMs, 0);\n }\n\n set sourceInMs(value: number | undefined) {\n this._sourceInMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"sourceout\",\n converter: durationConverter,\n })\n private _sourceOutMs: number | undefined = undefined;\n\n get sourceOutMs() {\n if (this._sourceOutMs === undefined) {\n return undefined;\n }\n if (\n this.intrinsicDurationMs &&\n this._sourceOutMs > this.intrinsicDurationMs\n ) {\n return this.intrinsicDurationMs;\n }\n return Math.max(this._sourceOutMs, 0);\n }\n\n set sourceOutMs(value: number | undefined) {\n this._sourceOutMs = value;\n }\n\n override updated(changedProperties: Map<PropertyKey, unknown>): void {\n super.updated?.(changedProperties);\n\n // Re-render the current frame when source-mapping or trim properties\n // change (the timeline-to-source mapping is different, so the visible\n // frame changes even though currentTimeMs hasn't).\n // Also render the initial frame when a standalone root becomes ready.\n const sourceChanged =\n changedProperties.has(\"_sourceInMs\") ||\n changedProperties.has(\"_sourceOutMs\");\n const trimChanged =\n changedProperties.has(\"_trimStartMs\") ||\n changedProperties.has(\"_trimEndMs\");\n const becameReady =\n changedProperties.has(\"contentReadyState\") &&\n changedProperties.get(\"contentReadyState\") !== \"ready\" &&\n this.contentReadyState === \"ready\";\n\n if (sourceChanged || trimChanged || becameReady) {\n if (this.rootTimegroup) {\n this.rootTimegroup.requestFrameRender();\n } else if (this.playbackController) {\n this.playbackController.runThrottledFrameTask();\n }\n }\n }\n\n @property({\n type: Number,\n attribute: \"startoffset\",\n converter: durationConverter,\n })\n private _startOffsetMs = 0;\n public get startOffsetMs(): number {\n return this._startOffsetMs;\n }\n\n @state()\n rootTimegroup?: EFTimegroup = this.getRootTimegroup();\n\n private getRootTimegroup(): EFTimegroup | undefined {\n let parent =\n this.tagName === \"EF-TIMEGROUP\" ? this : this.parentTimegroup;\n while (parent?.parentTimegroup) {\n parent = parent.parentTimegroup;\n }\n return parent as EFTimegroup | undefined;\n }\n\n get hasExplicitDuration() {\n return this._durationMs !== undefined;\n }\n\n get explicitDurationMs() {\n if (this.hasExplicitDuration) {\n return this._durationMs;\n }\n return undefined;\n }\n\n get hasOwnDuration() {\n return this.intrinsicDurationMs !== undefined || this.hasExplicitDuration;\n }\n\n get intrinsicDurationMs() {\n return undefined;\n }\n\n get durationMs() {\n // Prevent infinite loops: don't call parent.durationMs if parent is currently calculating\n // Lazy import to break circular dependency: EFTemporal -> EFTimegroup -> EFMedia -> EFTemporal\n const isTimegroupCalculatingDuration =\n getIsTimegroupCalculatingDuration();\n const parentDurationMs = isTimegroupCalculatingDuration(\n this.parentTimegroup,\n )\n ? undefined\n : this.parentTimegroup?.durationMs;\n const durationSource = determineDurationSource(\n this.intrinsicDurationMs,\n this._durationMs,\n parentDurationMs,\n );\n\n const modification = determineDurationModificationStrategy(\n this.trimStartMs,\n this.trimEndMs,\n this.sourceInMs,\n this.sourceOutMs,\n );\n\n return evaluateModifiedDuration(\n durationSource.baseDurationMs,\n modification,\n );\n }\n\n get sourceStartMs() {\n return this.trimStartMs ?? this.sourceInMs ?? 0;\n }\n\n #offsetMs() {\n return this._offsetMs || 0;\n }\n\n #parentTemporal() {\n let parent = this.parentElement;\n while (parent && !isEFTemporal(parent)) {\n parent = parent.parentElement;\n }\n return parent;\n }\n\n /**\n * The start time of the element within its parent timegroup.\n */\n get startTimeWithinParentMs() {\n const parent = this.#parentTemporal();\n if (!parent) {\n return 0;\n }\n return this.startTimeMs - parent.startTimeMs;\n }\n\n #loop = false;\n\n get startTimeMs(): number {\n const cachedStartTime = startTimeMsCache.get(this);\n if (cachedStartTime !== undefined) {\n return cachedStartTime;\n }\n\n const startTime = evaluateStartTime(\n this as InstanceType<Constructor<TemporalMixinInterface> & T>,\n this.parentTimegroup,\n this.#offsetMs(),\n (parent) => shallowGetTemporalElements(parent),\n );\n\n startTimeMsCache.set(this, startTime);\n return startTime;\n }\n\n get endTimeMs(): number {\n return this.startTimeMs + this.durationMs;\n }\n\n #currentTimeMs = 0;\n\n /**\n * Set the base local time (ms) used by ownCurrentTimeMs when no playback\n * controller is present. Called by EFTimegroup.seekForRender() to keep the\n * mixin's internal time in sync with the timegroup's own time state.\n * @internal\n */\n _setLocalTimeMs(value: number) {\n this.#currentTimeMs = value;\n }\n\n /**\n * The current time of the element within itself.\n * Compare with `currentTimeMs` to see the current time with respect to the root timegroup\n */\n get ownCurrentTimeMs(): number {\n const timeSource = determineCurrentTimeSource(\n this.playbackController,\n this.rootTimegroup,\n this.rootTimegroup === (this as any as EFTimegroup),\n this.#currentTimeMs,\n this.startTimeMs,\n this.durationMs,\n );\n return timeSource.timeMs;\n }\n\n /**\n * Element's current time for progress calculation.\n * Non-timegroup temporal elements use their local time (ownCurrentTimeMs)\n */\n get currentTimeMs() {\n return this.ownCurrentTimeMs;\n }\n\n set currentTimeMs(value: number) {\n const role = determineTemporalRole(this.parentTimegroup);\n\n // Apply current time based on role\n switch (role) {\n case \"root\":\n if (this.playbackController) {\n this.playbackController.currentTime = value / 1000;\n } else {\n this.#currentTimeMs = value;\n this.requestUpdate(\"currentTimeMs\");\n }\n break;\n case \"child\":\n if (\n this.rootTimegroup &&\n this.rootTimegroup !== (this as any as EFTimegroup)\n ) {\n this.rootTimegroup.currentTimeMs = value;\n } else {\n this.#currentTimeMs = value;\n this.requestUpdate(\"currentTimeMs\");\n }\n break;\n }\n }\n\n /**\n * Used to calculate the internal currentTimeMs of the element. This is useful\n * for mapping to internal media time codes for audio/video elements.\n */\n get currentSourceTimeMs() {\n const leadingTrimMs = this.sourceInMs || this.trimStartMs || 0;\n return this.ownCurrentTimeMs + leadingTrimMs;\n }\n\n didBecomeRoot() {\n // Don't create PlaybackController if:\n // 1. Explicitly disabled via attribute (e.g., for render clones)\n // 2. Already exists\n // 3. In headless rendering mode (EF_FRAMEGEN active)\n const noPlayback = (this as any).hasAttribute?.(\n \"data-no-playback-controller\",\n );\n const isRendering =\n typeof window !== \"undefined\" && \"FRAMEGEN_BRIDGE\" in window;\n if (noPlayback || this.playbackController || isRendering) {\n return;\n }\n\n this.playbackController = new PlaybackController(this as any);\n if (this.#loop) {\n this.playbackController.setLoop(this.#loop);\n }\n }\n\n didBecomeChild() {\n if (this.playbackController) {\n this.playbackController.remove();\n this.playbackController = undefined;\n }\n }\n }\n\n Object.defineProperty(TemporalMixinClass.prototype, EF_TEMPORAL, {\n value: true,\n });\n\n return TemporalMixinClass as unknown as Constructor<TemporalMixinInterface> &\n T;\n};\n"],"mappings":"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"}
|
|
@@ -16,6 +16,7 @@ declare class EFText extends EFText_base {
|
|
|
16
16
|
private validateStagger;
|
|
17
17
|
easing: string;
|
|
18
18
|
private mutationObserver?;
|
|
19
|
+
private animationObserver?;
|
|
19
20
|
private lastTextContent;
|
|
20
21
|
private _textContent;
|
|
21
22
|
private _templateElement;
|
|
@@ -38,6 +39,8 @@ declare class EFText extends EFText_base {
|
|
|
38
39
|
disconnectedCallback(): void;
|
|
39
40
|
protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
|
|
40
41
|
private setupMutationObserver;
|
|
42
|
+
private setupAnimationObserver;
|
|
43
|
+
private propagateAnimationToSegments;
|
|
41
44
|
private getTextContent;
|
|
42
45
|
private splitText;
|
|
43
46
|
private detectWordBoundaries;
|
package/dist/elements/EFText.js
CHANGED
|
@@ -7,6 +7,14 @@ import { LitElement, css, html } from "lit";
|
|
|
7
7
|
import { customElement, property } from "lit/decorators.js";
|
|
8
8
|
|
|
9
9
|
//#region src/elements/EFText.ts
|
|
10
|
+
const animationPropsToPropagate = [
|
|
11
|
+
"animation-name",
|
|
12
|
+
"animation-duration",
|
|
13
|
+
"animation-timing-function",
|
|
14
|
+
"animation-fill-mode",
|
|
15
|
+
"animation-iteration-count",
|
|
16
|
+
"animation-direction"
|
|
17
|
+
];
|
|
10
18
|
let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
11
19
|
constructor(..._args) {
|
|
12
20
|
super(..._args);
|
|
@@ -20,26 +28,10 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
|
20
28
|
static {
|
|
21
29
|
this.styles = [css`
|
|
22
30
|
:host {
|
|
23
|
-
display: inline
|
|
24
|
-
white-space: normal;
|
|
25
|
-
line-height: 1;
|
|
26
|
-
gap: 0;
|
|
27
|
-
}
|
|
28
|
-
:host([split="char"]) {
|
|
29
|
-
white-space: pre;
|
|
31
|
+
display: inline;
|
|
30
32
|
}
|
|
31
33
|
:host([split="line"]) {
|
|
32
|
-
display: flex;
|
|
33
|
-
flex-direction: column;
|
|
34
|
-
}
|
|
35
|
-
::slotted(*) {
|
|
36
|
-
display: inline-block;
|
|
37
|
-
margin: 0;
|
|
38
|
-
padding: 0;
|
|
39
|
-
}
|
|
40
|
-
.ef-word-wrapper {
|
|
41
34
|
display: inline-block;
|
|
42
|
-
white-space: nowrap;
|
|
43
35
|
}
|
|
44
36
|
`];
|
|
45
37
|
}
|
|
@@ -57,6 +49,7 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
|
57
49
|
return value;
|
|
58
50
|
}
|
|
59
51
|
#segmentsInitialized = false;
|
|
52
|
+
#lastPropagatedAnimation = "";
|
|
60
53
|
render() {
|
|
61
54
|
return html`<slot></slot>`;
|
|
62
55
|
}
|
|
@@ -72,7 +65,10 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
|
72
65
|
const textNode = document.createTextNode(newValue);
|
|
73
66
|
this.appendChild(textNode);
|
|
74
67
|
}
|
|
75
|
-
if (this.isConnected)
|
|
68
|
+
if (this.isConnected) {
|
|
69
|
+
this.emitContentChange("content");
|
|
70
|
+
this.splitText();
|
|
71
|
+
}
|
|
76
72
|
}
|
|
77
73
|
}
|
|
78
74
|
get textContent() {
|
|
@@ -132,15 +128,20 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
|
132
128
|
}
|
|
133
129
|
requestAnimationFrame(() => {
|
|
134
130
|
this.setupMutationObserver();
|
|
131
|
+
this.setupAnimationObserver();
|
|
135
132
|
this.splitText();
|
|
136
133
|
});
|
|
137
134
|
}
|
|
138
135
|
disconnectedCallback() {
|
|
139
136
|
super.disconnectedCallback();
|
|
140
137
|
this.mutationObserver?.disconnect();
|
|
138
|
+
this.animationObserver?.disconnect();
|
|
141
139
|
}
|
|
142
140
|
updated(changedProperties) {
|
|
143
|
-
if (changedProperties.has("split") || changedProperties.has("staggerMs") || changedProperties.has("easing") || changedProperties.has("durationMs"))
|
|
141
|
+
if (changedProperties.has("split") || changedProperties.has("staggerMs") || changedProperties.has("easing") || changedProperties.has("durationMs")) {
|
|
142
|
+
this.emitContentChange("content");
|
|
143
|
+
this.splitText();
|
|
144
|
+
}
|
|
144
145
|
}
|
|
145
146
|
setupMutationObserver() {
|
|
146
147
|
this.mutationObserver = new MutationObserver(() => {
|
|
@@ -157,6 +158,34 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
|
157
158
|
subtree: true
|
|
158
159
|
});
|
|
159
160
|
}
|
|
161
|
+
setupAnimationObserver() {
|
|
162
|
+
this.animationObserver = new MutationObserver(() => {
|
|
163
|
+
this.propagateAnimationToSegments();
|
|
164
|
+
});
|
|
165
|
+
this.animationObserver.observe(this, {
|
|
166
|
+
attributes: true,
|
|
167
|
+
attributeFilter: ["class", "style"]
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
propagateAnimationToSegments() {
|
|
171
|
+
const segments = this.segments;
|
|
172
|
+
if (segments.length === 0) return;
|
|
173
|
+
const computed = window.getComputedStyle(this);
|
|
174
|
+
const animationName = computed.animationName;
|
|
175
|
+
const fingerprint = animationPropsToPropagate.map((prop) => computed.getPropertyValue(prop)).join("|");
|
|
176
|
+
if (fingerprint === this.#lastPropagatedAnimation) return;
|
|
177
|
+
this.#lastPropagatedAnimation = fingerprint;
|
|
178
|
+
const hasAnimation = animationName && animationName !== "none";
|
|
179
|
+
const isLineMode = this.split === "line";
|
|
180
|
+
for (const segment of segments) if (hasAnimation) {
|
|
181
|
+
const isWhitespace = /^\s+$/.test(segment.segmentText || "");
|
|
182
|
+
if (!isLineMode && !isWhitespace) segment.setAttribute("data-animated", "");
|
|
183
|
+
for (const prop of animationPropsToPropagate) segment.style.setProperty(prop, computed.getPropertyValue(prop));
|
|
184
|
+
} else {
|
|
185
|
+
segment.removeAttribute("data-animated");
|
|
186
|
+
for (const prop of animationPropsToPropagate) segment.style.removeProperty(prop);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
160
189
|
getTextContent() {
|
|
161
190
|
let text = "";
|
|
162
191
|
for (const node of Array.from(this.childNodes)) if (node.nodeType === Node.TEXT_NODE) text += node.textContent || "";
|
|
@@ -182,6 +211,7 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
|
182
211
|
const text = this._textContent !== null ? this._textContent : this.getTextContent();
|
|
183
212
|
const trimmedText = text.trim();
|
|
184
213
|
const textStartOffset = text.indexOf(trimmedText);
|
|
214
|
+
if (this.#segmentsInitialized && this.segments.length > 0 && this.lastTextContent === text) return;
|
|
185
215
|
if (!text || trimmedText.length === 0) {
|
|
186
216
|
const existingSegments = Array.from(this.querySelectorAll("ef-text-segment"));
|
|
187
217
|
for (const segment of existingSegments) segment.remove();
|
|
@@ -243,7 +273,7 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
|
243
273
|
if (currentWordSpan) fragment.appendChild(currentWordSpan);
|
|
244
274
|
currentWordIndex = wordIndex;
|
|
245
275
|
currentWordSpan = document.createElement("span");
|
|
246
|
-
currentWordSpan.
|
|
276
|
+
currentWordSpan.style.whiteSpace = "nowrap";
|
|
247
277
|
}
|
|
248
278
|
if (currentWordSpan) currentWordSpan.appendChild(segment);
|
|
249
279
|
else fragment.appendChild(segment);
|
|
@@ -275,7 +305,7 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
|
275
305
|
if (currentWordSpan) fragment.appendChild(currentWordSpan);
|
|
276
306
|
currentWordIndex = wordIndex;
|
|
277
307
|
currentWordSpan = document.createElement("span");
|
|
278
|
-
currentWordSpan.
|
|
308
|
+
currentWordSpan.style.whiteSpace = "nowrap";
|
|
279
309
|
}
|
|
280
310
|
if (currentWordSpan) currentWordSpan.appendChild(segment);
|
|
281
311
|
else fragment.appendChild(segment);
|
|
@@ -303,12 +333,15 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
|
303
333
|
}
|
|
304
334
|
this.appendChild(fragment);
|
|
305
335
|
if (templateToPreserve) this.appendChild(templateToPreserve);
|
|
336
|
+
this.#lastPropagatedAnimation = "";
|
|
337
|
+
this.propagateAnimationToSegments();
|
|
306
338
|
const segmentElements = this.segments;
|
|
307
339
|
Promise.all(segmentElements.map((seg) => seg.updateComplete)).then(() => {
|
|
308
340
|
updateAnimations(this.rootTimegroup || this);
|
|
309
341
|
});
|
|
310
342
|
this.lastTextContent = text;
|
|
311
343
|
this._textContent = text;
|
|
344
|
+
this.#segmentsInitialized = true;
|
|
312
345
|
this._segmentsReadyResolvers.forEach((resolve) => {
|
|
313
346
|
resolve();
|
|
314
347
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFText.js","names":["EFText","textNodes: ChildNode[]","#segmentsInitialized","segments","wordBoundaries: Map<number, number> | null","currentWordIndex: number | null","currentWordSpan: HTMLSpanElement | null","staggerOffset: number | undefined","wordIndexForStagger: number","result: string[]"],"sources":["../../src/elements/EFText.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport type { EFTimegroup } from \"./EFTimegroup.js\";\nimport { evaluateEasing } from \"./easingUtils.js\";\nimport type { EFTextSegment } from \"./EFTextSegment.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\nimport type { AnimatableElement } from \"./updateAnimations.js\";\n\nexport type SplitMode = \"line\" | \"word\" | \"char\";\n\n@customElement(\"ef-text\")\nexport class EFText extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-flex;\n white-space: normal;\n line-height: 1;\n gap: 0;\n }\n :host([split=\"char\"]) {\n white-space: pre;\n }\n :host([split=\"line\"]) {\n display: flex;\n flex-direction: column;\n }\n ::slotted(*) {\n display: inline-block;\n margin: 0;\n padding: 0;\n }\n .ef-word-wrapper {\n display: inline-block;\n white-space: nowrap;\n }\n `,\n ];\n\n @property({ type: String, reflect: true })\n split: SplitMode = \"word\";\n\n private validateSplit(value: string): SplitMode {\n if (value === \"line\" || value === \"word\" || value === \"char\") {\n return value as SplitMode;\n }\n console.warn(\n `Invalid split value \"${value}\". Must be \"line\", \"word\", or \"char\". Defaulting to \"word\".`,\n );\n return \"word\";\n }\n\n @property({\n type: Number,\n attribute: \"stagger\",\n converter: durationConverter,\n })\n staggerMs?: number;\n\n private validateStagger(value: number | undefined): number | undefined {\n if (value === undefined) return undefined;\n if (value < 0) {\n console.warn(`Invalid stagger value ${value}ms. Must be >= 0. Using 0.`);\n return 0;\n }\n return value;\n }\n\n @property({ type: String, reflect: true })\n easing = \"linear\";\n\n private mutationObserver?: MutationObserver;\n private lastTextContent = \"\";\n private _textContent: string | null = null; // null means not initialized, \"\" means explicitly empty\n private _templateElement: HTMLTemplateElement | null = null;\n private _segmentsReadyResolvers: Array<() => void> = [];\n #segmentsInitialized = false;\n\n render() {\n return html`<slot></slot>`;\n }\n\n // Store text content so we can use it even after DOM is cleared\n set textContent(value: string | null) {\n const newValue = value || \"\";\n // Only update if value actually changed\n if (this._textContent !== newValue) {\n this._textContent = newValue;\n\n // Find template element if not already stored\n if (!this._templateElement && this.isConnected) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Clear any existing text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n // Add new text node if value is not empty\n if (newValue) {\n const textNode = document.createTextNode(newValue);\n this.appendChild(textNode);\n }\n // Trigger re-split\n if (this.isConnected) {\n this.splitText();\n }\n }\n }\n\n get textContent(): string {\n // If _textContent is null, it hasn't been initialized - read from DOM\n if (this._textContent === null) {\n return this.getTextContent();\n }\n // Otherwise use stored value (even if empty string)\n return this._textContent;\n }\n\n /**\n * Get all ef-text-segment elements directly\n * @public\n */\n get segments(): EFTextSegment[] {\n return Array.from(\n this.querySelectorAll(\"ef-text-segment[data-segment-created]\"),\n ) as EFTextSegment[];\n }\n\n /**\n * Returns a promise that resolves when segments are ready (created and connected)\n * Use this to wait for segments after setting textContent or other properties\n * @public\n */\n async whenSegmentsReady(): Promise<EFTextSegment[]> {\n // Wait for text element to be updated first\n await this.updateComplete;\n\n // If no text content, segments will be empty - return immediately\n // Use same logic as splitText to read text content\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return [];\n }\n\n // If segments already initialized and exist, return immediately (no RAF waits)\n if (this.#segmentsInitialized && this.segments.length > 0) {\n await Promise.all(this.segments.map((seg) => seg.updateComplete));\n return this.segments;\n }\n\n // Check if segments are already in DOM (synchronous check - no RAF)\n let segments = this.segments;\n if (segments.length > 0) {\n // Segments exist, just wait for their Lit updates (no RAF)\n await Promise.all(segments.map((seg) => seg.updateComplete));\n this.#segmentsInitialized = true;\n return segments;\n }\n\n // Segments don't exist yet - use the promise-based mechanism (no RAF polling)\n // This waits for splitText() to complete and resolve the promise\n return new Promise<EFTextSegment[]>((resolve, reject) => {\n const timeout = setTimeout(() => {\n // Remove our resolver if we timeout\n const index = this._segmentsReadyResolvers.indexOf(resolveWithSegments);\n if (index > -1) {\n this._segmentsReadyResolvers.splice(index, 1);\n }\n reject(new Error('Timeout waiting for text segments to be created'));\n }, 5000); // 5 second timeout\n\n const resolveWithSegments = () => {\n clearTimeout(timeout);\n // Wait for segment Lit updates\n const segments = this.segments;\n Promise.all(segments.map((seg) => seg.updateComplete)).then(() => {\n this.#segmentsInitialized = true;\n resolve(segments);\n });\n };\n\n this._segmentsReadyResolvers.push(resolveWithSegments);\n \n // Trigger splitText if it hasn't run yet\n // This handles the case where segments haven't been created at all\n if (segments.length === 0 && this.isConnected) {\n this.splitText();\n }\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n // Find and store template element before any modifications\n this._templateElement = this.querySelector(\"template\");\n\n // Initialize _textContent from DOM if not already set (for declarative usage)\n if (this._textContent === null) {\n this._textContent = this.getTextContent();\n this.lastTextContent = this._textContent;\n }\n\n // Use requestAnimationFrame to ensure DOM is ready\n requestAnimationFrame(() => {\n this.setupMutationObserver();\n this.splitText();\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.mutationObserver?.disconnect();\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n if (\n changedProperties.has(\"split\") ||\n changedProperties.has(\"staggerMs\") ||\n changedProperties.has(\"easing\") ||\n changedProperties.has(\"durationMs\")\n ) {\n this.splitText();\n }\n }\n\n private setupMutationObserver() {\n this.mutationObserver = new MutationObserver(() => {\n // Only react to changes that aren't from our own segment creation\n const currentText = this._textContent || this.getTextContent();\n if (currentText !== this.lastTextContent) {\n this._textContent = currentText;\n this.lastTextContent = currentText;\n this.splitText();\n }\n });\n\n this.mutationObserver.observe(this, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n\n private getTextContent(): string {\n // Get text content, handling both text nodes and HTML content\n let text = \"\";\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent || \"\";\n } else if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Skip ef-text-segment elements (they're created by us)\n if (element.tagName === \"EF-TEXT-SEGMENT\") {\n continue;\n }\n // Skip template elements (they're templates, not content)\n if (element.tagName === \"TEMPLATE\") {\n continue;\n }\n text += element.textContent || \"\";\n }\n }\n return text;\n }\n\n private splitText() {\n // Validate split mode\n const validatedSplit = this.validateSplit(this.split);\n if (validatedSplit !== this.split) {\n this.split = validatedSplit;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Validate stagger\n const validatedStagger = this.validateStagger(this.staggerMs);\n if (validatedStagger !== this.staggerMs) {\n this.staggerMs = validatedStagger;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Read text content - use stored _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n const trimmedText = text.trim();\n const textStartOffset = text.indexOf(trimmedText);\n if (!text || trimmedText.length === 0) {\n // Clear segments if no text\n const existingSegments = Array.from(\n this.querySelectorAll(\"ef-text-segment\"),\n );\n for (const segment of existingSegments) {\n segment.remove();\n }\n // Clear text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n this.lastTextContent = \"\";\n // Resolve any waiting promises\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n // Reset initialization flag when clearing segments\n this.#segmentsInitialized = false;\n return;\n }\n\n // Reset initialization flag when we're about to create new segments\n this.#segmentsInitialized = false;\n\n const segments = this.splitTextIntoSegments(text);\n const durationMs = this.durationMs || 1000; // Default 1 second if no duration\n\n // For character mode, detect word boundaries to wrap characters within words\n let wordBoundaries: Map<number, number> | null = null;\n if (this.split === \"char\") {\n wordBoundaries = this.detectWordBoundaries(text);\n }\n\n // Clear ALL child nodes (text nodes and segments) by replacing innerHTML\n // This ensures we don't have any leftover text nodes\n const fragment = document.createDocumentFragment();\n\n // Find template element if not already stored\n if (!this._templateElement) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Get template content structure\n // If template exists, clone it; otherwise create default ef-text-segment\n const templateContent = this._templateElement?.content;\n const templateSegments = templateContent\n ? Array.from(templateContent.querySelectorAll(\"ef-text-segment\"))\n : [];\n\n // If no template segments found, we'll create a default one\n const useTemplate = templateSegments.length > 0;\n const segmentsPerTextSegment = useTemplate ? templateSegments.length : 1;\n\n // For character mode with word wrapping, track current word and wrap segments\n let currentWordIndex: number | null = null;\n let currentWordSpan: HTMLSpanElement | null = null;\n let charIndex = 0; // Track position in original text for character mode\n\n // For word splitting, count only word segments (not whitespace) for stagger calculation\n const wordOnlySegments =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg))\n : segments;\n const wordSegmentCount = wordOnlySegments.length;\n\n // Track word index as we iterate (for word mode with duplicate words)\n // This ensures each occurrence of duplicate words gets a unique stagger index\n let wordStaggerIndex = 0;\n\n // Create new segments in a fragment first\n segments.forEach((segmentText, textIndex) => {\n // Calculate stagger offset if stagger is set\n let staggerOffset: number | undefined;\n if (this.staggerMs !== undefined) {\n // For word splitting, whitespace segments should inherit stagger from preceding word\n const isWhitespace = /^\\s+$/.test(segmentText);\n let wordIndexForStagger: number;\n\n if (this.split === \"word\" && isWhitespace) {\n // Whitespace inherits from the preceding word's index\n // Use the word stagger index (which is the index of the word before this whitespace)\n wordIndexForStagger = Math.max(0, wordStaggerIndex - 1);\n } else if (this.split === \"word\") {\n // For word mode, use the current word stagger index (incremented for each word encountered)\n // This ensures duplicate words get unique indices based on their position\n wordIndexForStagger = wordStaggerIndex;\n wordStaggerIndex++;\n } else {\n // For char/line mode, use the actual position in segments array\n wordIndexForStagger = textIndex;\n }\n\n // Apply easing to the stagger offset\n // Normalize index to 0-1 range (0 for first segment, 1 for last segment)\n const normalizedProgress =\n wordSegmentCount > 1\n ? wordIndexForStagger / (wordSegmentCount - 1)\n : 0;\n\n // Apply easing function to get eased progress\n const easedProgress = evaluateEasing(this.easing, normalizedProgress);\n\n // Calculate total stagger duration (last segment gets full stagger)\n const totalStaggerDuration = (wordSegmentCount - 1) * this.staggerMs;\n\n // Apply eased progress to total stagger duration\n staggerOffset = easedProgress * totalStaggerDuration;\n }\n\n if (useTemplate && templateContent) {\n // Clone template content for each text segment\n // This allows multiple ef-text-segment elements per character/word/line\n const clonedContent = templateContent.cloneNode(\n true,\n ) as DocumentFragment;\n const clonedSegments = Array.from(\n clonedContent.querySelectorAll(\"ef-text-segment\"),\n ) as EFTextSegment[];\n\n clonedSegments.forEach((segment, templateIndex) => {\n // Set properties - Lit will process these when element is connected\n segment.segmentText = segmentText;\n // Calculate segment index accounting for multiple segments per text segment\n segment.segmentIndex =\n textIndex * segmentsPerTextSegment + templateIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode with templates, also wrap in word spans\n if (this.split === \"char\" && wordBoundaries) {\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n if (wordIndex !== currentWordIndex) {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.className = \"ef-word-wrapper\";\n }\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n charIndex += segmentText.length;\n } else {\n fragment.appendChild(segment);\n }\n });\n } else {\n // No template - create default ef-text-segment\n const segment = document.createElement(\n \"ef-text-segment\",\n ) as EFTextSegment;\n\n segment.segmentText = segmentText;\n segment.segmentIndex = textIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode, wrap segments within words to prevent line breaks\n if (this.split === \"char\" && wordBoundaries) {\n // Map character index in trimmed text to original text position\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n // Check if we're starting a new word\n if (wordIndex !== currentWordIndex) {\n // Close previous word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n // Start new word span\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.className = \"ef-word-wrapper\";\n }\n // Append segment to current word span\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n // Not part of a word (whitespace/punctuation) - append directly\n // Close current word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n // Update character index for next iteration (in trimmed text)\n charIndex += segmentText.length;\n } else {\n // Not character mode or no word boundaries - append directly\n fragment.appendChild(segment);\n }\n }\n });\n\n // Close any remaining word span\n if (this.split === \"char\" && currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n\n // Ensure segments are connected to DOM before checking for animations\n // Append fragment first, then trigger updates\n\n // Replace all children with the fragment (this clears text nodes and old segments)\n // But preserve the template element if it exists\n const templateToPreserve = this._templateElement;\n while (this.firstChild) {\n const child = this.firstChild;\n // Don't remove the template element\n if (child === templateToPreserve) {\n // Skip template, but we need to move it after the fragment\n // So we'll remove it temporarily and re-add it after\n this.removeChild(child);\n continue;\n }\n this.removeChild(child);\n }\n this.appendChild(fragment);\n // Re-add template element if it existed\n if (templateToPreserve) {\n this.appendChild(templateToPreserve);\n }\n\n // Segments will pause their own animations in connectedCallback\n // Lit will automatically update them when they're connected to the DOM\n // Ensure segments are updated after being connected\n const segmentElements = this.segments;\n Promise.all(segmentElements.map((seg) => seg.updateComplete)).then(() => {\n const rootTimegroup = this.rootTimegroup || this;\n updateAnimations(rootTimegroup as AnimatableElement);\n });\n\n this.lastTextContent = text;\n this._textContent = text;\n\n // Resolve any waiting promises after segments are connected (synchronous)\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n }\n\n private detectWordBoundaries(text: string): Map<number, number> {\n // Create a map from character index to word index\n // Characters within the same word will have the same word index\n const boundaries = new Map<number, number>();\n const trimmedText = text.trim();\n if (!trimmedText) {\n return boundaries;\n }\n\n // Use Intl.Segmenter to detect word boundaries\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n\n // Find the offset of trimmedText within the original text\n const textStart = text.indexOf(trimmedText);\n\n let wordIndex = 0;\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Map all character positions in this word to the same word index\n for (let i = 0; i < seg.segment.length; i++) {\n const charPos = textStart + seg.index + i;\n boundaries.set(charPos, wordIndex);\n }\n wordIndex++;\n }\n }\n\n return boundaries;\n }\n\n private splitTextIntoSegments(text: string): string[] {\n // Trim text before segmenting to remove leading/trailing whitespace\n const trimmedText = text.trim();\n if (!trimmedText) {\n return [];\n }\n\n switch (this.split) {\n case \"line\": {\n // Split on newlines and trim each line\n const lines = trimmedText.split(/\\r?\\n/);\n return lines\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n }\n case \"word\": {\n // Use Intl.Segmenter for locale-aware word segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n const result: string[] = [];\n\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Word-like segment - add it\n result.push(seg.segment);\n } else if (/^\\s+$/.test(seg.segment)) {\n // Whitespace segment - add it as-is\n result.push(seg.segment);\n } else {\n // Punctuation segment - attach to preceding word if it exists\n if (result.length > 0) {\n const lastItem = result[result.length - 1];\n if (lastItem && !/^\\s+$/.test(lastItem)) {\n result[result.length - 1] = lastItem + seg.segment;\n } else {\n result.push(seg.segment);\n }\n } else {\n result.push(seg.segment);\n }\n }\n }\n\n return result;\n }\n case \"char\": {\n // Use Intl.Segmenter for grapheme-aware character segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"grapheme\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n return segments.map((seg) => seg.segment);\n }\n default:\n return [trimmedText];\n }\n }\n\n get intrinsicDurationMs(): number | undefined {\n // If explicit duration is set, use it\n if (this.hasExplicitDuration) {\n return undefined; // Let explicit duration take precedence\n }\n\n // If we have a parent timegroup that dictates duration (fixed) or inherits it (fit),\n // we should inherit from it instead of using our intrinsic duration.\n // For 'sequence' and 'contain' modes, the parent relies on our intrinsic duration,\n // so we must provide it.\n if (this.parentTimegroup) {\n const mode = this.parentTimegroup.mode;\n if (mode === \"fixed\" || mode === \"fit\") {\n return undefined;\n }\n }\n\n // Otherwise, calculate from content\n // Use _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return 0;\n }\n\n // Use the same splitting logic as splitTextIntoSegments for consistency\n const segments = this.splitTextIntoSegments(text);\n // For word splitting, only count word segments (not whitespace) for intrinsic duration\n const segmentCount =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg)).length || 1\n : segments.length || 1;\n\n return segmentCount * 1000;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text\": EFText;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFText.js","names":["EFText","textNodes: ChildNode[]","#segmentsInitialized","segments","#lastPropagatedAnimation","wordBoundaries: Map<number, number> | null","currentWordIndex: number | null","currentWordSpan: HTMLSpanElement | null","staggerOffset: number | undefined","wordIndexForStagger: number","result: string[]"],"sources":["../../src/elements/EFText.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\n\nimport { evaluateEasing } from \"./easingUtils.js\";\nimport type { EFTextSegment } from \"./EFTextSegment.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\nimport type { AnimatableElement } from \"./updateAnimations.js\";\n\nexport type SplitMode = \"line\" | \"word\" | \"char\";\n\n// Animation properties to propagate from ef-text to its segments.\n// animation-delay is intentionally excluded -- the stagger system manages delay per segment.\nconst animationPropsToPropagate = [\n \"animation-name\",\n \"animation-duration\",\n \"animation-timing-function\",\n \"animation-fill-mode\",\n \"animation-iteration-count\",\n \"animation-direction\",\n] as const;\n\n@customElement(\"ef-text\")\nexport class EFText extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline;\n }\n :host([split=\"line\"]) {\n display: inline-block;\n }\n `,\n ];\n\n @property({ type: String, reflect: true })\n split: SplitMode = \"word\";\n\n private validateSplit(value: string): SplitMode {\n if (value === \"line\" || value === \"word\" || value === \"char\") {\n return value as SplitMode;\n }\n console.warn(\n `Invalid split value \"${value}\". Must be \"line\", \"word\", or \"char\". Defaulting to \"word\".`,\n );\n return \"word\";\n }\n\n @property({\n type: Number,\n attribute: \"stagger\",\n converter: durationConverter,\n })\n staggerMs?: number;\n\n private validateStagger(value: number | undefined): number | undefined {\n if (value === undefined) return undefined;\n if (value < 0) {\n console.warn(`Invalid stagger value ${value}ms. Must be >= 0. Using 0.`);\n return 0;\n }\n return value;\n }\n\n @property({ type: String, reflect: true })\n easing = \"linear\";\n\n private mutationObserver?: MutationObserver;\n private animationObserver?: MutationObserver;\n private lastTextContent = \"\";\n private _textContent: string | null = null; // null means not initialized, \"\" means explicitly empty\n private _templateElement: HTMLTemplateElement | null = null;\n private _segmentsReadyResolvers: Array<() => void> = [];\n #segmentsInitialized = false;\n #lastPropagatedAnimation = \"\";\n\n render() {\n return html`<slot></slot>`;\n }\n\n // Store text content so we can use it even after DOM is cleared\n set textContent(value: string | null) {\n const newValue = value || \"\";\n // Only update if value actually changed\n if (this._textContent !== newValue) {\n this._textContent = newValue;\n\n // Find template element if not already stored\n if (!this._templateElement && this.isConnected) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Clear any existing text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n // Add new text node if value is not empty\n if (newValue) {\n const textNode = document.createTextNode(newValue);\n this.appendChild(textNode);\n }\n if (this.isConnected) {\n this.emitContentChange(\"content\");\n this.splitText();\n }\n }\n }\n\n get textContent(): string {\n // If _textContent is null, it hasn't been initialized - read from DOM\n if (this._textContent === null) {\n return this.getTextContent();\n }\n // Otherwise use stored value (even if empty string)\n return this._textContent;\n }\n\n /**\n * Get all ef-text-segment elements directly\n * @public\n */\n get segments(): EFTextSegment[] {\n return Array.from(\n this.querySelectorAll(\"ef-text-segment[data-segment-created]\"),\n ) as EFTextSegment[];\n }\n\n /**\n * Returns a promise that resolves when segments are ready (created and connected)\n * Use this to wait for segments after setting textContent or other properties\n * @public\n */\n async whenSegmentsReady(): Promise<EFTextSegment[]> {\n // Wait for text element to be updated first\n await this.updateComplete;\n\n // If no text content, segments will be empty - return immediately\n // Use same logic as splitText to read text content\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return [];\n }\n\n // If segments already initialized and exist, return immediately (no RAF waits)\n if (this.#segmentsInitialized && this.segments.length > 0) {\n await Promise.all(this.segments.map((seg) => seg.updateComplete));\n return this.segments;\n }\n\n // Check if segments are already in DOM (synchronous check - no RAF)\n let segments = this.segments;\n if (segments.length > 0) {\n // Segments exist, just wait for their Lit updates (no RAF)\n await Promise.all(segments.map((seg) => seg.updateComplete));\n this.#segmentsInitialized = true;\n return segments;\n }\n\n // Segments don't exist yet - use the promise-based mechanism (no RAF polling)\n // This waits for splitText() to complete and resolve the promise\n return new Promise<EFTextSegment[]>((resolve, reject) => {\n const timeout = setTimeout(() => {\n // Remove our resolver if we timeout\n const index = this._segmentsReadyResolvers.indexOf(resolveWithSegments);\n if (index > -1) {\n this._segmentsReadyResolvers.splice(index, 1);\n }\n reject(new Error(\"Timeout waiting for text segments to be created\"));\n }, 5000); // 5 second timeout\n\n const resolveWithSegments = () => {\n clearTimeout(timeout);\n // Wait for segment Lit updates\n const segments = this.segments;\n Promise.all(segments.map((seg) => seg.updateComplete)).then(() => {\n this.#segmentsInitialized = true;\n resolve(segments);\n });\n };\n\n this._segmentsReadyResolvers.push(resolveWithSegments);\n\n // Trigger splitText if it hasn't run yet\n // This handles the case where segments haven't been created at all\n if (segments.length === 0 && this.isConnected) {\n this.splitText();\n }\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n // Find and store template element before any modifications\n this._templateElement = this.querySelector(\"template\");\n\n // Initialize _textContent from DOM if not already set (for declarative usage)\n if (this._textContent === null) {\n this._textContent = this.getTextContent();\n this.lastTextContent = this._textContent;\n }\n\n // Use RAF to ensure DOM is fully ready before splitting text\n // Callers that need segments immediately (e.g., render clones) should await whenSegmentsReady()\n requestAnimationFrame(() => {\n this.setupMutationObserver();\n this.setupAnimationObserver();\n this.splitText();\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.mutationObserver?.disconnect();\n this.animationObserver?.disconnect();\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n if (\n changedProperties.has(\"split\") ||\n changedProperties.has(\"staggerMs\") ||\n changedProperties.has(\"easing\") ||\n changedProperties.has(\"durationMs\")\n ) {\n this.emitContentChange(\"content\");\n this.splitText();\n }\n }\n\n private setupMutationObserver() {\n this.mutationObserver = new MutationObserver(() => {\n // Only react to changes that aren't from our own segment creation\n const currentText = this._textContent || this.getTextContent();\n if (currentText !== this.lastTextContent) {\n this._textContent = currentText;\n this.lastTextContent = currentText;\n this.splitText();\n }\n });\n\n this.mutationObserver.observe(this, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n\n private setupAnimationObserver() {\n this.animationObserver = new MutationObserver(() => {\n this.propagateAnimationToSegments();\n });\n\n this.animationObserver.observe(this, {\n attributes: true,\n attributeFilter: [\"class\", \"style\"],\n });\n }\n\n private propagateAnimationToSegments() {\n const segments = this.segments;\n if (segments.length === 0) return;\n\n const computed = window.getComputedStyle(this);\n const animationName = computed.animationName;\n\n // Build a fingerprint to avoid redundant work\n const fingerprint = animationPropsToPropagate\n .map((prop) => computed.getPropertyValue(prop))\n .join(\"|\");\n\n if (fingerprint === this.#lastPropagatedAnimation) return;\n this.#lastPropagatedAnimation = fingerprint;\n\n const hasAnimation = animationName && animationName !== \"none\";\n const isLineMode = this.split === \"line\";\n\n for (const segment of segments) {\n if (hasAnimation) {\n // Mark non-whitespace segments so shadow DOM rule promotes them to inline-block\n // for transform support. Using an attribute (not inline style) so it survives the\n // visibility system's removeProperty(\"display\") calls.\n // Whitespace-only segments must stay inline — inline-block creates a new block\n // formatting context that collapses the space to zero width.\n const isWhitespace = /^\\s+$/.test(segment.segmentText || \"\");\n if (!isLineMode && !isWhitespace) {\n segment.setAttribute(\"data-animated\", \"\");\n }\n for (const prop of animationPropsToPropagate) {\n segment.style.setProperty(prop, computed.getPropertyValue(prop));\n }\n } else {\n segment.removeAttribute(\"data-animated\");\n for (const prop of animationPropsToPropagate) {\n segment.style.removeProperty(prop);\n }\n }\n }\n }\n\n private getTextContent(): string {\n // Get text content, handling both text nodes and HTML content\n let text = \"\";\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent || \"\";\n } else if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Skip ef-text-segment elements (they're created by us)\n if (element.tagName === \"EF-TEXT-SEGMENT\") {\n continue;\n }\n // Skip template elements (they're templates, not content)\n if (element.tagName === \"TEMPLATE\") {\n continue;\n }\n text += element.textContent || \"\";\n }\n }\n return text;\n }\n\n private splitText() {\n // Validate split mode\n const validatedSplit = this.validateSplit(this.split);\n if (validatedSplit !== this.split) {\n this.split = validatedSplit;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Validate stagger\n const validatedStagger = this.validateStagger(this.staggerMs);\n if (validatedStagger !== this.staggerMs) {\n this.staggerMs = validatedStagger;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Read text content - use stored _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n const trimmedText = text.trim();\n const textStartOffset = text.indexOf(trimmedText);\n\n // GUARD: Check if segments are already correct before clearing/recreating\n // This prevents redundant splits from RAF callbacks, updated(), etc.\n if (\n this.#segmentsInitialized &&\n this.segments.length > 0 &&\n this.lastTextContent === text\n ) {\n return;\n }\n\n if (!text || trimmedText.length === 0) {\n // Clear segments if no text\n const existingSegments = Array.from(\n this.querySelectorAll(\"ef-text-segment\"),\n );\n for (const segment of existingSegments) {\n segment.remove();\n }\n // Clear text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n this.lastTextContent = \"\";\n // Resolve any waiting promises\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n // Reset initialization flag when clearing segments\n this.#segmentsInitialized = false;\n return;\n }\n\n // Reset initialization flag when we're about to create new segments\n this.#segmentsInitialized = false;\n\n const segments = this.splitTextIntoSegments(text);\n const durationMs = this.durationMs || 1000; // Default 1 second if no duration\n\n // For character mode, detect word boundaries to wrap characters within words\n let wordBoundaries: Map<number, number> | null = null;\n if (this.split === \"char\") {\n wordBoundaries = this.detectWordBoundaries(text);\n }\n\n // Clear ALL child nodes (text nodes and segments) by replacing innerHTML\n // This ensures we don't have any leftover text nodes\n const fragment = document.createDocumentFragment();\n\n // Find template element if not already stored\n if (!this._templateElement) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Get template content structure\n // If template exists, clone it; otherwise create default ef-text-segment\n const templateContent = this._templateElement?.content;\n const templateSegments = templateContent\n ? Array.from(templateContent.querySelectorAll(\"ef-text-segment\"))\n : [];\n\n // If no template segments found, we'll create a default one\n const useTemplate = templateSegments.length > 0;\n const segmentsPerTextSegment = useTemplate ? templateSegments.length : 1;\n\n // For character mode with word wrapping, track current word and wrap segments\n let currentWordIndex: number | null = null;\n let currentWordSpan: HTMLSpanElement | null = null;\n let charIndex = 0; // Track position in original text for character mode\n\n // For word splitting, count only word segments (not whitespace) for stagger calculation\n const wordOnlySegments =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg))\n : segments;\n const wordSegmentCount = wordOnlySegments.length;\n\n // Track word index as we iterate (for word mode with duplicate words)\n // This ensures each occurrence of duplicate words gets a unique stagger index\n let wordStaggerIndex = 0;\n\n // Create new segments in a fragment first\n segments.forEach((segmentText, textIndex) => {\n // Calculate stagger offset if stagger is set\n let staggerOffset: number | undefined;\n if (this.staggerMs !== undefined) {\n // For word splitting, whitespace segments should inherit stagger from preceding word\n const isWhitespace = /^\\s+$/.test(segmentText);\n let wordIndexForStagger: number;\n\n if (this.split === \"word\" && isWhitespace) {\n // Whitespace inherits from the preceding word's index\n // Use the word stagger index (which is the index of the word before this whitespace)\n wordIndexForStagger = Math.max(0, wordStaggerIndex - 1);\n } else if (this.split === \"word\") {\n // For word mode, use the current word stagger index (incremented for each word encountered)\n // This ensures duplicate words get unique indices based on their position\n wordIndexForStagger = wordStaggerIndex;\n wordStaggerIndex++;\n } else {\n // For char/line mode, use the actual position in segments array\n wordIndexForStagger = textIndex;\n }\n\n // Apply easing to the stagger offset\n // Normalize index to 0-1 range (0 for first segment, 1 for last segment)\n const normalizedProgress =\n wordSegmentCount > 1\n ? wordIndexForStagger / (wordSegmentCount - 1)\n : 0;\n\n // Apply easing function to get eased progress\n const easedProgress = evaluateEasing(this.easing, normalizedProgress);\n\n // Calculate total stagger duration (last segment gets full stagger)\n const totalStaggerDuration = (wordSegmentCount - 1) * this.staggerMs;\n\n // Apply eased progress to total stagger duration\n staggerOffset = easedProgress * totalStaggerDuration;\n }\n\n if (useTemplate && templateContent) {\n // Clone template content for each text segment\n // This allows multiple ef-text-segment elements per character/word/line\n const clonedContent = templateContent.cloneNode(\n true,\n ) as DocumentFragment;\n const clonedSegments = Array.from(\n clonedContent.querySelectorAll(\"ef-text-segment\"),\n ) as EFTextSegment[];\n\n clonedSegments.forEach((segment, templateIndex) => {\n // Set properties - Lit will process these when element is connected\n segment.segmentText = segmentText;\n // Calculate segment index accounting for multiple segments per text segment\n segment.segmentIndex =\n textIndex * segmentsPerTextSegment + templateIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode with templates, also wrap in word spans\n if (this.split === \"char\" && wordBoundaries) {\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n if (wordIndex !== currentWordIndex) {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.style.whiteSpace = \"nowrap\";\n }\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n charIndex += segmentText.length;\n } else {\n fragment.appendChild(segment);\n }\n });\n } else {\n // No template - create default ef-text-segment\n const segment = document.createElement(\n \"ef-text-segment\",\n ) as EFTextSegment;\n\n segment.segmentText = segmentText;\n segment.segmentIndex = textIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode, wrap segments within words to prevent line breaks\n if (this.split === \"char\" && wordBoundaries) {\n // Map character index in trimmed text to original text position\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n // Check if we're starting a new word\n if (wordIndex !== currentWordIndex) {\n // Close previous word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n // Start new word span\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.style.whiteSpace = \"nowrap\";\n }\n // Append segment to current word span\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n // Not part of a word (whitespace/punctuation) - append directly\n // Close current word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n // Update character index for next iteration (in trimmed text)\n charIndex += segmentText.length;\n } else {\n // Not character mode or no word boundaries - append directly\n fragment.appendChild(segment);\n }\n }\n });\n\n // Close any remaining word span\n if (this.split === \"char\" && currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n\n // Ensure segments are connected to DOM before checking for animations\n // Append fragment first, then trigger updates\n\n // Replace all children with the fragment (this clears text nodes and old segments)\n // But preserve the template element if it exists\n const templateToPreserve = this._templateElement;\n while (this.firstChild) {\n const child = this.firstChild;\n // Don't remove the template element\n if (child === templateToPreserve) {\n // Skip template, but we need to move it after the fragment\n // So we'll remove it temporarily and re-add it after\n this.removeChild(child);\n continue;\n }\n this.removeChild(child);\n }\n this.appendChild(fragment);\n // Re-add template element if it existed\n if (templateToPreserve) {\n this.appendChild(templateToPreserve);\n }\n\n // Propagate animation properties from this element to newly created segments\n this.#lastPropagatedAnimation = \"\";\n this.propagateAnimationToSegments();\n\n // Segments will pause their own animations in connectedCallback\n // Lit will automatically update them when they're connected to the DOM\n // Ensure segments are updated after being connected\n const segmentElements = this.segments;\n Promise.all(segmentElements.map((seg) => seg.updateComplete)).then(() => {\n const rootTimegroup = this.rootTimegroup || this;\n updateAnimations(rootTimegroup as AnimatableElement);\n });\n\n this.lastTextContent = text;\n this._textContent = text;\n\n // Mark segments as initialized to prevent redundant splits\n this.#segmentsInitialized = true;\n\n // Resolve any waiting promises after segments are connected (synchronous)\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n }\n\n private detectWordBoundaries(text: string): Map<number, number> {\n // Create a map from character index to word index\n // Characters within the same word will have the same word index\n const boundaries = new Map<number, number>();\n const trimmedText = text.trim();\n if (!trimmedText) {\n return boundaries;\n }\n\n // Use Intl.Segmenter to detect word boundaries\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n\n // Find the offset of trimmedText within the original text\n const textStart = text.indexOf(trimmedText);\n\n let wordIndex = 0;\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Map all character positions in this word to the same word index\n for (let i = 0; i < seg.segment.length; i++) {\n const charPos = textStart + seg.index + i;\n boundaries.set(charPos, wordIndex);\n }\n wordIndex++;\n }\n }\n\n return boundaries;\n }\n\n private splitTextIntoSegments(text: string): string[] {\n // Trim text before segmenting to remove leading/trailing whitespace\n const trimmedText = text.trim();\n if (!trimmedText) {\n return [];\n }\n\n switch (this.split) {\n case \"line\": {\n // Split on newlines and trim each line\n const lines = trimmedText.split(/\\r?\\n/);\n return lines\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n }\n case \"word\": {\n // Use Intl.Segmenter for locale-aware word segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n const result: string[] = [];\n\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Word-like segment - add it\n result.push(seg.segment);\n } else if (/^\\s+$/.test(seg.segment)) {\n // Whitespace segment - add it as-is\n result.push(seg.segment);\n } else {\n // Punctuation segment - attach to preceding word if it exists\n if (result.length > 0) {\n const lastItem = result[result.length - 1];\n if (lastItem && !/^\\s+$/.test(lastItem)) {\n result[result.length - 1] = lastItem + seg.segment;\n } else {\n result.push(seg.segment);\n }\n } else {\n result.push(seg.segment);\n }\n }\n }\n\n return result;\n }\n case \"char\": {\n // Use Intl.Segmenter for grapheme-aware character segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"grapheme\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n return segments.map((seg) => seg.segment);\n }\n default:\n return [trimmedText];\n }\n }\n\n get intrinsicDurationMs(): number | undefined {\n // If explicit duration is set, use it\n if (this.hasExplicitDuration) {\n return undefined; // Let explicit duration take precedence\n }\n\n // If we have a parent timegroup that dictates duration (fixed) or inherits it (fit),\n // we should inherit from it instead of using our intrinsic duration.\n // For 'sequence' and 'contain' modes, the parent relies on our intrinsic duration,\n // so we must provide it.\n if (this.parentTimegroup) {\n const mode = this.parentTimegroup.mode;\n if (mode === \"fixed\" || mode === \"fit\") {\n return undefined;\n }\n }\n\n // Otherwise, calculate from content\n // Use _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return 0;\n }\n\n // Use the same splitting logic as splitTextIntoSegments for consistency\n const segments = this.splitTextIntoSegments(text);\n // For word splitting, only count word segments (not whitespace) for intrinsic duration\n const segmentCount =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg)).length || 1\n : segments.length || 1;\n\n return segmentCount * 1000;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text\": EFText;\n }\n}\n"],"mappings":";;;;;;;;;AAcA,MAAM,4BAA4B;CAChC;CACA;CACA;CACA;CACA;CACA;CACD;AAGM,mBAAMA,iBAAe,WAAW,WAAW,CAAC;;;eAa9B;gBA6BV;yBAIiB;sBACY;0BACiB;iCACF,EAAE;;;gBAhDvC,CACd,GAAG;;;;;;;MAQJ;;CAKD,AAAQ,cAAc,OAA0B;AAC9C,MAAI,UAAU,UAAU,UAAU,UAAU,UAAU,OACpD,QAAO;AAET,UAAQ,KACN,wBAAwB,MAAM,6DAC/B;AACD,SAAO;;CAUT,AAAQ,gBAAgB,OAA+C;AACrE,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,QAAQ,GAAG;AACb,WAAQ,KAAK,yBAAyB,MAAM,4BAA4B;AACxE,UAAO;;AAET,SAAO;;CAYT,uBAAuB;CACvB,2BAA2B;CAE3B,SAAS;AACP,SAAO,IAAI;;CAIb,IAAI,YAAY,OAAsB;EACpC,MAAM,WAAW,SAAS;AAE1B,MAAI,KAAK,iBAAiB,UAAU;AAClC,QAAK,eAAe;AAGpB,OAAI,CAAC,KAAK,oBAAoB,KAAK,YACjC,MAAK,mBAAmB,KAAK,cAAc,WAAW;GAIxD,MAAMC,YAAyB,EAAE;AACjC,QAAK,MAAM,QAAQ,MAAM,KAAK,KAAK,WAAW,CAC5C,KAAI,KAAK,aAAa,KAAK,UACzB,WAAU,KAAK,KAAkB;AAGrC,QAAK,MAAM,QAAQ,UACjB,MAAK,QAAQ;AAGf,OAAI,UAAU;IACZ,MAAM,WAAW,SAAS,eAAe,SAAS;AAClD,SAAK,YAAY,SAAS;;AAE5B,OAAI,KAAK,aAAa;AACpB,SAAK,kBAAkB,UAAU;AACjC,SAAK,WAAW;;;;CAKtB,IAAI,cAAsB;AAExB,MAAI,KAAK,iBAAiB,KACxB,QAAO,KAAK,gBAAgB;AAG9B,SAAO,KAAK;;;;;;CAOd,IAAI,WAA4B;AAC9B,SAAO,MAAM,KACX,KAAK,iBAAiB,wCAAwC,CAC/D;;;;;;;CAQH,MAAM,oBAA8C;AAElD,QAAM,KAAK;EAIX,MAAM,OACJ,KAAK,iBAAiB,OAAO,KAAK,eAAe,KAAK,gBAAgB;AACxE,MAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,WAAW,EAClC,QAAO,EAAE;AAIX,MAAI,MAAKC,uBAAwB,KAAK,SAAS,SAAS,GAAG;AACzD,SAAM,QAAQ,IAAI,KAAK,SAAS,KAAK,QAAQ,IAAI,eAAe,CAAC;AACjE,UAAO,KAAK;;EAId,IAAI,WAAW,KAAK;AACpB,MAAI,SAAS,SAAS,GAAG;AAEvB,SAAM,QAAQ,IAAI,SAAS,KAAK,QAAQ,IAAI,eAAe,CAAC;AAC5D,SAAKA,sBAAuB;AAC5B,UAAO;;AAKT,SAAO,IAAI,SAA0B,SAAS,WAAW;GACvD,MAAM,UAAU,iBAAiB;IAE/B,MAAM,QAAQ,KAAK,wBAAwB,QAAQ,oBAAoB;AACvE,QAAI,QAAQ,GACV,MAAK,wBAAwB,OAAO,OAAO,EAAE;AAE/C,2BAAO,IAAI,MAAM,kDAAkD,CAAC;MACnE,IAAK;GAER,MAAM,4BAA4B;AAChC,iBAAa,QAAQ;IAErB,MAAMC,aAAW,KAAK;AACtB,YAAQ,IAAIA,WAAS,KAAK,QAAQ,IAAI,eAAe,CAAC,CAAC,WAAW;AAChE,WAAKD,sBAAuB;AAC5B,aAAQC,WAAS;MACjB;;AAGJ,QAAK,wBAAwB,KAAK,oBAAoB;AAItD,OAAI,SAAS,WAAW,KAAK,KAAK,YAChC,MAAK,WAAW;IAElB;;CAGJ,oBAAoB;AAClB,QAAM,mBAAmB;AAEzB,OAAK,mBAAmB,KAAK,cAAc,WAAW;AAGtD,MAAI,KAAK,iBAAiB,MAAM;AAC9B,QAAK,eAAe,KAAK,gBAAgB;AACzC,QAAK,kBAAkB,KAAK;;AAK9B,8BAA4B;AAC1B,QAAK,uBAAuB;AAC5B,QAAK,wBAAwB;AAC7B,QAAK,WAAW;IAChB;;CAGJ,uBAAuB;AACrB,QAAM,sBAAsB;AAC5B,OAAK,kBAAkB,YAAY;AACnC,OAAK,mBAAmB,YAAY;;CAGtC,AAAU,QACR,mBACM;AACN,MACE,kBAAkB,IAAI,QAAQ,IAC9B,kBAAkB,IAAI,YAAY,IAClC,kBAAkB,IAAI,SAAS,IAC/B,kBAAkB,IAAI,aAAa,EACnC;AACA,QAAK,kBAAkB,UAAU;AACjC,QAAK,WAAW;;;CAIpB,AAAQ,wBAAwB;AAC9B,OAAK,mBAAmB,IAAI,uBAAuB;GAEjD,MAAM,cAAc,KAAK,gBAAgB,KAAK,gBAAgB;AAC9D,OAAI,gBAAgB,KAAK,iBAAiB;AACxC,SAAK,eAAe;AACpB,SAAK,kBAAkB;AACvB,SAAK,WAAW;;IAElB;AAEF,OAAK,iBAAiB,QAAQ,MAAM;GAClC,WAAW;GACX,eAAe;GACf,SAAS;GACV,CAAC;;CAGJ,AAAQ,yBAAyB;AAC/B,OAAK,oBAAoB,IAAI,uBAAuB;AAClD,QAAK,8BAA8B;IACnC;AAEF,OAAK,kBAAkB,QAAQ,MAAM;GACnC,YAAY;GACZ,iBAAiB,CAAC,SAAS,QAAQ;GACpC,CAAC;;CAGJ,AAAQ,+BAA+B;EACrC,MAAM,WAAW,KAAK;AACtB,MAAI,SAAS,WAAW,EAAG;EAE3B,MAAM,WAAW,OAAO,iBAAiB,KAAK;EAC9C,MAAM,gBAAgB,SAAS;EAG/B,MAAM,cAAc,0BACjB,KAAK,SAAS,SAAS,iBAAiB,KAAK,CAAC,CAC9C,KAAK,IAAI;AAEZ,MAAI,gBAAgB,MAAKC,wBAA0B;AACnD,QAAKA,0BAA2B;EAEhC,MAAM,eAAe,iBAAiB,kBAAkB;EACxD,MAAM,aAAa,KAAK,UAAU;AAElC,OAAK,MAAM,WAAW,SACpB,KAAI,cAAc;GAMhB,MAAM,eAAe,QAAQ,KAAK,QAAQ,eAAe,GAAG;AAC5D,OAAI,CAAC,cAAc,CAAC,aAClB,SAAQ,aAAa,iBAAiB,GAAG;AAE3C,QAAK,MAAM,QAAQ,0BACjB,SAAQ,MAAM,YAAY,MAAM,SAAS,iBAAiB,KAAK,CAAC;SAE7D;AACL,WAAQ,gBAAgB,gBAAgB;AACxC,QAAK,MAAM,QAAQ,0BACjB,SAAQ,MAAM,eAAe,KAAK;;;CAM1C,AAAQ,iBAAyB;EAE/B,IAAI,OAAO;AACX,OAAK,MAAM,QAAQ,MAAM,KAAK,KAAK,WAAW,CAC5C,KAAI,KAAK,aAAa,KAAK,UACzB,SAAQ,KAAK,eAAe;WACnB,KAAK,aAAa,KAAK,cAAc;GAC9C,MAAM,UAAU;AAEhB,OAAI,QAAQ,YAAY,kBACtB;AAGF,OAAI,QAAQ,YAAY,WACtB;AAEF,WAAQ,QAAQ,eAAe;;AAGnC,SAAO;;CAGT,AAAQ,YAAY;EAElB,MAAM,iBAAiB,KAAK,cAAc,KAAK,MAAM;AACrD,MAAI,mBAAmB,KAAK,OAAO;AACjC,QAAK,QAAQ;AACb;;EAIF,MAAM,mBAAmB,KAAK,gBAAgB,KAAK,UAAU;AAC7D,MAAI,qBAAqB,KAAK,WAAW;AACvC,QAAK,YAAY;AACjB;;EAIF,MAAM,OACJ,KAAK,iBAAiB,OAAO,KAAK,eAAe,KAAK,gBAAgB;EACxE,MAAM,cAAc,KAAK,MAAM;EAC/B,MAAM,kBAAkB,KAAK,QAAQ,YAAY;AAIjD,MACE,MAAKF,uBACL,KAAK,SAAS,SAAS,KACvB,KAAK,oBAAoB,KAEzB;AAGF,MAAI,CAAC,QAAQ,YAAY,WAAW,GAAG;GAErC,MAAM,mBAAmB,MAAM,KAC7B,KAAK,iBAAiB,kBAAkB,CACzC;AACD,QAAK,MAAM,WAAW,iBACpB,SAAQ,QAAQ;GAGlB,MAAMD,YAAyB,EAAE;AACjC,QAAK,MAAM,QAAQ,MAAM,KAAK,KAAK,WAAW,CAC5C,KAAI,KAAK,aAAa,KAAK,UACzB,WAAU,KAAK,KAAkB;AAGrC,QAAK,MAAM,QAAQ,UACjB,MAAK,QAAQ;AAEf,QAAK,kBAAkB;AAEvB,QAAK,wBAAwB,SAAS,YAAY;AAChD,aAAS;KACT;AACF,QAAK,0BAA0B,EAAE;AAEjC,SAAKC,sBAAuB;AAC5B;;AAIF,QAAKA,sBAAuB;EAE5B,MAAM,WAAW,KAAK,sBAAsB,KAAK;EACjD,MAAM,aAAa,KAAK,cAAc;EAGtC,IAAIG,iBAA6C;AACjD,MAAI,KAAK,UAAU,OACjB,kBAAiB,KAAK,qBAAqB,KAAK;EAKlD,MAAM,WAAW,SAAS,wBAAwB;AAGlD,MAAI,CAAC,KAAK,iBACR,MAAK,mBAAmB,KAAK,cAAc,WAAW;EAKxD,MAAM,kBAAkB,KAAK,kBAAkB;EAC/C,MAAM,mBAAmB,kBACrB,MAAM,KAAK,gBAAgB,iBAAiB,kBAAkB,CAAC,GAC/D,EAAE;EAGN,MAAM,cAAc,iBAAiB,SAAS;EAC9C,MAAM,yBAAyB,cAAc,iBAAiB,SAAS;EAGvE,IAAIC,mBAAkC;EACtC,IAAIC,kBAA0C;EAC9C,IAAI,YAAY;EAOhB,MAAM,oBAHJ,KAAK,UAAU,SACX,SAAS,QAAQ,QAAQ,CAAC,QAAQ,KAAK,IAAI,CAAC,GAC5C,UACoC;EAI1C,IAAI,mBAAmB;AAGvB,WAAS,SAAS,aAAa,cAAc;GAE3C,IAAIC;AACJ,OAAI,KAAK,cAAc,QAAW;IAEhC,MAAM,eAAe,QAAQ,KAAK,YAAY;IAC9C,IAAIC;AAEJ,QAAI,KAAK,UAAU,UAAU,aAG3B,uBAAsB,KAAK,IAAI,GAAG,mBAAmB,EAAE;aAC9C,KAAK,UAAU,QAAQ;AAGhC,2BAAsB;AACtB;UAGA,uBAAsB;IAKxB,MAAM,qBACJ,mBAAmB,IACf,uBAAuB,mBAAmB,KAC1C;AASN,oBANsB,eAAe,KAAK,QAAQ,mBAAmB,KAGvC,mBAAmB,KAAK,KAAK;;AAM7D,OAAI,eAAe,iBAAiB;IAGlC,MAAM,gBAAgB,gBAAgB,UACpC,KACD;AAKD,IAJuB,MAAM,KAC3B,cAAc,iBAAiB,kBAAkB,CAClD,CAEc,SAAS,SAAS,kBAAkB;AAEjD,aAAQ,cAAc;AAEtB,aAAQ,eACN,YAAY,yBAAyB;AACvC,aAAQ,iBAAiB;AACzB,aAAQ,eAAe;AACvB,aAAQ,kBAAkB,iBAAiB;AAG3C,SAAI,KAAK,UAAU,OACjB,SAAQ,aAAa,qBAAqB,OAAO;AAInD,aAAQ,aAAa,wBAAwB,OAAO;AAGpD,SAAI,KAAK,UAAU,UAAU,gBAAgB;MAC3C,MAAM,oBAAoB,kBAAkB;MAC5C,MAAM,YAAY,eAAe,IAAI,kBAAkB;AACvD,UAAI,cAAc,QAAW;AAC3B,WAAI,cAAc,kBAAkB;AAClC,YAAI,gBACF,UAAS,YAAY,gBAAgB;AAEvC,2BAAmB;AACnB,0BAAkB,SAAS,cAAc,OAAO;AAChD,wBAAgB,MAAM,aAAa;;AAErC,WAAI,gBACF,iBAAgB,YAAY,QAAQ;WAEpC,UAAS,YAAY,QAAQ;aAE1B;AACL,WAAI,iBAAiB;AACnB,iBAAS,YAAY,gBAAgB;AACrC,0BAAkB;AAClB,2BAAmB;;AAErB,gBAAS,YAAY,QAAQ;;AAE/B,mBAAa,YAAY;WAEzB,UAAS,YAAY,QAAQ;MAE/B;UACG;IAEL,MAAM,UAAU,SAAS,cACvB,kBACD;AAED,YAAQ,cAAc;AACtB,YAAQ,eAAe;AACvB,YAAQ,iBAAiB;AACzB,YAAQ,eAAe;AACvB,YAAQ,kBAAkB,iBAAiB;AAG3C,QAAI,KAAK,UAAU,OACjB,SAAQ,aAAa,qBAAqB,OAAO;AAInD,YAAQ,aAAa,wBAAwB,OAAO;AAGpD,QAAI,KAAK,UAAU,UAAU,gBAAgB;KAE3C,MAAM,oBAAoB,kBAAkB;KAC5C,MAAM,YAAY,eAAe,IAAI,kBAAkB;AACvD,SAAI,cAAc,QAAW;AAE3B,UAAI,cAAc,kBAAkB;AAElC,WAAI,gBACF,UAAS,YAAY,gBAAgB;AAGvC,0BAAmB;AACnB,yBAAkB,SAAS,cAAc,OAAO;AAChD,uBAAgB,MAAM,aAAa;;AAGrC,UAAI,gBACF,iBAAgB,YAAY,QAAQ;UAEpC,UAAS,YAAY,QAAQ;YAE1B;AAGL,UAAI,iBAAiB;AACnB,gBAAS,YAAY,gBAAgB;AACrC,yBAAkB;AAClB,0BAAmB;;AAErB,eAAS,YAAY,QAAQ;;AAG/B,kBAAa,YAAY;UAGzB,UAAS,YAAY,QAAQ;;IAGjC;AAGF,MAAI,KAAK,UAAU,UAAU,gBAC3B,UAAS,YAAY,gBAAgB;EAQvC,MAAM,qBAAqB,KAAK;AAChC,SAAO,KAAK,YAAY;GACtB,MAAM,QAAQ,KAAK;AAEnB,OAAI,UAAU,oBAAoB;AAGhC,SAAK,YAAY,MAAM;AACvB;;AAEF,QAAK,YAAY,MAAM;;AAEzB,OAAK,YAAY,SAAS;AAE1B,MAAI,mBACF,MAAK,YAAY,mBAAmB;AAItC,QAAKL,0BAA2B;AAChC,OAAK,8BAA8B;EAKnC,MAAM,kBAAkB,KAAK;AAC7B,UAAQ,IAAI,gBAAgB,KAAK,QAAQ,IAAI,eAAe,CAAC,CAAC,WAAW;AAEvE,oBADsB,KAAK,iBAAiB,KACQ;IACpD;AAEF,OAAK,kBAAkB;AACvB,OAAK,eAAe;AAGpB,QAAKF,sBAAuB;AAG5B,OAAK,wBAAwB,SAAS,YAAY;AAChD,YAAS;IACT;AACF,OAAK,0BAA0B,EAAE;;CAGnC,AAAQ,qBAAqB,MAAmC;EAG9D,MAAM,6BAAa,IAAI,KAAqB;EAC5C,MAAM,cAAc,KAAK,MAAM;AAC/B,MAAI,CAAC,YACH,QAAO;EAIT,MAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAC9C,aAAa,QACd,CAAC;EACF,MAAM,WAAW,MAAM,KAAK,UAAU,QAAQ,YAAY,CAAC;EAG3D,MAAM,YAAY,KAAK,QAAQ,YAAY;EAE3C,IAAI,YAAY;AAChB,OAAK,MAAM,OAAO,SAChB,KAAI,IAAI,YAAY;AAElB,QAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,QAAQ,KAAK;IAC3C,MAAM,UAAU,YAAY,IAAI,QAAQ;AACxC,eAAW,IAAI,SAAS,UAAU;;AAEpC;;AAIJ,SAAO;;CAGT,AAAQ,sBAAsB,MAAwB;EAEpD,MAAM,cAAc,KAAK,MAAM;AAC/B,MAAI,CAAC,YACH,QAAO,EAAE;AAGX,UAAQ,KAAK,OAAb;GACE,KAAK,OAGH,QADc,YAAY,MAAM,QAAQ,CAErC,KAAK,SAAS,KAAK,MAAM,CAAC,CAC1B,QAAQ,SAAS,KAAK,SAAS,EAAE;GAEtC,KAAK,QAAQ;IAEX,MAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAC9C,aAAa,QACd,CAAC;IACF,MAAM,WAAW,MAAM,KAAK,UAAU,QAAQ,YAAY,CAAC;IAC3D,MAAMQ,SAAmB,EAAE;AAE3B,SAAK,MAAM,OAAO,SAChB,KAAI,IAAI,WAEN,QAAO,KAAK,IAAI,QAAQ;aACf,QAAQ,KAAK,IAAI,QAAQ,CAElC,QAAO,KAAK,IAAI,QAAQ;aAGpB,OAAO,SAAS,GAAG;KACrB,MAAM,WAAW,OAAO,OAAO,SAAS;AACxC,SAAI,YAAY,CAAC,QAAQ,KAAK,SAAS,CACrC,QAAO,OAAO,SAAS,KAAK,WAAW,IAAI;SAE3C,QAAO,KAAK,IAAI,QAAQ;UAG1B,QAAO,KAAK,IAAI,QAAQ;AAK9B,WAAO;;GAET,KAAK,QAAQ;IAEX,MAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAC9C,aAAa,YACd,CAAC;AAEF,WADiB,MAAM,KAAK,UAAU,QAAQ,YAAY,CAAC,CAC3C,KAAK,QAAQ,IAAI,QAAQ;;GAE3C,QACE,QAAO,CAAC,YAAY;;;CAI1B,IAAI,sBAA0C;AAE5C,MAAI,KAAK,oBACP;AAOF,MAAI,KAAK,iBAAiB;GACxB,MAAM,OAAO,KAAK,gBAAgB;AAClC,OAAI,SAAS,WAAW,SAAS,MAC/B;;EAMJ,MAAM,OACJ,KAAK,iBAAiB,OAAO,KAAK,eAAe,KAAK,gBAAgB;AACxE,MAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,WAAW,EAClC,QAAO;EAIT,MAAM,WAAW,KAAK,sBAAsB,KAAK;AAOjD,UAJE,KAAK,UAAU,SACX,SAAS,QAAQ,QAAQ,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,UAAU,IACvD,SAAS,UAAU,KAEH;;;YAvuBvB,SAAS;CAAE,MAAM;CAAQ,SAAS;CAAM,CAAC;YAazC,SAAS;CACR,MAAM;CACN,WAAW;CACX,WAAW;CACZ,CAAC;YAYD,SAAS;CAAE,MAAM;CAAQ,SAAS;CAAM,CAAC;qBA1C3C,cAAc,UAAU"}
|