@editframe/elements 0.40.1-beta.0 → 0.40.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"EFTimegroup.js","names":["durationCache: WeakMap<EFTimegroup, number>","ancestor: Node | null","EFTimegroup","taskObj: {\n run(): void | Promise<number | undefined>;\n taskComplete: Promise<number | undefined>;\n }","#seekTaskAbortController","#pendingSeekTime","#currentTime","#seekTaskPromise","#runSeekTask","#recomputeAggregateReadyState","state","#trackedChildren","#childReadyStateHandler","#childContentChangeHandler","#mode","#overlapMs","#initializer","#restoringFromLocalStorage","#frameController","#qualityUpgradeScheduler","#customFrameTasks","#executeCustomFrameTasks","#contentEpoch","#runThrottledFrameTask","#userTimeMs","#processingPendingSeek","#seekInProgress","#getAllLitElementDescendants","result: LitElement[]","#onFrameCallback","#onFrameCleanup","#handleSlotChange","#syncChildListeners","#runInitializer","#setupPlaybackListener","#playbackListener","#previousDurationMs","#resizeObserver","#removePlaybackListener","#initializerHasRun","result: unknown","#copyTextSegmentData","updatePromises: Promise<any>[]","#waitForCaptionsData","waitPromises: Promise<unknown>[]","#copyInitializersToClone","#createRenderCloneFromFactory","#createRenderCloneFromDOM","#waitForDescendants","renderTarget: HTMLElement","#finalizeRenderClone","#copyCaptionsData","#copyTextContent","#mediaDurationsPromise","#waitForMediaDurations","rafId1: number","rafId2: number","timeoutId: ReturnType<typeof setTimeout>"],"sources":["../../src/elements/EFTimegroup.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport debug from \"debug\";\nimport { css, html, LitElement, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nimport { quantizeToFrameTimeS } from \"../utils/frameTime.js\";\nimport { getCloneFactory } from \"./cloneFactoryRegistry.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { isTracingEnabled, withSpan } from \"../otel/tracingHelpers.js\";\nimport {\n FrameController,\n type FrameRenderable,\n type FrameState,\n PRIORITY_DEFAULT,\n} from \"../preview/FrameController.js\";\nimport { QualityUpgradeScheduler } from \"../preview/QualityUpgradeScheduler.js\";\nimport { deepGetMediaElements, type EFMedia } from \"./EFMedia.js\";\nimport {\n EFTemporal,\n flushStartTimeMsCache,\n resetTemporalCache,\n shallowGetTemporalElements,\n timegroupContext,\n type TemporalMixinInterface,\n} from \"./EFTemporal.js\";\nimport { parseTimeToMs } from \"./parseTimeToMs.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.js\";\nimport { TimegroupController } from \"./TimegroupController.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\nimport {\n type ContainerInfo,\n getContainerInfoFromElement,\n} from \"./ContainerInfo.js\";\nimport {\n type ElementPositionInfo,\n getPositionInfoFromElement,\n} from \"./ElementPositionInfo.js\";\n// Import only types - actual function loaded dynamically\nimport type { RenderToVideoOptions } from \"../preview/renderTimegroupToVideo.types.js\";\nimport type { PlaybackControllerUpdateEvent } from \"../gui/PlaybackController.js\";\n\n// Side-effect imports for workbench wrapping\nimport \"../canvas/EFCanvas.js\";\nimport \"../gui/hierarchy/EFHierarchy.js\";\nimport \"../gui/EFFilmstrip.js\";\nimport \"../gui/EFWorkbench.js\";\nimport \"../gui/EFFitScale.js\";\nimport \"./EFPanZoom.js\";\n\nconst log = debug(\"ef:elements:EFTimegroup\");\n\n// Custom frame task callback type\nexport type FrameTaskCallback = (info: {\n ownCurrentTimeMs: number;\n currentTimeMs: number;\n durationMs: number;\n percentComplete: number;\n element: EFTimegroup;\n}) => void | Promise<void>;\n\n/**\n * Result of createRenderClone() - contains the clone, its container, and cleanup function.\n */\nexport interface RenderCloneResult {\n /** The cloned timegroup, fully functional with its own time state */\n clone: EFTimegroup;\n /** The offscreen container holding the clone */\n container: HTMLElement;\n /** Call this to remove the clone from DOM and clean up */\n cleanup: () => void;\n}\n\n/**\n * Initializer function type for setting up JavaScript behavior on timegroup instances.\n * This function is called on both the prime timeline and each render clone.\n *\n * CONSTRAINTS:\n * - MUST be synchronous (no async/await, no Promise return)\n * - MUST complete in <2000ms (error) or <100ms (warning)\n * - Should only register callbacks and set up behavior, not do expensive work\n * - GPU operations (WebGL context creation, shader compilation) may take up to ~1s\n */\nexport type TimegroupInitializer = (timegroup: EFTimegroup) => void;\n\nexport interface SeekForRenderTiming {\n updateComplete1Ms: number;\n updateComplete2Ms: number;\n updateComplete3Ms: number;\n textSegmentsMs: number;\n renderFrameMs: number;\n renderFrameQueryMs: number;\n renderFramePrepareMs: number;\n renderFrameDrawMs: number;\n renderFrameAnimsMs: number;\n frameTasksMs: number;\n totalMs: number;\n}\n\n// Constants for initializer time budget enforcement\nconst INITIALIZER_ERROR_THRESHOLD_MS = 2000;\nconst INITIALIZER_WARN_THRESHOLD_MS = 100;\n\n// ============================================================================\n// Purpose 1: Composition Rules - How Duration is Determined\n// ============================================================================\n//\n// A timegroup's duration is determined by its mode:\n// - \"fixed\": Uses explicit duration attribute (base case)\n// - \"sequence\": Sum of child durations minus overlaps\n// - \"contain\": Maximum of child durations\n// - \"fit\": Inherits duration from parent timegroup\n//\n// Core invariant: Every timegroup has exactly one duration value at any moment,\n// computed from either explicit specification (fixed mode) or child relationships\n// (sequence/contain/fit modes).\n//\n// ============================================================================\n\n/**\n * The four timegroup modes define how duration is calculated:\n * - \"fit\": Inherits duration from parent timegroup\n * - \"fixed\": Uses explicit duration attribute\n * - \"sequence\": Sum of child durations minus overlaps\n * - \"contain\": Maximum of child durations\n */\nexport type TimeMode = \"fit\" | \"fixed\" | \"sequence\" | \"contain\";\n\n// Cache for duration calculations to avoid O(n) recalculation on every access\n// Used by all modes (sequence, contain) to avoid repeated iteration through children\nlet durationCache: WeakMap<EFTimegroup, number> = new WeakMap();\n\nexport const flushDurationCache = () => {\n durationCache = new WeakMap();\n};\n\n// Keep alias for backwards compatibility\nexport const flushSequenceDurationCache = flushDurationCache;\n\n// Track timegroups currently calculating duration to prevent infinite loops\nconst durationCalculationInProgress = new WeakSet<EFTimegroup>();\n\n// Export function to check if a timegroup is currently calculating duration\n// This is used by EFTemporal to prevent calling parent.durationMs during calculation\nexport const isTimegroupCalculatingDuration = (\n timegroup: EFTimegroup | undefined,\n): boolean => {\n return (\n timegroup !== undefined && durationCalculationInProgress.has(timegroup)\n );\n};\n\n// Register this function with EFTemporal to break circular dependency\n// EFTemporal needs this function but can't import it directly due to circular dependency\nimport { registerIsTimegroupCalculatingDuration } from \"./EFTemporal.js\";\nregisterIsTimegroupCalculatingDuration(isTimegroupCalculatingDuration);\n\n/**\n * Determines if a timegroup has its own duration based on its mode.\n * This is the semantic rule: which modes produce independent durations.\n */\nfunction hasOwnDurationForMode(\n mode: TimeMode,\n hasExplicitDuration: boolean,\n): boolean {\n return (\n mode === \"contain\" ||\n mode === \"sequence\" ||\n (mode === \"fixed\" && hasExplicitDuration)\n );\n}\n\n/**\n * Determines if a child temporal element should participate in parent duration calculation.\n *\n * Semantic rule: Fit-mode children inherit from parent, so they don't contribute to parent's\n * duration calculation (to avoid circular dependencies). Children without own duration\n * also don't contribute.\n */\nfunction shouldParticipateInDurationCalculation(\n child: TemporalMixinInterface & HTMLElement,\n): boolean {\n // Fit timegroups look \"up\" to their parent for duration, so skip to avoid infinite loop\n if (child instanceof EFTimegroup && child.mode === \"fit\") {\n return false;\n }\n // Only children with their own duration contribute\n if (!child.hasOwnDuration) {\n return false;\n }\n return true;\n}\n\n/**\n * Evaluates duration for \"fit\" mode: inherits from parent.\n * Semantic rule: fit mode always matches parent duration, or 0 if no parent.\n */\nfunction evaluateFitDuration(parentTimegroup: EFTimegroup | undefined): number {\n if (!parentTimegroup) {\n return 0;\n }\n return parentTimegroup.durationMs;\n}\n\n/**\n * Evaluates duration for \"sequence\" mode: sum of children minus overlaps.\n * Semantic rule: sequence mode sums child durations, subtracting overlap between consecutive items.\n * Fit-mode children are excluded to avoid circular dependencies.\n */\nfunction evaluateSequenceDuration(\n timegroup: EFTimegroup,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n overlapMs: number,\n): number {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = durationCache.get(timegroup);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let duration = 0;\n let participatingIndex = 0;\n childTemporals.forEach((child) => {\n if (!shouldParticipateInDurationCalculation(child)) {\n return;\n }\n // Prevent infinite loops: skip children that are already calculating their duration\n if (\n child instanceof EFTimegroup &&\n durationCalculationInProgress.has(child)\n ) {\n return;\n }\n\n // Additional safety: if child is a timegroup, check if any of its ancestors\n // (EXCLUDING the current timegroup) are calculating.\n // This prevents cycles where a child's descendant eventually calls back to an ancestor,\n // but allows direct children of the current timegroup to participate.\n if (child instanceof EFTimegroup) {\n let ancestor: Node | null = child.parentNode;\n let shouldSkip = false;\n while (ancestor) {\n // Stop FIRST if we've reached the current timegroup - direct children are allowed\n if (ancestor === timegroup) {\n break;\n }\n if (\n ancestor instanceof EFTimegroup &&\n durationCalculationInProgress.has(ancestor)\n ) {\n // Found a calculating ancestor (not the current timegroup) - skip this child to prevent cycle\n shouldSkip = true;\n break;\n }\n ancestor = ancestor.parentNode;\n }\n if (shouldSkip) {\n return;\n }\n }\n\n // Subtract overlap for all items after the first\n if (participatingIndex > 0) {\n duration -= overlapMs;\n }\n duration += child.durationMs;\n participatingIndex++;\n });\n\n // Ensure non-negative duration (invariant)\n duration = Math.max(0, duration);\n\n // Cache the calculated duration\n durationCache.set(timegroup, duration);\n return duration;\n}\n\n/**\n * Evaluates duration for \"contain\" mode: maximum of children.\n * Semantic rule: contain mode takes the maximum child duration.\n * Fit-mode children and children without own duration are excluded.\n */\nfunction evaluateContainDuration(\n timegroup: EFTimegroup,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n): number {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = durationCache.get(timegroup);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let maxDuration = 0;\n for (const child of childTemporals) {\n if (!shouldParticipateInDurationCalculation(child)) {\n continue;\n }\n // Prevent infinite loops: skip children that are already calculating their duration\n // This check applies to all timegroup children, not just contain mode, because\n // a sequence-mode child could contain a contain-mode grandchild that\n // eventually references back to the parent through the parent chain\n if (\n child instanceof EFTimegroup &&\n durationCalculationInProgress.has(child)\n ) {\n continue;\n }\n\n // Additional safety: if child is a timegroup, check if any of its ancestors\n // (EXCLUDING the current timegroup) are calculating.\n // This prevents cycles where a child's descendant eventually calls back to an ancestor,\n // but allows direct children of the current timegroup to participate.\n if (child instanceof EFTimegroup) {\n let ancestor: Node | null = child.parentNode;\n let shouldSkip = false;\n while (ancestor) {\n // Stop FIRST if we've reached the current timegroup - direct children are allowed\n if (ancestor === timegroup) {\n break;\n }\n if (\n ancestor instanceof EFTimegroup &&\n durationCalculationInProgress.has(ancestor)\n ) {\n // Found a calculating ancestor (not the current timegroup) - skip this child to prevent cycle\n shouldSkip = true;\n break;\n }\n ancestor = ancestor.parentNode;\n }\n if (shouldSkip) {\n continue;\n }\n }\n\n maxDuration = Math.max(maxDuration, child.durationMs);\n }\n // Ensure non-negative duration (invariant)\n const duration = Math.max(0, maxDuration);\n\n // Cache the calculated duration\n durationCache.set(timegroup, duration);\n return duration;\n}\n\n/**\n * Evaluates duration based on timegroup mode.\n * This is the semantic evaluation function - it determines what duration should be.\n *\n * Note: Fixed mode is handled inline in the getter because it needs to call super.durationMs\n * which requires the class context. The other modes are extracted for clarity.\n */\nfunction evaluateDurationForMode(\n timegroup: EFTimegroup,\n mode: TimeMode,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n): number {\n switch (mode) {\n case \"fit\":\n return evaluateFitDuration(timegroup.parentTimegroup);\n case \"sequence\": {\n // Mark this timegroup as calculating duration to prevent infinite loops\n durationCalculationInProgress.add(timegroup);\n try {\n return evaluateSequenceDuration(\n timegroup,\n childTemporals,\n timegroup.overlapMs,\n );\n } finally {\n // Always remove the marker, even if an error occurs\n durationCalculationInProgress.delete(timegroup);\n }\n }\n case \"contain\": {\n // Mark this timegroup as calculating duration to prevent infinite loops\n durationCalculationInProgress.add(timegroup);\n try {\n return evaluateContainDuration(timegroup, childTemporals);\n } finally {\n // Always remove the marker, even if an error occurs\n durationCalculationInProgress.delete(timegroup);\n }\n }\n default:\n throw new Error(`Invalid time mode: ${mode}`);\n }\n}\n\nexport const shallowGetTimegroups = (\n element: Element,\n groups: EFTimegroup[] = [],\n) => {\n for (const child of Array.from(element.children)) {\n if (child instanceof EFTimegroup) {\n groups.push(child);\n } else {\n shallowGetTimegroups(child, groups);\n }\n }\n return groups;\n};\n\n// ============================================================================\n// Purpose 2: Time Propagation - How currentTime Flows Root to Children\n// ============================================================================\n//\n// Time propagation determines how the root timegroup's currentTime flows to child\n// temporal elements, computing each child's ownCurrentTime based on:\n// - The root's currentTime (global coordinate)\n// - The child's startTimeMs (determined by parent's composition mode)\n// - The parent's mode (sequence/contain/fit/fixed)\n//\n// Propagation rules by mode:\n// - Sequence: Each child's ownCurrentTime progresses within its time-shifted window\n// - Contain: All children share the same ownCurrentTime as parent\n// - Fit: Child ownCurrentTime = parent ownCurrentTime (identity mapping)\n//\n// Core invariant: Only root timegroup's currentTime should be written.\n// Child times are computed from parent state via ownCurrentTimeMs.\n//\n// Note: Time propagation logic is primarily implemented in EFTemporal.ts\n// (ownCurrentTimeMs getter and startTimeMs calculation). The timegroup's\n// currentTime setter triggers propagation by updating root time.\n//\n// ============================================================================\n\n// ============================================================================\n// Purpose 3: Seeking - Moving to a Specific Time\n// ============================================================================\n//\n// Seeking moves the timeline to a specific time position. This involves:\n// 1. Quantizing the requested time to frame boundaries (based on fps)\n// 2. Clamping to valid range [0, duration]\n// 3. Updating root timegroup's currentTime (which triggers time propagation)\n// 4. Waiting for all media and frame tasks to complete\n//\n// Core invariant: All time values snap to frame boundaries when FPS is set.\n// This ensures consistent seek/render behavior.\n//\n// ============================================================================\n\n/**\n * Evaluates the target time for a seek operation.\n * Applies quantization and clamping to determine the valid seek target.\n */\nfunction evaluateSeekTarget(\n requestedTime: number,\n durationMs: number,\n fps: number,\n): number {\n // Quantize to frame boundaries\n const quantizedTime = quantizeToFrameTimeS(requestedTime, fps);\n // Clamp to valid range [0, duration]\n return Math.max(0, Math.min(quantizedTime, durationMs / 1000));\n}\n\n@customElement(\"ef-timegroup\")\nexport class EFTimegroup\n extends EFTargetable(EFTemporal(TWMixin(LitElement)))\n implements FrameRenderable\n{\n static get observedAttributes(): string[] {\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mode\",\n \"overlap\",\n \"currenttime\",\n \"fit\",\n \"fps\",\n \"auto-init\",\n \"workbench\",\n ];\n }\n\n static styles = css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n\n ::slotted(ef-timegroup) {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n overflow: initial;\n }\n `;\n\n /** @internal */\n @provide({ context: timegroupContext })\n _timeGroupContext = this;\n\n /** @internal */\n @provide({ context: efContext })\n efContext = this;\n\n // ---- Content Readiness Aggregation ----\n\n #trackedChildren = new Set<TemporalMixinInterface & HTMLElement>();\n\n override shouldAutoReady(): boolean {\n return false;\n }\n\n #childReadyStateHandler = () => {\n this.#recomputeAggregateReadyState();\n };\n\n #childContentChangeHandler = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n this.emitContentChange(detail?.reason ?? \"content\");\n };\n\n #recomputeAggregateReadyState(): void {\n const children = shallowGetTemporalElements(this);\n if (children.length === 0) {\n this.setContentReadyState(\"ready\");\n return;\n }\n\n let hasLoading = false;\n let hasError = false;\n let hasIdle = false;\n\n for (const child of children) {\n const state = child.contentReadyState;\n if (state === \"loading\") hasLoading = true;\n else if (state === \"error\") hasError = true;\n else if (state === \"idle\") hasIdle = true;\n }\n\n if (hasError) {\n this.setContentReadyState(\"error\");\n } else if (hasLoading) {\n this.setContentReadyState(\"loading\");\n } else if (hasIdle) {\n this.setContentReadyState(\"loading\");\n } else {\n this.setContentReadyState(\"ready\");\n }\n }\n\n #syncChildListeners(): void {\n const currentChildren = new Set(\n shallowGetTemporalElements(this) as Array<\n TemporalMixinInterface & HTMLElement\n >,\n );\n\n // Remove listeners from children that left\n for (const child of this.#trackedChildren) {\n if (!currentChildren.has(child)) {\n child.removeEventListener(\n \"readystatechange\",\n this.#childReadyStateHandler,\n );\n child.removeEventListener(\n \"contentchange\",\n this.#childContentChangeHandler,\n );\n }\n }\n\n // Add listeners to new children\n for (const child of currentChildren) {\n if (!this.#trackedChildren.has(child)) {\n child.addEventListener(\n \"readystatechange\",\n this.#childReadyStateHandler,\n );\n child.addEventListener(\n \"contentchange\",\n this.#childContentChangeHandler,\n );\n }\n }\n\n this.#trackedChildren = currentChildren;\n this.#recomputeAggregateReadyState();\n }\n\n // ---- End Content Readiness Aggregation ----\n\n /** @public */\n #mode: TimeMode = \"contain\";\n get mode(): TimeMode {\n return this.#mode;\n }\n set mode(value: TimeMode) {\n if (this.#mode === value) return;\n const old = this.#mode;\n this.#mode = value;\n this.requestUpdate(\"mode\", old);\n if (this.getAttribute(\"mode\") !== value) {\n this.setAttribute(\"mode\", value);\n }\n }\n\n /** @public */\n #overlapMs = 0;\n get overlapMs(): number {\n return this.#overlapMs;\n }\n set overlapMs(value: number) {\n if (this.#overlapMs === value) return;\n const old = this.#overlapMs;\n this.#overlapMs = value;\n this.requestUpdate(\"overlapMs\", old);\n const attrVal = value > 0 ? `${value}ms` : null;\n if (attrVal && this.getAttribute(\"overlap\") !== attrVal) {\n this.setAttribute(\"overlap\", attrVal);\n } else if (!attrVal && this.hasAttribute(\"overlap\")) {\n this.removeAttribute(\"overlap\");\n }\n }\n\n #initializer?: TimegroupInitializer;\n\n /**\n * Initializer function for setting up JavaScript behavior on this timegroup.\n * This function is called ONCE per instance - on the prime timeline when first connected,\n * and on each render clone when created.\n *\n * Use this to register frame callbacks, set up event listeners, or initialize state.\n * The same initializer code runs on both prime and clones, eliminating duplication.\n *\n * CONSTRAINTS:\n * - MUST be synchronous (no async/await, no Promise return)\n * - MUST complete in <100ms (error thrown) or <10ms (warning logged)\n * - Should only register callbacks and set up behavior, not do expensive work\n *\n * TIMING:\n * - If set before element connects to DOM: runs automatically after connectedCallback\n * - If set after element is connected: runs immediately\n * - Clones automatically copy and run the initializer when created\n *\n * @example\n * ```javascript\n * const tg = document.querySelector('ef-timegroup');\n * tg.initializer = (instance) => {\n * // Runs once on prime timeline, once on each clone\n * instance.addFrameTask((info) => {\n * // Update content based on time\n * });\n * };\n * ```\n * @public\n */\n get initializer(): TimegroupInitializer | undefined {\n return this.#initializer;\n }\n\n set initializer(fn: TimegroupInitializer | undefined) {\n this.#initializer = fn;\n // Just store the function. Execution is handled by:\n // - connectedCallback (for elements that have initializer before connection)\n // - #copyInitializersToClone (explicitly triggers for render clones)\n }\n\n /**\n * Track if initializer has run on this instance to prevent double execution.\n * @internal\n */\n #initializerHasRun = false;\n\n /** @public */\n @property({ type: Number })\n fps = 30;\n\n /**\n * When true, automatically seeks to frame 0 after media durations are loaded.\n * Only applies to root timegroups (timegroups that are not nested inside another timegroup).\n * This ensures the first frame is rendered immediately on initialization.\n */\n @property({ type: Boolean, attribute: \"auto-init\" })\n autoInit = false;\n\n /**\n * When true, automatically wraps this root timegroup with an ef-workbench element.\n * The workbench provides development UI including hierarchy panel, timeline, and playback controls.\n * Only applies to root timegroups.\n * @public\n */\n @property({ type: Boolean, reflect: true })\n workbench = false;\n\n attributeChangedCallback(\n name: string,\n old: string | null,\n value: string | null,\n ): void {\n if (name === \"mode\" && value) {\n this.mode = value as typeof this.mode;\n }\n if (name === \"overlap\" && value) {\n this.overlapMs = parseTimeToMs(value);\n }\n if (name === \"auto-init\") {\n this.autoInit = value !== null;\n }\n if (name === \"fps\" && value) {\n this.fps = Number.parseFloat(value);\n }\n if (name === \"workbench\") {\n this.workbench = value !== null;\n }\n super.attributeChangedCallback(name, old, value);\n }\n\n /** @public */\n @property({ type: String })\n fit: \"none\" | \"contain\" | \"cover\" = \"none\";\n\n #resizeObserver?: ResizeObserver;\n\n /** Content epoch - increments when visual content changes (used by thumbnail cache) */\n #contentEpoch = 0;\n\n #currentTime: number | undefined = undefined;\n #userTimeMs: number = 0; // What the user last requested (for preview display)\n #seekInProgress = false;\n #pendingSeekTime: number | undefined;\n #processingPendingSeek = false;\n #restoringFromLocalStorage = false; // Guard to prevent recursive seeks during localStorage restoration\n\n /** @internal */\n isRestoringFromLocalStorage(): boolean {\n return this.#restoringFromLocalStorage;\n }\n\n /** @internal - Used by PlaybackController to set restoration state */\n setRestoringFromLocalStorage(value: boolean): void {\n this.#restoringFromLocalStorage = value;\n }\n #customFrameTasks: Set<FrameTaskCallback> = new Set();\n #onFrameCallback: FrameTaskCallback | null = null;\n #onFrameCleanup: (() => void) | null = null;\n #playbackListener: ((event: PlaybackControllerUpdateEvent) => void) | null =\n null;\n\n /**\n * Centralized frame controller for coordinating element rendering.\n * Replaces the distributed Lit Task hierarchy with a single control point.\n */\n #frameController: FrameController = new FrameController(this);\n\n /**\n * Get the frame controller for centralized rendering coordination.\n * @public\n */\n get frameController(): FrameController {\n return this.#frameController;\n }\n\n /**\n * Centralized quality upgrade scheduler for coordinating main-quality segment fetching.\n * Lives alongside FrameController to manage background quality upgrades.\n */\n #qualityUpgradeScheduler: QualityUpgradeScheduler =\n new QualityUpgradeScheduler({\n requestFrameRender: () => this.requestFrameRender(),\n });\n\n /**\n * Get the quality upgrade scheduler for background segment fetching.\n * @public\n */\n get qualityUpgradeScheduler(): QualityUpgradeScheduler {\n return this.#qualityUpgradeScheduler;\n }\n\n // ============================================================================\n // FrameRenderable Interface Implementation\n // ============================================================================\n // Allows FrameController to discover and coordinate nested timegroups.\n // This ensures frame callbacks registered on nested timegroups are executed.\n // ============================================================================\n\n /**\n * Query timegroup's readiness state for a given time.\n * Timegroups are always ready (no async preparation needed).\n * @public\n */\n getFrameState(_timeMs: number): FrameState {\n return {\n needsPreparation: false,\n isReady: true,\n priority: PRIORITY_DEFAULT,\n };\n }\n\n /**\n * Async preparation phase (no-op for timegroups).\n * Timegroups don't need preparation - they just coordinate child rendering.\n * @public\n */\n async prepareFrame(_timeMs: number, _signal: AbortSignal): Promise<void> {\n // No preparation needed for timegroups\n }\n\n /**\n * Synchronous render phase - executes custom frame callbacks.\n * Called by FrameController after all preparation is complete.\n * Kicks off async frame callbacks without blocking (they run in background).\n * @public\n */\n renderFrame(_timeMs: number): void {\n // Execute custom frame tasks registered via addFrameTask()\n // Fire and forget - callbacks can be async but we don't block here\n // The frameTask.taskComplete promise tracks completion if needed\n if (this.#customFrameTasks.size > 0) {\n this.#executeCustomFrameTasks().catch((error) => {\n console.error(\"EFTimegroup custom frame task error:\", error);\n });\n }\n }\n\n /**\n * Get the effective FPS for this timegroup.\n * During rendering, uses the render options FPS if available.\n * Otherwise uses the configured fps property.\n * @public\n */\n get effectiveFps(): number {\n // During rendering, prefer the render options FPS\n if (typeof window !== \"undefined\" && window.EF_FRAMEGEN?.renderOptions) {\n return window.EF_FRAMEGEN.renderOptions.encoderOptions.video.framerate;\n }\n return this.fps;\n }\n\n /**\n * Get the current content epoch (used by thumbnail cache).\n * The epoch increments whenever visual content changes.\n * @public\n */\n get contentEpoch(): number {\n return this.#contentEpoch;\n }\n\n /**\n * Increment content epoch (called when visual content changes).\n * This invalidates cached thumbnails by changing their cache keys.\n * @public\n */\n incrementContentEpoch(): void {\n this.#contentEpoch++;\n }\n\n /**\n * Request a frame re-render at the current time.\n *\n * Use this when the source-to-timeline mapping has changed (e.g., sourcein/sourceout)\n * but currentTimeMs hasn't. The FrameController only re-renders when currentTimeMs\n * or durationMs change, so this provides a way for child elements to request a\n * re-render when their internal state changes the visual output.\n * @public\n */\n requestFrameRender(): void {\n this.#frameController.abort();\n this.#runThrottledFrameTask();\n }\n\n async #runThrottledFrameTask(): Promise<void> {\n if (this.playbackController) {\n return this.playbackController.runThrottledFrameTask();\n }\n // Use FrameController directly (no frameTask fallback)\n try {\n await this.#frameController.renderFrame(this.currentTimeMs, {\n onAnimationsUpdate: (root) => {\n updateAnimations(root as typeof this);\n },\n });\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n return;\n }\n console.error(\"FrameController error:\", error);\n }\n }\n\n // ============================================================================\n // Purpose 3: Seeking Implementation\n // ============================================================================\n\n /** @public */\n @property({ type: Number, attribute: \"currenttime\" })\n set currentTime(time: number) {\n // Evaluate seek target (quantization and clamping)\n const seekTarget = evaluateSeekTarget(\n time,\n this.durationMs,\n this.effectiveFps,\n );\n\n // Delegate to playbackController if available\n if (this.playbackController) {\n this.playbackController.currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n return;\n }\n\n // Only root timegroups can have their currentTime set directly\n if (!this.isRootTimegroup) {\n return;\n }\n\n // Validate seek target\n if (Number.isNaN(seekTarget)) {\n return;\n }\n\n // Skip if already at target time (unless processing pending seek or restoring from localStorage)\n if (\n seekTarget === this.#currentTime &&\n !this.#processingPendingSeek &&\n !this.#restoringFromLocalStorage\n ) {\n return;\n }\n\n // Skip if this is the same as pending seek\n if (this.#pendingSeekTime === seekTarget) {\n return;\n }\n\n // Prevent recursive seeks during localStorage restoration\n if (this.#restoringFromLocalStorage && seekTarget !== this.#currentTime) {\n // Allow the restoration seek to proceed, but prevent subsequent seeks\n // The flag will be cleared after the seek completes\n }\n\n // Handle concurrent seeks by queuing pending seek\n // This ensures we only have ONE seek in flight at a time, avoiding wasted work.\n // When scrubbing quickly, intermediate positions are skipped entirely - we don't\n // start work we know will be thrown away.\n if (this.#seekInProgress) {\n this.#pendingSeekTime = seekTarget;\n this.#currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n return;\n }\n\n // Execute seek - update both source time and user time\n this.#currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n this.#seekInProgress = true;\n\n // Attach .catch() to prevent unhandled rejection warning - errors are handled by seekTask.onError\n Promise.resolve(this.seekTask.run())\n .catch(() => {})\n .finally(async () => {\n this.#seekInProgress = false;\n\n // CRITICAL: Coordinate animations after seekTask completes\n // This handles seeks from currentTime setter (like localStorage restore)\n const { updateAnimations } = await import(\"./updateAnimations.js\");\n updateAnimations(this);\n\n // Process pending seek if it differs from completed seek\n // This jumps directly to wherever the user ended up, skipping intermediates\n if (\n this.#pendingSeekTime !== undefined &&\n this.#pendingSeekTime !== seekTarget\n ) {\n const pendingTime = this.#pendingSeekTime;\n this.#pendingSeekTime = undefined;\n this.#processingPendingSeek = true;\n try {\n this.currentTime = pendingTime;\n } finally {\n this.#processingPendingSeek = false;\n }\n } else {\n this.#pendingSeekTime = undefined;\n }\n });\n }\n\n /** @public */\n get currentTime() {\n if (this.playbackController) {\n return this.playbackController.currentTime;\n }\n return this.#currentTime ?? 0;\n }\n\n /** @public */\n set currentTimeMs(ms: number) {\n this.currentTime = ms / 1000;\n }\n\n /** @public */\n get currentTimeMs() {\n return this.currentTime * 1000;\n }\n\n /**\n * The time the user last requested via seek/scrub.\n * Preview systems should use this instead of currentTimeMs to avoid\n * seeing intermediate times during batch operations (thumbnails, export).\n * @public\n */\n get userTimeMs(): number {\n return this.#userTimeMs;\n }\n\n /**\n * Seek to a specific time and wait for all frames to be ready.\n * This is the recommended way to seek in tests and programmatic control.\n *\n * Combines seeking (Purpose 3) with frame rendering (Purpose 4) to ensure\n * all visible elements are ready after the seek completes.\n *\n * Updates both the source time AND userTimeMs (what the preview displays).\n *\n * @param timeMs - Time in milliseconds to seek to\n * @returns Promise that resolves when the seek is complete and all visible children are ready\n * @public\n */\n async seek(timeMs: number): Promise<void> {\n // Update user time - this is what the preview should display\n this.#userTimeMs = timeMs;\n\n // Execute seek (Purpose 3)\n this.currentTimeMs = timeMs;\n await this.seekTask.taskComplete;\n\n // Handle localStorage when playbackController delegates seek\n if (this.playbackController) {\n this.saveTimeToLocalStorage(this.currentTime);\n }\n\n // Wait for frame rendering via FrameController\n await this.#frameController.renderFrame(timeMs, {\n onAnimationsUpdate: (root) => {\n updateAnimations(root as typeof this);\n },\n });\n }\n\n /**\n * Optimized seek for render loops.\n * Unlike `seek()`, this:\n * - Skips waitForMediaDurations (already loaded at render setup)\n * - Skips localStorage persistence\n * - Uses FrameController for centralized element coordination\n *\n * Still waits for all content to be ready (Lit updates, element preparation, rendering).\n *\n * @param timeMs - Time in milliseconds to seek to\n * @internal\n */\n async seekForRender(timeMs: number): Promise<SeekForRenderTiming> {\n const t0 = performance.now();\n // Set time directly (skip seekTask overhead)\n const newTime = timeMs / 1000;\n this.#userTimeMs = timeMs;\n this.#currentTime = newTime;\n // Sync playbackController if present so currentTime getter returns\n // the correct value regardless of which code path reads it.\n if (this.playbackController) {\n this.playbackController.currentTime = newTime;\n }\n // Sync the base mixin's local time so ownCurrentTimeMs returns the\n // correct value in render clones (which have no playback controller).\n this._setLocalTimeMs(timeMs);\n this.requestUpdate(\"currentTime\");\n\n // First await: let Lit propagate time to children\n await this.updateComplete;\n const t1 = performance.now();\n\n // Collect all LitElement descendants (not just those with frameTask)\n // This ensures ef-text, ef-captions, and other reactive elements update\n const allLitElements = this.#getAllLitElementDescendants();\n\n // Wait for ALL LitElement descendants to complete their reactive updates\n // This is critical for elements like ef-text and ef-captions that don't have frameTask\n await Promise.all(allLitElements.map((el) => el.updateComplete));\n const t2 = performance.now();\n\n // OwnCurrentTimeController defers child updates via queueMicrotask.\n // Those microtasks have fired by this point (between await boundaries).\n // Await a second pass of updateComplete to catch those deferred updates.\n await Promise.all(allLitElements.map((el) => el.updateComplete));\n const t3 = performance.now();\n\n // Wait for ef-text elements to have their segments ready\n // ef-text creates segments asynchronously via requestAnimationFrame\n const textElements = allLitElements.filter(\n (el) => el.tagName === \"EF-TEXT\",\n );\n if (textElements.length > 0) {\n await Promise.all(\n textElements.map((el) => {\n if (\n \"whenSegmentsReady\" in el &&\n typeof el.whenSegmentsReady === \"function\"\n ) {\n return (el as any).whenSegmentsReady();\n }\n return Promise.resolve();\n }),\n );\n\n // Force synchronous layout reflow after text segments are created/updated.\n // offsetHeight triggers layout computation — no need to yield a full rAF\n // (which costs 16-40ms and is throttled in hidden tabs).\n void this.offsetHeight;\n }\n const t4 = performance.now();\n\n // Use FrameController for centralized element coordination\n // This replaces the old distributed frameTask system\n // Animation updates are handled via the onAnimationsUpdate callback\n const rfTiming = await this.#frameController.renderFrame(timeMs, {\n waitForLitUpdate: false,\n onAnimationsUpdate: (root) => {\n updateAnimations(root as typeof this);\n // CRITICAL: Force style recalculation after updateAnimations sets animation.currentTime\n // Without this, getComputedStyle may return stale values (e.g., opacity: 0 instead of 1)\n // Accessing offsetWidth triggers synchronous style recalc\n void (root as HTMLElement).offsetWidth;\n },\n });\n const t5 = performance.now();\n\n // Execute custom frame tasks registered via addFrameTask()\n await this.#executeCustomFrameTasks();\n const t6 = performance.now();\n\n return {\n updateComplete1Ms: t1 - t0,\n updateComplete2Ms: t2 - t1,\n updateComplete3Ms: t3 - t2,\n textSegmentsMs: t4 - t3,\n renderFrameMs: t5 - t4,\n renderFrameQueryMs: rfTiming.queryMs,\n renderFramePrepareMs: rfTiming.prepareMs,\n renderFrameDrawMs: rfTiming.renderMs,\n renderFrameAnimsMs: rfTiming.animsMs,\n frameTasksMs: t6 - t5,\n totalMs: t6 - t0,\n };\n }\n\n /**\n * Collects all LitElement descendants recursively.\n * Used by seekForRender to ensure all reactive elements have updated.\n * Prunes subtrees of temporally-invisible elements — their Lit updates\n * still fire via microtasks (OwnCurrentTimeController), so skipping\n * the explicit await is safe.\n */\n #getAllLitElementDescendants(): LitElement[] {\n const result: LitElement[] = [];\n const currentTimeMs = this.currentTimeMs;\n\n const walk = (el: Element) => {\n for (const child of el.children) {\n // Temporal pruning: skip invisible temporal elements and their subtrees\n if (\"startTimeMs\" in child && \"endTimeMs\" in child) {\n const startMs = (child as any).startTimeMs ?? -Infinity;\n const endMs = (child as any).endTimeMs ?? Infinity;\n if (\n endMs > startMs &&\n (currentTimeMs < startMs || currentTimeMs >= endMs)\n ) {\n continue; // skip entire subtree\n }\n }\n\n if (child instanceof LitElement) {\n result.push(child);\n }\n walk(child);\n }\n };\n walk(this);\n\n return result;\n }\n\n /**\n * Determines if this is a root timegroup (no parent timegroups)\n * @public\n */\n get isRootTimegroup(): boolean {\n return !this.parentTimegroup;\n }\n\n /**\n * Property-based frame task callback for React integration.\n * When set, automatically registers the callback as a frame task.\n * Setting a new value automatically cleans up the previous callback.\n * Set to null or undefined to remove the callback.\n *\n * @example\n * // React usage:\n * <Timegroup onFrame={({ ownCurrentTimeMs, percentComplete }) => {\n * // Per-frame updates\n * }} />\n *\n * @public\n */\n get onFrame(): FrameTaskCallback | null {\n return this.#onFrameCallback;\n }\n\n set onFrame(callback: FrameTaskCallback | null | undefined) {\n // Clean up previous callback if exists\n if (this.#onFrameCleanup) {\n this.#onFrameCleanup();\n this.#onFrameCleanup = null;\n }\n this.#onFrameCallback = callback ?? null;\n\n // Register new callback if provided\n if (callback) {\n this.#onFrameCleanup = this.addFrameTask(callback);\n }\n }\n\n /**\n * Register a custom frame task callback that will be executed during frame rendering.\n * The callback receives timing information and can be async or sync.\n * Multiple callbacks can be registered and will execute in parallel.\n *\n * @param callback - Function to execute on each frame\n * @returns A cleanup function that removes the callback when called\n * @public\n */\n addFrameTask(callback: FrameTaskCallback): () => void {\n if (typeof callback !== \"function\") {\n throw new Error(\"Frame task callback must be a function\");\n }\n this.#customFrameTasks.add(callback);\n return () => {\n this.#customFrameTasks.delete(callback);\n };\n }\n\n /**\n * Remove a previously registered custom frame task callback.\n *\n * @param callback - The callback function to remove\n * @public\n */\n removeFrameTask(callback: FrameTaskCallback): void {\n this.#customFrameTasks.delete(callback);\n }\n\n /** @internal */\n saveTimeToLocalStorage(time: number) {\n try {\n if (this.id && this.isConnected && !Number.isNaN(time)) {\n localStorage.setItem(this.storageKey, time.toString());\n }\n } catch (error) {\n log(\"Failed to save time to localStorage\", error);\n }\n }\n\n render() {\n return html`<slot @slotchange=${this.#handleSlotChange}></slot> `;\n }\n\n #handleSlotChange = () => {\n resetTemporalCache();\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n\n this.requestUpdate();\n this.#syncChildListeners();\n this.emitContentChange(\"structure\");\n };\n\n /** @internal */\n loadTimeFromLocalStorage(): number | undefined {\n if (this.id) {\n try {\n const storedValue = localStorage.getItem(this.storageKey);\n if (storedValue === null) {\n return undefined;\n }\n const parsedValue = Number.parseFloat(storedValue);\n // Guard against NaN and Infinity which could cause issues\n if (Number.isNaN(parsedValue) || !Number.isFinite(parsedValue)) {\n return undefined;\n }\n return parsedValue;\n } catch (error) {\n log(\"Failed to load time from localStorage\", error);\n }\n }\n return undefined;\n }\n\n connectedCallback() {\n // CRITICAL: super.connectedCallback() MUST be synchronous for Lit lifecycle to work correctly.\n // Deferring it breaks render clones because updateComplete resolves before Lit initializes.\n //\n // EFTemporal.connectedCallback() handles root detection after Lit Context propagates:\n // - Schedules updateComplete.then(didBecomeRoot check)\n // - Only true roots (no parent after context) create PlaybackController\n //\n // PlaybackController.hostConnected() owns ALL root initialization:\n // - waitForMediaDurations\n // - localStorage time restoration\n // - initial seek\n //\n // This avoids the previous race conditions where both EFTimegroup.connectedCallback\n // and PlaybackController.hostConnected tried to initialize, causing concurrent seeks.\n super.connectedCallback();\n\n // Skip re-initialization when being moved for canvas preview capture.\n // EFTemporal.connectedCallback (super) already guards its own logic;\n // we guard the EFTimegroup-specific parts here (initializer, child\n // listeners, TimegroupController, wrapWithWorkbench).\n if ((this as any).canvasPreviewActive) return;\n\n // Run initializer after element is fully connected and Lit has updated\n // This ensures the element is in a stable state before user code runs\n this.updateComplete.then(() => {\n this.#runInitializer();\n // slotchange may not fire for empty timegroups, so run initial aggregation\n this.#syncChildListeners();\n });\n\n // Defer TimegroupController creation and workbench wrapping to next frame\n // These operations involve DOM queries (closest, getBoundingClientRect) which\n // can be expensive when many elements initialize simultaneously\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (this.parentTimegroup) {\n new TimegroupController(this.parentTimegroup, this);\n }\n\n if (this.shouldWrapWithWorkbench()) {\n this.wrapWithWorkbench();\n }\n });\n });\n }\n\n /**\n * Called when this timegroup becomes a root (no parent timegroup).\n * Sets up the playback listener after PlaybackController is created.\n * @internal\n */\n didBecomeRoot() {\n super.didBecomeRoot();\n this.#setupPlaybackListener();\n }\n\n /**\n * Setup listener on playbackController to sync userTimeMs during playback.\n */\n #setupPlaybackListener(): void {\n // Already setup or no controller\n if (this.#playbackListener || !this.playbackController) return;\n\n this.#playbackListener = (event: PlaybackControllerUpdateEvent) => {\n // Update userTimeMs during playback time changes\n // Clone-timeline: captures use separate clones, so Prime-timeline updates freely\n // Canvas preview reads userTimeMs to know what to render\n if (\n event.property === \"currentTimeMs\" &&\n typeof event.value === \"number\"\n ) {\n this.#userTimeMs = event.value;\n }\n };\n\n this.playbackController.addListener(this.#playbackListener);\n }\n\n /**\n * Remove playback listener on disconnect.\n */\n #removePlaybackListener(): void {\n if (this.#playbackListener && this.playbackController) {\n this.playbackController.removeListener(this.#playbackListener);\n }\n this.#playbackListener = null;\n }\n\n #previousDurationMs = 0;\n\n protected updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n if (changedProperties.has(\"mode\") || changedProperties.has(\"overlapMs\")) {\n durationCache.delete(this);\n }\n\n if (this.#previousDurationMs !== this.durationMs) {\n this.#previousDurationMs = this.durationMs;\n // Render clones are sequenced via seekForRender — don't trigger autonomous re-renders.\n // This prevents FrameController.abort() from interrupting an in-progress seekForRender.\n if (!this.hasAttribute(\"data-no-playback-controller\")) {\n this.#runThrottledFrameTask();\n }\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n\n // Skip teardown when being moved for canvas preview capture.\n // EFTemporal.disconnectedCallback (super) already guards its own logic.\n if ((this as any).canvasPreviewActive) return;\n\n this.#resizeObserver?.disconnect();\n this.#removePlaybackListener();\n for (const child of this.#trackedChildren) {\n child.removeEventListener(\n \"readystatechange\",\n this.#childReadyStateHandler,\n );\n child.removeEventListener(\n \"contentchange\",\n this.#childContentChangeHandler,\n );\n }\n this.#trackedChildren.clear();\n this.#qualityUpgradeScheduler.dispose();\n }\n\n /**\n * Render the timegroup to an MP4 video file and trigger download.\n * Captures each frame at the specified fps, encodes using WebCodecs via\n * MediaBunny, and downloads the resulting video.\n *\n * Uses dynamic import to only load render utilities in browser context.\n *\n * @param options - Rendering options (fps, codec, bitrate, filename, etc.)\n * @returns Promise that resolves when video is downloaded\n * @public\n */\n async renderToVideo(\n options?: RenderToVideoOptions,\n ): Promise<Uint8Array | undefined> {\n // Dynamic import - only loads in browser context when actually called\n const { renderTimegroupToVideo } =\n await import(\"../preview/renderTimegroupToVideo.js\");\n return renderTimegroupToVideo(this, options);\n }\n\n /**\n * Runs the initializer function with validation for synchronous execution and time budget.\n * Only runs once per instance. Safe to call multiple times - will skip if already run.\n * @throws Error if initializer returns a Promise (async not allowed)\n * @throws Error if initializer takes more than INITIALIZER_ERROR_THRESHOLD_MS\n * @internal\n */\n #runInitializer(): void {\n // Skip if no initializer or already run\n if (!this.initializer || this.#initializerHasRun) {\n return;\n }\n\n // Mark as run before executing to prevent recursion\n this.#initializerHasRun = true;\n\n const startTime = performance.now();\n const result: unknown = this.initializer(this);\n const elapsed = performance.now() - startTime;\n\n // Check for async (Promise return) - initializers MUST be synchronous\n if (\n result !== undefined &&\n result !== null &&\n typeof (result as any).then === \"function\"\n ) {\n throw new Error(\n \"Timeline initializer must be synchronous. \" +\n \"Do not return a Promise from the initializer function.\",\n );\n }\n\n // Time budget enforcement - initializers run for EVERY instance\n if (elapsed > INITIALIZER_ERROR_THRESHOLD_MS) {\n throw new Error(\n `Timeline initializer took ${elapsed.toFixed(1)}ms, exceeding the ${INITIALIZER_ERROR_THRESHOLD_MS}ms limit. ` +\n \"Initializers must be fast - move expensive work outside the initializer.\",\n );\n }\n\n if (elapsed > INITIALIZER_WARN_THRESHOLD_MS) {\n console.warn(\n `[ef-timegroup] Initializer took ${elapsed.toFixed(1)}ms, exceeding ${INITIALIZER_WARN_THRESHOLD_MS}ms. ` +\n \"Consider optimizing for better render performance.\",\n );\n }\n }\n\n /**\n * Copy captionsData property from original to clone.\n * cloneNode() only copies attributes, not JavaScript properties.\n * captionsData is often set via JS (e.g., captionsEl.captionsData = {...}),\n * so we must manually copy it to the cloned elements.\n * @internal\n */\n #copyCaptionsData(original: Element, clone: Element): void {\n // Find matching caption elements by position (querySelectorAll returns in document order)\n const originalCaptions = original.querySelectorAll(\"ef-captions\");\n const cloneCaptions = clone.querySelectorAll(\"ef-captions\");\n\n for (\n let i = 0;\n i < originalCaptions.length && i < cloneCaptions.length;\n i++\n ) {\n const origCap = originalCaptions[i] as any;\n const cloneCap = cloneCaptions[i] as any;\n\n // Copy loaded captions data from any source (JS property, captions-src, script element).\n // The loaded data is stored in unifiedCaptionsDataTask.value after async loading.\n // Setting captionsData on the clone gives it Priority 1, bypassing async loading.\n const loadedData =\n origCap.captionsData ?? origCap.unifiedCaptionsDataTask?.value;\n if (loadedData) {\n cloneCap.captionsData = loadedData;\n }\n }\n }\n\n /**\n * Copy ef-text _textContent property from original to cloned elements.\n * This MUST be called BEFORE elements upgrade (before updateComplete)\n * because splitText() runs in connectedCallback and will clear segments\n * if _textContent is null/empty.\n * @internal\n */\n #copyTextContent(original: Element, clone: Element): void {\n const originalTexts = original.querySelectorAll(\"ef-text\");\n const cloneTexts = clone.querySelectorAll(\"ef-text\");\n\n for (let i = 0; i < originalTexts.length && i < cloneTexts.length; i++) {\n const origText = originalTexts[i] as any;\n const cloneText = cloneTexts[i] as any;\n\n // Copy _textContent if it exists\n // This is a private property, so we access it via any\n if (origText._textContent !== undefined) {\n cloneText._textContent = origText._textContent;\n }\n // Also copy the segments getter to ensure we can read them\n if (origText._templateElement !== undefined) {\n cloneText._templateElement = origText._templateElement;\n }\n }\n }\n\n /**\n * Copy ef-text-segment properties from original to cloned elements.\n * segmentText and other properties are set via JS, not attributes,\n * so we must manually copy them to the cloned elements.\n * @internal\n */\n async #copyTextSegmentData(original: Element, clone: Element): Promise<void> {\n // Find matching text segment elements by position\n const originalSegments = original.querySelectorAll(\"ef-text-segment\");\n const cloneSegments = clone.querySelectorAll(\"ef-text-segment\");\n\n const updatePromises: Promise<any>[] = [];\n\n for (\n let i = 0;\n i < originalSegments.length && i < cloneSegments.length;\n i++\n ) {\n const origSeg = originalSegments[i] as any;\n const cloneSeg = cloneSegments[i] as any;\n\n // Copy all segment properties\n if (origSeg.segmentText !== undefined) {\n cloneSeg.segmentText = origSeg.segmentText;\n }\n if (origSeg.segmentIndex !== undefined) {\n cloneSeg.segmentIndex = origSeg.segmentIndex;\n }\n if (origSeg.staggerOffsetMs !== undefined) {\n cloneSeg.staggerOffsetMs = origSeg.staggerOffsetMs;\n }\n if (origSeg.segmentStartMs !== undefined) {\n cloneSeg.segmentStartMs = origSeg.segmentStartMs;\n }\n if (origSeg.segmentEndMs !== undefined) {\n cloneSeg.segmentEndMs = origSeg.segmentEndMs;\n }\n\n // Wait for Lit to render the updated segmentText to shadow DOM\n if (cloneSeg.updateComplete) {\n updatePromises.push(cloneSeg.updateComplete);\n }\n }\n\n // Wait for all segment updates to complete\n await Promise.all(updatePromises);\n }\n\n /**\n * Wait for all ef-captions elements to have their data loaded.\n * This is needed because EFCaptions is not an EFMedia, so waitForMediaDurations doesn't cover it.\n * Used by createRenderClone to ensure captions are ready before rendering.\n * @internal\n */\n async #waitForCaptionsData(root: Element): Promise<void> {\n // Find all ef-captions elements (including nested in timegroups)\n const captionsElements = root.querySelectorAll(\"ef-captions\");\n if (captionsElements.length === 0) return;\n\n // Wait for each caption element's data to load\n // Use duck-typing to check for loadCaptionsData method\n const waitPromises: Promise<unknown>[] = [];\n for (const el of captionsElements) {\n const captions = el as any;\n // Try new async method first\n if (typeof captions.loadCaptionsData === \"function\") {\n waitPromises.push(captions.loadCaptionsData().catch(() => {}));\n }\n // Fallback to task if present\n else if (captions.unifiedCaptionsDataTask?.taskComplete) {\n waitPromises.push(\n captions.unifiedCaptionsDataTask.taskComplete.catch(() => {}),\n );\n }\n }\n\n if (waitPromises.length > 0) {\n await Promise.all(waitPromises);\n }\n }\n\n /**\n * Copies initializers from original timegroup tree to cloned timegroup tree.\n * Handles both the root timegroup and all nested timegroups recursively.\n * @internal\n */\n async #copyInitializersToClone(\n original: EFTimegroup,\n clone: EFTimegroup,\n ): Promise<void> {\n // Copy and execute initializer at this level\n if (original.initializer) {\n clone.initializer = original.initializer;\n // Explicitly run the initializer on the clone\n // Wait for Lit update cycle to complete first so the element is stable\n await clone.updateComplete;\n clone.#runInitializer();\n }\n\n // Find all nested timegroups in both original and clone\n const originalNested = Array.from(\n original.querySelectorAll(\"ef-timegroup\"),\n ) as EFTimegroup[];\n const cloneNested = Array.from(\n clone.querySelectorAll(\"ef-timegroup\"),\n ) as EFTimegroup[];\n\n // Match up nested timegroups by index (they should correspond 1:1)\n for (let i = 0; i < originalNested.length && i < cloneNested.length; i++) {\n const origNested = originalNested[i];\n const cloneNestedItem = cloneNested[i];\n\n if (origNested!.initializer) {\n cloneNestedItem!.initializer = origNested!.initializer;\n await cloneNestedItem!.updateComplete;\n cloneNestedItem!.#runInitializer();\n }\n }\n }\n\n /**\n * Create an independent clone of this timegroup for rendering.\n * The clone is a fully functional ef-timegroup with its own animations\n * and time state, isolated from the original (Prime-timeline).\n *\n * OPTIONAL: An initializer can be set via `timegroup.initializer = (tg) => { ... }`\n * to re-run JavaScript setup (frame callbacks, React components) on each clone.\n *\n * This enables:\n * - Rendering without affecting user's preview position\n * - Concurrent renders with different clones\n * - Re-running JavaScript setup on each clone (if initializer is provided)\n *\n * @returns Promise resolving to clone, container, and cleanup function\n * @throws Error if initializer is async or takes too long\n * @public\n */\n async createRenderClone(): Promise<RenderCloneResult> {\n const factory = getCloneFactory(this);\n\n if (factory) {\n return this.#createRenderCloneFromFactory(factory);\n }\n return this.#createRenderCloneFromDOM();\n }\n\n /**\n * Wait for all LitElement descendants to update and for text segments to be ready.\n * This ensures the clone is fully initialized before rendering.\n * @internal\n */\n async #waitForDescendants(actualClone: EFTimegroup): Promise<void> {\n // Wait for all LitElement descendants\n const allLitElements = Array.from(actualClone.querySelectorAll(\"*\")).filter(\n (el) => el instanceof LitElement,\n ) as LitElement[];\n await Promise.all(allLitElements.map((el) => el.updateComplete));\n\n // Wait for text segments\n const textElements = allLitElements.filter(\n (el) => el.tagName === \"EF-TEXT\",\n );\n if (textElements.length > 0) {\n await Promise.all(\n textElements.map((el) => {\n if (\n \"whenSegmentsReady\" in el &&\n typeof el.whenSegmentsReady === \"function\"\n ) {\n return (el as any).whenSegmentsReady();\n }\n return Promise.resolve();\n }),\n );\n void actualClone.offsetHeight;\n await new Promise((resolve) => requestAnimationFrame(resolve));\n }\n }\n\n /**\n * Factory path: mount a fresh component tree (React, etc.) to produce\n * a fully functional clone. The factory is responsible for rendering\n * the component into the container and returning the root ef-timegroup.\n */\n async #createRenderCloneFromFactory(\n factory: NonNullable<ReturnType<typeof getCloneFactory>>,\n ): Promise<RenderCloneResult> {\n const width = this.offsetWidth || 1920;\n const height = this.offsetHeight || 1080;\n\n const container = document.createElement(\"div\");\n container.className = \"ef-render-clone-container\";\n container.style.cssText = `\n position: fixed;\n left: -9999px;\n top: 0;\n width: ${width}px;\n height: ${height}px;\n pointer-events: none;\n overflow: hidden;\n `;\n\n // Preserve ef-configuration context\n let renderTarget: HTMLElement = container;\n const originalConfig = this.closest(\"ef-configuration\");\n if (originalConfig) {\n const configClone = originalConfig.cloneNode(false) as HTMLElement;\n container.appendChild(configClone);\n renderTarget = configClone;\n }\n\n document.body.appendChild(container);\n\n // Mount the component tree — this produces a live ef-timegroup\n const { timegroup: actualClone, cleanup: factoryCleanup } =\n factory(renderTarget);\n\n if (!actualClone) {\n throw new Error(\n \"Clone factory did not produce an ef-timegroup. \" +\n \"Ensure the factory renders a component containing a Timegroup.\",\n );\n }\n\n // Mark as render clone\n actualClone.setAttribute(\"data-no-workbench\", \"true\");\n actualClone.setAttribute(\"data-no-playback-controller\", \"true\");\n actualClone.style.width = `${width}px`;\n actualClone.style.height = `${height}px`;\n actualClone.style.display = \"block\";\n\n // Wait for custom elements to upgrade and Lit to update\n await customElements.whenDefined(\"ef-timegroup\");\n customElements.upgrade(container);\n await actualClone.updateComplete;\n\n // Wait for all descendants to be ready\n await this.#waitForDescendants(actualClone);\n\n // Finalize clone: parent-child relationships, lock root, remove PlaybackController\n await this.#finalizeRenderClone(actualClone);\n\n return {\n clone: actualClone,\n container,\n cleanup: () => {\n container.remove();\n factoryCleanup();\n },\n };\n }\n\n /**\n * DOM path: deep clone the DOM tree and copy JavaScript properties.\n * Used for vanilla HTML/JS timelines that don't have a factory registered.\n */\n async #createRenderCloneFromDOM(): Promise<RenderCloneResult> {\n // 1. Create offscreen container\n const container = document.createElement(\"div\");\n container.className = \"ef-render-clone-container\";\n container.style.cssText = `\n position: fixed;\n left: -9999px;\n top: 0;\n width: ${this.offsetWidth || 1920}px;\n height: ${this.offsetHeight || 1080}px;\n pointer-events: none;\n overflow: hidden;\n `;\n\n // 2. Deep clone the DOM\n const cloneEl = this.cloneNode(true) as EFTimegroup;\n // Strip all id attributes from clone tree to prevent duplicate IDs in the document\n cloneEl.removeAttribute(\"id\");\n for (const el of cloneEl.querySelectorAll(\"[id]\")) {\n el.removeAttribute(\"id\");\n }\n cloneEl.setAttribute(\"data-no-workbench\", \"true\");\n cloneEl.setAttribute(\"data-no-playback-controller\", \"true\");\n\n const width = this.offsetWidth || 1920;\n const height = this.offsetHeight || 1080;\n cloneEl.style.width = `${width}px`;\n cloneEl.style.height = `${height}px`;\n cloneEl.style.display = \"block\";\n\n // 2b. Copy JavaScript properties that aren't cloned by cloneNode()\n this.#copyCaptionsData(this, cloneEl);\n this.#copyTextContent(this, cloneEl);\n\n // 3. Preserve ef-configuration context\n const originalConfig = this.closest(\"ef-configuration\");\n if (originalConfig) {\n const configClone = originalConfig.cloneNode(false) as HTMLElement;\n configClone.appendChild(cloneEl);\n container.appendChild(configClone);\n } else {\n container.appendChild(cloneEl);\n }\n\n document.body.appendChild(container);\n\n // Wait for custom elements to upgrade\n await cloneEl.updateComplete;\n\n // Copy initializers and run them on clones\n await this.#copyInitializersToClone(this, cloneEl);\n\n // Copy text segment data\n await this.#copyTextSegmentData(this, cloneEl);\n\n // Find the actual timegroup (initializer may have replaced the DOM)\n let actualClone = container.querySelector(\"ef-timegroup\") as EFTimegroup;\n if (!actualClone) {\n throw new Error(\n \"No ef-timegroup found after initializer. \" +\n \"Ensure your initializer renders a Timegroup (React) or does not remove the cloned element (vanilla JS).\",\n );\n }\n\n // Wait for custom elements to upgrade\n await customElements.whenDefined(\"ef-timegroup\");\n customElements.upgrade(container);\n actualClone = container.querySelector(\"ef-timegroup\") as EFTimegroup;\n if (!actualClone) {\n throw new Error(\"ef-timegroup element lost after upgrade\");\n }\n\n // Wait for LitElement updates\n await actualClone.updateComplete;\n\n // Wait for all descendants to be ready\n await this.#waitForDescendants(actualClone);\n\n // Copy text segment data again after initializer may have replaced DOM\n await this.#copyTextSegmentData(this, actualClone);\n\n // Finalize clone\n await this.#finalizeRenderClone(actualClone);\n\n return {\n clone: actualClone,\n container,\n cleanup: () => {\n container.remove();\n const reactRoot = (actualClone as any)._reactRoot;\n if (reactRoot) {\n queueMicrotask(() => {\n reactRoot.unmount();\n });\n }\n },\n };\n }\n\n /**\n * Shared finalization for both factory and DOM clone paths:\n * - Set up parent-child temporal relationships\n * - Lock root timegroup references\n * - Wait for media durations and captions\n * - Remove PlaybackController\n * - Initial seek to frame 0\n */\n async #finalizeRenderClone(actualClone: EFTimegroup): Promise<void> {\n // Set up parent-child relationships for temporal elements\n const setupParentChildRelationships = (\n parent: EFTimegroup,\n root: EFTimegroup,\n ) => {\n for (const child of parent.children) {\n if (child.tagName === \"EF-TIMEGROUP\") {\n const childTG = child as EFTimegroup;\n childTG.parentTimegroup = parent;\n childTG.rootTimegroup = root;\n (childTG as any).lockRootTimegroup();\n setupParentChildRelationships(childTG, root);\n } else if (\"parentTimegroup\" in child && \"rootTimegroup\" in child) {\n const temporal = child as TemporalMixinInterface & HTMLElement;\n temporal.parentTimegroup = parent;\n temporal.rootTimegroup = root;\n if (\n \"lockRootTimegroup\" in temporal &&\n typeof temporal.lockRootTimegroup === \"function\"\n ) {\n temporal.lockRootTimegroup();\n }\n } else if (child instanceof Element) {\n setupInContainer(child, parent, root);\n }\n }\n };\n\n const setupInContainer = (\n container: Element,\n nearestParentTG: EFTimegroup,\n root: EFTimegroup,\n ) => {\n for (const child of container.children) {\n if (child.tagName === \"EF-TIMEGROUP\") {\n const childTG = child as EFTimegroup;\n childTG.parentTimegroup = nearestParentTG;\n childTG.rootTimegroup = root;\n (childTG as any).lockRootTimegroup();\n setupParentChildRelationships(childTG, root);\n } else if (\"parentTimegroup\" in child && \"rootTimegroup\" in child) {\n const temporal = child as TemporalMixinInterface & HTMLElement;\n temporal.parentTimegroup = nearestParentTG;\n temporal.rootTimegroup = root;\n if (\n \"lockRootTimegroup\" in temporal &&\n typeof temporal.lockRootTimegroup === \"function\"\n ) {\n temporal.lockRootTimegroup();\n }\n } else if (child instanceof Element) {\n setupInContainer(child, nearestParentTG, root);\n }\n }\n };\n\n actualClone.rootTimegroup = actualClone;\n setupParentChildRelationships(actualClone, actualClone);\n\n await actualClone.updateComplete;\n\n // Lock root references to prevent Lit Context from overwriting\n actualClone.rootTimegroup = actualClone;\n (actualClone as any).lockRootTimegroup();\n const finalizeRootTimegroup = (el: Element) => {\n if (\"rootTimegroup\" in el && \"lockRootTimegroup\" in el) {\n (el as any).rootTimegroup = actualClone;\n (el as any).lockRootTimegroup();\n }\n for (const child of el.children) {\n finalizeRootTimegroup(child);\n }\n };\n finalizeRootTimegroup(actualClone);\n\n await actualClone.waitForMediaDurations();\n await this.#waitForCaptionsData(actualClone);\n\n // Remove PlaybackController — render clones use seekForRender directly\n if (actualClone.playbackController) {\n actualClone.playbackController.remove();\n actualClone.playbackController = undefined;\n }\n\n // Initial seek to frame 0\n await actualClone.seek(0);\n }\n\n /** @internal */\n get storageKey() {\n if (!this.id) {\n throw new Error(\"Timegroup must have an id to use localStorage.\");\n }\n return `ef-timegroup-${this.id}`;\n }\n\n /** @internal */\n get intrinsicDurationMs() {\n if (this.hasExplicitDuration) {\n return this.explicitDurationMs;\n }\n return undefined;\n }\n\n /** @internal */\n get hasOwnDuration() {\n return hasOwnDurationForMode(this.mode, this.hasExplicitDuration);\n }\n\n // ============================================================================\n // Purpose 1: Composition Rules Implementation\n // ============================================================================\n\n /** @public */\n get durationMs(): number {\n // Fixed mode delegates to parent class durationMs which handles trimming, source in/out, etc.\n if (this.mode === \"fixed\") {\n return super.durationMs;\n }\n\n // Evaluate duration semantics based on mode (Purpose 1)\n // childTemporals returns TemporalMixinInterface[], but we need HTMLElement intersection\n const childTemporalsAsElements = this.childTemporals as Array<\n TemporalMixinInterface & HTMLElement\n >;\n return evaluateDurationForMode(this, this.mode, childTemporalsAsElements);\n }\n\n // ============================================================================\n // Purpose 4: Frame Rendering - What Happens Each Frame\n // ============================================================================\n\n #mediaDurationsPromise: Promise<void> | undefined = undefined;\n\n /** @internal */\n async waitForMediaDurations(signal?: AbortSignal) {\n // Check abort before starting\n signal?.throwIfAborted();\n\n // Start loading media durations in background, but don't block if already in progress\n // This prevents multiple concurrent calls from creating redundant work\n if (!this.#mediaDurationsPromise) {\n this.#mediaDurationsPromise = this.#waitForMediaDurations(signal).catch(\n (err) => {\n // Re-throw AbortError to propagate cancellation\n if (err instanceof DOMException && err.name === \"AbortError\") {\n this.#mediaDurationsPromise = undefined;\n throw err;\n }\n console.error(\n `[EFTimegroup] waitForMediaDurations failed for ${this.id || \"unnamed\"}:`,\n err,\n );\n // Clear promise on error so it can be retried\n this.#mediaDurationsPromise = undefined;\n throw err;\n },\n );\n }\n\n // If signal is provided and aborted, throw immediately\n if (signal?.aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n\n return this.#mediaDurationsPromise;\n }\n\n /**\n * Wait for all media elements to load their initial segments.\n * Ideally we would only need the extracted index json data, but\n * that caused issues with constructing audio data. We had negative durations\n * in calculations and it was not clear why.\n */\n async #waitForMediaDurations(signal?: AbortSignal) {\n return withSpan(\n \"timegroup.waitForMediaDurations\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n // Check abort before starting\n signal?.throwIfAborted();\n\n // Don't wait for updateComplete during initialization - it causes deadlocks with nested timegroups\n // Instead, use a short delay to let elements connect, then scan for media elements\n // If elements aren't ready yet, we'll retry or they'll be picked up on the next update cycle\n await new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n\n const abortHandler = () => {\n clearTimeout(timeoutId);\n cancelAnimationFrame(rafId2);\n cancelAnimationFrame(rafId1);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", abortHandler, { once: true });\n\n let rafId1: number;\n let rafId2: number;\n let timeoutId: ReturnType<typeof setTimeout>;\n\n // Use multiple animation frames to ensure DOM is ready, but don't wait for all children\n rafId1 = requestAnimationFrame(() => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n rafId2 = requestAnimationFrame(() => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n // Small additional delay to let custom elements upgrade\n timeoutId = setTimeout(() => {\n signal?.removeEventListener(\"abort\", abortHandler);\n resolve();\n }, 10);\n });\n });\n });\n\n // Check abort after delay\n signal?.throwIfAborted();\n\n const mediaElements = deepGetMediaElements(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"mediaElementsCount\", mediaElements.length);\n }\n\n // Check abort after getting elements\n signal?.throwIfAborted();\n\n // Then, we must await the fragmentIndexTask to ensure all media elements have their\n // fragment index loaded, which is where their duration is parsed from.\n // Use Promise.allSettled with timeout to avoid blocking if asset server is slow\n const mediaLoadStart = Date.now();\n const MEDIA_LOAD_TIMEOUT_MS = 30000; // 30 second timeout per element\n\n const loadPromises = mediaElements.map(async (m, index) => {\n // Check abort before each element\n signal?.throwIfAborted();\n\n const elementStart = Date.now();\n try {\n // Use getMediaEngine async method if available\n if (typeof m.getMediaEngine === \"function\") {\n // Add timeout to prevent indefinite blocking\n const timeoutPromise = new Promise<undefined>((_, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(\n () =>\n reject(\n new Error(\n `Media element ${index} load timeout after ${MEDIA_LOAD_TIMEOUT_MS}ms`,\n ),\n ),\n MEDIA_LOAD_TIMEOUT_MS,\n );\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n },\n { once: true },\n );\n });\n\n await Promise.race([m.getMediaEngine(signal), timeoutPromise]);\n }\n // Fallback: check status and use taskComplete\n else if (m.mediaEngineTask) {\n // Status: INITIAL=0, PENDING=1, COMPLETE=2, ERROR=3\n const status = m.mediaEngineTask.status;\n\n // Already complete or errored - no need to wait\n if (status === 2 || status === 3) {\n return;\n }\n\n // Attach .catch() to taskComplete to prevent unhandled rejection\n const taskPromise = m.mediaEngineTask.taskComplete;\n taskPromise?.catch(() => {});\n\n if (taskPromise) {\n const timeoutPromise = new Promise<undefined>((_, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(\n () =>\n reject(\n new Error(\n `Media element ${index} load timeout after ${MEDIA_LOAD_TIMEOUT_MS}ms`,\n ),\n ),\n MEDIA_LOAD_TIMEOUT_MS,\n );\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n },\n { once: true },\n );\n });\n\n await Promise.race([taskPromise, timeoutPromise]);\n }\n }\n } catch (error) {\n // Re-throw AbortError to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Log only if tracing is enabled to reduce console noise\n if (isTracingEnabled()) {\n const elementElapsed = Date.now() - elementStart;\n console.error(\n `[EFTimegroup] Media element ${index} failed after ${elementElapsed}ms:`,\n error,\n );\n }\n // Don't throw - continue with other elements\n }\n });\n\n const results = await Promise.allSettled(loadPromises);\n\n // Check if any were aborted\n const aborted = results.some(\n (r) =>\n r.status === \"rejected\" &&\n r.reason instanceof DOMException &&\n r.reason.name === \"AbortError\",\n );\n if (aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n\n // Log any failures but don't throw - we want to continue even if some media fails\n const failures = results.filter((r) => r.status === \"rejected\");\n if (failures.length > 0 && isTracingEnabled()) {\n const mediaLoadElapsed = Date.now() - mediaLoadStart;\n console.warn(\n `[EFTimegroup] ${failures.length} media elements failed to load in ${mediaLoadElapsed}ms:`,\n failures.map((r) => (r.status === \"rejected\" ? r.reason : null)),\n );\n }\n\n // After waiting for durations, we must force some updates to cascade and ensure all temporal elements\n // have correct durations and start times. It is not ideal that we have to do this inside here,\n // but it is the best current way to ensure that all temporal elements have correct durations and start times.\n\n // Next, we must flush the startTimeMs cache to ensure all media elements have their\n // startTimeMs parsed fresh, otherwise the startTimeMs is cached per animation frame.\n flushStartTimeMsCache();\n\n // Flush duration cache since child durations may have changed\n flushSequenceDurationCache();\n\n // Request an update to the currentTime of this group, ensuring that time updates will cascade\n // down to children, forcing sequence groups to arrange correctly.\n // This also makes the filmstrip update correctly.\n // Defer using setTimeout(0) to avoid Lit warning about scheduling updates after update completed.\n // This method can be called during a task or after an update cycle completes, and using\n // setTimeout ensures we're completely outside any Lit update cycle.\n setTimeout(() => this.requestUpdate(\"currentTime\"), 0);\n // Note: We don't await updateComplete here during initialization to avoid deadlocks.\n // The update will complete asynchronously, and sequence groups will arrange correctly\n // once all timegroups have finished initializing. During normal operation (seeks, etc.),\n // the caller will wait for updateComplete explicitly if needed.\n },\n );\n }\n\n /** @internal */\n get childTemporals() {\n return shallowGetTemporalElements(this);\n }\n\n /**\n * Returns true if the timegroup should be wrapped with a workbench.\n *\n * A timegroup should be wrapped with a workbench if:\n * - It's being rendered (EF_RENDERING), OR\n * - The workbench property is set to true\n *\n * If the timegroup is already wrapped in a context provider like ef-preview,\n * it should NOT be wrapped in a workbench.\n * @internal\n */\n shouldWrapWithWorkbench() {\n // Never wrap when being captured by canvas preview — the element is\n // temporarily reparented for native rendering and must not spawn a\n // new workbench (which would read \"canvas\" from localStorage and\n // re-enter initCanvasMode, creating an infinite loop).\n if ((this as any).canvasPreviewActive) {\n return false;\n }\n\n // Only root timegroups should wrap with workbench\n if (!this.isRootTimegroup) {\n return false;\n }\n\n // Never wrap with workbench when inside a canvas\n // Canvas manages its own layout and coordinate system\n if (this.closest(\"ef-canvas\") !== null) {\n return false;\n }\n\n // Never wrap if already inside preview, workbench, or preview context\n if (\n this.closest(\"ef-preview\") !== null ||\n this.closest(\"ef-workbench\") !== null ||\n this.closest(\"ef-preview-context\") !== null\n ) {\n return false;\n }\n\n // Skip wrapping in test contexts or if explicitly disabled\n // Test contexts and render clones provide their own rendering infrastructure\n if (\n this.closest(\"test-context\") !== null ||\n this.hasAttribute(\"data-no-workbench\")\n ) {\n return false;\n }\n\n // During rendering, never wrap with workbench - timegroups can seek without it\n const isRendering = EF_RENDERING?.() === true;\n if (isRendering) {\n return false;\n }\n\n // Check URL param to disable workbench (only applies in non-rendering mode)\n if (typeof window !== \"undefined\") {\n const params = new URLSearchParams(window.location.search);\n if (\n params.get(\"noWorkbench\") === \"true\" ||\n params.get(\"no-workbench\") === \"true\"\n ) {\n return false;\n }\n }\n\n // Respect the explicit workbench property\n return this.workbench;\n }\n\n /** @internal */\n wrapWithWorkbench() {\n const workbench = document.createElement(\"ef-workbench\") as any;\n const parent = this.parentElement;\n\n // When in rendering mode, immediately set rendering=true before insertion\n // This prevents the workbench UI from ever being visible in rendered frames\n if (EF_RENDERING()) {\n // Use setAttribute to ensure it's set before the element connects and renders\n workbench.setAttribute(\"rendering\", \"\");\n workbench.rendering = true;\n }\n\n // Apply explicit sizing to ensure workbench fills its container\n if (parent === document.body) {\n // Direct child of body: use viewport units with fixed positioning\n workbench.style.position = \"fixed\";\n workbench.style.top = \"0\";\n workbench.style.left = \"0\";\n workbench.style.width = \"100vw\";\n workbench.style.height = \"100vh\";\n workbench.style.zIndex = \"0\";\n } else {\n // Embedded in container: ensure it fills the container\n // Use absolute positioning to prevent content-based sizing\n workbench.style.position = \"absolute\";\n workbench.style.top = \"0\";\n workbench.style.left = \"0\";\n workbench.style.width = \"100%\";\n workbench.style.height = \"100%\";\n }\n\n parent?.append(workbench);\n if (!this.hasAttribute(\"id\")) {\n this.setAttribute(\"id\", \"root-timegroup\");\n }\n\n // Create pan-zoom for selection overlay support\n // Must be in light DOM so canvas can find it via closest()\n const panZoom = document.createElement(\"ef-pan-zoom\");\n panZoom.id = \"workbench-panzoom\";\n panZoom.setAttribute(\"slot\", \"canvas\");\n panZoom.setAttribute(\"auto-fit\", \"\"); // Fit content to view on first render\n panZoom.style.width = \"100%\";\n panZoom.style.height = \"100%\";\n\n // Create canvas wrapper for selection/highlighting support\n // Get dimensions from the timegroup for explicit canvas sizing\n const rect = this.getBoundingClientRect();\n const canvas = document.createElement(\"ef-canvas\");\n canvas.id = \"workbench-canvas\";\n canvas.style.width = `${rect.width}px`;\n canvas.style.height = `${rect.height}px`;\n canvas.style.display = \"block\";\n\n // Move timegroup into canvas, canvas into pan-zoom\n canvas.append(this as unknown as Element);\n panZoom.append(canvas);\n workbench.append(panZoom);\n\n // Add hierarchy panel - targets canvas for selection support\n const hierarchy = document.createElement(\"ef-hierarchy\");\n hierarchy.setAttribute(\"slot\", \"hierarchy\");\n hierarchy.setAttribute(\"target\", \"workbench-canvas\");\n hierarchy.setAttribute(\"header\", \"Scenes\");\n workbench.append(hierarchy);\n\n // Add filmstrip/timeline - targets timegroup for playback\n const filmstrip = document.createElement(\"ef-filmstrip\");\n filmstrip.setAttribute(\"slot\", \"timeline\");\n filmstrip.setAttribute(\"target\", this.id);\n workbench.append(filmstrip);\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n * @internal\n */\n getMediaElements(): EFMedia[] {\n return deepGetMediaElements(this);\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n * @internal\n */\n async renderAudio(\n fromMs: number,\n toMs: number,\n signal?: AbortSignal,\n ): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs, signal);\n }\n\n async #executeCustomFrameTasks() {\n if (this.#customFrameTasks.size > 0) {\n const percentComplete =\n this.durationMs > 0 ? this.ownCurrentTimeMs / this.durationMs : 0;\n const frameInfo = {\n ownCurrentTimeMs: this.ownCurrentTimeMs,\n currentTimeMs: this.currentTimeMs,\n durationMs: this.durationMs,\n percentComplete,\n element: this,\n };\n\n await Promise.all(\n Array.from(this.#customFrameTasks).map((callback) =>\n Promise.resolve(callback(frameInfo)),\n ),\n );\n }\n }\n\n /** @internal */\n #seekTaskPromise: Promise<number | undefined> = Promise.resolve(undefined);\n #seekTaskAbortController: AbortController | null = null;\n\n seekTask = (() => {\n const self = this;\n const taskObj: {\n run(): void | Promise<number | undefined>;\n taskComplete: Promise<number | undefined>;\n } = {\n run: () => {\n // Abort any in-flight task\n self.#seekTaskAbortController?.abort();\n self.#seekTaskAbortController = new AbortController();\n const signal = self.#seekTaskAbortController.signal;\n\n const targetTime = self.#pendingSeekTime ?? self.#currentTime;\n self.#seekTaskPromise = self.#runSeekTask(targetTime, signal);\n taskObj.taskComplete = self.#seekTaskPromise;\n return self.#seekTaskPromise;\n },\n taskComplete: Promise.resolve(undefined),\n };\n return taskObj;\n })();\n\n async #runSeekTask(\n targetTime: number | undefined,\n signal: AbortSignal,\n ): Promise<number | undefined> {\n try {\n signal.throwIfAborted();\n\n // Delegate to playbackController if available\n if (this.playbackController) {\n // Wait for playbackController's seek to complete\n await this.playbackController.currentTime; // Trigger seek\n signal.throwIfAborted();\n return this.currentTime;\n }\n\n // Only root timegroups execute seek tasks\n if (!this.isRootTimegroup) {\n return undefined;\n }\n\n return await withSpan(\n \"timegroup.seekTask\",\n {\n timegroupId: this.id || \"unknown\",\n targetTime: targetTime ?? 0,\n durationMs: this.durationMs,\n },\n undefined,\n async (span) => {\n // Wait for media durations to be loaded\n try {\n await Promise.race([\n this.waitForMediaDurations(signal),\n new Promise<void>((_, reject) => {\n if (signal.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(\n () => reject(new Error(\"waitForMediaDurations timeout\")),\n 10000,\n );\n signal.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n },\n { once: true },\n );\n }),\n ]);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Continue with seek even if durations aren't loaded yet\n }\n\n signal.throwIfAborted();\n\n // Evaluate and apply seek target\n const newTime = evaluateSeekTarget(\n targetTime ?? 0,\n this.durationMs,\n this.effectiveFps,\n );\n if (isTracingEnabled()) {\n span.setAttribute(\"newTime\", newTime);\n }\n\n this.#currentTime = newTime;\n this.requestUpdate(\"currentTime\");\n\n await this.updateComplete;\n signal.throwIfAborted();\n\n await this.#runThrottledFrameTask();\n signal.throwIfAborted();\n\n if (!this.#restoringFromLocalStorage) {\n this.saveTimeToLocalStorage(this.#currentTime);\n }\n this.#seekInProgress = false;\n if (this.#restoringFromLocalStorage) {\n this.#restoringFromLocalStorage = false;\n }\n return newTime;\n },\n );\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n return undefined;\n }\n console.error(\"EFTimegroup seekTask error\", error);\n return undefined;\n }\n }\n\n /**\n * Get container information for this timegroup.\n * Timegroups are always containers and can contain children.\n * Display mode is determined from computed styles.\n *\n * @public\n */\n getContainerInfo(): ContainerInfo {\n const info = getContainerInfoFromElement(this);\n // Timegroups are always containers and can contain children\n return {\n ...info,\n isContainer: true,\n canContainChildren: true,\n };\n }\n\n /**\n * Get position information for this timegroup.\n * Returns computed bounds, transform, and rotation.\n *\n * @public\n */\n getPositionInfo(): ElementPositionInfo | null {\n return getPositionInfoFromElement(this);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timegroup\": EFTimegroup & Element;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EFTimegroup.js","names":["durationCache: WeakMap<EFTimegroup, number>","ancestor: Node | null","EFTimegroup","taskObj: {\n run(): void | Promise<number | undefined>;\n taskComplete: Promise<number | undefined>;\n }","#seekTaskAbortController","#pendingSeekTime","#currentTime","#seekTaskPromise","#runSeekTask","#recomputeAggregateReadyState","state","#trackedChildren","#childReadyStateHandler","#childContentChangeHandler","#mode","#overlapMs","#initializer","#restoringFromLocalStorage","#frameController","#qualityUpgradeScheduler","#customFrameTasks","#executeCustomFrameTasks","#contentEpoch","#runThrottledFrameTask","#userTimeMs","#processingPendingSeek","#seekInProgress","#getAllLitElementDescendants","result: LitElement[]","#onFrameCallback","#onFrameCleanup","#handleSlotChange","#syncChildListeners","#runInitializer","#setupPlaybackListener","#playbackListener","#previousDurationMs","#resizeObserver","#removePlaybackListener","#initializerHasRun","result: unknown","#copyTextSegmentData","updatePromises: Promise<any>[]","#waitForCaptionsData","waitPromises: Promise<unknown>[]","#copyInitializersToClone","#createRenderCloneFromFactory","#createRenderCloneFromDOM","#waitForDescendants","renderTarget: HTMLElement","#finalizeRenderClone","#copyCaptionsData","#copyTextContent","#mediaDurationsPromise","#waitForMediaDurations","rafId1: number","rafId2: number","timeoutId: ReturnType<typeof setTimeout>"],"sources":["../../src/elements/EFTimegroup.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport debug from \"debug\";\nimport { css, html, LitElement, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nimport { quantizeToFrameTimeS } from \"../utils/frameTime.js\";\nimport { getCloneFactory } from \"./cloneFactoryRegistry.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { isTracingEnabled, withSpan } from \"../otel/tracingHelpers.js\";\nimport {\n FrameController,\n type FrameRenderable,\n type FrameState,\n PRIORITY_DEFAULT,\n} from \"../preview/FrameController.js\";\nimport { QualityUpgradeScheduler } from \"../preview/QualityUpgradeScheduler.js\";\nimport { deepGetMediaElements, type EFMedia } from \"./EFMedia.js\";\nimport {\n EFTemporal,\n flushStartTimeMsCache,\n resetTemporalCache,\n shallowGetTemporalElements,\n timegroupContext,\n type TemporalMixinInterface,\n} from \"./EFTemporal.js\";\nimport { parseTimeToMs } from \"./parseTimeToMs.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.js\";\nimport { TimegroupController } from \"./TimegroupController.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\nimport {\n type ContainerInfo,\n getContainerInfoFromElement,\n} from \"./ContainerInfo.js\";\nimport {\n type ElementPositionInfo,\n getPositionInfoFromElement,\n} from \"./ElementPositionInfo.js\";\n// Import only types - actual function loaded dynamically\nimport type { RenderToVideoOptions } from \"../preview/renderTimegroupToVideo.types.js\";\nimport type { PlaybackControllerUpdateEvent } from \"../gui/PlaybackController.js\";\n\n// Side-effect imports for workbench wrapping\nimport \"../canvas/EFCanvas.js\";\nimport \"../gui/hierarchy/EFHierarchy.js\";\nimport \"../gui/EFFilmstrip.js\";\nimport \"../gui/EFWorkbench.js\";\nimport \"../gui/EFFitScale.js\";\nimport \"./EFPanZoom.js\";\n\nconst log = debug(\"ef:elements:EFTimegroup\");\n\n// Custom frame task callback type\nexport type FrameTaskCallback = (info: {\n ownCurrentTimeMs: number;\n currentTimeMs: number;\n durationMs: number;\n percentComplete: number;\n element: EFTimegroup;\n}) => void | Promise<void>;\n\n/**\n * Result of createRenderClone() - contains the clone, its container, and cleanup function.\n */\nexport interface RenderCloneResult {\n /** The cloned timegroup, fully functional with its own time state */\n clone: EFTimegroup;\n /** The offscreen container holding the clone */\n container: HTMLElement;\n /** Call this to remove the clone from DOM and clean up */\n cleanup: () => void;\n}\n\n/**\n * Initializer function type for setting up JavaScript behavior on timegroup instances.\n * This function is called on both the prime timeline and each render clone.\n *\n * CONSTRAINTS:\n * - MUST be synchronous (no async/await, no Promise return)\n * - MUST complete in <2000ms (error) or <100ms (warning)\n * - Should only register callbacks and set up behavior, not do expensive work\n * - GPU operations (WebGL context creation, shader compilation) may take up to ~1s\n */\nexport type TimegroupInitializer = (timegroup: EFTimegroup) => void;\n\n// Constants for initializer time budget enforcement\nconst INITIALIZER_ERROR_THRESHOLD_MS = 2000;\nconst INITIALIZER_WARN_THRESHOLD_MS = 100;\n\n// ============================================================================\n// Purpose 1: Composition Rules - How Duration is Determined\n// ============================================================================\n//\n// A timegroup's duration is determined by its mode:\n// - \"fixed\": Uses explicit duration attribute (base case)\n// - \"sequence\": Sum of child durations minus overlaps\n// - \"contain\": Maximum of child durations\n// - \"fit\": Inherits duration from parent timegroup\n//\n// Core invariant: Every timegroup has exactly one duration value at any moment,\n// computed from either explicit specification (fixed mode) or child relationships\n// (sequence/contain/fit modes).\n//\n// ============================================================================\n\n/**\n * The four timegroup modes define how duration is calculated:\n * - \"fit\": Inherits duration from parent timegroup\n * - \"fixed\": Uses explicit duration attribute\n * - \"sequence\": Sum of child durations minus overlaps\n * - \"contain\": Maximum of child durations\n */\nexport type TimeMode = \"fit\" | \"fixed\" | \"sequence\" | \"contain\";\n\n/**\n * Per-phase timing data returned by seekForRender().\n * All values are in milliseconds.\n */\nexport interface SeekForRenderTiming {\n updateComplete1Ms: number;\n updateComplete2Ms: number;\n updateComplete3Ms: number;\n textSegmentsMs: number;\n renderFrameMs: number;\n renderFrameQueryMs: number;\n renderFramePrepareMs: number;\n renderFrameDrawMs: number;\n renderFrameAnimsMs: number;\n frameTasksMs: number;\n totalMs: number;\n}\n\n// Cache for duration calculations to avoid O(n) recalculation on every access\n// Used by all modes (sequence, contain) to avoid repeated iteration through children\nlet durationCache: WeakMap<EFTimegroup, number> = new WeakMap();\n\nexport const flushDurationCache = () => {\n durationCache = new WeakMap();\n};\n\n// Keep alias for backwards compatibility\nexport const flushSequenceDurationCache = flushDurationCache;\n\n// Track timegroups currently calculating duration to prevent infinite loops\nconst durationCalculationInProgress = new WeakSet<EFTimegroup>();\n\n// Export function to check if a timegroup is currently calculating duration\n// This is used by EFTemporal to prevent calling parent.durationMs during calculation\nexport const isTimegroupCalculatingDuration = (\n timegroup: EFTimegroup | undefined,\n): boolean => {\n return (\n timegroup !== undefined && durationCalculationInProgress.has(timegroup)\n );\n};\n\n// Register this function with EFTemporal to break circular dependency\n// EFTemporal needs this function but can't import it directly due to circular dependency\nimport { registerIsTimegroupCalculatingDuration } from \"./EFTemporal.js\";\nregisterIsTimegroupCalculatingDuration(isTimegroupCalculatingDuration);\n\n/**\n * Determines if a timegroup has its own duration based on its mode.\n * This is the semantic rule: which modes produce independent durations.\n */\nfunction hasOwnDurationForMode(\n mode: TimeMode,\n hasExplicitDuration: boolean,\n): boolean {\n return (\n mode === \"contain\" ||\n mode === \"sequence\" ||\n (mode === \"fixed\" && hasExplicitDuration)\n );\n}\n\n/**\n * Determines if a child temporal element should participate in parent duration calculation.\n *\n * Semantic rule: Fit-mode children inherit from parent, so they don't contribute to parent's\n * duration calculation (to avoid circular dependencies). Children without own duration\n * also don't contribute.\n */\nfunction shouldParticipateInDurationCalculation(\n child: TemporalMixinInterface & HTMLElement,\n): boolean {\n // Fit timegroups look \"up\" to their parent for duration, so skip to avoid infinite loop\n if (child instanceof EFTimegroup && child.mode === \"fit\") {\n return false;\n }\n // Only children with their own duration contribute\n if (!child.hasOwnDuration) {\n return false;\n }\n return true;\n}\n\n/**\n * Evaluates duration for \"fit\" mode: inherits from parent.\n * Semantic rule: fit mode always matches parent duration, or 0 if no parent.\n */\nfunction evaluateFitDuration(parentTimegroup: EFTimegroup | undefined): number {\n if (!parentTimegroup) {\n return 0;\n }\n return parentTimegroup.durationMs;\n}\n\n/**\n * Evaluates duration for \"sequence\" mode: sum of children minus overlaps.\n * Semantic rule: sequence mode sums child durations, subtracting overlap between consecutive items.\n * Fit-mode children are excluded to avoid circular dependencies.\n */\nfunction evaluateSequenceDuration(\n timegroup: EFTimegroup,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n overlapMs: number,\n): number {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = durationCache.get(timegroup);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let duration = 0;\n let participatingIndex = 0;\n childTemporals.forEach((child) => {\n if (!shouldParticipateInDurationCalculation(child)) {\n return;\n }\n // Prevent infinite loops: skip children that are already calculating their duration\n if (\n child instanceof EFTimegroup &&\n durationCalculationInProgress.has(child)\n ) {\n return;\n }\n\n // Additional safety: if child is a timegroup, check if any of its ancestors\n // (EXCLUDING the current timegroup) are calculating.\n // This prevents cycles where a child's descendant eventually calls back to an ancestor,\n // but allows direct children of the current timegroup to participate.\n if (child instanceof EFTimegroup) {\n let ancestor: Node | null = child.parentNode;\n let shouldSkip = false;\n while (ancestor) {\n // Stop FIRST if we've reached the current timegroup - direct children are allowed\n if (ancestor === timegroup) {\n break;\n }\n if (\n ancestor instanceof EFTimegroup &&\n durationCalculationInProgress.has(ancestor)\n ) {\n // Found a calculating ancestor (not the current timegroup) - skip this child to prevent cycle\n shouldSkip = true;\n break;\n }\n ancestor = ancestor.parentNode;\n }\n if (shouldSkip) {\n return;\n }\n }\n\n // Subtract overlap for all items after the first\n if (participatingIndex > 0) {\n duration -= overlapMs;\n }\n duration += child.durationMs;\n participatingIndex++;\n });\n\n // Ensure non-negative duration (invariant)\n duration = Math.max(0, duration);\n\n // Cache the calculated duration\n durationCache.set(timegroup, duration);\n return duration;\n}\n\n/**\n * Evaluates duration for \"contain\" mode: maximum of children.\n * Semantic rule: contain mode takes the maximum child duration.\n * Fit-mode children and children without own duration are excluded.\n */\nfunction evaluateContainDuration(\n timegroup: EFTimegroup,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n): number {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = durationCache.get(timegroup);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let maxDuration = 0;\n for (const child of childTemporals) {\n if (!shouldParticipateInDurationCalculation(child)) {\n continue;\n }\n // Prevent infinite loops: skip children that are already calculating their duration\n // This check applies to all timegroup children, not just contain mode, because\n // a sequence-mode child could contain a contain-mode grandchild that\n // eventually references back to the parent through the parent chain\n if (\n child instanceof EFTimegroup &&\n durationCalculationInProgress.has(child)\n ) {\n continue;\n }\n\n // Additional safety: if child is a timegroup, check if any of its ancestors\n // (EXCLUDING the current timegroup) are calculating.\n // This prevents cycles where a child's descendant eventually calls back to an ancestor,\n // but allows direct children of the current timegroup to participate.\n if (child instanceof EFTimegroup) {\n let ancestor: Node | null = child.parentNode;\n let shouldSkip = false;\n while (ancestor) {\n // Stop FIRST if we've reached the current timegroup - direct children are allowed\n if (ancestor === timegroup) {\n break;\n }\n if (\n ancestor instanceof EFTimegroup &&\n durationCalculationInProgress.has(ancestor)\n ) {\n // Found a calculating ancestor (not the current timegroup) - skip this child to prevent cycle\n shouldSkip = true;\n break;\n }\n ancestor = ancestor.parentNode;\n }\n if (shouldSkip) {\n continue;\n }\n }\n\n maxDuration = Math.max(maxDuration, child.durationMs);\n }\n // Ensure non-negative duration (invariant)\n const duration = Math.max(0, maxDuration);\n\n // Cache the calculated duration\n durationCache.set(timegroup, duration);\n return duration;\n}\n\n/**\n * Evaluates duration based on timegroup mode.\n * This is the semantic evaluation function - it determines what duration should be.\n *\n * Note: Fixed mode is handled inline in the getter because it needs to call super.durationMs\n * which requires the class context. The other modes are extracted for clarity.\n */\nfunction evaluateDurationForMode(\n timegroup: EFTimegroup,\n mode: TimeMode,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n): number {\n switch (mode) {\n case \"fit\":\n return evaluateFitDuration(timegroup.parentTimegroup);\n case \"sequence\": {\n // Mark this timegroup as calculating duration to prevent infinite loops\n durationCalculationInProgress.add(timegroup);\n try {\n return evaluateSequenceDuration(\n timegroup,\n childTemporals,\n timegroup.overlapMs,\n );\n } finally {\n // Always remove the marker, even if an error occurs\n durationCalculationInProgress.delete(timegroup);\n }\n }\n case \"contain\": {\n // Mark this timegroup as calculating duration to prevent infinite loops\n durationCalculationInProgress.add(timegroup);\n try {\n return evaluateContainDuration(timegroup, childTemporals);\n } finally {\n // Always remove the marker, even if an error occurs\n durationCalculationInProgress.delete(timegroup);\n }\n }\n default:\n throw new Error(`Invalid time mode: ${mode}`);\n }\n}\n\nexport const shallowGetTimegroups = (\n element: Element,\n groups: EFTimegroup[] = [],\n) => {\n for (const child of Array.from(element.children)) {\n if (child instanceof EFTimegroup) {\n groups.push(child);\n } else {\n shallowGetTimegroups(child, groups);\n }\n }\n return groups;\n};\n\n// ============================================================================\n// Purpose 2: Time Propagation - How currentTime Flows Root to Children\n// ============================================================================\n//\n// Time propagation determines how the root timegroup's currentTime flows to child\n// temporal elements, computing each child's ownCurrentTime based on:\n// - The root's currentTime (global coordinate)\n// - The child's startTimeMs (determined by parent's composition mode)\n// - The parent's mode (sequence/contain/fit/fixed)\n//\n// Propagation rules by mode:\n// - Sequence: Each child's ownCurrentTime progresses within its time-shifted window\n// - Contain: All children share the same ownCurrentTime as parent\n// - Fit: Child ownCurrentTime = parent ownCurrentTime (identity mapping)\n//\n// Core invariant: Only root timegroup's currentTime should be written.\n// Child times are computed from parent state via ownCurrentTimeMs.\n//\n// Note: Time propagation logic is primarily implemented in EFTemporal.ts\n// (ownCurrentTimeMs getter and startTimeMs calculation). The timegroup's\n// currentTime setter triggers propagation by updating root time.\n//\n// ============================================================================\n\n// ============================================================================\n// Purpose 3: Seeking - Moving to a Specific Time\n// ============================================================================\n//\n// Seeking moves the timeline to a specific time position. This involves:\n// 1. Quantizing the requested time to frame boundaries (based on fps)\n// 2. Clamping to valid range [0, duration]\n// 3. Updating root timegroup's currentTime (which triggers time propagation)\n// 4. Waiting for all media and frame tasks to complete\n//\n// Core invariant: All time values snap to frame boundaries when FPS is set.\n// This ensures consistent seek/render behavior.\n//\n// ============================================================================\n\n/**\n * Evaluates the target time for a seek operation.\n * Applies quantization and clamping to determine the valid seek target.\n */\nfunction evaluateSeekTarget(\n requestedTime: number,\n durationMs: number,\n fps: number,\n): number {\n // Quantize to frame boundaries\n const quantizedTime = quantizeToFrameTimeS(requestedTime, fps);\n // Clamp to valid range [0, duration]\n return Math.max(0, Math.min(quantizedTime, durationMs / 1000));\n}\n\n@customElement(\"ef-timegroup\")\nexport class EFTimegroup\n extends EFTargetable(EFTemporal(TWMixin(LitElement)))\n implements FrameRenderable\n{\n static get observedAttributes(): string[] {\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mode\",\n \"overlap\",\n \"currenttime\",\n \"fit\",\n \"fps\",\n \"auto-init\",\n \"workbench\",\n ];\n }\n\n static styles = css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n\n ::slotted(ef-timegroup) {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n overflow: initial;\n }\n `;\n\n /** @internal */\n @provide({ context: timegroupContext })\n _timeGroupContext = this;\n\n /** @internal */\n @provide({ context: efContext })\n efContext = this;\n\n // ---- Content Readiness Aggregation ----\n\n #trackedChildren = new Set<TemporalMixinInterface & HTMLElement>();\n\n override shouldAutoReady(): boolean {\n return false;\n }\n\n #childReadyStateHandler = () => {\n this.#recomputeAggregateReadyState();\n };\n\n #childContentChangeHandler = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n this.emitContentChange(detail?.reason ?? \"content\");\n };\n\n #recomputeAggregateReadyState(): void {\n const children = shallowGetTemporalElements(this);\n if (children.length === 0) {\n this.setContentReadyState(\"ready\");\n return;\n }\n\n let hasLoading = false;\n let hasError = false;\n let hasIdle = false;\n\n for (const child of children) {\n const state = child.contentReadyState;\n if (state === \"loading\") hasLoading = true;\n else if (state === \"error\") hasError = true;\n else if (state === \"idle\") hasIdle = true;\n }\n\n if (hasError) {\n this.setContentReadyState(\"error\");\n } else if (hasLoading) {\n this.setContentReadyState(\"loading\");\n } else if (hasIdle) {\n this.setContentReadyState(\"loading\");\n } else {\n this.setContentReadyState(\"ready\");\n }\n }\n\n #syncChildListeners(): void {\n const currentChildren = new Set(\n shallowGetTemporalElements(this) as Array<\n TemporalMixinInterface & HTMLElement\n >,\n );\n\n // Remove listeners from children that left\n for (const child of this.#trackedChildren) {\n if (!currentChildren.has(child)) {\n child.removeEventListener(\n \"readystatechange\",\n this.#childReadyStateHandler,\n );\n child.removeEventListener(\n \"contentchange\",\n this.#childContentChangeHandler,\n );\n }\n }\n\n // Add listeners to new children\n for (const child of currentChildren) {\n if (!this.#trackedChildren.has(child)) {\n child.addEventListener(\n \"readystatechange\",\n this.#childReadyStateHandler,\n );\n child.addEventListener(\n \"contentchange\",\n this.#childContentChangeHandler,\n );\n }\n }\n\n this.#trackedChildren = currentChildren;\n this.#recomputeAggregateReadyState();\n }\n\n // ---- End Content Readiness Aggregation ----\n\n /** @public */\n #mode: TimeMode = \"contain\";\n get mode(): TimeMode {\n return this.#mode;\n }\n set mode(value: TimeMode) {\n if (this.#mode === value) return;\n const old = this.#mode;\n this.#mode = value;\n this.requestUpdate(\"mode\", old);\n if (this.getAttribute(\"mode\") !== value) {\n this.setAttribute(\"mode\", value);\n }\n }\n\n /** @public */\n #overlapMs = 0;\n get overlapMs(): number {\n return this.#overlapMs;\n }\n set overlapMs(value: number) {\n if (this.#overlapMs === value) return;\n const old = this.#overlapMs;\n this.#overlapMs = value;\n this.requestUpdate(\"overlapMs\", old);\n const attrVal = value > 0 ? `${value}ms` : null;\n if (attrVal && this.getAttribute(\"overlap\") !== attrVal) {\n this.setAttribute(\"overlap\", attrVal);\n } else if (!attrVal && this.hasAttribute(\"overlap\")) {\n this.removeAttribute(\"overlap\");\n }\n }\n\n #initializer?: TimegroupInitializer;\n\n /**\n * Initializer function for setting up JavaScript behavior on this timegroup.\n * This function is called ONCE per instance - on the prime timeline when first connected,\n * and on each render clone when created.\n *\n * Use this to register frame callbacks, set up event listeners, or initialize state.\n * The same initializer code runs on both prime and clones, eliminating duplication.\n *\n * CONSTRAINTS:\n * - MUST be synchronous (no async/await, no Promise return)\n * - MUST complete in <100ms (error thrown) or <10ms (warning logged)\n * - Should only register callbacks and set up behavior, not do expensive work\n *\n * TIMING:\n * - If set before element connects to DOM: runs automatically after connectedCallback\n * - If set after element is connected: runs immediately\n * - Clones automatically copy and run the initializer when created\n *\n * @example\n * ```javascript\n * const tg = document.querySelector('ef-timegroup');\n * tg.initializer = (instance) => {\n * // Runs once on prime timeline, once on each clone\n * instance.addFrameTask((info) => {\n * // Update content based on time\n * });\n * };\n * ```\n * @public\n */\n get initializer(): TimegroupInitializer | undefined {\n return this.#initializer;\n }\n\n set initializer(fn: TimegroupInitializer | undefined) {\n this.#initializer = fn;\n // Just store the function. Execution is handled by:\n // - connectedCallback (for elements that have initializer before connection)\n // - #copyInitializersToClone (explicitly triggers for render clones)\n }\n\n /**\n * Track if initializer has run on this instance to prevent double execution.\n * @internal\n */\n #initializerHasRun = false;\n\n /** @public */\n @property({ type: Number })\n fps = 30;\n\n /**\n * When true, automatically seeks to frame 0 after media durations are loaded.\n * Only applies to root timegroups (timegroups that are not nested inside another timegroup).\n * This ensures the first frame is rendered immediately on initialization.\n */\n @property({ type: Boolean, attribute: \"auto-init\" })\n autoInit = false;\n\n /**\n * When true, automatically wraps this root timegroup with an ef-workbench element.\n * The workbench provides development UI including hierarchy panel, timeline, and playback controls.\n * Only applies to root timegroups.\n * @public\n */\n @property({ type: Boolean, reflect: true })\n workbench = false;\n\n attributeChangedCallback(\n name: string,\n old: string | null,\n value: string | null,\n ): void {\n if (name === \"mode\" && value) {\n this.mode = value as typeof this.mode;\n }\n if (name === \"overlap\" && value) {\n this.overlapMs = parseTimeToMs(value);\n }\n if (name === \"auto-init\") {\n this.autoInit = value !== null;\n }\n if (name === \"fps\" && value) {\n this.fps = Number.parseFloat(value);\n }\n if (name === \"workbench\") {\n this.workbench = value !== null;\n }\n super.attributeChangedCallback(name, old, value);\n }\n\n /** @public */\n @property({ type: String })\n fit: \"none\" | \"contain\" | \"cover\" = \"none\";\n\n #resizeObserver?: ResizeObserver;\n\n /** Content epoch - increments when visual content changes (used by thumbnail cache) */\n #contentEpoch = 0;\n\n #currentTime: number | undefined = undefined;\n #userTimeMs: number = 0; // What the user last requested (for preview display)\n #seekInProgress = false;\n #pendingSeekTime: number | undefined;\n #processingPendingSeek = false;\n #restoringFromLocalStorage = false; // Guard to prevent recursive seeks during localStorage restoration\n\n /** @internal */\n isRestoringFromLocalStorage(): boolean {\n return this.#restoringFromLocalStorage;\n }\n\n /** @internal - Used by PlaybackController to set restoration state */\n setRestoringFromLocalStorage(value: boolean): void {\n this.#restoringFromLocalStorage = value;\n }\n #customFrameTasks: Set<FrameTaskCallback> = new Set();\n #onFrameCallback: FrameTaskCallback | null = null;\n #onFrameCleanup: (() => void) | null = null;\n #playbackListener: ((event: PlaybackControllerUpdateEvent) => void) | null =\n null;\n\n /**\n * Centralized frame controller for coordinating element rendering.\n * Replaces the distributed Lit Task hierarchy with a single control point.\n */\n #frameController: FrameController = new FrameController(this);\n\n /**\n * Get the frame controller for centralized rendering coordination.\n * @public\n */\n get frameController(): FrameController {\n return this.#frameController;\n }\n\n /**\n * Centralized quality upgrade scheduler for coordinating main-quality segment fetching.\n * Lives alongside FrameController to manage background quality upgrades.\n */\n #qualityUpgradeScheduler: QualityUpgradeScheduler =\n new QualityUpgradeScheduler({\n requestFrameRender: () => this.requestFrameRender(),\n });\n\n /**\n * Get the quality upgrade scheduler for background segment fetching.\n * @public\n */\n get qualityUpgradeScheduler(): QualityUpgradeScheduler {\n return this.#qualityUpgradeScheduler;\n }\n\n // ============================================================================\n // FrameRenderable Interface Implementation\n // ============================================================================\n // Allows FrameController to discover and coordinate nested timegroups.\n // This ensures frame callbacks registered on nested timegroups are executed.\n // ============================================================================\n\n /**\n * Query timegroup's readiness state for a given time.\n * Timegroups are always ready (no async preparation needed).\n * @public\n */\n getFrameState(_timeMs: number): FrameState {\n return {\n needsPreparation: false,\n isReady: true,\n priority: PRIORITY_DEFAULT,\n };\n }\n\n /**\n * Async preparation phase (no-op for timegroups).\n * Timegroups don't need preparation - they just coordinate child rendering.\n * @public\n */\n async prepareFrame(_timeMs: number, _signal: AbortSignal): Promise<void> {\n // No preparation needed for timegroups\n }\n\n /**\n * Synchronous render phase - executes custom frame callbacks.\n * Called by FrameController after all preparation is complete.\n * Kicks off async frame callbacks without blocking (they run in background).\n * @public\n */\n renderFrame(_timeMs: number): void {\n // Execute custom frame tasks registered via addFrameTask()\n // Fire and forget - callbacks can be async but we don't block here\n // The frameTask.taskComplete promise tracks completion if needed\n if (this.#customFrameTasks.size > 0) {\n this.#executeCustomFrameTasks().catch((error) => {\n console.error(\"EFTimegroup custom frame task error:\", error);\n });\n }\n }\n\n /**\n * Get the effective FPS for this timegroup.\n * During rendering, uses the render options FPS if available.\n * Otherwise uses the configured fps property.\n * @public\n */\n get effectiveFps(): number {\n // During rendering, prefer the render options FPS\n if (typeof window !== \"undefined\" && window.EF_FRAMEGEN?.renderOptions) {\n return window.EF_FRAMEGEN.renderOptions.encoderOptions.video.framerate;\n }\n return this.fps;\n }\n\n /**\n * Get the current content epoch (used by thumbnail cache).\n * The epoch increments whenever visual content changes.\n * @public\n */\n get contentEpoch(): number {\n return this.#contentEpoch;\n }\n\n /**\n * Increment content epoch (called when visual content changes).\n * This invalidates cached thumbnails by changing their cache keys.\n * @public\n */\n incrementContentEpoch(): void {\n this.#contentEpoch++;\n }\n\n /**\n * Request a frame re-render at the current time.\n *\n * Use this when the source-to-timeline mapping has changed (e.g., sourcein/sourceout)\n * but currentTimeMs hasn't. The FrameController only re-renders when currentTimeMs\n * or durationMs change, so this provides a way for child elements to request a\n * re-render when their internal state changes the visual output.\n * @public\n */\n requestFrameRender(): void {\n this.#frameController.abort();\n this.#runThrottledFrameTask();\n }\n\n async #runThrottledFrameTask(): Promise<void> {\n if (this.playbackController) {\n return this.playbackController.runThrottledFrameTask();\n }\n // Use FrameController directly (no frameTask fallback)\n try {\n await this.#frameController.renderFrame(this.currentTimeMs, {\n onAnimationsUpdate: (root) => {\n updateAnimations(root as typeof this);\n },\n });\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n return;\n }\n console.error(\"FrameController error:\", error);\n }\n }\n\n // ============================================================================\n // Purpose 3: Seeking Implementation\n // ============================================================================\n\n /** @public */\n @property({ type: Number, attribute: \"currenttime\" })\n set currentTime(time: number) {\n // Evaluate seek target (quantization and clamping)\n const seekTarget = evaluateSeekTarget(\n time,\n this.durationMs,\n this.effectiveFps,\n );\n\n // Delegate to playbackController if available\n if (this.playbackController) {\n this.playbackController.currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n return;\n }\n\n // Only root timegroups can have their currentTime set directly\n if (!this.isRootTimegroup) {\n return;\n }\n\n // Validate seek target\n if (Number.isNaN(seekTarget)) {\n return;\n }\n\n // Skip if already at target time (unless processing pending seek or restoring from localStorage)\n if (\n seekTarget === this.#currentTime &&\n !this.#processingPendingSeek &&\n !this.#restoringFromLocalStorage\n ) {\n return;\n }\n\n // Skip if this is the same as pending seek\n if (this.#pendingSeekTime === seekTarget) {\n return;\n }\n\n // Prevent recursive seeks during localStorage restoration\n if (this.#restoringFromLocalStorage && seekTarget !== this.#currentTime) {\n // Allow the restoration seek to proceed, but prevent subsequent seeks\n // The flag will be cleared after the seek completes\n }\n\n // Handle concurrent seeks by queuing pending seek\n // This ensures we only have ONE seek in flight at a time, avoiding wasted work.\n // When scrubbing quickly, intermediate positions are skipped entirely - we don't\n // start work we know will be thrown away.\n if (this.#seekInProgress) {\n this.#pendingSeekTime = seekTarget;\n this.#currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n return;\n }\n\n // Execute seek - update both source time and user time\n this.#currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n this.#seekInProgress = true;\n\n // Attach .catch() to prevent unhandled rejection warning - errors are handled by seekTask.onError\n Promise.resolve(this.seekTask.run())\n .catch(() => {})\n .finally(async () => {\n this.#seekInProgress = false;\n\n // CRITICAL: Coordinate animations after seekTask completes\n // This handles seeks from currentTime setter (like localStorage restore)\n const { updateAnimations } = await import(\"./updateAnimations.js\");\n updateAnimations(this);\n\n // Process pending seek if it differs from completed seek\n // This jumps directly to wherever the user ended up, skipping intermediates\n if (\n this.#pendingSeekTime !== undefined &&\n this.#pendingSeekTime !== seekTarget\n ) {\n const pendingTime = this.#pendingSeekTime;\n this.#pendingSeekTime = undefined;\n this.#processingPendingSeek = true;\n try {\n this.currentTime = pendingTime;\n } finally {\n this.#processingPendingSeek = false;\n }\n } else {\n this.#pendingSeekTime = undefined;\n }\n });\n }\n\n /** @public */\n get currentTime() {\n if (this.playbackController) {\n return this.playbackController.currentTime;\n }\n return this.#currentTime ?? 0;\n }\n\n /** @public */\n set currentTimeMs(ms: number) {\n this.currentTime = ms / 1000;\n }\n\n /** @public */\n get currentTimeMs() {\n return this.currentTime * 1000;\n }\n\n /**\n * The time the user last requested via seek/scrub.\n * Preview systems should use this instead of currentTimeMs to avoid\n * seeing intermediate times during batch operations (thumbnails, export).\n * @public\n */\n get userTimeMs(): number {\n return this.#userTimeMs;\n }\n\n /**\n * Seek to a specific time and wait for all frames to be ready.\n * This is the recommended way to seek in tests and programmatic control.\n *\n * Combines seeking (Purpose 3) with frame rendering (Purpose 4) to ensure\n * all visible elements are ready after the seek completes.\n *\n * Updates both the source time AND userTimeMs (what the preview displays).\n *\n * @param timeMs - Time in milliseconds to seek to\n * @returns Promise that resolves when the seek is complete and all visible children are ready\n * @public\n */\n async seek(timeMs: number): Promise<void> {\n // Update user time - this is what the preview should display\n this.#userTimeMs = timeMs;\n\n // Execute seek (Purpose 3)\n this.currentTimeMs = timeMs;\n await this.seekTask.taskComplete;\n\n // Handle localStorage when playbackController delegates seek\n if (this.playbackController) {\n this.saveTimeToLocalStorage(this.currentTime);\n }\n\n // Wait for frame rendering via FrameController\n await this.#frameController.renderFrame(timeMs, {\n onAnimationsUpdate: (root) => {\n updateAnimations(root as typeof this);\n },\n });\n }\n\n /**\n * Optimized seek for render loops.\n * Unlike `seek()`, this:\n * - Skips waitForMediaDurations (already loaded at render setup)\n * - Skips localStorage persistence\n * - Uses FrameController for centralized element coordination\n *\n * Still waits for all content to be ready (Lit updates, element preparation, rendering).\n *\n * @param timeMs - Time in milliseconds to seek to\n * @internal\n */\n async seekForRender(timeMs: number): Promise<SeekForRenderTiming> {\n const t0 = performance.now();\n\n // Set time directly (skip seekTask overhead)\n const newTime = timeMs / 1000;\n this.#userTimeMs = timeMs;\n this.#currentTime = newTime;\n // Sync playbackController if present so currentTime getter returns\n // the correct value regardless of which code path reads it.\n if (this.playbackController) {\n this.playbackController.currentTime = newTime;\n }\n // Sync the base mixin's local time so ownCurrentTimeMs returns the\n // correct value in render clones (which have no playback controller).\n this._setLocalTimeMs(timeMs);\n this.requestUpdate(\"currentTime\");\n\n // First await: let Lit propagate time to children\n const t1 = performance.now();\n await this.updateComplete;\n const updateComplete1Ms = performance.now() - t1;\n\n // Collect all LitElement descendants (not just those with frameTask)\n // This ensures ef-text, ef-captions, and other reactive elements update\n const allLitElements = this.#getAllLitElementDescendants();\n\n // Wait for ALL LitElement descendants to complete their reactive updates\n // This is critical for elements like ef-text and ef-captions that don't have frameTask\n const t2 = performance.now();\n await Promise.all(allLitElements.map((el) => el.updateComplete));\n const updateComplete2Ms = performance.now() - t2;\n\n // OwnCurrentTimeController defers child updates via queueMicrotask.\n // Those microtasks have fired by this point (between await boundaries).\n // Await a second pass of updateComplete to catch those deferred updates.\n const t3 = performance.now();\n await Promise.all(allLitElements.map((el) => el.updateComplete));\n const updateComplete3Ms = performance.now() - t3;\n\n // Wait for ef-text elements to have their segments ready\n // ef-text creates segments asynchronously via requestAnimationFrame\n const textElements = allLitElements.filter(\n (el) => el.tagName === \"EF-TEXT\",\n );\n const t4 = performance.now();\n if (textElements.length > 0) {\n await Promise.all(\n textElements.map((el) => {\n if (\n \"whenSegmentsReady\" in el &&\n typeof el.whenSegmentsReady === \"function\"\n ) {\n return (el as any).whenSegmentsReady();\n }\n return Promise.resolve();\n }),\n );\n\n // Force synchronous layout reflow after text segments are created/updated.\n // offsetHeight triggers layout computation — no need to yield a full rAF\n // (which costs 16-40ms and is throttled in hidden tabs).\n void this.offsetHeight;\n }\n const textSegmentsMs = performance.now() - t4;\n\n // Use FrameController for centralized element coordination\n // This replaces the old distributed frameTask system\n // Animation updates are handled via the onAnimationsUpdate callback\n const t5 = performance.now();\n const frameControllerTiming = await this.#frameController.renderFrame(\n timeMs,\n {\n waitForLitUpdate: false,\n onAnimationsUpdate: (root) => {\n updateAnimations(root as typeof this);\n // CRITICAL: Force style recalculation after updateAnimations sets animation.currentTime\n // Without this, getComputedStyle may return stale values (e.g., opacity: 0 instead of 1)\n // Accessing offsetWidth triggers synchronous style recalc\n void (root as HTMLElement).offsetWidth;\n },\n },\n );\n const renderFrameMs = performance.now() - t5;\n\n // Execute custom frame tasks registered via addFrameTask()\n const t6 = performance.now();\n await this.#executeCustomFrameTasks();\n const frameTasksMs = performance.now() - t6;\n\n const totalMs = performance.now() - t0;\n\n return {\n updateComplete1Ms,\n updateComplete2Ms,\n updateComplete3Ms,\n textSegmentsMs,\n renderFrameMs,\n renderFrameQueryMs: frameControllerTiming?.queryMs ?? 0,\n renderFramePrepareMs: frameControllerTiming?.prepareMs ?? 0,\n renderFrameDrawMs: frameControllerTiming?.renderMs ?? 0,\n renderFrameAnimsMs: frameControllerTiming?.animsMs ?? 0,\n frameTasksMs,\n totalMs,\n };\n }\n\n /**\n * Collects all LitElement descendants recursively.\n * Used by seekForRender to ensure all reactive elements have updated.\n * Prunes subtrees of temporally-invisible elements — their Lit updates\n * still fire via microtasks (OwnCurrentTimeController), so skipping\n * the explicit await is safe.\n */\n #getAllLitElementDescendants(): LitElement[] {\n const result: LitElement[] = [];\n const currentTimeMs = this.currentTimeMs;\n\n const walk = (el: Element) => {\n for (const child of el.children) {\n // Temporal pruning: skip invisible temporal elements and their subtrees\n if (\"startTimeMs\" in child && \"endTimeMs\" in child) {\n const startMs = (child as any).startTimeMs ?? -Infinity;\n const endMs = (child as any).endTimeMs ?? Infinity;\n if (\n endMs > startMs &&\n (currentTimeMs < startMs || currentTimeMs >= endMs)\n ) {\n continue; // skip entire subtree\n }\n }\n\n if (child instanceof LitElement) {\n result.push(child);\n }\n walk(child);\n }\n };\n walk(this);\n\n return result;\n }\n\n /**\n * Determines if this is a root timegroup (no parent timegroups)\n * @public\n */\n get isRootTimegroup(): boolean {\n return !this.parentTimegroup;\n }\n\n /**\n * Property-based frame task callback for React integration.\n * When set, automatically registers the callback as a frame task.\n * Setting a new value automatically cleans up the previous callback.\n * Set to null or undefined to remove the callback.\n *\n * @example\n * // React usage:\n * <Timegroup onFrame={({ ownCurrentTimeMs, percentComplete }) => {\n * // Per-frame updates\n * }} />\n *\n * @public\n */\n get onFrame(): FrameTaskCallback | null {\n return this.#onFrameCallback;\n }\n\n set onFrame(callback: FrameTaskCallback | null | undefined) {\n // Clean up previous callback if exists\n if (this.#onFrameCleanup) {\n this.#onFrameCleanup();\n this.#onFrameCleanup = null;\n }\n this.#onFrameCallback = callback ?? null;\n\n // Register new callback if provided\n if (callback) {\n this.#onFrameCleanup = this.addFrameTask(callback);\n }\n }\n\n /**\n * Register a custom frame task callback that will be executed during frame rendering.\n * The callback receives timing information and can be async or sync.\n * Multiple callbacks can be registered and will execute in parallel.\n *\n * @param callback - Function to execute on each frame\n * @returns A cleanup function that removes the callback when called\n * @public\n */\n addFrameTask(callback: FrameTaskCallback): () => void {\n if (typeof callback !== \"function\") {\n throw new Error(\"Frame task callback must be a function\");\n }\n this.#customFrameTasks.add(callback);\n return () => {\n this.#customFrameTasks.delete(callback);\n };\n }\n\n /**\n * Remove a previously registered custom frame task callback.\n *\n * @param callback - The callback function to remove\n * @public\n */\n removeFrameTask(callback: FrameTaskCallback): void {\n this.#customFrameTasks.delete(callback);\n }\n\n /** @internal */\n saveTimeToLocalStorage(time: number) {\n try {\n if (this.id && this.isConnected && !Number.isNaN(time)) {\n localStorage.setItem(this.storageKey, time.toString());\n }\n } catch (error) {\n log(\"Failed to save time to localStorage\", error);\n }\n }\n\n render() {\n return html`<slot @slotchange=${this.#handleSlotChange}></slot> `;\n }\n\n #handleSlotChange = () => {\n resetTemporalCache();\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n\n this.requestUpdate();\n this.#syncChildListeners();\n this.emitContentChange(\"structure\");\n };\n\n /** @internal */\n loadTimeFromLocalStorage(): number | undefined {\n if (this.id) {\n try {\n const storedValue = localStorage.getItem(this.storageKey);\n if (storedValue === null) {\n return undefined;\n }\n const parsedValue = Number.parseFloat(storedValue);\n // Guard against NaN and Infinity which could cause issues\n if (Number.isNaN(parsedValue) || !Number.isFinite(parsedValue)) {\n return undefined;\n }\n return parsedValue;\n } catch (error) {\n log(\"Failed to load time from localStorage\", error);\n }\n }\n return undefined;\n }\n\n connectedCallback() {\n // CRITICAL: super.connectedCallback() MUST be synchronous for Lit lifecycle to work correctly.\n // Deferring it breaks render clones because updateComplete resolves before Lit initializes.\n //\n // EFTemporal.connectedCallback() handles root detection after Lit Context propagates:\n // - Schedules updateComplete.then(didBecomeRoot check)\n // - Only true roots (no parent after context) create PlaybackController\n //\n // PlaybackController.hostConnected() owns ALL root initialization:\n // - waitForMediaDurations\n // - localStorage time restoration\n // - initial seek\n //\n // This avoids the previous race conditions where both EFTimegroup.connectedCallback\n // and PlaybackController.hostConnected tried to initialize, causing concurrent seeks.\n super.connectedCallback();\n\n // Skip re-initialization when being moved for canvas preview capture.\n // EFTemporal.connectedCallback (super) already guards its own logic;\n // we guard the EFTimegroup-specific parts here (initializer, child\n // listeners, TimegroupController, wrapWithWorkbench).\n if ((this as any).canvasPreviewActive) return;\n\n // Run initializer after element is fully connected and Lit has updated\n // This ensures the element is in a stable state before user code runs\n this.updateComplete.then(() => {\n this.#runInitializer();\n // slotchange may not fire for empty timegroups, so run initial aggregation\n this.#syncChildListeners();\n });\n\n // Defer TimegroupController creation and workbench wrapping to next frame\n // These operations involve DOM queries (closest, getBoundingClientRect) which\n // can be expensive when many elements initialize simultaneously\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (this.parentTimegroup) {\n new TimegroupController(this.parentTimegroup, this);\n }\n\n if (this.shouldWrapWithWorkbench()) {\n this.wrapWithWorkbench();\n }\n });\n });\n }\n\n /**\n * Called when this timegroup becomes a root (no parent timegroup).\n * Sets up the playback listener after PlaybackController is created.\n * @internal\n */\n didBecomeRoot() {\n super.didBecomeRoot();\n this.#setupPlaybackListener();\n }\n\n /**\n * Setup listener on playbackController to sync userTimeMs during playback.\n */\n #setupPlaybackListener(): void {\n // Already setup or no controller\n if (this.#playbackListener || !this.playbackController) return;\n\n this.#playbackListener = (event: PlaybackControllerUpdateEvent) => {\n // Update userTimeMs during playback time changes\n // Clone-timeline: captures use separate clones, so Prime-timeline updates freely\n // Canvas preview reads userTimeMs to know what to render\n if (\n event.property === \"currentTimeMs\" &&\n typeof event.value === \"number\"\n ) {\n this.#userTimeMs = event.value;\n }\n };\n\n this.playbackController.addListener(this.#playbackListener);\n }\n\n /**\n * Remove playback listener on disconnect.\n */\n #removePlaybackListener(): void {\n if (this.#playbackListener && this.playbackController) {\n this.playbackController.removeListener(this.#playbackListener);\n }\n this.#playbackListener = null;\n }\n\n #previousDurationMs = 0;\n\n protected updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n if (changedProperties.has(\"mode\") || changedProperties.has(\"overlapMs\")) {\n durationCache.delete(this);\n }\n\n if (this.#previousDurationMs !== this.durationMs) {\n this.#previousDurationMs = this.durationMs;\n // Render clones are sequenced via seekForRender — don't trigger autonomous re-renders.\n // This prevents FrameController.abort() from interrupting an in-progress seekForRender.\n if (!this.hasAttribute(\"data-no-playback-controller\")) {\n this.#runThrottledFrameTask();\n }\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n\n // Skip teardown when being moved for canvas preview capture.\n // EFTemporal.disconnectedCallback (super) already guards its own logic.\n if ((this as any).canvasPreviewActive) return;\n\n this.#resizeObserver?.disconnect();\n this.#removePlaybackListener();\n for (const child of this.#trackedChildren) {\n child.removeEventListener(\n \"readystatechange\",\n this.#childReadyStateHandler,\n );\n child.removeEventListener(\n \"contentchange\",\n this.#childContentChangeHandler,\n );\n }\n this.#trackedChildren.clear();\n this.#qualityUpgradeScheduler.dispose();\n }\n\n /**\n * Render the timegroup to an MP4 video file and trigger download.\n * Captures each frame at the specified fps, encodes using WebCodecs via\n * MediaBunny, and downloads the resulting video.\n *\n * Uses dynamic import to only load render utilities in browser context.\n *\n * @param options - Rendering options (fps, codec, bitrate, filename, etc.)\n * @returns Promise that resolves when video is downloaded\n * @public\n */\n async renderToVideo(\n options?: RenderToVideoOptions,\n ): Promise<Uint8Array | undefined> {\n // Dynamic import - only loads in browser context when actually called\n const { renderTimegroupToVideo } =\n await import(\"../preview/renderTimegroupToVideo.js\");\n return renderTimegroupToVideo(this, options);\n }\n\n /**\n * Runs the initializer function with validation for synchronous execution and time budget.\n * Only runs once per instance. Safe to call multiple times - will skip if already run.\n * @throws Error if initializer returns a Promise (async not allowed)\n * @throws Error if initializer takes more than INITIALIZER_ERROR_THRESHOLD_MS\n * @internal\n */\n #runInitializer(): void {\n // Skip if no initializer or already run\n if (!this.initializer || this.#initializerHasRun) {\n return;\n }\n\n // Mark as run before executing to prevent recursion\n this.#initializerHasRun = true;\n\n const startTime = performance.now();\n const result: unknown = this.initializer(this);\n const elapsed = performance.now() - startTime;\n\n // Check for async (Promise return) - initializers MUST be synchronous\n if (\n result !== undefined &&\n result !== null &&\n typeof (result as any).then === \"function\"\n ) {\n throw new Error(\n \"Timeline initializer must be synchronous. \" +\n \"Do not return a Promise from the initializer function.\",\n );\n }\n\n // Time budget enforcement - initializers run for EVERY instance\n if (elapsed > INITIALIZER_ERROR_THRESHOLD_MS) {\n throw new Error(\n `Timeline initializer took ${elapsed.toFixed(1)}ms, exceeding the ${INITIALIZER_ERROR_THRESHOLD_MS}ms limit. ` +\n \"Initializers must be fast - move expensive work outside the initializer.\",\n );\n }\n\n if (elapsed > INITIALIZER_WARN_THRESHOLD_MS) {\n console.warn(\n `[ef-timegroup] Initializer took ${elapsed.toFixed(1)}ms, exceeding ${INITIALIZER_WARN_THRESHOLD_MS}ms. ` +\n \"Consider optimizing for better render performance.\",\n );\n }\n }\n\n /**\n * Copy captionsData property from original to clone.\n * cloneNode() only copies attributes, not JavaScript properties.\n * captionsData is often set via JS (e.g., captionsEl.captionsData = {...}),\n * so we must manually copy it to the cloned elements.\n * @internal\n */\n #copyCaptionsData(original: Element, clone: Element): void {\n // Find matching caption elements by position (querySelectorAll returns in document order)\n const originalCaptions = original.querySelectorAll(\"ef-captions\");\n const cloneCaptions = clone.querySelectorAll(\"ef-captions\");\n\n for (\n let i = 0;\n i < originalCaptions.length && i < cloneCaptions.length;\n i++\n ) {\n const origCap = originalCaptions[i] as any;\n const cloneCap = cloneCaptions[i] as any;\n\n // Copy loaded captions data from any source (JS property, captions-src, script element).\n // The loaded data is stored in unifiedCaptionsDataTask.value after async loading.\n // Setting captionsData on the clone gives it Priority 1, bypassing async loading.\n const loadedData =\n origCap.captionsData ?? origCap.unifiedCaptionsDataTask?.value;\n if (loadedData) {\n cloneCap.captionsData = loadedData;\n }\n }\n }\n\n /**\n * Copy ef-text _textContent property from original to cloned elements.\n * This MUST be called BEFORE elements upgrade (before updateComplete)\n * because splitText() runs in connectedCallback and will clear segments\n * if _textContent is null/empty.\n * @internal\n */\n #copyTextContent(original: Element, clone: Element): void {\n const originalTexts = original.querySelectorAll(\"ef-text\");\n const cloneTexts = clone.querySelectorAll(\"ef-text\");\n\n for (let i = 0; i < originalTexts.length && i < cloneTexts.length; i++) {\n const origText = originalTexts[i] as any;\n const cloneText = cloneTexts[i] as any;\n\n // Copy _textContent if it exists\n // This is a private property, so we access it via any\n if (origText._textContent !== undefined) {\n cloneText._textContent = origText._textContent;\n }\n // Also copy the segments getter to ensure we can read them\n if (origText._templateElement !== undefined) {\n cloneText._templateElement = origText._templateElement;\n }\n }\n }\n\n /**\n * Copy ef-text-segment properties from original to cloned elements.\n * segmentText and other properties are set via JS, not attributes,\n * so we must manually copy them to the cloned elements.\n * @internal\n */\n async #copyTextSegmentData(original: Element, clone: Element): Promise<void> {\n // Find matching text segment elements by position\n const originalSegments = original.querySelectorAll(\"ef-text-segment\");\n const cloneSegments = clone.querySelectorAll(\"ef-text-segment\");\n\n const updatePromises: Promise<any>[] = [];\n\n for (\n let i = 0;\n i < originalSegments.length && i < cloneSegments.length;\n i++\n ) {\n const origSeg = originalSegments[i] as any;\n const cloneSeg = cloneSegments[i] as any;\n\n // Copy all segment properties\n if (origSeg.segmentText !== undefined) {\n cloneSeg.segmentText = origSeg.segmentText;\n }\n if (origSeg.segmentIndex !== undefined) {\n cloneSeg.segmentIndex = origSeg.segmentIndex;\n }\n if (origSeg.staggerOffsetMs !== undefined) {\n cloneSeg.staggerOffsetMs = origSeg.staggerOffsetMs;\n }\n if (origSeg.segmentStartMs !== undefined) {\n cloneSeg.segmentStartMs = origSeg.segmentStartMs;\n }\n if (origSeg.segmentEndMs !== undefined) {\n cloneSeg.segmentEndMs = origSeg.segmentEndMs;\n }\n\n // Wait for Lit to render the updated segmentText to shadow DOM\n if (cloneSeg.updateComplete) {\n updatePromises.push(cloneSeg.updateComplete);\n }\n }\n\n // Wait for all segment updates to complete\n await Promise.all(updatePromises);\n }\n\n /**\n * Wait for all ef-captions elements to have their data loaded.\n * This is needed because EFCaptions is not an EFMedia, so waitForMediaDurations doesn't cover it.\n * Used by createRenderClone to ensure captions are ready before rendering.\n * @internal\n */\n async #waitForCaptionsData(root: Element): Promise<void> {\n // Find all ef-captions elements (including nested in timegroups)\n const captionsElements = root.querySelectorAll(\"ef-captions\");\n if (captionsElements.length === 0) return;\n\n // Wait for each caption element's data to load\n // Use duck-typing to check for loadCaptionsData method\n const waitPromises: Promise<unknown>[] = [];\n for (const el of captionsElements) {\n const captions = el as any;\n // Try new async method first\n if (typeof captions.loadCaptionsData === \"function\") {\n waitPromises.push(captions.loadCaptionsData().catch(() => {}));\n }\n // Fallback to task if present\n else if (captions.unifiedCaptionsDataTask?.taskComplete) {\n waitPromises.push(\n captions.unifiedCaptionsDataTask.taskComplete.catch(() => {}),\n );\n }\n }\n\n if (waitPromises.length > 0) {\n await Promise.all(waitPromises);\n }\n }\n\n /**\n * Copies initializers from original timegroup tree to cloned timegroup tree.\n * Handles both the root timegroup and all nested timegroups recursively.\n * @internal\n */\n async #copyInitializersToClone(\n original: EFTimegroup,\n clone: EFTimegroup,\n ): Promise<void> {\n // Copy and execute initializer at this level\n if (original.initializer) {\n clone.initializer = original.initializer;\n // Explicitly run the initializer on the clone\n // Wait for Lit update cycle to complete first so the element is stable\n await clone.updateComplete;\n clone.#runInitializer();\n }\n\n // Find all nested timegroups in both original and clone\n const originalNested = Array.from(\n original.querySelectorAll(\"ef-timegroup\"),\n ) as EFTimegroup[];\n const cloneNested = Array.from(\n clone.querySelectorAll(\"ef-timegroup\"),\n ) as EFTimegroup[];\n\n // Match up nested timegroups by index (they should correspond 1:1)\n for (let i = 0; i < originalNested.length && i < cloneNested.length; i++) {\n const origNested = originalNested[i];\n const cloneNestedItem = cloneNested[i];\n\n if (origNested!.initializer) {\n cloneNestedItem!.initializer = origNested!.initializer;\n await cloneNestedItem!.updateComplete;\n cloneNestedItem!.#runInitializer();\n }\n }\n }\n\n /**\n * Create an independent clone of this timegroup for rendering.\n * The clone is a fully functional ef-timegroup with its own animations\n * and time state, isolated from the original (Prime-timeline).\n *\n * OPTIONAL: An initializer can be set via `timegroup.initializer = (tg) => { ... }`\n * to re-run JavaScript setup (frame callbacks, React components) on each clone.\n *\n * This enables:\n * - Rendering without affecting user's preview position\n * - Concurrent renders with different clones\n * - Re-running JavaScript setup on each clone (if initializer is provided)\n *\n * @returns Promise resolving to clone, container, and cleanup function\n * @throws Error if initializer is async or takes too long\n * @public\n */\n async createRenderClone(): Promise<RenderCloneResult> {\n const factory = getCloneFactory(this);\n\n if (factory) {\n return this.#createRenderCloneFromFactory(factory);\n }\n return this.#createRenderCloneFromDOM();\n }\n\n /**\n * Wait for all LitElement descendants to update and for text segments to be ready.\n * This ensures the clone is fully initialized before rendering.\n * @internal\n */\n async #waitForDescendants(actualClone: EFTimegroup): Promise<void> {\n // Wait for all LitElement descendants\n const allLitElements = Array.from(actualClone.querySelectorAll(\"*\")).filter(\n (el) => el instanceof LitElement,\n ) as LitElement[];\n await Promise.all(allLitElements.map((el) => el.updateComplete));\n\n // Wait for text segments\n const textElements = allLitElements.filter(\n (el) => el.tagName === \"EF-TEXT\",\n );\n if (textElements.length > 0) {\n await Promise.all(\n textElements.map((el) => {\n if (\n \"whenSegmentsReady\" in el &&\n typeof el.whenSegmentsReady === \"function\"\n ) {\n return (el as any).whenSegmentsReady();\n }\n return Promise.resolve();\n }),\n );\n void actualClone.offsetHeight;\n await new Promise((resolve) => requestAnimationFrame(resolve));\n }\n }\n\n /**\n * Factory path: mount a fresh component tree (React, etc.) to produce\n * a fully functional clone. The factory is responsible for rendering\n * the component into the container and returning the root ef-timegroup.\n */\n async #createRenderCloneFromFactory(\n factory: NonNullable<ReturnType<typeof getCloneFactory>>,\n ): Promise<RenderCloneResult> {\n const width = this.offsetWidth || 1920;\n const height = this.offsetHeight || 1080;\n\n const container = document.createElement(\"div\");\n container.className = \"ef-render-clone-container\";\n container.style.cssText = `\n position: fixed;\n left: -9999px;\n top: 0;\n width: ${width}px;\n height: ${height}px;\n pointer-events: none;\n overflow: hidden;\n `;\n\n // Preserve ef-configuration context\n let renderTarget: HTMLElement = container;\n const originalConfig = this.closest(\"ef-configuration\");\n if (originalConfig) {\n const configClone = originalConfig.cloneNode(false) as HTMLElement;\n container.appendChild(configClone);\n renderTarget = configClone;\n }\n\n document.body.appendChild(container);\n\n // Mount the component tree — this produces a live ef-timegroup\n const { timegroup: actualClone, cleanup: factoryCleanup } =\n factory(renderTarget);\n\n if (!actualClone) {\n throw new Error(\n \"Clone factory did not produce an ef-timegroup. \" +\n \"Ensure the factory renders a component containing a Timegroup.\",\n );\n }\n\n // Mark as render clone\n actualClone.setAttribute(\"data-no-workbench\", \"true\");\n actualClone.setAttribute(\"data-no-playback-controller\", \"true\");\n actualClone.style.width = `${width}px`;\n actualClone.style.height = `${height}px`;\n actualClone.style.display = \"block\";\n\n // Wait for custom elements to upgrade and Lit to update\n await customElements.whenDefined(\"ef-timegroup\");\n customElements.upgrade(container);\n await actualClone.updateComplete;\n\n // Wait for all descendants to be ready\n await this.#waitForDescendants(actualClone);\n\n // Finalize clone: parent-child relationships, lock root, remove PlaybackController\n await this.#finalizeRenderClone(actualClone);\n\n return {\n clone: actualClone,\n container,\n cleanup: () => {\n container.remove();\n factoryCleanup();\n },\n };\n }\n\n /**\n * DOM path: deep clone the DOM tree and copy JavaScript properties.\n * Used for vanilla HTML/JS timelines that don't have a factory registered.\n */\n async #createRenderCloneFromDOM(): Promise<RenderCloneResult> {\n // 1. Create offscreen container\n const container = document.createElement(\"div\");\n container.className = \"ef-render-clone-container\";\n container.style.cssText = `\n position: fixed;\n left: -9999px;\n top: 0;\n width: ${this.offsetWidth || 1920}px;\n height: ${this.offsetHeight || 1080}px;\n pointer-events: none;\n overflow: hidden;\n `;\n\n // 2. Deep clone the DOM\n const cloneEl = this.cloneNode(true) as EFTimegroup;\n // Strip all id attributes from clone tree to prevent duplicate IDs in the document\n cloneEl.removeAttribute(\"id\");\n for (const el of cloneEl.querySelectorAll(\"[id]\")) {\n el.removeAttribute(\"id\");\n }\n cloneEl.setAttribute(\"data-no-workbench\", \"true\");\n cloneEl.setAttribute(\"data-no-playback-controller\", \"true\");\n\n const width = this.offsetWidth || 1920;\n const height = this.offsetHeight || 1080;\n cloneEl.style.width = `${width}px`;\n cloneEl.style.height = `${height}px`;\n cloneEl.style.display = \"block\";\n\n // 2b. Copy JavaScript properties that aren't cloned by cloneNode()\n this.#copyCaptionsData(this, cloneEl);\n this.#copyTextContent(this, cloneEl);\n\n // 3. Preserve ef-configuration context\n const originalConfig = this.closest(\"ef-configuration\");\n if (originalConfig) {\n const configClone = originalConfig.cloneNode(false) as HTMLElement;\n configClone.appendChild(cloneEl);\n container.appendChild(configClone);\n } else {\n container.appendChild(cloneEl);\n }\n\n document.body.appendChild(container);\n\n // Wait for custom elements to upgrade\n await cloneEl.updateComplete;\n\n // Copy initializers and run them on clones\n await this.#copyInitializersToClone(this, cloneEl);\n\n // Copy text segment data\n await this.#copyTextSegmentData(this, cloneEl);\n\n // Find the actual timegroup (initializer may have replaced the DOM)\n let actualClone = container.querySelector(\"ef-timegroup\") as EFTimegroup;\n if (!actualClone) {\n throw new Error(\n \"No ef-timegroup found after initializer. \" +\n \"Ensure your initializer renders a Timegroup (React) or does not remove the cloned element (vanilla JS).\",\n );\n }\n\n // Wait for custom elements to upgrade\n await customElements.whenDefined(\"ef-timegroup\");\n customElements.upgrade(container);\n actualClone = container.querySelector(\"ef-timegroup\") as EFTimegroup;\n if (!actualClone) {\n throw new Error(\"ef-timegroup element lost after upgrade\");\n }\n\n // Wait for LitElement updates\n await actualClone.updateComplete;\n\n // Wait for all descendants to be ready\n await this.#waitForDescendants(actualClone);\n\n // Copy text segment data again after initializer may have replaced DOM\n await this.#copyTextSegmentData(this, actualClone);\n\n // Finalize clone\n await this.#finalizeRenderClone(actualClone);\n\n return {\n clone: actualClone,\n container,\n cleanup: () => {\n container.remove();\n const reactRoot = (actualClone as any)._reactRoot;\n if (reactRoot) {\n queueMicrotask(() => {\n reactRoot.unmount();\n });\n }\n },\n };\n }\n\n /**\n * Shared finalization for both factory and DOM clone paths:\n * - Set up parent-child temporal relationships\n * - Lock root timegroup references\n * - Wait for media durations and captions\n * - Remove PlaybackController\n * - Initial seek to frame 0\n */\n async #finalizeRenderClone(actualClone: EFTimegroup): Promise<void> {\n // Set up parent-child relationships for temporal elements\n const setupParentChildRelationships = (\n parent: EFTimegroup,\n root: EFTimegroup,\n ) => {\n for (const child of parent.children) {\n if (child.tagName === \"EF-TIMEGROUP\") {\n const childTG = child as EFTimegroup;\n childTG.parentTimegroup = parent;\n childTG.rootTimegroup = root;\n (childTG as any).lockRootTimegroup();\n setupParentChildRelationships(childTG, root);\n } else if (\"parentTimegroup\" in child && \"rootTimegroup\" in child) {\n const temporal = child as TemporalMixinInterface & HTMLElement;\n temporal.parentTimegroup = parent;\n temporal.rootTimegroup = root;\n if (\n \"lockRootTimegroup\" in temporal &&\n typeof temporal.lockRootTimegroup === \"function\"\n ) {\n temporal.lockRootTimegroup();\n }\n } else if (child instanceof Element) {\n setupInContainer(child, parent, root);\n }\n }\n };\n\n const setupInContainer = (\n container: Element,\n nearestParentTG: EFTimegroup,\n root: EFTimegroup,\n ) => {\n for (const child of container.children) {\n if (child.tagName === \"EF-TIMEGROUP\") {\n const childTG = child as EFTimegroup;\n childTG.parentTimegroup = nearestParentTG;\n childTG.rootTimegroup = root;\n (childTG as any).lockRootTimegroup();\n setupParentChildRelationships(childTG, root);\n } else if (\"parentTimegroup\" in child && \"rootTimegroup\" in child) {\n const temporal = child as TemporalMixinInterface & HTMLElement;\n temporal.parentTimegroup = nearestParentTG;\n temporal.rootTimegroup = root;\n if (\n \"lockRootTimegroup\" in temporal &&\n typeof temporal.lockRootTimegroup === \"function\"\n ) {\n temporal.lockRootTimegroup();\n }\n } else if (child instanceof Element) {\n setupInContainer(child, nearestParentTG, root);\n }\n }\n };\n\n actualClone.rootTimegroup = actualClone;\n setupParentChildRelationships(actualClone, actualClone);\n\n await actualClone.updateComplete;\n\n // Lock root references to prevent Lit Context from overwriting\n actualClone.rootTimegroup = actualClone;\n (actualClone as any).lockRootTimegroup();\n const finalizeRootTimegroup = (el: Element) => {\n if (\"rootTimegroup\" in el && \"lockRootTimegroup\" in el) {\n (el as any).rootTimegroup = actualClone;\n (el as any).lockRootTimegroup();\n }\n for (const child of el.children) {\n finalizeRootTimegroup(child);\n }\n };\n finalizeRootTimegroup(actualClone);\n\n await actualClone.waitForMediaDurations();\n await this.#waitForCaptionsData(actualClone);\n\n // Remove PlaybackController — render clones use seekForRender directly\n if (actualClone.playbackController) {\n actualClone.playbackController.remove();\n actualClone.playbackController = undefined;\n }\n\n // Initial seek to frame 0\n await actualClone.seek(0);\n }\n\n /** @internal */\n get storageKey() {\n if (!this.id) {\n throw new Error(\"Timegroup must have an id to use localStorage.\");\n }\n return `ef-timegroup-${this.id}`;\n }\n\n /** @internal */\n get intrinsicDurationMs() {\n if (this.hasExplicitDuration) {\n return this.explicitDurationMs;\n }\n return undefined;\n }\n\n /** @internal */\n get hasOwnDuration() {\n return hasOwnDurationForMode(this.mode, this.hasExplicitDuration);\n }\n\n // ============================================================================\n // Purpose 1: Composition Rules Implementation\n // ============================================================================\n\n /** @public */\n get durationMs(): number {\n // Fixed mode delegates to parent class durationMs which handles trimming, source in/out, etc.\n if (this.mode === \"fixed\") {\n return super.durationMs;\n }\n\n // Evaluate duration semantics based on mode (Purpose 1)\n // childTemporals returns TemporalMixinInterface[], but we need HTMLElement intersection\n const childTemporalsAsElements = this.childTemporals as Array<\n TemporalMixinInterface & HTMLElement\n >;\n return evaluateDurationForMode(this, this.mode, childTemporalsAsElements);\n }\n\n // ============================================================================\n // Purpose 4: Frame Rendering - What Happens Each Frame\n // ============================================================================\n\n #mediaDurationsPromise: Promise<void> | undefined = undefined;\n\n /** @internal */\n async waitForMediaDurations(signal?: AbortSignal) {\n // Check abort before starting\n signal?.throwIfAborted();\n\n // Start loading media durations in background, but don't block if already in progress\n // This prevents multiple concurrent calls from creating redundant work\n if (!this.#mediaDurationsPromise) {\n this.#mediaDurationsPromise = this.#waitForMediaDurations(signal).catch(\n (err) => {\n // Re-throw AbortError to propagate cancellation\n if (err instanceof DOMException && err.name === \"AbortError\") {\n this.#mediaDurationsPromise = undefined;\n throw err;\n }\n console.error(\n `[EFTimegroup] waitForMediaDurations failed for ${this.id || \"unnamed\"}:`,\n err,\n );\n // Clear promise on error so it can be retried\n this.#mediaDurationsPromise = undefined;\n throw err;\n },\n );\n }\n\n // If signal is provided and aborted, throw immediately\n if (signal?.aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n\n return this.#mediaDurationsPromise;\n }\n\n /**\n * Wait for all media elements to load their initial segments.\n * Ideally we would only need the extracted index json data, but\n * that caused issues with constructing audio data. We had negative durations\n * in calculations and it was not clear why.\n */\n async #waitForMediaDurations(signal?: AbortSignal) {\n return withSpan(\n \"timegroup.waitForMediaDurations\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n // Check abort before starting\n signal?.throwIfAborted();\n\n // Don't wait for updateComplete during initialization - it causes deadlocks with nested timegroups\n // Instead, use a short delay to let elements connect, then scan for media elements\n // If elements aren't ready yet, we'll retry or they'll be picked up on the next update cycle\n await new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n\n const abortHandler = () => {\n clearTimeout(timeoutId);\n cancelAnimationFrame(rafId2);\n cancelAnimationFrame(rafId1);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", abortHandler, { once: true });\n\n let rafId1: number;\n let rafId2: number;\n let timeoutId: ReturnType<typeof setTimeout>;\n\n // Use multiple animation frames to ensure DOM is ready, but don't wait for all children\n rafId1 = requestAnimationFrame(() => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n rafId2 = requestAnimationFrame(() => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n // Small additional delay to let custom elements upgrade\n timeoutId = setTimeout(() => {\n signal?.removeEventListener(\"abort\", abortHandler);\n resolve();\n }, 10);\n });\n });\n });\n\n // Check abort after delay\n signal?.throwIfAborted();\n\n const mediaElements = deepGetMediaElements(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"mediaElementsCount\", mediaElements.length);\n }\n\n // Check abort after getting elements\n signal?.throwIfAborted();\n\n // Then, we must await the fragmentIndexTask to ensure all media elements have their\n // fragment index loaded, which is where their duration is parsed from.\n // Use Promise.allSettled with timeout to avoid blocking if asset server is slow\n const mediaLoadStart = Date.now();\n const MEDIA_LOAD_TIMEOUT_MS = 30000; // 30 second timeout per element\n\n const loadPromises = mediaElements.map(async (m, index) => {\n // Check abort before each element\n signal?.throwIfAborted();\n\n const elementStart = Date.now();\n try {\n // Use getMediaEngine async method if available\n if (typeof m.getMediaEngine === \"function\") {\n // Add timeout to prevent indefinite blocking\n const timeoutPromise = new Promise<undefined>((_, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(\n () =>\n reject(\n new Error(\n `Media element ${index} load timeout after ${MEDIA_LOAD_TIMEOUT_MS}ms`,\n ),\n ),\n MEDIA_LOAD_TIMEOUT_MS,\n );\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n },\n { once: true },\n );\n });\n\n await Promise.race([m.getMediaEngine(signal), timeoutPromise]);\n }\n // Fallback: check status and use taskComplete\n else if (m.mediaEngineTask) {\n // Status: INITIAL=0, PENDING=1, COMPLETE=2, ERROR=3\n const status = m.mediaEngineTask.status;\n\n // Already complete or errored - no need to wait\n if (status === 2 || status === 3) {\n return;\n }\n\n // Attach .catch() to taskComplete to prevent unhandled rejection\n const taskPromise = m.mediaEngineTask.taskComplete;\n taskPromise?.catch(() => {});\n\n if (taskPromise) {\n const timeoutPromise = new Promise<undefined>((_, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(\n () =>\n reject(\n new Error(\n `Media element ${index} load timeout after ${MEDIA_LOAD_TIMEOUT_MS}ms`,\n ),\n ),\n MEDIA_LOAD_TIMEOUT_MS,\n );\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n },\n { once: true },\n );\n });\n\n await Promise.race([taskPromise, timeoutPromise]);\n }\n }\n } catch (error) {\n // Re-throw AbortError to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Log only if tracing is enabled to reduce console noise\n if (isTracingEnabled()) {\n const elementElapsed = Date.now() - elementStart;\n console.error(\n `[EFTimegroup] Media element ${index} failed after ${elementElapsed}ms:`,\n error,\n );\n }\n // Don't throw - continue with other elements\n }\n });\n\n const results = await Promise.allSettled(loadPromises);\n\n // Check if any were aborted\n const aborted = results.some(\n (r) =>\n r.status === \"rejected\" &&\n r.reason instanceof DOMException &&\n r.reason.name === \"AbortError\",\n );\n if (aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n\n // Log any failures but don't throw - we want to continue even if some media fails\n const failures = results.filter((r) => r.status === \"rejected\");\n if (failures.length > 0 && isTracingEnabled()) {\n const mediaLoadElapsed = Date.now() - mediaLoadStart;\n console.warn(\n `[EFTimegroup] ${failures.length} media elements failed to load in ${mediaLoadElapsed}ms:`,\n failures.map((r) => (r.status === \"rejected\" ? r.reason : null)),\n );\n }\n\n // After waiting for durations, we must force some updates to cascade and ensure all temporal elements\n // have correct durations and start times. It is not ideal that we have to do this inside here,\n // but it is the best current way to ensure that all temporal elements have correct durations and start times.\n\n // Next, we must flush the startTimeMs cache to ensure all media elements have their\n // startTimeMs parsed fresh, otherwise the startTimeMs is cached per animation frame.\n flushStartTimeMsCache();\n\n // Flush duration cache since child durations may have changed\n flushSequenceDurationCache();\n\n // Request an update to the currentTime of this group, ensuring that time updates will cascade\n // down to children, forcing sequence groups to arrange correctly.\n // This also makes the filmstrip update correctly.\n // Defer using setTimeout(0) to avoid Lit warning about scheduling updates after update completed.\n // This method can be called during a task or after an update cycle completes, and using\n // setTimeout ensures we're completely outside any Lit update cycle.\n setTimeout(() => this.requestUpdate(\"currentTime\"), 0);\n // Note: We don't await updateComplete here during initialization to avoid deadlocks.\n // The update will complete asynchronously, and sequence groups will arrange correctly\n // once all timegroups have finished initializing. During normal operation (seeks, etc.),\n // the caller will wait for updateComplete explicitly if needed.\n },\n );\n }\n\n /** @internal */\n get childTemporals() {\n return shallowGetTemporalElements(this);\n }\n\n /**\n * Returns true if the timegroup should be wrapped with a workbench.\n *\n * A timegroup should be wrapped with a workbench if:\n * - It's being rendered (EF_RENDERING), OR\n * - The workbench property is set to true\n *\n * If the timegroup is already wrapped in a context provider like ef-preview,\n * it should NOT be wrapped in a workbench.\n * @internal\n */\n shouldWrapWithWorkbench() {\n // Never wrap when being captured by canvas preview — the element is\n // temporarily reparented for native rendering and must not spawn a\n // new workbench (which would read \"canvas\" from localStorage and\n // re-enter initCanvasMode, creating an infinite loop).\n if ((this as any).canvasPreviewActive) {\n return false;\n }\n\n // Only root timegroups should wrap with workbench\n if (!this.isRootTimegroup) {\n return false;\n }\n\n // Never wrap with workbench when inside a canvas\n // Canvas manages its own layout and coordinate system\n if (this.closest(\"ef-canvas\") !== null) {\n return false;\n }\n\n // Never wrap if already inside preview, workbench, or preview context\n if (\n this.closest(\"ef-preview\") !== null ||\n this.closest(\"ef-workbench\") !== null ||\n this.closest(\"ef-preview-context\") !== null\n ) {\n return false;\n }\n\n // Skip wrapping in test contexts or if explicitly disabled\n // Test contexts and render clones provide their own rendering infrastructure\n if (\n this.closest(\"test-context\") !== null ||\n this.hasAttribute(\"data-no-workbench\")\n ) {\n return false;\n }\n\n // During rendering, never wrap with workbench - timegroups can seek without it\n const isRendering = EF_RENDERING?.() === true;\n if (isRendering) {\n return false;\n }\n\n // Check URL param to disable workbench (only applies in non-rendering mode)\n if (typeof window !== \"undefined\") {\n const params = new URLSearchParams(window.location.search);\n if (\n params.get(\"noWorkbench\") === \"true\" ||\n params.get(\"no-workbench\") === \"true\"\n ) {\n return false;\n }\n }\n\n // Respect the explicit workbench property\n return this.workbench;\n }\n\n /** @internal */\n wrapWithWorkbench() {\n const workbench = document.createElement(\"ef-workbench\") as any;\n const parent = this.parentElement;\n\n // When in rendering mode, immediately set rendering=true before insertion\n // This prevents the workbench UI from ever being visible in rendered frames\n if (EF_RENDERING()) {\n // Use setAttribute to ensure it's set before the element connects and renders\n workbench.setAttribute(\"rendering\", \"\");\n workbench.rendering = true;\n }\n\n // Apply explicit sizing to ensure workbench fills its container\n if (parent === document.body) {\n // Direct child of body: use viewport units with fixed positioning\n workbench.style.position = \"fixed\";\n workbench.style.top = \"0\";\n workbench.style.left = \"0\";\n workbench.style.width = \"100vw\";\n workbench.style.height = \"100vh\";\n workbench.style.zIndex = \"0\";\n } else {\n // Embedded in container: ensure it fills the container\n // Use absolute positioning to prevent content-based sizing\n workbench.style.position = \"absolute\";\n workbench.style.top = \"0\";\n workbench.style.left = \"0\";\n workbench.style.width = \"100%\";\n workbench.style.height = \"100%\";\n }\n\n parent?.append(workbench);\n if (!this.hasAttribute(\"id\")) {\n this.setAttribute(\"id\", \"root-timegroup\");\n }\n\n // Create pan-zoom for selection overlay support\n // Must be in light DOM so canvas can find it via closest()\n const panZoom = document.createElement(\"ef-pan-zoom\");\n panZoom.id = \"workbench-panzoom\";\n panZoom.setAttribute(\"slot\", \"canvas\");\n panZoom.setAttribute(\"auto-fit\", \"\"); // Fit content to view on first render\n panZoom.style.width = \"100%\";\n panZoom.style.height = \"100%\";\n\n // Create canvas wrapper for selection/highlighting support\n // Get dimensions from the timegroup for explicit canvas sizing\n const rect = this.getBoundingClientRect();\n const canvas = document.createElement(\"ef-canvas\");\n canvas.id = \"workbench-canvas\";\n canvas.style.width = `${rect.width}px`;\n canvas.style.height = `${rect.height}px`;\n canvas.style.display = \"block\";\n\n // Move timegroup into canvas, canvas into pan-zoom\n canvas.append(this as unknown as Element);\n panZoom.append(canvas);\n workbench.append(panZoom);\n\n // Add hierarchy panel - targets canvas for selection support\n const hierarchy = document.createElement(\"ef-hierarchy\");\n hierarchy.setAttribute(\"slot\", \"hierarchy\");\n hierarchy.setAttribute(\"target\", \"workbench-canvas\");\n hierarchy.setAttribute(\"header\", \"Scenes\");\n workbench.append(hierarchy);\n\n // Add filmstrip/timeline - targets timegroup for playback\n const filmstrip = document.createElement(\"ef-filmstrip\");\n filmstrip.setAttribute(\"slot\", \"timeline\");\n filmstrip.setAttribute(\"target\", this.id);\n workbench.append(filmstrip);\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n * @internal\n */\n getMediaElements(): EFMedia[] {\n return deepGetMediaElements(this);\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n * @internal\n */\n async renderAudio(\n fromMs: number,\n toMs: number,\n signal?: AbortSignal,\n ): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs, signal);\n }\n\n async #executeCustomFrameTasks() {\n if (this.#customFrameTasks.size > 0) {\n const percentComplete =\n this.durationMs > 0 ? this.ownCurrentTimeMs / this.durationMs : 0;\n const frameInfo = {\n ownCurrentTimeMs: this.ownCurrentTimeMs,\n currentTimeMs: this.currentTimeMs,\n durationMs: this.durationMs,\n percentComplete,\n element: this,\n };\n\n await Promise.all(\n Array.from(this.#customFrameTasks).map((callback) =>\n Promise.resolve(callback(frameInfo)),\n ),\n );\n }\n }\n\n /** @internal */\n #seekTaskPromise: Promise<number | undefined> = Promise.resolve(undefined);\n #seekTaskAbortController: AbortController | null = null;\n\n seekTask = (() => {\n const self = this;\n const taskObj: {\n run(): void | Promise<number | undefined>;\n taskComplete: Promise<number | undefined>;\n } = {\n run: () => {\n // Abort any in-flight task\n self.#seekTaskAbortController?.abort();\n self.#seekTaskAbortController = new AbortController();\n const signal = self.#seekTaskAbortController.signal;\n\n const targetTime = self.#pendingSeekTime ?? self.#currentTime;\n self.#seekTaskPromise = self.#runSeekTask(targetTime, signal);\n taskObj.taskComplete = self.#seekTaskPromise;\n return self.#seekTaskPromise;\n },\n taskComplete: Promise.resolve(undefined),\n };\n return taskObj;\n })();\n\n async #runSeekTask(\n targetTime: number | undefined,\n signal: AbortSignal,\n ): Promise<number | undefined> {\n try {\n signal.throwIfAborted();\n\n // Delegate to playbackController if available\n if (this.playbackController) {\n // Wait for playbackController's seek to complete\n await this.playbackController.currentTime; // Trigger seek\n signal.throwIfAborted();\n return this.currentTime;\n }\n\n // Only root timegroups execute seek tasks\n if (!this.isRootTimegroup) {\n return undefined;\n }\n\n return await withSpan(\n \"timegroup.seekTask\",\n {\n timegroupId: this.id || \"unknown\",\n targetTime: targetTime ?? 0,\n durationMs: this.durationMs,\n },\n undefined,\n async (span) => {\n // Wait for media durations to be loaded\n try {\n await Promise.race([\n this.waitForMediaDurations(signal),\n new Promise<void>((_, reject) => {\n if (signal.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(\n () => reject(new Error(\"waitForMediaDurations timeout\")),\n 10000,\n );\n signal.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n },\n { once: true },\n );\n }),\n ]);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Continue with seek even if durations aren't loaded yet\n }\n\n signal.throwIfAborted();\n\n // Evaluate and apply seek target\n const newTime = evaluateSeekTarget(\n targetTime ?? 0,\n this.durationMs,\n this.effectiveFps,\n );\n if (isTracingEnabled()) {\n span.setAttribute(\"newTime\", newTime);\n }\n\n this.#currentTime = newTime;\n this.requestUpdate(\"currentTime\");\n\n await this.updateComplete;\n signal.throwIfAborted();\n\n await this.#runThrottledFrameTask();\n signal.throwIfAborted();\n\n if (!this.#restoringFromLocalStorage) {\n this.saveTimeToLocalStorage(this.#currentTime);\n }\n this.#seekInProgress = false;\n if (this.#restoringFromLocalStorage) {\n this.#restoringFromLocalStorage = false;\n }\n return newTime;\n },\n );\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n return undefined;\n }\n console.error(\"EFTimegroup seekTask error\", error);\n return undefined;\n }\n }\n\n /**\n * Get container information for this timegroup.\n * Timegroups are always containers and can contain children.\n * Display mode is determined from computed styles.\n *\n * @public\n */\n getContainerInfo(): ContainerInfo {\n const info = getContainerInfoFromElement(this);\n // Timegroups are always containers and can contain children\n return {\n ...info,\n isContainer: true,\n canContainChildren: true,\n };\n }\n\n /**\n * Get position information for this timegroup.\n * Returns computed bounds, transform, and rotation.\n *\n * @public\n */\n getPositionInfo(): ElementPositionInfo | null {\n return getPositionInfoFromElement(this);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timegroup\": EFTimegroup & Element;\n }\n}\n"],"mappings":"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"}