@editframe/elements 0.35.0-beta → 0.36.1-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/canvas/EFCanvas.d.ts +4 -4
  2. package/dist/elements/EFAudio.d.ts +4 -4
  3. package/dist/elements/EFCaptions.d.ts +0 -4
  4. package/dist/elements/EFCaptions.js +12 -32
  5. package/dist/elements/EFCaptions.js.map +1 -1
  6. package/dist/elements/EFImage.js +11 -2
  7. package/dist/elements/EFImage.js.map +1 -1
  8. package/dist/elements/EFPanZoom.d.ts +4 -4
  9. package/dist/elements/EFSurface.d.ts +4 -4
  10. package/dist/elements/EFTemporal.js +1 -0
  11. package/dist/elements/EFTemporal.js.map +1 -1
  12. package/dist/elements/EFText.d.ts +4 -4
  13. package/dist/elements/EFTextSegment.d.ts +4 -4
  14. package/dist/elements/EFThumbnailStrip.d.ts +4 -4
  15. package/dist/elements/EFTimegroup.d.ts +40 -6
  16. package/dist/elements/EFTimegroup.js +127 -8
  17. package/dist/elements/EFTimegroup.js.map +1 -1
  18. package/dist/elements/EFVideo.d.ts +6 -6
  19. package/dist/elements/EFWaveform.d.ts +4 -4
  20. package/dist/elements/updateAnimations.js +113 -15
  21. package/dist/elements/updateAnimations.js.map +1 -1
  22. package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
  23. package/dist/gui/EFConfiguration.d.ts +4 -4
  24. package/dist/gui/EFControls.d.ts +2 -2
  25. package/dist/gui/EFDial.d.ts +4 -4
  26. package/dist/gui/EFFilmstrip.d.ts +2 -2
  27. package/dist/gui/EFFitScale.d.ts +3 -3
  28. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  29. package/dist/gui/EFPause.d.ts +4 -4
  30. package/dist/gui/EFPlay.d.ts +4 -4
  31. package/dist/gui/EFPreview.d.ts +4 -4
  32. package/dist/gui/EFResizableBox.d.ts +4 -4
  33. package/dist/gui/EFScrubber.d.ts +4 -4
  34. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  35. package/dist/gui/EFToggleLoop.d.ts +4 -4
  36. package/dist/gui/EFTogglePlay.d.ts +4 -4
  37. package/dist/gui/EFTransformHandles.d.ts +4 -4
  38. package/dist/gui/EFWorkbench.d.ts +6 -6
  39. package/dist/gui/EFWorkbench.js +38 -12
  40. package/dist/gui/EFWorkbench.js.map +1 -1
  41. package/dist/gui/TWMixin.js +1 -1
  42. package/dist/gui/TWMixin.js.map +1 -1
  43. package/dist/gui/hierarchy/EFHierarchy.d.ts +4 -4
  44. package/dist/gui/hierarchy/EFHierarchyItem.d.ts +2 -2
  45. package/dist/gui/timeline/tracks/ImageTrack.d.ts +2 -2
  46. package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +5 -5
  47. package/dist/gui/timeline/tracks/VideoTrack.d.ts +4 -4
  48. package/dist/gui/tree/EFTree.d.ts +4 -4
  49. package/dist/gui/tree/EFTreeItem.d.ts +4 -4
  50. package/dist/preview/FrameController.js +6 -1
  51. package/dist/preview/FrameController.js.map +1 -1
  52. package/dist/preview/encoding/canvasEncoder.js.map +1 -1
  53. package/dist/preview/encoding/mainThreadEncoder.js +3 -0
  54. package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
  55. package/dist/preview/renderTimegroupPreview.js +57 -55
  56. package/dist/preview/renderTimegroupPreview.js.map +1 -1
  57. package/dist/preview/renderTimegroupToCanvas.js +22 -23
  58. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  59. package/dist/preview/renderTimegroupToVideo.d.ts +2 -1
  60. package/dist/preview/renderTimegroupToVideo.js +77 -40
  61. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  62. package/dist/preview/rendering/renderToImage.d.ts +1 -0
  63. package/dist/preview/rendering/renderToImage.js +1 -26
  64. package/dist/preview/rendering/renderToImage.js.map +1 -1
  65. package/dist/preview/rendering/renderToImageForeignObject.js +34 -6
  66. package/dist/preview/rendering/renderToImageForeignObject.js.map +1 -1
  67. package/dist/preview/rendering/serializeTimelineDirect.js +379 -0
  68. package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -0
  69. package/dist/render/EFRenderAPI.js +45 -0
  70. package/dist/render/EFRenderAPI.js.map +1 -1
  71. package/dist/style.css +45 -0
  72. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"EFTemporal.js","names":["isTimegroupCalculatingDurationFn:\n | ((timegroup: EFTimegroup | undefined) => boolean)\n | null","fallbackFn: (timegroup: EFTimegroup | undefined) => boolean","assignedElements: Element[]","temporalCache: Map<Element, TemporalMixinInterface[]>","host: EFTimegroup","temporal: TemporalMixinInterface & LitElement","#lastKnownTimeMs","taskObj: { run(): void | Promise<void>; taskComplete: Promise<void> }","#frameTaskPromise","#parentTimegroup","#ownCurrentTimeController","#rootTimegroupLocked","#loop","#parentTemporal","#offsetMs","#currentTimeMs"],"sources":["../../src/elements/EFTemporal.ts"],"sourcesContent":["import { consume, createContext } from \"@lit/context\";\nimport type { LitElement, ReactiveController } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { PlaybackController } from \"../gui/PlaybackController.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport type { EFTimegroup } from \"./EFTimegroup.js\";\n// Lazy import to break circular dependency: EFTemporal -> EFTimegroup -> EFMedia -> EFTemporal\n// isTimegroupCalculatingDuration is only used at runtime in a getter, so we can import it lazily\n// Use a module-level variable that gets set when EFTimegroup module loads\nlet isTimegroupCalculatingDurationFn:\n | ((timegroup: EFTimegroup | undefined) => boolean)\n | null = null;\n\n// This function will be called by EFTimegroup when it loads to register the function\nexport const registerIsTimegroupCalculatingDuration = (\n fn: (timegroup: EFTimegroup | undefined) => boolean,\n) => {\n isTimegroupCalculatingDurationFn = fn;\n};\n\nconst getIsTimegroupCalculatingDuration = (): ((\n timegroup: EFTimegroup | undefined,\n) => boolean) => {\n if (isTimegroupCalculatingDurationFn) {\n return isTimegroupCalculatingDurationFn as (\n timegroup: EFTimegroup | undefined,\n ) => boolean;\n }\n\n // If not registered yet, try to import synchronously (only works if module is already loaded)\n // This is a fallback for cases where EFTimegroup hasn't called registerIsTimegroupCalculatingDuration\n // In practice, EFTimegroup will call registerIsTimegroupCalculatingDuration when it loads\n let fallbackFn: (timegroup: EFTimegroup | undefined) => boolean = () => false;\n try {\n // Access the function via a global or try to get it from the module cache\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const efTimegroupModule = (globalThis as any).__EFTimegroupModule;\n if (efTimegroupModule?.isTimegroupCalculatingDuration) {\n fallbackFn = efTimegroupModule.isTimegroupCalculatingDuration;\n }\n } catch {\n // Use default fallback\n }\n isTimegroupCalculatingDurationFn = fallbackFn;\n return fallbackFn;\n};\n\nexport const timegroupContext = createContext<EFTimegroup>(\n Symbol(\"timeGroupContext\"),\n);\n\n// ============================================================================\n// Core Concept 1: Temporal Role\n// ============================================================================\n// A temporal element is either a root (controls its own playback) or a child\n// (delegates playback to its root timegroup). This is the fundamental invariant.\n// ============================================================================\n\ntype TemporalRole = \"root\" | \"child\";\n\nfunction determineTemporalRole(\n parentTimegroup: EFTimegroup | undefined,\n): TemporalRole {\n return parentTimegroup === undefined ? \"root\" : \"child\";\n}\n\n// ============================================================================\n// Core Concept 2: Duration Source\n// ============================================================================\n// Duration comes from one of three sources: intrinsic (media-based),\n// explicit (attribute), or inherited (from parent). This determines the base\n// duration before any modifications.\n// ============================================================================\n\ntype DurationSource = \"intrinsic\" | \"explicit\" | \"inherited\";\n\ninterface DurationSourceResult {\n source: DurationSource;\n baseDurationMs: number;\n}\n\nfunction determineDurationSource(\n intrinsicDurationMs: number | undefined,\n explicitDurationMs: number | undefined,\n parentDurationMs: number | undefined,\n): DurationSourceResult {\n if (intrinsicDurationMs !== undefined) {\n return { source: \"intrinsic\", baseDurationMs: intrinsicDurationMs };\n }\n if (explicitDurationMs !== undefined) {\n return { source: \"explicit\", baseDurationMs: explicitDurationMs };\n }\n if (parentDurationMs !== undefined) {\n return { source: \"inherited\", baseDurationMs: parentDurationMs };\n }\n return { source: \"inherited\", baseDurationMs: 0 };\n}\n\n// ============================================================================\n// Core Concept 3: Duration Modification Strategy\n// ============================================================================\n// Duration can be modified by trimming (removing from edges) or source\n// manipulation (selecting a portion of the source). These are mutually\n// exclusive strategies.\n// ============================================================================\n\ntype DurationModificationStrategy = \"none\" | \"trimming\" | \"source-manipulation\";\n\ninterface DurationModificationState {\n strategy: DurationModificationStrategy;\n trimStartMs: number | undefined;\n trimEndMs: number | undefined;\n sourceInMs: number | undefined;\n sourceOutMs: number | undefined;\n}\n\nfunction determineDurationModificationStrategy(\n trimStartMs: number | undefined,\n trimEndMs: number | undefined,\n sourceInMs: number | undefined,\n sourceOutMs: number | undefined,\n): DurationModificationState {\n if (trimStartMs !== undefined || trimEndMs !== undefined) {\n return {\n strategy: \"trimming\",\n trimStartMs,\n trimEndMs,\n sourceInMs: undefined,\n sourceOutMs: undefined,\n };\n }\n if (sourceInMs !== undefined || sourceOutMs !== undefined) {\n return {\n strategy: \"source-manipulation\",\n trimStartMs: undefined,\n trimEndMs: undefined,\n sourceInMs,\n sourceOutMs,\n };\n }\n return {\n strategy: \"none\",\n trimStartMs: undefined,\n trimEndMs: undefined,\n sourceInMs: undefined,\n sourceOutMs: undefined,\n };\n}\n\nfunction evaluateModifiedDuration(\n baseDurationMs: number,\n modification: DurationModificationState,\n): number {\n if (baseDurationMs === 0) {\n return 0;\n }\n\n switch (modification.strategy) {\n case \"trimming\": {\n const trimmedDurationMs =\n baseDurationMs -\n (modification.trimStartMs ?? 0) -\n (modification.trimEndMs ?? 0);\n return Math.max(0, trimmedDurationMs);\n }\n case \"source-manipulation\": {\n const sourceInMs = modification.sourceInMs ?? 0;\n const sourceOutMs = modification.sourceOutMs ?? baseDurationMs;\n if (sourceInMs >= sourceOutMs) {\n return 0;\n }\n return sourceOutMs - sourceInMs;\n }\n case \"none\":\n return baseDurationMs;\n }\n}\n\n// ============================================================================\n// Core Concept 4: Start Time Calculation Strategy\n// ============================================================================\n// Start time is calculated differently based on parent timegroup mode:\n// - Sequence mode: based on previous sibling\n// - Other modes: based on parent start + offset\n// ============================================================================\n\ntype StartTimeStrategy = \"sequence\" | \"offset\";\n\nfunction determineStartTimeStrategy(\n parentTimegroup: EFTimegroup | undefined,\n): StartTimeStrategy {\n if (!parentTimegroup) {\n return \"offset\";\n }\n return parentTimegroup.mode === \"sequence\" ? \"sequence\" : \"offset\";\n}\n\nfunction evaluateStartTimeForSequence(\n _element: TemporalMixinInterface & HTMLElement,\n parentTimegroup: EFTimegroup,\n siblingTemporals: TemporalMixinInterface[],\n ownIndex: number,\n): number {\n if (ownIndex === 0) {\n return parentTimegroup.startTimeMs;\n }\n const previous = siblingTemporals[ownIndex - 1];\n if (!previous) {\n throw new Error(\"Previous temporal element not found\");\n }\n return previous.startTimeMs + previous.durationMs - parentTimegroup.overlapMs;\n}\n\nfunction evaluateStartTimeForOffset(\n parentTimegroup: EFTimegroup,\n offsetMs: number,\n): number {\n return parentTimegroup.startTimeMs + offsetMs;\n}\n\nfunction evaluateStartTime(\n element: TemporalMixinInterface & HTMLElement,\n parentTimegroup: EFTimegroup | undefined,\n offsetMs: number,\n getSiblingTemporals: (parent: EFTimegroup) => TemporalMixinInterface[],\n): number {\n if (!parentTimegroup) {\n return 0;\n }\n\n const strategy = determineStartTimeStrategy(parentTimegroup);\n switch (strategy) {\n case \"sequence\": {\n const siblingTemporals = getSiblingTemporals(parentTimegroup);\n const ownIndex = siblingTemporals.indexOf(element);\n if (ownIndex === -1) {\n return 0;\n }\n return evaluateStartTimeForSequence(\n element,\n parentTimegroup,\n siblingTemporals,\n ownIndex,\n );\n }\n case \"offset\":\n return evaluateStartTimeForOffset(parentTimegroup, offsetMs);\n }\n}\n\n// ============================================================================\n// Core Concept 5: Current Time Source\n// ============================================================================\n// Current time comes from one of three sources: playback controller (root\n// elements), root timegroup (child elements), or local storage (fallback).\n// ============================================================================\n\ntype CurrentTimeSource = \"playback-controller\" | \"root-timegroup\" | \"local\";\n\ninterface CurrentTimeSourceResult {\n source: CurrentTimeSource;\n timeMs: number;\n}\n\nfunction determineCurrentTimeSource(\n playbackController: PlaybackController | undefined,\n rootTimegroup: EFTimegroup | undefined,\n isRootTimegroup: boolean,\n localTimeMs: number,\n startTimeMs: number,\n durationMs: number,\n): CurrentTimeSourceResult {\n if (playbackController) {\n const timeMs = Math.min(\n Math.max(0, playbackController.currentTimeMs),\n durationMs,\n );\n return { source: \"playback-controller\", timeMs };\n }\n\n if (rootTimegroup && !isRootTimegroup) {\n const timeMs = Math.min(\n Math.max(0, rootTimegroup.currentTimeMs - startTimeMs),\n durationMs,\n );\n return { source: \"root-timegroup\", timeMs };\n }\n\n const timeMs = Math.min(Math.max(0, localTimeMs), durationMs);\n return { source: \"local\", timeMs };\n}\n\nexport declare class TemporalMixinInterface {\n playbackController?: PlaybackController;\n playing: boolean;\n loop: boolean;\n play(): void;\n pause(): void;\n\n get hasOwnDuration(): boolean;\n /**\n * Whether the element has a duration set as an attribute.\n */\n get hasExplicitDuration(): boolean;\n\n get sourceStartMs(): number;\n\n /**\n * Used to trim the start of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `trimstart=\"10s\"` is equivalent to `trimstart=\"10000ms\"`.\n *\n * @domAttribute \"trimstart\"\n */\n get trimStartMs(): number | undefined;\n\n /**\n * Used to trim the end of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `trimend=\"10s\"` is equivalent to `trimend=\"10000ms\"`.\n *\n * @domAttribute \"trimend\"\n */\n get trimEndMs(): number;\n\n set trimStartMs(value: number | undefined);\n set trimEndMs(value: number | undefined);\n set trimstart(value: string | undefined);\n set trimend(value: string | undefined);\n\n /**\n * The source in time of the element.\n *\n * This is an amount of time to trim off the beginning of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `sourcein=\"10s\"` is equivalent to `sourcein=\"10000ms\"`.\n *\n * If the sourcein time is greater than the duration of the media, the media\n * will not be played.\n *\n * If the media is 20 seconds long, and the `sourcein` value is set to `10s`, the\n * media will play for 10 seconds, starting at the 10 second mark.\n *\n * Can be used in conjunction with `sourceout` to create a trimmed media.\n *\n * @domAttribute \"sourcein\"\n */\n get sourceInMs(): number | undefined;\n\n /**\n * The source out time of the element.\n *\n * This is the point in time in the media that will be treated as the end of\n * the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `sourceout=\"10s\"` is equivalent to `sourceout=\"10000ms\"`.\n *\n * If the sourceout time is greater than the duration of the media, the media\n * will play until the end of the media.\n *\n * If the media is 20 seconds long, and the `sourceout` value is set to `10s`,\n * the media will play for 10 seconds, starting at zero seconds and ending at\n * the 10 second mark.\n *\n * Can be used in conjunction with `sourcein` to create a trimmed media.\n *\n * @domAttribute \"sourceout\"\n */\n get sourceOutMs(): number | undefined;\n\n set sourceInMs(value: number | undefined);\n set sourceOutMs(value: number | undefined);\n set sourcein(value: string | undefined);\n set sourceout(value: string | undefined);\n\n /**\n * @domAttribute \"duration\"\n */\n get durationMs(): number;\n\n get explicitDurationMs(): number | undefined;\n\n get intrinsicDurationMs(): number | undefined;\n\n /**\n * The start time of the element within its root timegroup in milliseconds.\n *\n * This is an absolute time according to the highest scoped timegroup the media element is contained within.\n *\n * The calculated value will depend on the mode of the timegroup and the offset of the media element.\n *\n * If the parent time group is in `sequence` mode, the start time will be the\n * start time of the previous sibling element plus the previous sibling's duration\n * minus the overlap of the previous sibling and the current sibling.\n *\n * If the parent time group is in `contain` or `fixed` mode, the start time will be\n * the start time of the parent time group plus the offset of the media element.\n */\n get startTimeMs(): number;\n /**\n * The end time of the element within its root timegroup in milliseconds.\n *\n * This is an absolute time according to the highest scoped timegroup the media\n * element is contained within. Computed by adding the media's duration to its\n * start time.\n *\n * If the media element has been trimmed, its end time will be calculated according it\n * its trimmed duration, not its original duration.\n */\n get endTimeMs(): number;\n /**\n * The start time of the element within its parent timegroup in milliseconds.\n *\n * This is a relative time according to the closest timegroup the media element\n * is contained within. Unless the media element has been given any kind of specific offset\n * it is common for this time to be zero.\n */\n get startTimeWithinParentMs(): number;\n\n /**\n * The current time of the element in milliseconds.\n *\n * This is a relative time according to the closest timegroup the media element\n * is contained within.\n *\n * This is suitable for determining the percentage of the media that has been\n * played.\n */\n get ownCurrentTimeMs(): number;\n\n /**\n * Element's current time for progress calculation.\n * For timegroups: their timeline currentTimeMs\n * For other temporal elements: their ownCurrentTimeMs\n */\n get currentTimeMs(): number;\n set currentTimeMs(value: number);\n /**\n * The current time of the element in milliseconds, adjusted for trimming.\n *\n * This is suitable for mapping to internal media time codes for audio/video\n * elements.\n *\n * For example, if the media has a `sourcein` value of 10s, when `ownCurrentTimeMs` is 0s,\n * `currentSourceTimeMs` will be 10s.\n *\n * sourcein=10s sourceout=10s\n * / / /\n * |--------|=================|---------|\n * ^\n * |_\n * currentSourceTimeMs === 10s\n * |_\n * ownCurrentTimeMs === 0s\n */\n get currentSourceTimeMs(): number;\n\n set duration(value: string);\n get duration(): string;\n\n /**\n * The offset of the element within its parent timegroup in milliseconds.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `offset=\"10s\"` is equivalent to `offset=\"10000ms\"`.\n *\n * This can be used to create a negative or positive offset for the start time of the media.\n *\n * This will change the start time of the media relative to it's otherwise normal start time.\n *\n * The duration of the element, or it's parent, or the start and end time of it's temporal siblings will not\n * be affected by this offset.\n *\n * @domAttribute \"offset\"\n */\n set offset(value: string);\n get offset(): string;\n\n /**\n * A convenience property for getting the nearest containing timegroup of the media element.\n */\n parentTimegroup?: EFTimegroup;\n\n /**\n * A convenience property for getting the root timegroup of the media element.\n */\n rootTimegroup?: EFTimegroup;\n\n /**\n * Frame task interface. Can be a Lit Task or a simple object with run() and taskComplete.\n * @deprecated Prefer using FrameRenderable interface via FrameController.\n */\n frameTask: { run(): void | Promise<void>; taskComplete: Promise<unknown> };\n\n didBecomeRoot(): void;\n didBecomeChild(): void;\n\n updateComplete: Promise<boolean>;\n}\n\nexport const isEFTemporal = (obj: any): obj is TemporalMixinInterface =>\n obj[EF_TEMPORAL];\n\nconst EF_TEMPORAL = Symbol(\"EF_TEMPORAL\");\n\nexport const deepGetTemporalElements = (\n element: Element,\n temporals: Array<TemporalMixinInterface & HTMLElement> = [],\n) => {\n // Get children to walk - handle both regular children and slotted content\n const children = getChildrenIncludingSlotted(element);\n \n for (const child of children) {\n if (isEFTemporal(child)) {\n temporals.push(child as TemporalMixinInterface & HTMLElement);\n }\n deepGetTemporalElements(child, temporals);\n }\n return temporals;\n};\n\n/**\n * Gets all child elements including slotted content for shadow DOM elements.\n * For elements with shadow DOM that contain slots, this returns the assigned\n * elements (slotted content) instead of just the shadow DOM children.\n */\nconst getChildrenIncludingSlotted = (element: Element): Element[] => {\n // If element has shadowRoot with slots, get assigned elements\n if (element.shadowRoot) {\n const slots = element.shadowRoot.querySelectorAll('slot');\n if (slots.length > 0) {\n const assignedElements: Element[] = [];\n for (const slot of slots) {\n assignedElements.push(...slot.assignedElements());\n }\n // Also include shadow DOM children that aren't slots (for mixed content)\n for (const child of element.shadowRoot.children) {\n if (child.tagName !== 'SLOT') {\n assignedElements.push(child);\n }\n }\n return assignedElements;\n }\n }\n \n // Fallback to regular children\n return Array.from(element.children);\n};\n\nexport const deepGetElementsWithFrameTasks = (\n element: Element,\n elements: Array<TemporalMixinInterface & HTMLElement> = [],\n) => {\n // Get children to walk - handle both regular children and slotted content\n const children = getChildrenIncludingSlotted(element);\n \n for (const child of children) {\n // Check for frameTask with run method (works with both Lit Task and our compatibility wrapper)\n const hasFrameTask = \"frameTask\" in child && \n child.frameTask != null && \n typeof (child.frameTask as any).run === \"function\";\n if (hasFrameTask) {\n elements.push(child as TemporalMixinInterface & HTMLElement);\n }\n deepGetElementsWithFrameTasks(child, elements);\n }\n return elements;\n};\n\nlet temporalCache: Map<Element, TemporalMixinInterface[]>;\nlet temporalCacheResetScheduled = false;\nexport const resetTemporalCache = () => {\n temporalCache = new Map();\n if (\n typeof requestAnimationFrame !== \"undefined\" &&\n !temporalCacheResetScheduled\n ) {\n temporalCacheResetScheduled = true;\n requestAnimationFrame(() => {\n temporalCacheResetScheduled = false;\n resetTemporalCache();\n });\n }\n};\nresetTemporalCache();\n\nexport const shallowGetTemporalElements = (\n element: Element,\n temporals: TemporalMixinInterface[] = [],\n) => {\n const cachedResult = temporalCache.get(element);\n if (cachedResult) {\n return cachedResult;\n }\n // Get children to walk - handle both regular children and slotted content\n const children = getChildrenIncludingSlotted(element);\n \n for (const child of children) {\n if (isEFTemporal(child)) {\n temporals.push(child);\n } else {\n shallowGetTemporalElements(child, temporals);\n }\n }\n temporalCache.set(element, temporals);\n return temporals;\n};\n\nexport class OwnCurrentTimeController implements ReactiveController {\n #lastKnownTimeMs: number | undefined = undefined;\n \n constructor(\n private host: EFTimegroup,\n private temporal: TemporalMixinInterface & LitElement,\n ) {\n host.addController(this);\n }\n\n hostUpdated() {\n // CRITICAL FIX: Only trigger child updates when root's currentTimeMs actually changes.\n // Previously, this fired on EVERY root update, causing 40+ child updates per root update.\n // With nested timegroups, this created cascading updates that locked up the main thread.\n const currentTimeMs = this.host.currentTimeMs;\n if (this.#lastKnownTimeMs === currentTimeMs) {\n return; // Time hasn't changed, no need to update children\n }\n this.#lastKnownTimeMs = currentTimeMs;\n \n // Defer update using setTimeout(0) to avoid Lit warning about scheduling updates after update completed.\n // This batches updates and ensures we're completely outside any Lit update cycle.\n // Using setTimeout instead of Promise.resolve() because microtasks run before Lit's\n // change detection completes.\n setTimeout(() => {\n this.temporal.requestUpdate(\"ownCurrentTimeMs\");\n }, 0);\n }\n\n remove() {\n this.host.removeController(this);\n }\n}\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\n\nlet startTimeMsCache = new WeakMap<Element, number>();\nlet startTimeMsCacheResetScheduled = false;\nconst resetStartTimeMsCache = () => {\n startTimeMsCache = new WeakMap();\n if (\n typeof requestAnimationFrame !== \"undefined\" &&\n !startTimeMsCacheResetScheduled\n ) {\n startTimeMsCacheResetScheduled = true;\n requestAnimationFrame(() => {\n startTimeMsCacheResetScheduled = false;\n resetStartTimeMsCache();\n });\n }\n};\nresetStartTimeMsCache();\n\nexport const flushStartTimeMsCache = () => {\n startTimeMsCache = new WeakMap();\n};\n\nexport const EFTemporal = <T extends Constructor<LitElement>>(\n superClass: T,\n) => {\n class TemporalMixinClass extends superClass {\n #ownCurrentTimeController?: OwnCurrentTimeController;\n\n #parentTimegroup?: EFTimegroup;\n #rootTimegroupLocked = false; // When true, rootTimegroup won't be auto-recalculated\n \n @consume({ context: timegroupContext, subscribe: true })\n set parentTimegroup(value: EFTimegroup | undefined) {\n const oldParent = this.#parentTimegroup;\n const oldRole = determineTemporalRole(oldParent);\n const newRole = determineTemporalRole(value);\n\n this.#parentTimegroup = value;\n\n this.#ownCurrentTimeController?.remove();\n // Only auto-calculate rootTimegroup if it hasn't been locked\n // (locked means it was manually set, e.g., for render clones)\n if (!this.#rootTimegroupLocked) {\n this.rootTimegroup = this.getRootTimegroup();\n }\n if (this.rootTimegroup) {\n this.#ownCurrentTimeController = new OwnCurrentTimeController(\n this.rootTimegroup,\n this as InstanceType<Constructor<TemporalMixinInterface> & T>,\n );\n }\n\n // Only trigger callbacks if role actually changed\n if (oldRole !== newRole) {\n if (newRole === \"root\") {\n this.didBecomeRoot();\n } else {\n this.didBecomeChild();\n }\n }\n }\n \n /**\n * Lock the rootTimegroup to prevent auto-recalculation.\n * Used for render clones where the root must be fixed.\n * @internal\n */\n lockRootTimegroup() {\n this.#rootTimegroupLocked = true;\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#ownCurrentTimeController?.remove();\n\n if (this.playbackController) {\n this.playbackController.remove();\n this.playbackController = undefined;\n }\n\n // Clean up tracked animations to prevent memory leaks\n // Use dynamic import to avoid circular dependency with updateAnimations\n import(\"./updateAnimations.js\").then(({ cleanupTrackedAnimations }) => {\n cleanupTrackedAnimations(this);\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n this.#ownCurrentTimeController?.remove();\n\n // Root detection: Check DOM structure to determine if this is truly a root.\n // \n // We can't rely on Lit Context (parentTimegroup) because context propagates\n // asynchronously during update cycles. Children may complete their first update\n // before ancestors have provided context, causing them to incorrectly think\n // they're roots.\n //\n // Instead, we check if there's an ancestor ef-timegroup in the DOM. This is\n // reliable because DOM structure is established synchronously at connection time.\n // \n // If there's NO ancestor timegroup, this is a true root → create PlaybackController.\n // If there IS an ancestor, wait for context to propagate (handled by parentTimegroup setter).\n // Note: closest() includes self, so we check from parentElement to find true ancestors.\n const hasAncestorTimegroup = this.parentElement?.closest('ef-timegroup') != null;\n \n if (!hasAncestorTimegroup && !this.playbackController) {\n // True root: no ancestor timegroup in DOM\n // Defer slightly to allow element to fully initialize\n this.updateComplete.then(() => {\n if (!this.isConnected) return;\n if (!this.playbackController) {\n this.didBecomeRoot();\n }\n });\n }\n // For elements WITH ancestors, the parentTimegroup setter will be called\n // when Lit Context propagates, and if the role changes, didBecomeRoot/didBecomeChild\n // will be called appropriately.\n }\n\n get parentTimegroup() {\n return this.#parentTimegroup;\n }\n\n playbackController?: PlaybackController;\n\n get playing(): boolean {\n if (!this.playbackController) {\n return false;\n }\n return this.playbackController.playing;\n }\n\n set playing(value: boolean) {\n if (!this.playbackController) {\n console.warn(\"Cannot set playing on non-root temporal element\", this);\n return;\n }\n this.playbackController.setPlaying(value);\n }\n\n play(): void {\n if (!this.playbackController) {\n console.warn(\"play() called on non-root temporal element\", this);\n return;\n }\n this.playbackController.play();\n }\n\n pause(): void {\n if (!this.playbackController) {\n console.warn(\"pause() called on non-root temporal element\", this);\n return;\n }\n this.playbackController.pause();\n }\n\n @property({ type: Boolean, reflect: true, attribute: \"loop\" })\n get loop(): boolean {\n return this.playbackController?.loop ?? this.#loop;\n }\n\n set loop(value: boolean) {\n const oldValue = this.#loop;\n this.#loop = value;\n if (this.playbackController) {\n this.playbackController.setLoop(value);\n }\n this.requestUpdate(\"loop\", oldValue);\n }\n\n @property({\n type: String,\n attribute: \"offset\",\n converter: durationConverter,\n })\n private _offsetMs = 0;\n\n @property({\n type: Number,\n attribute: \"duration\",\n converter: durationConverter,\n })\n private _durationMs?: number;\n\n set duration(value: string | undefined) {\n if (value !== undefined) {\n this.setAttribute(\"duration\", value);\n } else {\n this.removeAttribute(\"duration\");\n }\n }\n\n @property({\n type: Number,\n attribute: \"trimstart\",\n converter: durationConverter,\n })\n private _trimStartMs: number | undefined = undefined;\n\n get trimStartMs() {\n if (this._trimStartMs === undefined) {\n return undefined;\n }\n return Math.min(\n Math.max(this._trimStartMs, 0),\n this.intrinsicDurationMs ?? 0,\n );\n }\n\n set trimStartMs(value: number | undefined) {\n this._trimStartMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"trimend\",\n converter: durationConverter,\n })\n private _trimEndMs: number | undefined = undefined;\n\n get trimEndMs() {\n if (this._trimEndMs === undefined) {\n return undefined;\n }\n return Math.min(this._trimEndMs, this.intrinsicDurationMs ?? 0);\n }\n\n set trimEndMs(value: number | undefined) {\n this._trimEndMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"sourcein\",\n converter: durationConverter,\n })\n private _sourceInMs: number | undefined = undefined;\n\n get sourceInMs() {\n if (this._sourceInMs === undefined) {\n return undefined;\n }\n return Math.max(this._sourceInMs, 0);\n }\n\n set sourceInMs(value: number | undefined) {\n this._sourceInMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"sourceout\",\n converter: durationConverter,\n })\n private _sourceOutMs: number | undefined = undefined;\n\n get sourceOutMs() {\n if (this._sourceOutMs === undefined) {\n return undefined;\n }\n if (\n this.intrinsicDurationMs &&\n this._sourceOutMs > this.intrinsicDurationMs\n ) {\n return this.intrinsicDurationMs;\n }\n return Math.max(this._sourceOutMs, 0);\n }\n\n set sourceOutMs(value: number | undefined) {\n this._sourceOutMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"startoffset\",\n converter: durationConverter,\n })\n private _startOffsetMs = 0;\n public get startOffsetMs(): number {\n return this._startOffsetMs;\n }\n\n @state()\n rootTimegroup?: EFTimegroup = this.getRootTimegroup();\n\n private getRootTimegroup(): EFTimegroup | undefined {\n let parent =\n this.tagName === \"EF-TIMEGROUP\" ? this : this.parentTimegroup;\n while (parent?.parentTimegroup) {\n parent = parent.parentTimegroup;\n }\n return parent as EFTimegroup | undefined;\n }\n\n get hasExplicitDuration() {\n return this._durationMs !== undefined;\n }\n\n get explicitDurationMs() {\n if (this.hasExplicitDuration) {\n return this._durationMs;\n }\n return undefined;\n }\n\n get hasOwnDuration() {\n return this.intrinsicDurationMs !== undefined || this.hasExplicitDuration;\n }\n\n get intrinsicDurationMs() {\n return undefined;\n }\n\n get durationMs() {\n // Prevent infinite loops: don't call parent.durationMs if parent is currently calculating\n // Lazy import to break circular dependency: EFTemporal -> EFTimegroup -> EFMedia -> EFTemporal\n const isTimegroupCalculatingDuration =\n getIsTimegroupCalculatingDuration();\n const parentDurationMs = isTimegroupCalculatingDuration(\n this.parentTimegroup,\n )\n ? undefined\n : this.parentTimegroup?.durationMs;\n const durationSource = determineDurationSource(\n this.intrinsicDurationMs,\n this._durationMs,\n parentDurationMs,\n );\n\n const modification = determineDurationModificationStrategy(\n this.trimStartMs,\n this.trimEndMs,\n this.sourceInMs,\n this.sourceOutMs,\n );\n\n return evaluateModifiedDuration(\n durationSource.baseDurationMs,\n modification,\n );\n }\n\n get sourceStartMs() {\n return this.trimStartMs ?? this.sourceInMs ?? 0;\n }\n\n #offsetMs() {\n return this._offsetMs || 0;\n }\n\n #parentTemporal() {\n let parent = this.parentElement;\n while (parent && !isEFTemporal(parent)) {\n parent = parent.parentElement;\n }\n return parent;\n }\n\n /**\n * The start time of the element within its parent timegroup.\n */\n get startTimeWithinParentMs() {\n const parent = this.#parentTemporal();\n if (!parent) {\n return 0;\n }\n return this.startTimeMs - parent.startTimeMs;\n }\n\n #loop = false;\n\n get startTimeMs(): number {\n const cachedStartTime = startTimeMsCache.get(this);\n if (cachedStartTime !== undefined) {\n return cachedStartTime;\n }\n\n const startTime = evaluateStartTime(\n this as InstanceType<Constructor<TemporalMixinInterface> & T>,\n this.parentTimegroup,\n this.#offsetMs(),\n (parent) => shallowGetTemporalElements(parent),\n );\n\n startTimeMsCache.set(this, startTime);\n return startTime;\n }\n\n get endTimeMs(): number {\n return this.startTimeMs + this.durationMs;\n }\n\n #currentTimeMs = 0;\n\n /**\n * The current time of the element within itself.\n * Compare with `currentTimeMs` to see the current time with respect to the root timegroup\n */\n get ownCurrentTimeMs(): number {\n const timeSource = determineCurrentTimeSource(\n this.playbackController,\n this.rootTimegroup,\n this.rootTimegroup === (this as any as EFTimegroup),\n this.#currentTimeMs,\n this.startTimeMs,\n this.durationMs,\n );\n return timeSource.timeMs;\n }\n\n /**\n * Element's current time for progress calculation.\n * Non-timegroup temporal elements use their local time (ownCurrentTimeMs)\n */\n get currentTimeMs() {\n return this.ownCurrentTimeMs;\n }\n\n set currentTimeMs(value: number) {\n const role = determineTemporalRole(this.parentTimegroup);\n\n // Apply current time based on role\n switch (role) {\n case \"root\":\n if (this.playbackController) {\n this.playbackController.currentTime = value / 1000;\n } else {\n this.#currentTimeMs = value;\n this.requestUpdate(\"currentTimeMs\");\n }\n break;\n case \"child\":\n if (\n this.rootTimegroup &&\n this.rootTimegroup !== (this as any as EFTimegroup)\n ) {\n this.rootTimegroup.currentTimeMs = value;\n } else {\n this.#currentTimeMs = value;\n this.requestUpdate(\"currentTimeMs\");\n }\n break;\n }\n }\n\n /**\n * Used to calculate the internal currentTimeMs of the element. This is useful\n * for mapping to internal media time codes for audio/video elements.\n */\n get currentSourceTimeMs() {\n const leadingTrimMs = this.sourceInMs || this.trimStartMs || 0;\n return this.ownCurrentTimeMs + leadingTrimMs;\n }\n\n /**\n * @deprecated Use FrameRenderable interface via FrameController instead.\n * This is a compatibility wrapper - base class just waits for updateComplete.\n */\n #frameTaskPromise: Promise<void> = Promise.resolve();\n \n frameTask = (() => {\n const self = this;\n const taskObj: { run(): void | Promise<void>; taskComplete: Promise<void> } = {\n run: () => {\n self.#frameTaskPromise = self.updateComplete.then(() => {});\n taskObj.taskComplete = self.#frameTaskPromise;\n return self.#frameTaskPromise;\n },\n taskComplete: Promise.resolve(),\n };\n return taskObj;\n })();\n\n didBecomeRoot() {\n if (!this.playbackController) {\n this.playbackController = new PlaybackController(this as any);\n if (this.#loop) {\n this.playbackController.setLoop(this.#loop);\n }\n }\n }\n\n didBecomeChild() {\n if (this.playbackController) {\n this.playbackController.remove();\n this.playbackController = undefined;\n }\n }\n }\n\n Object.defineProperty(TemporalMixinClass.prototype, EF_TEMPORAL, {\n value: true,\n });\n\n return TemporalMixinClass as unknown as Constructor<TemporalMixinInterface> &\n T;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"EFTemporal.js","names":["isTimegroupCalculatingDurationFn:\n | ((timegroup: EFTimegroup | undefined) => boolean)\n | null","fallbackFn: (timegroup: EFTimegroup | undefined) => boolean","assignedElements: Element[]","temporalCache: Map<Element, TemporalMixinInterface[]>","host: EFTimegroup","temporal: TemporalMixinInterface & LitElement","#lastKnownTimeMs","taskObj: { run(): void | Promise<void>; taskComplete: Promise<void> }","#frameTaskPromise","#parentTimegroup","#ownCurrentTimeController","#rootTimegroupLocked","#loop","#parentTemporal","#offsetMs","#currentTimeMs"],"sources":["../../src/elements/EFTemporal.ts"],"sourcesContent":["import { consume, createContext } from \"@lit/context\";\nimport type { LitElement, ReactiveController } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { PlaybackController } from \"../gui/PlaybackController.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport type { EFTimegroup } from \"./EFTimegroup.js\";\n// Lazy import to break circular dependency: EFTemporal -> EFTimegroup -> EFMedia -> EFTemporal\n// isTimegroupCalculatingDuration is only used at runtime in a getter, so we can import it lazily\n// Use a module-level variable that gets set when EFTimegroup module loads\nlet isTimegroupCalculatingDurationFn:\n | ((timegroup: EFTimegroup | undefined) => boolean)\n | null = null;\n\n// This function will be called by EFTimegroup when it loads to register the function\nexport const registerIsTimegroupCalculatingDuration = (\n fn: (timegroup: EFTimegroup | undefined) => boolean,\n) => {\n isTimegroupCalculatingDurationFn = fn;\n};\n\nconst getIsTimegroupCalculatingDuration = (): ((\n timegroup: EFTimegroup | undefined,\n) => boolean) => {\n if (isTimegroupCalculatingDurationFn) {\n return isTimegroupCalculatingDurationFn as (\n timegroup: EFTimegroup | undefined,\n ) => boolean;\n }\n\n // If not registered yet, try to import synchronously (only works if module is already loaded)\n // This is a fallback for cases where EFTimegroup hasn't called registerIsTimegroupCalculatingDuration\n // In practice, EFTimegroup will call registerIsTimegroupCalculatingDuration when it loads\n let fallbackFn: (timegroup: EFTimegroup | undefined) => boolean = () => false;\n try {\n // Access the function via a global or try to get it from the module cache\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const efTimegroupModule = (globalThis as any).__EFTimegroupModule;\n if (efTimegroupModule?.isTimegroupCalculatingDuration) {\n fallbackFn = efTimegroupModule.isTimegroupCalculatingDuration;\n }\n } catch {\n // Use default fallback\n }\n isTimegroupCalculatingDurationFn = fallbackFn;\n return fallbackFn;\n};\n\nexport const timegroupContext = createContext<EFTimegroup>(\n Symbol(\"timeGroupContext\"),\n);\n\n// ============================================================================\n// Core Concept 1: Temporal Role\n// ============================================================================\n// A temporal element is either a root (controls its own playback) or a child\n// (delegates playback to its root timegroup). This is the fundamental invariant.\n// ============================================================================\n\ntype TemporalRole = \"root\" | \"child\";\n\nfunction determineTemporalRole(\n parentTimegroup: EFTimegroup | undefined,\n): TemporalRole {\n return parentTimegroup === undefined ? \"root\" : \"child\";\n}\n\n// ============================================================================\n// Core Concept 2: Duration Source\n// ============================================================================\n// Duration comes from one of three sources: intrinsic (media-based),\n// explicit (attribute), or inherited (from parent). This determines the base\n// duration before any modifications.\n// ============================================================================\n\ntype DurationSource = \"intrinsic\" | \"explicit\" | \"inherited\";\n\ninterface DurationSourceResult {\n source: DurationSource;\n baseDurationMs: number;\n}\n\nfunction determineDurationSource(\n intrinsicDurationMs: number | undefined,\n explicitDurationMs: number | undefined,\n parentDurationMs: number | undefined,\n): DurationSourceResult {\n if (intrinsicDurationMs !== undefined) {\n return { source: \"intrinsic\", baseDurationMs: intrinsicDurationMs };\n }\n if (explicitDurationMs !== undefined) {\n return { source: \"explicit\", baseDurationMs: explicitDurationMs };\n }\n if (parentDurationMs !== undefined) {\n return { source: \"inherited\", baseDurationMs: parentDurationMs };\n }\n return { source: \"inherited\", baseDurationMs: 0 };\n}\n\n// ============================================================================\n// Core Concept 3: Duration Modification Strategy\n// ============================================================================\n// Duration can be modified by trimming (removing from edges) or source\n// manipulation (selecting a portion of the source). These are mutually\n// exclusive strategies.\n// ============================================================================\n\ntype DurationModificationStrategy = \"none\" | \"trimming\" | \"source-manipulation\";\n\ninterface DurationModificationState {\n strategy: DurationModificationStrategy;\n trimStartMs: number | undefined;\n trimEndMs: number | undefined;\n sourceInMs: number | undefined;\n sourceOutMs: number | undefined;\n}\n\nfunction determineDurationModificationStrategy(\n trimStartMs: number | undefined,\n trimEndMs: number | undefined,\n sourceInMs: number | undefined,\n sourceOutMs: number | undefined,\n): DurationModificationState {\n if (trimStartMs !== undefined || trimEndMs !== undefined) {\n return {\n strategy: \"trimming\",\n trimStartMs,\n trimEndMs,\n sourceInMs: undefined,\n sourceOutMs: undefined,\n };\n }\n if (sourceInMs !== undefined || sourceOutMs !== undefined) {\n return {\n strategy: \"source-manipulation\",\n trimStartMs: undefined,\n trimEndMs: undefined,\n sourceInMs,\n sourceOutMs,\n };\n }\n return {\n strategy: \"none\",\n trimStartMs: undefined,\n trimEndMs: undefined,\n sourceInMs: undefined,\n sourceOutMs: undefined,\n };\n}\n\nfunction evaluateModifiedDuration(\n baseDurationMs: number,\n modification: DurationModificationState,\n): number {\n if (baseDurationMs === 0) {\n return 0;\n }\n\n switch (modification.strategy) {\n case \"trimming\": {\n const trimmedDurationMs =\n baseDurationMs -\n (modification.trimStartMs ?? 0) -\n (modification.trimEndMs ?? 0);\n return Math.max(0, trimmedDurationMs);\n }\n case \"source-manipulation\": {\n const sourceInMs = modification.sourceInMs ?? 0;\n const sourceOutMs = modification.sourceOutMs ?? baseDurationMs;\n if (sourceInMs >= sourceOutMs) {\n return 0;\n }\n return sourceOutMs - sourceInMs;\n }\n case \"none\":\n return baseDurationMs;\n }\n}\n\n// ============================================================================\n// Core Concept 4: Start Time Calculation Strategy\n// ============================================================================\n// Start time is calculated differently based on parent timegroup mode:\n// - Sequence mode: based on previous sibling\n// - Other modes: based on parent start + offset\n// ============================================================================\n\ntype StartTimeStrategy = \"sequence\" | \"offset\";\n\nfunction determineStartTimeStrategy(\n parentTimegroup: EFTimegroup | undefined,\n): StartTimeStrategy {\n if (!parentTimegroup) {\n return \"offset\";\n }\n return parentTimegroup.mode === \"sequence\" ? \"sequence\" : \"offset\";\n}\n\nfunction evaluateStartTimeForSequence(\n _element: TemporalMixinInterface & HTMLElement,\n parentTimegroup: EFTimegroup,\n siblingTemporals: TemporalMixinInterface[],\n ownIndex: number,\n): number {\n if (ownIndex === 0) {\n return parentTimegroup.startTimeMs;\n }\n const previous = siblingTemporals[ownIndex - 1];\n if (!previous) {\n throw new Error(\"Previous temporal element not found\");\n }\n return previous.startTimeMs + previous.durationMs - parentTimegroup.overlapMs;\n}\n\nfunction evaluateStartTimeForOffset(\n parentTimegroup: EFTimegroup,\n offsetMs: number,\n): number {\n return parentTimegroup.startTimeMs + offsetMs;\n}\n\nfunction evaluateStartTime(\n element: TemporalMixinInterface & HTMLElement,\n parentTimegroup: EFTimegroup | undefined,\n offsetMs: number,\n getSiblingTemporals: (parent: EFTimegroup) => TemporalMixinInterface[],\n): number {\n if (!parentTimegroup) {\n return 0;\n }\n\n const strategy = determineStartTimeStrategy(parentTimegroup);\n switch (strategy) {\n case \"sequence\": {\n const siblingTemporals = getSiblingTemporals(parentTimegroup);\n const ownIndex = siblingTemporals.indexOf(element);\n if (ownIndex === -1) {\n return 0;\n }\n return evaluateStartTimeForSequence(\n element,\n parentTimegroup,\n siblingTemporals,\n ownIndex,\n );\n }\n case \"offset\":\n return evaluateStartTimeForOffset(parentTimegroup, offsetMs);\n }\n}\n\n// ============================================================================\n// Core Concept 5: Current Time Source\n// ============================================================================\n// Current time comes from one of three sources: playback controller (root\n// elements), root timegroup (child elements), or local storage (fallback).\n// ============================================================================\n\ntype CurrentTimeSource = \"playback-controller\" | \"root-timegroup\" | \"local\";\n\ninterface CurrentTimeSourceResult {\n source: CurrentTimeSource;\n timeMs: number;\n}\n\nfunction determineCurrentTimeSource(\n playbackController: PlaybackController | undefined,\n rootTimegroup: EFTimegroup | undefined,\n isRootTimegroup: boolean,\n localTimeMs: number,\n startTimeMs: number,\n durationMs: number,\n): CurrentTimeSourceResult {\n if (playbackController) {\n const timeMs = Math.min(\n Math.max(0, playbackController.currentTimeMs),\n durationMs,\n );\n return { source: \"playback-controller\", timeMs };\n }\n\n if (rootTimegroup && !isRootTimegroup) {\n const timeMs = Math.min(\n Math.max(0, rootTimegroup.currentTimeMs - startTimeMs),\n durationMs,\n );\n return { source: \"root-timegroup\", timeMs };\n }\n\n const timeMs = Math.min(Math.max(0, localTimeMs), durationMs);\n return { source: \"local\", timeMs };\n}\n\nexport declare class TemporalMixinInterface {\n playbackController?: PlaybackController;\n playing: boolean;\n loop: boolean;\n play(): void;\n pause(): void;\n\n get hasOwnDuration(): boolean;\n /**\n * Whether the element has a duration set as an attribute.\n */\n get hasExplicitDuration(): boolean;\n\n get sourceStartMs(): number;\n\n /**\n * Used to trim the start of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `trimstart=\"10s\"` is equivalent to `trimstart=\"10000ms\"`.\n *\n * @domAttribute \"trimstart\"\n */\n get trimStartMs(): number | undefined;\n\n /**\n * Used to trim the end of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `trimend=\"10s\"` is equivalent to `trimend=\"10000ms\"`.\n *\n * @domAttribute \"trimend\"\n */\n get trimEndMs(): number;\n\n set trimStartMs(value: number | undefined);\n set trimEndMs(value: number | undefined);\n set trimstart(value: string | undefined);\n set trimend(value: string | undefined);\n\n /**\n * The source in time of the element.\n *\n * This is an amount of time to trim off the beginning of the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `sourcein=\"10s\"` is equivalent to `sourcein=\"10000ms\"`.\n *\n * If the sourcein time is greater than the duration of the media, the media\n * will not be played.\n *\n * If the media is 20 seconds long, and the `sourcein` value is set to `10s`, the\n * media will play for 10 seconds, starting at the 10 second mark.\n *\n * Can be used in conjunction with `sourceout` to create a trimmed media.\n *\n * @domAttribute \"sourcein\"\n */\n get sourceInMs(): number | undefined;\n\n /**\n * The source out time of the element.\n *\n * This is the point in time in the media that will be treated as the end of\n * the media.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `sourceout=\"10s\"` is equivalent to `sourceout=\"10000ms\"`.\n *\n * If the sourceout time is greater than the duration of the media, the media\n * will play until the end of the media.\n *\n * If the media is 20 seconds long, and the `sourceout` value is set to `10s`,\n * the media will play for 10 seconds, starting at zero seconds and ending at\n * the 10 second mark.\n *\n * Can be used in conjunction with `sourcein` to create a trimmed media.\n *\n * @domAttribute \"sourceout\"\n */\n get sourceOutMs(): number | undefined;\n\n set sourceInMs(value: number | undefined);\n set sourceOutMs(value: number | undefined);\n set sourcein(value: string | undefined);\n set sourceout(value: string | undefined);\n\n /**\n * @domAttribute \"duration\"\n */\n get durationMs(): number;\n\n get explicitDurationMs(): number | undefined;\n\n get intrinsicDurationMs(): number | undefined;\n\n /**\n * The start time of the element within its root timegroup in milliseconds.\n *\n * This is an absolute time according to the highest scoped timegroup the media element is contained within.\n *\n * The calculated value will depend on the mode of the timegroup and the offset of the media element.\n *\n * If the parent time group is in `sequence` mode, the start time will be the\n * start time of the previous sibling element plus the previous sibling's duration\n * minus the overlap of the previous sibling and the current sibling.\n *\n * If the parent time group is in `contain` or `fixed` mode, the start time will be\n * the start time of the parent time group plus the offset of the media element.\n */\n get startTimeMs(): number;\n /**\n * The end time of the element within its root timegroup in milliseconds.\n *\n * This is an absolute time according to the highest scoped timegroup the media\n * element is contained within. Computed by adding the media's duration to its\n * start time.\n *\n * If the media element has been trimmed, its end time will be calculated according it\n * its trimmed duration, not its original duration.\n */\n get endTimeMs(): number;\n /**\n * The start time of the element within its parent timegroup in milliseconds.\n *\n * This is a relative time according to the closest timegroup the media element\n * is contained within. Unless the media element has been given any kind of specific offset\n * it is common for this time to be zero.\n */\n get startTimeWithinParentMs(): number;\n\n /**\n * The current time of the element in milliseconds.\n *\n * This is a relative time according to the closest timegroup the media element\n * is contained within.\n *\n * This is suitable for determining the percentage of the media that has been\n * played.\n */\n get ownCurrentTimeMs(): number;\n\n /**\n * Element's current time for progress calculation.\n * For timegroups: their timeline currentTimeMs\n * For other temporal elements: their ownCurrentTimeMs\n */\n get currentTimeMs(): number;\n set currentTimeMs(value: number);\n /**\n * The current time of the element in milliseconds, adjusted for trimming.\n *\n * This is suitable for mapping to internal media time codes for audio/video\n * elements.\n *\n * For example, if the media has a `sourcein` value of 10s, when `ownCurrentTimeMs` is 0s,\n * `currentSourceTimeMs` will be 10s.\n *\n * sourcein=10s sourceout=10s\n * / / /\n * |--------|=================|---------|\n * ^\n * |_\n * currentSourceTimeMs === 10s\n * |_\n * ownCurrentTimeMs === 0s\n */\n get currentSourceTimeMs(): number;\n\n set duration(value: string);\n get duration(): string;\n\n /**\n * The offset of the element within its parent timegroup in milliseconds.\n *\n * This can be set in either seconds or milliseconds.\n *\n * For example, `offset=\"10s\"` is equivalent to `offset=\"10000ms\"`.\n *\n * This can be used to create a negative or positive offset for the start time of the media.\n *\n * This will change the start time of the media relative to it's otherwise normal start time.\n *\n * The duration of the element, or it's parent, or the start and end time of it's temporal siblings will not\n * be affected by this offset.\n *\n * @domAttribute \"offset\"\n */\n set offset(value: string);\n get offset(): string;\n\n /**\n * A convenience property for getting the nearest containing timegroup of the media element.\n */\n parentTimegroup?: EFTimegroup;\n\n /**\n * A convenience property for getting the root timegroup of the media element.\n */\n rootTimegroup?: EFTimegroup;\n\n /**\n * Frame task interface. Can be a Lit Task or a simple object with run() and taskComplete.\n * @deprecated Prefer using FrameRenderable interface via FrameController.\n */\n frameTask: { run(): void | Promise<void>; taskComplete: Promise<unknown> };\n\n didBecomeRoot(): void;\n didBecomeChild(): void;\n\n updateComplete: Promise<boolean>;\n}\n\nexport const isEFTemporal = (obj: any): obj is TemporalMixinInterface =>\n obj[EF_TEMPORAL];\n\nconst EF_TEMPORAL = Symbol(\"EF_TEMPORAL\");\n\nexport const deepGetTemporalElements = (\n element: Element,\n temporals: Array<TemporalMixinInterface & HTMLElement> = [],\n) => {\n // Get children to walk - handle both regular children and slotted content\n const children = getChildrenIncludingSlotted(element);\n \n for (const child of children) {\n if (isEFTemporal(child)) {\n temporals.push(child as TemporalMixinInterface & HTMLElement);\n }\n deepGetTemporalElements(child, temporals);\n }\n return temporals;\n};\n\n/**\n * Gets all child elements including slotted content for shadow DOM elements.\n * For elements with shadow DOM that contain slots, this returns the assigned\n * elements (slotted content) instead of just the shadow DOM children.\n */\nconst getChildrenIncludingSlotted = (element: Element): Element[] => {\n // If element has shadowRoot with slots, get assigned elements\n if (element.shadowRoot) {\n const slots = element.shadowRoot.querySelectorAll('slot');\n if (slots.length > 0) {\n const assignedElements: Element[] = [];\n for (const slot of slots) {\n assignedElements.push(...slot.assignedElements());\n }\n // Also include shadow DOM children that aren't slots (for mixed content)\n for (const child of element.shadowRoot.children) {\n if (child.tagName !== 'SLOT') {\n assignedElements.push(child);\n }\n }\n return assignedElements;\n }\n }\n \n // Fallback to regular children\n return Array.from(element.children);\n};\n\nexport const deepGetElementsWithFrameTasks = (\n element: Element,\n elements: Array<TemporalMixinInterface & HTMLElement> = [],\n) => {\n // Get children to walk - handle both regular children and slotted content\n const children = getChildrenIncludingSlotted(element);\n \n for (const child of children) {\n // Check for frameTask with run method (works with both Lit Task and our compatibility wrapper)\n const hasFrameTask = \"frameTask\" in child && \n child.frameTask != null && \n typeof (child.frameTask as any).run === \"function\";\n if (hasFrameTask) {\n elements.push(child as TemporalMixinInterface & HTMLElement);\n }\n deepGetElementsWithFrameTasks(child, elements);\n }\n return elements;\n};\n\nlet temporalCache: Map<Element, TemporalMixinInterface[]>;\nlet temporalCacheResetScheduled = false;\nexport const resetTemporalCache = () => {\n temporalCache = new Map();\n if (\n typeof requestAnimationFrame !== \"undefined\" &&\n !temporalCacheResetScheduled\n ) {\n temporalCacheResetScheduled = true;\n requestAnimationFrame(() => {\n temporalCacheResetScheduled = false;\n resetTemporalCache();\n });\n }\n};\nresetTemporalCache();\n\nexport const shallowGetTemporalElements = (\n element: Element,\n temporals: TemporalMixinInterface[] = [],\n) => {\n const cachedResult = temporalCache.get(element);\n if (cachedResult) {\n return cachedResult;\n }\n // Get children to walk - handle both regular children and slotted content\n const children = getChildrenIncludingSlotted(element);\n \n for (const child of children) {\n if (isEFTemporal(child)) {\n temporals.push(child);\n } else {\n shallowGetTemporalElements(child, temporals);\n }\n }\n temporalCache.set(element, temporals);\n return temporals;\n};\n\nexport class OwnCurrentTimeController implements ReactiveController {\n #lastKnownTimeMs: number | undefined = undefined;\n \n constructor(\n private host: EFTimegroup,\n private temporal: TemporalMixinInterface & LitElement,\n ) {\n host.addController(this);\n }\n\n hostUpdated() {\n // CRITICAL FIX: Only trigger child updates when root's currentTimeMs actually changes.\n // Previously, this fired on EVERY root update, causing 40+ child updates per root update.\n // With nested timegroups, this created cascading updates that locked up the main thread.\n const currentTimeMs = this.host.currentTimeMs;\n if (this.#lastKnownTimeMs === currentTimeMs) {\n return; // Time hasn't changed, no need to update children\n }\n this.#lastKnownTimeMs = currentTimeMs;\n \n // Defer update using setTimeout(0) to avoid Lit warning about scheduling updates after update completed.\n // This batches updates and ensures we're completely outside any Lit update cycle.\n // Using setTimeout instead of Promise.resolve() because microtasks run before Lit's\n // change detection completes.\n setTimeout(() => {\n this.temporal.requestUpdate(\"ownCurrentTimeMs\");\n }, 0);\n }\n\n remove() {\n this.host.removeController(this);\n }\n}\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\n\nlet startTimeMsCache = new WeakMap<Element, number>();\nlet startTimeMsCacheResetScheduled = false;\nconst resetStartTimeMsCache = () => {\n startTimeMsCache = new WeakMap();\n if (\n typeof requestAnimationFrame !== \"undefined\" &&\n !startTimeMsCacheResetScheduled\n ) {\n startTimeMsCacheResetScheduled = true;\n requestAnimationFrame(() => {\n startTimeMsCacheResetScheduled = false;\n resetStartTimeMsCache();\n });\n }\n};\nresetStartTimeMsCache();\n\nexport const flushStartTimeMsCache = () => {\n startTimeMsCache = new WeakMap();\n};\n\nexport const EFTemporal = <T extends Constructor<LitElement>>(\n superClass: T,\n) => {\n class TemporalMixinClass extends superClass {\n #ownCurrentTimeController?: OwnCurrentTimeController;\n\n #parentTimegroup?: EFTimegroup;\n #rootTimegroupLocked = false; // When true, rootTimegroup won't be auto-recalculated\n \n @consume({ context: timegroupContext, subscribe: true })\n set parentTimegroup(value: EFTimegroup | undefined) {\n const oldParent = this.#parentTimegroup;\n const oldRole = determineTemporalRole(oldParent);\n const newRole = determineTemporalRole(value);\n\n this.#parentTimegroup = value;\n\n this.#ownCurrentTimeController?.remove();\n // Only auto-calculate rootTimegroup if it hasn't been locked\n // (locked means it was manually set, e.g., for render clones)\n if (!this.#rootTimegroupLocked) {\n this.rootTimegroup = this.getRootTimegroup();\n }\n if (this.rootTimegroup) {\n this.#ownCurrentTimeController = new OwnCurrentTimeController(\n this.rootTimegroup,\n this as InstanceType<Constructor<TemporalMixinInterface> & T>,\n );\n }\n\n // Only trigger callbacks if role actually changed\n if (oldRole !== newRole) {\n if (newRole === \"root\") {\n this.didBecomeRoot();\n } else {\n this.didBecomeChild();\n }\n }\n }\n \n /**\n * Lock the rootTimegroup to prevent auto-recalculation.\n * Used for render clones where the root must be fixed.\n * @internal\n */\n lockRootTimegroup() {\n this.#rootTimegroupLocked = true;\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#ownCurrentTimeController?.remove();\n\n if (this.playbackController) {\n this.playbackController.remove();\n this.playbackController = undefined;\n }\n\n // Clean up tracked animations to prevent memory leaks\n // Use dynamic import to avoid circular dependency with updateAnimations\n import(\"./updateAnimations.js\").then(({ cleanupTrackedAnimations }) => {\n cleanupTrackedAnimations(this);\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n this.#ownCurrentTimeController?.remove();\n\n // Root detection: Check DOM structure to determine if this is truly a root.\n // \n // We can't rely on Lit Context (parentTimegroup) because context propagates\n // asynchronously during update cycles. Children may complete their first update\n // before ancestors have provided context, causing them to incorrectly think\n // they're roots.\n //\n // Instead, we check if there's an ancestor ef-timegroup in the DOM. This is\n // reliable because DOM structure is established synchronously at connection time.\n // \n // If there's NO ancestor timegroup, this is a true root → create PlaybackController.\n // If there IS an ancestor, wait for context to propagate (handled by parentTimegroup setter).\n // Note: closest() includes self, so we check from parentElement to find true ancestors.\n const hasAncestorTimegroup = this.parentElement?.closest('ef-timegroup') != null;\n \n if (!hasAncestorTimegroup && !this.playbackController) {\n // True root: no ancestor timegroup in DOM\n // Defer slightly to allow element to fully initialize\n this.updateComplete.then(() => {\n if (!this.isConnected) return;\n if (!this.playbackController) {\n this.didBecomeRoot();\n }\n });\n }\n // For elements WITH ancestors, the parentTimegroup setter will be called\n // when Lit Context propagates, and if the role changes, didBecomeRoot/didBecomeChild\n // will be called appropriately.\n }\n\n get parentTimegroup() {\n return this.#parentTimegroup;\n }\n\n playbackController?: PlaybackController;\n\n get playing(): boolean {\n if (!this.playbackController) {\n return false;\n }\n return this.playbackController.playing;\n }\n\n set playing(value: boolean) {\n if (!this.playbackController) {\n console.warn(\"Cannot set playing on non-root temporal element\", this);\n return;\n }\n this.playbackController.setPlaying(value);\n }\n\n play(): void {\n if (!this.playbackController) {\n console.warn(\"play() called on non-root temporal element\", this);\n return;\n }\n this.playbackController.play();\n }\n\n pause(): void {\n if (!this.playbackController) {\n console.warn(\"pause() called on non-root temporal element\", this);\n return;\n }\n this.playbackController.pause();\n }\n\n @property({ type: Boolean, reflect: true, attribute: \"loop\" })\n get loop(): boolean {\n return this.playbackController?.loop ?? this.#loop;\n }\n\n set loop(value: boolean) {\n const oldValue = this.#loop;\n this.#loop = value;\n if (this.playbackController) {\n this.playbackController.setLoop(value);\n }\n this.requestUpdate(\"loop\", oldValue);\n }\n\n @property({\n type: String,\n attribute: \"offset\",\n converter: durationConverter,\n })\n private _offsetMs = 0;\n\n @property({\n type: Number,\n attribute: \"duration\",\n converter: durationConverter,\n })\n private _durationMs?: number;\n\n set duration(value: string | undefined) {\n if (value !== undefined) {\n this.setAttribute(\"duration\", value);\n } else {\n this.removeAttribute(\"duration\");\n }\n }\n\n @property({\n type: Number,\n attribute: \"trimstart\",\n converter: durationConverter,\n })\n private _trimStartMs: number | undefined = undefined;\n\n get trimStartMs() {\n if (this._trimStartMs === undefined) {\n return undefined;\n }\n return Math.min(\n Math.max(this._trimStartMs, 0),\n this.intrinsicDurationMs ?? 0,\n );\n }\n\n set trimStartMs(value: number | undefined) {\n this._trimStartMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"trimend\",\n converter: durationConverter,\n })\n private _trimEndMs: number | undefined = undefined;\n\n get trimEndMs() {\n if (this._trimEndMs === undefined) {\n return undefined;\n }\n return Math.min(this._trimEndMs, this.intrinsicDurationMs ?? 0);\n }\n\n set trimEndMs(value: number | undefined) {\n this._trimEndMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"sourcein\",\n converter: durationConverter,\n })\n private _sourceInMs: number | undefined = undefined;\n\n get sourceInMs() {\n if (this._sourceInMs === undefined) {\n return undefined;\n }\n return Math.max(this._sourceInMs, 0);\n }\n\n set sourceInMs(value: number | undefined) {\n this._sourceInMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"sourceout\",\n converter: durationConverter,\n })\n private _sourceOutMs: number | undefined = undefined;\n\n get sourceOutMs() {\n if (this._sourceOutMs === undefined) {\n return undefined;\n }\n if (\n this.intrinsicDurationMs &&\n this._sourceOutMs > this.intrinsicDurationMs\n ) {\n return this.intrinsicDurationMs;\n }\n return Math.max(this._sourceOutMs, 0);\n }\n\n set sourceOutMs(value: number | undefined) {\n this._sourceOutMs = value;\n }\n\n @property({\n type: Number,\n attribute: \"startoffset\",\n converter: durationConverter,\n })\n private _startOffsetMs = 0;\n public get startOffsetMs(): number {\n return this._startOffsetMs;\n }\n\n @state()\n rootTimegroup?: EFTimegroup = this.getRootTimegroup();\n\n private getRootTimegroup(): EFTimegroup | undefined {\n let parent =\n this.tagName === \"EF-TIMEGROUP\" ? this : this.parentTimegroup;\n while (parent?.parentTimegroup) {\n parent = parent.parentTimegroup;\n }\n return parent as EFTimegroup | undefined;\n }\n\n get hasExplicitDuration() {\n return this._durationMs !== undefined;\n }\n\n get explicitDurationMs() {\n if (this.hasExplicitDuration) {\n return this._durationMs;\n }\n return undefined;\n }\n\n get hasOwnDuration() {\n return this.intrinsicDurationMs !== undefined || this.hasExplicitDuration;\n }\n\n get intrinsicDurationMs() {\n return undefined;\n }\n\n get durationMs() {\n // Prevent infinite loops: don't call parent.durationMs if parent is currently calculating\n // Lazy import to break circular dependency: EFTemporal -> EFTimegroup -> EFMedia -> EFTemporal\n const isTimegroupCalculatingDuration =\n getIsTimegroupCalculatingDuration();\n const parentDurationMs = isTimegroupCalculatingDuration(\n this.parentTimegroup,\n )\n ? undefined\n : this.parentTimegroup?.durationMs;\n const durationSource = determineDurationSource(\n this.intrinsicDurationMs,\n this._durationMs,\n parentDurationMs,\n );\n\n const modification = determineDurationModificationStrategy(\n this.trimStartMs,\n this.trimEndMs,\n this.sourceInMs,\n this.sourceOutMs,\n );\n\n return evaluateModifiedDuration(\n durationSource.baseDurationMs,\n modification,\n );\n }\n\n get sourceStartMs() {\n return this.trimStartMs ?? this.sourceInMs ?? 0;\n }\n\n #offsetMs() {\n return this._offsetMs || 0;\n }\n\n #parentTemporal() {\n let parent = this.parentElement;\n while (parent && !isEFTemporal(parent)) {\n parent = parent.parentElement;\n }\n return parent;\n }\n\n /**\n * The start time of the element within its parent timegroup.\n */\n get startTimeWithinParentMs() {\n const parent = this.#parentTemporal();\n if (!parent) {\n return 0;\n }\n return this.startTimeMs - parent.startTimeMs;\n }\n\n #loop = false;\n\n get startTimeMs(): number {\n const cachedStartTime = startTimeMsCache.get(this);\n if (cachedStartTime !== undefined) {\n return cachedStartTime;\n }\n\n const startTime = evaluateStartTime(\n this as InstanceType<Constructor<TemporalMixinInterface> & T>,\n this.parentTimegroup,\n this.#offsetMs(),\n (parent) => shallowGetTemporalElements(parent),\n );\n\n startTimeMsCache.set(this, startTime);\n return startTime;\n }\n\n get endTimeMs(): number {\n return this.startTimeMs + this.durationMs;\n }\n\n #currentTimeMs = 0;\n\n /**\n * The current time of the element within itself.\n * Compare with `currentTimeMs` to see the current time with respect to the root timegroup\n */\n get ownCurrentTimeMs(): number {\n const timeSource = determineCurrentTimeSource(\n this.playbackController,\n this.rootTimegroup,\n this.rootTimegroup === (this as any as EFTimegroup),\n this.#currentTimeMs,\n this.startTimeMs,\n this.durationMs,\n );\n return timeSource.timeMs;\n }\n\n /**\n * Element's current time for progress calculation.\n * Non-timegroup temporal elements use their local time (ownCurrentTimeMs)\n */\n get currentTimeMs() {\n return this.ownCurrentTimeMs;\n }\n\n set currentTimeMs(value: number) {\n const role = determineTemporalRole(this.parentTimegroup);\n\n // Apply current time based on role\n switch (role) {\n case \"root\":\n if (this.playbackController) {\n this.playbackController.currentTime = value / 1000;\n } else {\n this.#currentTimeMs = value;\n this.requestUpdate(\"currentTimeMs\");\n }\n break;\n case \"child\":\n if (\n this.rootTimegroup &&\n this.rootTimegroup !== (this as any as EFTimegroup)\n ) {\n this.rootTimegroup.currentTimeMs = value;\n } else {\n this.#currentTimeMs = value;\n this.requestUpdate(\"currentTimeMs\");\n }\n break;\n }\n }\n\n /**\n * Used to calculate the internal currentTimeMs of the element. This is useful\n * for mapping to internal media time codes for audio/video elements.\n */\n get currentSourceTimeMs() {\n const leadingTrimMs = this.sourceInMs || this.trimStartMs || 0;\n return this.ownCurrentTimeMs + leadingTrimMs;\n }\n\n /**\n * @deprecated Use FrameRenderable interface via FrameController instead.\n * This is a compatibility wrapper - base class just waits for updateComplete.\n */\n #frameTaskPromise: Promise<void> = Promise.resolve();\n \n frameTask = (() => {\n const self = this;\n const taskObj: { run(): void | Promise<void>; taskComplete: Promise<void> } = {\n run: () => {\n self.#frameTaskPromise = self.updateComplete.then(() => {});\n taskObj.taskComplete = self.#frameTaskPromise;\n return self.#frameTaskPromise;\n },\n taskComplete: Promise.resolve(),\n };\n return taskObj;\n })();\n\n didBecomeRoot() {\n // Never create PlaybackController for render clones\n // Render clones need direct time control via seekForRender\n if ((this as any).closest?.('.ef-render-clone-container')) {\n return;\n }\n \n if (!this.playbackController) {\n this.playbackController = new PlaybackController(this as any);\n if (this.#loop) {\n this.playbackController.setLoop(this.#loop);\n }\n }\n }\n\n didBecomeChild() {\n if (this.playbackController) {\n this.playbackController.remove();\n this.playbackController = undefined;\n }\n }\n }\n\n Object.defineProperty(TemporalMixinClass.prototype, EF_TEMPORAL, {\n value: true,\n });\n\n return TemporalMixinClass as unknown as Constructor<TemporalMixinInterface> &\n T;\n};\n"],"mappings":"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"}
@@ -1,15 +1,15 @@
1
1
  import { TemporalMixinInterface } from "./EFTemporal.js";
2
2
  import { EFTextSegment } from "./EFTextSegment.js";
3
- import * as lit4 from "lit";
3
+ import * as lit8 from "lit";
4
4
  import { LitElement, PropertyValueMap } from "lit";
5
- import * as lit_html4 from "lit-html";
5
+ import * as lit_html9 from "lit-html";
6
6
 
7
7
  //#region src/elements/EFText.d.ts
8
8
  type SplitMode = "line" | "word" | "char";
9
9
  declare const EFText_base: (new (...args: any[]) => TemporalMixinInterface) & typeof LitElement;
10
10
  declare class EFText extends EFText_base {
11
11
  #private;
12
- static styles: lit4.CSSResult[];
12
+ static styles: lit8.CSSResult[];
13
13
  split: SplitMode;
14
14
  private validateSplit;
15
15
  staggerMs?: number;
@@ -20,7 +20,7 @@ declare class EFText extends EFText_base {
20
20
  private _textContent;
21
21
  private _templateElement;
22
22
  private _segmentsReadyResolvers;
23
- render(): lit_html4.TemplateResult<1>;
23
+ render(): lit_html9.TemplateResult<1>;
24
24
  set textContent(value: string | null);
25
25
  get textContent(): string;
26
26
  /**
@@ -1,12 +1,12 @@
1
1
  import { TemporalMixinInterface } from "./EFTemporal.js";
2
- import * as lit5 from "lit";
2
+ import * as lit9 from "lit";
3
3
  import { LitElement } from "lit";
4
- import * as lit_html5 from "lit-html";
4
+ import * as lit_html10 from "lit-html";
5
5
 
6
6
  //#region src/elements/EFTextSegment.d.ts
7
7
  declare const EFTextSegment_base: (new (...args: any[]) => TemporalMixinInterface) & typeof LitElement;
8
8
  declare class EFTextSegment extends EFTextSegment_base {
9
- static styles: lit5.CSSResult[];
9
+ static styles: lit9.CSSResult[];
10
10
  /**
11
11
  * Registers animation styles that should be shared across all text segments.
12
12
  * This is the correct way to inject animation styles for segments - not via innerHTML.
@@ -32,7 +32,7 @@ declare class EFTextSegment extends EFTextSegment_base {
32
32
  * @param id The identifier of the stylesheet to remove
33
33
  */
34
34
  static unregisterAnimations(id: string): void;
35
- render(): lit_html5.TemplateResult<1>;
35
+ render(): lit_html10.TemplateResult<1>;
36
36
  private setCSSVariables;
37
37
  protected firstUpdated(): void;
38
38
  protected updated(): void;
@@ -1,13 +1,13 @@
1
1
  import { EFVideo } from "./EFVideo.js";
2
2
  import { EFTimegroup } from "./EFTimegroup.js";
3
- import * as lit27 from "lit";
3
+ import * as lit29 from "lit";
4
4
  import { LitElement } from "lit";
5
- import * as lit_html26 from "lit-html";
5
+ import * as lit_html28 from "lit-html";
6
6
  import * as lit_html_directives_ref4 from "lit-html/directives/ref";
7
7
 
8
8
  //#region src/elements/EFThumbnailStrip.d.ts
9
9
  declare class EFThumbnailStrip extends LitElement {
10
- static styles: lit27.CSSResult[];
10
+ static styles: lit29.CSSResult[];
11
11
  canvasRef: lit_html_directives_ref4.Ref<HTMLCanvasElement>;
12
12
  target: string;
13
13
  thumbnailWidth: number;
@@ -155,7 +155,7 @@ declare class EFThumbnailStrip extends LitElement {
155
155
  * Invalidate all cached thumbnails for this element.
156
156
  */
157
157
  invalidateAll(): void;
158
- render(): lit_html26.TemplateResult<1>;
158
+ render(): lit_html28.TemplateResult<1>;
159
159
  }
160
160
  declare global {
161
161
  interface HTMLElementTagNameMap {
@@ -1,4 +1,4 @@
1
- import { FrameController } from "../preview/FrameController.js";
1
+ import { FrameController, FrameRenderable, FrameState } from "../preview/FrameController.js";
2
2
  import { TemporalMixinInterface } from "./EFTemporal.js";
3
3
  import { EFMedia } from "./EFMedia.js";
4
4
  import { ContainerInfo } from "./ContainerInfo.js";
@@ -53,7 +53,7 @@ type TimegroupInitializer = (timegroup: EFTimegroup) => void;
53
53
  */
54
54
  type TimeMode = "fit" | "fixed" | "sequence" | "contain";
55
55
  declare const EFTimegroup_base: (new (...args: any[]) => TemporalMixinInterface) & typeof LitElement;
56
- declare class EFTimegroup extends EFTimegroup_base {
56
+ declare class EFTimegroup extends EFTimegroup_base implements FrameRenderable {
57
57
  #private;
58
58
  static get observedAttributes(): string[];
59
59
  static styles: lit0.CSSResult;
@@ -67,27 +67,42 @@ declare class EFTimegroup extends EFTimegroup_base {
67
67
  overlapMs: number;
68
68
  /**
69
69
  * Initializer function for setting up JavaScript behavior on this timegroup.
70
- * This function is called on both the prime timeline and each render clone.
70
+ * This function is called ONCE per instance - on the prime timeline when first connected,
71
+ * and on each render clone when created.
71
72
  *
72
- * REQUIRED for render operations (captureBatch, renderToVideo, createRenderClone).
73
+ * Use this to register frame callbacks, set up event listeners, or initialize state.
74
+ * The same initializer code runs on both prime and clones, eliminating duplication.
73
75
  *
74
76
  * CONSTRAINTS:
75
77
  * - MUST be synchronous (no async/await, no Promise return)
76
78
  * - MUST complete in <100ms (error thrown) or <10ms (warning logged)
77
79
  * - Should only register callbacks and set up behavior, not do expensive work
78
80
  *
81
+ * TIMING:
82
+ * - If set before element connects to DOM: runs automatically after connectedCallback
83
+ * - If set after element is connected: runs immediately
84
+ * - Clones automatically copy and run the initializer when created
85
+ *
79
86
  * @example
80
87
  * ```javascript
81
88
  * const tg = document.querySelector('ef-timegroup');
82
89
  * tg.initializer = (instance) => {
83
- * instance.addFrameCallback((time) => {
90
+ * // Runs once on prime timeline, once on each clone
91
+ * instance.addFrameTask((info) => {
84
92
  * // Update content based on time
85
93
  * });
86
94
  * };
87
95
  * ```
88
96
  * @public
89
97
  */
90
- initializer?: TimegroupInitializer;
98
+ get initializer(): TimegroupInitializer | undefined;
99
+ set initializer(fn: TimegroupInitializer | undefined);
100
+ /**
101
+ * Public accessor for initializer completion promise.
102
+ * Allows createRenderClone to wait for initializer before rendering.
103
+ * @internal
104
+ */
105
+ get initializerComplete(): Promise<void> | undefined;
91
106
  /** @public */
92
107
  fps: number;
93
108
  /**
@@ -115,6 +130,25 @@ declare class EFTimegroup extends EFTimegroup_base {
115
130
  * @public
116
131
  */
117
132
  get frameController(): FrameController;
133
+ /**
134
+ * Query timegroup's readiness state for a given time.
135
+ * Timegroups are always ready (no async preparation needed).
136
+ * @public
137
+ */
138
+ getFrameState(_timeMs: number): FrameState;
139
+ /**
140
+ * Async preparation phase (no-op for timegroups).
141
+ * Timegroups don't need preparation - they just coordinate child rendering.
142
+ * @public
143
+ */
144
+ prepareFrame(_timeMs: number, _signal: AbortSignal): Promise<void>;
145
+ /**
146
+ * Synchronous render phase - executes custom frame callbacks.
147
+ * Called by FrameController after all preparation is complete.
148
+ * Kicks off async frame callbacks without blocking (they run in background).
149
+ * @public
150
+ */
151
+ renderFrame(_timeMs: number): void;
118
152
  /**
119
153
  * Get the effective FPS for this timegroup.
120
154
  * During rendering, uses the render options FPS if available.
@@ -8,7 +8,7 @@ import { efContext } from "../gui/efContext.js";
8
8
  import { isContextMixin } from "../gui/ContextMixin.js";
9
9
  import { TWMixin } from "../gui/TWMixin2.js";
10
10
  import { isTracingEnabled, withSpan } from "../otel/tracingHelpers.js";
11
- import { FrameController } from "../preview/FrameController.js";
11
+ import { FrameController, PRIORITY_DEFAULT } from "../preview/FrameController.js";
12
12
  import { renderTemporalAudio } from "./renderTemporalAudio.js";
13
13
  import { EFTargetable } from "./TargetController.js";
14
14
  import { deepGetMediaElements } from "./EFMedia.js";
@@ -251,6 +251,65 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
251
251
  }
252
252
  `;
253
253
  }
254
+ #initializer;
255
+ /**
256
+ * Initializer function for setting up JavaScript behavior on this timegroup.
257
+ * This function is called ONCE per instance - on the prime timeline when first connected,
258
+ * and on each render clone when created.
259
+ *
260
+ * Use this to register frame callbacks, set up event listeners, or initialize state.
261
+ * The same initializer code runs on both prime and clones, eliminating duplication.
262
+ *
263
+ * CONSTRAINTS:
264
+ * - MUST be synchronous (no async/await, no Promise return)
265
+ * - MUST complete in <100ms (error thrown) or <10ms (warning logged)
266
+ * - Should only register callbacks and set up behavior, not do expensive work
267
+ *
268
+ * TIMING:
269
+ * - If set before element connects to DOM: runs automatically after connectedCallback
270
+ * - If set after element is connected: runs immediately
271
+ * - Clones automatically copy and run the initializer when created
272
+ *
273
+ * @example
274
+ * ```javascript
275
+ * const tg = document.querySelector('ef-timegroup');
276
+ * tg.initializer = (instance) => {
277
+ * // Runs once on prime timeline, once on each clone
278
+ * instance.addFrameTask((info) => {
279
+ * // Update content based on time
280
+ * });
281
+ * };
282
+ * ```
283
+ * @public
284
+ */
285
+ get initializer() {
286
+ return this.#initializer;
287
+ }
288
+ set initializer(fn) {
289
+ this.#initializer = fn;
290
+ if (fn && this.isConnected && !this.#initializerHasRun) this.#initializerComplete = this.updateComplete.then(() => {
291
+ this.#runInitializer();
292
+ });
293
+ }
294
+ /**
295
+ * Track if initializer has run on this instance to prevent double execution.
296
+ * @internal
297
+ */
298
+ #initializerHasRun = false;
299
+ /**
300
+ * Promise that resolves when initializer completes.
301
+ * Used by createRenderClone to ensure frame tasks are registered before rendering.
302
+ * @internal
303
+ */
304
+ #initializerComplete;
305
+ /**
306
+ * Public accessor for initializer completion promise.
307
+ * Allows createRenderClone to wait for initializer before rendering.
308
+ * @internal
309
+ */
310
+ get initializerComplete() {
311
+ return this.#initializerComplete;
312
+ }
254
313
  attributeChangedCallback(name, old, value) {
255
314
  if (name === "mode" && value) this.mode = value;
256
315
  if (name === "overlap" && value) this.overlapMs = parseTimeToMs(value);
@@ -293,6 +352,35 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
293
352
  return this.#frameController;
294
353
  }
295
354
  /**
355
+ * Query timegroup's readiness state for a given time.
356
+ * Timegroups are always ready (no async preparation needed).
357
+ * @public
358
+ */
359
+ getFrameState(_timeMs) {
360
+ return {
361
+ needsPreparation: false,
362
+ isReady: true,
363
+ priority: PRIORITY_DEFAULT
364
+ };
365
+ }
366
+ /**
367
+ * Async preparation phase (no-op for timegroups).
368
+ * Timegroups don't need preparation - they just coordinate child rendering.
369
+ * @public
370
+ */
371
+ async prepareFrame(_timeMs, _signal) {}
372
+ /**
373
+ * Synchronous render phase - executes custom frame callbacks.
374
+ * Called by FrameController after all preparation is complete.
375
+ * Kicks off async frame callbacks without blocking (they run in background).
376
+ * @public
377
+ */
378
+ renderFrame(_timeMs) {
379
+ if (this.#customFrameTasks.size > 0) this.#executeCustomFrameTasks().catch((error) => {
380
+ console.error("EFTimegroup custom frame task error:", error);
381
+ });
382
+ }
383
+ /**
296
384
  * Get the effective FPS for this timegroup.
297
385
  * During rendering, uses the render options FPS if available.
298
386
  * Otherwise uses the configured fps property.
@@ -566,6 +654,9 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
566
654
  }
567
655
  connectedCallback() {
568
656
  super.connectedCallback();
657
+ this.updateComplete.then(() => {
658
+ this.#runInitializer();
659
+ });
569
660
  requestAnimationFrame(() => {
570
661
  requestAnimationFrame(() => {
571
662
  if (this.parentTimegroup) new TimegroupController(this.parentTimegroup, this);
@@ -689,15 +780,16 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
689
780
  }
690
781
  /**
691
782
  * Runs the initializer function with validation for synchronous execution and time budget.
692
- * @throws Error if no initializer is set
783
+ * Only runs once per instance. Safe to call multiple times - will skip if already run.
693
784
  * @throws Error if initializer returns a Promise (async not allowed)
694
785
  * @throws Error if initializer takes more than INITIALIZER_ERROR_THRESHOLD_MS
695
786
  * @internal
696
787
  */
697
- #runInitializer(cloneEl) {
698
- if (!this.initializer) return;
788
+ #runInitializer() {
789
+ if (!this.initializer || this.#initializerHasRun) return;
790
+ this.#initializerHasRun = true;
699
791
  const startTime = performance.now();
700
- const result = this.initializer(cloneEl);
792
+ const result = this.initializer(this);
701
793
  const elapsed = performance.now() - startTime;
702
794
  if (result !== void 0 && result !== null && typeof result.then === "function") throw new Error("Timeline initializer must be synchronous. Do not return a Promise from the initializer function.");
703
795
  if (elapsed > INITIALIZER_ERROR_THRESHOLD_MS) throw new Error(`Timeline initializer took ${elapsed.toFixed(1)}ms, exceeding the ${INITIALIZER_ERROR_THRESHOLD_MS}ms limit. Initializers must be fast - move expensive work outside the initializer.`);
@@ -742,9 +834,10 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
742
834
  * so we must manually copy them to the cloned elements.
743
835
  * @internal
744
836
  */
745
- #copyTextSegmentData(original, clone) {
837
+ async #copyTextSegmentData(original, clone) {
746
838
  const originalSegments = original.querySelectorAll("ef-text-segment");
747
839
  const cloneSegments = clone.querySelectorAll("ef-text-segment");
840
+ const updatePromises = [];
748
841
  for (let i = 0; i < originalSegments.length && i < cloneSegments.length; i++) {
749
842
  const origSeg = originalSegments[i];
750
843
  const cloneSeg = cloneSegments[i];
@@ -753,7 +846,9 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
753
846
  if (origSeg.staggerOffsetMs !== void 0) cloneSeg.staggerOffsetMs = origSeg.staggerOffsetMs;
754
847
  if (origSeg.segmentStartMs !== void 0) cloneSeg.segmentStartMs = origSeg.segmentStartMs;
755
848
  if (origSeg.segmentEndMs !== void 0) cloneSeg.segmentEndMs = origSeg.segmentEndMs;
849
+ if (cloneSeg.updateComplete) updatePromises.push(cloneSeg.updateComplete);
756
850
  }
851
+ await Promise.all(updatePromises);
757
852
  }
758
853
  /**
759
854
  * Wait for all ef-captions elements to have their data loaded.
@@ -773,6 +868,29 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
773
868
  if (waitPromises.length > 0) await Promise.all(waitPromises);
774
869
  }
775
870
  /**
871
+ * Copies initializers from original timegroup tree to cloned timegroup tree.
872
+ * Handles both the root timegroup and all nested timegroups recursively.
873
+ * @internal
874
+ */
875
+ async #copyInitializersToClone(original, clone) {
876
+ const initializerPromises = [];
877
+ if (original.initializer) {
878
+ clone.initializer = original.initializer;
879
+ if (clone.initializerComplete) initializerPromises.push(clone.initializerComplete);
880
+ }
881
+ const originalNested = Array.from(original.querySelectorAll("ef-timegroup"));
882
+ const cloneNested = Array.from(clone.querySelectorAll("ef-timegroup"));
883
+ for (let i = 0; i < originalNested.length && i < cloneNested.length; i++) {
884
+ const origNested = originalNested[i];
885
+ const cloneNestedItem = cloneNested[i];
886
+ if (origNested.initializer) {
887
+ cloneNestedItem.initializer = origNested.initializer;
888
+ if (cloneNestedItem.initializerComplete) initializerPromises.push(cloneNestedItem.initializerComplete);
889
+ }
890
+ }
891
+ if (initializerPromises.length > 0) await Promise.all(initializerPromises);
892
+ }
893
+ /**
776
894
  * Create an independent clone of this timegroup for rendering.
777
895
  * The clone is a fully functional ef-timegroup with its own animations
778
896
  * and time state, isolated from the original (Prime-timeline).
@@ -813,8 +931,8 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
813
931
  } else container.appendChild(cloneEl);
814
932
  document.body.appendChild(container);
815
933
  await cloneEl.updateComplete;
816
- this.#copyTextSegmentData(this, cloneEl);
817
- this.#runInitializer(cloneEl);
934
+ await this.#copyInitializersToClone(this, cloneEl);
935
+ await this.#copyTextSegmentData(this, cloneEl);
818
936
  let actualClone = container.querySelector("ef-timegroup");
819
937
  if (!actualClone) throw new Error("No ef-timegroup found after initializer. Ensure your initializer renders a Timegroup (React) or does not remove the cloned element (vanilla JS).");
820
938
  await customElements.whenDefined("ef-timegroup");
@@ -822,6 +940,7 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
822
940
  actualClone = container.querySelector("ef-timegroup");
823
941
  if (!actualClone) throw new Error("ef-timegroup element lost after upgrade");
824
942
  await actualClone.updateComplete;
943
+ await this.#copyTextSegmentData(this, actualClone);
825
944
  const setupParentChildRelationships = (parent, root) => {
826
945
  for (const child of parent.children) if (child.tagName === "EF-TIMEGROUP") {
827
946
  const childTG = child;