@editframe/elements 0.34.9-beta → 0.34.11-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/elements/EFAudio.d.ts +2 -2
- package/dist/elements/EFImage.d.ts +6 -0
- package/dist/elements/EFImage.js +16 -1
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFText.d.ts +4 -4
- package/dist/elements/EFText.js +21 -31
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.d.ts +4 -4
- package/dist/elements/EFVideo.d.ts +4 -4
- package/dist/elements/EFWaveform.d.ts +4 -4
- package/dist/gui/EFConfiguration.d.ts +4 -4
- package/dist/gui/EFFilmstrip.d.ts +2 -2
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPreview.d.ts +4 -4
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFWorkbench.d.ts +4 -4
- package/dist/gui/hierarchy/EFHierarchy.d.ts +4 -4
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +2 -2
- package/dist/gui/tree/EFTree.d.ts +4 -4
- package/dist/gui/tree/EFTreeItem.d.ts +4 -4
- package/dist/preview/renderTimegroupPreview.js +40 -13
- package/dist/preview/renderTimegroupPreview.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FrameRenderable, FrameState, FrameTask } from "../preview/FrameController.js";
|
|
2
2
|
import { EFMedia } from "./EFMedia.js";
|
|
3
|
-
import * as
|
|
3
|
+
import * as lit_html3 from "lit-html";
|
|
4
4
|
import * as lit_html_directives_ref_js1 from "lit-html/directives/ref.js";
|
|
5
5
|
|
|
6
6
|
//#region src/elements/EFAudio.d.ts
|
|
@@ -19,7 +19,7 @@ declare class EFAudio extends EFAudio_base implements FrameRenderable {
|
|
|
19
19
|
volume: number;
|
|
20
20
|
audioElementRef: lit_html_directives_ref_js1.Ref<HTMLAudioElement>;
|
|
21
21
|
protected updated(changedProperties: Map<PropertyKey, unknown>): void;
|
|
22
|
-
render():
|
|
22
|
+
render(): lit_html3.TemplateResult<1>;
|
|
23
23
|
/**
|
|
24
24
|
* @deprecated Use FrameRenderable methods (prepareFrame, renderFrame) via FrameController instead.
|
|
25
25
|
* This is a compatibility wrapper that delegates to the new system.
|
|
@@ -20,6 +20,12 @@ declare class EFImage extends EFImage_base implements FrameRenderable {
|
|
|
20
20
|
* @public
|
|
21
21
|
*/
|
|
22
22
|
get renderVersion(): number;
|
|
23
|
+
/**
|
|
24
|
+
* Get whether the image has an alpha channel.
|
|
25
|
+
* Used to determine if we should encode as PNG (alpha) or JPEG (no alpha).
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
get hasAlpha(): boolean;
|
|
23
29
|
set assetId(value: string | null);
|
|
24
30
|
get assetId(): string | null;
|
|
25
31
|
render(): lit_html1.TemplateResult<1>;
|
package/dist/elements/EFImage.js
CHANGED
|
@@ -44,6 +44,19 @@ let EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitEle
|
|
|
44
44
|
get renderVersion() {
|
|
45
45
|
return this.#renderVersion;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether the loaded image has an alpha channel.
|
|
49
|
+
* JPEG images don't have alpha, PNG/WebP may have alpha.
|
|
50
|
+
*/
|
|
51
|
+
#hasAlpha = true;
|
|
52
|
+
/**
|
|
53
|
+
* Get whether the image has an alpha channel.
|
|
54
|
+
* Used to determine if we should encode as PNG (alpha) or JPEG (no alpha).
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
get hasAlpha() {
|
|
58
|
+
return this.#hasAlpha;
|
|
59
|
+
}
|
|
47
60
|
#assetId = null;
|
|
48
61
|
set assetId(value) {
|
|
49
62
|
this.#assetId = value;
|
|
@@ -103,6 +116,8 @@ let EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitEle
|
|
|
103
116
|
const image = new Image();
|
|
104
117
|
const blob = await response.blob();
|
|
105
118
|
signal?.throwIfAborted();
|
|
119
|
+
const mimeType = blob.type.toLowerCase();
|
|
120
|
+
this.#hasAlpha = !mimeType.includes("jpeg") && !mimeType.includes("jpg");
|
|
106
121
|
image.src = URL.createObjectURL(blob);
|
|
107
122
|
await new Promise((resolve, reject) => {
|
|
108
123
|
if (signal?.aborted) {
|
|
@@ -163,8 +178,8 @@ let EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitEle
|
|
|
163
178
|
if (changedProperties.has("src") || changedProperties.has("assetId")) {
|
|
164
179
|
this.#imageLoaded = false;
|
|
165
180
|
this.loadImage().catch(() => {});
|
|
181
|
+
this.#renderVersion++;
|
|
166
182
|
}
|
|
167
|
-
if (changedProperties.size > 0) this.#renderVersion++;
|
|
168
183
|
}
|
|
169
184
|
/**
|
|
170
185
|
* Get the natural dimensions of the image.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFImage.js","names":["EFImage","#renderVersion","#assetId","#imageLoaded","#lastLoadedPath","#imageLoadPromise","#doLoadImage"],"sources":["../../src/elements/EFImage.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport {\n type FrameRenderable,\n type FrameState,\n createFrameTaskWrapper,\n PRIORITY_IMAGE,\n} from \"../preview/FrameController.js\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\n\n@customElement(\"ef-image\")\nexport class EFImage extends EFTemporal(\n EFSourceMixin(FetchMixin(LitElement), {\n assetType: \"image_files\",\n }),\n) implements FrameRenderable {\n static styles = [\n css`\n :host {\n display: block;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n canvas, img {\n position: static;\n all: initial;\n width: 100%;\n height: 100%;\n }\n `,\n ];\n\n imageRef = createRef<HTMLImageElement>();\n canvasRef = createRef<HTMLCanvasElement>();\n\n /**\n * Render version counter - increments when visual content changes.\n * Used by RenderContext to cache rendered dataURLs.\n */\n #renderVersion = 0;\n\n /**\n * Get the current render version.\n * Version increments when src or assetId changes.\n * @public\n */\n get renderVersion(): number {\n return this.#renderVersion;\n }\n\n #assetId: string | null = null;\n @property({ type: String, attribute: \"asset-id\", reflect: true })\n set assetId(value: string | null) {\n this.#assetId = value;\n }\n\n get assetId() {\n return this.#assetId ?? this.getAttribute(\"asset-id\");\n }\n\n render() {\n const assetPath = this.assetPath();\n const isDirectUrl = this.isDirectUrl(assetPath);\n return isDirectUrl\n ? html`<img ${ref(this.imageRef)} src=${assetPath} />`\n : html`<canvas ${ref(this.canvasRef)}></canvas>`;\n }\n\n private isDirectUrl(src: string): boolean {\n return src.startsWith(\"http://\") || src.startsWith(\"https://\") || src.startsWith(\"data:\");\n }\n\n assetPath() {\n if (this.assetId) {\n return `${this.apiHost}/api/v1/image_files/${this.assetId}`;\n }\n if (this.isDirectUrl(this.src)) {\n return this.src;\n }\n // Normalize the path: remove leading slash and any double slashes\n let normalizedSrc = this.src.startsWith(\"/\")\n ? this.src.slice(1)\n : this.src;\n normalizedSrc = normalizedSrc.replace(/^\\/+/, \"\");\n // Use production API format for local files\n return `/api/v1/assets/local/image?src=${encodeURIComponent(normalizedSrc)}`;\n }\n\n get hasOwnDuration() {\n return this.hasExplicitDuration;\n }\n\n // ============================================================================\n // Image Loading - async method instead of Task\n // ============================================================================\n\n #imageLoaded = false;\n #imageLoadPromise: Promise<void> | null = null;\n #lastLoadedPath: string | null = null;\n\n /**\n * Load image from the configured source\n */\n async loadImage(signal?: AbortSignal): Promise<void> {\n const assetPath = this.assetPath();\n\n // Skip if no source\n if (!this.src && !this.assetId) {\n return;\n }\n\n // Return cached if path hasn't changed\n if (this.#imageLoaded && this.#lastLoadedPath === assetPath) {\n return;\n }\n\n // Return in-flight promise\n if (this.#imageLoadPromise && this.#lastLoadedPath === assetPath) {\n return this.#imageLoadPromise;\n }\n\n // For direct URLs, the img element handles loading\n if (this.isDirectUrl(assetPath)) {\n this.#imageLoaded = true;\n this.#lastLoadedPath = assetPath;\n return;\n }\n\n this.#lastLoadedPath = assetPath;\n this.#imageLoadPromise = this.#doLoadImage(assetPath, signal);\n\n try {\n await this.#imageLoadPromise;\n this.#imageLoaded = true;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Canvas not ready errors are expected during lifecycle\n if (error instanceof Error && error.message === \"Canvas not ready\") {\n return;\n }\n console.error(\"EFImage load error\", error);\n } finally {\n this.#imageLoadPromise = null;\n }\n }\n\n async #doLoadImage(assetPath: string, signal?: AbortSignal): Promise<void> {\n const response = await this.fetch(assetPath, { signal });\n signal?.throwIfAborted();\n \n const image = new Image();\n const blob = await response.blob();\n signal?.throwIfAborted();\n \n image.src = URL.createObjectURL(blob);\n\n await new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n URL.revokeObjectURL(image.src);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n \n const abortHandler = () => {\n URL.revokeObjectURL(image.src);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", abortHandler, { once: true });\n \n image.onload = () => {\n signal?.removeEventListener(\"abort\", abortHandler);\n resolve();\n };\n image.onerror = (error) => {\n signal?.removeEventListener(\"abort\", abortHandler);\n URL.revokeObjectURL(image.src);\n reject(error);\n };\n });\n\n signal?.throwIfAborted();\n\n if (!this.canvasRef.value) throw new Error(\"Canvas not ready\");\n const ctx = this.canvasRef.value.getContext(\"2d\");\n if (!ctx) throw new Error(\"Canvas 2d context not ready\");\n this.canvasRef.value.width = image.width;\n this.canvasRef.value.height = image.height;\n ctx.drawImage(image, 0, 0);\n \n URL.revokeObjectURL(image.src);\n }\n\n /**\n * @deprecated Use FrameRenderable methods (prepareFrame, renderFrame) via FrameController instead.\n * This is a compatibility wrapper that delegates to the new system.\n */\n frameTask = createFrameTaskWrapper(this);\n\n // ============================================================================\n // FrameRenderable Implementation\n // Centralized frame control - no Lit Tasks\n // ============================================================================\n\n /**\n * Query readiness state for a given time.\n * @implements FrameRenderable\n */\n getFrameState(_timeMs: number): FrameState {\n return {\n needsPreparation: !this.#imageLoaded,\n isReady: this.#imageLoaded,\n priority: PRIORITY_IMAGE,\n };\n }\n\n /**\n * Async preparation - waits for image to load.\n * @implements FrameRenderable\n */\n async prepareFrame(_timeMs: number, signal: AbortSignal): Promise<void> {\n await this.loadImage(signal);\n signal.throwIfAborted();\n }\n\n /**\n * Synchronous render - image is already displayed via img element or canvas.\n * @implements FrameRenderable\n */\n renderFrame(_timeMs: number): void {\n // Image is already displayed - no explicit render action needed\n }\n\n // ============================================================================\n // End FrameRenderable Implementation\n // ============================================================================\n\n protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {\n super.updated(changedProperties);\n \n // Trigger image load when src or assetId changes\n if (changedProperties.has(\"src\") || changedProperties.has(\"assetId\")) {\n this.#imageLoaded = false;\n this.loadImage().catch(() => {});\n }\n\n // Increment render version on any property change.\n if (changedProperties.size > 0) {\n this.#renderVersion++;\n }\n }\n\n /**\n * Get the natural dimensions of the image.\n * Returns null if the image hasn't loaded yet.\n *\n * @public\n */\n getNaturalDimensions(): { width: number; height: number } | null {\n // For direct URLs, check img element\n const img = this.imageRef.value;\n if (img && img.naturalWidth > 0 && img.naturalHeight > 0) {\n return {\n width: img.naturalWidth,\n height: img.naturalHeight,\n };\n }\n\n // For canvas-based images, check canvas dimensions\n const canvas = this.canvasRef.value;\n if (canvas && canvas.width > 0 && canvas.height > 0) {\n return {\n width: canvas.width,\n height: canvas.height,\n };\n }\n\n return null;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-image\": EFImage;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFImage.js","names":["EFImage","#renderVersion","#hasAlpha","#assetId","#imageLoaded","#lastLoadedPath","#imageLoadPromise","#doLoadImage"],"sources":["../../src/elements/EFImage.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport {\n type FrameRenderable,\n type FrameState,\n createFrameTaskWrapper,\n PRIORITY_IMAGE,\n} from \"../preview/FrameController.js\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\n\n@customElement(\"ef-image\")\nexport class EFImage extends EFTemporal(\n EFSourceMixin(FetchMixin(LitElement), {\n assetType: \"image_files\",\n }),\n) implements FrameRenderable {\n static styles = [\n css`\n :host {\n display: block;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n canvas, img {\n position: static;\n all: initial;\n width: 100%;\n height: 100%;\n }\n `,\n ];\n\n imageRef = createRef<HTMLImageElement>();\n canvasRef = createRef<HTMLCanvasElement>();\n\n /**\n * Render version counter - increments when visual content changes.\n * Used by RenderContext to cache rendered dataURLs.\n */\n #renderVersion = 0;\n\n /**\n * Get the current render version.\n * Version increments when src or assetId changes.\n * @public\n */\n get renderVersion(): number {\n return this.#renderVersion;\n }\n\n /**\n * Whether the loaded image has an alpha channel.\n * JPEG images don't have alpha, PNG/WebP may have alpha.\n */\n #hasAlpha = true; // Default to true (preserve alpha) until we know otherwise\n\n /**\n * Get whether the image has an alpha channel.\n * Used to determine if we should encode as PNG (alpha) or JPEG (no alpha).\n * @public\n */\n get hasAlpha(): boolean {\n return this.#hasAlpha;\n }\n\n #assetId: string | null = null;\n @property({ type: String, attribute: \"asset-id\", reflect: true })\n set assetId(value: string | null) {\n this.#assetId = value;\n }\n\n get assetId() {\n return this.#assetId ?? this.getAttribute(\"asset-id\");\n }\n\n render() {\n const assetPath = this.assetPath();\n const isDirectUrl = this.isDirectUrl(assetPath);\n return isDirectUrl\n ? html`<img ${ref(this.imageRef)} src=${assetPath} />`\n : html`<canvas ${ref(this.canvasRef)}></canvas>`;\n }\n\n private isDirectUrl(src: string): boolean {\n return src.startsWith(\"http://\") || src.startsWith(\"https://\") || src.startsWith(\"data:\");\n }\n\n assetPath() {\n if (this.assetId) {\n return `${this.apiHost}/api/v1/image_files/${this.assetId}`;\n }\n if (this.isDirectUrl(this.src)) {\n return this.src;\n }\n // Normalize the path: remove leading slash and any double slashes\n let normalizedSrc = this.src.startsWith(\"/\")\n ? this.src.slice(1)\n : this.src;\n normalizedSrc = normalizedSrc.replace(/^\\/+/, \"\");\n // Use production API format for local files\n return `/api/v1/assets/local/image?src=${encodeURIComponent(normalizedSrc)}`;\n }\n\n get hasOwnDuration() {\n return this.hasExplicitDuration;\n }\n\n // ============================================================================\n // Image Loading - async method instead of Task\n // ============================================================================\n\n #imageLoaded = false;\n #imageLoadPromise: Promise<void> | null = null;\n #lastLoadedPath: string | null = null;\n\n /**\n * Load image from the configured source\n */\n async loadImage(signal?: AbortSignal): Promise<void> {\n const assetPath = this.assetPath();\n\n // Skip if no source\n if (!this.src && !this.assetId) {\n return;\n }\n\n // Return cached if path hasn't changed\n if (this.#imageLoaded && this.#lastLoadedPath === assetPath) {\n return;\n }\n\n // Return in-flight promise\n if (this.#imageLoadPromise && this.#lastLoadedPath === assetPath) {\n return this.#imageLoadPromise;\n }\n\n // For direct URLs, the img element handles loading\n if (this.isDirectUrl(assetPath)) {\n this.#imageLoaded = true;\n this.#lastLoadedPath = assetPath;\n return;\n }\n\n this.#lastLoadedPath = assetPath;\n this.#imageLoadPromise = this.#doLoadImage(assetPath, signal);\n\n try {\n await this.#imageLoadPromise;\n this.#imageLoaded = true;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Canvas not ready errors are expected during lifecycle\n if (error instanceof Error && error.message === \"Canvas not ready\") {\n return;\n }\n console.error(\"EFImage load error\", error);\n } finally {\n this.#imageLoadPromise = null;\n }\n }\n\n async #doLoadImage(assetPath: string, signal?: AbortSignal): Promise<void> {\n const response = await this.fetch(assetPath, { signal });\n signal?.throwIfAborted();\n \n const image = new Image();\n const blob = await response.blob();\n signal?.throwIfAborted();\n \n // Detect if image has alpha channel based on MIME type\n // JPEG images don't have alpha, PNG/WebP may have alpha\n const mimeType = blob.type.toLowerCase();\n this.#hasAlpha = !mimeType.includes(\"jpeg\") && !mimeType.includes(\"jpg\");\n \n image.src = URL.createObjectURL(blob);\n\n await new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n URL.revokeObjectURL(image.src);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n \n const abortHandler = () => {\n URL.revokeObjectURL(image.src);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", abortHandler, { once: true });\n \n image.onload = () => {\n signal?.removeEventListener(\"abort\", abortHandler);\n resolve();\n };\n image.onerror = (error) => {\n signal?.removeEventListener(\"abort\", abortHandler);\n URL.revokeObjectURL(image.src);\n reject(error);\n };\n });\n\n signal?.throwIfAborted();\n\n if (!this.canvasRef.value) throw new Error(\"Canvas not ready\");\n const ctx = this.canvasRef.value.getContext(\"2d\");\n if (!ctx) throw new Error(\"Canvas 2d context not ready\");\n this.canvasRef.value.width = image.width;\n this.canvasRef.value.height = image.height;\n ctx.drawImage(image, 0, 0);\n \n URL.revokeObjectURL(image.src);\n }\n\n /**\n * @deprecated Use FrameRenderable methods (prepareFrame, renderFrame) via FrameController instead.\n * This is a compatibility wrapper that delegates to the new system.\n */\n frameTask = createFrameTaskWrapper(this);\n\n // ============================================================================\n // FrameRenderable Implementation\n // Centralized frame control - no Lit Tasks\n // ============================================================================\n\n /**\n * Query readiness state for a given time.\n * @implements FrameRenderable\n */\n getFrameState(_timeMs: number): FrameState {\n return {\n needsPreparation: !this.#imageLoaded,\n isReady: this.#imageLoaded,\n priority: PRIORITY_IMAGE,\n };\n }\n\n /**\n * Async preparation - waits for image to load.\n * @implements FrameRenderable\n */\n async prepareFrame(_timeMs: number, signal: AbortSignal): Promise<void> {\n await this.loadImage(signal);\n signal.throwIfAborted();\n }\n\n /**\n * Synchronous render - image is already displayed via img element or canvas.\n * @implements FrameRenderable\n */\n renderFrame(_timeMs: number): void {\n // Image is already displayed - no explicit render action needed\n }\n\n // ============================================================================\n // End FrameRenderable Implementation\n // ============================================================================\n\n protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {\n super.updated(changedProperties);\n \n // Trigger image load when src or assetId changes\n if (changedProperties.has(\"src\") || changedProperties.has(\"assetId\")) {\n this.#imageLoaded = false;\n this.loadImage().catch(() => {});\n // Increment render version only when actual image content changes\n this.#renderVersion++;\n }\n }\n\n /**\n * Get the natural dimensions of the image.\n * Returns null if the image hasn't loaded yet.\n *\n * @public\n */\n getNaturalDimensions(): { width: number; height: number } | null {\n // For direct URLs, check img element\n const img = this.imageRef.value;\n if (img && img.naturalWidth > 0 && img.naturalHeight > 0) {\n return {\n width: img.naturalWidth,\n height: img.naturalHeight,\n };\n }\n\n // For canvas-based images, check canvas dimensions\n const canvas = this.canvasRef.value;\n if (canvas && canvas.width > 0 && canvas.height > 0) {\n return {\n width: canvas.width,\n height: canvas.height,\n };\n }\n\n return null;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-image\": EFImage;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { TemporalMixinInterface } from "./EFTemporal.js";
|
|
2
2
|
import { EFTextSegment } from "./EFTextSegment.js";
|
|
3
|
-
import * as
|
|
3
|
+
import * as lit3 from "lit";
|
|
4
4
|
import { LitElement, PropertyValueMap } from "lit";
|
|
5
|
-
import * as
|
|
5
|
+
import * as lit_html2 from "lit-html";
|
|
6
6
|
|
|
7
7
|
//#region src/elements/EFText.d.ts
|
|
8
8
|
type SplitMode = "line" | "word" | "char";
|
|
9
9
|
declare const EFText_base: (new (...args: any[]) => TemporalMixinInterface) & typeof LitElement;
|
|
10
10
|
declare class EFText extends EFText_base {
|
|
11
11
|
#private;
|
|
12
|
-
static styles:
|
|
12
|
+
static styles: lit3.CSSResult[];
|
|
13
13
|
split: SplitMode;
|
|
14
14
|
private validateSplit;
|
|
15
15
|
staggerMs?: number;
|
|
@@ -20,7 +20,7 @@ declare class EFText extends EFText_base {
|
|
|
20
20
|
private _textContent;
|
|
21
21
|
private _templateElement;
|
|
22
22
|
private _segmentsReadyResolvers;
|
|
23
|
-
render():
|
|
23
|
+
render(): lit_html2.TemplateResult<1>;
|
|
24
24
|
set textContent(value: string | null);
|
|
25
25
|
get textContent(): string;
|
|
26
26
|
/**
|
package/dist/elements/EFText.js
CHANGED
|
@@ -99,34 +99,28 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
|
99
99
|
await Promise.all(this.segments.map((seg) => seg.updateComplete));
|
|
100
100
|
return this.segments;
|
|
101
101
|
}
|
|
102
|
-
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
103
102
|
let segments = this.segments;
|
|
104
103
|
if (segments.length > 0) {
|
|
105
104
|
await Promise.all(segments.map((seg) => seg.updateComplete));
|
|
106
|
-
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
107
|
-
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
108
105
|
this.#segmentsInitialized = true;
|
|
109
|
-
return
|
|
106
|
+
return segments;
|
|
110
107
|
}
|
|
111
|
-
return new Promise((resolve) => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
segments
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const timeout = setTimeout(() => {
|
|
110
|
+
const index = this._segmentsReadyResolvers.indexOf(resolveWithSegments);
|
|
111
|
+
if (index > -1) this._segmentsReadyResolvers.splice(index, 1);
|
|
112
|
+
reject(/* @__PURE__ */ new Error("Timeout waiting for text segments to be created"));
|
|
113
|
+
}, 5e3);
|
|
114
|
+
const resolveWithSegments = () => {
|
|
115
|
+
clearTimeout(timeout);
|
|
116
|
+
const segments$1 = this.segments;
|
|
117
|
+
Promise.all(segments$1.map((seg) => seg.updateComplete)).then(() => {
|
|
118
|
+
this.#segmentsInitialized = true;
|
|
119
|
+
resolve(segments$1);
|
|
123
120
|
});
|
|
124
|
-
else if (attempts < maxAttempts) {
|
|
125
|
-
attempts++;
|
|
126
|
-
requestAnimationFrame(checkSegments);
|
|
127
|
-
} else resolve([]);
|
|
128
121
|
};
|
|
129
|
-
|
|
122
|
+
this._segmentsReadyResolvers.push(resolveWithSegments);
|
|
123
|
+
if (segments.length === 0 && this.isConnected) this.splitText();
|
|
130
124
|
});
|
|
131
125
|
}
|
|
132
126
|
connectedCallback() {
|
|
@@ -309,20 +303,16 @@ let EFText = class EFText$1 extends EFTemporal(LitElement) {
|
|
|
309
303
|
}
|
|
310
304
|
this.appendChild(fragment);
|
|
311
305
|
if (templateToPreserve) this.appendChild(templateToPreserve);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
updateAnimations(this.rootTimegroup || this);
|
|
316
|
-
});
|
|
306
|
+
const segmentElements = this.segments;
|
|
307
|
+
Promise.all(segmentElements.map((seg) => seg.updateComplete)).then(() => {
|
|
308
|
+
updateAnimations(this.rootTimegroup || this);
|
|
317
309
|
});
|
|
318
310
|
this.lastTextContent = text;
|
|
319
311
|
this._textContent = text;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
resolve();
|
|
323
|
-
});
|
|
324
|
-
this._segmentsReadyResolvers = [];
|
|
312
|
+
this._segmentsReadyResolvers.forEach((resolve) => {
|
|
313
|
+
resolve();
|
|
325
314
|
});
|
|
315
|
+
this._segmentsReadyResolvers = [];
|
|
326
316
|
}
|
|
327
317
|
detectWordBoundaries(text) {
|
|
328
318
|
const boundaries = /* @__PURE__ */ new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFText.js","names":["EFText","textNodes: ChildNode[]","#segmentsInitialized","wordBoundaries: Map<number, number> | null","currentWordIndex: number | null","currentWordSpan: HTMLSpanElement | null","staggerOffset: number | undefined","wordIndexForStagger: number","result: string[]"],"sources":["../../src/elements/EFText.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport type { EFTimegroup } from \"./EFTimegroup.js\";\nimport { evaluateEasing } from \"./easingUtils.js\";\nimport type { EFTextSegment } from \"./EFTextSegment.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\nimport type { AnimatableElement } from \"./updateAnimations.js\";\n\nexport type SplitMode = \"line\" | \"word\" | \"char\";\n\n@customElement(\"ef-text\")\nexport class EFText extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-flex;\n white-space: normal;\n line-height: 1;\n gap: 0;\n }\n :host([split=\"char\"]) {\n white-space: pre;\n }\n :host([split=\"line\"]) {\n display: flex;\n flex-direction: column;\n }\n ::slotted(*) {\n display: inline-block;\n margin: 0;\n padding: 0;\n }\n .ef-word-wrapper {\n display: inline-block;\n white-space: nowrap;\n }\n `,\n ];\n\n @property({ type: String, reflect: true })\n split: SplitMode = \"word\";\n\n private validateSplit(value: string): SplitMode {\n if (value === \"line\" || value === \"word\" || value === \"char\") {\n return value as SplitMode;\n }\n console.warn(\n `Invalid split value \"${value}\". Must be \"line\", \"word\", or \"char\". Defaulting to \"word\".`,\n );\n return \"word\";\n }\n\n @property({\n type: Number,\n attribute: \"stagger\",\n converter: durationConverter,\n })\n staggerMs?: number;\n\n private validateStagger(value: number | undefined): number | undefined {\n if (value === undefined) return undefined;\n if (value < 0) {\n console.warn(`Invalid stagger value ${value}ms. Must be >= 0. Using 0.`);\n return 0;\n }\n return value;\n }\n\n @property({ type: String, reflect: true })\n easing = \"linear\";\n\n private mutationObserver?: MutationObserver;\n private lastTextContent = \"\";\n private _textContent: string | null = null; // null means not initialized, \"\" means explicitly empty\n private _templateElement: HTMLTemplateElement | null = null;\n private _segmentsReadyResolvers: Array<() => void> = [];\n #segmentsInitialized = false;\n\n render() {\n return html`<slot></slot>`;\n }\n\n // Store text content so we can use it even after DOM is cleared\n set textContent(value: string | null) {\n const newValue = value || \"\";\n // Only update if value actually changed\n if (this._textContent !== newValue) {\n this._textContent = newValue;\n\n // Find template element if not already stored\n if (!this._templateElement && this.isConnected) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Clear any existing text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n // Add new text node if value is not empty\n if (newValue) {\n const textNode = document.createTextNode(newValue);\n this.appendChild(textNode);\n }\n // Trigger re-split\n if (this.isConnected) {\n this.splitText();\n }\n }\n }\n\n get textContent(): string {\n // If _textContent is null, it hasn't been initialized - read from DOM\n if (this._textContent === null) {\n return this.getTextContent();\n }\n // Otherwise use stored value (even if empty string)\n return this._textContent;\n }\n\n /**\n * Get all ef-text-segment elements directly\n * @public\n */\n get segments(): EFTextSegment[] {\n return Array.from(\n this.querySelectorAll(\"ef-text-segment[data-segment-created]\"),\n ) as EFTextSegment[];\n }\n\n /**\n * Returns a promise that resolves when segments are ready (created and connected)\n * Use this to wait for segments after setting textContent or other properties\n * @public\n */\n async whenSegmentsReady(): Promise<EFTextSegment[]> {\n // Wait for text element to be updated first\n await this.updateComplete;\n\n // If no text content, segments will be empty - return immediately\n // Use same logic as splitText to read text content\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return [];\n }\n\n // If segments already initialized and exist, skip RAF waits\n if (this.#segmentsInitialized && this.segments.length > 0) {\n await Promise.all(this.segments.map((seg) => seg.updateComplete));\n return this.segments;\n }\n\n // First time: 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 // Mark as initialized after first successful wait\n this.#segmentsInitialized = true;\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 // Mark as initialized after first successful wait\n this.#segmentsInitialized = true;\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 const trimmedText = text.trim();\n const textStartOffset = text.indexOf(trimmedText);\n if (!text || trimmedText.length === 0) {\n // Clear segments if no text\n const existingSegments = Array.from(\n this.querySelectorAll(\"ef-text-segment\"),\n );\n for (const segment of existingSegments) {\n segment.remove();\n }\n // Clear text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n this.lastTextContent = \"\";\n // Resolve any waiting promises\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n // Reset initialization flag when clearing segments\n this.#segmentsInitialized = false;\n return;\n }\n\n // Reset initialization flag when we're about to create new segments\n this.#segmentsInitialized = false;\n\n const segments = this.splitTextIntoSegments(text);\n const durationMs = this.durationMs || 1000; // Default 1 second if no duration\n\n // For character mode, detect word boundaries to wrap characters within words\n let wordBoundaries: Map<number, number> | null = null;\n if (this.split === \"char\") {\n wordBoundaries = this.detectWordBoundaries(text);\n }\n\n // Clear ALL child nodes (text nodes and segments) by replacing innerHTML\n // This ensures we don't have any leftover text nodes\n const fragment = document.createDocumentFragment();\n\n // Find template element if not already stored\n if (!this._templateElement) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Get template content structure\n // If template exists, clone it; otherwise create default ef-text-segment\n const templateContent = this._templateElement?.content;\n const templateSegments = templateContent\n ? Array.from(templateContent.querySelectorAll(\"ef-text-segment\"))\n : [];\n\n // If no template segments found, we'll create a default one\n const useTemplate = templateSegments.length > 0;\n const segmentsPerTextSegment = useTemplate ? templateSegments.length : 1;\n\n // For character mode with word wrapping, track current word and wrap segments\n let currentWordIndex: number | null = null;\n let currentWordSpan: HTMLSpanElement | null = null;\n let charIndex = 0; // Track position in original text for character mode\n\n // For word splitting, count only word segments (not whitespace) for stagger calculation\n const wordOnlySegments =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg))\n : segments;\n const wordSegmentCount = wordOnlySegments.length;\n\n // Track word index as we iterate (for word mode with duplicate words)\n // This ensures each occurrence of duplicate words gets a unique stagger index\n let wordStaggerIndex = 0;\n\n // Create new segments in a fragment first\n segments.forEach((segmentText, textIndex) => {\n // Calculate stagger offset if stagger is set\n let staggerOffset: number | undefined;\n if (this.staggerMs !== undefined) {\n // For word splitting, whitespace segments should inherit stagger from preceding word\n const isWhitespace = /^\\s+$/.test(segmentText);\n let wordIndexForStagger: number;\n\n if (this.split === \"word\" && isWhitespace) {\n // Whitespace inherits from the preceding word's index\n // Use the word stagger index (which is the index of the word before this whitespace)\n wordIndexForStagger = Math.max(0, wordStaggerIndex - 1);\n } else if (this.split === \"word\") {\n // For word mode, use the current word stagger index (incremented for each word encountered)\n // This ensures duplicate words get unique indices based on their position\n wordIndexForStagger = wordStaggerIndex;\n wordStaggerIndex++;\n } else {\n // For char/line mode, use the actual position in segments array\n wordIndexForStagger = textIndex;\n }\n\n // Apply easing to the stagger offset\n // Normalize index to 0-1 range (0 for first segment, 1 for last segment)\n const normalizedProgress =\n wordSegmentCount > 1\n ? wordIndexForStagger / (wordSegmentCount - 1)\n : 0;\n\n // Apply easing function to get eased progress\n const easedProgress = evaluateEasing(this.easing, normalizedProgress);\n\n // Calculate total stagger duration (last segment gets full stagger)\n const totalStaggerDuration = (wordSegmentCount - 1) * this.staggerMs;\n\n // Apply eased progress to total stagger duration\n staggerOffset = easedProgress * totalStaggerDuration;\n }\n\n if (useTemplate && templateContent) {\n // Clone template content for each text segment\n // This allows multiple ef-text-segment elements per character/word/line\n const clonedContent = templateContent.cloneNode(\n true,\n ) as DocumentFragment;\n const clonedSegments = Array.from(\n clonedContent.querySelectorAll(\"ef-text-segment\"),\n ) as EFTextSegment[];\n\n clonedSegments.forEach((segment, templateIndex) => {\n // Set properties - Lit will process these when element is connected\n segment.segmentText = segmentText;\n // Calculate segment index accounting for multiple segments per text segment\n segment.segmentIndex =\n textIndex * segmentsPerTextSegment + templateIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode with templates, also wrap in word spans\n if (this.split === \"char\" && wordBoundaries) {\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n if (wordIndex !== currentWordIndex) {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.className = \"ef-word-wrapper\";\n }\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n charIndex += segmentText.length;\n } else {\n fragment.appendChild(segment);\n }\n });\n } else {\n // No template - create default ef-text-segment\n const segment = document.createElement(\n \"ef-text-segment\",\n ) as EFTextSegment;\n\n segment.segmentText = segmentText;\n segment.segmentIndex = textIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode, wrap segments within words to prevent line breaks\n if (this.split === \"char\" && wordBoundaries) {\n // Map character index in trimmed text to original text position\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n // Check if we're starting a new word\n if (wordIndex !== currentWordIndex) {\n // Close previous word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n // Start new word span\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.className = \"ef-word-wrapper\";\n }\n // Append segment to current word span\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n // Not part of a word (whitespace/punctuation) - append directly\n // Close current word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n // Update character index for next iteration (in trimmed text)\n charIndex += segmentText.length;\n } else {\n // Not character mode or no word boundaries - append directly\n fragment.appendChild(segment);\n }\n }\n });\n\n // Close any remaining word span\n if (this.split === \"char\" && currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n\n // Ensure segments are connected to DOM before checking for animations\n // Append fragment first, then trigger updates\n\n // Replace all children with the fragment (this clears text nodes and old segments)\n // But preserve the template element if it exists\n const templateToPreserve = this._templateElement;\n while (this.firstChild) {\n const child = this.firstChild;\n // Don't remove the template element\n if (child === templateToPreserve) {\n // Skip template, but we need to move it after the fragment\n // So we'll remove it temporarily and re-add it after\n this.removeChild(child);\n continue;\n }\n this.removeChild(child);\n }\n this.appendChild(fragment);\n // Re-add template element if it existed\n if (templateToPreserve) {\n this.appendChild(templateToPreserve);\n }\n\n // Segments will pause their own animations in connectedCallback\n // Lit will automatically update them when they're connected to the DOM\n // Ensure segments are updated after being connected\n requestAnimationFrame(() => {\n const segmentElements = this.segments;\n Promise.all(segmentElements.map((seg) => seg.updateComplete)).then(() => {\n const rootTimegroup = this.rootTimegroup || this;\n updateAnimations(rootTimegroup as AnimatableElement);\n });\n });\n\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 detectWordBoundaries(text: string): Map<number, number> {\n // Create a map from character index to word index\n // Characters within the same word will have the same word index\n const boundaries = new Map<number, number>();\n const trimmedText = text.trim();\n if (!trimmedText) {\n return boundaries;\n }\n\n // Use Intl.Segmenter to detect word boundaries\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n\n // Find the offset of trimmedText within the original text\n const textStart = text.indexOf(trimmedText);\n\n let wordIndex = 0;\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Map all character positions in this word to the same word index\n for (let i = 0; i < seg.segment.length; i++) {\n const charPos = textStart + seg.index + i;\n boundaries.set(charPos, wordIndex);\n }\n wordIndex++;\n }\n }\n\n return boundaries;\n }\n\n private splitTextIntoSegments(text: string): string[] {\n // Trim text before segmenting to remove leading/trailing whitespace\n const trimmedText = text.trim();\n if (!trimmedText) {\n return [];\n }\n\n switch (this.split) {\n case \"line\": {\n // Split on newlines and trim each line\n const lines = trimmedText.split(/\\r?\\n/);\n return lines\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n }\n case \"word\": {\n // Use Intl.Segmenter for locale-aware word segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n const result: string[] = [];\n\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Word-like segment - add it\n result.push(seg.segment);\n } else if (/^\\s+$/.test(seg.segment)) {\n // Whitespace segment - add it as-is\n result.push(seg.segment);\n } else {\n // Punctuation segment - attach to preceding word if it exists\n if (result.length > 0) {\n const lastItem = result[result.length - 1];\n if (lastItem && !/^\\s+$/.test(lastItem)) {\n result[result.length - 1] = lastItem + seg.segment;\n } else {\n result.push(seg.segment);\n }\n } else {\n result.push(seg.segment);\n }\n }\n }\n\n return result;\n }\n case \"char\": {\n // Use Intl.Segmenter for grapheme-aware character segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"grapheme\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n return segments.map((seg) => seg.segment);\n }\n default:\n return [trimmedText];\n }\n }\n\n get intrinsicDurationMs(): number | undefined {\n // If explicit duration is set, use it\n if (this.hasExplicitDuration) {\n return undefined; // Let explicit duration take precedence\n }\n\n // If we have a parent timegroup that dictates duration (fixed) or inherits it (fit),\n // we should inherit from it instead of using our intrinsic duration.\n // For 'sequence' and 'contain' modes, the parent relies on our intrinsic duration,\n // so we must provide it.\n if (this.parentTimegroup) {\n const mode = this.parentTimegroup.mode;\n if (mode === \"fixed\" || mode === \"fit\") {\n return undefined;\n }\n }\n\n // Otherwise, calculate from content\n // Use _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return 0;\n }\n\n // Use the same splitting logic as splitTextIntoSegments for consistency\n const segments = this.splitTextIntoSegments(text);\n // For word splitting, only count word segments (not whitespace) for intrinsic duration\n const segmentCount =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg)).length || 1\n : segments.length || 1;\n\n return segmentCount * 1000;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text\": EFText;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFText.js","names":["EFText","textNodes: ChildNode[]","#segmentsInitialized","segments","wordBoundaries: Map<number, number> | null","currentWordIndex: number | null","currentWordSpan: HTMLSpanElement | null","staggerOffset: number | undefined","wordIndexForStagger: number","result: string[]"],"sources":["../../src/elements/EFText.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport type { EFTimegroup } from \"./EFTimegroup.js\";\nimport { evaluateEasing } from \"./easingUtils.js\";\nimport type { EFTextSegment } from \"./EFTextSegment.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\nimport type { AnimatableElement } from \"./updateAnimations.js\";\n\nexport type SplitMode = \"line\" | \"word\" | \"char\";\n\n@customElement(\"ef-text\")\nexport class EFText extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-flex;\n white-space: normal;\n line-height: 1;\n gap: 0;\n }\n :host([split=\"char\"]) {\n white-space: pre;\n }\n :host([split=\"line\"]) {\n display: flex;\n flex-direction: column;\n }\n ::slotted(*) {\n display: inline-block;\n margin: 0;\n padding: 0;\n }\n .ef-word-wrapper {\n display: inline-block;\n white-space: nowrap;\n }\n `,\n ];\n\n @property({ type: String, reflect: true })\n split: SplitMode = \"word\";\n\n private validateSplit(value: string): SplitMode {\n if (value === \"line\" || value === \"word\" || value === \"char\") {\n return value as SplitMode;\n }\n console.warn(\n `Invalid split value \"${value}\". Must be \"line\", \"word\", or \"char\". Defaulting to \"word\".`,\n );\n return \"word\";\n }\n\n @property({\n type: Number,\n attribute: \"stagger\",\n converter: durationConverter,\n })\n staggerMs?: number;\n\n private validateStagger(value: number | undefined): number | undefined {\n if (value === undefined) return undefined;\n if (value < 0) {\n console.warn(`Invalid stagger value ${value}ms. Must be >= 0. Using 0.`);\n return 0;\n }\n return value;\n }\n\n @property({ type: String, reflect: true })\n easing = \"linear\";\n\n private mutationObserver?: MutationObserver;\n private lastTextContent = \"\";\n private _textContent: string | null = null; // null means not initialized, \"\" means explicitly empty\n private _templateElement: HTMLTemplateElement | null = null;\n private _segmentsReadyResolvers: Array<() => void> = [];\n #segmentsInitialized = false;\n\n render() {\n return html`<slot></slot>`;\n }\n\n // Store text content so we can use it even after DOM is cleared\n set textContent(value: string | null) {\n const newValue = value || \"\";\n // Only update if value actually changed\n if (this._textContent !== newValue) {\n this._textContent = newValue;\n\n // Find template element if not already stored\n if (!this._templateElement && this.isConnected) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Clear any existing text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n // Add new text node if value is not empty\n if (newValue) {\n const textNode = document.createTextNode(newValue);\n this.appendChild(textNode);\n }\n // Trigger re-split\n if (this.isConnected) {\n this.splitText();\n }\n }\n }\n\n get textContent(): string {\n // If _textContent is null, it hasn't been initialized - read from DOM\n if (this._textContent === null) {\n return this.getTextContent();\n }\n // Otherwise use stored value (even if empty string)\n return this._textContent;\n }\n\n /**\n * Get all ef-text-segment elements directly\n * @public\n */\n get segments(): EFTextSegment[] {\n return Array.from(\n this.querySelectorAll(\"ef-text-segment[data-segment-created]\"),\n ) as EFTextSegment[];\n }\n\n /**\n * Returns a promise that resolves when segments are ready (created and connected)\n * Use this to wait for segments after setting textContent or other properties\n * @public\n */\n async whenSegmentsReady(): Promise<EFTextSegment[]> {\n // Wait for text element to be updated first\n await this.updateComplete;\n\n // If no text content, segments will be empty - return immediately\n // Use same logic as splitText to read text content\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return [];\n }\n\n // If segments already initialized and exist, return immediately (no RAF waits)\n if (this.#segmentsInitialized && this.segments.length > 0) {\n await Promise.all(this.segments.map((seg) => seg.updateComplete));\n return this.segments;\n }\n\n // Check if segments are already in DOM (synchronous check - no RAF)\n let segments = this.segments;\n if (segments.length > 0) {\n // Segments exist, just wait for their Lit updates (no RAF)\n await Promise.all(segments.map((seg) => seg.updateComplete));\n this.#segmentsInitialized = true;\n return segments;\n }\n\n // Segments don't exist yet - use the promise-based mechanism (no RAF polling)\n // This waits for splitText() to complete and resolve the promise\n return new Promise<EFTextSegment[]>((resolve, reject) => {\n const timeout = setTimeout(() => {\n // Remove our resolver if we timeout\n const index = this._segmentsReadyResolvers.indexOf(resolveWithSegments);\n if (index > -1) {\n this._segmentsReadyResolvers.splice(index, 1);\n }\n reject(new Error('Timeout waiting for text segments to be created'));\n }, 5000); // 5 second timeout\n\n const resolveWithSegments = () => {\n clearTimeout(timeout);\n // Wait for segment Lit updates\n const segments = this.segments;\n Promise.all(segments.map((seg) => seg.updateComplete)).then(() => {\n this.#segmentsInitialized = true;\n resolve(segments);\n });\n };\n\n this._segmentsReadyResolvers.push(resolveWithSegments);\n \n // Trigger splitText if it hasn't run yet\n // This handles the case where segments haven't been created at all\n if (segments.length === 0 && this.isConnected) {\n this.splitText();\n }\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n // Find and store template element before any modifications\n this._templateElement = this.querySelector(\"template\");\n\n // Initialize _textContent from DOM if not already set (for declarative usage)\n if (this._textContent === null) {\n this._textContent = this.getTextContent();\n this.lastTextContent = this._textContent;\n }\n\n // Use requestAnimationFrame to ensure DOM is ready\n requestAnimationFrame(() => {\n this.setupMutationObserver();\n this.splitText();\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.mutationObserver?.disconnect();\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n if (\n changedProperties.has(\"split\") ||\n changedProperties.has(\"staggerMs\") ||\n changedProperties.has(\"easing\") ||\n changedProperties.has(\"durationMs\")\n ) {\n this.splitText();\n }\n }\n\n private setupMutationObserver() {\n this.mutationObserver = new MutationObserver(() => {\n // Only react to changes that aren't from our own segment creation\n const currentText = this._textContent || this.getTextContent();\n if (currentText !== this.lastTextContent) {\n this._textContent = currentText;\n this.lastTextContent = currentText;\n this.splitText();\n }\n });\n\n this.mutationObserver.observe(this, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n\n private getTextContent(): string {\n // Get text content, handling both text nodes and HTML content\n let text = \"\";\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent || \"\";\n } else if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Skip ef-text-segment elements (they're created by us)\n if (element.tagName === \"EF-TEXT-SEGMENT\") {\n continue;\n }\n // Skip template elements (they're templates, not content)\n if (element.tagName === \"TEMPLATE\") {\n continue;\n }\n text += element.textContent || \"\";\n }\n }\n return text;\n }\n\n private splitText() {\n // Validate split mode\n const validatedSplit = this.validateSplit(this.split);\n if (validatedSplit !== this.split) {\n this.split = validatedSplit;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Validate stagger\n const validatedStagger = this.validateStagger(this.staggerMs);\n if (validatedStagger !== this.staggerMs) {\n this.staggerMs = validatedStagger;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Read text content - use stored _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n const trimmedText = text.trim();\n const textStartOffset = text.indexOf(trimmedText);\n if (!text || trimmedText.length === 0) {\n // Clear segments if no text\n const existingSegments = Array.from(\n this.querySelectorAll(\"ef-text-segment\"),\n );\n for (const segment of existingSegments) {\n segment.remove();\n }\n // Clear text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n this.lastTextContent = \"\";\n // Resolve any waiting promises\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n // Reset initialization flag when clearing segments\n this.#segmentsInitialized = false;\n return;\n }\n\n // Reset initialization flag when we're about to create new segments\n this.#segmentsInitialized = false;\n\n const segments = this.splitTextIntoSegments(text);\n const durationMs = this.durationMs || 1000; // Default 1 second if no duration\n\n // For character mode, detect word boundaries to wrap characters within words\n let wordBoundaries: Map<number, number> | null = null;\n if (this.split === \"char\") {\n wordBoundaries = this.detectWordBoundaries(text);\n }\n\n // Clear ALL child nodes (text nodes and segments) by replacing innerHTML\n // This ensures we don't have any leftover text nodes\n const fragment = document.createDocumentFragment();\n\n // Find template element if not already stored\n if (!this._templateElement) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Get template content structure\n // If template exists, clone it; otherwise create default ef-text-segment\n const templateContent = this._templateElement?.content;\n const templateSegments = templateContent\n ? Array.from(templateContent.querySelectorAll(\"ef-text-segment\"))\n : [];\n\n // If no template segments found, we'll create a default one\n const useTemplate = templateSegments.length > 0;\n const segmentsPerTextSegment = useTemplate ? templateSegments.length : 1;\n\n // For character mode with word wrapping, track current word and wrap segments\n let currentWordIndex: number | null = null;\n let currentWordSpan: HTMLSpanElement | null = null;\n let charIndex = 0; // Track position in original text for character mode\n\n // For word splitting, count only word segments (not whitespace) for stagger calculation\n const wordOnlySegments =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg))\n : segments;\n const wordSegmentCount = wordOnlySegments.length;\n\n // Track word index as we iterate (for word mode with duplicate words)\n // This ensures each occurrence of duplicate words gets a unique stagger index\n let wordStaggerIndex = 0;\n\n // Create new segments in a fragment first\n segments.forEach((segmentText, textIndex) => {\n // Calculate stagger offset if stagger is set\n let staggerOffset: number | undefined;\n if (this.staggerMs !== undefined) {\n // For word splitting, whitespace segments should inherit stagger from preceding word\n const isWhitespace = /^\\s+$/.test(segmentText);\n let wordIndexForStagger: number;\n\n if (this.split === \"word\" && isWhitespace) {\n // Whitespace inherits from the preceding word's index\n // Use the word stagger index (which is the index of the word before this whitespace)\n wordIndexForStagger = Math.max(0, wordStaggerIndex - 1);\n } else if (this.split === \"word\") {\n // For word mode, use the current word stagger index (incremented for each word encountered)\n // This ensures duplicate words get unique indices based on their position\n wordIndexForStagger = wordStaggerIndex;\n wordStaggerIndex++;\n } else {\n // For char/line mode, use the actual position in segments array\n wordIndexForStagger = textIndex;\n }\n\n // Apply easing to the stagger offset\n // Normalize index to 0-1 range (0 for first segment, 1 for last segment)\n const normalizedProgress =\n wordSegmentCount > 1\n ? wordIndexForStagger / (wordSegmentCount - 1)\n : 0;\n\n // Apply easing function to get eased progress\n const easedProgress = evaluateEasing(this.easing, normalizedProgress);\n\n // Calculate total stagger duration (last segment gets full stagger)\n const totalStaggerDuration = (wordSegmentCount - 1) * this.staggerMs;\n\n // Apply eased progress to total stagger duration\n staggerOffset = easedProgress * totalStaggerDuration;\n }\n\n if (useTemplate && templateContent) {\n // Clone template content for each text segment\n // This allows multiple ef-text-segment elements per character/word/line\n const clonedContent = templateContent.cloneNode(\n true,\n ) as DocumentFragment;\n const clonedSegments = Array.from(\n clonedContent.querySelectorAll(\"ef-text-segment\"),\n ) as EFTextSegment[];\n\n clonedSegments.forEach((segment, templateIndex) => {\n // Set properties - Lit will process these when element is connected\n segment.segmentText = segmentText;\n // Calculate segment index accounting for multiple segments per text segment\n segment.segmentIndex =\n textIndex * segmentsPerTextSegment + templateIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode with templates, also wrap in word spans\n if (this.split === \"char\" && wordBoundaries) {\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n if (wordIndex !== currentWordIndex) {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.className = \"ef-word-wrapper\";\n }\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n charIndex += segmentText.length;\n } else {\n fragment.appendChild(segment);\n }\n });\n } else {\n // No template - create default ef-text-segment\n const segment = document.createElement(\n \"ef-text-segment\",\n ) as EFTextSegment;\n\n segment.segmentText = segmentText;\n segment.segmentIndex = textIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode, wrap segments within words to prevent line breaks\n if (this.split === \"char\" && wordBoundaries) {\n // Map character index in trimmed text to original text position\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n // Check if we're starting a new word\n if (wordIndex !== currentWordIndex) {\n // Close previous word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n // Start new word span\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.className = \"ef-word-wrapper\";\n }\n // Append segment to current word span\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n // Not part of a word (whitespace/punctuation) - append directly\n // Close current word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n // Update character index for next iteration (in trimmed text)\n charIndex += segmentText.length;\n } else {\n // Not character mode or no word boundaries - append directly\n fragment.appendChild(segment);\n }\n }\n });\n\n // Close any remaining word span\n if (this.split === \"char\" && currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n\n // Ensure segments are connected to DOM before checking for animations\n // Append fragment first, then trigger updates\n\n // Replace all children with the fragment (this clears text nodes and old segments)\n // But preserve the template element if it exists\n const templateToPreserve = this._templateElement;\n while (this.firstChild) {\n const child = this.firstChild;\n // Don't remove the template element\n if (child === templateToPreserve) {\n // Skip template, but we need to move it after the fragment\n // So we'll remove it temporarily and re-add it after\n this.removeChild(child);\n continue;\n }\n this.removeChild(child);\n }\n this.appendChild(fragment);\n // Re-add template element if it existed\n if (templateToPreserve) {\n this.appendChild(templateToPreserve);\n }\n\n // Segments will pause their own animations in connectedCallback\n // Lit will automatically update them when they're connected to the DOM\n // Ensure segments are updated after being connected\n const segmentElements = this.segments;\n Promise.all(segmentElements.map((seg) => seg.updateComplete)).then(() => {\n const rootTimegroup = this.rootTimegroup || this;\n updateAnimations(rootTimegroup as AnimatableElement);\n });\n\n this.lastTextContent = text;\n this._textContent = text;\n\n // Resolve any waiting promises after segments are connected (synchronous)\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n }\n\n private detectWordBoundaries(text: string): Map<number, number> {\n // Create a map from character index to word index\n // Characters within the same word will have the same word index\n const boundaries = new Map<number, number>();\n const trimmedText = text.trim();\n if (!trimmedText) {\n return boundaries;\n }\n\n // Use Intl.Segmenter to detect word boundaries\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n\n // Find the offset of trimmedText within the original text\n const textStart = text.indexOf(trimmedText);\n\n let wordIndex = 0;\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Map all character positions in this word to the same word index\n for (let i = 0; i < seg.segment.length; i++) {\n const charPos = textStart + seg.index + i;\n boundaries.set(charPos, wordIndex);\n }\n wordIndex++;\n }\n }\n\n return boundaries;\n }\n\n private splitTextIntoSegments(text: string): string[] {\n // Trim text before segmenting to remove leading/trailing whitespace\n const trimmedText = text.trim();\n if (!trimmedText) {\n return [];\n }\n\n switch (this.split) {\n case \"line\": {\n // Split on newlines and trim each line\n const lines = trimmedText.split(/\\r?\\n/);\n return lines\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n }\n case \"word\": {\n // Use Intl.Segmenter for locale-aware word segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n const result: string[] = [];\n\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Word-like segment - add it\n result.push(seg.segment);\n } else if (/^\\s+$/.test(seg.segment)) {\n // Whitespace segment - add it as-is\n result.push(seg.segment);\n } else {\n // Punctuation segment - attach to preceding word if it exists\n if (result.length > 0) {\n const lastItem = result[result.length - 1];\n if (lastItem && !/^\\s+$/.test(lastItem)) {\n result[result.length - 1] = lastItem + seg.segment;\n } else {\n result.push(seg.segment);\n }\n } else {\n result.push(seg.segment);\n }\n }\n }\n\n return result;\n }\n case \"char\": {\n // Use Intl.Segmenter for grapheme-aware character segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"grapheme\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n return segments.map((seg) => seg.segment);\n }\n default:\n return [trimmedText];\n }\n }\n\n get intrinsicDurationMs(): number | undefined {\n // If explicit duration is set, use it\n if (this.hasExplicitDuration) {\n return undefined; // Let explicit duration take precedence\n }\n\n // If we have a parent timegroup that dictates duration (fixed) or inherits it (fit),\n // we should inherit from it instead of using our intrinsic duration.\n // For 'sequence' and 'contain' modes, the parent relies on our intrinsic duration,\n // so we must provide it.\n if (this.parentTimegroup) {\n const mode = this.parentTimegroup.mode;\n if (mode === \"fixed\" || mode === \"fit\") {\n return undefined;\n }\n }\n\n // Otherwise, calculate from content\n // Use _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return 0;\n }\n\n // Use the same splitting logic as splitTextIntoSegments for consistency\n const segments = this.splitTextIntoSegments(text);\n // For word splitting, only count word segments (not whitespace) for intrinsic duration\n const segmentCount =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg)).length || 1\n : segments.length || 1;\n\n return segmentCount * 1000;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text\": EFText;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { TemporalMixinInterface } from "./EFTemporal.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit6 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html6 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:
|
|
9
|
+
static styles: lit6.CSSResult[];
|
|
10
10
|
/**
|
|
11
11
|
* Registers animation styles that should be shared across all text segments.
|
|
12
12
|
* This is the correct way to inject animation styles for segments - not via innerHTML.
|
|
@@ -32,7 +32,7 @@ declare class EFTextSegment extends EFTextSegment_base {
|
|
|
32
32
|
* @param id The identifier of the stylesheet to remove
|
|
33
33
|
*/
|
|
34
34
|
static unregisterAnimations(id: string): void;
|
|
35
|
-
render():
|
|
35
|
+
render(): lit_html6.TemplateResult<1>;
|
|
36
36
|
private setCSSVariables;
|
|
37
37
|
protected firstUpdated(): void;
|
|
38
38
|
protected updated(): void;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { FrameRenderable, FrameState, FrameTask } from "../preview/FrameController.js";
|
|
2
2
|
import { EFFramegen } from "../EF_FRAMEGEN.js";
|
|
3
3
|
import { EFMedia } from "./EFMedia.js";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit4 from "lit";
|
|
5
5
|
import { PropertyValueMap } from "lit";
|
|
6
|
-
import * as
|
|
6
|
+
import * as lit_html4 from "lit-html";
|
|
7
7
|
import * as lit_html_directives_ref0 from "lit-html/directives/ref";
|
|
8
8
|
|
|
9
9
|
//#region src/elements/EFVideo.d.ts
|
|
@@ -23,7 +23,7 @@ interface LoadingState {
|
|
|
23
23
|
declare const EFVideo_base: typeof EFMedia;
|
|
24
24
|
declare class EFVideo extends EFVideo_base implements FrameRenderable {
|
|
25
25
|
#private;
|
|
26
|
-
static styles:
|
|
26
|
+
static styles: lit4.CSSResult[];
|
|
27
27
|
canvasRef: lit_html_directives_ref0.Ref<HTMLCanvasElement>;
|
|
28
28
|
/**
|
|
29
29
|
* Duration in milliseconds for video buffering ahead of current time
|
|
@@ -76,7 +76,7 @@ declare class EFVideo extends EFVideo_base implements FrameRenderable {
|
|
|
76
76
|
};
|
|
77
77
|
constructor();
|
|
78
78
|
protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
|
|
79
|
-
render():
|
|
79
|
+
render(): lit_html4.TemplateResult<1>;
|
|
80
80
|
get canvasElement(): HTMLCanvasElement | undefined;
|
|
81
81
|
/**
|
|
82
82
|
* @deprecated Use FrameRenderable methods (prepareFrame, renderFrame) via FrameController instead.
|
|
@@ -3,16 +3,16 @@ import { TemporalMixinInterface } from "./EFTemporal.js";
|
|
|
3
3
|
import { EFVideo } from "./EFVideo.js";
|
|
4
4
|
import { EFAudio } from "./EFAudio.js";
|
|
5
5
|
import { TargetController } from "./TargetController.js";
|
|
6
|
-
import * as
|
|
6
|
+
import * as lit7 from "lit";
|
|
7
7
|
import { LitElement, PropertyValueMap } from "lit";
|
|
8
8
|
import { Ref } from "lit/directives/ref.js";
|
|
9
|
-
import * as
|
|
9
|
+
import * as lit_html7 from "lit-html";
|
|
10
10
|
|
|
11
11
|
//#region src/elements/EFWaveform.d.ts
|
|
12
12
|
declare const EFWaveform_base: (new (...args: any[]) => TemporalMixinInterface) & typeof LitElement;
|
|
13
13
|
declare class EFWaveform extends EFWaveform_base implements FrameRenderable {
|
|
14
14
|
#private;
|
|
15
|
-
static styles:
|
|
15
|
+
static styles: lit7.CSSResult;
|
|
16
16
|
canvasRef: Ref<HTMLCanvasElement>;
|
|
17
17
|
private ctx;
|
|
18
18
|
private styleObserver;
|
|
@@ -24,7 +24,7 @@ declare class EFWaveform extends EFWaveform_base implements FrameRenderable {
|
|
|
24
24
|
* @public
|
|
25
25
|
*/
|
|
26
26
|
get renderVersion(): number;
|
|
27
|
-
render():
|
|
27
|
+
render(): lit_html7.TemplateResult<1>;
|
|
28
28
|
mode: "roundBars" | "bars" | "bricks" | "line" | "curve" | "pixel" | "wave" | "spikes";
|
|
29
29
|
color: string;
|
|
30
30
|
target: string;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as lit5 from "lit";
|
|
2
2
|
import { LitElement } from "lit";
|
|
3
|
-
import * as
|
|
3
|
+
import * as lit_html5 from "lit-html";
|
|
4
4
|
|
|
5
5
|
//#region src/gui/EFConfiguration.d.ts
|
|
6
6
|
declare class EFConfiguration extends LitElement {
|
|
7
|
-
static styles:
|
|
7
|
+
static styles: lit5.CSSResult[];
|
|
8
8
|
efConfiguration: this;
|
|
9
9
|
apiHost?: string;
|
|
10
10
|
signingURL: string;
|
|
@@ -15,7 +15,7 @@ declare class EFConfiguration extends LitElement {
|
|
|
15
15
|
* - "jit": Force JitMediaEngine for all sources (uses /api/v1/transcode/* URLs)
|
|
16
16
|
*/
|
|
17
17
|
mediaEngine?: "cloud" | "local" | "jit";
|
|
18
|
-
render():
|
|
18
|
+
render(): lit_html5.TemplateResult<1>;
|
|
19
19
|
}
|
|
20
20
|
declare global {
|
|
21
21
|
interface HTMLElementTagNameMap {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TemporalMixinInterface } from "../elements/EFTemporal.js";
|
|
2
2
|
import "./timeline/EFTimeline.js";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html11 from "lit-html";
|
|
5
5
|
import * as lit_html_directives_ref0 from "lit-html/directives/ref";
|
|
6
6
|
|
|
7
7
|
//#region src/gui/EFFilmstrip.d.ts
|
|
@@ -22,7 +22,7 @@ declare class EFFilmstrip extends EFFilmstrip_base {
|
|
|
22
22
|
timelineRef: lit_html_directives_ref0.Ref<HTMLElement>;
|
|
23
23
|
connectedCallback(): void;
|
|
24
24
|
protected willUpdate(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
25
|
-
render():
|
|
25
|
+
render(): lit_html11.TemplateResult<1>;
|
|
26
26
|
}
|
|
27
27
|
declare global {
|
|
28
28
|
interface HTMLElementTagNameMap {
|
package/dist/gui/EFPause.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit17 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html17 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFPause.d.ts
|
|
7
7
|
declare const EFPause_base: (new (...args: any[]) => {
|
|
@@ -10,13 +10,13 @@ declare const EFPause_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFPause extends EFPause_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit17.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
get efContext(): ControllableInterface | null;
|
|
16
16
|
connectedCallback(): void;
|
|
17
17
|
disconnectedCallback(): void;
|
|
18
18
|
updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
19
|
-
render():
|
|
19
|
+
render(): lit_html17.TemplateResult<1>;
|
|
20
20
|
handleClick: () => void;
|
|
21
21
|
}
|
|
22
22
|
declare global {
|
package/dist/gui/EFPlay.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit14 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html14 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFPlay.d.ts
|
|
7
7
|
declare const EFPlay_base: (new (...args: any[]) => {
|
|
@@ -10,13 +10,13 @@ declare const EFPlay_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFPlay extends EFPlay_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit14.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
get efContext(): ControllableInterface | null;
|
|
16
16
|
connectedCallback(): void;
|
|
17
17
|
disconnectedCallback(): void;
|
|
18
18
|
updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
19
|
-
render():
|
|
19
|
+
render(): lit_html14.TemplateResult<1>;
|
|
20
20
|
handleClick: () => void;
|
|
21
21
|
}
|
|
22
22
|
declare global {
|
package/dist/gui/EFPreview.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { ContextMixinInterface } from "./ContextMixin.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit10 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html10 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFPreview.d.ts
|
|
7
7
|
declare const EFPreview_base: (new (...args: any[]) => ContextMixinInterface) & typeof LitElement;
|
|
8
8
|
declare class EFPreview extends EFPreview_base {
|
|
9
|
-
static styles:
|
|
9
|
+
static styles: lit10.CSSResult[];
|
|
10
10
|
focusedElement?: HTMLElement;
|
|
11
11
|
/**
|
|
12
12
|
* Find the closest temporal element (timegroup, video, audio, etc.)
|
|
13
13
|
*/
|
|
14
14
|
private findClosestTemporal;
|
|
15
15
|
constructor();
|
|
16
|
-
render():
|
|
16
|
+
render(): lit_html10.TemplateResult<1>;
|
|
17
17
|
}
|
|
18
18
|
declare global {
|
|
19
19
|
interface HTMLElementTagNameMap {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit16 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html16 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFToggleLoop.d.ts
|
|
7
7
|
declare const EFToggleLoop_base: (new (...args: any[]) => {
|
|
@@ -10,9 +10,9 @@ declare const EFToggleLoop_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFToggleLoop extends EFToggleLoop_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit16.CSSResult[];
|
|
14
14
|
get context(): ControllableInterface | null;
|
|
15
|
-
render():
|
|
15
|
+
render(): lit_html16.TemplateResult<1>;
|
|
16
16
|
}
|
|
17
17
|
declare global {
|
|
18
18
|
interface HTMLElementTagNameMap {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit15 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html15 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFTogglePlay.d.ts
|
|
7
7
|
declare const EFTogglePlay_base: (new (...args: any[]) => {
|
|
@@ -10,12 +10,12 @@ declare const EFTogglePlay_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFTogglePlay extends EFTogglePlay_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit15.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
get efContext(): ControllableInterface | null;
|
|
16
16
|
connectedCallback(): void;
|
|
17
17
|
disconnectedCallback(): void;
|
|
18
|
-
render():
|
|
18
|
+
render(): lit_html15.TemplateResult<1>;
|
|
19
19
|
togglePlay: () => void;
|
|
20
20
|
private getPlaybackController;
|
|
21
21
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { ContextMixinInterface } from "./ContextMixin.js";
|
|
2
2
|
import { RenderToVideoOptions } from "../preview/renderTimegroupToVideo.js";
|
|
3
3
|
import "./EFFitScale.js";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit9 from "lit";
|
|
5
5
|
import { LitElement, PropertyValueMap } from "lit";
|
|
6
|
-
import * as
|
|
6
|
+
import * as lit_html9 from "lit-html";
|
|
7
7
|
import * as lit_html_directives_ref_js2 from "lit-html/directives/ref.js";
|
|
8
8
|
|
|
9
9
|
//#region src/gui/EFWorkbench.d.ts
|
|
10
10
|
declare const EFWorkbench_base: (new (...args: any[]) => ContextMixinInterface) & typeof LitElement;
|
|
11
11
|
declare class EFWorkbench extends EFWorkbench_base {
|
|
12
|
-
static styles:
|
|
12
|
+
static styles: lit9.CSSResult[];
|
|
13
13
|
rendering: boolean;
|
|
14
14
|
private panZoomTransform;
|
|
15
15
|
private isExporting;
|
|
@@ -194,7 +194,7 @@ declare class EFWorkbench extends EFWorkbench_base {
|
|
|
194
194
|
updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
|
|
195
195
|
drawOverlays: () => void;
|
|
196
196
|
private renderPlaybackStats;
|
|
197
|
-
render():
|
|
197
|
+
render(): lit_html9.TemplateResult<1>;
|
|
198
198
|
}
|
|
199
199
|
declare global {
|
|
200
200
|
interface HTMLElementTagNameMap {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { SelectionContext } from "../../canvas/selection/selectionContext.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit8 from "lit";
|
|
3
3
|
import { LitElement, PropertyValues } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html8 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/hierarchy/EFHierarchy.d.ts
|
|
7
7
|
declare const EFHierarchy_base: typeof LitElement;
|
|
8
8
|
declare class EFHierarchy extends EFHierarchy_base {
|
|
9
|
-
static styles:
|
|
9
|
+
static styles: lit8.CSSResult[];
|
|
10
10
|
target: string;
|
|
11
11
|
header: string;
|
|
12
12
|
showHeader: boolean;
|
|
@@ -53,7 +53,7 @@ declare class EFHierarchy extends EFHierarchy_base {
|
|
|
53
53
|
private autoSelectFirstRootTimegroup;
|
|
54
54
|
private setupSelectionListener;
|
|
55
55
|
private removeSelectionListener;
|
|
56
|
-
render():
|
|
56
|
+
render(): lit_html8.TemplateResult<1>;
|
|
57
57
|
}
|
|
58
58
|
declare global {
|
|
59
59
|
interface HTMLElementTagNameMap {
|
|
@@ -4,13 +4,13 @@ import { EFAudio } from "../../elements/EFAudio.js";
|
|
|
4
4
|
import { EFTimegroup } from "../../elements/EFTimegroup.js";
|
|
5
5
|
import { EFImage } from "../../elements/EFImage.js";
|
|
6
6
|
import { HierarchyContext } from "./hierarchyContext.js";
|
|
7
|
-
import * as
|
|
7
|
+
import * as lit12 from "lit";
|
|
8
8
|
import { LitElement, PropertyValues, TemplateResult, nothing } from "lit";
|
|
9
9
|
|
|
10
10
|
//#region src/gui/hierarchy/EFHierarchyItem.d.ts
|
|
11
11
|
declare const EFHierarchyItem_base: typeof LitElement;
|
|
12
12
|
declare class EFHierarchyItem<ElementType extends HTMLElement = HTMLElement> extends EFHierarchyItem_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit12.CSSResult[];
|
|
14
14
|
hierarchyContext?: HierarchyContext;
|
|
15
15
|
canvasSelectionContext?: SelectionContext;
|
|
16
16
|
element: ElementType;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { TreeItem } from "./treeContext.js";
|
|
2
2
|
import "./EFTreeItem.js";
|
|
3
|
-
import * as
|
|
3
|
+
import * as lit13 from "lit";
|
|
4
4
|
import { LitElement, PropertyValues } from "lit";
|
|
5
|
-
import * as
|
|
5
|
+
import * as lit_html13 from "lit-html";
|
|
6
6
|
|
|
7
7
|
//#region src/gui/tree/EFTree.d.ts
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ import * as lit_html12 from "lit-html";
|
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
declare class EFTree extends LitElement {
|
|
32
|
-
static styles:
|
|
32
|
+
static styles: lit13.CSSResult;
|
|
33
33
|
/** Tree items to display */
|
|
34
34
|
items: TreeItem[];
|
|
35
35
|
/** Optional header text */
|
|
@@ -48,7 +48,7 @@ declare class EFTree extends LitElement {
|
|
|
48
48
|
protected willUpdate(changedProperties: PropertyValues): void;
|
|
49
49
|
protected updated(changedProperties: PropertyValues): void;
|
|
50
50
|
connectedCallback(): void;
|
|
51
|
-
render():
|
|
51
|
+
render(): lit_html13.TemplateResult<1>;
|
|
52
52
|
}
|
|
53
53
|
declare global {
|
|
54
54
|
interface HTMLElementTagNameMap {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TreeContext, TreeItem } from "./treeContext.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit11 from "lit";
|
|
3
3
|
import { LitElement, nothing } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html12 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/tree/EFTreeItem.d.ts
|
|
7
7
|
|
|
@@ -17,7 +17,7 @@ import * as lit_html13 from "lit-html";
|
|
|
17
17
|
* @fires tree-item-click - When item is clicked (for selection)
|
|
18
18
|
*/
|
|
19
19
|
declare class EFTreeItem extends LitElement {
|
|
20
|
-
static styles:
|
|
20
|
+
static styles: lit11.CSSResult;
|
|
21
21
|
treeContext?: TreeContext;
|
|
22
22
|
item: TreeItem;
|
|
23
23
|
private localExpanded;
|
|
@@ -26,7 +26,7 @@ declare class EFTreeItem extends LitElement {
|
|
|
26
26
|
get hasChildren(): boolean;
|
|
27
27
|
private handleClick;
|
|
28
28
|
private handleExpandClick;
|
|
29
|
-
render():
|
|
29
|
+
render(): lit_html12.TemplateResult<1> | typeof nothing;
|
|
30
30
|
}
|
|
31
31
|
declare global {
|
|
32
32
|
interface HTMLElementTagNameMap {
|
|
@@ -194,7 +194,9 @@ function restoreHiddenNodes(removed) {
|
|
|
194
194
|
}
|
|
195
195
|
/**
|
|
196
196
|
* Unified CSS property sync for all elements (canvas clones and regular elements).
|
|
197
|
-
*
|
|
197
|
+
*
|
|
198
|
+
* Canvas clones use a limited property set matching the original implementation
|
|
199
|
+
* to avoid dimension/layout issues. Regular elements use the full SYNC_PROPERTIES array.
|
|
198
200
|
*
|
|
199
201
|
* @param source - Source element to read styles from
|
|
200
202
|
* @param clone - Clone element to write styles to
|
|
@@ -202,23 +204,50 @@ function restoreHiddenNodes(removed) {
|
|
|
202
204
|
*/
|
|
203
205
|
function syncElementStyles(source, clone, contentSource) {
|
|
204
206
|
const cloneStyle = clone.style;
|
|
205
|
-
const propLen = SYNC_PROPERTIES.length;
|
|
206
207
|
const tagName = source.tagName;
|
|
208
|
+
if (!!contentSource) {
|
|
209
|
+
let cs;
|
|
210
|
+
let contentCs;
|
|
211
|
+
try {
|
|
212
|
+
cs = getComputedStyle(source);
|
|
213
|
+
if (contentSource) contentCs = getComputedStyle(contentSource);
|
|
214
|
+
} catch {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
cloneStyle.position = cs.position;
|
|
218
|
+
cloneStyle.top = cs.top;
|
|
219
|
+
cloneStyle.right = cs.right;
|
|
220
|
+
cloneStyle.bottom = cs.bottom;
|
|
221
|
+
cloneStyle.left = cs.left;
|
|
222
|
+
cloneStyle.margin = cs.margin;
|
|
223
|
+
cloneStyle.zIndex = cs.zIndex;
|
|
224
|
+
cloneStyle.transform = cs.transform;
|
|
225
|
+
cloneStyle.transformOrigin = cs.transformOrigin;
|
|
226
|
+
cloneStyle.opacity = cs.opacity;
|
|
227
|
+
cloneStyle.visibility = cs.visibility;
|
|
228
|
+
cloneStyle.backfaceVisibility = cs.backfaceVisibility;
|
|
229
|
+
cloneStyle.transformStyle = cs.transformStyle;
|
|
230
|
+
if (contentCs) {
|
|
231
|
+
cloneStyle.width = contentCs.width;
|
|
232
|
+
cloneStyle.height = contentCs.height;
|
|
233
|
+
}
|
|
234
|
+
cloneStyle.display = "block";
|
|
235
|
+
cloneStyle.animation = "none";
|
|
236
|
+
cloneStyle.transition = "none";
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const propLen = SYNC_PROPERTIES.length;
|
|
207
240
|
if (HAS_COMPUTED_STYLE_MAP) {
|
|
208
241
|
let srcMap;
|
|
209
|
-
let contentMap;
|
|
210
242
|
try {
|
|
211
243
|
srcMap = source.computedStyleMap();
|
|
212
|
-
if (contentSource) contentMap = contentSource.computedStyleMap();
|
|
213
244
|
} catch {
|
|
214
245
|
return;
|
|
215
246
|
}
|
|
216
247
|
for (let j = 0; j < propLen; j++) {
|
|
217
248
|
const kebab = SYNC_PROPERTIES_KEBAB[j];
|
|
218
249
|
const camel = SYNC_PROPERTIES[j];
|
|
219
|
-
const
|
|
220
|
-
if (!styleMap) continue;
|
|
221
|
-
const srcVal = styleMap.get(kebab);
|
|
250
|
+
const srcVal = srcMap.get(kebab);
|
|
222
251
|
if (!srcVal) continue;
|
|
223
252
|
const strVal = srcVal.toString();
|
|
224
253
|
if (camel === "display") {
|
|
@@ -234,17 +263,14 @@ function syncElementStyles(source, clone, contentSource) {
|
|
|
234
263
|
}
|
|
235
264
|
} else {
|
|
236
265
|
let cs;
|
|
237
|
-
let contentCs;
|
|
238
266
|
try {
|
|
239
267
|
cs = getComputedStyle(source);
|
|
240
|
-
if (contentSource) contentCs = getComputedStyle(contentSource);
|
|
241
268
|
} catch {
|
|
242
269
|
return;
|
|
243
270
|
}
|
|
244
271
|
const srcStyle = cs;
|
|
245
|
-
const contentStyle = contentCs;
|
|
246
272
|
for (const prop of SYNC_PROPERTIES) {
|
|
247
|
-
const srcVal =
|
|
273
|
+
const srcVal = srcStyle[prop];
|
|
248
274
|
if (!srcVal) continue;
|
|
249
275
|
if (prop === "display") {
|
|
250
276
|
cloneStyle.display = srcVal === "none" && !(tagName && (tagName === "EF-CAPTIONS-ACTIVE-WORD" || tagName === "EF-CAPTIONS-BEFORE-ACTIVE-WORD" || tagName === "EF-CAPTIONS-AFTER-ACTIVE-WORD" || tagName === "EF-CAPTIONS-SEGMENT")) ? "block" : srcVal;
|
|
@@ -359,7 +385,8 @@ function buildCloneStructure(source, timeMs) {
|
|
|
359
385
|
const clone$1 = document.createElement("canvas");
|
|
360
386
|
clone$1.width = shadowCanvas.width || srcEl.clientWidth;
|
|
361
387
|
clone$1.height = shadowCanvas.height || srcEl.clientHeight;
|
|
362
|
-
if (srcEl.tagName === "EF-
|
|
388
|
+
if (srcEl.tagName === "EF-WAVEFORM") clone$1.dataset.preserveAlpha = "true";
|
|
389
|
+
else if (srcEl.tagName === "EF-IMAGE" && "hasAlpha" in srcEl && srcEl.hasAlpha) clone$1.dataset.preserveAlpha = "true";
|
|
363
390
|
const ctx = clone$1.getContext("2d");
|
|
364
391
|
if (ctx) try {
|
|
365
392
|
ctx.drawImage(shadowCanvas, 0, 0);
|
|
@@ -384,7 +411,7 @@ function buildCloneStructure(source, timeMs) {
|
|
|
384
411
|
const clone$1 = document.createElement("canvas");
|
|
385
412
|
clone$1.width = shadowImg.naturalWidth;
|
|
386
413
|
clone$1.height = shadowImg.naturalHeight;
|
|
387
|
-
clone$1.dataset.preserveAlpha = "true";
|
|
414
|
+
if (srcEl.tagName === "EF-IMAGE" && "hasAlpha" in srcEl && srcEl.hasAlpha) clone$1.dataset.preserveAlpha = "true";
|
|
388
415
|
const ctx = clone$1.getContext("2d");
|
|
389
416
|
if (ctx) try {
|
|
390
417
|
ctx.drawImage(shadowImg, 0, 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderTimegroupPreview.js","names":["CSS_SAFE_DEFAULT_VALUES: Record<string, string | string[]>","removed: RemovedNodeInfo[]","srcMap: StylePropertyMapReadOnly","contentMap: StylePropertyMapReadOnly | undefined","cs: CSSStyleDeclaration","contentCs: CSSStyleDeclaration | undefined","node: CloneNode","node","clone","syncState: SyncState","syncStats: SyncStats","rules: string[]"],"sources":["../../src/preview/renderTimegroupPreview.ts"],"sourcesContent":["/**\n * Canvas refresh fix:\n * Canvas pixels must be explicitly cleared and redrawn in syncNodeStyles\n * to ensure video frames are captured correctly during foreignObject rendering.\n * Without clearRect, stale frames from previous seeks may be serialized.\n * \n * See FOREIGNOBJECT_BUG_FIX.md for detailed explanation.\n */\n\nimport { logger } from \"./logger.js\";\n\n/**\n * Elements to skip entirely when building the preview.\n */\nconst SKIP_TAGS = new Set([\n \"EF-AUDIO\",\n \"EF-THUMBNAIL-STRIP\",\n \"EF-FILMSTRIP\",\n \"EF-TIMELINE\",\n \"EF-WORKBENCH\",\n \"SCRIPT\",\n \"STYLE\",\n]);\n\n/**\n * All CSS properties to sync (camelCase for style[] access).\n */\nconst SYNC_PROPERTIES = [\n \"display\", \"visibility\", \"opacity\",\n \"position\", \"top\", \"right\", \"bottom\", \"left\", \"zIndex\",\n \"width\", \"height\", \"minWidth\", \"minHeight\", \"maxWidth\", \"maxHeight\",\n \"flex\", \"flexFlow\", \"justifyContent\", \"alignItems\", \"alignContent\", \"alignSelf\", \"gap\",\n \"gridTemplate\", \"gridColumn\", \"gridRow\", \"gridArea\",\n \"margin\", \"padding\", \"boxSizing\",\n \"border\", \"borderTop\", \"borderRight\", \"borderBottom\", \"borderLeft\", \"borderRadius\",\n \"background\", \"color\", \"boxShadow\", \"filter\", \"backdropFilter\", \"clipPath\",\n \"font\", \"textAlign\", \"textDecoration\", \"textTransform\",\n \"letterSpacing\", \"whiteSpace\", \"textOverflow\", \"lineHeight\",\n \"transform\", \"transformOrigin\", \"transformStyle\",\n \"perspective\", \"perspectiveOrigin\", \"backfaceVisibility\",\n \"cursor\", \"pointerEvents\", \"userSelect\", \"overflow\",\n] as const;\n\n/**\n * Kebab-case versions for computedStyleMap.get() - pre-computed for speed.\n */\nconst SYNC_PROPERTIES_KEBAB = SYNC_PROPERTIES.map(prop =>\n prop.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`)\n);\n\n/**\n * Feature detection: computedStyleMap is ~15% faster for style syncing.\n */\nconst HAS_COMPUTED_STYLE_MAP = typeof Element !== \"undefined\" && typeof Element.prototype.computedStyleMap === \"function\";\n\n/**\n * CSS initial/default values for SAFE-TO-SKIP properties.\n * Only includes NON-INHERITED properties where skipping the default\n * won't affect visual output.\n * \n * EXCLUDED (must always serialize):\n * - Inherited properties (color, font, text-*, visibility, cursor)\n * - Display (affects layout significantly)\n * - Properties where \"auto\" computes to a specific value\n * \n * INCLUDED (safe to skip):\n * - Transform/filter effects (none = no effect)\n * - Box shadows (none = no shadow)\n * - Borders when none/0 (no visual impact)\n * - backdrop-filter (none = no effect)\n */\nconst CSS_SAFE_DEFAULT_VALUES: Record<string, string | string[]> = {\n // Transforms & effects - safe to skip \"none\" (no visual impact)\n transform: \"none\",\n filter: \"none\",\n backdropFilter: \"none\",\n boxShadow: \"none\",\n \n // Borders - safe to skip when none/0\n border: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderTop: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderRight: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderBottom: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderLeft: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderRadius: [\"0px\", \"0\"],\n \n // Positioning - safe to skip \"static\"\n position: \"static\",\n \n // Z-index - \"auto\" is safe when position is static\n zIndex: \"auto\",\n \n // 3D transforms - safe to skip defaults\n transformStyle: \"flat\",\n perspective: \"none\",\n backfaceVisibility: \"visible\",\n};\n\n/**\n * Check if a value matches a safe-to-skip default.\n */\nfunction isDefaultValue(prop: string, value: string): boolean {\n const defaults = CSS_SAFE_DEFAULT_VALUES[prop];\n if (!defaults) return false;\n if (Array.isArray(defaults)) {\n return defaults.includes(value);\n }\n return defaults === value;\n}\n\n// Re-export temporal types from shared module\nexport {\n type TemporalElement,\n isTemporal,\n getTemporalBounds,\n isVisibleAtTime,\n} from \"./previewTypes.js\";\n\n// Import for internal use\nimport {\n getTemporalBounds,\n} from \"./previewTypes.js\";\n\n/**\n * Tree node representing a source/clone pair with children.\n * This replaces the flat array approach for cleaner recursive traversal.\n */\nexport interface CloneNode {\n source: Element;\n clone: HTMLElement;\n children: CloneNode[];\n isCanvasClone: boolean;\n /** Cached temporal bounds for this node */\n bounds: { startMs: number; endMs: number };\n /** Parent node reference for ancestor visibility checks */\n parent: CloneNode | null;\n}\n\n/** Tree-based sync state */\nexport interface CloneTree {\n root: CloneNode | null;\n}\n\n/** Sync state with tree structure and delta tracking */\nexport interface SyncState {\n tree: CloneTree;\n nodeCount: number; // Total number of nodes (for debugging/logging)\n /** Maps clone canvases to their original source elements (ef-video, ef-image, etc.) */\n canvasSourceMap: WeakMap<HTMLCanvasElement, Element>;\n /** Previous frame's visible set for delta tracking */\n previousVisibleSet: Set<CloneNode>;\n /** Current frame's visible set (updated by syncStyles) */\n currentVisibleSet: Set<CloneNode>;\n}\n\n/** Info needed to restore a removed node */\ninterface RemovedNodeInfo {\n node: CloneNode;\n parent: Node;\n nextSibling: Node | null;\n}\n\n/**\n * Remove hidden nodes from the clone DOM for serialization.\n * Returns info needed to restore them afterward.\n * \n * This physically removes non-visible nodes so they won't be serialized,\n * avoiding the cost of serializing hidden elements and their resources.\n */\nexport function removeHiddenNodesForSerialization(state: SyncState): RemovedNodeInfo[] {\n const removed: RemovedNodeInfo[] = [];\n const visibleSet = state.currentVisibleSet;\n \n // Traverse all nodes and remove those not in visible set\n function visit(node: CloneNode): void {\n // First recurse to children (before potentially removing this node)\n for (const child of node.children) {\n visit(child);\n }\n \n // If this node isn't visible, remove it from DOM\n if (!visibleSet.has(node)) {\n const parent = node.clone.parentNode;\n if (parent) {\n const nextSibling = node.clone.nextSibling;\n parent.removeChild(node.clone);\n removed.push({ node, parent, nextSibling });\n }\n }\n }\n \n if (state.tree.root) {\n visit(state.tree.root);\n }\n \n return removed;\n}\n\n/**\n * Restore previously removed hidden nodes to the clone DOM.\n * Must be called after serialization to maintain tree integrity for next frame.\n */\nexport function restoreHiddenNodes(removed: RemovedNodeInfo[]): void {\n // Restore in reverse order to maintain correct DOM positions\n for (let i = removed.length - 1; i >= 0; i--) {\n const { node, parent, nextSibling } = removed[i]!;\n if (nextSibling) {\n parent.insertBefore(node.clone, nextSibling);\n } else {\n parent.appendChild(node.clone);\n }\n }\n}\n\n/**\n * Get visible canvases from the current visible set.\n * Use this to skip encoding hidden canvases during serialization.\n */\nexport function getVisibleCanvases(state: SyncState): Set<HTMLCanvasElement> {\n const visibleCanvases = new Set<HTMLCanvasElement>();\n for (const node of state.currentVisibleSet) {\n if (node.clone instanceof HTMLCanvasElement) {\n visibleCanvases.add(node.clone);\n }\n }\n return visibleCanvases;\n}\n\n/**\n * Traverse all nodes in the clone tree, calling the callback for each.\n */\nexport function traverseCloneTree(state: SyncState, callback: (node: CloneNode) => void): void {\n function visit(node: CloneNode): void {\n callback(node);\n for (const child of node.children) {\n visit(child);\n }\n }\n if (state.tree.root) {\n visit(state.tree.root);\n }\n}\n\n/**\n * Unified CSS property sync for all elements (canvas clones and regular elements).\n * Single source of truth using the SYNC_PROPERTIES array.\n * \n * @param source - Source element to read styles from\n * @param clone - Clone element to write styles to\n * @param contentSource - Optional content element for width/height (canvas clones only)\n */\nfunction syncElementStyles(\n source: Element,\n clone: HTMLElement,\n contentSource?: Element,\n): void {\n const cloneStyle = clone.style as any;\n const propLen = SYNC_PROPERTIES.length;\n const tagName = (source as HTMLElement).tagName;\n \n if (HAS_COMPUTED_STYLE_MAP) {\n let srcMap: StylePropertyMapReadOnly;\n let contentMap: StylePropertyMapReadOnly | undefined;\n \n try {\n srcMap = source.computedStyleMap();\n if (contentSource) {\n contentMap = contentSource.computedStyleMap();\n }\n } catch { return; }\n \n for (let j = 0; j < propLen; j++) {\n const kebab = SYNC_PROPERTIES_KEBAB[j]!;\n const camel = SYNC_PROPERTIES[j]!;\n \n // For canvas clones, width/height come from content element (shadow canvas/img)\n const useContentSource = contentSource && (camel === \"width\" || camel === \"height\");\n const styleMap = useContentSource ? contentMap : srcMap;\n if (!styleMap) continue;\n \n const srcVal = styleMap.get(kebab);\n if (!srcVal) continue;\n \n const strVal = srcVal.toString();\n \n if (camel === \"display\") {\n // For caption child elements, preserve display:none when explicitly set\n // (they use it to hide empty content, not for temporal visibility)\n const isCaptionChild = tagName && (\n tagName === 'EF-CAPTIONS-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-BEFORE-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-AFTER-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-SEGMENT'\n );\n const targetDisplay = (strVal === \"none\" && !isCaptionChild) ? \"block\" : strVal;\n cloneStyle.display = targetDisplay;\n continue;\n }\n \n // Skip clipPath - clones always have clipPath: none for rendering\n // (source may have clip-path: inset(100%) from proxy mode)\n if (camel === \"clipPath\") continue;\n \n // OPTIMIZATION: Skip default values to reduce serialized HTML size\n // If the computed value is the CSS default, don't set it as inline style\n if (isDefaultValue(camel, strVal)) {\n // Remove from inline style if it was previously set\n if (cloneStyle[camel]) cloneStyle[camel] = \"\";\n continue;\n }\n \n cloneStyle[camel] = strVal;\n }\n } else {\n let cs: CSSStyleDeclaration;\n let contentCs: CSSStyleDeclaration | undefined;\n \n try {\n cs = getComputedStyle(source);\n if (contentSource) {\n contentCs = getComputedStyle(contentSource);\n }\n } catch { return; }\n \n const srcStyle = cs as any;\n const contentStyle = contentCs as any;\n \n for (const prop of SYNC_PROPERTIES) {\n // For canvas clones, width/height come from content element (shadow canvas/img)\n const useContentSource = contentSource && (prop === \"width\" || prop === \"height\");\n const srcVal = useContentSource ? contentStyle?.[prop] : srcStyle[prop];\n if (!srcVal) continue;\n \n if (prop === \"display\") {\n // For caption child elements, preserve display:none when explicitly set\n // (they use it to hide empty content, not for temporal visibility)\n const isCaptionChild = tagName && (\n tagName === 'EF-CAPTIONS-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-BEFORE-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-AFTER-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-SEGMENT'\n );\n const targetDisplay = (srcVal === \"none\" && !isCaptionChild) ? \"block\" : srcVal;\n cloneStyle.display = targetDisplay;\n continue;\n }\n \n // Skip clipPath - clones always have clipPath: none for rendering\n // (source may have clip-path: inset(100%) from proxy mode)\n if (prop === \"clipPath\") continue;\n \n // OPTIMIZATION: Skip default values to reduce serialized HTML size\n if (isDefaultValue(prop, srcVal)) {\n if (cloneStyle[prop]) cloneStyle[prop] = \"\";\n continue;\n }\n \n cloneStyle[prop] = srcVal;\n }\n }\n \n // Disable animations/transitions to prevent re-animation (browser optimizes redundant writes)\n cloneStyle.animation = \"none\";\n cloneStyle.transition = \"none\";\n}\n\n/**\n * Refresh canvas pixel content from shadow DOM source.\n * Handles both shadow canvas and shadow img sources.\n */\nfunction refreshCanvasPixels(node: CloneNode): void {\n const { source, clone } = node;\n const canvas = clone as HTMLCanvasElement;\n const shadowCanvas = source.shadowRoot?.querySelector(\"canvas\");\n const shadowImg = source.shadowRoot?.querySelector(\"img\");\n \n if (shadowCanvas) {\n // Update buffer dimensions if needed\n if (canvas.width !== shadowCanvas.width) canvas.width = shadowCanvas.width;\n if (canvas.height !== shadowCanvas.height) canvas.height = shadowCanvas.height;\n \n // Copy pixels with explicit clear\n const ctx = canvas.getContext(\"2d\");\n if (ctx && shadowCanvas.width > 0 && shadowCanvas.height > 0) {\n try {\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.drawImage(shadowCanvas, 0, 0);\n } catch (e) {\n logger.warn(\"[refreshCanvasPixels] Canvas draw failed:\", e);\n }\n }\n } else if (shadowImg?.complete && shadowImg.naturalWidth > 0) {\n // Update buffer dimensions if needed\n if (canvas.width !== shadowImg.naturalWidth) canvas.width = shadowImg.naturalWidth;\n if (canvas.height !== shadowImg.naturalHeight) canvas.height = shadowImg.naturalHeight;\n \n // Copy pixels with explicit clear\n const ctx = canvas.getContext(\"2d\");\n if (ctx) {\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n try { ctx.drawImage(shadowImg, 0, 0); } catch {}\n }\n }\n}\n\n/**\n * Sync text content from light DOM to clone.\n */\nfunction syncTextContent(source: Element, clone: HTMLElement): void {\n const srcTextNode = source.childNodes[0];\n if (srcTextNode?.nodeType === Node.TEXT_NODE) {\n const srcText = srcTextNode.textContent || \"\";\n const cloneTextNode = clone.childNodes[0];\n \n if (cloneTextNode?.nodeType === Node.TEXT_NODE) {\n // Update existing text node\n if (cloneTextNode.textContent !== srcText) cloneTextNode.textContent = srcText;\n } else if (!clone.childNodes.length) {\n // Only create text node if clone has NO children (was empty when initially cloned)\n // Don't set textContent as it would delete element children!\n clone.appendChild(document.createTextNode(srcText));\n }\n }\n}\n\n/**\n * Sync input element value.\n */\nfunction syncInputValue(source: Element, clone: HTMLElement): void {\n if (source instanceof HTMLInputElement) {\n const srcVal = source.value;\n const cloneInput = clone as HTMLInputElement;\n if (cloneInput.value !== srcVal) {\n cloneInput.value = srcVal;\n cloneInput.setAttribute(\"value\", srcVal);\n }\n }\n}\n\n/**\n * Build clone tree structure with minimal overhead.\n * Caches temporal bounds on each node for visibility checks.\n * Optionally syncs styles in the same pass if timeMs is provided.\n */\nexport function buildCloneStructure(source: Element, timeMs?: number): {\n container: HTMLDivElement;\n syncState: SyncState;\n} {\n const container = document.createElement(\"div\");\n container.style.cssText = \"position:absolute;top:0;left:0;width:100%;height:100%\";\n \n let nodeCount = 0;\n const canvasSourceMap = new WeakMap<HTMLCanvasElement, Element>();\n \n function cloneElement(srcEl: Element, parentNode: CloneNode | null): CloneNode | null {\n if (SKIP_TAGS.has(srcEl.tagName)) return null;\n \n // Get temporal bounds upfront for indexing\n const bounds = getTemporalBounds(srcEl);\n \n // SVG - clone entire subtree (no children tracking needed)\n if (srcEl instanceof SVGElement) {\n const svgClone = srcEl.cloneNode(true) as SVGElement;\n const node: CloneNode = {\n source: srcEl,\n clone: svgClone as unknown as HTMLElement,\n children: [],\n isCanvasClone: false,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n return node;\n }\n \n // Canvas - copy pixels\n if (srcEl instanceof HTMLCanvasElement) {\n const canvas = document.createElement(\"canvas\");\n canvas.width = srcEl.width;\n canvas.height = srcEl.height;\n const ctx = canvas.getContext(\"2d\");\n if (ctx) {\n try { ctx.drawImage(srcEl, 0, 0); } catch {}\n }\n // Raw canvas elements don't need style syncing, just return clone\n // return null;\n }\n \n // Custom elements with shadow canvas (e.g., ef-video, ef-image)\n const isCustom = srcEl.tagName.includes(\"-\");\n if (isCustom && srcEl.shadowRoot) {\n const shadowCanvas = srcEl.shadowRoot.querySelector(\"canvas\");\n if (shadowCanvas) {\n const clone = document.createElement(\"canvas\");\n clone.width = shadowCanvas.width || srcEl.clientWidth;\n clone.height = shadowCanvas.height || srcEl.clientHeight;\n // Mark ef-image canvases to preserve transparency (use PNG instead of JPEG)\n // ef-video doesn't need this since videos don't have transparency\n if (srcEl.tagName === \"EF-IMAGE\" || srcEl.tagName === \"EF-WAVEFORM\") {\n clone.dataset.preserveAlpha = \"true\";\n }\n \n const ctx = clone.getContext(\"2d\");\n if (ctx) {\n try { ctx.drawImage(shadowCanvas, 0, 0); } catch {}\n }\n \n // Copy initial CSS styles using unified sync\n // Pass shadowCanvas as contentSource for width/height\n try {\n syncElementStyles(srcEl, clone, shadowCanvas);\n } catch {}\n \n // Map clone canvas to source element for RenderContext caching\n canvasSourceMap.set(clone, srcEl);\n \n const node: CloneNode = {\n source: srcEl,\n clone,\n children: [],\n isCanvasClone: true,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n return node;\n }\n \n const shadowImg = srcEl.shadowRoot.querySelector(\"img\");\n if (shadowImg?.complete && shadowImg.naturalWidth > 0) {\n const clone = document.createElement(\"canvas\");\n clone.width = shadowImg.naturalWidth;\n clone.height = shadowImg.naturalHeight;\n // Mark as image-sourced canvas to preserve transparency (use PNG instead of JPEG)\n clone.dataset.preserveAlpha = \"true\";\n const ctx = clone.getContext(\"2d\");\n if (ctx) {\n try { ctx.drawImage(shadowImg, 0, 0); } catch {}\n }\n \n // Copy initial CSS styles using unified sync\n // Pass shadowImg as contentSource for width/height\n try {\n syncElementStyles(srcEl, clone, shadowImg);\n } catch {}\n \n // Map clone canvas to source element for RenderContext caching\n canvasSourceMap.set(clone, srcEl);\n \n const node: CloneNode = {\n source: srcEl,\n clone,\n children: [],\n isCanvasClone: true,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n return node;\n }\n }\n \n // Standard element clone\n const clone = document.createElement(isCustom ? \"div\" : srcEl.tagName.toLowerCase()) as HTMLElement;\n \n // Copy attributes - OPTIMIZATION: Early exit if no attributes\n const attrs = srcEl.attributes;\n const attrLen = attrs.length;\n if (attrLen > 0) {\n for (let i = 0; i < attrLen; i++) {\n const attr = attrs[i]!;\n const name = attr.name.toLowerCase();\n if (name === \"id\" || name.startsWith(\"on\")) continue;\n if (isCustom && name !== \"class\" && !name.startsWith(\"data-\")) continue;\n try { clone.setAttribute(attr.name, attr.value); } catch {}\n }\n }\n \n if (srcEl instanceof HTMLImageElement && srcEl.src) {\n (clone as HTMLImageElement).src = srcEl.src;\n }\n if (srcEl instanceof HTMLInputElement) {\n (clone as HTMLInputElement).value = srcEl.value;\n }\n \n const node: CloneNode = {\n source: srcEl,\n clone,\n children: [],\n isCanvasClone: false,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n \n // Shadow DOM children - OPTIMIZATION: Early exit if no childNodes\n if (srcEl.shadowRoot) {\n const shadowChildren = srcEl.shadowRoot.childNodes;\n const shadowLen = shadowChildren.length;\n if (shadowLen > 0) {\n // For text segments, ALWAYS create a text node placeholder even if empty.\n // Caption elements now use light DOM, so they don't need special handling here.\n const isTextSegment = srcEl.tagName === 'EF-TEXT-SEGMENT';\n let hasTextNode = false;\n \n for (let i = 0; i < shadowLen; i++) {\n const child = shadowChildren[i]!;\n if (child.nodeType === Node.TEXT_NODE) {\n const text = child.textContent?.trim();\n // Always include text for text segments (even if whitespace-only, e.g., \" \")\n if (text || isTextSegment) {\n clone.appendChild(document.createTextNode(child.textContent || \"\"));\n hasTextNode = true;\n }\n } else if (child.nodeType === Node.ELEMENT_NODE) {\n const el = child as Element;\n if (el.tagName === \"STYLE\" || el.tagName === \"SLOT\") continue;\n const childNode = cloneElement(el, node);\n if (childNode) {\n node.children.push(childNode);\n clone.appendChild(childNode.clone);\n }\n }\n }\n \n // For text segments, ensure there's always a text node for syncStyles to update\n if (isTextSegment && !hasTextNode) {\n clone.appendChild(document.createTextNode(\"\"));\n }\n }\n }\n \n // Light DOM children - OPTIMIZATION: Use indexed loop for performance\n const lightChildren = srcEl.childNodes;\n const lightLen = lightChildren.length;\n for (let i = 0; i < lightLen; i++) {\n const child = lightChildren[i]!;\n if (child.nodeType === Node.TEXT_NODE) {\n const text = child.textContent?.trim();\n if (text) clone.appendChild(document.createTextNode(text));\n } else if (child.nodeType === Node.ELEMENT_NODE) {\n const childNode = cloneElement(child as Element, node);\n if (childNode) {\n node.children.push(childNode);\n clone.appendChild(childNode.clone);\n }\n }\n }\n \n return node;\n }\n \n const root = cloneElement(source, null);\n if (root) container.appendChild(root.clone);\n \n const syncState: SyncState = {\n tree: { root },\n nodeCount,\n canvasSourceMap,\n previousVisibleSet: new Set(),\n currentVisibleSet: new Set(),\n };\n \n // Sync styles in the same pass if timeMs is provided\n if (timeMs !== undefined && root) {\n syncStylesWithIndex(syncState, timeMs);\n }\n \n return {\n container,\n syncState,\n };\n}\n\n/**\n * Sync a single node's styles (extracted for reuse).\n * Now uses unified style syncing with clear separation of concerns:\n * 1. Canvas pixel refresh (if canvas clone)\n * 2. Unified CSS property sync (all elements)\n * 3. Content sync (text, input values)\n */\nfunction syncNodeStyles(node: CloneNode): void {\n const { source, clone, isCanvasClone } = node;\n \n // 1. Canvas-specific: Refresh pixel content from shadow DOM\n if (isCanvasClone) {\n refreshCanvasPixels(node);\n }\n \n // 2. Unified: Sync ALL CSS properties using SYNC_PROPERTIES array\n // For canvas clones, pass content source (shadow canvas/img) for width/height\n const contentSource = isCanvasClone\n ? (source.shadowRoot?.querySelector(\"canvas\") || source.shadowRoot?.querySelector(\"img\") || undefined)\n : undefined;\n syncElementStyles(source, clone, contentSource);\n \n // 3. Element-specific: Sync text content and input values\n syncTextContent(source, clone);\n syncInputValue(source, clone);\n}\n\n// Performance instrumentation counters\ninterface SyncStats {\n nodesVisited: number;\n nodesCulledByParent: number;\n nodesCulledByTemporal: number;\n nodesProcessed: number;\n nodesFullSync: number; // Newly visible nodes (full sync)\n nodesIncrementalSync: number; // Still visible nodes (incremental sync)\n nodesHidden: number; // Newly hidden nodes\n indexQueryTimeMs: number;\n syncTimeMs: number;\n}\n\nlet syncStats: SyncStats = {\n nodesVisited: 0,\n nodesCulledByParent: 0,\n nodesCulledByTemporal: 0,\n nodesProcessed: 0,\n nodesFullSync: 0,\n nodesIncrementalSync: 0,\n nodesHidden: 0,\n indexQueryTimeMs: 0,\n syncTimeMs: 0,\n};\n\n/**\n * Visibility delta between frames.\n * Used for incremental updates - only sync what changed.\n */\ninterface VisibilityDelta {\n nowVisible: Set<CloneNode>; // Need full style sync + show\n stillVisible: Set<CloneNode>; // Only sync animated properties (or skip if same time)\n nowHidden: Set<CloneNode>; // Just set display:none\n}\n\n/**\n * Compute visibility delta between previous and current frame.\n */\nfunction computeVisibilityDelta(\n previousSet: Set<CloneNode>,\n currentSet: Set<CloneNode>,\n): VisibilityDelta {\n const nowVisible = new Set<CloneNode>();\n const stillVisible = new Set<CloneNode>();\n const nowHidden = new Set<CloneNode>();\n \n // Find nodes that became visible or stayed visible\n for (const node of currentSet) {\n if (previousSet.has(node)) {\n stillVisible.add(node);\n } else {\n nowVisible.add(node);\n }\n }\n \n // Find nodes that became hidden\n for (const node of previousSet) {\n if (!currentSet.has(node)) {\n nowHidden.add(node);\n }\n }\n \n return { nowVisible, stillVisible, nowHidden };\n}\n\n/**\n * Build visible set by recursive traversal with bounds checking.\n * Queries fresh bounds from source elements each time - bounds are computed\n * dynamically by timegroups based on composition mode.\n */\nfunction buildVisibleSetRecursive(\n node: CloneNode,\n timeMs: number,\n visibleSet: Set<CloneNode>,\n): void {\n const { children, source } = node;\n \n // Get fresh bounds from source element (not cached - timegroup bounds are dynamic)\n const bounds = getTemporalBounds(source);\n \n // Check if this node is visible at current time\n const isVisible = timeMs >= bounds.startMs && timeMs <= bounds.endMs;\n \n if (isVisible) {\n visibleSet.add(node);\n // Recurse to children\n for (const child of children) {\n buildVisibleSetRecursive(child, timeMs, visibleSet);\n }\n }\n // If not visible, skip entire subtree\n}\n\n/**\n * Sync styles with recursive visibility check and delta tracking.\n * \n * DELTA TRACKING: Tracks visibility changes between frames to minimize work:\n * - nowVisible nodes: Full style sync + show\n * - stillVisible nodes: Incremental sync (source DOM may have changed)\n * - nowHidden nodes: Just hide (display:none)\n */\nfunction syncStylesWithIndex(state: SyncState, timeMs: number): void {\n const queryStart = performance.now();\n \n // Build the set of visible nodes by recursive traversal\n const visibleSet = new Set<CloneNode>();\n if (state.tree.root) {\n buildVisibleSetRecursive(state.tree.root, timeMs, visibleSet);\n }\n \n // Compute delta from previous frame\n const delta = computeVisibilityDelta(state.previousVisibleSet, visibleSet);\n \n syncStats.indexQueryTimeMs = performance.now() - queryStart;\n \n // Now traverse the tree but use the delta for O(1) sync decisions\n const syncStart = performance.now();\n if (state.tree.root) {\n syncNodeWithDelta(state.tree.root, visibleSet, delta);\n }\n syncStats.syncTimeMs = performance.now() - syncStart;\n \n // Update state for next frame and expose current visible set\n state.previousVisibleSet = visibleSet;\n state.currentVisibleSet = visibleSet;\n}\n\n/**\n * Sync a node using visibility delta for incremental updates.\n * \n * DELTA TRACKING optimization:\n * - nowVisible: Full style sync (element just appeared)\n * - stillVisible: Incremental sync (source DOM may have changed)\n * - nowHidden: Just hide the element\n * - Not in any set: Skip entirely (was already hidden)\n */\nfunction syncNodeWithDelta(\n node: CloneNode,\n visibleSet: Set<CloneNode>,\n delta: VisibilityDelta,\n): void {\n syncStats.nodesVisited++;\n \n const isVisible = visibleSet.has(node);\n \n if (!isVisible) {\n // Node is not visible - ALWAYS set display:none\n // This handles both \"just became hidden\" and \"initial build with node outside time range\"\n node.clone.style.display = \"none\";\n if (delta.nowHidden.has(node)) {\n syncStats.nodesHidden++;\n }\n // Already hidden nodes: skip (don't even recurse to children)\n syncStats.nodesCulledByTemporal++;\n return;\n }\n \n // Node is visible - determine sync strategy\n if (delta.nowVisible.has(node)) {\n // Just became visible - need full style sync\n syncNodeStyles(node);\n syncStats.nodesFullSync++;\n } else if (delta.stillVisible.has(node)) {\n // Was visible, still visible - still need to sync\n // Source DOM properties can change independently of time (input values, text, etc.)\n // TODO: Phase 5 could track property changes for smarter incremental sync\n syncNodeStyles(node);\n syncStats.nodesIncrementalSync++;\n }\n \n syncStats.nodesProcessed++;\n \n // Recurse to children\n for (const child of node.children) {\n syncNodeWithDelta(child, visibleSet, delta);\n }\n}\n\n/**\n * Legacy recursive sync (kept for comparison/fallback).\n * Returns early if the node is temporally culled, skipping ALL descendants.\n * @deprecated Use syncStylesWithIndex for better performance\n */\nexport function syncNodeRecursiveLegacy(node: CloneNode, timeMs: number): void {\n const { clone, children, bounds } = node;\n syncStats.nodesVisited++;\n \n // Temporal culling - check if this node is visible at current time\n // NOTE: Canvas clones now participate in temporal culling (lazy canvas copying).\n // Invalid bounds [0,0] are treated as [-Infinity, Infinity] by getTemporalBounds.\n {\n // OPTIMIZATION: Check if parent is already hidden to skip bounds computation\n const parent = clone.parentElement;\n if (parent instanceof HTMLElement) {\n // If parent has display:none, this element is already hidden - skip bounds check\n if (parent.style.display === \"none\") {\n clone.style.display = \"none\";\n syncStats.nodesCulledByParent++;\n return;\n }\n }\n \n // Use cached bounds from node instead of calling getTemporalBounds\n const { startMs, endMs } = bounds;\n if (timeMs < startMs || timeMs > endMs) {\n // Hide this element and BAIL OUT - skip all descendants automatically!\n clone.style.display = \"none\";\n syncStats.nodesCulledByTemporal++;\n return;\n }\n }\n \n // Sync this node's styles\n syncNodeStyles(node);\n syncStats.nodesProcessed++;\n \n // Recursively sync children\n for (const child of children) {\n syncNodeRecursiveLegacy(child, timeMs);\n }\n}\n\n/**\n * Sync all CSS properties from source elements to their clones.\n * Uses interval index for O(log n + k) visibility queries instead of O(n) traversal.\n * Uses delta tracking for incremental updates between frames.\n */\nexport function syncStyles(state: SyncState, timeMs: number): void {\n // Reset stats\n syncStats = {\n nodesVisited: 0,\n nodesCulledByParent: 0,\n nodesCulledByTemporal: 0,\n nodesProcessed: 0,\n nodesFullSync: 0,\n nodesIncrementalSync: 0,\n nodesHidden: 0,\n indexQueryTimeMs: 0,\n syncTimeMs: 0,\n };\n \n // Use interval-index-based sync with delta tracking\n syncStylesWithIndex(state, timeMs);\n}\n\n/**\n * Collect document styles for shadow DOM injection.\n */\nexport function collectDocumentStyles(): string {\n const rules: string[] = [];\n try {\n for (const sheet of document.styleSheets) {\n try {\n if (sheet.cssRules) {\n for (const rule of sheet.cssRules) {\n rules.push(rule.cssText);\n }\n }\n } catch {}\n }\n } catch {}\n return rules.join(\"\\n\");\n}\n\n\n// Backward-compatible aliases\nexport const syncStaticStyles = syncStyles;\nexport const syncAnimatedStyles = syncStyles;\n\n/**\n * Override clip-path, opacity, and optionally transform on the root clone element.\n * The source may have these properties set for proxy mode or workbench scaling.\n * \n * @param syncState - The sync state containing the clone tree\n * @param fullReset - If true, also resets opacity and transform (for capture operations)\n */\nexport function overrideRootCloneStyles(syncState: SyncState, fullReset: boolean = false): void {\n const rootClone = syncState.tree.root?.clone;\n if (!rootClone) return;\n \n rootClone.style.clipPath = \"none\";\n if (fullReset) {\n rootClone.style.opacity = \"1\";\n rootClone.style.transform = \"none\";\n }\n}\n\n/**\n * Create a live preview of a timegroup with a refresh function.\n * Used by EFWorkbench for the \"computed\" preview mode.\n * \n * @param source - The source timegroup to preview\n * @returns Object with preview container and refresh function\n */\nexport function renderTimegroupPreview(source: Element): {\n container: HTMLDivElement;\n refresh: (timeMs?: number) => void;\n} {\n const { container, syncState } = buildCloneStructure(source);\n \n // Initial style sync\n syncStyles(syncState, 0);\n \n return {\n container,\n refresh: (timeMs?: number) => {\n syncStyles(syncState, timeMs ?? 0);\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"renderTimegroupPreview.js","names":["CSS_SAFE_DEFAULT_VALUES: Record<string, string | string[]>","removed: RemovedNodeInfo[]","cs: CSSStyleDeclaration","contentCs: CSSStyleDeclaration | undefined","srcMap: StylePropertyMapReadOnly","node: CloneNode","node","clone","syncState: SyncState","syncStats: SyncStats","rules: string[]"],"sources":["../../src/preview/renderTimegroupPreview.ts"],"sourcesContent":["/**\n * Canvas refresh fix:\n * Canvas pixels must be explicitly cleared and redrawn in syncNodeStyles\n * to ensure video frames are captured correctly during foreignObject rendering.\n * Without clearRect, stale frames from previous seeks may be serialized.\n * \n * See FOREIGNOBJECT_BUG_FIX.md for detailed explanation.\n */\n\nimport { logger } from \"./logger.js\";\n\n/**\n * Elements to skip entirely when building the preview.\n */\nconst SKIP_TAGS = new Set([\n \"EF-AUDIO\",\n \"EF-THUMBNAIL-STRIP\",\n \"EF-FILMSTRIP\",\n \"EF-TIMELINE\",\n \"EF-WORKBENCH\",\n \"SCRIPT\",\n \"STYLE\",\n]);\n\n/**\n * All CSS properties to sync (camelCase for style[] access).\n */\nconst SYNC_PROPERTIES = [\n \"display\", \"visibility\", \"opacity\",\n \"position\", \"top\", \"right\", \"bottom\", \"left\", \"zIndex\",\n \"width\", \"height\", \"minWidth\", \"minHeight\", \"maxWidth\", \"maxHeight\",\n \"flex\", \"flexFlow\", \"justifyContent\", \"alignItems\", \"alignContent\", \"alignSelf\", \"gap\",\n \"gridTemplate\", \"gridColumn\", \"gridRow\", \"gridArea\",\n \"margin\", \"padding\", \"boxSizing\",\n \"border\", \"borderTop\", \"borderRight\", \"borderBottom\", \"borderLeft\", \"borderRadius\",\n \"background\", \"color\", \"boxShadow\", \"filter\", \"backdropFilter\", \"clipPath\",\n \"font\", \"textAlign\", \"textDecoration\", \"textTransform\",\n \"letterSpacing\", \"whiteSpace\", \"textOverflow\", \"lineHeight\",\n \"transform\", \"transformOrigin\", \"transformStyle\",\n \"perspective\", \"perspectiveOrigin\", \"backfaceVisibility\",\n \"cursor\", \"pointerEvents\", \"userSelect\", \"overflow\",\n] as const;\n\n/**\n * Kebab-case versions for computedStyleMap.get() - pre-computed for speed.\n */\nconst SYNC_PROPERTIES_KEBAB = SYNC_PROPERTIES.map(prop =>\n prop.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`)\n);\n\n/**\n * Feature detection: computedStyleMap is ~15% faster for style syncing.\n */\nconst HAS_COMPUTED_STYLE_MAP = typeof Element !== \"undefined\" && typeof Element.prototype.computedStyleMap === \"function\";\n\n/**\n * CSS initial/default values for SAFE-TO-SKIP properties.\n * Only includes NON-INHERITED properties where skipping the default\n * won't affect visual output.\n * \n * EXCLUDED (must always serialize):\n * - Inherited properties (color, font, text-*, visibility, cursor)\n * - Display (affects layout significantly)\n * - Properties where \"auto\" computes to a specific value\n * \n * INCLUDED (safe to skip):\n * - Transform/filter effects (none = no effect)\n * - Box shadows (none = no shadow)\n * - Borders when none/0 (no visual impact)\n * - backdrop-filter (none = no effect)\n */\nconst CSS_SAFE_DEFAULT_VALUES: Record<string, string | string[]> = {\n // Transforms & effects - safe to skip \"none\" (no visual impact)\n transform: \"none\",\n filter: \"none\",\n backdropFilter: \"none\",\n boxShadow: \"none\",\n \n // Borders - safe to skip when none/0\n border: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderTop: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderRight: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderBottom: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderLeft: [\"none\", \"0px none\", \"0px\", \"0px none rgb(0, 0, 0)\"],\n borderRadius: [\"0px\", \"0\"],\n \n // Positioning - safe to skip \"static\"\n position: \"static\",\n \n // Z-index - \"auto\" is safe when position is static\n zIndex: \"auto\",\n \n // 3D transforms - safe to skip defaults\n transformStyle: \"flat\",\n perspective: \"none\",\n backfaceVisibility: \"visible\",\n};\n\n/**\n * Check if a value matches a safe-to-skip default.\n */\nfunction isDefaultValue(prop: string, value: string): boolean {\n const defaults = CSS_SAFE_DEFAULT_VALUES[prop];\n if (!defaults) return false;\n if (Array.isArray(defaults)) {\n return defaults.includes(value);\n }\n return defaults === value;\n}\n\n// Re-export temporal types from shared module\nexport {\n type TemporalElement,\n isTemporal,\n getTemporalBounds,\n isVisibleAtTime,\n} from \"./previewTypes.js\";\n\n// Import for internal use\nimport {\n getTemporalBounds,\n} from \"./previewTypes.js\";\n\n/**\n * Tree node representing a source/clone pair with children.\n * This replaces the flat array approach for cleaner recursive traversal.\n */\nexport interface CloneNode {\n source: Element;\n clone: HTMLElement;\n children: CloneNode[];\n isCanvasClone: boolean;\n /** Cached temporal bounds for this node */\n bounds: { startMs: number; endMs: number };\n /** Parent node reference for ancestor visibility checks */\n parent: CloneNode | null;\n}\n\n/** Tree-based sync state */\nexport interface CloneTree {\n root: CloneNode | null;\n}\n\n/** Sync state with tree structure and delta tracking */\nexport interface SyncState {\n tree: CloneTree;\n nodeCount: number; // Total number of nodes (for debugging/logging)\n /** Maps clone canvases to their original source elements (ef-video, ef-image, etc.) */\n canvasSourceMap: WeakMap<HTMLCanvasElement, Element>;\n /** Previous frame's visible set for delta tracking */\n previousVisibleSet: Set<CloneNode>;\n /** Current frame's visible set (updated by syncStyles) */\n currentVisibleSet: Set<CloneNode>;\n}\n\n/** Info needed to restore a removed node */\ninterface RemovedNodeInfo {\n node: CloneNode;\n parent: Node;\n nextSibling: Node | null;\n}\n\n/**\n * Remove hidden nodes from the clone DOM for serialization.\n * Returns info needed to restore them afterward.\n * \n * This physically removes non-visible nodes so they won't be serialized,\n * avoiding the cost of serializing hidden elements and their resources.\n */\nexport function removeHiddenNodesForSerialization(state: SyncState): RemovedNodeInfo[] {\n const removed: RemovedNodeInfo[] = [];\n const visibleSet = state.currentVisibleSet;\n \n // Traverse all nodes and remove those not in visible set\n function visit(node: CloneNode): void {\n // First recurse to children (before potentially removing this node)\n for (const child of node.children) {\n visit(child);\n }\n \n // If this node isn't visible, remove it from DOM\n if (!visibleSet.has(node)) {\n const parent = node.clone.parentNode;\n if (parent) {\n const nextSibling = node.clone.nextSibling;\n parent.removeChild(node.clone);\n removed.push({ node, parent, nextSibling });\n }\n }\n }\n \n if (state.tree.root) {\n visit(state.tree.root);\n }\n \n return removed;\n}\n\n/**\n * Restore previously removed hidden nodes to the clone DOM.\n * Must be called after serialization to maintain tree integrity for next frame.\n */\nexport function restoreHiddenNodes(removed: RemovedNodeInfo[]): void {\n // Restore in reverse order to maintain correct DOM positions\n for (let i = removed.length - 1; i >= 0; i--) {\n const { node, parent, nextSibling } = removed[i]!;\n if (nextSibling) {\n parent.insertBefore(node.clone, nextSibling);\n } else {\n parent.appendChild(node.clone);\n }\n }\n}\n\n/**\n * Get visible canvases from the current visible set.\n * Use this to skip encoding hidden canvases during serialization.\n */\nexport function getVisibleCanvases(state: SyncState): Set<HTMLCanvasElement> {\n const visibleCanvases = new Set<HTMLCanvasElement>();\n for (const node of state.currentVisibleSet) {\n if (node.clone instanceof HTMLCanvasElement) {\n visibleCanvases.add(node.clone);\n }\n }\n return visibleCanvases;\n}\n\n/**\n * Traverse all nodes in the clone tree, calling the callback for each.\n */\nexport function traverseCloneTree(state: SyncState, callback: (node: CloneNode) => void): void {\n function visit(node: CloneNode): void {\n callback(node);\n for (const child of node.children) {\n visit(child);\n }\n }\n if (state.tree.root) {\n visit(state.tree.root);\n }\n}\n\n/**\n * Unified CSS property sync for all elements (canvas clones and regular elements).\n * \n * Canvas clones use a limited property set matching the original implementation\n * to avoid dimension/layout issues. Regular elements use the full SYNC_PROPERTIES array.\n * \n * @param source - Source element to read styles from\n * @param clone - Clone element to write styles to\n * @param contentSource - Optional content element for width/height (canvas clones only)\n */\nfunction syncElementStyles(\n source: Element,\n clone: HTMLElement,\n contentSource?: Element,\n): void {\n const cloneStyle = clone.style as any;\n const tagName = (source as HTMLElement).tagName;\n const isCanvasClone = !!contentSource;\n \n // Canvas clones: Use exact property list from original implementation\n if (isCanvasClone) {\n let cs: CSSStyleDeclaration;\n let contentCs: CSSStyleDeclaration | undefined;\n \n try {\n cs = getComputedStyle(source);\n if (contentSource) {\n contentCs = getComputedStyle(contentSource);\n }\n } catch { return; }\n \n // Exact properties from original copyCanvasCloneStyles + syncNodeStyles\n cloneStyle.position = cs.position;\n cloneStyle.top = cs.top;\n cloneStyle.right = cs.right;\n cloneStyle.bottom = cs.bottom;\n cloneStyle.left = cs.left;\n cloneStyle.margin = cs.margin;\n cloneStyle.zIndex = cs.zIndex;\n cloneStyle.transform = cs.transform;\n cloneStyle.transformOrigin = cs.transformOrigin;\n cloneStyle.opacity = cs.opacity;\n cloneStyle.visibility = cs.visibility;\n cloneStyle.backfaceVisibility = cs.backfaceVisibility;\n cloneStyle.transformStyle = cs.transformStyle;\n \n // Width/height from content source (shadow canvas/img)\n if (contentCs) {\n cloneStyle.width = contentCs.width;\n cloneStyle.height = contentCs.height;\n }\n \n cloneStyle.display = \"block\";\n cloneStyle.animation = \"none\";\n cloneStyle.transition = \"none\";\n \n return;\n }\n \n // Regular elements: full property sync from SYNC_PROPERTIES\n const propLen = SYNC_PROPERTIES.length;\n \n if (HAS_COMPUTED_STYLE_MAP) {\n let srcMap: StylePropertyMapReadOnly;\n \n try {\n srcMap = source.computedStyleMap();\n } catch { return; }\n \n for (let j = 0; j < propLen; j++) {\n const kebab = SYNC_PROPERTIES_KEBAB[j]!;\n const camel = SYNC_PROPERTIES[j]!;\n \n const srcVal = srcMap.get(kebab);\n if (!srcVal) continue;\n \n const strVal = srcVal.toString();\n \n if (camel === \"display\") {\n // For caption child elements, preserve display:none when explicitly set\n // (they use it to hide empty content, not for temporal visibility)\n const isCaptionChild = tagName && (\n tagName === 'EF-CAPTIONS-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-BEFORE-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-AFTER-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-SEGMENT'\n );\n const targetDisplay = (strVal === \"none\" && !isCaptionChild) ? \"block\" : strVal;\n cloneStyle.display = targetDisplay;\n continue;\n }\n \n // Skip clipPath - clones always have clipPath: none for rendering\n // (source may have clip-path: inset(100%) from proxy mode)\n if (camel === \"clipPath\") continue;\n \n // OPTIMIZATION: Skip default values to reduce serialized HTML size\n // If the computed value is the CSS default, don't set it as inline style\n if (isDefaultValue(camel, strVal)) {\n // Remove from inline style if it was previously set\n if (cloneStyle[camel]) cloneStyle[camel] = \"\";\n continue;\n }\n \n cloneStyle[camel] = strVal;\n }\n } else {\n let cs: CSSStyleDeclaration;\n \n try {\n cs = getComputedStyle(source);\n } catch { return; }\n \n const srcStyle = cs as any;\n \n for (const prop of SYNC_PROPERTIES) {\n const srcVal = srcStyle[prop];\n if (!srcVal) continue;\n \n if (prop === \"display\") {\n // For caption child elements, preserve display:none when explicitly set\n // (they use it to hide empty content, not for temporal visibility)\n const isCaptionChild = tagName && (\n tagName === 'EF-CAPTIONS-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-BEFORE-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-AFTER-ACTIVE-WORD' ||\n tagName === 'EF-CAPTIONS-SEGMENT'\n );\n const targetDisplay = (srcVal === \"none\" && !isCaptionChild) ? \"block\" : srcVal;\n cloneStyle.display = targetDisplay;\n continue;\n }\n \n // Skip clipPath - clones always have clipPath: none for rendering\n // (source may have clip-path: inset(100%) from proxy mode)\n if (prop === \"clipPath\") continue;\n \n // OPTIMIZATION: Skip default values to reduce serialized HTML size\n if (isDefaultValue(prop, srcVal)) {\n if (cloneStyle[prop]) cloneStyle[prop] = \"\";\n continue;\n }\n \n cloneStyle[prop] = srcVal;\n }\n }\n \n // Disable animations/transitions to prevent re-animation\n cloneStyle.animation = \"none\";\n cloneStyle.transition = \"none\";\n}\n\n/**\n * Refresh canvas pixel content from shadow DOM source.\n * Handles both shadow canvas and shadow img sources.\n */\nfunction refreshCanvasPixels(node: CloneNode): void {\n const { source, clone } = node;\n const canvas = clone as HTMLCanvasElement;\n const shadowCanvas = source.shadowRoot?.querySelector(\"canvas\");\n const shadowImg = source.shadowRoot?.querySelector(\"img\");\n \n if (shadowCanvas) {\n // Update buffer dimensions if needed\n if (canvas.width !== shadowCanvas.width) canvas.width = shadowCanvas.width;\n if (canvas.height !== shadowCanvas.height) canvas.height = shadowCanvas.height;\n \n // Copy pixels with explicit clear\n const ctx = canvas.getContext(\"2d\");\n if (ctx && shadowCanvas.width > 0 && shadowCanvas.height > 0) {\n try {\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.drawImage(shadowCanvas, 0, 0);\n } catch (e) {\n logger.warn(\"[refreshCanvasPixels] Canvas draw failed:\", e);\n }\n }\n } else if (shadowImg?.complete && shadowImg.naturalWidth > 0) {\n // Update buffer dimensions if needed\n if (canvas.width !== shadowImg.naturalWidth) canvas.width = shadowImg.naturalWidth;\n if (canvas.height !== shadowImg.naturalHeight) canvas.height = shadowImg.naturalHeight;\n \n // Copy pixels with explicit clear\n const ctx = canvas.getContext(\"2d\");\n if (ctx) {\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n try { ctx.drawImage(shadowImg, 0, 0); } catch {}\n }\n }\n}\n\n/**\n * Sync text content from light DOM to clone.\n */\nfunction syncTextContent(source: Element, clone: HTMLElement): void {\n const srcTextNode = source.childNodes[0];\n if (srcTextNode?.nodeType === Node.TEXT_NODE) {\n const srcText = srcTextNode.textContent || \"\";\n const cloneTextNode = clone.childNodes[0];\n \n if (cloneTextNode?.nodeType === Node.TEXT_NODE) {\n // Update existing text node\n if (cloneTextNode.textContent !== srcText) cloneTextNode.textContent = srcText;\n } else if (!clone.childNodes.length) {\n // Only create text node if clone has NO children (was empty when initially cloned)\n // Don't set textContent as it would delete element children!\n clone.appendChild(document.createTextNode(srcText));\n }\n }\n}\n\n/**\n * Sync input element value.\n */\nfunction syncInputValue(source: Element, clone: HTMLElement): void {\n if (source instanceof HTMLInputElement) {\n const srcVal = source.value;\n const cloneInput = clone as HTMLInputElement;\n if (cloneInput.value !== srcVal) {\n cloneInput.value = srcVal;\n cloneInput.setAttribute(\"value\", srcVal);\n }\n }\n}\n\n/**\n * Build clone tree structure with minimal overhead.\n * Caches temporal bounds on each node for visibility checks.\n * Optionally syncs styles in the same pass if timeMs is provided.\n */\nexport function buildCloneStructure(source: Element, timeMs?: number): {\n container: HTMLDivElement;\n syncState: SyncState;\n} {\n const container = document.createElement(\"div\");\n container.style.cssText = \"position:absolute;top:0;left:0;width:100%;height:100%\";\n \n let nodeCount = 0;\n const canvasSourceMap = new WeakMap<HTMLCanvasElement, Element>();\n \n function cloneElement(srcEl: Element, parentNode: CloneNode | null): CloneNode | null {\n if (SKIP_TAGS.has(srcEl.tagName)) return null;\n \n // Get temporal bounds upfront for indexing\n const bounds = getTemporalBounds(srcEl);\n \n // SVG - clone entire subtree (no children tracking needed)\n if (srcEl instanceof SVGElement) {\n const svgClone = srcEl.cloneNode(true) as SVGElement;\n const node: CloneNode = {\n source: srcEl,\n clone: svgClone as unknown as HTMLElement,\n children: [],\n isCanvasClone: false,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n return node;\n }\n \n // Canvas - copy pixels\n if (srcEl instanceof HTMLCanvasElement) {\n const canvas = document.createElement(\"canvas\");\n canvas.width = srcEl.width;\n canvas.height = srcEl.height;\n const ctx = canvas.getContext(\"2d\");\n if (ctx) {\n try { ctx.drawImage(srcEl, 0, 0); } catch {}\n }\n // Raw canvas elements don't need style syncing, just return clone\n // return null;\n }\n \n // Custom elements with shadow canvas (e.g., ef-video, ef-image)\n const isCustom = srcEl.tagName.includes(\"-\");\n if (isCustom && srcEl.shadowRoot) {\n const shadowCanvas = srcEl.shadowRoot.querySelector(\"canvas\");\n if (shadowCanvas) {\n const clone = document.createElement(\"canvas\");\n clone.width = shadowCanvas.width || srcEl.clientWidth;\n clone.height = shadowCanvas.height || srcEl.clientHeight;\n // Check if the element actually has alpha channel before preserving it\n // ef-image tracks hasAlpha based on MIME type (JPEG=false, PNG/WebP=true)\n // ef-waveform always needs alpha for proper rendering\n if (srcEl.tagName === \"EF-WAVEFORM\") {\n clone.dataset.preserveAlpha = \"true\";\n } else if (srcEl.tagName === \"EF-IMAGE\" && \"hasAlpha\" in srcEl && (srcEl as any).hasAlpha) {\n clone.dataset.preserveAlpha = \"true\";\n }\n \n const ctx = clone.getContext(\"2d\");\n if (ctx) {\n try { ctx.drawImage(shadowCanvas, 0, 0); } catch {}\n }\n \n // Copy initial CSS styles using unified sync\n // Pass shadowCanvas as contentSource for width/height\n try {\n syncElementStyles(srcEl, clone, shadowCanvas);\n } catch {}\n \n // Map clone canvas to source element for RenderContext caching\n canvasSourceMap.set(clone, srcEl);\n \n const node: CloneNode = {\n source: srcEl,\n clone,\n children: [],\n isCanvasClone: true,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n return node;\n }\n \n const shadowImg = srcEl.shadowRoot.querySelector(\"img\");\n if (shadowImg?.complete && shadowImg.naturalWidth > 0) {\n const clone = document.createElement(\"canvas\");\n clone.width = shadowImg.naturalWidth;\n clone.height = shadowImg.naturalHeight;\n // Check if the element actually has alpha channel before preserving it\n // For direct img elements, check the element's hasAlpha property\n if (srcEl.tagName === \"EF-IMAGE\" && \"hasAlpha\" in srcEl && (srcEl as any).hasAlpha) {\n clone.dataset.preserveAlpha = \"true\";\n }\n const ctx = clone.getContext(\"2d\");\n if (ctx) {\n try { ctx.drawImage(shadowImg, 0, 0); } catch {}\n }\n \n // Copy initial CSS styles using unified sync\n // Pass shadowImg as contentSource for width/height\n try {\n syncElementStyles(srcEl, clone, shadowImg);\n } catch {}\n \n // Map clone canvas to source element for RenderContext caching\n canvasSourceMap.set(clone, srcEl);\n \n const node: CloneNode = {\n source: srcEl,\n clone,\n children: [],\n isCanvasClone: true,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n return node;\n }\n }\n \n // Standard element clone\n const clone = document.createElement(isCustom ? \"div\" : srcEl.tagName.toLowerCase()) as HTMLElement;\n \n // Copy attributes - OPTIMIZATION: Early exit if no attributes\n const attrs = srcEl.attributes;\n const attrLen = attrs.length;\n if (attrLen > 0) {\n for (let i = 0; i < attrLen; i++) {\n const attr = attrs[i]!;\n const name = attr.name.toLowerCase();\n if (name === \"id\" || name.startsWith(\"on\")) continue;\n if (isCustom && name !== \"class\" && !name.startsWith(\"data-\")) continue;\n try { clone.setAttribute(attr.name, attr.value); } catch {}\n }\n }\n \n if (srcEl instanceof HTMLImageElement && srcEl.src) {\n (clone as HTMLImageElement).src = srcEl.src;\n }\n if (srcEl instanceof HTMLInputElement) {\n (clone as HTMLInputElement).value = srcEl.value;\n }\n \n const node: CloneNode = {\n source: srcEl,\n clone,\n children: [],\n isCanvasClone: false,\n bounds,\n parent: parentNode,\n };\n nodeCount++;\n \n // Shadow DOM children - OPTIMIZATION: Early exit if no childNodes\n if (srcEl.shadowRoot) {\n const shadowChildren = srcEl.shadowRoot.childNodes;\n const shadowLen = shadowChildren.length;\n if (shadowLen > 0) {\n // For text segments, ALWAYS create a text node placeholder even if empty.\n // Caption elements now use light DOM, so they don't need special handling here.\n const isTextSegment = srcEl.tagName === 'EF-TEXT-SEGMENT';\n let hasTextNode = false;\n \n for (let i = 0; i < shadowLen; i++) {\n const child = shadowChildren[i]!;\n if (child.nodeType === Node.TEXT_NODE) {\n const text = child.textContent?.trim();\n // Always include text for text segments (even if whitespace-only, e.g., \" \")\n if (text || isTextSegment) {\n clone.appendChild(document.createTextNode(child.textContent || \"\"));\n hasTextNode = true;\n }\n } else if (child.nodeType === Node.ELEMENT_NODE) {\n const el = child as Element;\n if (el.tagName === \"STYLE\" || el.tagName === \"SLOT\") continue;\n const childNode = cloneElement(el, node);\n if (childNode) {\n node.children.push(childNode);\n clone.appendChild(childNode.clone);\n }\n }\n }\n \n // For text segments, ensure there's always a text node for syncStyles to update\n if (isTextSegment && !hasTextNode) {\n clone.appendChild(document.createTextNode(\"\"));\n }\n }\n }\n \n // Light DOM children - OPTIMIZATION: Use indexed loop for performance\n const lightChildren = srcEl.childNodes;\n const lightLen = lightChildren.length;\n for (let i = 0; i < lightLen; i++) {\n const child = lightChildren[i]!;\n if (child.nodeType === Node.TEXT_NODE) {\n const text = child.textContent?.trim();\n if (text) clone.appendChild(document.createTextNode(text));\n } else if (child.nodeType === Node.ELEMENT_NODE) {\n const childNode = cloneElement(child as Element, node);\n if (childNode) {\n node.children.push(childNode);\n clone.appendChild(childNode.clone);\n }\n }\n }\n \n return node;\n }\n \n const root = cloneElement(source, null);\n if (root) container.appendChild(root.clone);\n \n const syncState: SyncState = {\n tree: { root },\n nodeCount,\n canvasSourceMap,\n previousVisibleSet: new Set(),\n currentVisibleSet: new Set(),\n };\n \n // Sync styles in the same pass if timeMs is provided\n if (timeMs !== undefined && root) {\n syncStylesWithIndex(syncState, timeMs);\n }\n \n return {\n container,\n syncState,\n };\n}\n\n/**\n * Sync a single node's styles (extracted for reuse).\n * Now uses unified style syncing with clear separation of concerns:\n * 1. Canvas pixel refresh (if canvas clone)\n * 2. Unified CSS property sync (all elements)\n * 3. Content sync (text, input values)\n */\nfunction syncNodeStyles(node: CloneNode): void {\n const { source, clone, isCanvasClone } = node;\n \n // 1. Canvas-specific: Refresh pixel content from shadow DOM\n if (isCanvasClone) {\n refreshCanvasPixels(node);\n }\n \n // 2. Unified: Sync ALL CSS properties using SYNC_PROPERTIES array\n // For canvas clones, pass content source (shadow canvas/img) for width/height\n const contentSource = isCanvasClone\n ? (source.shadowRoot?.querySelector(\"canvas\") || source.shadowRoot?.querySelector(\"img\") || undefined)\n : undefined;\n syncElementStyles(source, clone, contentSource);\n \n // 3. Element-specific: Sync text content and input values\n syncTextContent(source, clone);\n syncInputValue(source, clone);\n}\n\n// Performance instrumentation counters\ninterface SyncStats {\n nodesVisited: number;\n nodesCulledByParent: number;\n nodesCulledByTemporal: number;\n nodesProcessed: number;\n nodesFullSync: number; // Newly visible nodes (full sync)\n nodesIncrementalSync: number; // Still visible nodes (incremental sync)\n nodesHidden: number; // Newly hidden nodes\n indexQueryTimeMs: number;\n syncTimeMs: number;\n}\n\nlet syncStats: SyncStats = {\n nodesVisited: 0,\n nodesCulledByParent: 0,\n nodesCulledByTemporal: 0,\n nodesProcessed: 0,\n nodesFullSync: 0,\n nodesIncrementalSync: 0,\n nodesHidden: 0,\n indexQueryTimeMs: 0,\n syncTimeMs: 0,\n};\n\n/**\n * Visibility delta between frames.\n * Used for incremental updates - only sync what changed.\n */\ninterface VisibilityDelta {\n nowVisible: Set<CloneNode>; // Need full style sync + show\n stillVisible: Set<CloneNode>; // Only sync animated properties (or skip if same time)\n nowHidden: Set<CloneNode>; // Just set display:none\n}\n\n/**\n * Compute visibility delta between previous and current frame.\n */\nfunction computeVisibilityDelta(\n previousSet: Set<CloneNode>,\n currentSet: Set<CloneNode>,\n): VisibilityDelta {\n const nowVisible = new Set<CloneNode>();\n const stillVisible = new Set<CloneNode>();\n const nowHidden = new Set<CloneNode>();\n \n // Find nodes that became visible or stayed visible\n for (const node of currentSet) {\n if (previousSet.has(node)) {\n stillVisible.add(node);\n } else {\n nowVisible.add(node);\n }\n }\n \n // Find nodes that became hidden\n for (const node of previousSet) {\n if (!currentSet.has(node)) {\n nowHidden.add(node);\n }\n }\n \n return { nowVisible, stillVisible, nowHidden };\n}\n\n/**\n * Build visible set by recursive traversal with bounds checking.\n * Queries fresh bounds from source elements each time - bounds are computed\n * dynamically by timegroups based on composition mode.\n */\nfunction buildVisibleSetRecursive(\n node: CloneNode,\n timeMs: number,\n visibleSet: Set<CloneNode>,\n): void {\n const { children, source } = node;\n \n // Get fresh bounds from source element (not cached - timegroup bounds are dynamic)\n const bounds = getTemporalBounds(source);\n \n // Check if this node is visible at current time\n const isVisible = timeMs >= bounds.startMs && timeMs <= bounds.endMs;\n \n if (isVisible) {\n visibleSet.add(node);\n // Recurse to children\n for (const child of children) {\n buildVisibleSetRecursive(child, timeMs, visibleSet);\n }\n }\n // If not visible, skip entire subtree\n}\n\n/**\n * Sync styles with recursive visibility check and delta tracking.\n * \n * DELTA TRACKING: Tracks visibility changes between frames to minimize work:\n * - nowVisible nodes: Full style sync + show\n * - stillVisible nodes: Incremental sync (source DOM may have changed)\n * - nowHidden nodes: Just hide (display:none)\n */\nfunction syncStylesWithIndex(state: SyncState, timeMs: number): void {\n const queryStart = performance.now();\n \n // Build the set of visible nodes by recursive traversal\n const visibleSet = new Set<CloneNode>();\n if (state.tree.root) {\n buildVisibleSetRecursive(state.tree.root, timeMs, visibleSet);\n }\n \n // Compute delta from previous frame\n const delta = computeVisibilityDelta(state.previousVisibleSet, visibleSet);\n \n syncStats.indexQueryTimeMs = performance.now() - queryStart;\n \n // Now traverse the tree but use the delta for O(1) sync decisions\n const syncStart = performance.now();\n if (state.tree.root) {\n syncNodeWithDelta(state.tree.root, visibleSet, delta);\n }\n syncStats.syncTimeMs = performance.now() - syncStart;\n \n // Update state for next frame and expose current visible set\n state.previousVisibleSet = visibleSet;\n state.currentVisibleSet = visibleSet;\n}\n\n/**\n * Sync a node using visibility delta for incremental updates.\n * \n * DELTA TRACKING optimization:\n * - nowVisible: Full style sync (element just appeared)\n * - stillVisible: Incremental sync (source DOM may have changed)\n * - nowHidden: Just hide the element\n * - Not in any set: Skip entirely (was already hidden)\n */\nfunction syncNodeWithDelta(\n node: CloneNode,\n visibleSet: Set<CloneNode>,\n delta: VisibilityDelta,\n): void {\n syncStats.nodesVisited++;\n \n const isVisible = visibleSet.has(node);\n \n if (!isVisible) {\n // Node is not visible - ALWAYS set display:none\n // This handles both \"just became hidden\" and \"initial build with node outside time range\"\n node.clone.style.display = \"none\";\n if (delta.nowHidden.has(node)) {\n syncStats.nodesHidden++;\n }\n // Already hidden nodes: skip (don't even recurse to children)\n syncStats.nodesCulledByTemporal++;\n return;\n }\n \n // Node is visible - determine sync strategy\n if (delta.nowVisible.has(node)) {\n // Just became visible - need full style sync\n syncNodeStyles(node);\n syncStats.nodesFullSync++;\n } else if (delta.stillVisible.has(node)) {\n // Was visible, still visible - still need to sync\n // Source DOM properties can change independently of time (input values, text, etc.)\n // TODO: Phase 5 could track property changes for smarter incremental sync\n syncNodeStyles(node);\n syncStats.nodesIncrementalSync++;\n }\n \n syncStats.nodesProcessed++;\n \n // Recurse to children\n for (const child of node.children) {\n syncNodeWithDelta(child, visibleSet, delta);\n }\n}\n\n/**\n * Legacy recursive sync (kept for comparison/fallback).\n * Returns early if the node is temporally culled, skipping ALL descendants.\n * @deprecated Use syncStylesWithIndex for better performance\n */\nexport function syncNodeRecursiveLegacy(node: CloneNode, timeMs: number): void {\n const { clone, children, bounds } = node;\n syncStats.nodesVisited++;\n \n // Temporal culling - check if this node is visible at current time\n // NOTE: Canvas clones now participate in temporal culling (lazy canvas copying).\n // Invalid bounds [0,0] are treated as [-Infinity, Infinity] by getTemporalBounds.\n {\n // OPTIMIZATION: Check if parent is already hidden to skip bounds computation\n const parent = clone.parentElement;\n if (parent instanceof HTMLElement) {\n // If parent has display:none, this element is already hidden - skip bounds check\n if (parent.style.display === \"none\") {\n clone.style.display = \"none\";\n syncStats.nodesCulledByParent++;\n return;\n }\n }\n \n // Use cached bounds from node instead of calling getTemporalBounds\n const { startMs, endMs } = bounds;\n if (timeMs < startMs || timeMs > endMs) {\n // Hide this element and BAIL OUT - skip all descendants automatically!\n clone.style.display = \"none\";\n syncStats.nodesCulledByTemporal++;\n return;\n }\n }\n \n // Sync this node's styles\n syncNodeStyles(node);\n syncStats.nodesProcessed++;\n \n // Recursively sync children\n for (const child of children) {\n syncNodeRecursiveLegacy(child, timeMs);\n }\n}\n\n/**\n * Sync all CSS properties from source elements to their clones.\n * Uses interval index for O(log n + k) visibility queries instead of O(n) traversal.\n * Uses delta tracking for incremental updates between frames.\n */\nexport function syncStyles(state: SyncState, timeMs: number): void {\n // Reset stats\n syncStats = {\n nodesVisited: 0,\n nodesCulledByParent: 0,\n nodesCulledByTemporal: 0,\n nodesProcessed: 0,\n nodesFullSync: 0,\n nodesIncrementalSync: 0,\n nodesHidden: 0,\n indexQueryTimeMs: 0,\n syncTimeMs: 0,\n };\n \n // Use interval-index-based sync with delta tracking\n syncStylesWithIndex(state, timeMs);\n}\n\n/**\n * Collect document styles for shadow DOM injection.\n */\nexport function collectDocumentStyles(): string {\n const rules: string[] = [];\n try {\n for (const sheet of document.styleSheets) {\n try {\n if (sheet.cssRules) {\n for (const rule of sheet.cssRules) {\n rules.push(rule.cssText);\n }\n }\n } catch {}\n }\n } catch {}\n return rules.join(\"\\n\");\n}\n\n\n// Backward-compatible aliases\nexport const syncStaticStyles = syncStyles;\nexport const syncAnimatedStyles = syncStyles;\n\n/**\n * Override clip-path, opacity, and optionally transform on the root clone element.\n * The source may have these properties set for proxy mode or workbench scaling.\n * \n * @param syncState - The sync state containing the clone tree\n * @param fullReset - If true, also resets opacity and transform (for capture operations)\n */\nexport function overrideRootCloneStyles(syncState: SyncState, fullReset: boolean = false): void {\n const rootClone = syncState.tree.root?.clone;\n if (!rootClone) return;\n \n rootClone.style.clipPath = \"none\";\n if (fullReset) {\n rootClone.style.opacity = \"1\";\n rootClone.style.transform = \"none\";\n }\n}\n\n/**\n * Create a live preview of a timegroup with a refresh function.\n * Used by EFWorkbench for the \"computed\" preview mode.\n * \n * @param source - The source timegroup to preview\n * @returns Object with preview container and refresh function\n */\nexport function renderTimegroupPreview(source: Element): {\n container: HTMLDivElement;\n refresh: (timeMs?: number) => void;\n} {\n const { container, syncState } = buildCloneStructure(source);\n \n // Initial style sync\n syncStyles(syncState, 0);\n \n return {\n container,\n refresh: (timeMs?: number) => {\n syncStyles(syncState, timeMs ?? 0);\n },\n };\n}\n"],"mappings":";;;;;;;AAcA,MAAM,YAAY,IAAI,IAAI;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;AAKF,MAAM,kBAAkB;CACtB;CAAW;CAAc;CACzB;CAAY;CAAO;CAAS;CAAU;CAAQ;CAC9C;CAAS;CAAU;CAAY;CAAa;CAAY;CACxD;CAAQ;CAAY;CAAkB;CAAc;CAAgB;CAAa;CACjF;CAAgB;CAAc;CAAW;CACzC;CAAU;CAAW;CACrB;CAAU;CAAa;CAAe;CAAgB;CAAc;CACpE;CAAc;CAAS;CAAa;CAAU;CAAkB;CAChE;CAAQ;CAAa;CAAkB;CACvC;CAAiB;CAAc;CAAgB;CAC/C;CAAa;CAAmB;CAChC;CAAe;CAAqB;CACpC;CAAU;CAAiB;CAAc;CAC1C;;;;AAKD,MAAM,wBAAwB,gBAAgB,KAAI,SAChD,KAAK,QAAQ,WAAU,MAAK,IAAI,EAAE,aAAa,GAAG,CACnD;;;;AAKD,MAAM,yBAAyB,OAAO,YAAY,eAAe,OAAO,QAAQ,UAAU,qBAAqB;;;;;;;;;;;;;;;;;AAkB/G,MAAMA,0BAA6D;CAEjE,WAAW;CACX,QAAQ;CACR,gBAAgB;CAChB,WAAW;CAGX,QAAQ;EAAC;EAAQ;EAAY;EAAO;EAAwB;CAC5D,WAAW;EAAC;EAAQ;EAAY;EAAO;EAAwB;CAC/D,aAAa;EAAC;EAAQ;EAAY;EAAO;EAAwB;CACjE,cAAc;EAAC;EAAQ;EAAY;EAAO;EAAwB;CAClE,YAAY;EAAC;EAAQ;EAAY;EAAO;EAAwB;CAChE,cAAc,CAAC,OAAO,IAAI;CAG1B,UAAU;CAGV,QAAQ;CAGR,gBAAgB;CAChB,aAAa;CACb,oBAAoB;CACrB;;;;AAKD,SAAS,eAAe,MAAc,OAAwB;CAC5D,MAAM,WAAW,wBAAwB;AACzC,KAAI,CAAC,SAAU,QAAO;AACtB,KAAI,MAAM,QAAQ,SAAS,CACzB,QAAO,SAAS,SAAS,MAAM;AAEjC,QAAO,aAAa;;;;;;;;;AA8DtB,SAAgB,kCAAkC,OAAqC;CACrF,MAAMC,UAA6B,EAAE;CACrC,MAAM,aAAa,MAAM;CAGzB,SAAS,MAAM,MAAuB;AAEpC,OAAK,MAAM,SAAS,KAAK,SACvB,OAAM,MAAM;AAId,MAAI,CAAC,WAAW,IAAI,KAAK,EAAE;GACzB,MAAM,SAAS,KAAK,MAAM;AAC1B,OAAI,QAAQ;IACV,MAAM,cAAc,KAAK,MAAM;AAC/B,WAAO,YAAY,KAAK,MAAM;AAC9B,YAAQ,KAAK;KAAE;KAAM;KAAQ;KAAa,CAAC;;;;AAKjD,KAAI,MAAM,KAAK,KACb,OAAM,MAAM,KAAK,KAAK;AAGxB,QAAO;;;;;;AAOT,SAAgB,mBAAmB,SAAkC;AAEnE,MAAK,IAAI,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;EAC5C,MAAM,EAAE,MAAM,QAAQ,gBAAgB,QAAQ;AAC9C,MAAI,YACF,QAAO,aAAa,KAAK,OAAO,YAAY;MAE5C,QAAO,YAAY,KAAK,MAAM;;;;;;;;;;;;;AA4CpC,SAAS,kBACP,QACA,OACA,eACM;CACN,MAAM,aAAa,MAAM;CACzB,MAAM,UAAW,OAAuB;AAIxC,KAHsB,CAAC,CAAC,eAGL;EACjB,IAAIC;EACJ,IAAIC;AAEJ,MAAI;AACF,QAAK,iBAAiB,OAAO;AAC7B,OAAI,cACF,aAAY,iBAAiB,cAAc;UAEvC;AAAE;;AAGV,aAAW,WAAW,GAAG;AACzB,aAAW,MAAM,GAAG;AACpB,aAAW,QAAQ,GAAG;AACtB,aAAW,SAAS,GAAG;AACvB,aAAW,OAAO,GAAG;AACrB,aAAW,SAAS,GAAG;AACvB,aAAW,SAAS,GAAG;AACvB,aAAW,YAAY,GAAG;AAC1B,aAAW,kBAAkB,GAAG;AAChC,aAAW,UAAU,GAAG;AACxB,aAAW,aAAa,GAAG;AAC3B,aAAW,qBAAqB,GAAG;AACnC,aAAW,iBAAiB,GAAG;AAG/B,MAAI,WAAW;AACb,cAAW,QAAQ,UAAU;AAC7B,cAAW,SAAS,UAAU;;AAGhC,aAAW,UAAU;AACrB,aAAW,YAAY;AACvB,aAAW,aAAa;AAExB;;CAIF,MAAM,UAAU,gBAAgB;AAEhC,KAAI,wBAAwB;EAC1B,IAAIC;AAEJ,MAAI;AACF,YAAS,OAAO,kBAAkB;UAC5B;AAAE;;AAEV,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,KAAK;GAChC,MAAM,QAAQ,sBAAsB;GACpC,MAAM,QAAQ,gBAAgB;GAE9B,MAAM,SAAS,OAAO,IAAI,MAAM;AAChC,OAAI,CAAC,OAAQ;GAEb,MAAM,SAAS,OAAO,UAAU;AAEhC,OAAI,UAAU,WAAW;AAUvB,eAAW,UADY,WAAW,UAAU,EANrB,YACrB,YAAY,6BACZ,YAAY,oCACZ,YAAY,mCACZ,YAAY,0BAEiD,UAAU;AAEzE;;AAKF,OAAI,UAAU,WAAY;AAI1B,OAAI,eAAe,OAAO,OAAO,EAAE;AAEjC,QAAI,WAAW,OAAQ,YAAW,SAAS;AAC3C;;AAGF,cAAW,SAAS;;QAEjB;EACL,IAAIF;AAEJ,MAAI;AACF,QAAK,iBAAiB,OAAO;UACvB;AAAE;;EAEV,MAAM,WAAW;AAEjB,OAAK,MAAM,QAAQ,iBAAiB;GAClC,MAAM,SAAS,SAAS;AACxB,OAAI,CAAC,OAAQ;AAEb,OAAI,SAAS,WAAW;AAUtB,eAAW,UADY,WAAW,UAAU,EANrB,YACrB,YAAY,6BACZ,YAAY,oCACZ,YAAY,mCACZ,YAAY,0BAEiD,UAAU;AAEzE;;AAKF,OAAI,SAAS,WAAY;AAGzB,OAAI,eAAe,MAAM,OAAO,EAAE;AAChC,QAAI,WAAW,MAAO,YAAW,QAAQ;AACzC;;AAGF,cAAW,QAAQ;;;AAKvB,YAAW,YAAY;AACvB,YAAW,aAAa;;;;;;AAO1B,SAAS,oBAAoB,MAAuB;CAClD,MAAM,EAAE,QAAQ,UAAU;CAC1B,MAAM,SAAS;CACf,MAAM,eAAe,OAAO,YAAY,cAAc,SAAS;CAC/D,MAAM,YAAY,OAAO,YAAY,cAAc,MAAM;AAEzD,KAAI,cAAc;AAEhB,MAAI,OAAO,UAAU,aAAa,MAAO,QAAO,QAAQ,aAAa;AACrE,MAAI,OAAO,WAAW,aAAa,OAAQ,QAAO,SAAS,aAAa;EAGxE,MAAM,MAAM,OAAO,WAAW,KAAK;AACnC,MAAI,OAAO,aAAa,QAAQ,KAAK,aAAa,SAAS,EACzD,KAAI;AACF,OAAI,UAAU,GAAG,GAAG,OAAO,OAAO,OAAO,OAAO;AAChD,OAAI,UAAU,cAAc,GAAG,EAAE;WAC1B,GAAG;AACV,UAAO,KAAK,6CAA6C,EAAE;;YAGtD,WAAW,YAAY,UAAU,eAAe,GAAG;AAE5D,MAAI,OAAO,UAAU,UAAU,aAAc,QAAO,QAAQ,UAAU;AACtE,MAAI,OAAO,WAAW,UAAU,cAAe,QAAO,SAAS,UAAU;EAGzE,MAAM,MAAM,OAAO,WAAW,KAAK;AACnC,MAAI,KAAK;AACP,OAAI,UAAU,GAAG,GAAG,OAAO,OAAO,OAAO,OAAO;AAChD,OAAI;AAAE,QAAI,UAAU,WAAW,GAAG,EAAE;WAAU;;;;;;;AAQpD,SAAS,gBAAgB,QAAiB,OAA0B;CAClE,MAAM,cAAc,OAAO,WAAW;AACtC,KAAI,aAAa,aAAa,KAAK,WAAW;EAC5C,MAAM,UAAU,YAAY,eAAe;EAC3C,MAAM,gBAAgB,MAAM,WAAW;AAEvC,MAAI,eAAe,aAAa,KAAK,WAEnC;OAAI,cAAc,gBAAgB,QAAS,eAAc,cAAc;aAC9D,CAAC,MAAM,WAAW,OAG3B,OAAM,YAAY,SAAS,eAAe,QAAQ,CAAC;;;;;;AAQzD,SAAS,eAAe,QAAiB,OAA0B;AACjE,KAAI,kBAAkB,kBAAkB;EACtC,MAAM,SAAS,OAAO;EACtB,MAAM,aAAa;AACnB,MAAI,WAAW,UAAU,QAAQ;AAC/B,cAAW,QAAQ;AACnB,cAAW,aAAa,SAAS,OAAO;;;;;;;;;AAU9C,SAAgB,oBAAoB,QAAiB,QAGnD;CACA,MAAM,YAAY,SAAS,cAAc,MAAM;AAC/C,WAAU,MAAM,UAAU;CAE1B,IAAI,YAAY;CAChB,MAAM,kCAAkB,IAAI,SAAqC;CAEjE,SAAS,aAAa,OAAgB,YAAgD;AACpF,MAAI,UAAU,IAAI,MAAM,QAAQ,CAAE,QAAO;EAGzC,MAAM,SAAS,kBAAkB,MAAM;AAGvC,MAAI,iBAAiB,YAAY;GAE/B,MAAMG,SAAkB;IACtB,QAAQ;IACR,OAHe,MAAM,UAAU,KAAK;IAIpC,UAAU,EAAE;IACZ,eAAe;IACf;IACA,QAAQ;IACT;AACD;AACA,UAAOC;;AAIT,MAAI,iBAAiB,mBAAmB;GACtC,MAAM,SAAS,SAAS,cAAc,SAAS;AAC/C,UAAO,QAAQ,MAAM;AACrB,UAAO,SAAS,MAAM;GACtB,MAAM,MAAM,OAAO,WAAW,KAAK;AACnC,OAAI,IACF,KAAI;AAAE,QAAI,UAAU,OAAO,GAAG,EAAE;WAAU;;EAO9C,MAAM,WAAW,MAAM,QAAQ,SAAS,IAAI;AAC5C,MAAI,YAAY,MAAM,YAAY;GAChC,MAAM,eAAe,MAAM,WAAW,cAAc,SAAS;AAC7D,OAAI,cAAc;IAChB,MAAMC,UAAQ,SAAS,cAAc,SAAS;AAC9C,YAAM,QAAQ,aAAa,SAAS,MAAM;AAC1C,YAAM,SAAS,aAAa,UAAU,MAAM;AAI5C,QAAI,MAAM,YAAY,cACpB,SAAM,QAAQ,gBAAgB;aACrB,MAAM,YAAY,cAAc,cAAc,SAAU,MAAc,SAC/E,SAAM,QAAQ,gBAAgB;IAGhC,MAAM,MAAMA,QAAM,WAAW,KAAK;AAClC,QAAI,IACF,KAAI;AAAE,SAAI,UAAU,cAAc,GAAG,EAAE;YAAU;AAKnD,QAAI;AACF,uBAAkB,OAAOA,SAAO,aAAa;YACvC;AAGR,oBAAgB,IAAIA,SAAO,MAAM;IAEjC,MAAMF,SAAkB;KACtB,QAAQ;KACR;KACA,UAAU,EAAE;KACZ,eAAe;KACf;KACA,QAAQ;KACT;AACD;AACA,WAAOC;;GAGT,MAAM,YAAY,MAAM,WAAW,cAAc,MAAM;AACvD,OAAI,WAAW,YAAY,UAAU,eAAe,GAAG;IACrD,MAAMC,UAAQ,SAAS,cAAc,SAAS;AAC9C,YAAM,QAAQ,UAAU;AACxB,YAAM,SAAS,UAAU;AAGzB,QAAI,MAAM,YAAY,cAAc,cAAc,SAAU,MAAc,SACxE,SAAM,QAAQ,gBAAgB;IAEhC,MAAM,MAAMA,QAAM,WAAW,KAAK;AAClC,QAAI,IACF,KAAI;AAAE,SAAI,UAAU,WAAW,GAAG,EAAE;YAAU;AAKhD,QAAI;AACF,uBAAkB,OAAOA,SAAO,UAAU;YACpC;AAGR,oBAAgB,IAAIA,SAAO,MAAM;IAEjC,MAAMF,SAAkB;KACtB,QAAQ;KACR;KACA,UAAU,EAAE;KACZ,eAAe;KACf;KACA,QAAQ;KACT;AACD;AACA,WAAOC;;;EAKX,MAAM,QAAQ,SAAS,cAAc,WAAW,QAAQ,MAAM,QAAQ,aAAa,CAAC;EAGpF,MAAM,QAAQ,MAAM;EACpB,MAAM,UAAU,MAAM;AACtB,MAAI,UAAU,EACZ,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,KAAK;GAChC,MAAM,OAAO,MAAM;GACnB,MAAM,OAAO,KAAK,KAAK,aAAa;AACpC,OAAI,SAAS,QAAQ,KAAK,WAAW,KAAK,CAAE;AAC5C,OAAI,YAAY,SAAS,WAAW,CAAC,KAAK,WAAW,QAAQ,CAAE;AAC/D,OAAI;AAAE,UAAM,aAAa,KAAK,MAAM,KAAK,MAAM;WAAU;;AAI7D,MAAI,iBAAiB,oBAAoB,MAAM,IAC7C,CAAC,MAA2B,MAAM,MAAM;AAE1C,MAAI,iBAAiB,iBACnB,CAAC,MAA2B,QAAQ,MAAM;EAG5C,MAAMD,OAAkB;GACtB,QAAQ;GACR;GACA,UAAU,EAAE;GACZ,eAAe;GACf;GACA,QAAQ;GACT;AACD;AAGA,MAAI,MAAM,YAAY;GACpB,MAAM,iBAAiB,MAAM,WAAW;GACxC,MAAM,YAAY,eAAe;AACjC,OAAI,YAAY,GAAG;IAGjB,MAAM,gBAAgB,MAAM,YAAY;IACxC,IAAI,cAAc;AAElB,SAAK,IAAI,IAAI,GAAG,IAAI,WAAW,KAAK;KAClC,MAAM,QAAQ,eAAe;AAC7B,SAAI,MAAM,aAAa,KAAK,WAG1B;UAFa,MAAM,aAAa,MAAM,IAE1B,eAAe;AACzB,aAAM,YAAY,SAAS,eAAe,MAAM,eAAe,GAAG,CAAC;AACnE,qBAAc;;gBAEP,MAAM,aAAa,KAAK,cAAc;MAC/C,MAAM,KAAK;AACX,UAAI,GAAG,YAAY,WAAW,GAAG,YAAY,OAAQ;MACrD,MAAM,YAAY,aAAa,IAAI,KAAK;AACxC,UAAI,WAAW;AACb,YAAK,SAAS,KAAK,UAAU;AAC7B,aAAM,YAAY,UAAU,MAAM;;;;AAMxC,QAAI,iBAAiB,CAAC,YACpB,OAAM,YAAY,SAAS,eAAe,GAAG,CAAC;;;EAMpD,MAAM,gBAAgB,MAAM;EAC5B,MAAM,WAAW,cAAc;AAC/B,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,KAAK;GACjC,MAAM,QAAQ,cAAc;AAC5B,OAAI,MAAM,aAAa,KAAK,WAAW;IACrC,MAAM,OAAO,MAAM,aAAa,MAAM;AACtC,QAAI,KAAM,OAAM,YAAY,SAAS,eAAe,KAAK,CAAC;cACjD,MAAM,aAAa,KAAK,cAAc;IAC/C,MAAM,YAAY,aAAa,OAAkB,KAAK;AACtD,QAAI,WAAW;AACb,UAAK,SAAS,KAAK,UAAU;AAC7B,WAAM,YAAY,UAAU,MAAM;;;;AAKxC,SAAO;;CAGT,MAAM,OAAO,aAAa,QAAQ,KAAK;AACvC,KAAI,KAAM,WAAU,YAAY,KAAK,MAAM;CAE3C,MAAMG,YAAuB;EAC3B,MAAM,EAAE,MAAM;EACd;EACA;EACA,oCAAoB,IAAI,KAAK;EAC7B,mCAAmB,IAAI,KAAK;EAC7B;AAGD,KAAI,WAAW,UAAa,KAC1B,qBAAoB,WAAW,OAAO;AAGxC,QAAO;EACL;EACA;EACD;;;;;;;;;AAUH,SAAS,eAAe,MAAuB;CAC7C,MAAM,EAAE,QAAQ,OAAO,kBAAkB;AAGzC,KAAI,cACF,qBAAoB,KAAK;AAQ3B,mBAAkB,QAAQ,OAHJ,gBACjB,OAAO,YAAY,cAAc,SAAS,IAAI,OAAO,YAAY,cAAc,MAAM,IAAI,SAC1F,OAC2C;AAG/C,iBAAgB,QAAQ,MAAM;AAC9B,gBAAe,QAAQ,MAAM;;AAgB/B,IAAIC,YAAuB;CACzB,cAAc;CACd,qBAAqB;CACrB,uBAAuB;CACvB,gBAAgB;CAChB,eAAe;CACf,sBAAsB;CACtB,aAAa;CACb,kBAAkB;CAClB,YAAY;CACb;;;;AAeD,SAAS,uBACP,aACA,YACiB;CACjB,MAAM,6BAAa,IAAI,KAAgB;CACvC,MAAM,+BAAe,IAAI,KAAgB;CACzC,MAAM,4BAAY,IAAI,KAAgB;AAGtC,MAAK,MAAM,QAAQ,WACjB,KAAI,YAAY,IAAI,KAAK,CACvB,cAAa,IAAI,KAAK;KAEtB,YAAW,IAAI,KAAK;AAKxB,MAAK,MAAM,QAAQ,YACjB,KAAI,CAAC,WAAW,IAAI,KAAK,CACvB,WAAU,IAAI,KAAK;AAIvB,QAAO;EAAE;EAAY;EAAc;EAAW;;;;;;;AAQhD,SAAS,yBACP,MACA,QACA,YACM;CACN,MAAM,EAAE,UAAU,WAAW;CAG7B,MAAM,SAAS,kBAAkB,OAAO;AAKxC,KAFkB,UAAU,OAAO,WAAW,UAAU,OAAO,OAEhD;AACb,aAAW,IAAI,KAAK;AAEpB,OAAK,MAAM,SAAS,SAClB,0BAAyB,OAAO,QAAQ,WAAW;;;;;;;;;;;AAczD,SAAS,oBAAoB,OAAkB,QAAsB;CACnE,MAAM,aAAa,YAAY,KAAK;CAGpC,MAAM,6BAAa,IAAI,KAAgB;AACvC,KAAI,MAAM,KAAK,KACb,0BAAyB,MAAM,KAAK,MAAM,QAAQ,WAAW;CAI/D,MAAM,QAAQ,uBAAuB,MAAM,oBAAoB,WAAW;AAE1E,WAAU,mBAAmB,YAAY,KAAK,GAAG;CAGjD,MAAM,YAAY,YAAY,KAAK;AACnC,KAAI,MAAM,KAAK,KACb,mBAAkB,MAAM,KAAK,MAAM,YAAY,MAAM;AAEvD,WAAU,aAAa,YAAY,KAAK,GAAG;AAG3C,OAAM,qBAAqB;AAC3B,OAAM,oBAAoB;;;;;;;;;;;AAY5B,SAAS,kBACP,MACA,YACA,OACM;AACN,WAAU;AAIV,KAAI,CAFc,WAAW,IAAI,KAAK,EAEtB;AAGd,OAAK,MAAM,MAAM,UAAU;AAC3B,MAAI,MAAM,UAAU,IAAI,KAAK,CAC3B,WAAU;AAGZ,YAAU;AACV;;AAIF,KAAI,MAAM,WAAW,IAAI,KAAK,EAAE;AAE9B,iBAAe,KAAK;AACpB,YAAU;YACD,MAAM,aAAa,IAAI,KAAK,EAAE;AAIvC,iBAAe,KAAK;AACpB,YAAU;;AAGZ,WAAU;AAGV,MAAK,MAAM,SAAS,KAAK,SACvB,mBAAkB,OAAO,YAAY,MAAM;;;;;;;AAqD/C,SAAgB,WAAW,OAAkB,QAAsB;AAEjE,aAAY;EACV,cAAc;EACd,qBAAqB;EACrB,uBAAuB;EACvB,gBAAgB;EAChB,eAAe;EACf,sBAAsB;EACtB,aAAa;EACb,kBAAkB;EAClB,YAAY;EACb;AAGD,qBAAoB,OAAO,OAAO;;;;;AAMpC,SAAgB,wBAAgC;CAC9C,MAAMC,QAAkB,EAAE;AAC1B,KAAI;AACF,OAAK,MAAM,SAAS,SAAS,YAC3B,KAAI;AACF,OAAI,MAAM,SACR,MAAK,MAAM,QAAQ,MAAM,SACvB,OAAM,KAAK,KAAK,QAAQ;UAGtB;SAEJ;AACR,QAAO,MAAM,KAAK,KAAK;;;;;;;;;AAezB,SAAgB,wBAAwB,WAAsB,YAAqB,OAAa;CAC9F,MAAM,YAAY,UAAU,KAAK,MAAM;AACvC,KAAI,CAAC,UAAW;AAEhB,WAAU,MAAM,WAAW;AAC3B,KAAI,WAAW;AACb,YAAU,MAAM,UAAU;AAC1B,YAAU,MAAM,YAAY;;;;;;;;;;AAWhC,SAAgB,uBAAuB,QAGrC;CACA,MAAM,EAAE,WAAW,cAAc,oBAAoB,OAAO;AAG5D,YAAW,WAAW,EAAE;AAExB,QAAO;EACL;EACA,UAAU,WAAoB;AAC5B,cAAW,WAAW,UAAU,EAAE;;EAErC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.11-beta",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"license": "UNLICENSED",
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@bramus/style-observer": "^1.3.0",
|
|
16
|
-
"@editframe/assets": "0.34.
|
|
16
|
+
"@editframe/assets": "0.34.11-beta",
|
|
17
17
|
"@lit/context": "^1.1.6",
|
|
18
18
|
"@opentelemetry/api": "^1.9.0",
|
|
19
19
|
"@opentelemetry/context-zone": "^1.26.0",
|