@editframe/elements 0.42.5 → 0.42.8
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/LICENSE-FULL.md +148 -0
- package/dist/elements/EFCaptions.d.ts +4 -4
- package/dist/elements/EFImage.d.ts +4 -4
- package/dist/elements/EFMedia.d.ts +2 -2
- package/dist/elements/EFTemporal.js +3 -1
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +4 -4
- package/dist/elements/EFText.js +1 -0
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.d.ts +4 -4
- package/dist/elements/EFTimegroup.js +12 -1
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +4 -4
- package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
- package/dist/gui/EFConfiguration.d.ts +4 -4
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFFilmstrip.d.ts +4 -4
- package/dist/gui/EFOverlayItem.d.ts +4 -4
- package/dist/gui/EFOverlayLayer.d.ts +4 -4
- 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/EFScrubber.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFTimelineRuler.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/timeline/EFTimeline.d.ts +2 -2
- package/dist/gui/timeline/EFTimeline.js +18 -1
- package/dist/gui/timeline/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/TrimHandles.d.ts +4 -4
- package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +4 -4
- package/dist/gui/tree/EFTree.d.ts +4 -4
- package/dist/gui/tree/EFTreeItem.d.ts +4 -4
- package/dist/preview/renderTimegroupToCanvas.js +9 -0
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.js +1 -1
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +2 -1
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
- package/package.json +8 -3
- package/scripts/build-css.js +0 -44
- package/test/__cache__/GET__api_v1_transcode_audio_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__32da3954ba60c96ad732020c65a08ebc/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__32da3954ba60c96ad732020c65a08ebc/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_audio_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__b0b2b07efcf607de8ee0f650328c32f7/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__b0b2b07efcf607de8ee0f650328c32f7/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_audio_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a75c2252b542e0c152c780e9a8d7b154/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a75c2252b542e0c152c780e9a8d7b154/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_audio_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a64ff1cfb1b52cae14df4b5dfa1e222b/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a64ff1cfb1b52cae14df4b5dfa1e222b/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_audio_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__91e8a522f950809b9f09f4173113b4b0/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__91e8a522f950809b9f09f4173113b4b0/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_audio_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__e66d2c831d951e74ad0aeaa6489795d0/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__e66d2c831d951e74ad0aeaa6489795d0/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +0 -1
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +0 -17
- package/test/__cache__/GET__api_v1_transcode_scrub_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__6ff5127ebeda578a679474347fbd6137/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_scrub_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__6ff5127ebeda578a679474347fbd6137/metadata.json +0 -16
- package/test/__cache__/GET__api_v1_transcode_scrub_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__f6d4793fc9ff854ee9a738917fb64a53/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_scrub_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__f6d4793fc9ff854ee9a738917fb64a53/metadata.json +0 -16
- package/test/cache-integration-verification.browsertest.ts +0 -84
- package/test/constants.ts +0 -8
- package/test/createJitTestClips.ts +0 -425
- package/test/profilingPlugin.ts +0 -221
- package/test/recordReplayProxyPlugin.js +0 -428
- package/test/setup.ts +0 -71
- package/test/useAssetMSW.ts +0 -53
- package/test/useMSW.ts +0 -40
- package/test/useTranscodeMSW.ts +0 -191
- package/test/visualRegressionUtils.ts +0 -300
- package/tsdown.config.ts +0 -65
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFText.js","names":["EFText","textNodes: ChildNode[]","#segmentsInitialized","segments","#lastPropagatedAnimation","wordBoundaries: Map<number, number> | null","currentWordIndex: number | null","currentWordSpan: HTMLSpanElement | null","staggerOffset: number | undefined","wordIndexForStagger: number","result: string[]"],"sources":["../../src/elements/EFText.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport { EFTemporal, flushStartTimeMsCache } from \"./EFTemporal.js\";\nimport { flushSequenceDurationCache } from \"./EFTimegroup.js\";\n\nimport { evaluateEasing } from \"./easingUtils.js\";\nimport type { EFTextSegment } from \"./EFTextSegment.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\nimport type { AnimatableElement } from \"./updateAnimations.js\";\n\nexport type SplitMode = \"line\" | \"word\" | \"char\";\n\n// Animation properties to propagate from ef-text to its segments.\n// animation-delay is intentionally excluded -- the stagger system manages delay per segment.\nconst animationPropsToPropagate = [\n \"animation-name\",\n \"animation-duration\",\n \"animation-timing-function\",\n \"animation-fill-mode\",\n \"animation-iteration-count\",\n \"animation-direction\",\n] as const;\n\n@customElement(\"ef-text\")\nexport class EFText extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline;\n }\n :host([split=\"line\"]) {\n display: inline-block;\n }\n `,\n ];\n\n @property({ type: String, reflect: true })\n split: SplitMode = \"word\";\n\n private validateSplit(value: string): SplitMode {\n if (value === \"line\" || value === \"word\" || value === \"char\") {\n return value as SplitMode;\n }\n console.warn(\n `Invalid split value \"${value}\". Must be \"line\", \"word\", or \"char\". Defaulting to \"word\".`,\n );\n return \"word\";\n }\n\n @property({\n type: Number,\n attribute: \"stagger\",\n converter: durationConverter,\n })\n staggerMs?: number;\n\n private validateStagger(value: number | undefined): number | undefined {\n if (value === undefined) return undefined;\n if (value < 0) {\n console.warn(`Invalid stagger value ${value}ms. Must be >= 0. Using 0.`);\n return 0;\n }\n return value;\n }\n\n @property({ type: String, reflect: true })\n easing = \"linear\";\n\n private mutationObserver?: MutationObserver;\n private animationObserver?: MutationObserver;\n private lastTextContent = \"\";\n private _textContent: string | null = null; // null means not initialized, \"\" means explicitly empty\n private _templateElement: HTMLTemplateElement | null = null;\n private _segmentsReadyResolvers: Array<() => void> = [];\n #segmentsInitialized = false;\n #lastPropagatedAnimation = \"\";\n\n render() {\n return html`<slot></slot>`;\n }\n\n // Store text content so we can use it even after DOM is cleared\n set textContent(value: string | null) {\n const newValue = value || \"\";\n // Only update if value actually changed\n if (this._textContent !== newValue) {\n this._textContent = newValue;\n\n // Find template element if not already stored\n if (!this._templateElement && this.isConnected) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Clear any existing text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n // Add new text node if value is not empty\n if (newValue) {\n const textNode = document.createTextNode(newValue);\n this.appendChild(textNode);\n }\n if (this.isConnected) {\n this.emitContentChange(\"content\");\n this.splitText();\n }\n }\n }\n\n get textContent(): string {\n // If _textContent is null, it hasn't been initialized - read from DOM\n if (this._textContent === null) {\n return this.getTextContent();\n }\n // Otherwise use stored value (even if empty string)\n return this._textContent;\n }\n\n /**\n * Get all ef-text-segment elements directly\n * @public\n */\n get segments(): EFTextSegment[] {\n return Array.from(\n this.querySelectorAll(\"ef-text-segment[data-segment-created]\"),\n ) as EFTextSegment[];\n }\n\n /**\n * Returns a promise that resolves when segments are ready (created and connected)\n * Use this to wait for segments after setting textContent or other properties\n * @public\n */\n async whenSegmentsReady(): Promise<EFTextSegment[]> {\n // Wait for text element to be updated first\n await this.updateComplete;\n\n // If no text content, segments will be empty - return immediately\n // Use same logic as splitText to read text content\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return [];\n }\n\n // If segments already initialized and exist, return immediately (no RAF waits)\n if (this.#segmentsInitialized && this.segments.length > 0) {\n await Promise.all(this.segments.map((seg) => seg.updateComplete));\n return this.segments;\n }\n\n // Check if segments are already in DOM (synchronous check - no RAF)\n let segments = this.segments;\n if (segments.length > 0) {\n // Segments exist, just wait for their Lit updates (no RAF)\n await Promise.all(segments.map((seg) => seg.updateComplete));\n this.#segmentsInitialized = true;\n return segments;\n }\n\n // Segments don't exist yet - use the promise-based mechanism (no RAF polling)\n // This waits for splitText() to complete and resolve the promise\n return new Promise<EFTextSegment[]>((resolve, reject) => {\n const timeout = setTimeout(() => {\n // Remove our resolver if we timeout\n const index = this._segmentsReadyResolvers.indexOf(resolveWithSegments);\n if (index > -1) {\n this._segmentsReadyResolvers.splice(index, 1);\n }\n reject(new Error(\"Timeout waiting for text segments to be created\"));\n }, 5000); // 5 second timeout\n\n const resolveWithSegments = () => {\n clearTimeout(timeout);\n // Wait for segment Lit updates\n const segments = this.segments;\n Promise.all(segments.map((seg) => seg.updateComplete)).then(() => {\n this.#segmentsInitialized = true;\n resolve(segments);\n });\n };\n\n this._segmentsReadyResolvers.push(resolveWithSegments);\n\n // Trigger splitText if it hasn't run yet\n // This handles the case where segments haven't been created at all\n if (segments.length === 0 && this.isConnected) {\n this.splitText();\n }\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n // Find and store template element before any modifications\n this._templateElement = this.querySelector(\"template\");\n\n // Initialize _textContent from DOM if not already set (for declarative usage)\n if (this._textContent === null) {\n this._textContent = this.getTextContent();\n this.lastTextContent = this._textContent;\n }\n\n // Use RAF to ensure DOM is fully ready before splitting text\n // Callers that need segments immediately (e.g., render clones) should await whenSegmentsReady()\n requestAnimationFrame(() => {\n this.setupMutationObserver();\n this.setupAnimationObserver();\n this.splitText();\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.mutationObserver?.disconnect();\n this.animationObserver?.disconnect();\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n if (\n changedProperties.has(\"split\") ||\n changedProperties.has(\"staggerMs\") ||\n changedProperties.has(\"easing\") ||\n changedProperties.has(\"durationMs\")\n ) {\n this.emitContentChange(\"content\");\n this.splitText();\n }\n\n if (changedProperties.has(\"_durationMs\") && this.parentTimegroup) {\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n }\n }\n\n private setupMutationObserver() {\n this.mutationObserver = new MutationObserver(() => {\n // Only react to changes that aren't from our own segment creation\n const currentText = this._textContent || this.getTextContent();\n if (currentText !== this.lastTextContent) {\n this._textContent = currentText;\n this.lastTextContent = currentText;\n this.splitText();\n }\n });\n\n this.mutationObserver.observe(this, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n\n private setupAnimationObserver() {\n this.animationObserver = new MutationObserver(() => {\n this.propagateAnimationToSegments();\n });\n\n this.animationObserver.observe(this, {\n attributes: true,\n attributeFilter: [\"class\", \"style\"],\n });\n }\n\n private propagateAnimationToSegments() {\n const segments = this.segments;\n if (segments.length === 0) return;\n\n const computed = window.getComputedStyle(this);\n const animationName = computed.animationName;\n\n // Build a fingerprint to avoid redundant work\n const fingerprint = animationPropsToPropagate\n .map((prop) => computed.getPropertyValue(prop))\n .join(\"|\");\n\n if (fingerprint === this.#lastPropagatedAnimation) return;\n this.#lastPropagatedAnimation = fingerprint;\n\n const hasAnimation = animationName && animationName !== \"none\";\n\n for (const segment of segments) {\n // Whitespace segments are layout spacers — they must not receive animation.\n // Animating them (especially with opacity/transform keyframes and backwards\n // fill-mode) causes spaces to visually collapse between animated words/chars.\n if (segment.hasAttribute(\"data-whitespace\")) {\n for (const prop of animationPropsToPropagate) {\n segment.style.removeProperty(prop);\n }\n continue;\n }\n\n if (hasAnimation) {\n for (const prop of animationPropsToPropagate) {\n let value = computed.getPropertyValue(prop);\n if (prop === \"animation-fill-mode\" && value === \"none\") {\n value = \"backwards\";\n }\n segment.style.setProperty(prop, value);\n }\n } else {\n for (const prop of animationPropsToPropagate) {\n segment.style.removeProperty(prop);\n }\n }\n }\n }\n\n private getTextContent(): string {\n // Get text content, handling both text nodes and HTML content\n let text = \"\";\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent || \"\";\n } else if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Skip ef-text-segment elements (they're created by us)\n if (element.tagName === \"EF-TEXT-SEGMENT\") {\n continue;\n }\n // Skip template elements (they're templates, not content)\n if (element.tagName === \"TEMPLATE\") {\n continue;\n }\n text += element.textContent || \"\";\n }\n }\n return text;\n }\n\n private splitText() {\n // Validate split mode\n const validatedSplit = this.validateSplit(this.split);\n if (validatedSplit !== this.split) {\n this.split = validatedSplit;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Validate stagger\n const validatedStagger = this.validateStagger(this.staggerMs);\n if (validatedStagger !== this.staggerMs) {\n this.staggerMs = validatedStagger;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Read text content - use stored _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n const trimmedText = text.trim();\n const textStartOffset = text.indexOf(trimmedText);\n\n // GUARD: Check if segments are already correct before clearing/recreating\n // This prevents redundant splits from RAF callbacks, updated(), etc.\n if (\n this.#segmentsInitialized &&\n this.segments.length > 0 &&\n this.lastTextContent === text\n ) {\n return;\n }\n\n if (!text || trimmedText.length === 0) {\n // Clear segments if no text\n const existingSegments = Array.from(\n this.querySelectorAll(\"ef-text-segment\"),\n );\n for (const segment of existingSegments) {\n segment.remove();\n }\n // Clear text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n this.lastTextContent = \"\";\n // Resolve any waiting promises\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n // Reset initialization flag when clearing segments\n this.#segmentsInitialized = false;\n return;\n }\n\n // Reset initialization flag when we're about to create new segments\n this.#segmentsInitialized = false;\n\n const segments = this.splitTextIntoSegments(text);\n const durationMs = this.durationMs || 1000; // Default 1 second if no duration\n\n // For character mode, detect word boundaries to wrap characters within words\n let wordBoundaries: Map<number, number> | null = null;\n if (this.split === \"char\") {\n wordBoundaries = this.detectWordBoundaries(text);\n }\n\n // Clear ALL child nodes (text nodes and segments) by replacing innerHTML\n // This ensures we don't have any leftover text nodes\n const fragment = document.createDocumentFragment();\n\n // Find template element if not already stored\n if (!this._templateElement) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Get template content structure\n // If template exists, clone it; otherwise create default ef-text-segment\n const templateContent = this._templateElement?.content;\n const templateSegments = templateContent\n ? Array.from(templateContent.querySelectorAll(\"ef-text-segment\"))\n : [];\n\n // If no template segments found, we'll create a default one\n const useTemplate = templateSegments.length > 0;\n const segmentsPerTextSegment = useTemplate ? templateSegments.length : 1;\n\n // For character mode with word wrapping, track current word and wrap segments\n let currentWordIndex: number | null = null;\n let currentWordSpan: HTMLSpanElement | null = null;\n let charIndex = 0; // Track position in original text for character mode\n\n // For word splitting, count only word segments (not whitespace) for stagger calculation\n const wordOnlySegments =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg))\n : segments;\n const wordSegmentCount = wordOnlySegments.length;\n\n // Track word index as we iterate (for word mode with duplicate words)\n // This ensures each occurrence of duplicate words gets a unique stagger index\n let wordStaggerIndex = 0;\n\n // Create new segments in a fragment first\n segments.forEach((segmentText, textIndex) => {\n // Calculate stagger offset if stagger is set\n let staggerOffset: number | undefined;\n if (this.staggerMs !== undefined) {\n // For word splitting, whitespace segments should inherit stagger from preceding word\n const isWhitespace = /^\\s+$/.test(segmentText);\n let wordIndexForStagger: number;\n\n if (this.split === \"word\" && isWhitespace) {\n // Whitespace inherits from the preceding word's index\n // Use the word stagger index (which is the index of the word before this whitespace)\n wordIndexForStagger = Math.max(0, wordStaggerIndex - 1);\n } else if (this.split === \"word\") {\n // For word mode, use the current word stagger index (incremented for each word encountered)\n // This ensures duplicate words get unique indices based on their position\n wordIndexForStagger = wordStaggerIndex;\n wordStaggerIndex++;\n } else {\n // For char/line mode, use the actual position in segments array\n wordIndexForStagger = textIndex;\n }\n\n // Apply easing to the stagger offset\n // Normalize index to 0-1 range (0 for first segment, 1 for last segment)\n const normalizedProgress =\n wordSegmentCount > 1\n ? wordIndexForStagger / (wordSegmentCount - 1)\n : 0;\n\n // Apply easing function to get eased progress\n const easedProgress = evaluateEasing(this.easing, normalizedProgress);\n\n // Calculate total stagger duration (last segment gets full stagger)\n const totalStaggerDuration = (wordSegmentCount - 1) * this.staggerMs;\n\n // Apply eased progress to total stagger duration\n staggerOffset = easedProgress * totalStaggerDuration;\n }\n\n if (useTemplate && templateContent) {\n // Clone template content for each text segment\n // This allows multiple ef-text-segment elements per character/word/line\n const clonedContent = templateContent.cloneNode(\n true,\n ) as DocumentFragment;\n const clonedSegments = Array.from(\n clonedContent.querySelectorAll(\"ef-text-segment\"),\n ) as EFTextSegment[];\n\n clonedSegments.forEach((segment, templateIndex) => {\n // Set properties - Lit will process these when element is connected\n segment.segmentText = segmentText;\n // Calculate segment index accounting for multiple segments per text segment\n segment.segmentIndex =\n textIndex * segmentsPerTextSegment + templateIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n if (/^\\s+$/.test(segmentText)) {\n segment.setAttribute(\"data-whitespace\", \"\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode with templates, also wrap in word spans\n if (this.split === \"char\" && wordBoundaries) {\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n if (wordIndex !== currentWordIndex) {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.style.whiteSpace = \"nowrap\";\n }\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n charIndex += segmentText.length;\n } else {\n fragment.appendChild(segment);\n }\n });\n } else {\n // No template - create default ef-text-segment\n const segment = document.createElement(\n \"ef-text-segment\",\n ) as EFTextSegment;\n\n segment.segmentText = segmentText;\n segment.segmentIndex = textIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n if (/^\\s+$/.test(segmentText)) {\n segment.setAttribute(\"data-whitespace\", \"\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode, wrap segments within words to prevent line breaks\n if (this.split === \"char\" && wordBoundaries) {\n // Map character index in trimmed text to original text position\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n // Check if we're starting a new word\n if (wordIndex !== currentWordIndex) {\n // Close previous word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n // Start new word span\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.style.whiteSpace = \"nowrap\";\n }\n // Append segment to current word span\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n // Not part of a word (whitespace/punctuation) - append directly\n // Close current word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n // Update character index for next iteration (in trimmed text)\n charIndex += segmentText.length;\n } else {\n // Not character mode or no word boundaries - append directly\n fragment.appendChild(segment);\n }\n }\n });\n\n // Close any remaining word span\n if (this.split === \"char\" && currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n\n // Ensure segments are connected to DOM before checking for animations\n // Append fragment first, then trigger updates\n\n // Replace all children with the fragment (this clears text nodes and old segments)\n // But preserve the template element if it exists\n const templateToPreserve = this._templateElement;\n while (this.firstChild) {\n const child = this.firstChild;\n // Don't remove the template element\n if (child === templateToPreserve) {\n // Skip template, but we need to move it after the fragment\n // So we'll remove it temporarily and re-add it after\n this.removeChild(child);\n continue;\n }\n this.removeChild(child);\n }\n this.appendChild(fragment);\n // Re-add template element if it existed\n if (templateToPreserve) {\n this.appendChild(templateToPreserve);\n }\n\n // Propagate animation properties from this element to newly created segments\n this.#lastPropagatedAnimation = \"\";\n this.propagateAnimationToSegments();\n\n // For template-path segments: their animation comes from a class, not from ef-text.\n // propagateAnimationToSegments() is a no-op when ef-text has no animation, but its\n // else-branch removes all animation inline styles — so we apply the fill-mode default\n // after propagation to avoid being cleared.\n if (useTemplate) {\n for (const segment of this.segments) {\n if (segment.hasAttribute(\"data-whitespace\")) continue;\n const computedFill = window\n .getComputedStyle(segment)\n .getPropertyValue(\"animation-fill-mode\");\n if (computedFill === \"none\" || computedFill === \"\") {\n segment.style.setProperty(\"animation-fill-mode\", \"backwards\");\n }\n }\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 // Mark segments as initialized to prevent redundant splits\n this.#segmentsInitialized = true;\n\n // Resolve any waiting promises after segments are connected (synchronous)\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n }\n\n private detectWordBoundaries(text: string): Map<number, number> {\n // Create a map from character index to word index\n // Characters within the same word will have the same word index\n const boundaries = new Map<number, number>();\n const trimmedText = text.trim();\n if (!trimmedText) {\n return boundaries;\n }\n\n // Use Intl.Segmenter to detect word boundaries\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n\n // Find the offset of trimmedText within the original text\n const textStart = text.indexOf(trimmedText);\n\n let wordIndex = 0;\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Map all character positions in this word to the same word index\n for (let i = 0; i < seg.segment.length; i++) {\n const charPos = textStart + seg.index + i;\n boundaries.set(charPos, wordIndex);\n }\n wordIndex++;\n }\n }\n\n return boundaries;\n }\n\n private splitTextIntoSegments(text: string): string[] {\n // Trim text before segmenting to remove leading/trailing whitespace\n const trimmedText = text.trim();\n if (!trimmedText) {\n return [];\n }\n\n switch (this.split) {\n case \"line\": {\n // Split on newlines and trim each line\n const lines = trimmedText.split(/\\r?\\n/);\n return lines\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n }\n case \"word\": {\n // Use Intl.Segmenter for locale-aware word segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n const result: string[] = [];\n\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Word-like segment - add it\n result.push(seg.segment);\n } else if (/^\\s+$/.test(seg.segment)) {\n // Whitespace segment - add it as-is\n result.push(seg.segment);\n } else {\n // Punctuation segment - attach to preceding word if it exists\n if (result.length > 0) {\n const lastItem = result[result.length - 1];\n if (lastItem && !/^\\s+$/.test(lastItem)) {\n result[result.length - 1] = lastItem + seg.segment;\n } else {\n result.push(seg.segment);\n }\n } else {\n result.push(seg.segment);\n }\n }\n }\n\n return result;\n }\n case \"char\": {\n // Use Intl.Segmenter for grapheme-aware character segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"grapheme\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n return segments.map((seg) => seg.segment);\n }\n default:\n return [trimmedText];\n }\n }\n\n get intrinsicDurationMs(): number | undefined {\n // If explicit duration is set, use it\n if (this.hasExplicitDuration) {\n return undefined; // Let explicit duration take precedence\n }\n\n // If we have a parent timegroup that dictates duration (fixed) or inherits it (fit),\n // we should inherit from it instead of using our intrinsic duration.\n // For 'sequence' and 'contain' modes, the parent relies on our intrinsic duration,\n // so we must provide it.\n if (this.parentTimegroup) {\n const mode = this.parentTimegroup.mode;\n if (mode === \"fixed\" || mode === \"fit\") {\n return undefined;\n }\n }\n\n // Otherwise, calculate from content\n // Use _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return 0;\n }\n\n // Use the same splitting logic as splitTextIntoSegments for consistency\n const segments = this.splitTextIntoSegments(text);\n // For word splitting, only count word segments (not whitespace) for intrinsic duration\n const segmentCount =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg)).length || 1\n : segments.length || 1;\n\n return segmentCount * 1000;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text\": EFText;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFText.js","names":["EFText","textNodes: ChildNode[]","#segmentsInitialized","segments","#lastPropagatedAnimation","wordBoundaries: Map<number, number> | null","currentWordIndex: number | null","currentWordSpan: HTMLSpanElement | null","staggerOffset: number | undefined","wordIndexForStagger: number","result: string[]"],"sources":["../../src/elements/EFText.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { durationConverter } from \"./durationConverter.js\";\nimport { EFTemporal, flushStartTimeMsCache } from \"./EFTemporal.js\";\nimport { flushSequenceDurationCache } from \"./EFTimegroup.js\";\n\nimport { evaluateEasing } from \"./easingUtils.js\";\nimport type { EFTextSegment } from \"./EFTextSegment.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\nimport type { AnimatableElement } from \"./updateAnimations.js\";\n\nexport type SplitMode = \"line\" | \"word\" | \"char\";\n\n// Animation properties to propagate from ef-text to its segments.\n// animation-delay is intentionally excluded -- the stagger system manages delay per segment.\nconst animationPropsToPropagate = [\n \"animation-name\",\n \"animation-duration\",\n \"animation-timing-function\",\n \"animation-fill-mode\",\n \"animation-iteration-count\",\n \"animation-direction\",\n] as const;\n\n@customElement(\"ef-text\")\nexport class EFText extends EFTemporal(LitElement) {\n static styles = [\n css`\n :host {\n display: inline;\n }\n :host([split=\"line\"]) {\n display: inline-block;\n }\n `,\n ];\n\n @property({ type: String, reflect: true })\n split: SplitMode = \"word\";\n\n private validateSplit(value: string): SplitMode {\n if (value === \"line\" || value === \"word\" || value === \"char\") {\n return value as SplitMode;\n }\n console.warn(\n `Invalid split value \"${value}\". Must be \"line\", \"word\", or \"char\". Defaulting to \"word\".`,\n );\n return \"word\";\n }\n\n @property({\n type: Number,\n attribute: \"stagger\",\n converter: durationConverter,\n })\n staggerMs?: number;\n\n private validateStagger(value: number | undefined): number | undefined {\n if (value === undefined) return undefined;\n if (value < 0) {\n console.warn(`Invalid stagger value ${value}ms. Must be >= 0. Using 0.`);\n return 0;\n }\n return value;\n }\n\n @property({ type: String, reflect: true })\n easing = \"linear\";\n\n private mutationObserver?: MutationObserver;\n private animationObserver?: MutationObserver;\n private lastTextContent = \"\";\n private _textContent: string | null = null; // null means not initialized, \"\" means explicitly empty\n private _templateElement: HTMLTemplateElement | null = null;\n private _segmentsReadyResolvers: Array<() => void> = [];\n #segmentsInitialized = false;\n #lastPropagatedAnimation = \"\";\n\n render() {\n return html`<slot></slot>`;\n }\n\n // Store text content so we can use it even after DOM is cleared\n set textContent(value: string | null) {\n const newValue = value || \"\";\n // Only update if value actually changed\n if (this._textContent !== newValue) {\n this._textContent = newValue;\n\n // Find template element if not already stored\n if (!this._templateElement && this.isConnected) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Clear any existing text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n // Add new text node if value is not empty\n if (newValue) {\n const textNode = document.createTextNode(newValue);\n this.appendChild(textNode);\n }\n if (this.isConnected) {\n this.emitContentChange(\"content\");\n this.splitText();\n }\n }\n }\n\n get textContent(): string {\n // If _textContent is null, it hasn't been initialized - read from DOM\n if (this._textContent === null) {\n return this.getTextContent();\n }\n // Otherwise use stored value (even if empty string)\n return this._textContent;\n }\n\n /**\n * Get all ef-text-segment elements directly\n * @public\n */\n get segments(): EFTextSegment[] {\n return Array.from(\n this.querySelectorAll(\"ef-text-segment[data-segment-created]\"),\n ) as EFTextSegment[];\n }\n\n /**\n * Returns a promise that resolves when segments are ready (created and connected)\n * Use this to wait for segments after setting textContent or other properties\n * @public\n */\n async whenSegmentsReady(): Promise<EFTextSegment[]> {\n // Wait for text element to be updated first\n await this.updateComplete;\n\n // If no text content, segments will be empty - return immediately\n // Use same logic as splitText to read text content\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return [];\n }\n\n // If segments already initialized and exist, return immediately (no RAF waits)\n if (this.#segmentsInitialized && this.segments.length > 0) {\n await Promise.all(this.segments.map((seg) => seg.updateComplete));\n return this.segments;\n }\n\n // Check if segments are already in DOM (synchronous check - no RAF)\n let segments = this.segments;\n if (segments.length > 0) {\n // Segments exist, just wait for their Lit updates (no RAF)\n await Promise.all(segments.map((seg) => seg.updateComplete));\n this.#segmentsInitialized = true;\n return segments;\n }\n\n // Segments don't exist yet - use the promise-based mechanism (no RAF polling)\n // This waits for splitText() to complete and resolve the promise\n return new Promise<EFTextSegment[]>((resolve, reject) => {\n const timeout = setTimeout(() => {\n // Remove our resolver if we timeout\n const index = this._segmentsReadyResolvers.indexOf(resolveWithSegments);\n if (index > -1) {\n this._segmentsReadyResolvers.splice(index, 1);\n }\n reject(new Error(\"Timeout waiting for text segments to be created\"));\n }, 5000); // 5 second timeout\n\n const resolveWithSegments = () => {\n clearTimeout(timeout);\n // Wait for segment Lit updates\n const segments = this.segments;\n Promise.all(segments.map((seg) => seg.updateComplete)).then(() => {\n this.#segmentsInitialized = true;\n resolve(segments);\n });\n };\n\n this._segmentsReadyResolvers.push(resolveWithSegments);\n\n // Trigger splitText if it hasn't run yet\n // This handles the case where segments haven't been created at all\n if (segments.length === 0 && this.isConnected) {\n this.splitText();\n }\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n // Find and store template element before any modifications\n this._templateElement = this.querySelector(\"template\");\n\n // Initialize _textContent from DOM if not already set (for declarative usage)\n if (this._textContent === null) {\n this._textContent = this.getTextContent();\n this.lastTextContent = this._textContent;\n }\n\n // Use RAF to ensure DOM is fully ready before splitting text\n // Callers that need segments immediately (e.g., render clones) should await whenSegmentsReady()\n requestAnimationFrame(() => {\n this.setupMutationObserver();\n this.setupAnimationObserver();\n this.splitText();\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.mutationObserver?.disconnect();\n this.animationObserver?.disconnect();\n }\n\n protected override updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n if (\n changedProperties.has(\"split\") ||\n changedProperties.has(\"staggerMs\") ||\n changedProperties.has(\"easing\") ||\n changedProperties.has(\"durationMs\")\n ) {\n this.emitContentChange(\"content\");\n this.splitText();\n }\n\n if (changedProperties.has(\"_durationMs\") && this.parentTimegroup) {\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n }\n }\n\n private setupMutationObserver() {\n this.mutationObserver = new MutationObserver(() => {\n // Only react to changes that aren't from our own segment creation\n const currentText = this._textContent || this.getTextContent();\n if (currentText !== this.lastTextContent) {\n this._textContent = currentText;\n this.lastTextContent = currentText;\n this.splitText();\n }\n });\n\n this.mutationObserver.observe(this, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n\n private setupAnimationObserver() {\n this.animationObserver = new MutationObserver(() => {\n this.propagateAnimationToSegments();\n });\n\n this.animationObserver.observe(this, {\n attributes: true,\n attributeFilter: [\"class\", \"style\"],\n });\n }\n\n private propagateAnimationToSegments() {\n const segments = this.segments;\n if (segments.length === 0) return;\n\n const computed = window.getComputedStyle(this);\n const animationName = computed.animationName;\n\n // Build a fingerprint to avoid redundant work\n const fingerprint = animationPropsToPropagate\n .map((prop) => computed.getPropertyValue(prop))\n .join(\"|\");\n\n if (fingerprint === this.#lastPropagatedAnimation) return;\n this.#lastPropagatedAnimation = fingerprint;\n\n const hasAnimation = animationName && animationName !== \"none\";\n\n for (const segment of segments) {\n // Whitespace segments are layout spacers — they must not receive animation.\n // Animating them (especially with opacity/transform keyframes and backwards\n // fill-mode) causes spaces to visually collapse between animated words/chars.\n if (segment.hasAttribute(\"data-whitespace\")) {\n for (const prop of animationPropsToPropagate) {\n segment.style.removeProperty(prop);\n }\n continue;\n }\n\n if (hasAnimation) {\n for (const prop of animationPropsToPropagate) {\n let value = computed.getPropertyValue(prop);\n if (prop === \"animation-fill-mode\" && value === \"none\") {\n value = \"backwards\";\n }\n segment.style.setProperty(prop, value);\n }\n } else {\n for (const prop of animationPropsToPropagate) {\n segment.style.removeProperty(prop);\n }\n }\n }\n }\n\n private getTextContent(): string {\n // Get text content, handling both text nodes and HTML content\n let text = \"\";\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent || \"\";\n } else if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Skip ef-text-segment elements (they're created by us)\n if (element.tagName === \"EF-TEXT-SEGMENT\") {\n continue;\n }\n // Skip template elements (they're templates, not content)\n if (element.tagName === \"TEMPLATE\") {\n continue;\n }\n text += element.textContent || \"\";\n }\n }\n return text;\n }\n\n private splitText() {\n // Validate split mode\n const validatedSplit = this.validateSplit(this.split);\n if (validatedSplit !== this.split) {\n this.split = validatedSplit;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Validate stagger\n const validatedStagger = this.validateStagger(this.staggerMs);\n if (validatedStagger !== this.staggerMs) {\n this.staggerMs = validatedStagger;\n return; // Will trigger updated() which calls splitText() again\n }\n\n // Read text content - use stored _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n const trimmedText = text.trim();\n const textStartOffset = text.indexOf(trimmedText);\n\n // GUARD: Check if segments are already correct before clearing/recreating\n // This prevents redundant splits from RAF callbacks, updated(), etc.\n if (\n this.#segmentsInitialized &&\n this.segments.length > 0 &&\n this.lastTextContent === text\n ) {\n return;\n }\n\n if (!text || trimmedText.length === 0) {\n // Clear segments if no text\n const existingSegments = Array.from(\n this.querySelectorAll(\"ef-text-segment\"),\n );\n for (const segment of existingSegments) {\n segment.remove();\n }\n // Clear text nodes\n const textNodes: ChildNode[] = [];\n for (const node of Array.from(this.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n textNodes.push(node as ChildNode);\n }\n }\n for (const node of textNodes) {\n node.remove();\n }\n this.lastTextContent = \"\";\n // Resolve any waiting promises\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n // Reset initialization flag when clearing segments\n this.#segmentsInitialized = false;\n return;\n }\n\n // Reset initialization flag when we're about to create new segments\n this.#segmentsInitialized = false;\n\n const segments = this.splitTextIntoSegments(text);\n const durationMs = this.durationMs || 1000; // Default 1 second if no duration\n\n // For character mode, detect word boundaries to wrap characters within words\n let wordBoundaries: Map<number, number> | null = null;\n if (this.split === \"char\") {\n wordBoundaries = this.detectWordBoundaries(text);\n }\n\n // Clear ALL child nodes (text nodes and segments) by replacing innerHTML\n // This ensures we don't have any leftover text nodes\n const fragment = document.createDocumentFragment();\n\n // Find template element if not already stored\n if (!this._templateElement) {\n this._templateElement = this.querySelector(\"template\");\n }\n\n // Get template content structure\n // If template exists, clone it; otherwise create default ef-text-segment\n const templateContent = this._templateElement?.content;\n const templateSegments = templateContent\n ? Array.from(templateContent.querySelectorAll(\"ef-text-segment\"))\n : [];\n\n // If no template segments found, we'll create a default one\n const useTemplate = templateSegments.length > 0;\n const segmentsPerTextSegment = useTemplate ? templateSegments.length : 1;\n\n // For character mode with word wrapping, track current word and wrap segments\n let currentWordIndex: number | null = null;\n let currentWordSpan: HTMLSpanElement | null = null;\n let charIndex = 0; // Track position in original text for character mode\n\n // For word splitting, count only word segments (not whitespace) for stagger calculation\n const wordOnlySegments =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg))\n : segments;\n const wordSegmentCount = wordOnlySegments.length;\n\n // Track word index as we iterate (for word mode with duplicate words)\n // This ensures each occurrence of duplicate words gets a unique stagger index\n let wordStaggerIndex = 0;\n\n // Create new segments in a fragment first\n segments.forEach((segmentText, textIndex) => {\n // Calculate stagger offset if stagger is set\n let staggerOffset: number | undefined;\n if (this.staggerMs !== undefined) {\n // For word splitting, whitespace segments should inherit stagger from preceding word\n const isWhitespace = /^\\s+$/.test(segmentText);\n let wordIndexForStagger: number;\n\n if (this.split === \"word\" && isWhitespace) {\n // Whitespace inherits from the preceding word's index\n // Use the word stagger index (which is the index of the word before this whitespace)\n wordIndexForStagger = Math.max(0, wordStaggerIndex - 1);\n } else if (this.split === \"word\") {\n // For word mode, use the current word stagger index (incremented for each word encountered)\n // This ensures duplicate words get unique indices based on their position\n wordIndexForStagger = wordStaggerIndex;\n wordStaggerIndex++;\n } else {\n // For char/line mode, use the actual position in segments array\n wordIndexForStagger = textIndex;\n }\n\n // Apply easing to the stagger offset\n // Normalize index to 0-1 range (0 for first segment, 1 for last segment)\n const normalizedProgress =\n wordSegmentCount > 1\n ? wordIndexForStagger / (wordSegmentCount - 1)\n : 0;\n\n // Apply easing function to get eased progress\n const easedProgress = evaluateEasing(this.easing, normalizedProgress);\n\n // Calculate total stagger duration (last segment gets full stagger)\n const totalStaggerDuration = (wordSegmentCount - 1) * this.staggerMs;\n\n // Apply eased progress to total stagger duration\n staggerOffset = easedProgress * totalStaggerDuration;\n }\n\n if (useTemplate && templateContent) {\n // Clone template content for each text segment\n // This allows multiple ef-text-segment elements per character/word/line\n const clonedContent = templateContent.cloneNode(\n true,\n ) as DocumentFragment;\n const clonedSegments = Array.from(\n clonedContent.querySelectorAll(\"ef-text-segment\"),\n ) as EFTextSegment[];\n\n clonedSegments.forEach((segment, templateIndex) => {\n // Set properties - Lit will process these when element is connected\n segment.segmentText = segmentText;\n // Calculate segment index accounting for multiple segments per text segment\n segment.segmentIndex =\n textIndex * segmentsPerTextSegment + templateIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n if (/^\\s+$/.test(segmentText)) {\n segment.setAttribute(\"data-whitespace\", \"\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode with templates, also wrap in word spans\n if (this.split === \"char\" && wordBoundaries) {\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n if (wordIndex !== currentWordIndex) {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.style.whiteSpace = \"nowrap\";\n }\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n charIndex += segmentText.length;\n } else {\n fragment.appendChild(segment);\n }\n });\n } else {\n // No template - create default ef-text-segment\n const segment = document.createElement(\n \"ef-text-segment\",\n ) as EFTextSegment;\n\n segment.segmentText = segmentText;\n segment.segmentIndex = textIndex;\n segment.segmentStartMs = 0;\n segment.segmentEndMs = durationMs;\n segment.staggerOffsetMs = staggerOffset ?? 0;\n\n // Set data attribute for line mode to enable block display\n if (this.split === \"line\") {\n segment.setAttribute(\"data-line-segment\", \"true\");\n }\n\n if (/^\\s+$/.test(segmentText)) {\n segment.setAttribute(\"data-whitespace\", \"\");\n }\n\n // Mark as created to avoid being picked up as template\n segment.setAttribute(\"data-segment-created\", \"true\");\n\n // For character mode, wrap segments within words to prevent line breaks\n if (this.split === \"char\" && wordBoundaries) {\n // Map character index in trimmed text to original text position\n const originalCharIndex = textStartOffset + charIndex;\n const wordIndex = wordBoundaries.get(originalCharIndex);\n if (wordIndex !== undefined) {\n // Check if we're starting a new word\n if (wordIndex !== currentWordIndex) {\n // Close previous word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n // Start new word span\n currentWordIndex = wordIndex;\n currentWordSpan = document.createElement(\"span\");\n currentWordSpan.style.whiteSpace = \"nowrap\";\n }\n // Append segment to current word span\n if (currentWordSpan) {\n currentWordSpan.appendChild(segment);\n } else {\n fragment.appendChild(segment);\n }\n } else {\n // Not part of a word (whitespace/punctuation) - append directly\n // Close current word span if it exists\n if (currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n currentWordSpan = null;\n currentWordIndex = null;\n }\n fragment.appendChild(segment);\n }\n // Update character index for next iteration (in trimmed text)\n charIndex += segmentText.length;\n } else {\n // Not character mode or no word boundaries - append directly\n fragment.appendChild(segment);\n }\n }\n });\n\n // Close any remaining word span\n if (this.split === \"char\" && currentWordSpan) {\n fragment.appendChild(currentWordSpan);\n }\n\n // Ensure segments are connected to DOM before checking for animations\n // Append fragment first, then trigger updates\n\n // Replace all children with the fragment (this clears text nodes and old segments)\n // But preserve the template element if it exists\n const templateToPreserve = this._templateElement;\n while (this.firstChild) {\n const child = this.firstChild;\n // Don't remove the template element\n if (child === templateToPreserve) {\n // Skip template, but we need to move it after the fragment\n // So we'll remove it temporarily and re-add it after\n this.removeChild(child);\n continue;\n }\n this.removeChild(child);\n }\n this.appendChild(fragment);\n // Re-add template element if it existed\n if (templateToPreserve) {\n this.appendChild(templateToPreserve);\n }\n\n // Propagate animation properties from this element to newly created segments\n this.#lastPropagatedAnimation = \"\";\n this.propagateAnimationToSegments();\n\n // For template-path segments: their animation comes from a class, not from ef-text.\n // propagateAnimationToSegments() is a no-op when ef-text has no animation, but its\n // else-branch removes all animation inline styles — so we apply the fill-mode default\n // after propagation to avoid being cleared.\n if (useTemplate) {\n for (const segment of this.segments) {\n if (segment.hasAttribute(\"data-whitespace\")) continue;\n const computedFill = window\n .getComputedStyle(segment)\n .getPropertyValue(\"animation-fill-mode\");\n if (computedFill === \"none\" || computedFill === \"\") {\n segment.style.setProperty(\"animation-fill-mode\", \"backwards\");\n }\n }\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 // Mark segments as initialized to prevent redundant splits\n this.#segmentsInitialized = true;\n\n // Resolve any waiting promises after segments are connected (synchronous)\n this._segmentsReadyResolvers.forEach((resolve) => {\n resolve();\n });\n this._segmentsReadyResolvers = [];\n }\n\n private detectWordBoundaries(text: string): Map<number, number> {\n // Create a map from character index to word index\n // Characters within the same word will have the same word index\n const boundaries = new Map<number, number>();\n const trimmedText = text.trim();\n if (!trimmedText) {\n return boundaries;\n }\n\n // Use Intl.Segmenter to detect word boundaries\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n\n // Find the offset of trimmedText within the original text\n const textStart = text.indexOf(trimmedText);\n\n let wordIndex = 0;\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Map all character positions in this word to the same word index\n for (let i = 0; i < seg.segment.length; i++) {\n const charPos = textStart + seg.index + i;\n boundaries.set(charPos, wordIndex);\n }\n wordIndex++;\n }\n }\n\n return boundaries;\n }\n\n private splitTextIntoSegments(text: string): string[] {\n // Trim text before segmenting to remove leading/trailing whitespace\n const trimmedText = text.trim();\n if (!trimmedText) {\n return [];\n }\n\n switch (this.split) {\n case \"line\": {\n // Split on newlines and trim each line\n const lines = trimmedText.split(/\\r?\\n/);\n return lines\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n }\n case \"word\": {\n // Use Intl.Segmenter for locale-aware word segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"word\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n const result: string[] = [];\n\n for (const seg of segments) {\n if (seg.isWordLike) {\n // Word-like segment - add it\n result.push(seg.segment);\n } else if (/^\\s+$/.test(seg.segment)) {\n // Whitespace segment - add it as-is\n result.push(seg.segment);\n } else {\n // Punctuation segment - attach to preceding word if it exists\n if (result.length > 0) {\n const lastItem = result[result.length - 1];\n if (lastItem && !/^\\s+$/.test(lastItem)) {\n result[result.length - 1] = lastItem + seg.segment;\n } else {\n result.push(seg.segment);\n }\n } else {\n result.push(seg.segment);\n }\n }\n }\n\n return result;\n }\n case \"char\": {\n // Use Intl.Segmenter for grapheme-aware character segmentation\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: \"grapheme\",\n });\n const segments = Array.from(segmenter.segment(trimmedText));\n return segments.map((seg) => seg.segment);\n }\n default:\n return [trimmedText];\n }\n }\n\n get intrinsicDurationMs(): number | undefined {\n // If explicit duration is set, use it\n if (this.hasExplicitDuration) {\n return undefined; // Let explicit duration take precedence\n }\n\n // If we have a parent timegroup that dictates duration (fixed) or inherits it (fit),\n // we should inherit from it instead of using our intrinsic duration.\n // For 'sequence' and 'contain' modes, the parent relies on our intrinsic duration,\n // so we must provide it.\n if (this.parentTimegroup) {\n const mode = this.parentTimegroup.mode;\n if (mode === \"fixed\" || mode === \"fit\") {\n return undefined;\n }\n }\n\n // Otherwise, calculate from content\n // Use _textContent if set, otherwise read from DOM\n const text =\n this._textContent !== null ? this._textContent : this.getTextContent();\n if (!text || text.trim().length === 0) {\n return 0;\n }\n\n // Use the same splitting logic as splitTextIntoSegments for consistency\n const segments = this.splitTextIntoSegments(text);\n // For word splitting, only count word segments (not whitespace) for intrinsic duration\n const segmentCount =\n this.split === \"word\"\n ? segments.filter((seg) => !/^\\s+$/.test(seg)).length || 1\n : segments.length || 1;\n\n return segmentCount * 1000;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-text\": EFText;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,13 +1,13 @@
|
|
|
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:
|
|
10
|
-
render():
|
|
9
|
+
static styles: lit6.CSSResult[];
|
|
10
|
+
render(): lit_html6.TemplateResult<1>;
|
|
11
11
|
private setCSSVariables;
|
|
12
12
|
protected firstUpdated(): void;
|
|
13
13
|
protected updated(): void;
|
|
@@ -240,6 +240,10 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
240
240
|
const detail = e.detail;
|
|
241
241
|
this.emitContentChange(detail?.reason ?? "content");
|
|
242
242
|
};
|
|
243
|
+
#childDurationChangeHandler = () => {
|
|
244
|
+
durationCache.delete(this);
|
|
245
|
+
this.requestUpdate();
|
|
246
|
+
};
|
|
243
247
|
#recomputeAggregateReadyState() {
|
|
244
248
|
const children = shallowGetTemporalElements(this);
|
|
245
249
|
if (children.length === 0) {
|
|
@@ -265,10 +269,12 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
265
269
|
for (const child of this.#trackedChildren) if (!currentChildren.has(child)) {
|
|
266
270
|
child.removeEventListener("readystatechange", this.#childReadyStateHandler);
|
|
267
271
|
child.removeEventListener("contentchange", this.#childContentChangeHandler);
|
|
272
|
+
child.removeEventListener("durationchange", this.#childDurationChangeHandler);
|
|
268
273
|
}
|
|
269
274
|
for (const child of currentChildren) if (!this.#trackedChildren.has(child)) {
|
|
270
275
|
child.addEventListener("readystatechange", this.#childReadyStateHandler);
|
|
271
276
|
child.addEventListener("contentchange", this.#childContentChangeHandler);
|
|
277
|
+
child.addEventListener("durationchange", this.#childDurationChangeHandler);
|
|
272
278
|
}
|
|
273
279
|
this.#trackedChildren = currentChildren;
|
|
274
280
|
this.#recomputeAggregateReadyState();
|
|
@@ -761,7 +767,7 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
761
767
|
this.#setupPlaybackListener();
|
|
762
768
|
const hostname = typeof window !== "undefined" ? window.location.hostname : "";
|
|
763
769
|
const isEditframeDomain = hostname === "editframe.com" || hostname.endsWith(".editframe.com");
|
|
764
|
-
if (this.playbackController && typeof
|
|
770
|
+
if (this.playbackController && typeof __EF_TELEMETRY_ENABLED__ !== "undefined" && __EF_TELEMETRY_ENABLED__ && !isEditframeDomain) fetch("https://editframe.com/api/v1/telemetry", {
|
|
765
771
|
method: "POST",
|
|
766
772
|
headers: { "Content-Type": "application/json" },
|
|
767
773
|
body: JSON.stringify({ event_type: "load" }),
|
|
@@ -791,6 +797,10 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
791
797
|
if (changedProperties.has("mode") || changedProperties.has("overlapMs")) durationCache.delete(this);
|
|
792
798
|
if (this.#previousDurationMs !== this.durationMs) {
|
|
793
799
|
this.#previousDurationMs = this.durationMs;
|
|
800
|
+
this.dispatchEvent(new CustomEvent("durationchange", {
|
|
801
|
+
bubbles: true,
|
|
802
|
+
composed: true
|
|
803
|
+
}));
|
|
794
804
|
if (!this.hasAttribute("data-no-playback-controller")) this.#runThrottledFrameTask();
|
|
795
805
|
}
|
|
796
806
|
}
|
|
@@ -802,6 +812,7 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
802
812
|
for (const child of this.#trackedChildren) {
|
|
803
813
|
child.removeEventListener("readystatechange", this.#childReadyStateHandler);
|
|
804
814
|
child.removeEventListener("contentchange", this.#childContentChangeHandler);
|
|
815
|
+
child.removeEventListener("durationchange", this.#childDurationChangeHandler);
|
|
805
816
|
}
|
|
806
817
|
this.#trackedChildren.clear();
|
|
807
818
|
this.#qualityUpgradeScheduler.dispose();
|