@editframe/elements 0.30.0-beta.14 → 0.30.2-beta.0

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.
@@ -2,7 +2,7 @@ import { MediaEngine } from "../transcoding/types/index.js";
2
2
  import { EFMedia } from "./EFMedia.js";
3
3
  import * as _lit_task8 from "@lit/task";
4
4
  import { Task } from "@lit/task";
5
- import * as lit_html1 from "lit-html";
5
+ import * as lit_html6 from "lit-html";
6
6
  import * as lit_html_directives_ref_js1 from "lit-html/directives/ref.js";
7
7
 
8
8
  //#region src/elements/EFAudio.d.ts
@@ -10,7 +10,7 @@ declare const EFAudio_base: typeof EFMedia;
10
10
  declare class EFAudio extends EFAudio_base {
11
11
  private _propertyHack;
12
12
  audioElementRef: lit_html_directives_ref_js1.Ref<HTMLAudioElement>;
13
- render(): lit_html1.TemplateResult<1>;
13
+ render(): lit_html6.TemplateResult<1>;
14
14
  frameTask: Task<readonly [_lit_task8.TaskStatus, _lit_task8.TaskStatus, _lit_task8.TaskStatus, _lit_task8.TaskStatus], void>;
15
15
  /**
16
16
  * Legacy getter for fragment index task (maps to audioSegmentIdTask)
@@ -4,9 +4,9 @@ import { FetchMixinInterface } from "./FetchMixin.js";
4
4
  import { EFAudio } from "./EFAudio.js";
5
5
  import { EFVideo } from "./EFVideo.js";
6
6
  import { Task, TaskStatus } from "@lit/task";
7
- import * as lit3 from "lit";
7
+ import * as lit1 from "lit";
8
8
  import { LitElement, PropertyValueMap } from "lit";
9
- import * as lit_html3 from "lit-html";
9
+ import * as lit_html1 from "lit-html";
10
10
 
11
11
  //#region src/elements/EFCaptions.d.ts
12
12
  interface WordSegment {
@@ -25,8 +25,8 @@ interface Caption {
25
25
  }
26
26
  declare const EFCaptionsActiveWord_base: (new (...args: any[]) => TemporalMixinInterface) & typeof LitElement;
27
27
  declare class EFCaptionsActiveWord extends EFCaptionsActiveWord_base {
28
- static styles: lit3.CSSResult[];
29
- render(): lit_html3.TemplateResult<1> | undefined;
28
+ static styles: lit1.CSSResult[];
29
+ render(): lit_html1.TemplateResult<1> | undefined;
30
30
  wordStartMs: number;
31
31
  wordEndMs: number;
32
32
  wordText: string;
@@ -38,8 +38,8 @@ declare class EFCaptionsActiveWord extends EFCaptionsActiveWord_base {
38
38
  }
39
39
  declare const EFCaptionsSegment_base: (new (...args: any[]) => TemporalMixinInterface) & typeof LitElement;
40
40
  declare class EFCaptionsSegment extends EFCaptionsSegment_base {
41
- static styles: lit3.CSSResult[];
42
- render(): lit_html3.TemplateResult<1> | undefined;
41
+ static styles: lit1.CSSResult[];
42
+ render(): lit_html1.TemplateResult<1> | undefined;
43
43
  segmentStartMs: number;
44
44
  segmentEndMs: number;
45
45
  segmentText: string;
@@ -49,8 +49,8 @@ declare class EFCaptionsSegment extends EFCaptionsSegment_base {
49
49
  get durationMs(): number;
50
50
  }
51
51
  declare class EFCaptionsBeforeActiveWord extends EFCaptionsSegment {
52
- static styles: lit3.CSSResult[];
53
- render(): lit_html3.TemplateResult<1> | undefined;
52
+ static styles: lit1.CSSResult[];
53
+ render(): lit_html1.TemplateResult<1> | undefined;
54
54
  hidden: boolean;
55
55
  segmentText: string;
56
56
  segmentStartMs: number;
@@ -60,8 +60,8 @@ declare class EFCaptionsBeforeActiveWord extends EFCaptionsSegment {
60
60
  get durationMs(): number;
61
61
  }
62
62
  declare class EFCaptionsAfterActiveWord extends EFCaptionsSegment {
63
- static styles: lit3.CSSResult[];
64
- render(): lit_html3.TemplateResult<1> | undefined;
63
+ static styles: lit1.CSSResult[];
64
+ render(): lit_html1.TemplateResult<1> | undefined;
65
65
  hidden: boolean;
66
66
  segmentText: string;
67
67
  segmentStartMs: number;
@@ -72,7 +72,7 @@ declare class EFCaptionsAfterActiveWord extends EFCaptionsSegment {
72
72
  }
73
73
  declare const EFCaptions_base: (new (...args: any[]) => EFSourceMixinInterface) & (new (...args: any[]) => TemporalMixinInterface) & (new (...args: any[]) => FetchMixinInterface) & typeof LitElement;
74
74
  declare class EFCaptions extends EFCaptions_base {
75
- static styles: lit3.CSSResult[];
75
+ static styles: lit1.CSSResult[];
76
76
  targetSelector: string;
77
77
  set target(value: string);
78
78
  wordStyle: string;
@@ -95,7 +95,7 @@ declare class EFCaptions extends EFCaptions_base {
95
95
  segmentContainers: HTMLCollectionOf<EFCaptionsSegment>;
96
96
  beforeActiveWordContainers: HTMLCollectionOf<EFCaptionsBeforeActiveWord>;
97
97
  afterActiveWordContainers: HTMLCollectionOf<EFCaptionsAfterActiveWord>;
98
- render(): lit_html3.TemplateResult<1>;
98
+ render(): lit_html1.TemplateResult<1>;
99
99
  transcriptionsPath(): string | null;
100
100
  captionsPath(): string | null;
101
101
  protected md5SumLoader: Task<readonly [string, typeof fetch], string | null | undefined>;
@@ -9,7 +9,7 @@ import { AudioBufferState } from "./EFMedia/audioTasks/makeAudioBufferTask.js";
9
9
  import { ControllableInterface } from "../gui/Controllable.js";
10
10
  import { UrlGenerator } from "../transcoding/utils/UrlGenerator.js";
11
11
  import * as _lit_task0 from "@lit/task";
12
- import * as lit1 from "lit";
12
+ import * as lit6 from "lit";
13
13
  import { LitElement, PropertyValueMap } from "lit";
14
14
  import * as mediabunny0 from "mediabunny";
15
15
 
@@ -23,7 +23,7 @@ declare class EFMedia extends EFMedia_base {
23
23
  static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;
24
24
  static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;
25
25
  static get observedAttributes(): string[];
26
- static styles: lit1.CSSResult[];
26
+ static styles: lit6.CSSResult[];
27
27
  /**
28
28
  * Duration in milliseconds for audio buffering ahead of current time
29
29
  * @domAttribute "audio-buffer-duration"
@@ -1,14 +1,14 @@
1
1
  import { TemporalMixinInterface } from "./EFTemporal.js";
2
2
  import { EFTextSegment } from "./EFTextSegment.js";
3
- import * as lit8 from "lit";
3
+ import * as lit9 from "lit";
4
4
  import { LitElement, PropertyValueMap } from "lit";
5
- import * as lit_html8 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
- static styles: lit8.CSSResult[];
11
+ static styles: lit9.CSSResult[];
12
12
  split: SplitMode;
13
13
  private validateSplit;
14
14
  staggerMs?: number;
@@ -19,7 +19,7 @@ declare class EFText extends EFText_base {
19
19
  private _textContent;
20
20
  private _templateElement;
21
21
  private _segmentsReadyResolvers;
22
- render(): lit_html8.TemplateResult<1>;
22
+ render(): lit_html9.TemplateResult<1>;
23
23
  set textContent(value: string | null);
24
24
  get textContent(): string;
25
25
  /**
@@ -258,45 +258,26 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
258
258
  });
259
259
  }
260
260
  splitTextIntoSegments(text) {
261
+ const trimmedText = text.trim();
262
+ if (!trimmedText) return [];
261
263
  switch (this.split) {
262
- case "line": return text.split(/\r?\n/).filter((line) => line.length > 0);
264
+ case "line": return trimmedText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
263
265
  case "word": {
264
- const words = [];
265
- const parts = text.split(/(\S+)/);
266
- for (const part of parts) if (part.trim().length > 0) words.push(part);
267
- else if (part.length > 0) {
268
- if (words.length > 0) words[words.length - 1] += part;
269
- }
270
- return words.length > 0 ? words : [text];
266
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "word" });
267
+ return Array.from(segmenter.segment(trimmedText)).filter((seg) => seg.isWordLike).map((seg) => seg.segment);
271
268
  }
272
- case "char": return Array.from(text);
273
- default: return [text];
269
+ case "char": {
270
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
271
+ return Array.from(segmenter.segment(trimmedText)).map((seg) => seg.segment);
272
+ }
273
+ default: return [trimmedText];
274
274
  }
275
275
  }
276
276
  get intrinsicDurationMs() {
277
277
  if (this.hasExplicitDuration) return;
278
278
  const text = this._textContent !== null ? this._textContent : this.getTextContent();
279
279
  if (!text || text.trim().length === 0) return 0;
280
- let segmentCount = 1;
281
- switch (this.split) {
282
- case "line":
283
- segmentCount = text.split(/\r?\n/).filter((line) => line.trim().length > 0).length || 1;
284
- break;
285
- case "word": {
286
- const words = [];
287
- const parts = text.split(/(\S+)/);
288
- for (const part of parts) if (part.trim().length > 0) words.push(part);
289
- else if (part.length > 0) {
290
- if (words.length > 0) words[words.length - 1] += part;
291
- }
292
- segmentCount = words.length > 0 ? words.length : 1;
293
- break;
294
- }
295
- case "char":
296
- segmentCount = text.length || 1;
297
- break;
298
- }
299
- return segmentCount * 1e3;
280
+ return (this.splitTextIntoSegments(text).length || 1) * 1e3;
300
281
  }
301
282
  };
302
283
  __decorate([property({
@@ -1 +1 @@
1
- {"version":3,"file":"EFText.js","names":["EFText","textNodes: ChildNode[]","staggerOffset: number | undefined","words: string[]"],"sources":["../../src/elements/EFText.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport { evaluateEasing } from \"./easingUtils.js\";\nimport type { EFTextSegment } from \"./EFTextSegment.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\n\nexport type SplitMode = \"line\" | \"word\" | \"char\";\n\n@customElement(\"ef-text\")\nexport class EFText extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-flex;\n white-space: normal;\n line-height: 1;\n gap: 0;\n }\n :host([split=\"char\"]) {\n white-space: pre;\n }\n :host([split=\"line\"]) {\n display: flex;\n flex-direction: column;\n }\n ::slotted(*) {\n display: inline-block;\n margin: 0;\n padding: 0;\n }\n `,\n ];\n\n @property({ type: String, reflect: true })\n split: SplitMode = \"word\";\n\n private validateSplit(value: string): SplitMode {\n if (value === \"line\" || value === \"word\" || value === \"char\") {\n return value as SplitMode;\n }\n console.warn(\n `Invalid split value \"${value}\". Must be \"line\", \"word\", or \"char\". Defaulting to \"word\".`,\n );\n return \"word\";\n }\n\n @property({\n type: Number,\n attribute: \"stagger\",\n converter: durationConverter,\n })\n staggerMs?: number;\n\n private validateStagger(value: number | undefined): number | undefined {\n if (value === undefined) return undefined;\n if (value < 0) {\n console.warn(`Invalid stagger value ${value}ms. Must be >= 0. Using 0.`);\n return 0;\n }\n return value;\n }\n\n @property({ type: String, reflect: true })\n easing = \"linear\";\n\n private mutationObserver?: MutationObserver;\n private lastTextContent = \"\";\n private _textContent: string | null = null; // null means not initialized, \"\" means explicitly empty\n private _templateElement: HTMLTemplateElement | null = null;\n private _segmentsReadyResolvers: Array<() => void> = [];\n\n render() {\n return html`<slot></slot>`;\n }\n\n // Store text content so we can use it even after DOM is cleared\n set textContent(value: string | null) {\n const newValue = value || \"\";\n // Only update if value actually changed\n if (this._textContent !== newValue) {\n this._textContent = newValue;\n\n // Find template element if not already stored\n if (!this._templateElement && this.isConnected) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Clear any existing text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n // Add new text node if value is not empty\n if (newValue) {\n const textNode = document.createTextNode(newValue);\n this.appendChild(textNode);\n }\n // Trigger re-split\n if (this.isConnected) {\n this.splitText();\n }\n }\n }\n\n get textContent(): string {\n // If _textContent is null, it hasn't been initialized - read from DOM\n if (this._textContent === null) {\n return this.getTextContent();\n }\n // Otherwise use stored value (even if empty string)\n return this._textContent;\n }\n\n /**\n * Get all ef-text-segment elements directly\n * @public\n */\n get segments(): EFTextSegment[] {\n return Array.from(\n this.querySelectorAll(\"ef-text-segment[data-segment-created]\"),\n ) as EFTextSegment[];\n }\n\n /**\n * Returns a promise that resolves when segments are ready (created and connected)\n * Use this to wait for segments after setting textContent or other properties\n * @public\n */\n async whenSegmentsReady(): Promise<EFTextSegment[]> {\n // Wait for text element to be updated first\n await this.updateComplete;\n\n // If no text content, segments will be empty - return immediately\n // Use same logic as splitText to read text content\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return [];\n }\n\n // Wait a frame for splitText to run\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // If segments already exist and are connected, wait for updates\n let segments = this.segments;\n if (segments.length > 0) {\n // Wait for all segments to be updated\n await Promise.all(segments.map((seg) => seg.updateComplete));\n // Wait one more frame to ensure connectedCallback has run and properties are set\n await new Promise((resolve) => requestAnimationFrame(resolve));\n // Wait one more frame to ensure properties are fully processed\n await new Promise((resolve) => requestAnimationFrame(resolve));\n return this.segments;\n }\n\n // Otherwise, wait for segments to be created (with timeout)\n return new Promise<EFTextSegment[]>((resolve) => {\n let attempts = 0;\n const maxAttempts = 100; // 100 frames = ~1.6 seconds at 60fps\n\n const checkSegments = () => {\n segments = this.segments;\n if (segments.length > 0) {\n // Wait for all segments to be updated\n Promise.all(segments.map((seg) => seg.updateComplete)).then(() => {\n // Wait frames to ensure connectedCallback has run and properties are set\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n resolve(this.segments);\n });\n });\n });\n } else if (attempts < maxAttempts) {\n attempts++;\n requestAnimationFrame(checkSegments);\n } else {\n // Timeout - return empty array (might be empty text)\n resolve([]);\n }\n };\n checkSegments();\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n // Find and store template element before any modifications\n this._templateElement = this.querySelector(\"template\");\n\n // Initialize _textContent from DOM if not already set (for declarative usage)\n if (this._textContent === null) {\n this._textContent = this.getTextContent();\n this.lastTextContent = this._textContent;\n }\n\n // Use requestAnimationFrame to ensure DOM is ready\n requestAnimationFrame(() => {\n this.setupMutationObserver();\n this.splitText();\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.mutationObserver?.disconnect();\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n if (\n changedProperties.has(\"split\") ||\n changedProperties.has(\"staggerMs\") ||\n changedProperties.has(\"easing\") ||\n changedProperties.has(\"durationMs\")\n ) {\n this.splitText();\n }\n }\n\n private setupMutationObserver() {\n this.mutationObserver = new MutationObserver(() => {\n // Only react to changes that aren't from our own segment creation\n const currentText = this._textContent || this.getTextContent();\n if (currentText !== this.lastTextContent) {\n this._textContent = currentText;\n this.lastTextContent = currentText;\n this.splitText();\n }\n });\n\n this.mutationObserver.observe(this, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n\n private getTextContent(): string {\n // Get text content, handling both text nodes and HTML content\n let text = \"\";\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent || \"\";\n } else if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Skip ef-text-segment elements (they're created by us)\n if (element.tagName === \"EF-TEXT-SEGMENT\") {\n continue;\n }\n // Skip template elements (they're templates, not content)\n if (element.tagName === \"TEMPLATE\") {\n continue;\n }\n text += element.textContent || \"\";\n }\n }\n return text;\n }\n\n private splitText() {\n // Validate split mode\n const validatedSplit = this.validateSplit(this.split);\n if (validatedSplit !== this.split) {\n this.split = validatedSplit;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Validate stagger\n const validatedStagger = this.validateStagger(this.staggerMs);\n if (validatedStagger !== this.staggerMs) {\n this.staggerMs = validatedStagger;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Read text content - use stored _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n // Clear segments if no text\n const existingSegments = Array.from(\n this.querySelectorAll(\"ef-text-segment\"),\n );\n for (const segment of existingSegments) {\n segment.remove();\n }\n // Clear text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n this.lastTextContent = \"\";\n // Resolve any waiting promises\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n return;\n }\n\n const segments = this.splitTextIntoSegments(text);\n const durationMs = this.durationMs || 1000; // Default 1 second if no duration\n\n // Clear ALL child nodes (text nodes and segments) by replacing innerHTML\n // This ensures we don't have any leftover text nodes\n const fragment = document.createDocumentFragment();\n\n // Find template element if not already stored\n if (!this._templateElement) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Get template content structure\n // If template exists, clone it; otherwise create default ef-text-segment\n const templateContent = this._templateElement?.content;\n const templateSegments = templateContent\n ? Array.from(templateContent.querySelectorAll(\"ef-text-segment\"))\n : [];\n\n // If no template segments found, we'll create a default one\n const useTemplate = templateSegments.length > 0;\n const segmentsPerTextSegment = useTemplate ? templateSegments.length : 1;\n\n // Create new segments in a fragment first\n segments.forEach((segmentText, textIndex) => {\n // Calculate stagger offset if stagger is set\n let staggerOffset: number | undefined;\n if (this.staggerMs !== undefined) {\n // Apply easing to the stagger offset\n // Normalize index to 0-1 range (0 for first segment, 1 for last segment)\n const totalSegments = segments.length;\n const normalizedProgress =\n totalSegments > 1 ? textIndex / (totalSegments - 1) : 0;\n\n // Apply easing function to get eased progress\n const easedProgress = evaluateEasing(this.easing, normalizedProgress);\n\n // Calculate total stagger duration (last segment gets full stagger)\n const totalStaggerDuration = (totalSegments - 1) * this.staggerMs;\n\n // Apply eased progress to total stagger duration\n staggerOffset = easedProgress * totalStaggerDuration;\n }\n\n if (useTemplate && templateContent) {\n // Clone template content for each text segment\n // This allows multiple ef-text-segment elements per character/word/line\n const clonedContent = templateContent.cloneNode(\n true,\n ) as DocumentFragment;\n const clonedSegments = Array.from(\n clonedContent.querySelectorAll(\"ef-text-segment\"),\n ) as EFTextSegment[];\n\n clonedSegments.forEach((segment, templateIndex) => {\n // Set properties - Lit will process these when element is connected\n segment.segmentText = segmentText;\n // Calculate segment index accounting for multiple segments per text segment\n segment.segmentIndex =\n textIndex * segmentsPerTextSegment + templateIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n fragment.appendChild(segment);\n });\n } else {\n // No template - create default ef-text-segment\n const segment = document.createElement(\n \"ef-text-segment\",\n ) as EFTextSegment;\n\n segment.segmentText = segmentText;\n segment.segmentIndex = textIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n fragment.appendChild(segment);\n }\n });\n\n // Ensure segments are connected to DOM before checking for animations\n // Append fragment first, then trigger updates\n\n // Replace all children with the fragment (this clears text nodes and old segments)\n // But preserve the template element if it exists\n const templateToPreserve = this._templateElement;\n while (this.firstChild) {\n const child = this.firstChild;\n // Don't remove the template element\n if (child === templateToPreserve) {\n // Skip template, but we need to move it after the fragment\n // So we'll remove it temporarily and re-add it after\n this.removeChild(child);\n continue;\n }\n this.removeChild(child);\n }\n this.appendChild(fragment);\n // Re-add template element if it existed\n if (templateToPreserve) {\n this.appendChild(templateToPreserve);\n }\n\n // Segments will pause their own animations in connectedCallback\n // Lit will automatically update them when they're connected to the DOM\n // Ensure segments are updated after being connected\n requestAnimationFrame(() => {\n const segmentElements = this.segments;\n Promise.all(segmentElements.map((seg) => seg.updateComplete)).then(() => {\n // Wait an additional frame to ensure animations are paused in connectedCallback\n // Then trigger updateAnimations to set correct state\n // This ensures animations are positioned correctly on first load\n requestAnimationFrame(() => {\n const rootTimegroup = this.rootTimegroup;\n if (rootTimegroup) {\n updateAnimations(rootTimegroup);\n } else {\n updateAnimations(this);\n }\n });\n });\n });\n\n this.lastTextContent = text;\n this._textContent = text;\n\n // Resolve any waiting promises after segments are connected\n requestAnimationFrame(() => {\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n });\n }\n\n private splitTextIntoSegments(text: string): string[] {\n switch (this.split) {\n case \"line\": {\n // Split on newlines\n const lines = text.split(/\\r?\\n/);\n // Filter out empty lines but keep the structure\n return lines.filter((line) => line.length > 0);\n }\n case \"word\": {\n // Split on whitespace boundaries, preserving spaces after words\n const words: string[] = [];\n // Split by word boundaries, but keep spaces after words\n const parts = text.split(/(\\S+)/);\n for (const part of parts) {\n if (part.trim().length > 0) {\n // This is a word - check if there's a space after it in the original text\n words.push(part);\n } else if (part.length > 0) {\n // This is whitespace - attach it to the previous word if exists, otherwise skip\n if (words.length > 0) {\n words[words.length - 1] += part;\n }\n }\n }\n // If no words found, return original text\n return words.length > 0 ? words : [text];\n }\n case \"char\": {\n // Split every character, preserving whitespace\n return Array.from(text);\n }\n default:\n return [text];\n }\n }\n\n get intrinsicDurationMs(): number | undefined {\n // If explicit duration is set, use it\n if (this.hasExplicitDuration) {\n return undefined; // Let explicit duration take precedence\n }\n\n // Otherwise, calculate from content\n // Use _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return 0;\n }\n\n // Default to 1 second per segment (can be overridden with explicit duration)\n // Use the same splitting logic as splitTextIntoSegments\n let segmentCount = 1;\n switch (this.split) {\n case \"line\": {\n const lines = text\n .split(/\\r?\\n/)\n .filter((line) => line.trim().length > 0);\n segmentCount = lines.length || 1;\n break;\n }\n case \"word\": {\n // Use same logic as splitTextIntoSegments for consistency\n const words: string[] = [];\n const parts = text.split(/(\\S+)/);\n for (const part of parts) {\n if (part.trim().length > 0) {\n words.push(part);\n } else if (part.length > 0) {\n if (words.length > 0) {\n words[words.length - 1] += part;\n }\n }\n }\n segmentCount = words.length > 0 ? words.length : 1;\n break;\n }\n case \"char\": {\n segmentCount = text.length || 1;\n break;\n }\n }\n\n return segmentCount * 1000;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text\": EFText;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EFText.js","names":["EFText","textNodes: ChildNode[]","staggerOffset: number | undefined"],"sources":["../../src/elements/EFText.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport { evaluateEasing } from \"./easingUtils.js\";\nimport type { EFTextSegment } from \"./EFTextSegment.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\n\nexport type SplitMode = \"line\" | \"word\" | \"char\";\n\n@customElement(\"ef-text\")\nexport class EFText extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-flex;\n white-space: normal;\n line-height: 1;\n gap: 0;\n }\n :host([split=\"char\"]) {\n white-space: pre;\n }\n :host([split=\"line\"]) {\n display: flex;\n flex-direction: column;\n }\n ::slotted(*) {\n display: inline-block;\n margin: 0;\n padding: 0;\n }\n `,\n ];\n\n @property({ type: String, reflect: true })\n split: SplitMode = \"word\";\n\n private validateSplit(value: string): SplitMode {\n if (value === \"line\" || value === \"word\" || value === \"char\") {\n return value as SplitMode;\n }\n console.warn(\n `Invalid split value \"${value}\". Must be \"line\", \"word\", or \"char\". Defaulting to \"word\".`,\n );\n return \"word\";\n }\n\n @property({\n type: Number,\n attribute: \"stagger\",\n converter: durationConverter,\n })\n staggerMs?: number;\n\n private validateStagger(value: number | undefined): number | undefined {\n if (value === undefined) return undefined;\n if (value < 0) {\n console.warn(`Invalid stagger value ${value}ms. Must be >= 0. Using 0.`);\n return 0;\n }\n return value;\n }\n\n @property({ type: String, reflect: true })\n easing = \"linear\";\n\n private mutationObserver?: MutationObserver;\n private lastTextContent = \"\";\n private _textContent: string | null = null; // null means not initialized, \"\" means explicitly empty\n private _templateElement: HTMLTemplateElement | null = null;\n private _segmentsReadyResolvers: Array<() => void> = [];\n\n render() {\n return html`<slot></slot>`;\n }\n\n // Store text content so we can use it even after DOM is cleared\n set textContent(value: string | null) {\n const newValue = value || \"\";\n // Only update if value actually changed\n if (this._textContent !== newValue) {\n this._textContent = newValue;\n\n // Find template element if not already stored\n if (!this._templateElement && this.isConnected) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Clear any existing text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n // Add new text node if value is not empty\n if (newValue) {\n const textNode = document.createTextNode(newValue);\n this.appendChild(textNode);\n }\n // Trigger re-split\n if (this.isConnected) {\n this.splitText();\n }\n }\n }\n\n get textContent(): string {\n // If _textContent is null, it hasn't been initialized - read from DOM\n if (this._textContent === null) {\n return this.getTextContent();\n }\n // Otherwise use stored value (even if empty string)\n return this._textContent;\n }\n\n /**\n * Get all ef-text-segment elements directly\n * @public\n */\n get segments(): EFTextSegment[] {\n return Array.from(\n this.querySelectorAll(\"ef-text-segment[data-segment-created]\"),\n ) as EFTextSegment[];\n }\n\n /**\n * Returns a promise that resolves when segments are ready (created and connected)\n * Use this to wait for segments after setting textContent or other properties\n * @public\n */\n async whenSegmentsReady(): Promise<EFTextSegment[]> {\n // Wait for text element to be updated first\n await this.updateComplete;\n\n // If no text content, segments will be empty - return immediately\n // Use same logic as splitText to read text content\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return [];\n }\n\n // Wait a frame for splitText to run\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // If segments already exist and are connected, wait for updates\n let segments = this.segments;\n if (segments.length > 0) {\n // Wait for all segments to be updated\n await Promise.all(segments.map((seg) => seg.updateComplete));\n // Wait one more frame to ensure connectedCallback has run and properties are set\n await new Promise((resolve) => requestAnimationFrame(resolve));\n // Wait one more frame to ensure properties are fully processed\n await new Promise((resolve) => requestAnimationFrame(resolve));\n return this.segments;\n }\n\n // Otherwise, wait for segments to be created (with timeout)\n return new Promise<EFTextSegment[]>((resolve) => {\n let attempts = 0;\n const maxAttempts = 100; // 100 frames = ~1.6 seconds at 60fps\n\n const checkSegments = () => {\n segments = this.segments;\n if (segments.length > 0) {\n // Wait for all segments to be updated\n Promise.all(segments.map((seg) => seg.updateComplete)).then(() => {\n // Wait frames to ensure connectedCallback has run and properties are set\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n resolve(this.segments);\n });\n });\n });\n } else if (attempts < maxAttempts) {\n attempts++;\n requestAnimationFrame(checkSegments);\n } else {\n // Timeout - return empty array (might be empty text)\n resolve([]);\n }\n };\n checkSegments();\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n // Find and store template element before any modifications\n this._templateElement = this.querySelector(\"template\");\n\n // Initialize _textContent from DOM if not already set (for declarative usage)\n if (this._textContent === null) {\n this._textContent = this.getTextContent();\n this.lastTextContent = this._textContent;\n }\n\n // Use requestAnimationFrame to ensure DOM is ready\n requestAnimationFrame(() => {\n this.setupMutationObserver();\n this.splitText();\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.mutationObserver?.disconnect();\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n if (\n changedProperties.has(\"split\") ||\n changedProperties.has(\"staggerMs\") ||\n changedProperties.has(\"easing\") ||\n changedProperties.has(\"durationMs\")\n ) {\n this.splitText();\n }\n }\n\n private setupMutationObserver() {\n this.mutationObserver = new MutationObserver(() => {\n // Only react to changes that aren't from our own segment creation\n const currentText = this._textContent || this.getTextContent();\n if (currentText !== this.lastTextContent) {\n this._textContent = currentText;\n this.lastTextContent = currentText;\n this.splitText();\n }\n });\n\n this.mutationObserver.observe(this, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n\n private getTextContent(): string {\n // Get text content, handling both text nodes and HTML content\n let text = \"\";\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent || \"\";\n } else if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Skip ef-text-segment elements (they're created by us)\n if (element.tagName === \"EF-TEXT-SEGMENT\") {\n continue;\n }\n // Skip template elements (they're templates, not content)\n if (element.tagName === \"TEMPLATE\") {\n continue;\n }\n text += element.textContent || \"\";\n }\n }\n return text;\n }\n\n private splitText() {\n // Validate split mode\n const validatedSplit = this.validateSplit(this.split);\n if (validatedSplit !== this.split) {\n this.split = validatedSplit;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Validate stagger\n const validatedStagger = this.validateStagger(this.staggerMs);\n if (validatedStagger !== this.staggerMs) {\n this.staggerMs = validatedStagger;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Read text content - use stored _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n // Clear segments if no text\n const existingSegments = Array.from(\n this.querySelectorAll(\"ef-text-segment\"),\n );\n for (const segment of existingSegments) {\n segment.remove();\n }\n // Clear text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n this.lastTextContent = \"\";\n // Resolve any waiting promises\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n return;\n }\n\n const segments = this.splitTextIntoSegments(text);\n const durationMs = this.durationMs || 1000; // Default 1 second if no duration\n\n // Clear ALL child nodes (text nodes and segments) by replacing innerHTML\n // This ensures we don't have any leftover text nodes\n const fragment = document.createDocumentFragment();\n\n // Find template element if not already stored\n if (!this._templateElement) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Get template content structure\n // If template exists, clone it; otherwise create default ef-text-segment\n const templateContent = this._templateElement?.content;\n const templateSegments = templateContent\n ? Array.from(templateContent.querySelectorAll(\"ef-text-segment\"))\n : [];\n\n // If no template segments found, we'll create a default one\n const useTemplate = templateSegments.length > 0;\n const segmentsPerTextSegment = useTemplate ? templateSegments.length : 1;\n\n // Create new segments in a fragment first\n segments.forEach((segmentText, textIndex) => {\n // Calculate stagger offset if stagger is set\n let staggerOffset: number | undefined;\n if (this.staggerMs !== undefined) {\n // Apply easing to the stagger offset\n // Normalize index to 0-1 range (0 for first segment, 1 for last segment)\n const totalSegments = segments.length;\n const normalizedProgress =\n totalSegments > 1 ? textIndex / (totalSegments - 1) : 0;\n\n // Apply easing function to get eased progress\n const easedProgress = evaluateEasing(this.easing, normalizedProgress);\n\n // Calculate total stagger duration (last segment gets full stagger)\n const totalStaggerDuration = (totalSegments - 1) * this.staggerMs;\n\n // Apply eased progress to total stagger duration\n staggerOffset = easedProgress * totalStaggerDuration;\n }\n\n if (useTemplate && templateContent) {\n // Clone template content for each text segment\n // This allows multiple ef-text-segment elements per character/word/line\n const clonedContent = templateContent.cloneNode(\n true,\n ) as DocumentFragment;\n const clonedSegments = Array.from(\n clonedContent.querySelectorAll(\"ef-text-segment\"),\n ) as EFTextSegment[];\n\n clonedSegments.forEach((segment, templateIndex) => {\n // Set properties - Lit will process these when element is connected\n segment.segmentText = segmentText;\n // Calculate segment index accounting for multiple segments per text segment\n segment.segmentIndex =\n textIndex * segmentsPerTextSegment + templateIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n fragment.appendChild(segment);\n });\n } else {\n // No template - create default ef-text-segment\n const segment = document.createElement(\n \"ef-text-segment\",\n ) as EFTextSegment;\n\n segment.segmentText = segmentText;\n segment.segmentIndex = textIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n fragment.appendChild(segment);\n }\n });\n\n // Ensure segments are connected to DOM before checking for animations\n // Append fragment first, then trigger updates\n\n // Replace all children with the fragment (this clears text nodes and old segments)\n // But preserve the template element if it exists\n const templateToPreserve = this._templateElement;\n while (this.firstChild) {\n const child = this.firstChild;\n // Don't remove the template element\n if (child === templateToPreserve) {\n // Skip template, but we need to move it after the fragment\n // So we'll remove it temporarily and re-add it after\n this.removeChild(child);\n continue;\n }\n this.removeChild(child);\n }\n this.appendChild(fragment);\n // Re-add template element if it existed\n if (templateToPreserve) {\n this.appendChild(templateToPreserve);\n }\n\n // Segments will pause their own animations in connectedCallback\n // Lit will automatically update them when they're connected to the DOM\n // Ensure segments are updated after being connected\n requestAnimationFrame(() => {\n const segmentElements = this.segments;\n Promise.all(segmentElements.map((seg) => seg.updateComplete)).then(() => {\n // Wait an additional frame to ensure animations are paused in connectedCallback\n // Then trigger updateAnimations to set correct state\n // This ensures animations are positioned correctly on first load\n requestAnimationFrame(() => {\n const rootTimegroup = this.rootTimegroup;\n if (rootTimegroup) {\n updateAnimations(rootTimegroup);\n } else {\n updateAnimations(this);\n }\n });\n });\n });\n\n this.lastTextContent = text;\n this._textContent = text;\n\n // Resolve any waiting promises after segments are connected\n requestAnimationFrame(() => {\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n });\n }\n\n private splitTextIntoSegments(text: string): string[] {\n // Trim text before segmenting to remove leading/trailing whitespace\n const trimmedText = text.trim();\n if (!trimmedText) {\n return [];\n }\n\n switch (this.split) {\n case \"line\": {\n // Split on newlines and trim each line\n const lines = trimmedText.split(/\\r?\\n/);\n return lines\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n }\n case \"word\": {\n // Use Intl.Segmenter for locale-aware word segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n // Filter to only include word-like segments (excludes whitespace/punctuation)\n return segments\n .filter((seg) => seg.isWordLike)\n .map((seg) => seg.segment);\n }\n case \"char\": {\n // Use Intl.Segmenter for grapheme-aware character segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"grapheme\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n return segments.map((seg) => seg.segment);\n }\n default:\n return [trimmedText];\n }\n }\n\n get intrinsicDurationMs(): number | undefined {\n // If explicit duration is set, use it\n if (this.hasExplicitDuration) {\n return undefined; // Let explicit duration take precedence\n }\n\n // Otherwise, calculate from content\n // Use _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return 0;\n }\n\n // Use the same splitting logic as splitTextIntoSegments for consistency\n const segments = this.splitTextIntoSegments(text);\n const segmentCount = segments.length || 1;\n\n return segmentCount * 1000;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text\": EFText;\n }\n}\n"],"mappings":"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"}
@@ -1,13 +1,13 @@
1
1
  import { TemporalMixinInterface } from "./EFTemporal.js";
2
- import * as lit9 from "lit";
2
+ import * as lit10 from "lit";
3
3
  import { LitElement } from "lit";
4
- import * as lit_html9 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: lit9.CSSResult[];
10
- render(): lit_html9.TemplateResult<1>;
9
+ static styles: lit10.CSSResult[];
10
+ render(): lit_html10.TemplateResult<1>;
11
11
  private _animationsPaused;
12
12
  connectedCallback(): void;
13
13
  segmentText: string;
@@ -1 +1 @@
1
- {"version":3,"file":"EFTextSegment.js","names":["EFTextSegment"],"sources":["../../src/elements/EFTextSegment.ts"],"sourcesContent":["import { css, html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport type { EFText } from \"./EFText.js\";\n\n@customElement(\"ef-text-segment\")\nexport class EFTextSegment extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-block;\n white-space: pre;\n line-height: 1;\n }\n :host([data-line-segment]) {\n display: block;\n white-space: normal;\n }\n :host([hidden]) {\n opacity: 0;\n pointer-events: none;\n }\n `,\n ];\n\n render() {\n // Set deterministic --ef-seed value based on segment index\n const seed = (this.segmentIndex * 9007) % 233; // Prime numbers for better distribution\n const seedValue = seed / 233; // Normalize to 0-1 range\n this.style.setProperty(\"--ef-seed\", seedValue.toString());\n\n // Set stagger offset CSS variable\n // staggerOffsetMs is always set (defaults to 0), so we can always set the CSS variable\n const offsetMs = this.staggerOffsetMs ?? 0;\n this.style.setProperty(\"--ef-stagger-offset\", `${offsetMs}ms`);\n\n // Set index CSS variable\n this.style.setProperty(\"--ef-index\", this.segmentIndex.toString());\n\n return html`${this.segmentText}`;\n }\n\n private _animationsPaused = false;\n\n connectedCallback() {\n super.connectedCallback();\n // CRITICAL: Pause all animations once when segment is first connected\n // CSS animations start automatically and must be paused so updateAnimations can control them\n // Only do this once on initial load\n if (!this._animationsPaused) {\n // Wait for segment to be fully updated before pausing animations\n this.updateComplete.then(() => {\n requestAnimationFrame(() => {\n const animations = this.getAnimations();\n for (const animation of animations) {\n // Ensure animation is in a playable state\n // If it's finished, reset it\n if (animation.playState === \"finished\") {\n animation.cancel();\n animation.play();\n animation.pause();\n } else if (animation.playState === \"running\") {\n // Pause if running, preserving current visual state\n animation.pause();\n }\n // Don't reset currentTime here - let updateAnimations set it based on timeline\n // This preserves any visual state that was already applied\n }\n this._animationsPaused = true;\n // Note: updateAnimations is called from parent EFText after all segments are created\n // This avoids calling it multiple times (once per segment)\n });\n });\n }\n }\n\n @property({ type: String, attribute: false })\n segmentText = \"\";\n\n @property({ type: Number, attribute: false })\n segmentIndex = 0;\n\n @property({ type: Number, attribute: false })\n staggerOffsetMs?: number;\n\n @property({ type: Number, attribute: false })\n segmentStartMs = 0;\n\n @property({ type: Number, attribute: false })\n segmentEndMs = 0;\n\n @property({ type: Boolean, reflect: true })\n hidden = false;\n\n get startTimeMs() {\n // Get parent text element's absolute start time, then add our local offset\n const parentText = this.closest(\"ef-text\") as EFText;\n const parentStartTime = parentText?.startTimeMs || 0;\n return parentStartTime + (this.segmentStartMs || 0);\n }\n\n get endTimeMs() {\n const parentText = this.closest(\"ef-text\") as EFText;\n const parentStartTime = parentText?.startTimeMs || 0;\n return parentStartTime + (this.segmentEndMs || 0);\n }\n\n get durationMs(): number {\n return this.segmentEndMs - this.segmentStartMs;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text-segment\": EFTextSegment;\n }\n}\n"],"mappings":";;;;;;AAMO,0BAAMA,wBAAsB,WAAW,WAAW,CAAC;;;2BAoC5B;qBAmCd;sBAGC;wBAME;sBAGF;gBAGN;;;gBArFO,CACd,GAAG;;;;;;;;;;;;;;MAeJ;;CAED,SAAS;EAGP,MAAM,YADQ,KAAK,eAAe,OAAQ,MACjB;AACzB,OAAK,MAAM,YAAY,aAAa,UAAU,UAAU,CAAC;EAIzD,MAAM,WAAW,KAAK,mBAAmB;AACzC,OAAK,MAAM,YAAY,uBAAuB,GAAG,SAAS,IAAI;AAG9D,OAAK,MAAM,YAAY,cAAc,KAAK,aAAa,UAAU,CAAC;AAElE,SAAO,IAAI,GAAG,KAAK;;CAKrB,oBAAoB;AAClB,QAAM,mBAAmB;AAIzB,MAAI,CAAC,KAAK,kBAER,MAAK,eAAe,WAAW;AAC7B,+BAA4B;IAC1B,MAAM,aAAa,KAAK,eAAe;AACvC,SAAK,MAAM,aAAa,WAGtB,KAAI,UAAU,cAAc,YAAY;AACtC,eAAU,QAAQ;AAClB,eAAU,MAAM;AAChB,eAAU,OAAO;eACR,UAAU,cAAc,UAEjC,WAAU,OAAO;AAKrB,SAAK,oBAAoB;KAGzB;IACF;;CAsBN,IAAI,cAAc;AAIhB,UAFmB,KAAK,QAAQ,UAAU,EACN,eAAe,MACzB,KAAK,kBAAkB;;CAGnD,IAAI,YAAY;AAGd,UAFmB,KAAK,QAAQ,UAAU,EACN,eAAe,MACzB,KAAK,gBAAgB;;CAGjD,IAAI,aAAqB;AACvB,SAAO,KAAK,eAAe,KAAK;;;YAhCjC,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAO,CAAC;YAG5C,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAO,CAAC;YAG5C,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAO,CAAC;YAG5C,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAO,CAAC;YAG5C,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAO,CAAC;YAG5C,SAAS;CAAE,MAAM;CAAS,SAAS;CAAM,CAAC;4BAtF5C,cAAc,kBAAkB"}
1
+ {"version":3,"file":"EFTextSegment.js","names":["EFTextSegment"],"sources":["../../src/elements/EFTextSegment.ts"],"sourcesContent":["import { css, html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport type { EFText } from \"./EFText.js\";\n\n@customElement(\"ef-text-segment\")\nexport class EFTextSegment extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-block;\n white-space: pre;\n line-height: 1;\n }\n :host([data-line-segment]) {\n display: block;\n white-space: normal;\n }\n :host([hidden]) {\n opacity: 0;\n pointer-events: none;\n }\n `,\n ];\n\n render() {\n // Set deterministic --ef-seed value based on segment index\n const seed = (this.segmentIndex * 9007) % 233; // Prime numbers for better distribution\n const seedValue = seed / 233; // Normalize to 0-1 range\n this.style.setProperty(\"--ef-seed\", seedValue.toString());\n\n // Set stagger offset CSS variable\n // staggerOffsetMs is always set (defaults to 0), so we can always set the CSS variable\n const offsetMs = this.staggerOffsetMs ?? 0;\n this.style.setProperty(\"--ef-stagger-offset\", `${offsetMs}ms`);\n\n // Set index CSS variable\n this.style.setProperty(\"--ef-index\", this.segmentIndex.toString());\n\n return html`${this.segmentText}`;\n }\n\n private _animationsPaused = false;\n\n connectedCallback() {\n super.connectedCallback();\n // CRITICAL: Pause all animations once when segment is first connected\n // Animations (both CSS and WAAPI) start automatically and must be paused so updateAnimations can control them\n // Only do this once on initial load\n if (!this._animationsPaused) {\n // Wait for segment to be fully updated before pausing animations\n this.updateComplete.then(() => {\n requestAnimationFrame(() => {\n const animations = this.getAnimations();\n for (const animation of animations) {\n // Ensure animation is in a playable state\n // If it's finished, reset it\n if (animation.playState === \"finished\") {\n animation.cancel();\n animation.play();\n animation.pause();\n } else if (animation.playState === \"running\") {\n // Pause if running, preserving current visual state\n animation.pause();\n }\n // Don't reset currentTime here - let updateAnimations set it based on timeline\n // This preserves any visual state that was already applied\n }\n this._animationsPaused = true;\n // Note: updateAnimations is called from parent EFText after all segments are created\n // This avoids calling it multiple times (once per segment)\n });\n });\n }\n }\n\n @property({ type: String, attribute: false })\n segmentText = \"\";\n\n @property({ type: Number, attribute: false })\n segmentIndex = 0;\n\n @property({ type: Number, attribute: false })\n staggerOffsetMs?: number;\n\n @property({ type: Number, attribute: false })\n segmentStartMs = 0;\n\n @property({ type: Number, attribute: false })\n segmentEndMs = 0;\n\n @property({ type: Boolean, reflect: true })\n hidden = false;\n\n get startTimeMs() {\n // Get parent text element's absolute start time, then add our local offset\n const parentText = this.closest(\"ef-text\") as EFText;\n const parentStartTime = parentText?.startTimeMs || 0;\n return parentStartTime + (this.segmentStartMs || 0);\n }\n\n get endTimeMs() {\n const parentText = this.closest(\"ef-text\") as EFText;\n const parentStartTime = parentText?.startTimeMs || 0;\n return parentStartTime + (this.segmentEndMs || 0);\n }\n\n get durationMs(): number {\n return this.segmentEndMs - this.segmentStartMs;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text-segment\": EFTextSegment;\n }\n}\n"],"mappings":";;;;;;AAMO,0BAAMA,wBAAsB,WAAW,WAAW,CAAC;;;2BAoC5B;qBAmCd;sBAGC;wBAME;sBAGF;gBAGN;;;gBArFO,CACd,GAAG;;;;;;;;;;;;;;MAeJ;;CAED,SAAS;EAGP,MAAM,YADQ,KAAK,eAAe,OAAQ,MACjB;AACzB,OAAK,MAAM,YAAY,aAAa,UAAU,UAAU,CAAC;EAIzD,MAAM,WAAW,KAAK,mBAAmB;AACzC,OAAK,MAAM,YAAY,uBAAuB,GAAG,SAAS,IAAI;AAG9D,OAAK,MAAM,YAAY,cAAc,KAAK,aAAa,UAAU,CAAC;AAElE,SAAO,IAAI,GAAG,KAAK;;CAKrB,oBAAoB;AAClB,QAAM,mBAAmB;AAIzB,MAAI,CAAC,KAAK,kBAER,MAAK,eAAe,WAAW;AAC7B,+BAA4B;IAC1B,MAAM,aAAa,KAAK,eAAe;AACvC,SAAK,MAAM,aAAa,WAGtB,KAAI,UAAU,cAAc,YAAY;AACtC,eAAU,QAAQ;AAClB,eAAU,MAAM;AAChB,eAAU,OAAO;eACR,UAAU,cAAc,UAEjC,WAAU,OAAO;AAKrB,SAAK,oBAAoB;KAGzB;IACF;;CAsBN,IAAI,cAAc;AAIhB,UAFmB,KAAK,QAAQ,UAAU,EACN,eAAe,MACzB,KAAK,kBAAkB;;CAGnD,IAAI,YAAY;AAGd,UAFmB,KAAK,QAAQ,UAAU,EACN,eAAe,MACzB,KAAK,gBAAgB;;CAGjD,IAAI,aAAqB;AACvB,SAAO,KAAK,eAAe,KAAK;;;YAhCjC,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAO,CAAC;YAG5C,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAO,CAAC;YAG5C,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAO,CAAC;YAG5C,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAO,CAAC;YAG5C,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAO,CAAC;YAG5C,SAAS;CAAE,MAAM;CAAS,SAAS;CAAM,CAAC;4BAtF5C,cAAc,kBAAkB"}
@@ -11,7 +11,7 @@ import { renderTemporalAudio } from "./renderTemporalAudio.js";
11
11
  import { EFTargetable } from "./TargetController.js";
12
12
  import { deepGetMediaElements } from "./EFMedia.js";
13
13
  import { TimegroupController } from "./TimegroupController.js";
14
- import { evaluateTemporalStateForAnimation, updateAnimations } from "./updateAnimations.js";
14
+ import { evaluateAnimationVisibilityState, updateAnimations } from "./updateAnimations.js";
15
15
  import { provide } from "@lit/context";
16
16
  import { Task, TaskStatus } from "@lit/task";
17
17
  import debug from "debug";
@@ -205,7 +205,7 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
205
205
  if (this.playbackController) this.saveTimeToLocalStorage(this.currentTime);
206
206
  await this.frameTask.taskComplete;
207
207
  const visibleElements = deepGetElementsWithFrameTasks(this).filter((element) => {
208
- return evaluateTemporalStateForAnimation(element).isVisible;
208
+ return evaluateAnimationVisibilityState(element).isVisible;
209
209
  });
210
210
  await Promise.all(visibleElements.map(async (element) => {
211
211
  if ("waitForFrameReady" in element && typeof element.waitForFrameReady === "function") await element.waitForFrameReady();
@@ -376,7 +376,7 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
376
376
  const temporalElements = deepGetElementsWithFrameTasks(this);
377
377
  if (isTracingEnabled()) span.setAttribute("temporalElementsCount", temporalElements.length);
378
378
  const visibleElements = temporalElements.filter((element) => {
379
- return evaluateTemporalStateForAnimation(element).isVisible;
379
+ return evaluateAnimationVisibilityState(element).isVisible;
380
380
  });
381
381
  if (isTracingEnabled()) span.setAttribute("visibleElementsCount", visibleElements.length);
382
382
  const promiseStart = performance.now();
@@ -1 +1 @@
1
- {"version":3,"file":"EFTimegroup.js","names":["sequenceDurationCache: WeakMap<EFTimegroup, number>","EFTimegroup","#executeCustomFrameTasks","#pendingSeekTime","#currentTime","#seekInProgress","#processingPendingSeek","#customFrameTasks","#handleSlotChange","#previousDurationMs","#resizeObserver","#waitForMediaDurations","loaderTasks: Promise<any>[]"],"sources":["../../src/elements/EFTimegroup.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { Task, TaskStatus } from \"@lit/task\";\nimport debug from \"debug\";\nimport { css, html, LitElement, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nimport { EF_INTERACTIVE } from \"../EF_INTERACTIVE.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { isTracingEnabled, withSpan } from \"../otel/tracingHelpers.js\";\nimport { deepGetMediaElements, type EFMedia } from \"./EFMedia.js\";\nimport {\n deepGetElementsWithFrameTasks,\n EFTemporal,\n flushStartTimeMsCache,\n resetTemporalCache,\n shallowGetTemporalElements,\n timegroupContext,\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 {\n evaluateTemporalStateForAnimation,\n updateAnimations,\n} from \"./updateAnimations.ts\";\n\ndeclare global {\n var EF_DEV_WORKBENCH: boolean | undefined;\n}\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// Cache for sequence mode duration calculations to avoid O(n) recalculation\nlet sequenceDurationCache: WeakMap<EFTimegroup, number> = new WeakMap();\n\nexport const flushSequenceDurationCache = () => {\n sequenceDurationCache = new WeakMap();\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@customElement(\"ef-timegroup\")\nexport class EFTimegroup extends EFTargetable(EFTemporal(TWMixin(LitElement))) {\n static get observedAttributes(): string[] {\n // biome-ignore lint/complexity/noThisInStatic: It's okay to use this here\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mode\",\n \"overlap\",\n \"currenttime\",\n \"fit\",\n \"fps\",\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 @provide({ context: timegroupContext })\n _timeGroupContext = this;\n\n @provide({ context: efContext })\n efContext = this;\n\n mode: \"fit\" | \"fixed\" | \"sequence\" | \"contain\" = \"contain\";\n overlapMs = 0;\n\n @property({ type: Number })\n fps = 30;\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 === \"fps\" && value) {\n this.fps = Number.parseFloat(value);\n }\n super.attributeChangedCallback(name, old, value);\n }\n\n @property({ type: String })\n fit: \"none\" | \"contain\" | \"cover\" = \"none\";\n\n #resizeObserver?: ResizeObserver;\n\n #currentTime: number | undefined = undefined;\n #seekInProgress = false;\n #pendingSeekTime: number | undefined;\n #processingPendingSeek = false;\n #customFrameTasks: Set<FrameTaskCallback> = new Set();\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 */\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 * Quantize a time value to the nearest frame boundary based on effectiveFps.\n * @param timeSeconds - Time in seconds\n * @returns Time quantized to frame boundaries in seconds\n */\n private quantizeToFrameTime(timeSeconds: number): number {\n const fps = this.effectiveFps;\n if (!fps || fps <= 0) return timeSeconds;\n const frameDurationS = 1 / fps;\n return Math.round(timeSeconds / frameDurationS) * frameDurationS;\n }\n\n private async runThrottledFrameTask(): Promise<void> {\n if (this.playbackController) {\n return this.playbackController.runThrottledFrameTask();\n }\n await this.frameTask.run();\n }\n\n @property({ type: Number, attribute: \"currenttime\" })\n set currentTime(time: number) {\n // Quantize time to frame boundaries based on fps\n // Do this BEFORE delegating to playbackController to ensure consistency\n time = this.quantizeToFrameTime(time);\n\n if (this.playbackController) {\n this.playbackController.currentTime = time;\n return;\n }\n\n time = Math.max(0, Math.min(this.durationMs / 1000, time));\n if (!this.isRootTimegroup) {\n return;\n }\n if (Number.isNaN(time)) {\n return;\n }\n if (time === this.#currentTime && !this.#processingPendingSeek) {\n return;\n }\n if (this.#pendingSeekTime === time) {\n return;\n }\n\n if (this.#seekInProgress) {\n this.#pendingSeekTime = time;\n this.#currentTime = time;\n return;\n }\n\n this.#currentTime = time;\n this.#seekInProgress = true;\n\n this.seekTask.run().finally(() => {\n if (\n this.#pendingSeekTime !== undefined &&\n this.#pendingSeekTime !== time\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 get currentTime() {\n if (this.playbackController) {\n return this.playbackController.currentTime;\n }\n return this.#currentTime ?? 0;\n }\n\n set currentTimeMs(ms: number) {\n this.currentTime = ms / 1000;\n }\n\n get currentTimeMs() {\n return this.currentTime * 1000;\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 * @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 */\n async seek(timeMs: number): Promise<void> {\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 await this.frameTask.taskComplete;\n\n // Ensure all visible elements have completed their reactive update cycles AND frame rendering\n // waitForFrameTasks() calls frameTask.run() on children, but this may happen before child\n // elements have processed property changes from requestUpdate(). To ensure frame data is\n // accurate, we wait for updateComplete first, then ensure the frameTask has run with the\n // updated properties. Elements like EFVideo provide waitForFrameReady() for this pattern.\n const temporalElements = deepGetElementsWithFrameTasks(this);\n const visibleElements = temporalElements.filter((element) => {\n const animationState = evaluateTemporalStateForAnimation(element);\n return animationState.isVisible;\n });\n\n await Promise.all(\n visibleElements.map(async (element) => {\n if (\n \"waitForFrameReady\" in element &&\n typeof element.waitForFrameReady === \"function\"\n ) {\n await (element as any).waitForFrameReady();\n } else {\n await element.updateComplete;\n }\n }),\n );\n }\n\n /**\n * Determines if this is a root timegroup (no parent timegroups)\n */\n get isRootTimegroup(): boolean {\n return !this.parentTimegroup;\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 */\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 */\n removeFrameTask(callback: FrameTaskCallback): void {\n this.#customFrameTasks.delete(callback);\n }\n\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 // Invalidate caches when slot content changes\n resetTemporalCache();\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n\n // Request update to trigger recalculation of dependent properties\n this.requestUpdate();\n };\n\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 return Number.parseFloat(storedValue);\n } catch (error) {\n log(\"Failed to load time from localStorage\", error);\n }\n }\n return undefined;\n }\n\n connectedCallback() {\n super.connectedCallback();\n\n if (!this.playbackController) {\n this.waitForMediaDurations().then(async () => {\n let didLoadFromStorage = false;\n if (this.id) {\n const maybeLoadedTime = this.loadTimeFromLocalStorage();\n if (maybeLoadedTime !== undefined) {\n this.currentTime = maybeLoadedTime;\n didLoadFromStorage = true;\n }\n }\n if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) {\n this.seekTask.run();\n } else if (didLoadFromStorage) {\n await this.seekTask.run();\n }\n });\n }\n\n if (this.parentTimegroup) {\n new TimegroupController(this.parentTimegroup, this);\n }\n\n if (this.shouldWrapWithWorkbench()) {\n this.wrapWithWorkbench();\n }\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 sequenceDurationCache.delete(this);\n }\n\n if (this.#previousDurationMs !== this.durationMs) {\n this.#previousDurationMs = this.durationMs;\n this.runThrottledFrameTask();\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#resizeObserver?.disconnect();\n }\n\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 get intrinsicDurationMs() {\n if (this.hasExplicitDuration) {\n return this.explicitDurationMs;\n }\n return undefined;\n }\n\n get hasOwnDuration() {\n return (\n this.mode === \"contain\" ||\n this.mode === \"sequence\" ||\n (this.mode === \"fixed\" && this.hasExplicitDuration)\n );\n }\n\n get durationMs(): number {\n switch (this.mode) {\n case \"fit\": {\n if (!this.parentTimegroup) {\n return 0;\n }\n return this.parentTimegroup.durationMs;\n }\n case \"fixed\":\n return super.durationMs;\n case \"sequence\": {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = sequenceDurationCache.get(this);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let duration = 0;\n this.childTemporals.forEach((child, index) => {\n if (child instanceof EFTimegroup && child.mode === \"fit\") {\n return;\n }\n if (index > 0) {\n duration -= this.overlapMs;\n }\n duration += child.durationMs;\n });\n\n // Cache the calculated duration\n sequenceDurationCache.set(this, duration);\n return duration;\n }\n case \"contain\": {\n let maxDuration = 0;\n for (const child of this.childTemporals) {\n // fit timegroups look \"up\" to their parent timegroup for their duration\n // so we need to skip them to avoid an infinite loop\n if (child instanceof EFTimegroup && child.mode === \"fit\") {\n continue;\n }\n if (!child.hasOwnDuration) {\n continue;\n }\n maxDuration = Math.max(maxDuration, child.durationMs);\n }\n return maxDuration;\n }\n default:\n throw new Error(`Invalid time mode: ${this.mode}`);\n }\n }\n\n async getPendingFrameTasks(signal?: AbortSignal) {\n await this.waitForNestedUpdates(signal);\n signal?.throwIfAborted();\n const temporals = deepGetElementsWithFrameTasks(this);\n\n // Filter to only include temporally visible elements for frame processing\n // (but keep all elements for duration calculations)\n // Use the target timeline time if we're in the middle of seeking\n const timelineTimeMs =\n (this.#pendingSeekTime ?? this.#currentTime ?? 0) * 1000;\n const activeTemporals = temporals.filter((temporal) => {\n // Skip timeline filtering if temporal doesn't have timeline position info\n if (!(\"startTimeMs\" in temporal) || !(\"endTimeMs\" in temporal)) {\n return true; // Keep non-temporal elements\n }\n\n // Only process frame tasks for elements that overlap the current timeline\n // Use same epsilon logic as seek task for consistency\n const epsilon = 0.001; // 1µs offset to break ties at boundaries\n const startTimeMs = (temporal as any).startTimeMs as number;\n const endTimeMs = (temporal as any).endTimeMs as number;\n const elementStartsBeforeEnd = startTimeMs <= timelineTimeMs + epsilon;\n // Root timegroups should remain visible at exact end time, but other elements use exclusive end for clean transitions\n const isRootTimegroup =\n temporal.tagName.toLowerCase() === \"ef-timegroup\" &&\n !(temporal as any).parentTimegroup;\n const useInclusiveEnd = isRootTimegroup;\n const elementEndsAfterStart = useInclusiveEnd\n ? endTimeMs >= timelineTimeMs\n : endTimeMs > timelineTimeMs;\n return elementStartsBeforeEnd && elementEndsAfterStart;\n });\n\n const frameTasks = activeTemporals.map((temporal) => temporal.frameTask);\n frameTasks.forEach((task) => {\n task.run();\n });\n\n return frameTasks.filter((task) => task.status < TaskStatus.COMPLETE);\n }\n\n async waitForNestedUpdates(signal?: AbortSignal) {\n const limit = 10;\n let steps = 0;\n let isComplete = true;\n while (true) {\n steps++;\n if (steps > limit) {\n throw new Error(\"Reached update depth limit.\");\n }\n isComplete = await this.updateComplete;\n signal?.throwIfAborted();\n if (isComplete) {\n break;\n }\n }\n }\n\n async waitForFrameTasks() {\n const result = await withSpan(\n \"timegroup.waitForFrameTasks\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n const innerStart = performance.now();\n\n const temporalElements = deepGetElementsWithFrameTasks(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"temporalElementsCount\", temporalElements.length);\n }\n\n // Filter to only include temporally visible elements for frame processing\n // Use animation-friendly visibility to prevent animation jumps at exact boundaries\n const visibleElements = temporalElements.filter((element) => {\n const animationState = evaluateTemporalStateForAnimation(element);\n return animationState.isVisible;\n });\n if (isTracingEnabled()) {\n span.setAttribute(\"visibleElementsCount\", visibleElements.length);\n }\n\n const promiseStart = performance.now();\n\n await Promise.all(\n visibleElements.map((element) => element.frameTask.run()),\n );\n const promiseEnd = performance.now();\n\n const innerEnd = performance.now();\n if (isTracingEnabled()) {\n span.setAttribute(\"actualInnerMs\", innerEnd - innerStart);\n span.setAttribute(\"promiseAwaitMs\", promiseEnd - promiseStart);\n }\n },\n );\n\n return result;\n }\n\n mediaDurationsPromise: Promise<void> | undefined = undefined;\n\n async waitForMediaDurations() {\n if (!this.mediaDurationsPromise) {\n this.mediaDurationsPromise = this.#waitForMediaDurations();\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() {\n return withSpan(\n \"timegroup.waitForMediaDurations\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n // We must await updateComplete to ensure all media elements inside this are connected\n // and will match deepGetMediaElements\n await this.updateComplete;\n const mediaElements = deepGetMediaElements(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"mediaElementsCount\", mediaElements.length);\n }\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 await Promise.all(\n mediaElements.map((m) =>\n m.mediaEngineTask.value\n ? Promise.resolve()\n : m.mediaEngineTask.run(),\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 this.requestUpdate(\"currentTime\");\n // Finally, we must await updateComplete to ensure all temporal elements have their\n // currentTime updated and all animations have run.\n\n await this.updateComplete;\n },\n );\n }\n\n get childTemporals() {\n return shallowGetTemporalElements(this);\n }\n\n get contextProvider() {\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n return parent;\n }\n parent = parent.parentNode;\n }\n return null;\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 * - It's in interactive mode (EF_INTERACTIVE) with the dev workbench flag set\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 */\n shouldWrapWithWorkbench() {\n const isRendering = EF_RENDERING?.() === true;\n\n // During rendering, always wrap with workbench (needed by EF_FRAMEGEN)\n if (isRendering) {\n return (\n this.closest(\"ef-timegroup\") === this &&\n this.closest(\"ef-preview\") === null &&\n this.closest(\"ef-workbench\") === null &&\n this.closest(\"test-context\") === null\n );\n }\n\n // During interactive mode, respect the dev workbench flag\n if (!globalThis.EF_DEV_WORKBENCH) {\n return false;\n }\n\n return (\n EF_INTERACTIVE &&\n this.closest(\"ef-timegroup\") === this &&\n this.closest(\"ef-preview\") === null &&\n this.closest(\"ef-workbench\") === null &&\n this.closest(\"test-context\") === null\n );\n }\n\n wrapWithWorkbench() {\n const workbench = document.createElement(\"ef-workbench\");\n this.parentElement?.append(workbench);\n if (!this.hasAttribute(\"id\")) {\n this.setAttribute(\"id\", \"root-this\");\n }\n this.setAttribute(\"slot\", \"canvas\");\n workbench.append(this as unknown as Element);\n\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 get efElements() {\n return Array.from(\n this.querySelectorAll(\n \"ef-audio, ef-video, ef-image, ef-captions, ef-waveform\",\n ),\n );\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 */\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 */\n async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs);\n }\n\n /**\n * TEMPORARY TEST METHOD: Renders audio and immediately plays it back\n * Usage: timegroup.testPlayAudio(0, 5000) // Play first 5 seconds\n */\n async testPlayAudio(fromMs: number, toMs: number) {\n // Render the audio using the existing renderAudio method\n const renderedBuffer = await this.renderAudio(fromMs, toMs);\n\n // Create a regular AudioContext for playback\n const playbackContext = new AudioContext();\n\n // Create a buffer source and connect it\n const bufferSource = playbackContext.createBufferSource();\n bufferSource.buffer = renderedBuffer;\n bufferSource.connect(playbackContext.destination);\n\n // Start playback immediately\n bufferSource.start(0);\n\n // Return a promise that resolves when playback ends\n return new Promise<void>((resolve) => {\n bufferSource.onended = () => {\n playbackContext.close();\n resolve();\n };\n });\n }\n\n async loadMd5Sums() {\n const efElements = this.efElements;\n const loaderTasks: Promise<any>[] = [];\n for (const el of efElements) {\n const md5SumLoader = (el as any).md5SumLoader;\n if (md5SumLoader instanceof Task) {\n md5SumLoader.run();\n loaderTasks.push(md5SumLoader.taskComplete);\n }\n }\n\n await Promise.all(loaderTasks);\n\n efElements.forEach((el) => {\n if (\"productionSrc\" in el && el.productionSrc instanceof Function) {\n el.setAttribute(\"src\", el.productionSrc());\n }\n });\n }\n\n frameTask = new Task(this, {\n // autoRun: EF_INTERACTIVE,\n autoRun: false,\n args: () => [this.ownCurrentTimeMs, this.currentTimeMs] as const,\n task: async ([ownCurrentTimeMs, currentTimeMs]) => {\n if (this.isRootTimegroup) {\n await withSpan(\n \"timegroup.frameTask\",\n {\n timegroupId: this.id || \"unknown\",\n ownCurrentTimeMs,\n currentTimeMs,\n },\n undefined,\n async () => {\n await this.waitForFrameTasks();\n await this.#executeCustomFrameTasks();\n updateAnimations(this);\n },\n );\n } else {\n // Non-root timegroups execute their custom frame tasks when called\n await this.#executeCustomFrameTasks();\n }\n },\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 seekTask = new Task(this, {\n autoRun: false,\n args: () => [this.#pendingSeekTime ?? this.#currentTime] as const,\n onComplete: () => {},\n task: async ([targetTime]) => {\n if (this.playbackController) {\n await this.playbackController.seekTask.taskComplete;\n return this.currentTime;\n }\n\n if (!this.isRootTimegroup) {\n return;\n }\n return 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 await this.waitForMediaDurations();\n const newTime = Math.max(\n 0,\n Math.min(targetTime ?? 0, this.durationMs / 1000),\n );\n if (isTracingEnabled()) {\n span.setAttribute(\"newTime\", newTime);\n }\n // Apply the clamped time back to currentTime\n\n this.#currentTime = newTime;\n this.requestUpdate(\"currentTime\");\n await this.runThrottledFrameTask();\n this.saveTimeToLocalStorage(this.#currentTime);\n this.#seekInProgress = false;\n return newTime;\n },\n );\n },\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":["sequenceDurationCache: WeakMap<EFTimegroup, number>","EFTimegroup","#executeCustomFrameTasks","#pendingSeekTime","#currentTime","#seekInProgress","#processingPendingSeek","#customFrameTasks","#handleSlotChange","#previousDurationMs","#resizeObserver","#waitForMediaDurations","loaderTasks: Promise<any>[]"],"sources":["../../src/elements/EFTimegroup.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { Task, TaskStatus } from \"@lit/task\";\nimport debug from \"debug\";\nimport { css, html, LitElement, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nimport { EF_INTERACTIVE } from \"../EF_INTERACTIVE.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { isTracingEnabled, withSpan } from \"../otel/tracingHelpers.js\";\nimport { deepGetMediaElements, type EFMedia } from \"./EFMedia.js\";\nimport {\n deepGetElementsWithFrameTasks,\n EFTemporal,\n flushStartTimeMsCache,\n resetTemporalCache,\n shallowGetTemporalElements,\n timegroupContext,\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 {\n evaluateAnimationVisibilityState,\n updateAnimations,\n} from \"./updateAnimations.ts\";\n\ndeclare global {\n var EF_DEV_WORKBENCH: boolean | undefined;\n}\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// Cache for sequence mode duration calculations to avoid O(n) recalculation\nlet sequenceDurationCache: WeakMap<EFTimegroup, number> = new WeakMap();\n\nexport const flushSequenceDurationCache = () => {\n sequenceDurationCache = new WeakMap();\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@customElement(\"ef-timegroup\")\nexport class EFTimegroup extends EFTargetable(EFTemporal(TWMixin(LitElement))) {\n static get observedAttributes(): string[] {\n // biome-ignore lint/complexity/noThisInStatic: It's okay to use this here\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mode\",\n \"overlap\",\n \"currenttime\",\n \"fit\",\n \"fps\",\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 @provide({ context: timegroupContext })\n _timeGroupContext = this;\n\n @provide({ context: efContext })\n efContext = this;\n\n mode: \"fit\" | \"fixed\" | \"sequence\" | \"contain\" = \"contain\";\n overlapMs = 0;\n\n @property({ type: Number })\n fps = 30;\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 === \"fps\" && value) {\n this.fps = Number.parseFloat(value);\n }\n super.attributeChangedCallback(name, old, value);\n }\n\n @property({ type: String })\n fit: \"none\" | \"contain\" | \"cover\" = \"none\";\n\n #resizeObserver?: ResizeObserver;\n\n #currentTime: number | undefined = undefined;\n #seekInProgress = false;\n #pendingSeekTime: number | undefined;\n #processingPendingSeek = false;\n #customFrameTasks: Set<FrameTaskCallback> = new Set();\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 */\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 * Quantize a time value to the nearest frame boundary based on effectiveFps.\n * @param timeSeconds - Time in seconds\n * @returns Time quantized to frame boundaries in seconds\n */\n private quantizeToFrameTime(timeSeconds: number): number {\n const fps = this.effectiveFps;\n if (!fps || fps <= 0) return timeSeconds;\n const frameDurationS = 1 / fps;\n return Math.round(timeSeconds / frameDurationS) * frameDurationS;\n }\n\n private async runThrottledFrameTask(): Promise<void> {\n if (this.playbackController) {\n return this.playbackController.runThrottledFrameTask();\n }\n await this.frameTask.run();\n }\n\n @property({ type: Number, attribute: \"currenttime\" })\n set currentTime(time: number) {\n // Quantize time to frame boundaries based on fps\n // Do this BEFORE delegating to playbackController to ensure consistency\n time = this.quantizeToFrameTime(time);\n\n if (this.playbackController) {\n this.playbackController.currentTime = time;\n return;\n }\n\n time = Math.max(0, Math.min(this.durationMs / 1000, time));\n if (!this.isRootTimegroup) {\n return;\n }\n if (Number.isNaN(time)) {\n return;\n }\n if (time === this.#currentTime && !this.#processingPendingSeek) {\n return;\n }\n if (this.#pendingSeekTime === time) {\n return;\n }\n\n if (this.#seekInProgress) {\n this.#pendingSeekTime = time;\n this.#currentTime = time;\n return;\n }\n\n this.#currentTime = time;\n this.#seekInProgress = true;\n\n this.seekTask.run().finally(() => {\n if (\n this.#pendingSeekTime !== undefined &&\n this.#pendingSeekTime !== time\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 get currentTime() {\n if (this.playbackController) {\n return this.playbackController.currentTime;\n }\n return this.#currentTime ?? 0;\n }\n\n set currentTimeMs(ms: number) {\n this.currentTime = ms / 1000;\n }\n\n get currentTimeMs() {\n return this.currentTime * 1000;\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 * @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 */\n async seek(timeMs: number): Promise<void> {\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 await this.frameTask.taskComplete;\n\n // Ensure all visible elements have completed their reactive update cycles AND frame rendering\n // waitForFrameTasks() calls frameTask.run() on children, but this may happen before child\n // elements have processed property changes from requestUpdate(). To ensure frame data is\n // accurate, we wait for updateComplete first, then ensure the frameTask has run with the\n // updated properties. Elements like EFVideo provide waitForFrameReady() for this pattern.\n const temporalElements = deepGetElementsWithFrameTasks(this);\n const visibleElements = temporalElements.filter((element) => {\n const animationState = evaluateAnimationVisibilityState(element);\n return animationState.isVisible;\n });\n\n await Promise.all(\n visibleElements.map(async (element) => {\n if (\n \"waitForFrameReady\" in element &&\n typeof element.waitForFrameReady === \"function\"\n ) {\n await (element as any).waitForFrameReady();\n } else {\n await element.updateComplete;\n }\n }),\n );\n }\n\n /**\n * Determines if this is a root timegroup (no parent timegroups)\n */\n get isRootTimegroup(): boolean {\n return !this.parentTimegroup;\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 */\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 */\n removeFrameTask(callback: FrameTaskCallback): void {\n this.#customFrameTasks.delete(callback);\n }\n\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 // Invalidate caches when slot content changes\n resetTemporalCache();\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n\n // Request update to trigger recalculation of dependent properties\n this.requestUpdate();\n };\n\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 return Number.parseFloat(storedValue);\n } catch (error) {\n log(\"Failed to load time from localStorage\", error);\n }\n }\n return undefined;\n }\n\n connectedCallback() {\n super.connectedCallback();\n\n if (!this.playbackController) {\n this.waitForMediaDurations().then(async () => {\n let didLoadFromStorage = false;\n if (this.id) {\n const maybeLoadedTime = this.loadTimeFromLocalStorage();\n if (maybeLoadedTime !== undefined) {\n this.currentTime = maybeLoadedTime;\n didLoadFromStorage = true;\n }\n }\n if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) {\n this.seekTask.run();\n } else if (didLoadFromStorage) {\n await this.seekTask.run();\n }\n });\n }\n\n if (this.parentTimegroup) {\n new TimegroupController(this.parentTimegroup, this);\n }\n\n if (this.shouldWrapWithWorkbench()) {\n this.wrapWithWorkbench();\n }\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 sequenceDurationCache.delete(this);\n }\n\n if (this.#previousDurationMs !== this.durationMs) {\n this.#previousDurationMs = this.durationMs;\n this.runThrottledFrameTask();\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#resizeObserver?.disconnect();\n }\n\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 get intrinsicDurationMs() {\n if (this.hasExplicitDuration) {\n return this.explicitDurationMs;\n }\n return undefined;\n }\n\n get hasOwnDuration() {\n return (\n this.mode === \"contain\" ||\n this.mode === \"sequence\" ||\n (this.mode === \"fixed\" && this.hasExplicitDuration)\n );\n }\n\n get durationMs(): number {\n switch (this.mode) {\n case \"fit\": {\n if (!this.parentTimegroup) {\n return 0;\n }\n return this.parentTimegroup.durationMs;\n }\n case \"fixed\":\n return super.durationMs;\n case \"sequence\": {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = sequenceDurationCache.get(this);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let duration = 0;\n this.childTemporals.forEach((child, index) => {\n if (child instanceof EFTimegroup && child.mode === \"fit\") {\n return;\n }\n if (index > 0) {\n duration -= this.overlapMs;\n }\n duration += child.durationMs;\n });\n\n // Cache the calculated duration\n sequenceDurationCache.set(this, duration);\n return duration;\n }\n case \"contain\": {\n let maxDuration = 0;\n for (const child of this.childTemporals) {\n // fit timegroups look \"up\" to their parent timegroup for their duration\n // so we need to skip them to avoid an infinite loop\n if (child instanceof EFTimegroup && child.mode === \"fit\") {\n continue;\n }\n if (!child.hasOwnDuration) {\n continue;\n }\n maxDuration = Math.max(maxDuration, child.durationMs);\n }\n return maxDuration;\n }\n default:\n throw new Error(`Invalid time mode: ${this.mode}`);\n }\n }\n\n async getPendingFrameTasks(signal?: AbortSignal) {\n await this.waitForNestedUpdates(signal);\n signal?.throwIfAborted();\n const temporals = deepGetElementsWithFrameTasks(this);\n\n // Filter to only include temporally visible elements for frame processing\n // (but keep all elements for duration calculations)\n // Use the target timeline time if we're in the middle of seeking\n const timelineTimeMs =\n (this.#pendingSeekTime ?? this.#currentTime ?? 0) * 1000;\n const activeTemporals = temporals.filter((temporal) => {\n // Skip timeline filtering if temporal doesn't have timeline position info\n if (!(\"startTimeMs\" in temporal) || !(\"endTimeMs\" in temporal)) {\n return true; // Keep non-temporal elements\n }\n\n // Only process frame tasks for elements that overlap the current timeline\n // Use same epsilon logic as seek task for consistency\n const epsilon = 0.001; // 1µs offset to break ties at boundaries\n const startTimeMs = (temporal as any).startTimeMs as number;\n const endTimeMs = (temporal as any).endTimeMs as number;\n const elementStartsBeforeEnd = startTimeMs <= timelineTimeMs + epsilon;\n // Root timegroups should remain visible at exact end time, but other elements use exclusive end for clean transitions\n const isRootTimegroup =\n temporal.tagName.toLowerCase() === \"ef-timegroup\" &&\n !(temporal as any).parentTimegroup;\n const useInclusiveEnd = isRootTimegroup;\n const elementEndsAfterStart = useInclusiveEnd\n ? endTimeMs >= timelineTimeMs\n : endTimeMs > timelineTimeMs;\n return elementStartsBeforeEnd && elementEndsAfterStart;\n });\n\n const frameTasks = activeTemporals.map((temporal) => temporal.frameTask);\n frameTasks.forEach((task) => {\n task.run();\n });\n\n return frameTasks.filter((task) => task.status < TaskStatus.COMPLETE);\n }\n\n async waitForNestedUpdates(signal?: AbortSignal) {\n const limit = 10;\n let steps = 0;\n let isComplete = true;\n while (true) {\n steps++;\n if (steps > limit) {\n throw new Error(\"Reached update depth limit.\");\n }\n isComplete = await this.updateComplete;\n signal?.throwIfAborted();\n if (isComplete) {\n break;\n }\n }\n }\n\n async waitForFrameTasks() {\n const result = await withSpan(\n \"timegroup.waitForFrameTasks\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n const innerStart = performance.now();\n\n const temporalElements = deepGetElementsWithFrameTasks(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"temporalElementsCount\", temporalElements.length);\n }\n\n // Filter to only include temporally visible elements for frame processing\n // Use animation-friendly visibility to prevent animation jumps at exact boundaries\n const visibleElements = temporalElements.filter((element) => {\n const animationState = evaluateAnimationVisibilityState(element);\n return animationState.isVisible;\n });\n if (isTracingEnabled()) {\n span.setAttribute(\"visibleElementsCount\", visibleElements.length);\n }\n\n const promiseStart = performance.now();\n\n await Promise.all(\n visibleElements.map((element) => element.frameTask.run()),\n );\n const promiseEnd = performance.now();\n\n const innerEnd = performance.now();\n if (isTracingEnabled()) {\n span.setAttribute(\"actualInnerMs\", innerEnd - innerStart);\n span.setAttribute(\"promiseAwaitMs\", promiseEnd - promiseStart);\n }\n },\n );\n\n return result;\n }\n\n mediaDurationsPromise: Promise<void> | undefined = undefined;\n\n async waitForMediaDurations() {\n if (!this.mediaDurationsPromise) {\n this.mediaDurationsPromise = this.#waitForMediaDurations();\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() {\n return withSpan(\n \"timegroup.waitForMediaDurations\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n // We must await updateComplete to ensure all media elements inside this are connected\n // and will match deepGetMediaElements\n await this.updateComplete;\n const mediaElements = deepGetMediaElements(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"mediaElementsCount\", mediaElements.length);\n }\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 await Promise.all(\n mediaElements.map((m) =>\n m.mediaEngineTask.value\n ? Promise.resolve()\n : m.mediaEngineTask.run(),\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 this.requestUpdate(\"currentTime\");\n // Finally, we must await updateComplete to ensure all temporal elements have their\n // currentTime updated and all animations have run.\n\n await this.updateComplete;\n },\n );\n }\n\n get childTemporals() {\n return shallowGetTemporalElements(this);\n }\n\n get contextProvider() {\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n return parent;\n }\n parent = parent.parentNode;\n }\n return null;\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 * - It's in interactive mode (EF_INTERACTIVE) with the dev workbench flag set\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 */\n shouldWrapWithWorkbench() {\n const isRendering = EF_RENDERING?.() === true;\n\n // During rendering, always wrap with workbench (needed by EF_FRAMEGEN)\n if (isRendering) {\n return (\n this.closest(\"ef-timegroup\") === this &&\n this.closest(\"ef-preview\") === null &&\n this.closest(\"ef-workbench\") === null &&\n this.closest(\"test-context\") === null\n );\n }\n\n // During interactive mode, respect the dev workbench flag\n if (!globalThis.EF_DEV_WORKBENCH) {\n return false;\n }\n\n return (\n EF_INTERACTIVE &&\n this.closest(\"ef-timegroup\") === this &&\n this.closest(\"ef-preview\") === null &&\n this.closest(\"ef-workbench\") === null &&\n this.closest(\"test-context\") === null\n );\n }\n\n wrapWithWorkbench() {\n const workbench = document.createElement(\"ef-workbench\");\n this.parentElement?.append(workbench);\n if (!this.hasAttribute(\"id\")) {\n this.setAttribute(\"id\", \"root-this\");\n }\n this.setAttribute(\"slot\", \"canvas\");\n workbench.append(this as unknown as Element);\n\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 get efElements() {\n return Array.from(\n this.querySelectorAll(\n \"ef-audio, ef-video, ef-image, ef-captions, ef-waveform\",\n ),\n );\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 */\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 */\n async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs);\n }\n\n /**\n * TEMPORARY TEST METHOD: Renders audio and immediately plays it back\n * Usage: timegroup.testPlayAudio(0, 5000) // Play first 5 seconds\n */\n async testPlayAudio(fromMs: number, toMs: number) {\n // Render the audio using the existing renderAudio method\n const renderedBuffer = await this.renderAudio(fromMs, toMs);\n\n // Create a regular AudioContext for playback\n const playbackContext = new AudioContext();\n\n // Create a buffer source and connect it\n const bufferSource = playbackContext.createBufferSource();\n bufferSource.buffer = renderedBuffer;\n bufferSource.connect(playbackContext.destination);\n\n // Start playback immediately\n bufferSource.start(0);\n\n // Return a promise that resolves when playback ends\n return new Promise<void>((resolve) => {\n bufferSource.onended = () => {\n playbackContext.close();\n resolve();\n };\n });\n }\n\n async loadMd5Sums() {\n const efElements = this.efElements;\n const loaderTasks: Promise<any>[] = [];\n for (const el of efElements) {\n const md5SumLoader = (el as any).md5SumLoader;\n if (md5SumLoader instanceof Task) {\n md5SumLoader.run();\n loaderTasks.push(md5SumLoader.taskComplete);\n }\n }\n\n await Promise.all(loaderTasks);\n\n efElements.forEach((el) => {\n if (\"productionSrc\" in el && el.productionSrc instanceof Function) {\n el.setAttribute(\"src\", el.productionSrc());\n }\n });\n }\n\n frameTask = new Task(this, {\n // autoRun: EF_INTERACTIVE,\n autoRun: false,\n args: () => [this.ownCurrentTimeMs, this.currentTimeMs] as const,\n task: async ([ownCurrentTimeMs, currentTimeMs]) => {\n if (this.isRootTimegroup) {\n await withSpan(\n \"timegroup.frameTask\",\n {\n timegroupId: this.id || \"unknown\",\n ownCurrentTimeMs,\n currentTimeMs,\n },\n undefined,\n async () => {\n await this.waitForFrameTasks();\n await this.#executeCustomFrameTasks();\n updateAnimations(this);\n },\n );\n } else {\n // Non-root timegroups execute their custom frame tasks when called\n await this.#executeCustomFrameTasks();\n }\n },\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 seekTask = new Task(this, {\n autoRun: false,\n args: () => [this.#pendingSeekTime ?? this.#currentTime] as const,\n onComplete: () => {},\n task: async ([targetTime]) => {\n if (this.playbackController) {\n await this.playbackController.seekTask.taskComplete;\n return this.currentTime;\n }\n\n if (!this.isRootTimegroup) {\n return;\n }\n return 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 await this.waitForMediaDurations();\n const newTime = Math.max(\n 0,\n Math.min(targetTime ?? 0, this.durationMs / 1000),\n );\n if (isTracingEnabled()) {\n span.setAttribute(\"newTime\", newTime);\n }\n // Apply the clamped time back to currentTime\n\n this.#currentTime = newTime;\n this.requestUpdate(\"currentTime\");\n await this.runThrottledFrameTask();\n this.saveTimeToLocalStorage(this.#currentTime);\n this.#seekInProgress = false;\n return newTime;\n },\n );\n },\n });\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timegroup\": EFTimegroup & Element;\n }\n}\n"],"mappings":"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"}
@@ -4,10 +4,10 @@ import { MediaEngine } from "../transcoding/types/index.js";
4
4
  import { EFMedia } from "./EFMedia.js";
5
5
  import { VideoBufferState } from "./EFMedia/videoTasks/makeVideoBufferTask.js";
6
6
  import { Task } from "@lit/task";
7
- import * as lit2 from "lit";
7
+ import * as lit7 from "lit";
8
8
  import { PropertyValueMap } from "lit";
9
9
  import * as mediabunny0 from "mediabunny";
10
- import * as lit_html2 from "lit-html";
10
+ import * as lit_html7 from "lit-html";
11
11
  import * as lit_html_directives_ref0 from "lit-html/directives/ref";
12
12
 
13
13
  //#region src/elements/EFVideo.d.ts
@@ -21,7 +21,7 @@ interface LoadingState {
21
21
  }
22
22
  declare const EFVideo_base: typeof EFMedia;
23
23
  declare class EFVideo extends EFVideo_base {
24
- static styles: lit2.CSSResult[];
24
+ static styles: lit7.CSSResult[];
25
25
  canvasRef: lit_html_directives_ref0.Ref<HTMLCanvasElement>;
26
26
  /**
27
27
  * Duration in milliseconds for video buffering ahead of current time
@@ -60,7 +60,7 @@ declare class EFVideo extends EFVideo_base {
60
60
  };
61
61
  constructor();
62
62
  protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
63
- render(): lit_html2.TemplateResult<1>;
63
+ render(): lit_html7.TemplateResult<1>;
64
64
  get canvasElement(): HTMLCanvasElement | undefined;
65
65
  frameTask: Task<readonly [number], void>;
66
66
  /**