@motion.page/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +817 -0
- package/dist/core/Animation.d.ts +230 -0
- package/dist/core/AnimationBuilder.d.ts +125 -0
- package/dist/core/Engine.d.ts +140 -0
- package/dist/core/Motion.d.ts +56 -0
- package/dist/core/PropTween.d.ts +81 -0
- package/dist/core/Ticker.d.ts +107 -0
- package/dist/core/Timeline.d.ts +294 -0
- package/dist/easing/index.d.ts +65 -0
- package/dist/fit/FitResolver.d.ts +12 -0
- package/dist/index.cjs +34 -0
- package/dist/index.cjs.map +52 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +52 -0
- package/dist/memory/AnimationPool.d.ts +14 -0
- package/dist/memory/ObjectPool.d.ts +37 -0
- package/dist/memory/PropTweenPool.d.ts +14 -0
- package/dist/registries/SDKRegistry.d.ts +69 -0
- package/dist/render/CSSRenderer.d.ts +33 -0
- package/dist/render/PathRenderer.d.ts +24 -0
- package/dist/render/RenderBatch.d.ts +27 -0
- package/dist/render/TransformCache.d.ts +54 -0
- package/dist/stagger/StaggerResolver.d.ts +28 -0
- package/dist/triggers/BaseTrigger.d.ts +61 -0
- package/dist/triggers/CursorTrigger.d.ts +86 -0
- package/dist/triggers/EventTrigger.d.ts +48 -0
- package/dist/triggers/GestureTrigger.d.ts +137 -0
- package/dist/triggers/MarkerManager.d.ts +42 -0
- package/dist/triggers/MouseMoveTrigger.d.ts +49 -0
- package/dist/triggers/PageExitTrigger.d.ts +74 -0
- package/dist/triggers/PageLoadTrigger.d.ts +37 -0
- package/dist/triggers/PinManager.d.ts +131 -0
- package/dist/triggers/ScrollTrigger.d.ts +103 -0
- package/dist/triggers/TriggerManager.d.ts +133 -0
- package/dist/types/index.d.ts +355 -0
- package/dist/types/public.d.ts +7 -0
- package/dist/utils/ColorParser.d.ts +23 -0
- package/dist/utils/DrawSVGParser.d.ts +62 -0
- package/dist/utils/FilterParser.d.ts +49 -0
- package/dist/utils/MotionUtils.d.ts +136 -0
- package/dist/utils/PathParser.d.ts +89 -0
- package/dist/utils/PositionParser.d.ts +24 -0
- package/dist/utils/PropertyParser.d.ts +159 -0
- package/dist/utils/QuickSetter.d.ts +16 -0
- package/dist/utils/ScrollPositionParser.d.ts +19 -0
- package/dist/utils/StyleReset.d.ts +73 -0
- package/dist/utils/TargetResolver.d.ts +23 -0
- package/dist/utils/TextSplitter.d.ts +90 -0
- package/dist/utils/executeTimelineAction.d.ts +18 -0
- package/dist/utils/getLayoutRect.d.ts +10 -0
- package/package.json +56 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/utils/executeTimelineAction.ts", "../src/registries/SDKRegistry.ts", "../src/triggers/TriggerManager.ts", "../src/utils/TargetResolver.ts", "../src/triggers/BaseTrigger.ts", "../src/utils/getLayoutRect.ts", "../src/triggers/EventTrigger.ts", "../src/render/TransformCache.ts", "../src/render/RenderBatch.ts", "../src/core/Ticker.ts", "../src/utils/PathParser.ts", "../src/utils/PropertyParser.ts", "../src/utils/StyleReset.ts", "../src/triggers/PinManager.ts", "../src/utils/ScrollPositionParser.ts", "../src/triggers/MarkerManager.ts", "../src/triggers/ScrollTrigger.ts", "../src/triggers/PageLoadTrigger.ts", "../src/triggers/MouseMoveTrigger.ts", "../src/triggers/GestureTrigger.ts", "../src/render/CSSRenderer.ts", "../src/render/PathRenderer.ts", "../src/core/PropTween.ts", "../src/memory/ObjectPool.ts", "../src/memory/PropTweenPool.ts", "../src/core/AnimationBuilder.ts", "../src/utils/PositionParser.ts", "../src/core/Timeline.ts", "../src/utils/QuickSetter.ts", "../src/triggers/CursorTrigger.ts", "../src/triggers/PageExitTrigger.ts", "../src/utils/ColorParser.ts", "../src/utils/FilterParser.ts", "../src/utils/DrawSVGParser.ts", "../src/utils/TextSplitter.ts", "../src/stagger/StaggerResolver.ts", "../src/fit/FitResolver.ts", "../src/easing/index.ts", "../src/core/Animation.ts", "../src/memory/AnimationPool.ts", "../src/core/Engine.ts", "../src/utils/MotionUtils.ts", "../src/core/Motion.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Minimal interface for timeline playback control.\n * Using an interface instead of importing Timeline directly to avoid circular dependencies\n * (utils/ never imports from core/).\n */\nexport interface PlaybackTarget {\n play(): unknown;\n pause(): unknown;\n reverse(): unknown;\n restart(): unknown;\n progress(value: number): unknown;\n}\n\nexport type TimelineAction = 'play' | 'pause' | 'resume' | 'reverse' | 'restart' | 'reset' | 'complete' | 'none';\n\n/**\n * Execute a GSAP-compatible timeline action. Maps action strings to method calls.\n * Covers all 8 GSAP toggleActions keywords.\n */\nexport function executeTimelineAction(action: TimelineAction, timeline: PlaybackTarget): void {\n switch (action) {\n case 'play':\n case 'resume': // GSAP compat — resume is semantically identical to play()\n timeline.play();\n break;\n case 'pause':\n timeline.pause();\n break;\n case 'reverse':\n timeline.reverse();\n break;\n case 'restart':\n timeline.restart();\n break;\n case 'reset':\n timeline.pause();\n timeline.progress(0);\n break;\n case 'complete':\n timeline.progress(1);\n timeline.pause();\n break;\n case 'none':\n // Intentional no-op\n break;\n default: {\n const unknownAction = action as unknown as string;\n console.warn(\n `[Motion] Unknown timeline action: \"${unknownAction}\". ` +\n `Valid actions: play, pause, resume, reverse, restart, reset, complete, none`\n );\n }\n }\n}\n",
|
|
6
|
+
"/**\n * SDKRegistry - Central registry for all optional SDK modules\n *\n * Optional modules register themselves here when loaded.\n * Core modules access features through this registry with optional chaining.\n * This enables tree-shaking: unused modules aren't bundled.\n */\n\nimport type { ParsedFilterFunction } from '../utils/FilterParser';\nimport type { ParsedDrawSVG } from '../utils/DrawSVGParser';\nimport type { AnimationTarget, StaggerVars, SplitType } from '../types';\nimport type { TriggerManager } from '../triggers/TriggerManager';\n\n// --- Parser interfaces ---\n\ninterface ColorParserFunctions {\n parseColor: (color: string, element?: Element) => Float32Array | null;\n getCurrentColor: (element: Element, property: string) => Float32Array;\n}\n\ninterface FilterParserFunctions {\n parseFilter: (filter: string) => ParsedFilterFunction[] | null;\n getCurrentFilter: (element: Element) => ParsedFilterFunction[];\n mergeFilterArrays: (start: ParsedFilterFunction[], end: ParsedFilterFunction[]) => { start: ParsedFilterFunction[]; end: ParsedFilterFunction[] };\n interpolateFilters: (start: ParsedFilterFunction[], end: ParsedFilterFunction[], progress: number) => ParsedFilterFunction[];\n filterToString: (filters: ParsedFilterFunction[]) => string;\n}\n\ninterface DrawSVGParserFunctions {\n parseDrawSVG: (value: string | { start?: number; end?: number }, element?: Element) => ParsedDrawSVG | null;\n getCurrentDrawSVG: (element: Element) => ParsedDrawSVG;\n getPathLength: (element: Element) => number;\n applyDrawSVG: (element: Element, start: number, end: number, length: number) => void;\n}\n\n// --- Utility interfaces ---\n\ninterface TextSplitterFunctions {\n split: (element: Element, type: SplitType, options?: { mask?: boolean }) => HTMLElement[];\n revert: (element: Element) => boolean;\n isSplit: (element: Element) => boolean;\n}\n\ninterface StyleResetFunctions {\n registerAnimatedProps: (element: Element, props: string[]) => void;\n clearAnimationStylesForProps: (element: Element, props: string[]) => void;\n clearAnimationStylesAndUnregister: (element: Element) => void;\n clearPinStylesAndUnregister: (element: Element) => void;\n}\n\ninterface FitResolverFunctions {\n registerPendingSetup(\n animation: import('../core/Animation').Animation,\n element: Element,\n fitConfig: import('../types').FitConfig,\n propTweenPool: typeof import('../memory/PropTweenPool').PropTweenPool\n ): void;\n}\n\n/**\n * Unified registry for all optional SDK features.\n * Each slot is null until the corresponding module loads and self-registers.\n */\nexport const SDKRegistry = {\n // Parsers\n color: null as ColorParserFunctions | null,\n filter: null as FilterParserFunctions | null,\n drawSVG: null as DrawSVGParserFunctions | null,\n\n // Utilities\n stagger: null as { resolve: (targets: AnimationTarget[], stagger: number | StaggerVars) => number[] } | null,\n textSplitter: null as TextSplitterFunctions | null,\n triggerManager: null as { getInstance: () => TriggerManager } | null,\n styleReset: null as StyleResetFunctions | null,\n fit: null as FitResolverFunctions | null,\n};\n",
|
|
7
|
+
"/**\n * TriggerManager\n *\n * Coordinates all trigger types (pageLoad, pageExit, scroll, hover, click, mouseMove, gesture)\n * and activates timelines based on trigger conditions.\n *\n * Uses a registry pattern for trigger classes to avoid importing them directly.\n * Each trigger module registers itself via TriggerManager.registerTrigger() when loaded.\n * This enables tree-shaking: only trigger types included in the bundle are available.\n */\n\nimport type { ScrollConfig, MouseMoveConfig, GestureConfig, HoverConfig, ClickConfig, CursorConfig, PageExitConfig } from '../types';\nimport type { Timeline } from '../core/Timeline';\nimport { SDKRegistry } from '../registries/SDKRegistry';\n\n/** Config for page load trigger */\nexport interface PageLoadTriggerConfig {\n timing?: 'before' | 'during' | 'after';\n}\n\n/** Interface that trigger instances must satisfy for cleanup */\ninterface TriggerInstance {\n enable(): void;\n disable(): void;\n}\n\n/** Interface for PageLoadTrigger specifically */\ninterface PageLoadTriggerInstance {\n register(timeline: Timeline, config: PageLoadTriggerConfig): void;\n unregister(timeline: Timeline): void;\n clear(): void;\n _reset(): void;\n}\n\n/** Trigger class constructors stored in registry */\ntype TriggerFactory = new (...args: any[]) => TriggerInstance;\n\nexport class TriggerManager {\n private static _instance: TriggerManager;\n\n /** Registry for trigger class constructors - populated by each trigger module on load */\n private static _triggerRegistry: Map<string, TriggerFactory> = new Map();\n /** PageLoadTrigger factory - separate because it has a different interface */\n private static _pageLoadTriggerFactory: (new () => PageLoadTriggerInstance) | null = null;\n\n /** Debounce delay (ms) for the window resize → refreshScrollTriggers handler */\n private static readonly _RESIZE_DEBOUNCE_MS = 200;\n\n private _pageLoadTrigger: PageLoadTriggerInstance | null = null;\n private _scrollTriggers: Map<Timeline, TriggerInstance> = new Map();\n private _eventTriggers: Map<Timeline, TriggerInstance> = new Map();\n private _mouseMoveTriggers: Map<Timeline, TriggerInstance> = new Map();\n private _gestureTriggers: Map<Timeline, TriggerInstance> = new Map();\n private _cursorTriggers: Map<Timeline, TriggerInstance> = new Map();\n private _pageExitTriggers: Map<Timeline, TriggerInstance> = new Map();\n\n /** Bound resize handler (created once, reused for add/removeEventListener) */\n private _resizeHandler: (() => void) | null = null;\n /** Timer id for debounced resize */\n private _resizeTimer: ReturnType<typeof setTimeout> | null = null;\n\n /** All trigger maps — used by unregister() and killAll() for bulk iteration */\n private get _allTriggerMaps(): Map<Timeline, TriggerInstance>[] {\n return [\n this._scrollTriggers,\n this._eventTriggers,\n this._mouseMoveTriggers,\n this._gestureTriggers,\n this._cursorTriggers,\n this._pageExitTriggers,\n ];\n }\n\n private constructor() {\n // PageLoadTrigger is created lazily on first use, not eagerly in constructor\n }\n\n static getInstance(): TriggerManager {\n if (!TriggerManager._instance) {\n TriggerManager._instance = new TriggerManager();\n }\n return TriggerManager._instance;\n }\n\n /**\n * Register a trigger class. Called by each trigger module when it loads.\n * @internal\n */\n static registerTrigger(type: string, factory: TriggerFactory): void {\n TriggerManager._triggerRegistry.set(type, factory);\n }\n\n /**\n * Register the PageLoadTrigger class. Called by PageLoadTrigger module when it loads.\n * @internal\n */\n static registerPageLoadTrigger(factory: new () => PageLoadTriggerInstance): void {\n TriggerManager._pageLoadTriggerFactory = factory;\n }\n\n /** Get or create the PageLoadTrigger instance */\n private _getPageLoadTrigger(): PageLoadTriggerInstance | null {\n if (!this._pageLoadTrigger && TriggerManager._pageLoadTriggerFactory) {\n this._pageLoadTrigger = new TriggerManager._pageLoadTriggerFactory();\n }\n return this._pageLoadTrigger;\n }\n\n /** Create a trigger instance from the registry */\n private _createTrigger(type: string, ...args: any[]): TriggerInstance | null {\n const Factory = TriggerManager._triggerRegistry.get(type);\n if (!Factory) {\n const available = Array.from(TriggerManager._triggerRegistry.keys());\n console.warn(\n `[Motion SDK] Trigger type '${type}' is not available in this bundle. ` +\n `Available triggers: ${available.join(', ') || 'none'}. ` +\n `Animations using .on${type.charAt(0).toUpperCase() + type.slice(1)}() will be skipped.`\n );\n return null;\n }\n return new Factory(...args);\n }\n\n /**\n * Register a pageLoad trigger\n */\n registerPageLoad(timeline: Timeline, config: PageLoadTriggerConfig): void {\n this.unregister(timeline);\n const pageLoad = this._getPageLoadTrigger();\n if (pageLoad) {\n pageLoad.register(timeline, config);\n }\n }\n\n /**\n * Attach a debounced window resize listener that refreshes all scroll triggers.\n * Called lazily when the first scroll trigger is registered.\n * @internal\n */\n private _attachResizeListener(): void {\n if (this._resizeHandler || typeof window === 'undefined') return;\n\n this._resizeHandler = () => {\n if (this._resizeTimer !== null) clearTimeout(this._resizeTimer);\n this._resizeTimer = setTimeout(() => {\n this._resizeTimer = null;\n this.refreshScrollTriggers();\n }, TriggerManager._RESIZE_DEBOUNCE_MS);\n };\n\n window.addEventListener('resize', this._resizeHandler, { passive: true });\n }\n\n /**\n * Detach the window resize listener.\n * Called when the last scroll trigger is removed or killAll() is invoked.\n * @internal\n */\n private _detachResizeListener(): void {\n if (!this._resizeHandler || typeof window === 'undefined') return;\n\n window.removeEventListener('resize', this._resizeHandler);\n if (this._resizeTimer !== null) {\n clearTimeout(this._resizeTimer);\n this._resizeTimer = null;\n }\n this._resizeHandler = null;\n }\n\n /**\n * Register a scroll trigger\n */\n registerScroll(timeline: Timeline, config: ScrollConfig): void {\n this.unregister(timeline);\n const scrollTrigger = this._createTrigger('scroll', timeline, config);\n if (!scrollTrigger) return; // Gracefully skip if trigger module not loaded\n this._scrollTriggers.set(timeline, scrollTrigger);\n timeline._trigger = scrollTrigger;\n scrollTrigger.enable();\n\n // Lazily attach resize listener when first scroll trigger is added\n this._attachResizeListener();\n }\n\n /**\n * Register a hover trigger\n */\n registerHover(timeline: Timeline, config: HoverConfig): void {\n this.unregister(timeline);\n const eventTrigger = this._createTrigger('event', timeline, {\n type: 'hover',\n target: config.target,\n onLeave: config.onLeave,\n leaveDelay: config.leaveDelay,\n });\n if (!eventTrigger) return; // Gracefully skip if trigger module not loaded\n this._eventTriggers.set(timeline, eventTrigger);\n timeline._trigger = eventTrigger;\n eventTrigger.enable();\n }\n\n /**\n * Register a click trigger\n */\n registerClick(timeline: Timeline, config: ClickConfig): void {\n this.unregister(timeline);\n const eventTrigger = this._createTrigger('event', timeline, {\n type: 'click',\n target: config.target,\n secondTarget: config.secondTarget,\n toggle: config.toggle,\n preventDefault: config.preventDefault,\n });\n if (!eventTrigger) return; // Gracefully skip if trigger module not loaded\n this._eventTriggers.set(timeline, eventTrigger);\n timeline._trigger = eventTrigger;\n eventTrigger.enable();\n }\n\n /**\n * Register a mouseMove trigger\n */\n registerMouseMove(timeline: Timeline, config: MouseMoveConfig): void {\n this.unregister(timeline);\n const mouseMoveTrigger = this._createTrigger('mouseMove', timeline, config);\n if (!mouseMoveTrigger) return; // Gracefully skip if trigger module not loaded\n this._mouseMoveTriggers.set(timeline, mouseMoveTrigger);\n timeline._trigger = mouseMoveTrigger;\n mouseMoveTrigger.enable();\n }\n\n /**\n * Register a gesture trigger\n */\n registerGesture(timeline: Timeline, config: GestureConfig): void {\n this.unregister(timeline);\n const gestureTrigger = this._createTrigger('gesture', timeline, config);\n if (!gestureTrigger) return; // Gracefully skip if trigger module not loaded\n this._gestureTriggers.set(timeline, gestureTrigger);\n timeline._trigger = gestureTrigger;\n gestureTrigger.enable();\n }\n\n /**\n * Register a cursor trigger\n */\n registerCursor(timeline: Timeline, config: CursorConfig): void {\n this.unregister(timeline);\n const cursorTrigger = this._createTrigger('cursor', timeline, config);\n if (!cursorTrigger) return; // Gracefully skip if trigger module not loaded\n this._cursorTriggers.set(timeline, cursorTrigger);\n timeline._trigger = cursorTrigger;\n cursorTrigger.enable();\n }\n\n /**\n * Register a page exit trigger\n */\n registerPageExit(timeline: Timeline, config: PageExitConfig): void {\n this.unregister(timeline);\n const pageExitTrigger = this._createTrigger('pageExit', timeline, config);\n if (!pageExitTrigger) return; // Gracefully skip if trigger module not loaded\n this._pageExitTriggers.set(timeline, pageExitTrigger);\n timeline._trigger = pageExitTrigger;\n pageExitTrigger.enable();\n }\n\n /**\n * Unregister a timeline\n */\n unregister(timeline: Timeline): void {\n // PageLoad uses a separate API (unregister per-timeline, not disable())\n const pageLoad = this._getPageLoadTrigger();\n if (pageLoad) {\n pageLoad.unregister(timeline);\n }\n\n // Disable and remove from all trigger maps\n for (const map of this._allTriggerMaps) {\n const trigger = map.get(timeline);\n if (trigger) {\n trigger.disable();\n map.delete(timeline);\n }\n }\n\n timeline._trigger = undefined;\n\n // Detach resize listener when no scroll triggers remain\n if (this._scrollTriggers.size === 0) {\n this._detachResizeListener();\n }\n }\n\n /**\n * Refresh all position-based triggers after layout changes (e.g. window resize).\n * This includes scroll triggers, hover event triggers, gesture triggers, and\n * mouseMove triggers — all of which cache element bounds at setup time.\n */\n refreshScrollTriggers(): void {\n this._scrollTriggers.forEach(trigger => (trigger as any).refresh?.());\n this._eventTriggers.forEach(trigger => (trigger as any).refresh?.());\n this._gestureTriggers.forEach(trigger => (trigger as any).refresh?.());\n this._mouseMoveTriggers.forEach(trigger => (trigger as any).refresh?.());\n }\n\n /**\n * Kill all triggers\n */\n killAll(): void {\n // PageLoad uses .clear() (kills all at once, not per-timeline)\n const pageLoad = this._getPageLoadTrigger();\n if (pageLoad) {\n pageLoad.clear();\n }\n\n for (const map of this._allTriggerMaps) {\n map.forEach(trigger => trigger.disable());\n map.clear();\n }\n\n // No scroll triggers left — detach resize listener\n this._detachResizeListener();\n }\n\n /**\n * Reset for testing - DO NOT USE IN PRODUCTION\n * Resets the singleton instance\n * @internal\n */\n static _reset(): void {\n if (TriggerManager._instance) {\n if (TriggerManager._instance._pageLoadTrigger) {\n TriggerManager._instance._pageLoadTrigger._reset();\n }\n TriggerManager._instance.killAll();\n TriggerManager._instance = undefined!;\n }\n }\n}\n\n// Self-register with the registry\nSDKRegistry.triggerManager = { getInstance: TriggerManager.getInstance };\n",
|
|
8
|
+
"/**\n * TargetResolver - Convert various target formats to AnimationTarget array\n *\n * Supports both DOM elements and plain JS objects for universal animation.\n */\n\nimport type { AnimationTarget, ObjectTarget, TargetInput } from '../types';\n\n/**\n * Execute callback when DOM is ready\n * If DOM is already ready, executes immediately\n */\nexport function whenDOMReady(callback: () => void): void {\n if (typeof document === 'undefined') {\n callback();\n return;\n }\n \n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', callback, { once: true });\n } else {\n callback();\n }\n}\n\n/**\n * Check if value is a plain JS object (not DOM, not array-like)\n * Used to detect objects like { x: 0, y: 0 } or mesh.position\n */\nfunction isPlainObject(value: unknown): value is ObjectTarget {\n if (value === null || typeof value !== 'object') return false;\n if (value instanceof Element) return false;\n if ('nodeType' in value) return false; // DOM node\n if ('style' in value) return false; // DOM element or mock element\n\n // Check for ThreeJS-style objects (Vector3, Euler, etc.)\n // These have 'length' but also have named numeric properties like x, y, z\n const obj = value as Record<string, unknown>;\n if ('x' in obj && typeof obj.x === 'number') {\n return true; // ThreeJS Vector3, Euler, or similar\n }\n\n // Not array-like (NodeList, Array, HTMLCollection)\n if ('length' in value) return false;\n\n return true;\n}\n\n/**\n * Check if a resolved target is a DOM Element\n * Used for caching _isElement flag in PropTween\n * Also recognizes mock elements (objects with 'style' property) in test environments\n */\nexport function isElement(target: AnimationTarget): target is Element {\n return target instanceof Element ||\n (typeof target === 'object' && target !== null &&\n ('nodeType' in target || 'style' in target));\n}\n\n/**\n * Resolve animation targets to an array of AnimationTargets\n * @param targets - CSS selector, string[], Element, NodeList, Element[], plain object, or object array\n * @returns Array of resolved AnimationTargets (Element or ObjectTarget)\n */\nexport function resolveTargets(targets: TargetInput): AnimationTarget[] {\n // Plain object target (e.g., { x: 0, y: 0 } or mesh.position)\n if (isPlainObject(targets)) {\n return [targets];\n }\n\n // String selector\n if (typeof targets === 'string') {\n if (typeof document !== 'undefined') {\n try {\n return Array.from(document.querySelectorAll(targets));\n } catch (e) {\n console.warn(`[Motion] Invalid selector \"${targets}\":`, e);\n return [];\n }\n }\n return [];\n }\n\n // Single Element\n if (targets instanceof Element) {\n return [targets];\n }\n\n // Check if it's a mock element (has a style property) - for test environments\n if (targets && typeof targets === 'object' && 'style' in targets && !Array.isArray(targets)) {\n return [targets as unknown as Element];\n }\n\n // NodeList\n if (targets instanceof NodeList) {\n return Array.from(targets) as Element[];\n }\n\n // Array - could be string[], Element[] or ObjectTarget[]\n if (Array.isArray(targets)) {\n if (targets.length === 0) return [];\n\n // Array of string selectors - resolve each and flatten\n if (typeof targets[0] === 'string') {\n if (typeof document === 'undefined') return [];\n const elements: Element[] = [];\n for (const selector of targets as string[]) {\n try {\n elements.push(...Array.from(document.querySelectorAll(selector)));\n } catch (e) {\n console.warn(`[Motion] Invalid selector \"${selector}\":`, e);\n }\n }\n return elements;\n }\n\n // Check first element to determine type\n if (isPlainObject(targets[0])) {\n return targets as ObjectTarget[];\n }\n return targets as Element[];\n }\n\n // Handle array-like objects (HTMLCollection)\n if (targets && typeof targets === 'object' && 'length' in targets) {\n return Array.from(targets as ArrayLike<Element>);\n }\n\n return [];\n}\n",
|
|
9
|
+
"/**\n * BaseTrigger - Abstract base class for all standard trigger types\n *\n * Extracts the common enable/disable lifecycle pattern shared by\n * ScrollTrigger, EventTrigger, MouseMoveTrigger, GestureTrigger, and CursorTrigger.\n *\n * Provides:\n * - enable/disable guards with _enabled flag\n * - DOM-ready gating via whenDOMReady\n * - Shared helper methods for selector resolution and listener cleanup\n *\n * NOT used by PageLoadTrigger (which has a different lifecycle pattern).\n */\n\nimport type { Timeline } from '../core/Timeline';\nimport { whenDOMReady } from '../utils/TargetResolver';\n\nexport abstract class BaseTrigger<TConfig> {\n protected _timeline: Timeline;\n protected _config: TConfig;\n protected _enabled: boolean = false;\n\n constructor(timeline: Timeline, config: TConfig) {\n this._timeline = timeline;\n this._config = config;\n }\n\n /**\n * Enable the trigger. Guards against double-enable and waits for DOM ready.\n * Calls _onEnable() once the DOM is ready (or immediately if already ready).\n */\n public enable(): void {\n if (this._enabled) return;\n this._enabled = true;\n\n whenDOMReady(() => {\n // May have been disabled while waiting for DOM to be ready\n if (!this._enabled) return;\n this._onEnable();\n });\n }\n\n /**\n * Disable the trigger. Guards against double-disable.\n * Calls _onDisable() immediately (no DOM-ready gating needed for teardown).\n */\n public disable(): void {\n if (!this._enabled) return;\n this._enabled = false;\n this._onDisable();\n }\n\n /**\n * Subclass: set up event listeners, resolve targets, start ticker loops, etc.\n * Called once per enable cycle after DOM is ready.\n */\n protected abstract _onEnable(): void;\n\n /**\n * Subclass: remove event listeners, clear state, stop ticker loops, etc.\n * Called once per disable cycle.\n */\n protected abstract _onDisable(): void;\n\n /**\n * Resolve a single element from a string selector or Element reference.\n * Returns the fallback (defaults to null) if selector is undefined, not found, or invalid.\n *\n * Note: non-string values are treated as direct element references without an instanceof\n * check, so this works with both real DOM elements and mock objects in tests.\n */\n protected _resolveElement(\n selector: string | Element | undefined,\n fallback?: EventTarget | null\n ): HTMLElement | EventTarget | null {\n if (selector === undefined || selector === null) {\n return fallback ?? null;\n }\n // Non-string: treat as direct element reference (works with mock elements in tests)\n if (typeof selector !== 'string') {\n return selector as HTMLElement;\n }\n // String selector: query the DOM\n if (typeof document !== 'undefined') {\n try {\n const el = document.querySelector(selector);\n if (el !== null) return el as unknown as HTMLElement;\n } catch (e) {\n console.warn(`[Motion] Invalid selector \"${selector}\":`, e);\n }\n }\n return fallback ?? null;\n }\n\n /**\n * Resolve multiple elements from a string selector or single Element reference.\n * Returns an empty array if selector is undefined, not found, or invalid.\n *\n * Note: non-string values are treated as direct element references without an instanceof\n * check, so this works with both real DOM elements and mock objects in tests.\n */\n protected _resolveElements(\n selector: string | Element | undefined\n ): Element[] {\n if (selector === undefined || selector === null) return [];\n // Non-string: treat as direct element reference (works with mock elements in tests)\n if (typeof selector !== 'string') {\n return [selector as Element];\n }\n // String selector: query the DOM\n if (typeof document !== 'undefined') {\n try {\n const elements = document.querySelectorAll(selector);\n return Array.from(elements);\n } catch (e) {\n console.warn(`[Motion] Invalid selector \"${selector}\":`, e);\n return [];\n }\n }\n return [];\n }\n\n /**\n * Remove all event listeners tracked in a nested listener map and clear the map.\n * Accepts Map<EventTarget, Map<eventName, listener>>.\n */\n protected _cleanupListenerMap(\n listeners: Map<EventTarget, Map<string, EventListener>>\n ): void {\n listeners.forEach((eventMap, target) => {\n eventMap.forEach((listener, eventName) => {\n target.removeEventListener(eventName, listener);\n });\n });\n listeners.clear();\n }\n}\n",
|
|
10
|
+
"/**\n * Returns the element's bounding rect as if no CSS transform were applied.\n * Temporarily strips the inline transform, measures, then restores it.\n * This gives us the \"layout\" position in the document flow.\n *\n * Used by ScrollTrigger and MarkerManager to compute trigger positions and\n * marker placements relative to the element's original layout position,\n * regardless of any animation transforms currently applied.\n */\nexport function getLayoutRect(element: Element): DOMRect {\n const el = element as HTMLElement;\n const savedTransform = el.style.transform;\n const savedTransition = el.style.transition;\n\n // Disable transitions to prevent visual flicker during measurement\n el.style.transition = 'none';\n el.style.transform = 'none';\n\n const rect = el.getBoundingClientRect();\n\n // Restore\n el.style.transform = savedTransform;\n el.style.transition = savedTransition;\n\n // Force reflow to apply restored styles synchronously before next paint\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n el.offsetHeight;\n\n return rect;\n}\n",
|
|
11
|
+
"/**\n * EventTrigger\n *\n * Triggers timelines based on DOM events:\n * - hover: plays on mouseenter, reverses on mouseleave\n * - click: plays on click with optional toggle behavior (reverse/restart on 2nd click)\n */\n\nimport type { Timeline } from '../core/Timeline';\nimport { executeTimelineAction } from '../utils/executeTimelineAction';\nimport type { TimelineAction } from '../utils/executeTimelineAction';\nimport { TriggerManager } from './TriggerManager';\nimport { BaseTrigger } from './BaseTrigger';\nimport { getLayoutRect } from '../utils/getLayoutRect';\n\n/** Internal config for EventTrigger */\nexport interface EventTriggerConfig {\n type: 'hover' | 'click';\n target?: string | Element;\n // Click-specific options\n secondTarget?: string | Element; // Element for 2nd click action\n toggle?: 'reverse' | 'restart' | 'play'; // 2nd click behavior\n preventDefault?: boolean;\n // Hover-specific options\n onLeave?: 'reverse' | 'pause' | 'stop' | 'restart' | 'none'; // Mouse leave behavior\n leaveDelay?: number; // Delay before leave action (seconds)\n}\n\nexport class EventTrigger extends BaseTrigger<EventTriggerConfig> {\n private _targets: Element[] = [];\n private _secondTargets: Element[] = []; // Targets for 2nd click\n private _listeners: Map<EventTarget, Map<string, EventListener>> = new Map();\n private _isForward: boolean = true; // Toggle state for click\n\n // Hover frozen-rect state — bounds are snapshotted at rest so that\n // translateY animations don't cause infinite mouseenter/mouseleave jitter.\n private _frozenRects: Map<Element, DOMRect> = new Map();\n private _wasHovering: boolean = false;\n private _hoverLeaveTimeout: number | null = null;\n private _boundMouseMoveHandler: ((e: MouseEvent) => void) | null = null;\n\n constructor(timeline: Timeline, config: EventTriggerConfig) {\n super(timeline, config);\n }\n\n /**\n * Set up event listeners once DOM is ready.\n */\n protected _onEnable(): void {\n // Resolve target elements\n this._resolveTargets();\n\n // Add event listeners based on trigger type\n if (this._config.type === 'hover') {\n this._addHoverListeners();\n } else if (this._config.type === 'click') {\n this._addClickListeners();\n }\n }\n\n /**\n * Remove all event listeners and cancel pending timers.\n */\n protected _onDisable(): void {\n // Remove global mousemove handler used by hover frozen-rect detection\n if (this._boundMouseMoveHandler) {\n window.removeEventListener('mousemove', this._boundMouseMoveHandler);\n this._boundMouseMoveHandler = null;\n }\n\n // Clear pending hover leave timeout\n if (this._hoverLeaveTimeout !== null) {\n clearTimeout(this._hoverLeaveTimeout);\n this._hoverLeaveTimeout = null;\n }\n\n // Clear hover state\n this._frozenRects.clear();\n this._wasHovering = false;\n\n // Remove all click listeners\n this._cleanupListenerMap(this._listeners);\n }\n\n /**\n * Re-snapshot frozen rects after layout changes (e.g. window resize).\n * Called by TriggerManager.refreshScrollTriggers() on resize.\n */\n refresh(): void {\n if (this._config.type !== 'hover') return;\n for (const element of this._targets) {\n this._frozenRects.set(element, getLayoutRect(element));\n }\n }\n\n private _resolveTargets(): void {\n // Resolve primary targets\n this._targets = this._resolveElements(this._config.target);\n\n // Resolve secondary targets (for 2nd click) - defaults to primary targets\n if (this._config.secondTarget) {\n this._secondTargets = this._resolveElements(this._config.secondTarget);\n } else {\n this._secondTargets = this._targets;\n }\n }\n\n private _addHoverListeners(): void {\n const onLeave = this._config.onLeave || 'reverse';\n const leaveDelay = this._config.leaveDelay || 0;\n\n // Snapshot element bounds at their rest (layout) position using getLayoutRect.\n // This strips any inline transform currently applied by an animation, measures\n // the element as if no transform exists, then restores the transform.\n // Using frozen rects prevents the jitter loop that occurs when a translateY\n // animation moves the element away from the cursor: with live bounds the browser\n // would fire mouseleave → reverse → element returns → mouseenter → play → repeat.\n for (const element of this._targets) {\n this._frozenRects.set(element, getLayoutRect(element));\n }\n\n // A single window mousemove listener replaces per-element mouseenter/mouseleave.\n // Hit-testing against frozen rects keeps detection stable during animation.\n this._boundMouseMoveHandler = (e: MouseEvent) => {\n const cx = e.clientX;\n const cy = e.clientY;\n\n // Check if cursor is inside any frozen target rect\n let isOverTarget = false;\n for (const rect of this._frozenRects.values()) {\n if (cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom) {\n isOverTarget = true;\n break;\n }\n }\n\n if (isOverTarget && !this._wasHovering) {\n // Cursor entered — cancel any pending leave and play timeline\n this._wasHovering = true;\n if (this._hoverLeaveTimeout !== null) {\n clearTimeout(this._hoverLeaveTimeout);\n this._hoverLeaveTimeout = null;\n }\n this._timeline.play();\n } else if (!isOverTarget && this._wasHovering) {\n // Cursor left — execute leave action (with optional delay)\n this._wasHovering = false;\n if (onLeave === 'none') return;\n\n const executeLeaveAction = () => {\n this._handleLeaveAction(onLeave);\n this._hoverLeaveTimeout = null;\n };\n\n if (leaveDelay > 0) {\n this._hoverLeaveTimeout = window.setTimeout(executeLeaveAction, leaveDelay * 1000);\n } else {\n executeLeaveAction();\n }\n }\n };\n\n window.addEventListener('mousemove', this._boundMouseMoveHandler);\n }\n\n private _handleLeaveAction(action: 'reverse' | 'pause' | 'stop' | 'restart' | 'none'): void {\n // Map EventTrigger's 'stop' to the shared utility's 'reset' (same behavior: pause + progress(0))\n const mapped: TimelineAction = action === 'stop' ? 'reset' : action;\n executeTimelineAction(mapped, this._timeline);\n }\n\n private _addClickListeners(): void {\n const toggle = this._config.toggle || 'play';\n const preventDefault = this._config.preventDefault || false;\n const hasSeparateSecondTarget = this._config.secondTarget && this._config.secondTarget !== this._config.target;\n\n // Add listeners to primary targets (1st click / play)\n this._targets.forEach(element => {\n const elementListeners = this._listeners.get(element) || new Map<string, EventListener>();\n\n const clickListener = (e: Event) => {\n if (preventDefault) {\n e.preventDefault();\n }\n\n // If same element handles both clicks, use toggle state\n if (!hasSeparateSecondTarget) {\n if (this._isForward) {\n // First click - play\n this._timeline.restart();\n if (toggle !== 'play') {\n this._isForward = false;\n }\n } else {\n // Second click - reverse/restart/play based on toggle setting\n this._handleSecondClick(toggle);\n this._isForward = true;\n }\n } else {\n // Separate targets: primary always plays\n this._timeline.restart();\n }\n };\n\n element.addEventListener('click', clickListener);\n elementListeners.set('click', clickListener);\n this._listeners.set(element, elementListeners);\n });\n\n // Add listeners to secondary targets (2nd click) if different from primary\n if (hasSeparateSecondTarget) {\n this._secondTargets.forEach(element => {\n // Skip if already has listeners (overlapping selectors)\n if (this._listeners.has(element)) return;\n\n const elementListeners = new Map<string, EventListener>();\n\n const clickListener = (e: Event) => {\n if (preventDefault) {\n e.preventDefault();\n }\n this._handleSecondClick(toggle);\n };\n\n element.addEventListener('click', clickListener);\n elementListeners.set('click', clickListener);\n this._listeners.set(element, elementListeners);\n });\n }\n }\n\n private _handleSecondClick(toggle: 'reverse' | 'restart' | 'play'): void {\n executeTimelineAction(toggle, this._timeline);\n }\n}\n\n// Register with TriggerManager\nTriggerManager.registerTrigger('event', EventTrigger);\n",
|
|
12
|
+
"/**\n * TransformCache - Optimized transform value storage\n *\n * Uses Float32Array for fast transform value storage per element.\n * Separate array for units (string per property).\n * Dirty flag bitmask to skip unchanged properties.\n * Cached transform string to avoid rebuilding.\n */\n\n// Transform property indices in Float32Array\n// PERSPECTIVE must be first for correct CSS transform order\nconst enum TransformIndex {\n PERSPECTIVE = 0,\n X = 1,\n Y = 2,\n Z = 3,\n ROTATION = 4,\n ROTATION_X = 5,\n ROTATION_Y = 6,\n ROTATION_Z = 7,\n SCALE = 8,\n SCALE_X = 9,\n SCALE_Y = 10,\n SCALE_Z = 11,\n SKEW_X = 12,\n SKEW_Y = 13,\n LENGTH = 14\n}\n\n// Map property names to indices\nconst PROP_TO_INDEX: Record<string, TransformIndex> = {\n perspective: TransformIndex.PERSPECTIVE,\n x: TransformIndex.X,\n y: TransformIndex.Y,\n z: TransformIndex.Z,\n rotate: TransformIndex.ROTATION,\n rotateX: TransformIndex.ROTATION_X,\n rotateY: TransformIndex.ROTATION_Y,\n rotateZ: TransformIndex.ROTATION_Z,\n scale: TransformIndex.SCALE,\n scaleX: TransformIndex.SCALE_X,\n scaleY: TransformIndex.SCALE_Y,\n scaleZ: TransformIndex.SCALE_Z,\n skewX: TransformIndex.SKEW_X,\n skewY: TransformIndex.SKEW_Y\n};\n\n// Default units per property index\nconst DEFAULT_UNITS: string[] = [\n 'px', // PERSPECTIVE\n 'px', // X\n 'px', // Y\n 'px', // Z\n 'deg', // ROTATION\n 'deg', // ROTATION_X\n 'deg', // ROTATION_Y\n 'deg', // ROTATION_Z\n '', // SCALE\n '', // SCALE_X\n '', // SCALE_Y\n '', // SCALE_Z\n 'deg', // SKEW_X\n 'deg' // SKEW_Y\n];\n\ninterface TransformData {\n values: Float32Array;\n units: string[];\n dirty: number; // Bitmask of dirty flags (cleared after build)\n used: number; // Bitmask of properties ever set (never cleared, tracks animated props)\n cachedString: string;\n pinDirty: boolean; // True when pin offset changed since last build\n}\n\n// WeakMap to store transform data per element\nconst transformCache = new WeakMap<Element, TransformData>();\n\n// Separate WeakMap for pin offsets (set by PinManager, composed into final transform string)\ninterface PinOffset {\n x: number;\n y: number;\n z: number;\n}\nconst pinOffsetCache = new WeakMap<Element, PinOffset>();\n\n// Pre-allocated array for building transform strings (avoids allocation per frame)\n// Safe to reuse since JS is single-threaded\nconst transformParts: string[] = [];\n\nconst MATRIX_EPSILON = 1e-6;\n\nfunction parseNumberWithUnit(raw: string): { value: number; unit: string } {\n const match = raw.trim().match(/([-+]?\\d*\\.?\\d+(?:e[-+]?\\d+)?)([a-z%]*)/i);\n if (!match) {\n return { value: 0, unit: '' };\n }\n return { value: parseFloat(match[1]), unit: match[2] || '' };\n}\n\nfunction toDegrees(value: number, unit: string): number {\n switch (unit) {\n case 'rad':\n return (value * 180) / Math.PI;\n case 'turn':\n return value * 360;\n case 'grad':\n return value * 0.9;\n default:\n return value;\n }\n}\n\nfunction parseAngle(raw: string): number {\n const parsed = parseNumberWithUnit(raw);\n return toDegrees(parsed.value, parsed.unit || 'deg');\n}\n\nfunction assignTransformValue(\n data: TransformData,\n property: string,\n value: number,\n unit?: string\n): void {\n const index = PROP_TO_INDEX[property];\n if (index === undefined) return;\n data.values[index] = value;\n const resolvedUnit = unit && unit.length > 0 ? unit : DEFAULT_UNITS[index] || '';\n data.units[index] = resolvedUnit;\n data.used |= (1 << index);\n}\n\nfunction parseTransformFunctions(transform: string, data: TransformData): void {\n const regex = /([a-zA-Z0-9]+)\\(([^)]+)\\)/g;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(transform)) !== null) {\n const fn = match[1];\n const args = match[2].match(/[-+]?\\d*\\.?\\d+(?:e[-+]?\\d+)?[a-z%]*/gi) || [];\n\n switch (fn) {\n case 'translate': {\n const x = args[0] ? parseNumberWithUnit(args[0]) : { value: 0, unit: '' };\n const y = args[1] ? parseNumberWithUnit(args[1]) : { value: 0, unit: '' };\n assignTransformValue(data, 'x', x.value, x.unit);\n assignTransformValue(data, 'y', y.value, y.unit);\n break;\n }\n case 'translate3d': {\n const x = args[0] ? parseNumberWithUnit(args[0]) : { value: 0, unit: '' };\n const y = args[1] ? parseNumberWithUnit(args[1]) : { value: 0, unit: '' };\n const z = args[2] ? parseNumberWithUnit(args[2]) : { value: 0, unit: '' };\n assignTransformValue(data, 'x', x.value, x.unit);\n assignTransformValue(data, 'y', y.value, y.unit);\n assignTransformValue(data, 'z', z.value, z.unit);\n break;\n }\n case 'translateX': {\n const x = args[0] ? parseNumberWithUnit(args[0]) : { value: 0, unit: '' };\n assignTransformValue(data, 'x', x.value, x.unit);\n break;\n }\n case 'translateY': {\n const y = args[0] ? parseNumberWithUnit(args[0]) : { value: 0, unit: '' };\n assignTransformValue(data, 'y', y.value, y.unit);\n break;\n }\n case 'translateZ': {\n const z = args[0] ? parseNumberWithUnit(args[0]) : { value: 0, unit: '' };\n assignTransformValue(data, 'z', z.value, z.unit);\n break;\n }\n case 'scale': {\n const x = args[0] ? parseFloat(args[0]) : 1;\n const y = args[1] ? parseFloat(args[1]) : x;\n assignTransformValue(data, 'scaleX', x);\n assignTransformValue(data, 'scaleY', y);\n break;\n }\n case 'scale3d': {\n const x = args[0] ? parseFloat(args[0]) : 1;\n const y = args[1] ? parseFloat(args[1]) : 1;\n const z = args[2] ? parseFloat(args[2]) : 1;\n assignTransformValue(data, 'scaleX', x);\n assignTransformValue(data, 'scaleY', y);\n assignTransformValue(data, 'scaleZ', z);\n break;\n }\n case 'scaleX': {\n const x = args[0] ? parseFloat(args[0]) : 1;\n assignTransformValue(data, 'scaleX', x);\n break;\n }\n case 'scaleY': {\n const y = args[0] ? parseFloat(args[0]) : 1;\n assignTransformValue(data, 'scaleY', y);\n break;\n }\n case 'scaleZ': {\n const z = args[0] ? parseFloat(args[0]) : 1;\n assignTransformValue(data, 'scaleZ', z);\n break;\n }\n case 'rotate':\n assignTransformValue(data, 'rotate', args[0] ? parseAngle(args[0]) : 0, 'deg');\n break;\n case 'rotateX':\n assignTransformValue(data, 'rotateX', args[0] ? parseAngle(args[0]) : 0, 'deg');\n break;\n case 'rotateY':\n assignTransformValue(data, 'rotateY', args[0] ? parseAngle(args[0]) : 0, 'deg');\n break;\n case 'rotateZ':\n assignTransformValue(data, 'rotateZ', args[0] ? parseAngle(args[0]) : 0, 'deg');\n break;\n case 'skew': {\n const x = args[0] ? parseAngle(args[0]) : 0;\n const y = args[1] ? parseAngle(args[1]) : 0;\n assignTransformValue(data, 'skewX', x, 'deg');\n assignTransformValue(data, 'skewY', y, 'deg');\n break;\n }\n case 'skewX':\n assignTransformValue(data, 'skewX', args[0] ? parseAngle(args[0]) : 0, 'deg');\n break;\n case 'skewY':\n assignTransformValue(data, 'skewY', args[0] ? parseAngle(args[0]) : 0, 'deg');\n break;\n case 'perspective': {\n const perspective = args[0] ? parseNumberWithUnit(args[0]) : { value: 0, unit: '' };\n assignTransformValue(data, 'perspective', perspective.value, perspective.unit);\n break;\n }\n default:\n break;\n }\n }\n}\n\nfunction decomposeMatrix(\n m11: number,\n m12: number,\n m13: number,\n m21: number,\n m22: number,\n m23: number,\n m31: number,\n m32: number,\n m33: number,\n m41: number,\n m42: number,\n m43: number,\n is2D: boolean,\n data: TransformData\n): void {\n if (is2D) {\n // 2D decomposition: extract rotation + skew from top-left 2x2 (m11,m12,m21,m22)\n const scaleX = Math.hypot(m11, m12);\n const determinant = m11 * m22 - m12 * m21;\n const scaleY = scaleX !== 0 ? determinant / scaleX : 0;\n\n const rotation = scaleX !== 0 ? Math.atan2(m12, m11) : 0;\n const skewX = scaleX !== 0 ? Math.atan2(m11 * m21 + m12 * m22, scaleX * scaleX) : 0;\n\n assignTransformValue(data, 'x', m41, 'px');\n assignTransformValue(data, 'y', m42, 'px');\n assignTransformValue(data, 'scaleX', scaleX || 1);\n assignTransformValue(data, 'scaleY', scaleY || 1);\n assignTransformValue(data, 'rotate', toDegrees(rotation, 'rad'), 'deg');\n if (Math.abs(skewX) > MATRIX_EPSILON) {\n assignTransformValue(data, 'skewX', toDegrees(skewX, 'rad'), 'deg');\n }\n return;\n }\n\n // 3D decomposition: extract Euler angles from rotation matrix\n const scaleX = Math.hypot(m11, m12, m13) || 1;\n const scaleY = Math.hypot(m21, m22, m23) || 1;\n const scaleZ = Math.hypot(m31, m32, m33) || 1;\n\n const rm11 = m11 / scaleX;\n const rm12 = m12 / scaleX;\n const rm13 = m13 / scaleX;\n const rm21 = m21 / scaleY;\n const rm22 = m22 / scaleY;\n const rm23 = m23 / scaleY;\n const rm31 = m31 / scaleZ;\n const rm32 = m32 / scaleZ;\n const rm33 = m33 / scaleZ;\n\n const sy = Math.sqrt(rm11 * rm11 + rm21 * rm21);\n const singular = sy < MATRIX_EPSILON;\n\n let rotateX = 0;\n let rotateY = 0;\n let rotateZ = 0;\n\n if (!singular) {\n rotateX = Math.atan2(rm32, rm33);\n rotateY = Math.atan2(-rm31, sy);\n rotateZ = Math.atan2(rm21, rm11);\n } else {\n rotateX = Math.atan2(-rm23, rm22);\n rotateY = Math.atan2(-rm31, sy);\n rotateZ = 0;\n }\n\n assignTransformValue(data, 'x', m41, 'px');\n assignTransformValue(data, 'y', m42, 'px');\n assignTransformValue(data, 'z', m43, 'px');\n assignTransformValue(data, 'scaleX', scaleX);\n assignTransformValue(data, 'scaleY', scaleY);\n assignTransformValue(data, 'scaleZ', scaleZ);\n assignTransformValue(data, 'rotateX', toDegrees(rotateX, 'rad'), 'deg');\n assignTransformValue(data, 'rotateY', toDegrees(rotateY, 'rad'), 'deg');\n assignTransformValue(data, 'rotateZ', toDegrees(rotateZ, 'rad'), 'deg');\n}\n\nfunction parseComputedTransform(transform: string, data: TransformData): void {\n if (!transform || transform === 'none') return;\n\n if (typeof DOMMatrixReadOnly !== 'undefined') {\n const matrix = new DOMMatrixReadOnly(transform);\n decomposeMatrix(\n matrix.m11, matrix.m12, matrix.m13,\n matrix.m21, matrix.m22, matrix.m23,\n matrix.m31, matrix.m32, matrix.m33,\n matrix.m41, matrix.m42, matrix.m43,\n matrix.is2D, data\n );\n return;\n }\n\n const values = transform.match(/[-+]?\\d*\\.?\\d+(?:e[-+]?\\d+)?/gi) || [];\n\n if (transform.startsWith('matrix3d') && values.length >= 16) {\n const nums = values.map(Number);\n decomposeMatrix(\n nums[0], nums[1], nums[2],\n nums[4], nums[5], nums[6],\n nums[8], nums[9], nums[10],\n nums[12], nums[13], nums[14],\n false, data\n );\n return;\n }\n\n if (transform.startsWith('matrix') && values.length >= 6) {\n const nums = values.map(Number);\n decomposeMatrix(\n nums[0], nums[1], 0,\n nums[2], nums[3], 0,\n 0, 0, 1,\n nums[4], nums[5], 0,\n true, data\n );\n }\n}\n\nfunction hydrateTransformData(element: Element, data: TransformData): void {\n if (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) return;\n\n const htmlEl = element as HTMLElement;\n const inlineTransform = htmlEl.style.transform;\n\n if (inlineTransform && inlineTransform !== 'none') {\n parseTransformFunctions(inlineTransform, data);\n data.cachedString = inlineTransform;\n return;\n }\n\n try {\n if (typeof window !== 'undefined' && window.getComputedStyle) {\n const computed = window.getComputedStyle(element);\n const computedTransform = computed.transform || computed.getPropertyValue('transform');\n if (computedTransform && computedTransform !== 'none') {\n parseComputedTransform(computedTransform, data);\n data.cachedString = computedTransform;\n }\n }\n } catch {\n // Ignore computed style failures (e.g., test environment)\n }\n}\n\n/**\n * Get or create transform data for an element\n */\nexport function getTransformData(element: Element): TransformData {\n let data = transformCache.get(element);\n\n if (!data) {\n // Initialize with default values\n const values = new Float32Array(TransformIndex.LENGTH);\n values[TransformIndex.SCALE] = 1;\n values[TransformIndex.SCALE_X] = 1;\n values[TransformIndex.SCALE_Y] = 1;\n values[TransformIndex.SCALE_Z] = 1;\n\n data = {\n values,\n units: [...DEFAULT_UNITS],\n dirty: 0,\n used: 0,\n cachedString: '',\n pinDirty: false\n };\n\n hydrateTransformData(element, data);\n\n transformCache.set(element, data);\n }\n\n return data;\n}\n\n/**\n * Set a transform value and unit, mark as dirty\n */\nexport function setTransformValue(\n element: Element,\n property: string,\n value: number,\n unit?: string\n): void {\n const data = getTransformData(element);\n const index = PROP_TO_INDEX[property];\n\n if (index !== undefined) {\n data.values[index] = value;\n if (unit !== undefined) {\n data.units[index] = unit;\n }\n data.used |= (1 << index);\n\n if (property === 'scale') {\n data.values[TransformIndex.SCALE_X] = value;\n data.values[TransformIndex.SCALE_Y] = value;\n data.dirty |= (1 << TransformIndex.SCALE_X) | (1 << TransformIndex.SCALE_Y);\n } else if (property === 'scaleX' || property === 'scaleY') {\n data.values[TransformIndex.SCALE] = 1;\n data.dirty |= (1 << TransformIndex.SCALE);\n }\n\n if (property === 'rotate') {\n data.values[TransformIndex.ROTATION_Z] = 0;\n data.dirty |= (1 << TransformIndex.ROTATION_Z);\n } else if (property === 'rotateZ') {\n data.values[TransformIndex.ROTATION] = 0;\n data.dirty |= (1 << TransformIndex.ROTATION);\n }\n data.dirty |= (1 << index); // Set dirty bit\n }\n}\n\n/**\n * Get a transform value\n */\nexport function getTransformValue(element: Element, property: string): number {\n const data = getTransformData(element);\n const index = PROP_TO_INDEX[property];\n if (index === undefined) {\n return property.startsWith('scale') ? 1 : 0;\n }\n\n if (property === 'scale') {\n if (data.values[index] !== 1) {\n return data.values[index];\n }\n const scaleX = data.values[TransformIndex.SCALE_X];\n const scaleY = data.values[TransformIndex.SCALE_Y];\n if (Math.abs(scaleX - scaleY) < MATRIX_EPSILON && scaleX !== 1) {\n return scaleX;\n }\n return data.values[index];\n }\n\n if (property === 'rotate') {\n if (data.values[index] !== 0) {\n return data.values[index];\n }\n const rotateZ = data.values[TransformIndex.ROTATION_Z];\n if (\n rotateZ !== 0 &&\n data.values[TransformIndex.ROTATION_X] === 0 &&\n data.values[TransformIndex.ROTATION_Y] === 0\n ) {\n return rotateZ;\n }\n return data.values[index];\n }\n\n return data.values[index];\n}\n\n/**\n * Get a transform unit\n */\nexport function getTransformUnit(element: Element, property: string): string {\n const data = getTransformData(element);\n const index = PROP_TO_INDEX[property];\n if (index === undefined) {\n return '';\n }\n return data.units[index] || DEFAULT_UNITS[index] || '';\n}\n\n/**\n * Format a single length value for a CSS translate function.\n * Emits bare \"0\" (no unit) for zero values, which is valid CSS and matches the\n * format historically written by PinManager (e.g. translate3d(0, 400px, 0)).\n */\nfunction formatPinLength(v: number): string {\n return v === 0 ? '0' : `${Math.round(v)}px`;\n}\n\n/**\n * Build the translate3d string for a pin offset.\n * Zero components are emitted without a unit (`0`) so the output matches the\n * format that tests and the existing PinManager code previously produced.\n */\nfunction buildPinTranslate(x: number, y: number, z: number): string {\n return `translate3d(${formatPinLength(x)}, ${formatPinLength(y)}, ${formatPinLength(z)})`;\n}\n\n/**\n * Register a pin offset for an element.\n * The pin offset is prepended to the animation transform in buildTransformString().\n * Call this instead of writing directly to element.style.transform from PinManager\n * so that both pin position and animation values are composed into a single write.\n */\nexport function setPinOffset(element: Element, x: number, y: number, z: number): void {\n pinOffsetCache.set(element, { x, y, z });\n // Mark the cached transform string as stale so the next build includes the new offset\n const data = transformCache.get(element);\n if (data) {\n data.pinDirty = true;\n }\n}\n\n/**\n * Remove the pin offset for an element (call when element exits pin zone).\n * Marks the cached transform string as stale so the next build omits the offset.\n */\nexport function clearPinOffset(element: Element): void {\n const hadOffset = pinOffsetCache.has(element);\n pinOffsetCache.delete(element);\n if (hadOffset) {\n const data = transformCache.get(element);\n if (data) {\n data.pinDirty = true;\n }\n }\n}\n\n/**\n * Build the CSS transform string from cached values\n */\nexport function buildTransformString(element: Element): string {\n const data = transformCache.get(element);\n const pinOffset = pinOffsetCache.get(element);\n\n // When there is no animation data at all, return just the pin offset (or nothing)\n if (!data) {\n if (pinOffset && (pinOffset.x !== 0 || pinOffset.y !== 0 || pinOffset.z !== 0)) {\n return buildPinTranslate(pinOffset.x, pinOffset.y, pinOffset.z);\n }\n return '';\n }\n\n // If neither animation nor pin offset changed, return the cached composite string\n if (data.dirty === 0 && !data.pinDirty && data.cachedString) {\n return data.cachedString;\n }\n\n const values = data.values;\n const units = data.units;\n\n // Clear pre-allocated array without allocation\n transformParts.length = 0;\n\n // Build transform string in optimal order\n // perspective -> translate -> rotate -> scale -> skew\n //\n // IMPORTANT: If a property was animated (tracked in `used` bitmask), we must\n // always emit it even at default value. Otherwise the inline style becomes \"\"\n // and stylesheet CSS snaps back.\n const used = data.used;\n\n // Perspective (must be first for 3D context)\n // NOTE: perspective(0px) causes extreme visual distortion (viewer at zero distance),\n // so we intentionally do NOT emit perspective at 0 even if it was animated.\n const perspectiveVal = values[TransformIndex.PERSPECTIVE];\n if (perspectiveVal !== 0) {\n const perspectiveUnit = units[TransformIndex.PERSPECTIVE];\n transformParts.push(`perspective(${perspectiveVal}${perspectiveUnit})`);\n }\n\n // Translate - handle mixed units by using separate translateX/Y/Z\n const xVal = values[TransformIndex.X];\n const yVal = values[TransformIndex.Y];\n const zVal = values[TransformIndex.Z];\n const xUnit = units[TransformIndex.X];\n const yUnit = units[TransformIndex.Y];\n const zUnit = units[TransformIndex.Z];\n const xUsed = used & (1 << TransformIndex.X);\n const yUsed = used & (1 << TransformIndex.Y);\n const zUsed = used & (1 << TransformIndex.Z);\n\n if (xVal !== 0 || yVal !== 0 || zVal !== 0 || xUsed || yUsed || zUsed) {\n // Check if all units match for potential translate3d optimization\n const allSameUnit = xUnit === yUnit && ((zVal === 0 && !zUsed) || yUnit === zUnit);\n\n if (zVal !== 0 || zUsed) {\n if (allSameUnit) {\n transformParts.push(`translate3d(${xVal}${xUnit}, ${yVal}${yUnit}, ${zVal}${zUnit})`);\n } else {\n if (xVal !== 0 || xUsed) transformParts.push(`translateX(${xVal}${xUnit})`);\n if (yVal !== 0 || yUsed) transformParts.push(`translateY(${yVal}${yUnit})`);\n transformParts.push(`translateZ(${zVal}${zUnit})`);\n }\n } else if (allSameUnit && (xVal !== 0 || xUsed) && (yVal !== 0 || yUsed)) {\n transformParts.push(`translate(${xVal}${xUnit}, ${yVal}${yUnit})`);\n } else {\n if (xVal !== 0 || xUsed) transformParts.push(`translateX(${xVal}${xUnit})`);\n if (yVal !== 0 || yUsed) transformParts.push(`translateY(${yVal}${yUnit})`);\n }\n }\n\n // Rotate\n if (values[TransformIndex.ROTATION] !== 0 || (used & (1 << TransformIndex.ROTATION))) {\n transformParts.push(`rotate(${values[TransformIndex.ROTATION]}deg)`);\n }\n if (values[TransformIndex.ROTATION_X] !== 0 || (used & (1 << TransformIndex.ROTATION_X))) {\n transformParts.push(`rotateX(${values[TransformIndex.ROTATION_X]}deg)`);\n }\n if (values[TransformIndex.ROTATION_Y] !== 0 || (used & (1 << TransformIndex.ROTATION_Y))) {\n transformParts.push(`rotateY(${values[TransformIndex.ROTATION_Y]}deg)`);\n }\n if (values[TransformIndex.ROTATION_Z] !== 0 || (used & (1 << TransformIndex.ROTATION_Z))) {\n transformParts.push(`rotateZ(${values[TransformIndex.ROTATION_Z]}deg)`);\n }\n\n // Scale\n const scaleUsed = used & (1 << TransformIndex.SCALE);\n const scaleXUsed = used & (1 << TransformIndex.SCALE_X);\n const scaleYUsed = used & (1 << TransformIndex.SCALE_Y);\n if (values[TransformIndex.SCALE] !== 1 || scaleUsed) {\n transformParts.push(`scale(${values[TransformIndex.SCALE]})`);\n } else if (\n values[TransformIndex.SCALE_X] !== 1 ||\n values[TransformIndex.SCALE_Y] !== 1 ||\n scaleXUsed || scaleYUsed\n ) {\n transformParts.push(`scaleX(${values[TransformIndex.SCALE_X]})`);\n transformParts.push(`scaleY(${values[TransformIndex.SCALE_Y]})`);\n }\n if (values[TransformIndex.SCALE_Z] !== 1 || (used & (1 << TransformIndex.SCALE_Z))) {\n transformParts.push(`scaleZ(${values[TransformIndex.SCALE_Z]})`);\n }\n\n // Skew\n if (values[TransformIndex.SKEW_X] !== 0 || (used & (1 << TransformIndex.SKEW_X))) {\n transformParts.push(`skewX(${values[TransformIndex.SKEW_X]}deg)`);\n }\n if (values[TransformIndex.SKEW_Y] !== 0 || (used & (1 << TransformIndex.SKEW_Y))) {\n transformParts.push(`skewY(${values[TransformIndex.SKEW_Y]}deg)`);\n }\n\n // Compose animation parts with pin offset (pin offset is prepended so it acts as\n // the base-position shift; animation transforms apply on top of it)\n const animStr = transformParts.join(' ');\n if (pinOffset && (pinOffset.x !== 0 || pinOffset.y !== 0 || pinOffset.z !== 0)) {\n const pinStr = buildPinTranslate(pinOffset.x, pinOffset.y, pinOffset.z);\n data.cachedString = animStr ? `${pinStr} ${animStr}` : pinStr;\n } else {\n data.cachedString = animStr;\n }\n\n data.dirty = 0; // Clear animation dirty flags\n data.pinDirty = false; // Clear pin dirty flag\n\n return data.cachedString;\n}\n\n/**\n * Clear transform cache for an element\n * @param clearInlineStyle - If true, also clears element's inline transform to prevent hydration from reading stale values\n */\nexport function clearTransformCache(element: Element, clearInlineStyle: boolean = false): void {\n transformCache.delete(element);\n // Optionally clear inline transform so hydration doesn't read stale rotation/position\n if (clearInlineStyle && element instanceof HTMLElement) {\n element.style.transform = '';\n }\n}\n",
|
|
13
|
+
"/**\n * RenderBatch - Batched DOM write system\n *\n * Collects all property changes during a frame and flushes them\n * in a single batch at frame end. This eliminates layout thrashing\n * and reduces DOM writes from O(properties) to O(elements).\n *\n * Key optimizations:\n * - One transform string build per element (not per property)\n * - One DOM write per element for transforms\n * - Batched CSS property writes\n * - Zero allocations during animation (uses Sets/Maps)\n */\n\nimport { buildTransformString } from './TransformCache';\n\n// Elements with pending transform changes\nconst pendingTransforms: Set<Element> = new Set();\n\n// Elements with pending CSS property changes: Element -> Map<property, value>\nconst pendingStyles: Map<Element, Map<string, string>> = new Map();\n\n// Track if we have pending work\nlet hasPendingWork = false;\n\n/**\n * Queue a transform update for an element\n * Called after setTransformValue updates the cache\n */\nexport function queueTransform(element: Element): void {\n pendingTransforms.add(element);\n hasPendingWork = true;\n}\n\n/**\n * Queue a CSS property update for an element\n */\nexport function queueStyle(element: Element, property: string, value: string): void {\n let elementStyles = pendingStyles.get(element);\n if (!elementStyles) {\n elementStyles = new Map();\n pendingStyles.set(element, elementStyles);\n }\n elementStyles.set(property, value);\n hasPendingWork = true;\n}\n\n/**\n * Flush all pending DOM writes\n * Called once at end of each animation frame\n */\nexport function flush(): void {\n if (!hasPendingWork) return;\n\n // 1. Flush all transform updates (one DOM write per element)\n for (const element of pendingTransforms) {\n const htmlElement = element as HTMLElement;\n if (htmlElement.style) {\n // buildTransformString uses the dirty flag to optimize string building\n const transformString = buildTransformString(element);\n htmlElement.style.transform = transformString;\n }\n }\n pendingTransforms.clear();\n\n // 2. Flush all CSS property updates\n for (const [element, properties] of pendingStyles) {\n const htmlElement = element as HTMLElement;\n if (htmlElement.style) {\n for (const [property, value] of properties) {\n try {\n if (property.startsWith('--')) {\n // CSS variables use setProperty with raw name\n htmlElement.style.setProperty(property, value);\n } else {\n (htmlElement.style as CSSStyleDeclaration & Record<string, string>)[property] = value;\n }\n } catch {\n // Silently fail for invalid properties\n }\n }\n }\n }\n pendingStyles.clear();\n\n hasPendingWork = false;\n}\n",
|
|
14
|
+
"/**\n * Ticker Singleton - Single RAF loop for all animations\n *\n * Manages the requestAnimationFrame loop with:\n * - Lag smoothing (cap delta when tab backgrounded)\n * - Priority-based listener ordering\n * - Automatic start/stop based on listener count\n * - Batched DOM writes at end of frame via RenderBatch\n */\n\nimport { flush as flushRenderBatch } from '../render/RenderBatch';\n\n// Module-level inTick flag for fast access (avoids getInstance() calls in hot path)\nlet _inTick = false;\n\n/**\n * Fast check if we're inside a tick (exported for hot path performance)\n * Use this instead of Ticker.getInstance().isInTick() in render code\n */\nexport function isInTick(): boolean {\n return _inTick;\n}\n\n/**\n * Run a callback in \"tick mode\" — writes are batched, then flushed at the end.\n *\n * Use this when updating timeline progress from outside the RAF loop\n * (e.g. scroll event handlers) to avoid layout thrashing from immediate\n * per-property DOM writes. The callback's DOM mutations are queued via\n * RenderBatch and flushed in a single pass after the callback returns.\n */\nexport function runBatched(callback: () => void): void {\n if (_inTick) {\n // Already inside a tick — just run directly, flush will happen at frame end\n callback();\n return;\n }\n\n _inTick = true;\n try {\n callback();\n flushRenderBatch();\n } finally {\n _inTick = false;\n }\n}\n\ntype TickerListener = (deltaTime: number, elapsedTime: number) => void;\n\ninterface Listener {\n callback: TickerListener;\n priority: number;\n}\n\nexport class Ticker {\n private static instance: Ticker;\n\n private listeners: Listener[] = [];\n private _listenersDirty: boolean = true;\n private _listenersSnapshot: Listener[] = [];\n private _isRunning: boolean = false;\n private rafId: number | null = null;\n private lastTime: number = 0;\n private elapsedTime: number = 0;\n private lagThreshold: number = 500; // Max delta in ms (0.5 seconds)\n private maxDelta: number = 33; // Cap delta at ~30fps when lagging\n\n // FPS tracking\n private fpsFrames: number = 0;\n private fpsTime: number = 0;\n private currentFPS: number = 60;\n private lastFrameTime: number = 0;\n\n private constructor() {\n // Private constructor for singleton\n }\n\n /**\n * Get the singleton Ticker instance\n */\n static getInstance(): Ticker {\n if (!Ticker.instance) {\n Ticker.instance = new Ticker();\n }\n return Ticker.instance;\n }\n\n /**\n * Add a listener to the ticker\n * @param callback Function to call on each tick\n * @param priority Higher priority callbacks execute first (default: 0)\n */\n add(callback: TickerListener, priority: number = 0): void {\n // Check if callback already exists\n const existingIndex = this.listeners.findIndex(l => l.callback === callback);\n if (existingIndex !== -1) {\n // Update priority if already exists\n this.listeners[existingIndex].priority = priority;\n } else {\n this.listeners.push({ callback, priority });\n }\n\n // Sort by priority (higher priority first)\n this.listeners.sort((a, b) => b.priority - a.priority);\n this._listenersDirty = true;\n\n // Start RAF loop if this is the first listener\n if (!this._isRunning && this.listeners.length > 0) {\n this.start();\n }\n }\n\n /**\n * Remove a listener from the ticker\n * @param callback Function to remove\n */\n remove(callback: TickerListener): void {\n const index = this.listeners.findIndex(l => l.callback === callback);\n if (index !== -1) {\n this.listeners.splice(index, 1);\n this._listenersDirty = true;\n }\n\n // Stop RAF loop if no more listeners\n if (this.listeners.length === 0 && this._isRunning) {\n this.stop();\n }\n }\n\n /**\n * Start the RAF loop\n */\n private start(): void {\n if (this._isRunning) return;\n\n // SSR / Node guard: requestAnimationFrame is not available outside browsers\n if (typeof requestAnimationFrame === 'undefined') {\n this._isRunning = false;\n return;\n }\n\n this._isRunning = true;\n this.lastTime = performance.now();\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n /**\n * Stop the RAF loop\n */\n private stop(): void {\n if (!this._isRunning) return;\n\n this._isRunning = false;\n if (this.rafId !== null && typeof cancelAnimationFrame !== 'undefined') {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n /**\n * Pause the ticker (for debug purposes)\n */\n pause(): void {\n this.stop();\n }\n\n /**\n * Resume the ticker (for debug purposes)\n */\n resume(): void {\n if (this.listeners.length > 0) {\n this.start();\n }\n }\n\n /**\n * Main tick function called by RAF\n */\n private tick = (): void => {\n if (!this._isRunning) return;\n\n const currentTime = performance.now();\n let delta = currentTime - this.lastTime;\n\n // Apply lag smoothing - cap delta when tab was backgrounded\n if (delta > this.lagThreshold) {\n // Tab was likely backgrounded, use maxDelta instead\n delta = this.maxDelta;\n this.lastTime = currentTime - delta; // Adjust lastTime\n } else {\n this.lastTime = currentTime;\n }\n\n // Convert to seconds\n const deltaSeconds = delta / 1000;\n this.elapsedTime += deltaSeconds;\n this.lastFrameTime = delta;\n\n // Update FPS tracking\n this.updateFPS(delta);\n\n // Mark that we're inside a tick - renders will queue instead of write immediately\n _inTick = true;\n\n // Call all listeners in priority order\n // Use copy-on-write snapshot: only rebuild when listeners change, not every frame\n if (this._listenersDirty) {\n this._listenersSnapshot = [...this.listeners];\n this._listenersDirty = false;\n }\n\n for (const listener of this._listenersSnapshot) {\n try {\n listener.callback(deltaSeconds, this.elapsedTime);\n } catch (e) {\n console.error('[Motion] Uncaught error in animation callback:', e);\n }\n }\n\n // Flush all batched DOM writes at end of frame\n try {\n flushRenderBatch();\n } catch (e) {\n console.error('[Motion] Uncaught error in render batch flush:', e);\n }\n\n // Mark tick complete - subsequent renders will write immediately\n _inTick = false;\n\n // Schedule next tick (guard for SSR safety — tick should only run in browser, but be defensive)\n if (typeof requestAnimationFrame !== 'undefined') {\n this.rafId = requestAnimationFrame(this.tick);\n }\n };\n\n /**\n * Update FPS calculation\n */\n private updateFPS(delta: number): void {\n this.fpsFrames++;\n this.fpsTime += delta;\n\n // Update FPS every second\n if (this.fpsTime >= 1000) {\n this.currentFPS = Math.round((this.fpsFrames * 1000) / this.fpsTime);\n this.fpsFrames = 0;\n this.fpsTime = 0;\n }\n }\n\n /**\n * Get current FPS\n */\n getFPS(): number {\n return this.currentFPS;\n }\n\n /**\n * Get last frame delta time in ms\n */\n getLastDelta(): number {\n return this.lastFrameTime;\n }\n\n /**\n * Check if ticker is running\n */\n isRunning(): boolean {\n return this._isRunning;\n }\n\n /**\n * Check if we're currently inside a tick\n * Used by render system to decide: queue (in tick) or write immediately (outside tick)\n * Note: For hot path performance, use the exported isInTick() function instead\n */\n isInTick(): boolean {\n return _inTick;\n }\n\n /**\n * Get listener count\n */\n getListenerCount(): number {\n return this.listeners.length;\n }\n\n /**\n * Remove all listeners (for testing)\n */\n removeAll(): void {\n this.listeners = [];\n this.stop();\n }\n}\n",
|
|
15
|
+
"/**\n * PathParser - Parse and animate elements along SVG paths\n *\n * Handles:\n * - CSS selector to existing <path> element\n * - Direct Element reference\n * - Raw SVG path data string (starting with M/m)\n * - Position and angle calculation along path\n * - Alignment offset calculation\n */\n\n/**\n * Point on a path with position and angle\n */\nexport interface PathPoint {\n x: number;\n y: number;\n angle: number; // rotation in degrees\n}\n\n/**\n * Parsed path data ready for animation\n */\nexport interface ParsedPathData {\n pathData: string;\n length: number;\n}\n\n// Hidden SVG elements for path calculations (created lazily)\nlet hiddenSVG: SVGSVGElement | null = null;\nlet hiddenPath: SVGPathElement | null = null;\n\n// Cache for path lengths (path data string -> length)\n// Uses simple LRU eviction when cache exceeds max size\nconst PATH_CACHE_MAX_SIZE = 100;\nconst pathLengthCache = new Map<string, number>();\n\n/**\n * Get from cache with LRU update (move to end)\n */\nfunction cacheGet(key: string): number | undefined {\n const value = pathLengthCache.get(key);\n if (value !== undefined) {\n // Move to end (most recently used)\n pathLengthCache.delete(key);\n pathLengthCache.set(key, value);\n }\n return value;\n}\n\n/**\n * Set cache with LRU eviction\n */\nfunction cacheSet(key: string, value: number): void {\n // If key exists, delete first to update position\n if (pathLengthCache.has(key)) {\n pathLengthCache.delete(key);\n }\n // Evict oldest entries if at capacity\n while (pathLengthCache.size >= PATH_CACHE_MAX_SIZE) {\n const firstKey = pathLengthCache.keys().next().value;\n if (firstKey !== undefined) {\n pathLengthCache.delete(firstKey);\n } else {\n break;\n }\n }\n pathLengthCache.set(key, value);\n}\n\n/**\n * Check if a string is raw SVG path data (starts with M or m command)\n */\nexport function isPathData(value: string): boolean {\n return /^\\s*[Mm]/.test(value);\n}\n\n/**\n * Get or create the hidden SVG element for path calculations\n * \n * Note: This uses a single shared path element. This is safe because:\n * 1. JavaScript is single-threaded\n * 2. Each getPointAtProgress call sets path data immediately before use\n * 3. LUT building loops are synchronous (no await between iterations)\n */\nfunction getHiddenPath(): SVGPathElement {\n if (hiddenPath) return hiddenPath;\n\n // Check if we're in a browser environment\n if (typeof document === 'undefined') {\n throw new Error('PathParser requires a browser environment');\n }\n\n // Create hidden SVG container\n hiddenSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n hiddenSVG.style.cssText = 'position:absolute;width:0;height:0;overflow:hidden;visibility:hidden;';\n hiddenSVG.setAttribute('aria-hidden', 'true');\n\n // Create path element\n hiddenPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n hiddenSVG.appendChild(hiddenPath);\n\n // Append to document\n document.body.appendChild(hiddenSVG);\n\n return hiddenPath;\n}\n\n/**\n * Resolve path target to path data string\n * \n * @param target - CSS selector, Element, or raw path data\n * @returns Path data string or null if not found\n */\nexport function resolvePathData(target: string | Element): string | null {\n // Direct Element reference\n if (target instanceof Element) {\n return target.getAttribute('d');\n }\n\n // String - could be path data or selector\n if (typeof target === 'string') {\n // Check if it's raw path data\n if (isPathData(target)) {\n return target;\n }\n\n // Try as CSS selector\n if (typeof document !== 'undefined') {\n const el = document.querySelector(target);\n if (el) {\n return el.getAttribute('d');\n }\n }\n }\n\n return null;\n}\n\n/**\n * Get the total length of a path\n * Results are cached per path data string\n */\nexport function getPathLength(pathData: string): number {\n // Check cache first (with LRU update)\n const cached = cacheGet(pathData);\n if (cached !== undefined) {\n return cached;\n }\n\n try {\n const path = getHiddenPath();\n path.setAttribute('d', pathData);\n const length = path.getTotalLength();\n cacheSet(pathData, length);\n return length;\n } catch {\n return 0;\n }\n}\n\n/**\n * Clear the path length cache\n * Call when path data changes dynamically\n */\nexport function clearPathLengthCache(): void {\n pathLengthCache.clear();\n}\n\n/**\n * Get a point on the path at a given progress (0-1)\n * \n * @param pathData - SVG path data string\n * @param progress - Position on path (0-1)\n * @param calculateAngle - Whether to calculate rotation angle\n * @returns Point with x, y, and angle\n */\nexport function getPointAtProgress(\n pathData: string,\n progress: number,\n calculateAngle: boolean = false\n): PathPoint {\n try {\n const path = getHiddenPath();\n path.setAttribute('d', pathData);\n\n const length = path.getTotalLength();\n const clampedProgress = Math.max(0, Math.min(1, progress));\n const distance = length * clampedProgress;\n\n const point = path.getPointAtLength(distance);\n\n let angle = 0;\n if (calculateAngle) {\n angle = getAngleAtLength(path, distance, length);\n }\n\n return {\n x: point.x,\n y: point.y,\n angle,\n };\n } catch {\n return { x: 0, y: 0, angle: 0 };\n }\n}\n\n/**\n * Calculate the tangent angle at a point on the path\n * Uses finite difference method with two close points\n */\nfunction getAngleAtLength(\n path: SVGPathElement,\n distance: number,\n totalLength: number\n): number {\n const delta = Math.min(0.1, totalLength * 0.001); // Small offset, proportional to path length\n\n // Get two points close together\n const d1 = Math.max(0, distance - delta);\n const d2 = Math.min(totalLength, distance + delta);\n\n const p1 = path.getPointAtLength(d1);\n const p2 = path.getPointAtLength(d2);\n\n // Calculate angle from the difference\n const dx = p2.x - p1.x;\n const dy = p2.y - p1.y;\n\n // Convert to degrees\n return Math.atan2(dy, dx) * (180 / Math.PI);\n}\n\n/**\n * Calculate the offset for the animated element itself\n * This is used to position the element's center (or specified origin) on the path\n * \n * @param element - The element being animated (selector, Element, or undefined)\n * @param alignAt - Origin point as [x%, y%], default [50, 50] (center)\n * @returns Offset to subtract from path position\n */\nexport function calculateElementOffset(\n element: string | Element | undefined,\n alignAt: [number, number] = [50, 50]\n): { x: number; y: number } {\n if (!element) {\n return { x: 0, y: 0 };\n }\n\n let el: Element | null = null;\n\n if (element instanceof Element) {\n el = element;\n } else if (typeof element === 'string' && typeof document !== 'undefined') {\n el = document.querySelector(element);\n }\n\n if (!el || typeof el.getBoundingClientRect !== 'function') {\n return { x: 0, y: 0 };\n }\n\n const rect = el.getBoundingClientRect();\n\n // Calculate offset based on alignAt percentages relative to element size\n const offsetX = (rect.width * alignAt[0]) / 100;\n const offsetY = (rect.height * alignAt[1]) / 100;\n\n return { x: offsetX, y: offsetY };\n}\n\n/**\n * Calculate offset to position path relative to an align element\n * The path's first point (M command) will be positioned at the align element's center\n * \n * @param align - Element or selector to align the path to\n * @param pathData - SVG path data string\n * @param animatedElement - The element being animated (to calculate relative offset)\n * @returns Offset to add to path coordinates\n */\nexport function calculatePathAlignOffset(\n align: string | Element | undefined,\n pathData: string,\n animatedElement?: Element\n): { x: number; y: number } {\n if (!align) {\n return { x: 0, y: 0 };\n }\n\n // Get align element\n let alignEl: Element | null = null;\n if (align instanceof Element) {\n alignEl = align;\n } else if (typeof align === 'string' && typeof document !== 'undefined') {\n alignEl = document.querySelector(align);\n }\n\n if (!alignEl) {\n return { x: 0, y: 0 };\n }\n\n // Get the first point of the path (start position)\n const firstPoint = getPointAtProgress(pathData, 0, false);\n\n // If we have the animated element, calculate relative offset\n if (animatedElement && animatedElement instanceof HTMLElement) {\n // Get element's original position (without animation transforms)\n // Only clear transforms if element has them - avoids unnecessary reflows\n const currentTransform = animatedElement.style.transform;\n const hasTransform = currentTransform && currentTransform !== 'none' && currentTransform !== '';\n \n if (hasTransform) {\n animatedElement.style.transform = 'none';\n }\n \n const animatedRect = animatedElement.getBoundingClientRect();\n const alignRect = alignEl.getBoundingClientRect();\n \n if (hasTransform) {\n animatedElement.style.transform = currentTransform;\n }\n \n // Calculate offset from animated element's original position to align element's center\n const relativeX = (alignRect.left + alignRect.width / 2) - (animatedRect.left + animatedRect.width / 2);\n const relativeY = (alignRect.top + alignRect.height / 2) - (animatedRect.top + animatedRect.height / 2);\n \n // Subtract the path's first point so path starts at align element\n return {\n x: relativeX - firstPoint.x,\n y: relativeY - firstPoint.y,\n };\n }\n\n // Fallback: just offset by negative of path's first point\n // This makes the path start at the element's current position\n return {\n x: -firstPoint.x,\n y: -firstPoint.y,\n };\n}\n\n/**\n * Parse a path configuration and prepare it for animation\n */\nexport function parsePath(\n target: string | Element,\n align?: string | Element,\n alignAt: [number, number] = [50, 50]\n): ParsedPathData | null {\n const pathData = resolvePathData(target);\n\n if (!pathData) {\n return null;\n }\n\n const length = getPathLength(pathData);\n\n if (length === 0) {\n return null;\n }\n\n return {\n pathData,\n length,\n };\n}\n\n/**\n * Cleanup function - removes hidden SVG from DOM\n * Call when SDK is destroyed or for testing cleanup\n */\nexport function cleanupPathParser(): void {\n if (hiddenSVG && hiddenSVG.parentNode) {\n hiddenSVG.parentNode.removeChild(hiddenSVG);\n }\n hiddenSVG = null;\n hiddenPath = null;\n pathLengthCache.clear();\n}\n",
|
|
16
|
+
"/**\n * PropertyParser - Parse and normalize animation properties\n *\n * Handles:\n * - Transform shortcuts (x, y, rotate, scale, etc.)\n * - Relative values (+=, -=, *=, /=)\n * - Unit parsing and defaulting\n * - Color parsing (hex, rgb, hsl, named, CSS variables) - OPTIONAL\n * - Filter parsing (blur, brightness, contrast, etc.) - OPTIONAL\n * - DrawSVG parsing (SVG stroke animations) - OPTIONAL\n * \n * Optional parsers are loaded conditionally via SDKRegistry.\n * If a parser is not loaded, those property types will be skipped.\n */\n\nimport type { AnimationTarget } from '../types';\nimport { clearTransformCache, getTransformValue as getCachedTransformValue } from '../render/TransformCache';\nimport { SDKRegistry } from '../registries/SDKRegistry';\nimport type { ParsedFilterFunction } from './FilterParser';\nimport type { ParsedDrawSVG } from './DrawSVGParser';\nimport {\n resolvePathData,\n getPathLength as getMotionPathLength,\n calculateElementOffset,\n calculatePathAlignOffset,\n type ParsedPathData\n} from './PathParser';\nimport type { PathConfig } from '../types';\n\n/**\n * Valid property value types for animation\n */\nexport type PropertyValue = number | string;\n\n// Transform properties that map to CSS transforms\nconst TRANSFORM_PROPS = new Set([\n 'perspective',\n 'x', 'y', 'z',\n 'rotate', 'rotateX', 'rotateY', 'rotateZ',\n 'scale', 'scaleX', 'scaleY', 'scaleZ',\n 'skewX', 'skewY'\n]);\n\n// Properties that default to px units\nconst PX_PROPS = new Set([\n 'perspective',\n 'x', 'y', 'z',\n 'width', 'height',\n 'top', 'left', 'right', 'bottom',\n 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',\n 'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',\n 'borderRadius', 'fontSize', 'lineHeight'\n]);\n\n// Properties that default to deg units\nconst DEG_PROPS = new Set([\n 'rotate', 'rotateX', 'rotateY', 'rotateZ',\n 'skewX', 'skewY'\n]);\n\n// Properties that are unitless numbers\nconst UNITLESS_PROPS = new Set([\n 'opacity', 'scale', 'scaleX', 'scaleY', 'scaleZ', 'z-index', 'zIndex'\n]);\n\n// Properties that are colors\nconst COLOR_PROPS = new Set([\n 'color',\n 'backgroundColor',\n 'borderColor',\n 'borderTopColor',\n 'borderRightColor',\n 'borderBottomColor',\n 'borderLeftColor',\n 'outlineColor',\n 'textDecorationColor',\n 'caretColor',\n 'fill',\n 'stroke'\n]);\n\n// Named CSS colors for looksLikeColor detection (hoisted to avoid per-call allocation)\nconst NAMED_COLORS = new Set([\n 'red', 'green', 'blue', 'yellow', 'orange', 'purple', 'pink', 'cyan',\n 'magenta', 'white', 'black', 'gray', 'grey', 'transparent',\n 'crimson', 'gold', 'silver', 'navy', 'teal', 'maroon', 'olive',\n 'lime', 'aqua', 'fuchsia', 'coral', 'salmon', 'tomato', 'indigo'\n]);\n\n/**\n * Base fields shared by all parsed property types\n */\ninterface ParsedPropertyBase {\n property: string;\n isTransform: boolean;\n}\n\n/**\n * Scalar property (numbers with units)\n */\ninterface ParsedScalarProperty extends ParsedPropertyBase {\n type: 'scalar';\n startValue: number;\n endValue: number;\n unit: string;\n}\n\n/**\n * Color property (RGBA values)\n */\ninterface ParsedColorProperty extends ParsedPropertyBase {\n type: 'color';\n startColor: Float32Array;\n endColor: Float32Array;\n}\n\n/**\n * Filter property (CSS filters)\n */\ninterface ParsedFilterProperty extends ParsedPropertyBase {\n type: 'filter';\n startFilters: ParsedFilterFunction[];\n endFilters: ParsedFilterFunction[];\n}\n\n/**\n * DrawSVG property (SVG stroke animation)\n */\ninterface ParsedDrawSVGProperty extends ParsedPropertyBase {\n type: 'drawSVG';\n startDraw: ParsedDrawSVG; // Start state { start: 0-1, end: 0-1 }\n endDraw: ParsedDrawSVG; // End state { start: 0-1, end: 0-1 }\n length: number; // Cached path length\n}\n\n/**\n * Path property (motion along SVG path)\n */\ninterface ParsedPathProperty extends ParsedPropertyBase {\n type: 'path';\n pathData: string; // SVG path \"d\" attribute\n pathLength: number; // Cached total length\n startProgress: number; // 0-1\n endProgress: number; // 0-1\n rotate: boolean; // Auto-rotate along tangent\n alignOffset: { x: number; y: number }; // Element origin offset (alignAt)\n pathOffset: { x: number; y: number }; // Path position offset (align element)\n}\n\n/**\n * Discriminated union of all parsed property types\n */\nexport type ParsedProperty = ParsedScalarProperty | ParsedColorProperty | ParsedFilterProperty | ParsedDrawSVGProperty | ParsedPathProperty;\n\n/**\n * Check if a property is a transform property\n */\nexport function isTransformProp(prop: string): boolean {\n return TRANSFORM_PROPS.has(prop);\n}\n\n/**\n * Check if a property is a color property\n */\nexport function isColorProp(prop: string): boolean {\n return COLOR_PROPS.has(prop);\n}\n\n/**\n * Check if a property is the filter property\n */\nexport function isFilterProp(prop: string): boolean {\n return prop === 'filter';\n}\n\n/**\n * Check if a property is the drawSVG property\n */\nexport function isDrawSVGProp(prop: string): boolean {\n return prop === 'drawSVG';\n}\n\n/**\n * Check if a property is the path property (motion path)\n */\nexport function isPathProp(prop: string): boolean {\n return prop === 'path';\n}\n\n/**\n * Check if a property is a CSS variable (custom property)\n */\nexport function isCSSVariable(prop: string): boolean {\n return prop.startsWith('--');\n}\n\n/**\n * Check if a value looks like a color (for CSS variable type detection)\n */\nexport function looksLikeColor(value: PropertyValue): boolean {\n if (typeof value !== 'string') return false;\n const trimmed = value.trim().toLowerCase();\n // Hex colors\n if (trimmed.startsWith('#')) return true;\n // RGB/RGBA/HSL/HSLA functions\n if (/^(rgb|rgba|hsl|hsla)\\s*\\(/.test(trimmed)) return true;\n // Delegate to ColorParser for full CSS named color coverage (148+ colors via browser engine)\n const colorParser = SDKRegistry.color;\n if (colorParser) return colorParser.parseColor(trimmed) !== null;\n return NAMED_COLORS.has(trimmed); // graceful fallback if ColorParser not loaded\n}\n\n/**\n * Parse a property value and extract numeric value and unit\n */\nexport function parseValue(value: PropertyValue): { value: number; unit: string } {\n if (typeof value === 'number') {\n if (isNaN(value) || !isFinite(value)) {\n console.warn(`[Motion] Property value is ${value}. This property will not animate correctly.`);\n return { value: 0, unit: '' };\n }\n return { value, unit: '' };\n }\n\n if (typeof value === 'string') {\n // Check for relative operators\n const relativeMatch = value.match(/^([+\\-*/])=(.+)/);\n if (relativeMatch) {\n const numMatch = relativeMatch[2].match(/([-+]?[\\d.]+(?:e[-+]?\\d+)?)([a-z%]*)/i);\n if (numMatch) {\n return {\n value: parseFloat(numMatch[1]),\n unit: numMatch[2] || ''\n };\n }\n }\n\n // Regular value with unit (supports scientific notation like 2.5e-16)\n const match = value.match(/([-+]?[\\d.]+(?:e[-+]?\\d+)?)([a-z%]*)/i);\n if (match) {\n return {\n value: parseFloat(match[1]),\n unit: match[2] || ''\n };\n }\n }\n\n return { value: 0, unit: '' };\n}\n\n/**\n * Get the default unit for a property\n */\nexport function getDefaultUnit(prop: string): string {\n if (UNITLESS_PROPS.has(prop)) {\n return '';\n }\n if (DEG_PROPS.has(prop)) {\n return 'deg';\n }\n if (PX_PROPS.has(prop)) {\n return 'px';\n }\n return '';\n}\n\n/**\n * Get the default value for a property when no computed value is available\n */\nfunction getDefaultPropertyValue(prop: string): number {\n // Opacity defaults to 1 (fully visible)\n if (prop === 'opacity') {\n return 1;\n }\n // Scale properties default to 1\n if (prop.startsWith('scale')) {\n return 1;\n }\n // Most other properties default to 0\n return 0;\n}\n\n/**\n * Get the current computed value of a property from a target\n * Handles both DOM Elements and plain objects\n */\nexport function getCurrentValue(target: AnimationTarget, prop: string): number {\n // Plain object target - read property directly\n if (!(target instanceof Element)) {\n const obj = target as Record<string, number>;\n if (prop in obj) {\n return obj[prop];\n }\n // Return default for known properties\n return getDefaultTransformValue(prop);\n }\n\n // DOM Element - get from transform cache or computed style\n if (isTransformProp(prop)) {\n return getCachedTransformValue(target, prop);\n }\n\n // Get from computed style (handle test environments)\n try {\n if (typeof window !== 'undefined' && window.getComputedStyle) {\n const computed = window.getComputedStyle(target);\n const value = computed.getPropertyValue(camelToKebab(prop));\n\n if (value) {\n const parsed = parseValue(value);\n return parsed.value;\n }\n }\n } catch (e) {\n // Fail gracefully in test environments\n }\n\n // Return property-specific default (not just 0 for everything)\n return getDefaultPropertyValue(prop);\n}\n\n/**\n * Get the original CSS value AND unit of a property (excluding inline styles)\n * Returns both value and unit to preserve the original CSS unit (%, rem, em, vh, etc.)\n */\nexport function getOriginalCSSValueWithUnit(target: Element, prop: string): { value: number; unit: string } {\n const htmlEl = target as HTMLElement;\n const kebabProp = camelToKebab(prop);\n\n // For transform properties, we need special handling\n if (isTransformProp(prop)) {\n // Save current inline transform\n const inlineTransform = htmlEl.style.transform;\n // Clear it\n clearTransformCache(target);\n htmlEl.style.transform = '';\n // Get computed (will now reflect stylesheet only)\n const value = getCachedTransformValue(target, prop);\n // Restore inline\n htmlEl.style.transform = inlineTransform;\n clearTransformCache(target);\n // Transform properties use their default units\n return { value, unit: getDefaultUnit(prop) };\n }\n\n // For regular CSS properties\n try {\n if (typeof window !== 'undefined' && window.getComputedStyle) {\n // Save current inline value\n const inlineValue = htmlEl.style.getPropertyValue(kebabProp);\n // Temporarily remove inline style\n htmlEl.style.removeProperty(kebabProp);\n // Get computed style (now reflects stylesheet only)\n const computed = window.getComputedStyle(target);\n const value = computed.getPropertyValue(kebabProp);\n // Restore inline style\n if (inlineValue) {\n htmlEl.style.setProperty(kebabProp, inlineValue);\n }\n\n if (value) {\n const parsed = parseValue(value);\n return { value: parsed.value, unit: parsed.unit };\n }\n }\n } catch (e) {\n // Fail gracefully\n }\n\n return { value: getDefaultPropertyValue(prop), unit: getDefaultUnit(prop) };\n}\n\n/**\n * Get default value for transform properties\n */\nfunction getDefaultTransformValue(prop: string): number {\n if (prop.startsWith('scale')) {\n return 1;\n }\n return 0;\n}\n\n/**\n * Convert camelCase to kebab-case\n */\nexport function camelToKebab(str: string): string {\n return str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);\n}\n\n/**\n * Check if a value is relative (+=, -=, *=, /=)\n */\nexport function isRelativeValue(value: PropertyValue): boolean {\n if (typeof value === 'string') {\n return /^[+\\-*/]=/.test(value);\n }\n return false;\n}\n\n/**\n * Get relative operator from value string\n */\nexport function getRelativeOperator(value: string): '+=' | '-=' | '*=' | '/=' | null {\n const match = value.match(/^([+\\-*/])=/);\n return match ? (match[0] as '+=' | '-=' | '*=' | '/=') : null;\n}\n\n/**\n * Calculate relative end value\n */\nexport function calculateRelativeValue(\n startValue: number,\n relativeValue: number,\n operator: '+=' | '-=' | '*=' | '/='\n): number {\n switch (operator) {\n case '+=':\n return startValue + relativeValue;\n case '-=':\n return startValue - relativeValue;\n case '*=':\n return startValue * relativeValue;\n case '/=':\n return relativeValue !== 0 ? startValue / relativeValue : startValue;\n default:\n return relativeValue;\n }\n}\n\n/**\n * Parse a color property for animation\n * Returns null if ColorParser is not loaded\n */\nfunction parseColorProperty(\n element: Element,\n prop: string,\n targetValue: PropertyValue,\n fromValue?: PropertyValue\n): ParsedColorProperty | null {\n const colorParser = SDKRegistry.color;\n if (!colorParser) {\n return null; // ColorParser not loaded\n }\n\n // Empty string means \"use current computed color\" (used when from() has color but to() doesn't)\n const targetStr = String(targetValue);\n const endColor = targetStr === ''\n ? colorParser.getCurrentColor(element, prop)\n : colorParser.parseColor(targetStr, element);\n\n const startColor = fromValue !== undefined\n ? colorParser.parseColor(String(fromValue), element)\n : colorParser.getCurrentColor(element, prop);\n\n return {\n type: 'color',\n property: prop,\n isTransform: false,\n startColor: startColor || new Float32Array([0, 0, 0, 1]),\n endColor: endColor || new Float32Array([0, 0, 0, 1]),\n };\n}\n\n/**\n * Parse a filter property for animation\n * Returns null if FilterParser is not loaded\n */\nfunction parseFilterProperty(\n element: Element,\n prop: string,\n targetValue: PropertyValue,\n fromValue?: PropertyValue\n): ParsedFilterProperty | null {\n const filterParser = SDKRegistry.filter;\n if (!filterParser) {\n return null; // FilterParser not loaded\n }\n\n // Parse target filter (empty string means \"use current\")\n const targetStr = String(targetValue);\n let endFilters = targetStr === ''\n ? filterParser.getCurrentFilter(element)\n : filterParser.parseFilter(targetStr);\n\n // Parse start filter\n let startFilters = fromValue !== undefined\n ? filterParser.parseFilter(String(fromValue))\n : filterParser.getCurrentFilter(element);\n\n // Ensure both are arrays (default to empty if parsing failed)\n startFilters = startFilters || [];\n endFilters = endFilters || [];\n\n // Merge arrays to ensure same length and order\n const merged = filterParser.mergeFilterArrays(startFilters, endFilters);\n\n return {\n type: 'filter',\n property: prop,\n isTransform: false,\n startFilters: merged.start,\n endFilters: merged.end,\n };\n}\n\n/**\n * Parse a CSS variable property for animation\n */\nfunction parseCSSVariableProperty(\n element: Element,\n prop: string,\n targetValue: PropertyValue,\n fromValue?: PropertyValue\n): ParsedProperty {\n // Detect if value is a color by checking if it looks like one\n const isColorValue = looksLikeColor(targetValue) || (fromValue !== undefined && looksLikeColor(fromValue));\n\n if (isColorValue) {\n const colorParser = SDKRegistry.color;\n if (colorParser) {\n // Parse as color\n const targetStr = String(targetValue);\n const endColor = colorParser.parseColor(targetStr, element);\n\n const startColor = fromValue !== undefined\n ? colorParser.parseColor(String(fromValue), element)\n : getCSSVariableColor(element, prop);\n\n return {\n type: 'color',\n property: prop,\n isTransform: false,\n startColor: startColor || new Float32Array([0, 0, 0, 1]),\n endColor: endColor || new Float32Array([0, 0, 0, 1]),\n };\n }\n // ColorParser not loaded, fall through to scalar\n }\n\n // Parse as scalar (numeric value)\n const parsedTo = parseValue(targetValue);\n let startValue: number;\n\n if (fromValue !== undefined) {\n startValue = parseValue(fromValue).value;\n } else {\n startValue = getCSSVariableNumber(element, prop);\n }\n\n return {\n type: 'scalar',\n property: prop,\n isTransform: false,\n startValue,\n endValue: parsedTo.value,\n unit: parsedTo.unit,\n };\n}\n\n/**\n * Get current CSS variable value as a color\n */\nfunction getCSSVariableColor(element: Element, prop: string): Float32Array | null {\n const colorParser = SDKRegistry.color;\n if (!colorParser) return null;\n\n try {\n if (typeof window !== 'undefined' && window.getComputedStyle) {\n const computed = window.getComputedStyle(element);\n const value = computed.getPropertyValue(prop).trim();\n if (value) {\n return colorParser.parseColor(value, element);\n }\n }\n } catch {\n // Fail gracefully\n }\n return null;\n}\n\n/**\n * Get current CSS variable value as a number\n */\nfunction getCSSVariableNumber(element: Element, prop: string): number {\n try {\n if (typeof window !== 'undefined' && window.getComputedStyle) {\n const computed = window.getComputedStyle(element);\n const value = computed.getPropertyValue(prop).trim();\n if (value) {\n return parseValue(value).value;\n }\n }\n } catch {\n // Fail gracefully\n }\n return 0;\n}\n\n/**\n * Parse a drawSVG property for animation\n * Returns null if DrawSVGParser is not loaded\n */\nfunction parseDrawSVGProperty(\n element: Element,\n prop: string,\n targetValue: PropertyValue,\n fromValue?: PropertyValue\n): ParsedDrawSVGProperty | null {\n const drawSVGParser = SDKRegistry.drawSVG;\n if (!drawSVGParser) {\n return null; // DrawSVGParser not loaded\n }\n\n // Get path length (cached)\n const length = drawSVGParser.getPathLength(element);\n\n // Parse target (end) value\n const endDraw = drawSVGParser.parseDrawSVG(targetValue as string | { start?: number; end?: number }, element)\n || { start: 0, end: 1 };\n\n // Parse start value\n const startDraw = fromValue !== undefined\n ? drawSVGParser.parseDrawSVG(fromValue as string | { start?: number; end?: number }, element) || { start: 0, end: 1 }\n : drawSVGParser.getCurrentDrawSVG(element);\n\n return {\n type: 'drawSVG',\n property: prop,\n isTransform: false,\n startDraw,\n endDraw,\n length,\n };\n}\n\n/**\n * Parse a path property for motion path animation\n * Supports path in TO only, FROM only, or both FROM and TO\n */\nfunction parsePathProperty(\n element: Element,\n prop: string,\n targetValue: PropertyValue,\n fromValue?: PropertyValue\n): ParsedPathProperty | null {\n // Path config can be in targetValue (TO), fromValue (FROM), or both\n const toConfig = targetValue as unknown as PathConfig | undefined;\n const fromConfig = fromValue as unknown as PathConfig | undefined;\n\n // Get the primary config (prefer TO, fall back to FROM)\n const primaryConfig = toConfig?.target ? toConfig : fromConfig;\n if (!primaryConfig?.target) {\n return null;\n }\n\n // Resolve path data from target (selector, element, or raw path data)\n const pathData = resolvePathData(primaryConfig.target);\n if (!pathData) {\n return null;\n }\n\n // Get path length\n const pathLength = getMotionPathLength(pathData);\n if (pathLength === 0) {\n return null;\n }\n\n // Get progress values - support FROM only, TO only, or both\n let startProgress: number;\n let endProgress: number;\n\n if (toConfig?.target && fromConfig?.target) {\n // Both FROM and TO specified\n startProgress = fromConfig.start ?? 0;\n endProgress = toConfig.end ?? 1;\n } else if (toConfig?.target) {\n // TO only - animate from start to end on the path\n startProgress = toConfig.start ?? 0;\n endProgress = toConfig.end ?? 1;\n } else {\n // FROM only - animate from the from position to end of path\n startProgress = fromConfig!.start ?? 0;\n endProgress = fromConfig!.end ?? 1;\n }\n\n // Get rotation setting (from either config)\n const rotate = toConfig?.rotate ?? fromConfig?.rotate ?? false;\n\n // Calculate element origin offset (alignAt - where on the element to position)\n const alignAt = toConfig?.alignAt ?? fromConfig?.alignAt ?? [50, 50];\n const alignOffset = calculateElementOffset(element, alignAt);\n\n // Calculate path offset (align - position path relative to another element)\n const alignTarget = toConfig?.align ?? fromConfig?.align;\n const pathOffset = calculatePathAlignOffset(alignTarget, pathData, element);\n\n return {\n type: 'path',\n property: prop,\n isTransform: false,\n pathData,\n pathLength,\n startProgress,\n endProgress,\n rotate,\n alignOffset,\n pathOffset,\n };\n}\n\n/**\n * Parse a property for animation\n * Handles both DOM Elements and plain objects\n */\nexport function parseProperty(\n target: AnimationTarget,\n prop: string,\n targetValue: PropertyValue,\n fromValue?: PropertyValue\n): ParsedProperty {\n const isTargetElement = target instanceof Element;\n\n // Handle CSS variables (DOM elements only)\n if (isTargetElement && isCSSVariable(prop)) {\n return parseCSSVariableProperty(target, prop, targetValue, fromValue);\n }\n\n // Handle color properties (DOM elements only, requires ColorParser)\n if (isTargetElement && isColorProp(prop)) {\n const colorParsed = parseColorProperty(target, prop, targetValue, fromValue);\n if (colorParsed) {\n return colorParsed;\n }\n // ColorParser not loaded - skip this property by returning a no-op scalar\n // The property will animate from 0 to 0 (no visible effect)\n }\n\n // Handle filter property (DOM elements only, requires FilterParser)\n if (isTargetElement && isFilterProp(prop)) {\n const filterParsed = parseFilterProperty(target, prop, targetValue, fromValue);\n if (filterParsed) {\n return filterParsed;\n }\n // FilterParser not loaded - skip this property\n }\n\n // Handle drawSVG property (DOM elements only, requires DrawSVGParser)\n if (isTargetElement && isDrawSVGProp(prop)) {\n const drawSVGParsed = parseDrawSVGProperty(target, prop, targetValue, fromValue);\n if (drawSVGParsed) {\n return drawSVGParsed;\n }\n // DrawSVGParser not loaded - skip this property\n }\n\n // Handle path property (motion path, DOM elements only)\n if (isTargetElement && isPathProp(prop)) {\n const parsed = parsePathProperty(target, prop, targetValue, fromValue);\n if (parsed) {\n return parsed;\n }\n // Fall through to scalar if path parsing failed\n }\n\n // Scalar properties work for both DOM elements and plain objects\n const isTransform = isTransformProp(prop);\n const isRelative = fromValue === undefined && isRelativeValue(targetValue);\n\n let startValue: number;\n let endValue: number;\n let unit: string;\n\n if (fromValue !== undefined) {\n // FromTo animation - both values are specified\n const parsedFrom = parseValue(fromValue);\n const parsedTo = parseValue(targetValue);\n\n startValue = parsedFrom.value;\n endValue = parsedTo.value;\n unit = parsedTo.unit || parsedFrom.unit || getDefaultUnit(prop);\n } else if (isRelative) {\n // Relative animation (+=, -=, *=, /=)\n const relativeOp = getRelativeOperator(targetValue as string)!;\n const current = getCurrentValue(target, prop);\n const parsed = parseValue(targetValue);\n\n startValue = current;\n endValue = calculateRelativeValue(current, parsed.value, relativeOp);\n unit = parsed.unit || getDefaultUnit(prop);\n } else {\n // Regular \"to\" animation\n const current = getCurrentValue(target, prop);\n const parsed = parseValue(targetValue);\n\n startValue = current;\n endValue = parsed.value;\n unit = parsed.unit || getDefaultUnit(prop);\n }\n\n return {\n type: 'scalar',\n property: prop,\n isTransform,\n startValue,\n endValue,\n unit,\n };\n}\n",
|
|
17
|
+
"/**\n * StyleReset - Clear animation-related inline styles from elements\n *\n * Only clears styles that our SDK has animated or set for pinning,\n * preserving user's original inline styles.\n * Uses registries to track which CSS properties have been set by the SDK.\n */\n\nimport { isTransformProp, isFilterProp, isCSSVariable, isDrawSVGProp } from './PropertyParser';\nimport { SDKRegistry } from '../registries/SDKRegistry';\n\n// ============================================================================\n// Generic prop registry factory (internal)\n// ============================================================================\n\ninterface PropRegistry {\n register(element: Element, prop: string): void;\n registerMany(element: Element, props: string[]): void;\n get(element: Element): Set<string> | undefined;\n clearRegistry(element: Element): void;\n}\n\n/**\n * Creates a WeakMap-backed registry that tracks which CSS properties have been\n * set on each element. The WeakMap ensures elements are garbage collected when\n * removed from the DOM without needing explicit cleanup.\n */\nfunction createPropRegistry(): PropRegistry {\n const registry = new WeakMap<Element, Set<string>>();\n\n return {\n register(element: Element, prop: string): void {\n let props = registry.get(element);\n if (!props) {\n props = new Set<string>();\n registry.set(element, props);\n }\n props.add(prop);\n },\n\n registerMany(element: Element, props: string[]): void {\n for (const prop of props) {\n this.register(element, prop);\n }\n },\n\n get(element: Element): Set<string> | undefined {\n return registry.get(element);\n },\n\n clearRegistry(element: Element): void {\n registry.delete(element);\n },\n };\n}\n\n// ============================================================================\n// Registry instances\n// ============================================================================\n\n/**\n * Tracks which CSS properties have been animated on each element.\n * Uses WeakMap so elements can be garbage collected when removed from DOM.\n */\nconst animatedProps = createPropRegistry();\n\n/**\n * Registry of original inline transform values for animated elements.\n */\nconst originalTransformRegistry = new WeakMap<Element, string | null>();\n\n/**\n * Tracks which pin-related CSS properties have been set on each element.\n * Separate from animation props because pin styles are set/restored by PinManager.\n */\nconst pinnedProps = createPropRegistry();\n\n/**\n * Map animation property names to actual CSS property names.\n * Transform properties (x, y, rotate, scale, etc.) all map to 'transform'.\n * Filter property maps to 'filter'.\n * DrawSVG maps to strokeDasharray and strokeDashoffset.\n * Other properties map directly.\n */\nfunction animPropToCssProps(prop: string): string[] {\n if (isTransformProp(prop)) {\n return ['transform'];\n }\n if (isFilterProp(prop)) {\n return ['filter'];\n }\n if (isDrawSVGProp(prop)) {\n return ['strokeDasharray', 'strokeDashoffset'];\n }\n // CSS variables and other properties map directly\n return [prop];\n}\n\n/**\n * Register that a CSS property has been animated on an element.\n * Call this when setting inline styles during animation.\n */\nexport function registerAnimatedProp(element: Element, prop: string): void {\n // Capture the element's original inline transform before the first animation\n if (isTransformProp(prop) && !originalTransformRegistry.has(element)) {\n if (element instanceof HTMLElement || element instanceof SVGElement) {\n const inlineTransform = (element as HTMLElement).style.transform;\n originalTransformRegistry.set(element, inlineTransform || null);\n }\n }\n\n // Normalize animation prop name(s) to CSS property name(s) and register each\n const cssProps = animPropToCssProps(prop);\n for (const cssProp of cssProps) {\n animatedProps.register(element, cssProp);\n }\n}\n\n/**\n * Register multiple animated properties at once.\n */\nexport function registerAnimatedProps(element: Element, props: string[]): void {\n for (const prop of props) {\n registerAnimatedProp(element, prop);\n }\n}\n\n/**\n * Get all CSS properties that have been animated on an element.\n */\nexport function getAnimatedProps(element: Element): Set<string> {\n return animatedProps.get(element) || new Set();\n}\n\n/**\n * Clear tracking for an element (called after Motion.reset).\n */\nexport function clearAnimatedPropsRegistry(element: Element): void {\n animatedProps.clearRegistry(element);\n originalTransformRegistry.delete(element);\n}\n\n/**\n * Clear specific animation properties from an element's inline styles.\n * Only clears the properties specified, preserving other inline styles.\n *\n * @param element - The element to clear styles from\n * @param props - Animation property names to clear (e.g., 'x', 'opacity', 'backgroundColor')\n */\nexport function clearAnimationStylesForProps(element: Element, props: string[]): void {\n if (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) return;\n\n // Convert animation props to CSS props and dedupe\n const cssPropsSet = new Set<string>();\n for (const prop of props) {\n for (const cssProp of animPropToCssProps(prop)) {\n cssPropsSet.add(cssProp);\n }\n }\n\n // Clear each CSS property\n for (const cssProp of cssPropsSet) {\n if (isCSSVariable(cssProp)) {\n // CSS variables need special handling\n element.style.removeProperty(cssProp);\n } else {\n // Regular properties\n (element.style as unknown as Record<string, string>)[cssProp] = '';\n }\n }\n}\n\n/**\n * Clear all animation-related inline styles that our SDK has set on an element.\n * Only clears properties tracked in the registry, preserving user's original inline styles.\n */\nexport function clearAnimationStyles(element: Element): void {\n if (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) return;\n\n const trackedProps = animatedProps.get(element);\n if (!trackedProps || trackedProps.size === 0) {\n return; // Nothing to clear\n }\n\n // Clear each tracked CSS property\n for (const cssProp of trackedProps) {\n if (cssProp === 'transform') {\n // Restore the element's original inline transform rather than simply blanking it\n const originalTransform = originalTransformRegistry.get(element);\n if (originalTransform !== undefined) {\n if (originalTransform) {\n element.style.transform = originalTransform;\n } else {\n element.style.removeProperty('transform');\n }\n continue;\n }\n }\n if (isCSSVariable(cssProp)) {\n element.style.removeProperty(cssProp);\n } else {\n (element.style as unknown as Record<string, string>)[cssProp] = '';\n }\n }\n}\n\n/**\n * Clear animation styles and remove from registry (full reset).\n * Use this for Motion.reset() to completely clean up.\n */\nexport function clearAnimationStylesAndUnregister(element: Element): void {\n clearAnimationStyles(element);\n clearAnimatedPropsRegistry(element);\n}\n\n/**\n * Clear animation styles from multiple elements\n */\nexport function clearAnimationStylesAll(elements: Element[]): void {\n for (const element of elements) {\n clearAnimationStyles(element);\n }\n}\n\n// ============================================================================\n// Pin Style Tracking\n// ============================================================================\n\n/**\n * Register that a pin-related CSS property has been set on an element.\n * Call this when PinManager sets styles during pinning.\n */\nexport function registerPinnedProp(element: Element, prop: string): void {\n pinnedProps.register(element, prop);\n}\n\n/**\n * Register multiple pinned properties at once.\n */\nexport function registerPinnedProps(element: Element, props: string[]): void {\n pinnedProps.registerMany(element, props);\n}\n\n/**\n * Get all pin-related CSS properties that have been set on an element.\n */\nexport function getPinnedProps(element: Element): Set<string> {\n return pinnedProps.get(element) || new Set();\n}\n\n/**\n * Clear tracking for pinned properties on an element.\n */\nexport function clearPinnedPropsRegistry(element: Element): void {\n pinnedProps.clearRegistry(element);\n}\n\n/**\n * Clear pin-related inline styles that our SDK has set on an element.\n * Only clears properties tracked in the pin registry.\n */\nexport function clearPinStyles(element: Element): void {\n if (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) return;\n\n const trackedProps = pinnedProps.get(element);\n if (!trackedProps || trackedProps.size === 0) {\n return; // Nothing to clear\n }\n\n // Clear each tracked pin property\n for (const prop of trackedProps) {\n (element.style as unknown as Record<string, string>)[prop] = '';\n }\n}\n\n/**\n * Clear pin styles and remove from registry.\n * Use this for Motion.reset() to completely clean up pin styles.\n */\nexport function clearPinStylesAndUnregister(element: Element): void {\n clearPinStyles(element);\n clearPinnedPropsRegistry(element);\n}\n\n// Self-register with the registry\nSDKRegistry.styleReset = {\n registerAnimatedProps,\n clearAnimationStylesForProps,\n clearAnimationStylesAndUnregister,\n clearPinStylesAndUnregister,\n};\n",
|
|
18
|
+
"/**\n * PinManager\n *\n * Handles element pinning during scroll animations.\n * Extracted from ScrollTrigger for better separation of concerns.\n *\n * Two pinning strategies based on scroller type:\n * 1. Window scroller: position:fixed (seamless, no jitter)\n * 2. Custom scroller: translate3d (counters scroll movement)\n *\n * State machine: before → pinned → after\n */\n\nimport { registerPinnedProps, clearPinnedPropsRegistry } from '../utils/StyleReset';\nimport { setPinOffset, clearPinOffset, buildTransformString } from '../render/TransformCache';\n\ninterface OriginalStyles {\n position: string;\n top: string;\n left: string;\n width: string;\n marginTop: string;\n marginBottom: string;\n marginLeft: string;\n marginRight: string;\n zIndex: string;\n}\n\nexport type PinState = 'before' | 'pinned' | 'after';\n\nexport class PinManager {\n // -------------------------------------------------------------------------\n // Global registry: maps each DOM element to the single PinManager that owns it.\n // Prevents multiple ScrollTrigger instances (e.g. from each:true + pin string)\n // from wrapping the same pinned element in nested spacers.\n // -------------------------------------------------------------------------\n private static _registry: Map<HTMLElement, PinManager> = new Map();\n\n // Tracks which ScrollTrigger IDs are currently referencing each PinManager.\n // The shared manager is only cleaned up when the last user calls release().\n private static _users: Map<HTMLElement, Set<number>> = new Map();\n\n /**\n * Get or create a shared PinManager for the given element.\n * If another ScrollTrigger already pinned this element, returns that same\n * PinManager so no additional spacer is created. Tracks `id` as a user.\n */\n static getOrCreate(id: number, element: HTMLElement): PinManager {\n let manager = PinManager._registry.get(element);\n if (!manager) {\n manager = new PinManager(id);\n PinManager._registry.set(element, manager);\n }\n // Register this ScrollTrigger as a user of the shared manager\n let users = PinManager._users.get(element);\n if (!users) {\n users = new Set();\n PinManager._users.set(element, users);\n }\n users.add(id);\n return manager;\n }\n\n /**\n * Release a ScrollTrigger's reference to a pinned element's PinManager.\n * When the last user releases, cleanup() is called and registry entries removed.\n */\n static release(id: number, element: HTMLElement): void {\n const users = PinManager._users.get(element);\n if (!users) return;\n users.delete(id);\n if (users.size === 0) {\n // Last user released — remove from registry first, then tear down the pin\n const manager = PinManager._registry.get(element);\n PinManager._registry.delete(element);\n PinManager._users.delete(element);\n manager?.cleanup();\n }\n }\n\n // Unique ID for spacer data attribute\n private _id: number;\n\n // Element being pinned\n private _element: HTMLElement | null = null;\n\n // Saved styles for restoration\n private _originalStyles: OriginalStyles | null = null;\n\n // Spacer element to preserve layout during fixed pin\n private _spacer: HTMLElement | null = null;\n\n // Current pin state\n private _currentState: PinState = 'before';\n\n // Pin range (scroll positions)\n private _pinStart: number = 0;\n private _pinEnd: number = 0;\n private _pinDistance: number = 0;\n\n // Strategy flag\n private _useFixedPin: boolean = false;\n\n // Pin spacing mode\n private _pinSpacing: boolean | 'margin' | 'padding' | undefined;\n\n // Element height at setup time (used for 'after' offset calculation with sibling spacer)\n private _elementHeight: number = 0;\n\n // Fixed position coordinates (for fixed pin strategy)\n private _fixedTop: number = 0;\n private _fixedLeft: number = 0;\n private _width: number = 0;\n\n constructor(id: number) {\n this._id = id;\n }\n\n /**\n * Setup pinning for an element\n *\n * @param element - The element to pin\n * @param pinStart - Scroll position where pin starts\n * @param pinEnd - Scroll position where pin ends\n * @param scrollerStartOffset - Viewport offset for fixed positioning\n * @param useFixedPin - true for window scroller (position:fixed), false for custom (transform)\n */\n setup(\n element: HTMLElement,\n pinStart: number,\n pinEnd: number,\n scrollerStartOffset: number,\n useFixedPin: boolean,\n pinSpacing?: boolean | 'margin' | 'padding'\n ): void {\n if (typeof document === 'undefined') return;\n\n this._element = element;\n this._pinStart = pinStart;\n this._pinEnd = pinEnd;\n this._pinDistance = pinEnd - pinStart;\n this._useFixedPin = useFixedPin;\n this._pinSpacing = pinSpacing;\n\n const isRefresh = this._spacer !== null;\n // Always measure dimensions from the element itself, never the spacer.\n // The spacer's BCR height includes padding-bottom (= pinDistance) because its\n // box-sizing is content-box, so using spacer.getBoundingClientRect().height would\n // cause _elementHeight to snowball on each refresh cycle:\n // height(n+1) = height(n) + pinDistance\n // The element's BCR height is its natural content height even while position:fixed.\n const elementRect = element.getBoundingClientRect();\n // Margins were transferred to the spacer during first setup, so read them from\n // the spacer on refresh — the element's own margins are zeroed at that point.\n const computedStyle = (isRefresh && this._spacer)\n ? window.getComputedStyle(this._spacer)\n : window.getComputedStyle(element);\n\n // Store the element's actual content height (never the spacer's inflated height).\n this._elementHeight = elementRect.height;\n\n // Only capture original styles on first setup, not on refresh\n if (!isRefresh) {\n this._originalStyles = {\n position: element.style.position,\n top: element.style.top,\n left: element.style.left,\n width: element.style.width,\n marginTop: element.style.marginTop,\n marginBottom: element.style.marginBottom,\n marginLeft: element.style.marginLeft,\n marginRight: element.style.marginRight,\n zIndex: element.style.zIndex,\n };\n }\n\n if (useFixedPin) {\n // Store fixed position values — always use the element's rect for dimensions.\n this._fixedTop = scrollerStartOffset;\n this._fixedLeft = elementRect.left;\n this._width = elementRect.width;\n\n const resolved = this._resolvePinSpacing();\n\n // Build spacer CSS — the spacer wraps the element (GSAP-compatible pattern).\n // It replaces the element in the document flow and provides pinDistance of\n // extra scroll room via padding or margin.\n // Use elementRect dimensions so the spacer content-height always equals the\n // element's natural height, regardless of refresh cycles.\n let spacerCSS = `\n display: block;\n box-sizing: content-box;\n width: ${elementRect.width}px;\n height: ${elementRect.height}px;\n margin-top: ${computedStyle.marginTop};\n margin-left: ${computedStyle.marginLeft};\n margin-right: ${computedStyle.marginRight};\n overflow: visible;\n `;\n\n if (resolved === 'padding') {\n // Add pin distance as padding-bottom on spacer to push content down\n spacerCSS += `padding-bottom: ${this._pinDistance}px;`;\n spacerCSS += `margin-bottom: ${computedStyle.marginBottom};`;\n } else if (resolved === 'margin') {\n // Add pin distance as margin-bottom on spacer to push content down\n const originalMarginBottom = parseFloat(computedStyle.marginBottom) || 0;\n spacerCSS += `margin-bottom: ${originalMarginBottom + this._pinDistance}px;`;\n } else {\n // No spacing — spacer only preserves element's original space\n spacerCSS += `margin-bottom: ${computedStyle.marginBottom};`;\n }\n\n if (!this._spacer) {\n // First setup: create spacer and wrap element inside it\n this._spacer = document.createElement('div');\n element.parentNode?.insertBefore(this._spacer, element);\n this._spacer.appendChild(element);\n\n // Transfer margins to spacer — element's margins are now zero\n // so they don't double-count within the wrapper\n element.style.marginTop = '0';\n element.style.marginBottom = '0';\n element.style.marginLeft = '0';\n element.style.marginRight = '0';\n }\n\n // Update spacer styles (works for both first and subsequent setups / refresh)\n this._spacer.style.cssText = spacerCSS;\n this._spacer.setAttribute('data-scrolltrigger-spacer', String(this._id));\n }\n\n this._currentState = 'before';\n }\n\n /**\n * Update pin state based on scroll position\n */\n update(scrollTop: number): void {\n if (!this._element) return;\n\n const newState: PinState =\n scrollTop < this._pinStart\n ? 'before'\n : scrollTop < this._pinEnd\n ? 'pinned'\n : 'after';\n\n if (this._useFixedPin) {\n // Fixed pinning: only update DOM on state change\n if (newState !== this._currentState) {\n this._updateFixed(newState);\n this._currentState = newState;\n }\n } else {\n // Transform pinning: must update transform continuously during 'pinned' state\n if (newState !== this._currentState || newState === 'pinned') {\n this._updateTransform(newState, scrollTop);\n this._currentState = newState;\n }\n }\n }\n\n /**\n * Clean up pin - restore original styles and remove spacer\n */\n cleanup(): void {\n // Safety guard: if cleanup() is called directly (bypassing release()),\n // remove this manager from the registry so no stale references remain.\n if (this._element && PinManager._registry.get(this._element) === this) {\n PinManager._registry.delete(this._element);\n PinManager._users.delete(this._element);\n }\n\n if (this._element && this._originalStyles) {\n const el = this._element;\n\n // Remove pin offset from TransformCache so subsequent animation renders\n // do not include the pin translation\n clearPinOffset(el);\n\n // Restore all original styles including margins (transferred to spacer during setup)\n el.style.position = this._originalStyles.position;\n el.style.top = this._originalStyles.top;\n el.style.left = this._originalStyles.left;\n el.style.width = this._originalStyles.width;\n el.style.marginTop = this._originalStyles.marginTop;\n el.style.marginBottom = this._originalStyles.marginBottom;\n el.style.marginLeft = this._originalStyles.marginLeft;\n el.style.marginRight = this._originalStyles.marginRight;\n el.style.zIndex = this._originalStyles.zIndex;\n el.style.transform = '';\n\n // Clear pin styles from registry since we've restored originals\n clearPinnedPropsRegistry(el);\n }\n\n // Unwrap: move element back to spacer's parent, then remove spacer\n if (this._spacer?.parentNode && this._element) {\n this._spacer.parentNode.insertBefore(this._element, this._spacer);\n this._spacer.parentNode.removeChild(this._spacer);\n }\n\n this._element = null;\n this._originalStyles = null;\n this._spacer = null;\n this._currentState = 'before';\n this._pinSpacing = undefined;\n }\n\n /**\n * Get the pinned element\n */\n getElement(): HTMLElement | null {\n return this._element;\n }\n\n /**\n * Get the pin distance (scroll range over which element is pinned)\n */\n getPinDistance(): number {\n return this._pinDistance;\n }\n\n /**\n * Get current pin state\n */\n getState(): PinState {\n return this._currentState;\n }\n\n /**\n * Get a layout rect for trigger-position calculations.\n *\n * Used by ScrollTrigger._calculateTriggerPositions() and refresh() to obtain the\n * correct document-relative position of the pinned element when it is position:fixed\n * — querying the element itself in that state returns viewport-relative coordinates,\n * which produce incorrect trigger positions.\n *\n * Returns a synthetic rect that combines:\n * - top/left/width from the spacer (document-flow position)\n * - height from _elementHeight (the element's natural content height)\n *\n * The spacer's own BCR height is NOT used because it includes padding-bottom\n * (= pinDistance, added for pin spacing), which would inflate callers' end-position\n * calculations and grow the pinDistance on every refresh cycle.\n *\n * Returns `null` when the spacer is not in the DOM (transform-pin strategy or\n * before first setup), signalling the caller to fall back to the element's own rect.\n */\n getLayoutRect(): { top: number; left: number; width: number; height: number; bottom: number; right: number } | null {\n if (this._spacer?.parentNode) {\n const spacerRect = (this._spacer as HTMLElement).getBoundingClientRect();\n // Use spacer's position for document-flow coordinates, but _elementHeight\n // for the height so callers see the element's natural dimensions — not the\n // spacer's inflated height that includes padding-bottom = pinDistance.\n return {\n top: spacerRect.top,\n left: spacerRect.left,\n width: spacerRect.width,\n height: this._elementHeight,\n bottom: spacerRect.top + this._elementHeight,\n right: spacerRect.left + spacerRect.width,\n };\n }\n return null;\n }\n\n /**\n * Check if using fixed pin strategy\n */\n isFixedPin(): boolean {\n return this._useFixedPin;\n }\n\n /**\n * Resolve pinSpacing config to a concrete value\n */\n private _resolvePinSpacing(): 'padding' | 'margin' | false {\n if (this._pinSpacing === false) return false;\n if (this._pinSpacing === 'margin') return 'margin';\n // true, 'padding', undefined all resolve to 'padding' (default)\n return 'padding';\n }\n\n /**\n * Fixed positioning for window scroller (seamless pinning)\n *\n * Wrapper pattern: the spacer wraps the element at all times (created in\n * setup, removed in cleanup). State changes only toggle styles on the\n * element — no spacer insertion/removal between states.\n *\n * Layout math (wrapper):\n * Spacer content-height = elementHeight\n * Spacer padding-bottom = pinDistance (when pinSpacing enabled)\n * Spacer total box = elementHeight + pinDistance\n *\n * 'before': element in flow inside spacer at top — no offset\n * 'pinned': element position:fixed, spacer holds space\n * 'after': element back in flow at spacer top, translateY(pinDistance)\n * moves it visually to (originalPos + pinDistance) — seamless\n * transition with the next section\n */\n private _updateFixed(state: PinState): void {\n const el = this._element!;\n\n if (state === 'before') {\n // Restore to normal flow inside spacer — element sits at spacer top\n el.style.position = this._originalStyles?.position || '';\n el.style.top = this._originalStyles?.top || '';\n el.style.left = this._originalStyles?.left || '';\n el.style.width = this._originalStyles?.width || '';\n // Margins stay zeroed — spacer owns them\n el.style.marginTop = '0';\n el.style.marginLeft = '0';\n el.style.zIndex = this._originalStyles?.zIndex || '';\n // Clear pin offset so the next animation render doesn't include it\n clearPinOffset(el);\n el.style.transform = buildTransformString(el);\n } else if (state === 'pinned') {\n // Fix element to viewport — no pin offset needed while position:fixed is active;\n // animation transforms apply directly on top of the fixed viewport position\n el.style.position = 'fixed';\n el.style.top = `${this._fixedTop}px`;\n el.style.left = `${this._fixedLeft}px`;\n el.style.width = `${this._width}px`;\n el.style.marginTop = '0';\n el.style.marginLeft = '0';\n el.style.zIndex = '100';\n clearPinOffset(el);\n el.style.transform = buildTransformString(el);\n // Register that we've set these pin styles\n registerPinnedProps(el, ['position', 'top', 'left', 'width', 'marginTop', 'marginLeft', 'zIndex', 'transform']);\n } else {\n // After pin zone: element returns to flow inside spacer.\n // The element sits at the top of the spacer's content area.\n // translateY(pinDistance) moves it visually to (originalPos + pinDistance),\n // while the spacer's total box (elementHeight + pinDistance) controls\n // document flow — no extra space because the element is INSIDE the spacer.\n el.style.position = this._originalStyles?.position || '';\n el.style.top = this._originalStyles?.top || '';\n el.style.left = this._originalStyles?.left || '';\n el.style.width = this._originalStyles?.width || '';\n // Margins stay zeroed — spacer owns them\n el.style.marginTop = '0';\n el.style.marginLeft = '0';\n el.style.zIndex = this._originalStyles?.zIndex || '';\n setPinOffset(el, 0, this._pinDistance, 0);\n el.style.transform = buildTransformString(el);\n }\n }\n\n /**\n * Transform-based pinning for custom scroll containers\n */\n private _updateTransform(state: PinState, scrollTop: number): void {\n const el = this._element!;\n\n if (state === 'before') {\n el.style.zIndex = this._originalStyles?.zIndex || '';\n // Clear pin offset so subsequent animation renders produce no pin translation\n clearPinOffset(el);\n el.style.transform = buildTransformString(el);\n } else if (state === 'pinned') {\n const scrolledPast = Math.round(scrollTop - this._pinStart);\n el.style.zIndex = '100';\n // Counter-scroll with pin offset; animation transform is composed on top\n setPinOffset(el, 0, scrolledPast, 0);\n el.style.transform = buildTransformString(el);\n // Register that we've set these pin styles\n registerPinnedProps(el, ['transform', 'zIndex']);\n } else {\n el.style.zIndex = this._originalStyles?.zIndex || '';\n // Offset element by full pin distance; animation transform is composed on top\n setPinOffset(el, 0, this._pinDistance, 0);\n el.style.transform = buildTransformString(el);\n // Register transform for the 'after' state offset\n registerPinnedProps(el, ['transform', 'zIndex']);\n }\n }\n}\n",
|
|
19
|
+
"/**\n * ScrollPositionParser\n *\n * Shared utility for parsing scroll trigger position values.\n * Used by ScrollTrigger and MarkerManager.\n *\n * Supports:\n * - Keywords: top, center, bottom\n * - Keywords with offsets: top+100px, center-50%, bottom+20vh\n * - Units: px, %, vh, vw\n */\n\n/**\n * Parse a position value into pixels\n * @param value - Position string (e.g., \"top\", \"center\", \"50%\", \"100px\", \"top+100px\")\n * @param referenceSize - Reference size for percentage calculations (element height)\n * @param viewportHeight - Viewport height for vh calculations\n * @param viewportWidth - Viewport width for vw calculations (optional)\n */\nexport function parseScrollPosition(\n value: string,\n referenceSize: number,\n viewportHeight: number,\n viewportWidth?: number\n): number {\n if (!value) return 0;\n\n const val = value.trim().toLowerCase();\n\n // Check for keyword with offset: \"top+100px\", \"center-50%\", etc.\n const keywordOffsetMatch = val.match(/^(top|center|bottom)([+-])(-?\\d+(?:\\.\\d+)?)(px|%|vh|vw)?$/);\n if (keywordOffsetMatch) {\n const keyword = keywordOffsetMatch[1];\n const sign = keywordOffsetMatch[2] === '-' ? -1 : 1;\n const offsetNum = parseFloat(keywordOffsetMatch[3]);\n const offsetUnit = keywordOffsetMatch[4] || 'px';\n\n // Get base position from keyword\n let base = 0;\n switch (keyword) {\n case 'top': base = 0; break;\n case 'center': base = referenceSize / 2; break;\n case 'bottom': base = referenceSize; break;\n }\n\n // Parse offset\n const offset = parseUnit(offsetNum, offsetUnit, referenceSize, viewportHeight, viewportWidth);\n return base + (sign * offset);\n }\n\n // Simple keywords\n switch (val) {\n case 'top': return 0;\n case 'center': return referenceSize / 2;\n case 'bottom': return referenceSize;\n }\n\n // Plain number with unit: \"100px\", \"50%\", \"-20vh\"\n const match = val.match(/^(-?\\d+(?:\\.\\d+)?)(px|%|vh|vw)?$/);\n if (match) {\n const num = parseFloat(match[1]);\n const unit = match[2] || 'px';\n return parseUnit(num, unit, referenceSize, viewportHeight, viewportWidth);\n }\n\n return 0;\n}\n\n/**\n * Parse a numeric value with unit into pixels\n */\nfunction parseUnit(\n num: number,\n unit: string,\n referenceSize: number,\n viewportHeight: number,\n viewportWidth?: number\n): number {\n switch (unit) {\n case 'px': return num;\n case '%': return (num / 100) * referenceSize;\n case 'vh': return (num / 100) * viewportHeight;\n case 'vw': return (num / 100) * (viewportWidth ?? (typeof window !== 'undefined' ? window.innerWidth : 0));\n default: return num;\n }\n}\n",
|
|
20
|
+
"/**\n * MarkerManager\n *\n * Manages debug markers for ScrollTrigger visualization.\n * Shows 4 markers:\n * - scroller-start/end: Fixed position lines showing where triggers fire\n * - start/end: Element-relative lines showing trigger points on the element\n */\n\nimport type { MarkerConfig } from '../types';\nimport { parseScrollPosition } from '../utils/ScrollPositionParser';\nimport { getLayoutRect } from '../utils/getLayoutRect';\n\ninterface MarkerElements {\n scrollerStart: HTMLElement;\n scrollerEnd: HTMLElement;\n elementStart: HTMLElement;\n elementEnd: HTMLElement;\n}\n\ninterface MarkerSetupConfig {\n scroller: Element | Window;\n triggerElement: HTMLElement | null;\n startConfig: string;\n endConfig: string;\n markerConfig: MarkerConfig | boolean;\n viewportHeight: number;\n}\n\nexport class MarkerManager {\n private _id: number;\n private _markers?: MarkerElements;\n private _markerContainer?: HTMLElement;\n private _triggerElement?: HTMLElement;\n private _scrollerStartOffset: number = 0;\n private _scrollerEndOffset: number = 0;\n private _scroller: Element | Window = window;\n private _startConfig: string = 'top top';\n private _endConfig: string = 'bottom top';\n\n constructor(triggerId: number) {\n this._id = triggerId;\n }\n\n /**\n * Create debug markers showing trigger positions\n */\n setup(config: MarkerSetupConfig): void {\n if (typeof document === 'undefined') return;\n\n // Store config for updates\n this._scroller = config.scroller;\n this._triggerElement = config.triggerElement || undefined;\n this._startConfig = config.startConfig;\n this._endConfig = config.endConfig;\n\n const markerConfig = config.markerConfig;\n const startColor = (typeof markerConfig === 'object' ? markerConfig.startColor : null) || 'lime';\n const endColor = (typeof markerConfig === 'object' ? markerConfig.endColor : null) || 'red';\n const fontSize = (typeof markerConfig === 'object' ? markerConfig.fontSize : null) || '10px';\n\n const isCustomScroller = this._scroller !== window;\n const scrollerHeight = config.viewportHeight;\n\n // Parse scroller positions (where on viewport the trigger line is)\n const startParts = this._startConfig.split(' ');\n const scrollerStartPos = parseScrollPosition(startParts[1] || 'top', scrollerHeight, scrollerHeight);\n\n const scrollerEndPos = this._endConfig.startsWith('+=')\n ? scrollerStartPos\n : parseScrollPosition(this._endConfig.split(' ')[1] || 'top', scrollerHeight, scrollerHeight);\n\n // Create a container for all markers (easier cleanup)\n this._markerContainer = document.createElement('div');\n this._markerContainer.setAttribute('data-scrolltrigger-markers', String(this._id));\n this._markerContainer.style.cssText = 'position: absolute; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; z-index: 9999;';\n\n // Helper to create a marker line with label\n const createMarker = (label: string, color: string, side: 'left' | 'right'): HTMLElement => {\n const marker = document.createElement('div');\n marker.setAttribute('data-marker', label);\n marker.style.cssText = `\n position: absolute;\n ${side}: 0;\n height: 1px;\n width: 50px;\n background: ${color};\n pointer-events: none;\n `;\n\n const labelEl = document.createElement('span');\n labelEl.textContent = label;\n labelEl.style.cssText = `\n position: absolute;\n ${side}: 0;\n top: 2px;\n background: ${color};\n color: ${color === 'lime' ? '#000' : '#fff'};\n padding: 1px 3px;\n font-size: ${fontSize};\n font-family: monospace;\n white-space: nowrap;\n border-radius: 2px;\n line-height: 1.2;\n `;\n marker.appendChild(labelEl);\n return marker;\n };\n\n // Create scroller markers (fixed position indicators on right)\n const scrollerStartMarker = createMarker('scroller-start', startColor, 'right');\n const scrollerEndMarker = createMarker('scroller-end', endColor, 'right');\n\n // Create element markers (move with element on left)\n const elementStartMarker = createMarker('start', startColor, 'left');\n const elementEndMarker = createMarker('end', endColor, 'left');\n\n // Hide element markers if no trigger element\n if (!this._triggerElement) {\n elementStartMarker.style.display = 'none';\n elementEndMarker.style.display = 'none';\n }\n\n this._markerContainer.appendChild(scrollerStartMarker);\n this._markerContainer.appendChild(scrollerEndMarker);\n this._markerContainer.appendChild(elementStartMarker);\n this._markerContainer.appendChild(elementEndMarker);\n\n this._markers = {\n scrollerStart: scrollerStartMarker,\n scrollerEnd: scrollerEndMarker,\n elementStart: elementStartMarker,\n elementEnd: elementEndMarker\n };\n\n // Append container to scroller\n if (isCustomScroller) {\n const scrollerEl = this._scroller as HTMLElement;\n // Ensure scroller has position for absolute children\n const computedPosition = window.getComputedStyle(scrollerEl).position;\n if (computedPosition === 'static') {\n scrollerEl.style.position = 'relative';\n }\n scrollerEl.appendChild(this._markerContainer);\n } else {\n document.body.appendChild(this._markerContainer);\n this._markerContainer.style.position = 'fixed';\n }\n\n // Store initial scroller positions\n this._scrollerStartOffset = scrollerStartPos;\n this._scrollerEndOffset = scrollerEndPos;\n }\n\n /**\n * Update marker positions on scroll\n */\n update(scrollTop: number, triggerElement: HTMLElement | null, viewportHeight: number): void {\n if (!this._markers || !this._markerContainer) return;\n\n // Update trigger element reference if changed\n this._triggerElement = triggerElement || undefined;\n\n const isCustomScroller = this._scroller !== window;\n\n // Position scroller markers (they stay \"fixed\" in the viewport)\n if (isCustomScroller) {\n // For custom scrollers: add scrollTop to keep them visually fixed\n this._markers.scrollerStart.style.top = `${scrollTop + this._scrollerStartOffset}px`;\n this._markers.scrollerEnd.style.top = `${scrollTop + this._scrollerEndOffset}px`;\n } else {\n // For window: truly fixed\n this._markers.scrollerStart.style.top = `${this._scrollerStartOffset}px`;\n this._markers.scrollerEnd.style.top = `${this._scrollerEndOffset}px`;\n }\n\n // Position element markers (they track the trigger element's layout position,\n // not its transformed/animated position, so markers stay fixed at the scroll\n // point where the trigger fires regardless of any active animation transforms).\n if (this._triggerElement) {\n const elRect = getLayoutRect(this._triggerElement);\n\n // Parse element position offsets\n const elStartOffset = parseScrollPosition(this._startConfig.split(' ')[0] || 'top', elRect.height, viewportHeight);\n\n const elEndOffset = this._endConfig.startsWith('+=')\n ? elStartOffset // For relative end, show at same position as start\n : parseScrollPosition(this._endConfig.split(' ')[0] || 'bottom', elRect.height, viewportHeight);\n\n this._markers.elementStart.style.display = '';\n this._markers.elementEnd.style.display = '';\n\n if (isCustomScroller) {\n const scrollerRect = (this._scroller as Element).getBoundingClientRect();\n // Element position in scroller content coordinates\n const elTopInContent = elRect.top - scrollerRect.top + scrollTop;\n this._markers.elementStart.style.top = `${elTopInContent + elStartOffset}px`;\n this._markers.elementEnd.style.top = `${elTopInContent + elEndOffset}px`;\n } else {\n // For window: use viewport-relative (fixed positioning)\n this._markers.elementStart.style.top = `${elRect.top + elStartOffset}px`;\n this._markers.elementEnd.style.top = `${elRect.top + elEndOffset}px`;\n }\n } else {\n // No trigger element - hide element markers\n this._markers.elementStart.style.display = 'none';\n this._markers.elementEnd.style.display = 'none';\n }\n }\n\n /**\n * Remove all markers\n */\n cleanup(): void {\n if (this._markerContainer && this._markerContainer.parentNode) {\n this._markerContainer.parentNode.removeChild(this._markerContainer);\n }\n this._markerContainer = undefined;\n this._markers = undefined;\n this._triggerElement = undefined;\n }\n}\n",
|
|
21
|
+
"/**\n * ScrollTrigger\n *\n * Triggers timelines based on scroll position:\n * - start/end parameters define trigger zone (GSAP-style)\n * - scrub links animation progress to scroll position\n * - scrub: number adds smooth lag (in seconds)\n * - scroller: custom scroll container (default: window)\n * - pin: fix element in place during animation\n * - markers shows debug markers (optional)\n *\n * Pin implementation:\n * - Window scroller: uses position:fixed (seamless, no jitter)\n * - Custom scroller: uses transform (slight lag unavoidable)\n *\n * Start/End format: \"<element_position> <scroller_position>\"\n * - Keywords: top, center, bottom\n * - Units: 100px, 50%, 20vh\n * - Relative: +=500px (for end only)\n */\n\nimport type { ScrollConfig } from '../types';\nimport type { Timeline } from '../core/Timeline';\nimport { Ticker, runBatched } from '../core/Ticker';\nimport { PinManager } from './PinManager';\nimport { MarkerManager } from './MarkerManager';\nimport { parseScrollPosition } from '../utils/ScrollPositionParser';\nimport { executeTimelineAction } from '../utils/executeTimelineAction';\nimport { getLayoutRect } from '../utils/getLayoutRect';\nimport type { TimelineAction } from '../utils/executeTimelineAction';\nimport { TriggerManager } from './TriggerManager';\nimport { BaseTrigger } from './BaseTrigger';\n\nexport class ScrollTrigger extends BaseTrigger<ScrollConfig> {\n // Unique ID for this ScrollTrigger instance\n private static _nextId: number = 0;\n private _id: number;\n\n // Priority for smooth-scrub Ticker listener.\n // Engine registers at priority 0; running at 1 ensures the scrub progress update\n // executes BEFORE Engine's timeline.update() in the same frame.\n private static readonly _SCRUB_TICKER_PRIORITY = 1;\n\n // Global registry of active instances for window.load refresh.\n // Images/fonts loading after DOMContentLoaded shift layout, causing incorrect\n // trigger positions. A one-time load listener refreshes all instances.\n private static _activeInstances: Set<ScrollTrigger> = new Set();\n private static _loadListenerAdded: boolean = false;\n\n private _scrollListener?: () => void;\n private _scrubTickListener: ((deltaTime: number, elapsedTime: number) => void) | null = null;\n private _currentProgress: number = 0;\n private _targetProgress: number = 0;\n private _scrubLag: number = 0;\n private _scroller!: Element | Window;\n\n // Trigger zone (calculated from start/end config)\n private _triggerStart: number = 0;\n private _triggerEnd: number = 0;\n\n // Pin manager (handles all pin state and logic)\n private _pinManager: PinManager | null = null;\n\n // Marker manager (handles debug markers)\n private _markerManager: MarkerManager | null = null;\n\n // Track trigger state for non-scrub mode (play once on enter, reverse on leave)\n private _triggerState: 'before' | 'active' | 'after' = 'before';\n private _toggleActions: [TimelineAction, TimelineAction, TimelineAction, TimelineAction] = ['play', 'reverse', 'play', 'reverse'];\n private _initializing: boolean = false;\n\n constructor(timeline: Timeline, config: ScrollConfig) {\n super(timeline, config);\n this._id = ScrollTrigger._nextId++;\n this._scrubLag = typeof config.scrub === 'number' ? config.scrub : 0;\n }\n\n /**\n * Register the global window.load listener (once).\n * After all resources finish loading, refresh every active ScrollTrigger\n * so trigger positions account for layout shifts caused by images/fonts.\n */\n private static _ensureLoadListener(): void {\n if (ScrollTrigger._loadListenerAdded || typeof window === 'undefined') return;\n ScrollTrigger._loadListenerAdded = true;\n\n // If the page already loaded, no need for a listener\n if (document.readyState === 'complete') return;\n\n window.addEventListener('load', () => {\n // Small delay to let browsers finalize layout after load\n setTimeout(() => {\n for (const instance of ScrollTrigger._activeInstances) {\n instance.refresh();\n }\n }, 100);\n }, { once: true });\n }\n\n private _resolveScroller(): void {\n if (this._config.scroller) {\n if (typeof this._config.scroller === 'string') {\n if (typeof document === 'undefined') {\n this._scroller = window;\n return;\n }\n try {\n const el = document.querySelector(this._config.scroller);\n this._scroller = el ?? window;\n } catch (e) {\n console.warn(`[Motion] Invalid scroller selector \"${this._config.scroller}\":`, e);\n this._scroller = window;\n }\n } else {\n this._scroller = this._config.scroller;\n }\n } else {\n this._scroller = window;\n }\n }\n\n private _resolvePinElement(): HTMLElement | null {\n if (!this._config.pin || typeof document === 'undefined') return null;\n\n if (typeof this._config.pin === 'string') {\n return document.querySelector(this._config.pin);\n } else if (this._config.pin === true) {\n const firstAnimation = this._timeline.getFirstChildAnimation();\n if (firstAnimation) {\n const targets = firstAnimation.getTargets();\n if (targets.length > 0 && targets[0] instanceof HTMLElement) {\n return targets[0];\n }\n }\n }\n return null;\n }\n\n /**\n * Default start position — matches GSAP behavior:\n * - With pin: 'top top' (element top hits viewport top)\n * - Without pin: 'top bottom' (element top hits viewport bottom)\n */\n private _defaultStart(): string {\n return this._config.start || (this._config.pin ? 'top top' : 'top bottom');\n }\n\n /**\n * Default end position — matches GSAP behavior:\n * 'bottom top' (element bottom hits viewport top)\n */\n private _defaultEnd(): string {\n return this._config.end || 'bottom top';\n }\n\n private _getFirstAnimatedElement(): HTMLElement | null {\n const firstAnimation = this._timeline.getFirstChildAnimation();\n if (firstAnimation) {\n const targets = firstAnimation.getTargets();\n if (targets.length > 0 && targets[0] instanceof HTMLElement) {\n return targets[0];\n }\n }\n return null;\n }\n\n /**\n * Resolve the trigger element from config or first animation\n */\n private _resolveTriggerElement(): HTMLElement | null {\n // If target is an Element, use it directly\n if (this._config.target instanceof HTMLElement) {\n return this._config.target;\n }\n\n // If target is a string selector, query for it\n if (typeof this._config.target === 'string' && typeof document !== 'undefined') {\n const el = document.querySelector(this._config.target);\n if (el instanceof HTMLElement) {\n return el;\n }\n }\n\n // Fall back to first animated element\n return this._getFirstAnimatedElement();\n }\n\n /**\n * Calculate trigger start/end scroll positions based on config.\n *\n * Pin distance equals the trigger range (triggerEnd - triggerStart).\n * The PinManager spacer adds exactly that many pixels of scroll room to the\n * page, so the animation always plays over the full configured distance while\n * the element is pinned — no separate \"extension\" step is needed.\n *\n * When refresh() is called while the element is position:fixed (pinned), we\n * use the spacer's rect instead of the element's rect. A position:fixed\n * element returns viewport-relative coordinates from getBoundingClientRect(),\n * which would produce incorrect document-relative trigger positions. The\n * spacer sits in the element's original place in the document flow, so its\n * rect always reflects the correct base for the calculation.\n */\n private _calculateTriggerPositions(pinElement: HTMLElement | null): void {\n const el = pinElement || this._resolveTriggerElement();\n const scrollerHeight = this._getViewportHeight();\n const scrollTop = this._getScrollTop();\n\n if (!el) {\n this._triggerStart = 0;\n this._triggerEnd = this._getScrollHeight();\n return;\n }\n\n // Prefer the spacer's rect when it is in the DOM: the pinned element is\n // position:fixed and its rect is viewport-relative, not document-relative.\n // For non-pinned elements, strip inline transforms before measuring so the\n // trigger position reflects the element's layout position, not its animated\n // (post-transform) position.\n const elRect = this._pinManager?.getLayoutRect() ?? getLayoutRect(el);\n\n let elTopInDocument: number;\n if (this._scroller instanceof Element) {\n const scrollerRect = this._scroller.getBoundingClientRect();\n elTopInDocument = elRect.top - scrollerRect.top + scrollTop;\n } else {\n elTopInDocument = elRect.top + scrollTop;\n }\n\n // Parse start: \"element_pos scroller_pos\"\n const start = this._defaultStart();\n const startParts = start.split(' ');\n const elStartPos = startParts[0] || 'top';\n const scrollerStartPos = startParts[1] || 'top';\n\n const elStartOffset = parseScrollPosition(elStartPos, elRect.height, scrollerHeight);\n const scrollerStartOffset = parseScrollPosition(scrollerStartPos, scrollerHeight, scrollerHeight);\n\n // Trigger starts when element position reaches scroller position\n this._triggerStart = elTopInDocument + elStartOffset - scrollerStartOffset;\n\n // Parse end\n const end = this._defaultEnd();\n\n if (end.startsWith('+=')) {\n const offset = parseScrollPosition(end.slice(2), scrollerHeight, scrollerHeight);\n this._triggerEnd = this._triggerStart + offset;\n } else {\n const endParts = end.split(' ');\n const elEndPos = endParts[0] || 'bottom';\n const scrollerEndPos = endParts[1] || 'top';\n\n const elEndOffset = parseScrollPosition(elEndPos, elRect.height, scrollerHeight);\n const scrollerEndOffset = parseScrollPosition(scrollerEndPos, scrollerHeight, scrollerHeight);\n\n this._triggerEnd = elTopInDocument + elEndOffset - scrollerEndOffset;\n }\n\n if (this._triggerEnd <= this._triggerStart) {\n this._triggerEnd = this._triggerStart + Math.max(scrollerHeight, 100);\n }\n }\n\n protected _onEnable(): void {\n this._resolveScroller();\n const pinElement = this._resolvePinElement();\n this._calculateTriggerPositions(pinElement);\n\n // Register for window.load refresh and add resize listener\n ScrollTrigger._activeInstances.add(this);\n ScrollTrigger._ensureLoadListener();\n\n // Parse toggleActions string into a 4-tuple (only relevant for non-scrub mode)\n const actionsStr = this._config.toggleActions || 'play reverse play reverse';\n const parts = actionsStr.trim().split(/\\s+/);\n this._toggleActions = [\n (parts[0] || 'play') as TimelineAction,\n (parts[1] || 'reverse') as TimelineAction,\n (parts[2] || 'play') as TimelineAction,\n (parts[3] || 'reverse') as TimelineAction,\n ];\n\n if (pinElement) {\n // Calculate fixed-pin top position from start config.\n // scrollerStartOffset = viewport Y where the element's reference point was\n // elStartOffset = distance from element's top edge to reference point\n // fixedTop = viewport Y for element's top edge = scrollerStartOffset - elStartOffset\n const start = this._defaultStart();\n const startParts = start.split(' ');\n const viewportHeight = this._getViewportHeight();\n const elStartOffset = parseScrollPosition(startParts[0] || 'top', pinElement.getBoundingClientRect().height, viewportHeight);\n const scrollerStartOffset = parseScrollPosition(startParts[1] || 'top', viewportHeight, viewportHeight);\n const fixedTop = scrollerStartOffset - elStartOffset;\n\n // Pin zone deliberately equals the animation trigger zone:\n // pinStart = _triggerStart, pinEnd = _triggerEnd\n // pinDistance = _triggerEnd - _triggerStart\n //\n // The spacer adds `pinDistance` of scroll room to the page so the\n // animation plays over the full configured distance while the element\n // is fixed — this is the GSAP-compatible behaviour (the spacer IS the\n // \"auto-extension\" of the scrollable range). No separate adjustment to\n // _triggerEnd is required.\n // Use the shared registry to get-or-create the PinManager for this element.\n // This prevents multiple ScrollTrigger instances (e.g. from each:true) from\n // creating nested spacers around the same DOM element.\n this._pinManager = PinManager.getOrCreate(this._id, pinElement);\n this._pinManager.setup(\n pinElement,\n this._triggerStart,\n this._triggerEnd,\n fixedTop,\n this._scroller === window,\n this._config.pinSpacing\n );\n }\n\n if (this._config.markers && typeof document !== 'undefined') {\n const triggerElement = this._pinManager?.getElement() || this._resolveTriggerElement();\n this._markerManager = new MarkerManager(this._id);\n this._markerManager.setup({\n scroller: this._scroller,\n triggerElement,\n startConfig: this._defaultStart(),\n endConfig: this._defaultEnd(),\n markerConfig: this._config.markers,\n viewportHeight: this._getViewportHeight()\n });\n }\n\n this._scrollListener = () => this._onScroll();\n if (this._scroller) {\n this._scroller.addEventListener('scroll', this._scrollListener, { passive: true });\n // Set initial state based on current scroll position without firing actions\n this._initializing = true;\n this._onScroll();\n this._initializing = false;\n\n // Fire initial actions if page loaded mid-scroll (non-scrub mode only).\n // The _initializing pass above synced _triggerState but deliberately skipped\n // actions. Now that state is settled, fire the correct entry actions so\n // elements that are already in (or past) the trigger zone animate immediately.\n if (this._config.scrub === false || this._config.scrub === undefined) {\n if (this._triggerState === 'active') {\n // Page loaded inside trigger zone — fire onEnter\n executeTimelineAction(this._toggleActions[0], this._timeline);\n } else if (this._triggerState === 'after') {\n // Page loaded past trigger zone — show completed state silently.\n // Don't fire toggle actions here because play() + reverse() at time 0\n // leaves the timeline stuck at progress=0 (reverse can't go below 0).\n // Instead, just seek to the end state so elements appear as if the\n // animation already completed naturally.\n this._timeline.progress(1);\n this._timeline.pause();\n }\n }\n }\n }\n\n protected _onDisable(): void {\n if (this._scrollListener && this._scroller) {\n this._scroller.removeEventListener('scroll', this._scrollListener);\n }\n\n this._markerManager?.cleanup();\n this._markerManager = null;\n\n // Release this trigger's share of the pinned element's PinManager.\n // cleanup() only runs when the last user releases (shared registry).\n if (this._pinManager) {\n const pinElement = this._pinManager.getElement();\n if (pinElement) {\n PinManager.release(this._id, pinElement);\n } else {\n this._pinManager.cleanup();\n }\n this._pinManager = null;\n }\n\n this._stopSmoothScrollLoop();\n\n // Unregister from global refresh\n ScrollTrigger._activeInstances.delete(this);\n }\n\n /**\n * Refresh trigger positions (call after layout changes like resize)\n */\n refresh(): void {\n if (!this._enabled) return;\n\n const pinElement = this._pinManager?.getElement() || null;\n this._calculateTriggerPositions(pinElement);\n\n // Update pin manager with new positions\n if (this._pinManager && pinElement) {\n const start = this._defaultStart();\n const startParts = start.split(' ');\n const viewportHeight = this._getViewportHeight();\n // After Bug 1 fix, getLayoutRect()?.height returns the element's natural height\n // (not the spacer's inflated height that includes padding-bottom = pinDistance).\n const elHeight = this._pinManager.getLayoutRect()?.height ?? pinElement.getBoundingClientRect().height;\n const elStartOffset = parseScrollPosition(startParts[0] || 'top', elHeight, viewportHeight);\n const scrollerStartOffset = parseScrollPosition(startParts[1] || 'top', viewportHeight, viewportHeight);\n const fixedTop = scrollerStartOffset - elStartOffset;\n\n this._pinManager.setup(\n pinElement,\n this._triggerStart,\n this._triggerEnd,\n fixedTop,\n this._scroller === window,\n this._config.pinSpacing\n );\n }\n\n // Update markers with new positions\n if (this._markerManager && this._config.markers) {\n const triggerElement = this._pinManager?.getElement() || this._resolveTriggerElement();\n this._markerManager.setup({\n scroller: this._scroller,\n triggerElement,\n startConfig: this._defaultStart(),\n endConfig: this._defaultEnd(),\n markerConfig: this._config.markers,\n viewportHeight: this._getViewportHeight()\n });\n }\n\n // Re-run scroll handler to update state\n this._onScroll();\n }\n private _getViewportHeight(): number {\n if (this._scroller === window) {\n return typeof window !== 'undefined' ? window.innerHeight : 0;\n } else if (this._scroller instanceof Element) {\n return this._scroller.clientHeight;\n }\n return 0;\n }\n\n private _getScrollTop(): number {\n if (this._scroller === window) {\n return typeof window !== 'undefined' ? (window.scrollY || window.pageYOffset) : 0;\n } else if (this._scroller instanceof Element) {\n return this._scroller.scrollTop;\n }\n return 0;\n }\n\n private _getScrollHeight(): number {\n if (this._scroller === window) {\n if (typeof document !== 'undefined' && document.documentElement) {\n return document.documentElement.scrollHeight - window.innerHeight;\n }\n return 0;\n } else if (this._scroller instanceof Element) {\n return this._scroller.scrollHeight - this._scroller.clientHeight;\n }\n return 0;\n }\n\n private _onScroll(): void {\n if (!this._enabled) return;\n\n const scrollTop = this._getScrollTop();\n\n // Calculate progress based on trigger zone\n const triggerRange = this._triggerEnd - this._triggerStart;\n let progress = triggerRange > 0\n ? (scrollTop - this._triggerStart) / triggerRange\n : 0;\n progress = Math.max(0, Math.min(1, progress));\n\n // Apply snap (before assigning to _targetProgress)\n progress = this._applySnap(progress);\n\n this._targetProgress = progress;\n\n // Handle pin\n this._pinManager?.update(scrollTop);\n\n // Update all markers\n if (this._markerManager) {\n const triggerElement = this._pinManager?.getElement() || this._resolveTriggerElement();\n this._markerManager.update(scrollTop, triggerElement, this._getViewportHeight());\n }\n\n // Handle scrub/play\n if (this._config.scrub !== false && this._config.scrub !== undefined) {\n if (this._scrubLag === 0) {\n // Instant scrub: apply progress synchronously.\n // runBatched batches per-property writes into one DOM write per element.\n this._currentProgress = this._targetProgress;\n runBatched(() => this._timeline.progress(this._currentProgress));\n } else {\n this._startSmoothScrollLoop();\n }\n } else {\n // Non-scrub mode: play once on enter, reverse on leave\n // Determine current state\n let newState: 'before' | 'active' | 'after';\n if (progress <= 0) {\n newState = 'before';\n } else if (progress >= 1) {\n newState = 'after';\n } else {\n newState = 'active';\n }\n\n // Handle state transitions\n if (newState !== this._triggerState) {\n const prevState = this._triggerState;\n this._triggerState = newState;\n\n // Don't fire transition actions during initialization — just sync state\n if (this._initializing) return;\n\n // Direction-aware transitions for toggleActions\n // Format: [onEnter, onLeave, onEnterBack, onLeaveBack]\n // Wrapped in runBatched to batch per-property DOM writes\n // (fast-scroll paths call progress() which writes to DOM)\n runBatched(() => {\n if (newState === 'active' && prevState === 'before') {\n // onEnter — scrolling DOWN into trigger zone\n executeTimelineAction(this._toggleActions[0], this._timeline);\n } else if (newState === 'after' && prevState === 'active') {\n // onLeave — scrolling DOWN past trigger zone\n executeTimelineAction(this._toggleActions[1], this._timeline);\n } else if (newState === 'active' && prevState === 'after') {\n // onEnterBack — scrolling UP back into trigger zone\n executeTimelineAction(this._toggleActions[2], this._timeline);\n } else if (newState === 'before' && prevState === 'active') {\n // onLeaveBack — scrolling UP past trigger zone\n executeTimelineAction(this._toggleActions[3], this._timeline);\n } else if (newState === 'after' && prevState === 'before') {\n // Fast-scroll forward: fire onEnter then onLeave\n executeTimelineAction(this._toggleActions[0], this._timeline);\n // Ensure timeline reaches completion before onLeave fires\n this._timeline.progress(1);\n executeTimelineAction(this._toggleActions[1], this._timeline);\n } else if (newState === 'before' && prevState === 'after') {\n // Fast-scroll backward: fire onEnterBack then onLeaveBack\n executeTimelineAction(this._toggleActions[2], this._timeline);\n // Ensure timeline reaches start before onLeaveBack fires\n this._timeline.progress(0);\n executeTimelineAction(this._toggleActions[3], this._timeline);\n }\n });\n }\n }\n }\n\n /**\n * Apply snap to a raw progress value based on config.snap.\n * - number: snap to evenly-spaced increments (e.g. 0.25 → 0, 0.25, 0.5, 0.75, 1)\n * - number[]: snap to nearest value in array\n * - function: custom snap function\n */\n private _applySnap(progress: number): number {\n const snap = this._config.snap;\n if (snap == null) return progress;\n\n if (typeof snap === 'function') {\n return snap(progress);\n }\n\n if (Array.isArray(snap)) {\n // Find nearest value in array\n let closest = snap[0] ?? progress;\n let minDist = Math.abs(progress - closest);\n for (let i = 1; i < snap.length; i++) {\n const dist = Math.abs(progress - snap[i]!);\n if (dist < minDist) {\n minDist = dist;\n closest = snap[i]!;\n }\n }\n return closest;\n }\n\n if (typeof snap === 'number' && snap > 0) {\n // Snap to evenly-spaced increments\n return Math.round(progress / snap) * snap;\n }\n\n return progress;\n }\n\n private _startSmoothScrollLoop(): void {\n if (this._scrubTickListener) return; // idempotent — listener already registered\n\n this._scrubTickListener = (deltaTime: number, _elapsedTime: number) => {\n if (!this._enabled) {\n this._stopSmoothScrollLoop();\n return;\n }\n\n const diff = this._targetProgress - this._currentProgress;\n\n // Frame-rate independent exponential smoothing:\n // exp(-3) ≈ 0.05, so after scrubLag seconds ~95% of distance is covered\n const catchUp = 1 - Math.exp((-deltaTime * 3) / this._scrubLag);\n this._currentProgress += diff * catchUp;\n\n // Snap to target when close enough and unregister the listener\n if (Math.abs(this._targetProgress - this._currentProgress) < 0.0001) {\n this._currentProgress = this._targetProgress;\n this._timeline.progress(this._currentProgress);\n this._stopSmoothScrollLoop();\n return;\n }\n\n this._timeline.progress(this._currentProgress);\n };\n\n Ticker.getInstance().add(this._scrubTickListener, ScrollTrigger._SCRUB_TICKER_PRIORITY);\n }\n\n private _stopSmoothScrollLoop(): void {\n if (this._scrubTickListener) {\n Ticker.getInstance().remove(this._scrubTickListener);\n this._scrubTickListener = null;\n }\n }\n}\n\n// Register with TriggerManager\nTriggerManager.registerTrigger('scroll', ScrollTrigger);\n",
|
|
22
|
+
"/**\n * PageLoadTrigger\n *\n * Triggers timelines based on page load events:\n * - timing: 'before' - plays immediately (before DOMContentLoaded)\n * - timing: 'during' - plays on DOMContentLoaded\n * - timing: 'after' - plays on window load event\n */\n\nimport type { Timeline } from '../core/Timeline';\nimport type { PageLoadTriggerConfig } from './TriggerManager';\nimport { TriggerManager } from './TriggerManager';\n\ninterface PageLoadRegistration {\n timeline: Timeline;\n timing: 'before' | 'during' | 'after';\n}\n\nexport class PageLoadTrigger {\n private _registrations: PageLoadRegistration[] = [];\n private _domContentLoaded: boolean = false;\n private _windowLoaded: boolean = false;\n private _initialized: boolean = false;\n\n constructor() {\n this._initialize();\n }\n\n private _initialize(): void {\n if (this._initialized) return;\n this._initialized = true;\n\n // Check if already loaded\n if (typeof document !== 'undefined') {\n // Always add listeners in case document is still loading\n document.addEventListener('DOMContentLoaded', () => {\n this._domContentLoaded = true;\n this._triggerDuring();\n });\n\n if (typeof window !== 'undefined') {\n window.addEventListener('load', () => {\n this._windowLoaded = true;\n this._triggerAfter();\n });\n }\n\n // Check current state\n if (document.readyState === 'interactive' || document.readyState === 'complete') {\n // DOMContentLoaded already fired\n this._domContentLoaded = true;\n }\n\n if (document.readyState === 'complete') {\n // Everything already loaded\n this._windowLoaded = true;\n }\n }\n }\n\n /**\n * Register a timeline with pageLoad trigger\n */\n register(timeline: Timeline, config: PageLoadTriggerConfig): void {\n const timing = config.timing || 'during';\n\n this._registrations.push({\n timeline,\n timing,\n });\n\n // If timing is 'before' or events already fired, play immediately\n if (timing === 'before') {\n timeline.play();\n } else if (timing === 'during' && this._domContentLoaded) {\n timeline.play();\n } else if (timing === 'after' && this._windowLoaded) {\n timeline.play();\n }\n }\n\n /**\n * Unregister a timeline from page load trigger\n */\n unregister(timeline: Timeline): void {\n this._registrations = this._registrations.filter(reg => reg.timeline !== timeline);\n }\n\n /**\n * Clear all registrations\n */\n clear(): void {\n this._registrations = [];\n }\n\n private _triggerDuring(): void {\n this._registrations\n .filter(reg => reg.timing === 'during')\n .forEach(reg => reg.timeline.play());\n }\n\n private _triggerAfter(): void {\n this._registrations\n .filter(reg => reg.timing === 'after')\n .forEach(reg => reg.timeline.play());\n }\n\n /**\n * Reset for testing - DO NOT USE IN PRODUCTION\n * @internal\n */\n _reset(): void {\n this._registrations = [];\n this._domContentLoaded = false;\n this._windowLoaded = false;\n this._initialized = false;\n // Re-initialize to set up event listeners with fresh state\n this._initialize();\n }\n}\n\n// Register with TriggerManager\nTriggerManager.registerPageLoadTrigger(PageLoadTrigger);\n",
|
|
23
|
+
"/**\n * MouseMoveTrigger\n *\n * Triggers timelines based on mouse movement:\n * - distance: progress based on distance from center of viewport/target\n * - axis: progress based on X/Y position within viewport/target\n *\n * Supports:\n * - Viewport or custom target element tracking\n * - Optional smoothing (lerp interpolation)\n * - Custom start/leave progress values\n * - Per-element triggers with \"each\" mode\n */\n\nimport type { MouseMoveConfig } from '../types';\nimport type { Timeline } from '../core/Timeline';\nimport { Ticker } from '../core/Ticker';\nimport { TriggerManager } from './TriggerManager';\nimport { BaseTrigger } from './BaseTrigger';\nimport { getLayoutRect } from '../utils/getLayoutRect';\n\ninterface MouseState {\n /** Current X progress (0-1) */\n x: number;\n /** Current Y progress (0-1) */\n y: number;\n /** Current distance progress (0-1) */\n distance: number;\n /** Whether mouse is currently inside the tracking area */\n isInside: boolean;\n}\n\ninterface TargetState {\n /** Target X progress (before smoothing) */\n targetX: number;\n /** Target Y progress (before smoothing) */\n targetY: number;\n /** Target distance progress (before smoothing) */\n targetDistance: number;\n}\n\nexport class MouseMoveTrigger extends BaseTrigger<MouseMoveConfig> {\n private _targets: Element[] = [];\n private _listeners: Map<EventTarget, Map<string, EventListener>> = new Map();\n\n // Frozen rects snapshotted at rest so that progress calculations remain stable\n // while animations transform target elements.\n private _frozenRects: Map<Element, DOMRect> = new Map();\n\n // Mouse state\n private _state: MouseState = {\n x: 0.5,\n y: 0.5,\n distance: 0.5,\n isInside: false,\n };\n\n // Target state for smoothing\n private _targetState: TargetState = {\n targetX: 0.5,\n targetY: 0.5,\n targetDistance: 0.5,\n };\n\n // Ticker callback for smoothing\n private _tickerCallback?: (deltaTime: number) => void;\n\n // Defaults\n private readonly _defaultStartProgress = 0.5;\n private readonly _defaultLeaveProgress = 0.5;\n\n constructor(timeline: Timeline, config: MouseMoveConfig) {\n super(timeline, config);\n this._config = {\n type: 'distance',\n startProgress: this._defaultStartProgress,\n leaveProgress: this._defaultLeaveProgress,\n ...config,\n };\n\n // Initialize state with start progress\n const startProgress = this._config.startProgress ?? this._defaultStartProgress;\n this._state.x = startProgress;\n this._state.y = startProgress;\n this._state.distance = startProgress;\n this._targetState.targetX = startProgress;\n this._targetState.targetY = startProgress;\n this._targetState.targetDistance = startProgress;\n }\n\n /**\n * Set up mouse event listeners once DOM is ready.\n */\n protected _onEnable(): void {\n // Resolve target elements\n this._targets = this._resolveElements(this._config.target);\n\n // Add mouse event listeners\n if (this._targets.length === 0) {\n // Viewport mode\n this._addViewportListeners();\n } else {\n // Target element mode\n this._addTargetListeners();\n }\n\n // Start ticker for smoothing if enabled\n if (this._config.smooth !== undefined && this._config.smooth > 0) {\n this._startSmoothingTicker();\n }\n\n // Set initial progress\n this._updateTimelineProgress();\n }\n\n /**\n * Remove all event listeners and stop the smoothing ticker.\n */\n protected _onDisable(): void {\n // Stop smoothing ticker\n if (this._tickerCallback) {\n Ticker.getInstance().remove(this._tickerCallback);\n this._tickerCallback = undefined;\n }\n\n // Clear frozen rects\n this._frozenRects.clear();\n\n // Remove all listeners\n this._cleanupListenerMap(this._listeners);\n }\n\n private _addViewportListeners(): void {\n if (typeof window === 'undefined') return;\n\n const listeners = new Map<string, EventListener>();\n\n // Mouse move on window\n const mousemoveListener = (e: Event) => {\n const mouseEvent = e as MouseEvent;\n this._handleViewportMouseMove(mouseEvent.clientX, mouseEvent.clientY);\n };\n window.addEventListener('mousemove', mousemoveListener);\n listeners.set('mousemove', mousemoveListener);\n\n // For viewport mode, mouse is always \"inside\"\n this._state.isInside = true;\n\n this._listeners.set(window, listeners);\n }\n\n /**\n * Re-snapshot frozen rects after layout changes (e.g. window resize).\n * Called by TriggerManager on resize.\n */\n refresh(): void {\n for (const element of this._targets) {\n this._frozenRects.set(element, getLayoutRect(element));\n }\n }\n\n private _addTargetListeners(): void {\n this._targets.forEach(element => {\n // Snapshot bounds at rest position so progress calculations are stable\n // even while the element is being animated (e.g. a translateY hover animation).\n this._frozenRects.set(element, getLayoutRect(element));\n\n const elementListeners = new Map<string, EventListener>();\n\n // Mouse move\n const mousemoveListener = (e: Event) => {\n const mouseEvent = e as MouseEvent;\n this._handleTargetMouseMove(element, mouseEvent.clientX, mouseEvent.clientY);\n };\n element.addEventListener('mousemove', mousemoveListener);\n elementListeners.set('mousemove', mousemoveListener);\n\n // Mouse enter\n const mouseenterListener = () => {\n this._state.isInside = true;\n };\n element.addEventListener('mouseenter', mouseenterListener);\n elementListeners.set('mouseenter', mouseenterListener);\n\n // Mouse leave\n const mouseleaveListener = () => {\n this._state.isInside = false;\n this._handleMouseLeave();\n };\n element.addEventListener('mouseleave', mouseleaveListener);\n elementListeners.set('mouseleave', mouseleaveListener);\n\n this._listeners.set(element, elementListeners);\n });\n }\n\n private _handleViewportMouseMove(clientX: number, clientY: number): void {\n if (typeof window === 'undefined') return;\n\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const centerX = viewportWidth / 2;\n const centerY = viewportHeight / 2;\n\n if (this._config.type === 'axis') {\n // Axis mode: map position to 0-1 range\n // Left/top = 0, Right/bottom = 1\n this._targetState.targetX = clientX / viewportWidth;\n this._targetState.targetY = clientY / viewportHeight;\n } else {\n // Distance mode: calculate distance from center\n const dx = clientX - centerX;\n const dy = clientY - centerY;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n // Calculate max distance (to farthest corner)\n const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);\n\n // Map distance to progress: closer = 1 (end), farther = 0 (start)\n this._targetState.targetDistance = 1 - Math.min(distance / maxDistance, 1);\n }\n\n this._applyProgress();\n }\n\n private _handleTargetMouseMove(element: Element, clientX: number, clientY: number): void {\n // Use the frozen layout rect captured at setup (or last refresh) rather than\n // a live getBoundingClientRect() call. This keeps progress calculations\n // anchored to the element's rest position, unaffected by animation transforms.\n const rect = this._frozenRects.get(element) ?? element.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n const centerY = rect.top + rect.height / 2;\n\n if (this._config.type === 'axis') {\n // Axis mode: map position within element to 0-1 range\n const relativeX = clientX - rect.left;\n const relativeY = clientY - rect.top;\n this._targetState.targetX = Math.max(0, Math.min(1, relativeX / rect.width));\n this._targetState.targetY = Math.max(0, Math.min(1, relativeY / rect.height));\n } else {\n // Distance mode: calculate distance from element center\n const dx = clientX - centerX;\n const dy = clientY - centerY;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n // Calculate max distance (to farthest corner of element)\n const maxDistance = Math.sqrt(\n Math.pow(rect.width / 2, 2) + Math.pow(rect.height / 2, 2)\n );\n\n // Map distance to progress: closer = 1 (end), farther = 0 (start)\n this._targetState.targetDistance = 1 - Math.min(distance / maxDistance, 1);\n }\n\n this._applyProgress();\n }\n\n private _handleMouseLeave(): void {\n const leaveProgress = this._config.leaveProgress ?? this._defaultLeaveProgress;\n\n // Set target to leave progress\n this._targetState.targetX = leaveProgress;\n this._targetState.targetY = leaveProgress;\n this._targetState.targetDistance = leaveProgress;\n\n // If no smoothing, apply immediately\n if (!this._config.smooth) {\n this._state.x = leaveProgress;\n this._state.y = leaveProgress;\n this._state.distance = leaveProgress;\n this._updateTimelineProgress();\n }\n // With smoothing, the ticker will handle the transition\n }\n\n private _applyProgress(): void {\n if (this._config.smooth) {\n // Smoothing enabled - ticker handles interpolation\n return;\n }\n\n // No smoothing - apply directly\n this._state.x = this._targetState.targetX;\n this._state.y = this._targetState.targetY;\n this._state.distance = this._targetState.targetDistance;\n this._updateTimelineProgress();\n }\n\n private _startSmoothingTicker(): void {\n // Clamp smooth to valid range (0-1)\n const smooth = Math.max(0, Math.min(1, this._config.smooth ?? 0.1));\n\n this._tickerCallback = (deltaTime: number) => {\n // Lerp current values toward target (frame-rate independent)\n const newX = this._lerp(this._state.x, this._targetState.targetX, smooth, deltaTime);\n const newY = this._lerp(this._state.y, this._targetState.targetY, smooth, deltaTime);\n const newDistance = this._lerp(this._state.distance, this._targetState.targetDistance, smooth, deltaTime);\n\n // Check if values have converged (within epsilon) - skip update if idle\n const epsilon = 0.0001;\n const xConverged = Math.abs(newX - this._state.x) < epsilon && Math.abs(newX - this._targetState.targetX) < epsilon;\n const yConverged = Math.abs(newY - this._state.y) < epsilon && Math.abs(newY - this._targetState.targetY) < epsilon;\n const distConverged = Math.abs(newDistance - this._state.distance) < epsilon && Math.abs(newDistance - this._targetState.targetDistance) < epsilon;\n\n // Update state\n this._state.x = newX;\n this._state.y = newY;\n this._state.distance = newDistance;\n\n // Only update timeline if values actually changed\n if (!xConverged || !yConverged || !distConverged) {\n this._updateTimelineProgress();\n }\n };\n\n Ticker.getInstance().add(this._tickerCallback);\n }\n\n private _lerp(current: number, target: number, smooth: number, deltaTime: number): number {\n // Lerp with small epsilon check to avoid infinite tiny updates\n const diff = target - current;\n if (Math.abs(diff) < 0.0001) {\n return target;\n }\n // Frame-rate independent smoothing using exponential decay\n // smooth is inverted: 0 = instant/fast, 1 = maximum smoothing (slowest)\n // Maps smooth 0→1 to internal lerp factor ~0.5→0.005\n const lerpFactor = 0.5 * Math.pow(1 - smooth, 2) + 0.005;\n const decay = 1 - Math.pow(1 - lerpFactor, deltaTime * 60);\n return current + diff * decay;\n }\n\n private _updateTimelineProgress(): void {\n if (this._config.type === 'axis') {\n // Axis mode: set progress per axis\n this._timeline.setAxisProgress('x', this._state.x);\n this._timeline.setAxisProgress('y', this._state.y);\n } else {\n // Distance mode: set overall progress\n this._timeline.progress(this._state.distance);\n }\n }\n}\n\n// Register with TriggerManager\nTriggerManager.registerTrigger('mouseMove', MouseMoveTrigger);\n",
|
|
24
|
+
"/**\n * GestureTrigger\n *\n * Detects user gestures (pointer, touch, wheel, scroll) and triggers\n * timeline actions based on directional movement and interaction events.\n *\n * Performance optimizations:\n * - RAF synchronization for callback execution\n * - Throttled scroll/wheel event processing\n * - Cached config values\n * - Minimal allocations in hot paths\n */\n\nimport type { GestureConfig, GestureEvent, GestureAction } from '../types';\nimport type { Timeline } from '../core/Timeline';\nimport { executeTimelineAction } from '../utils/executeTimelineAction';\nimport type { TimelineAction } from '../utils/executeTimelineAction';\nimport { TriggerManager } from './TriggerManager';\nimport { BaseTrigger } from './BaseTrigger';\nimport { getLayoutRect } from '../utils/getLayoutRect';\n\n/** Local typed view of GestureConfig with properly-typed internal fields for each-mode. */\ninterface GestureConfigTyped extends GestureConfig {\n _siblings?: Timeline[];\n _index?: number;\n}\n\nexport class GestureTrigger extends BaseTrigger<GestureConfig> {\n private _target: EventTarget = window;\n\n // Cached config values (avoid repeated lookups in hot paths)\n private _tolerance: number;\n private _dragMinimum: number;\n private _dragMinimumSquared: number;\n private _wheelSpeed: number;\n private _scrollSpeed: number;\n private _stopDelay: number;\n private _preventDefault: boolean;\n private _lockAxis: boolean;\n private _animationStep: number | Partial<Record<string, number>>;\n private _smooth: number;\n\n // Gesture state\n private _startX: number = 0;\n private _startY: number = 0;\n private _deltaX: number = 0;\n private _deltaY: number = 0;\n private _lockedAxis: 'x' | 'y' | null = null;\n private _isPressed: boolean = false;\n private _isDragging: boolean = false;\n private _lastDirectionX: 1 | -1 | null = null;\n private _lastDirectionY: 1 | -1 | null = null;\n\n // Scroll tracking\n private _lastScrollX: number = 0;\n private _lastScrollY: number = 0;\n\n // Stop detection (timestamp-based to reduce timeout churn)\n private _stopTimeout: ReturnType<typeof setTimeout> | null = null;\n private _isMoving: boolean = false;\n private _lastActivityTime: number = 0;\n\n // RAF synchronization for actions\n private _pendingActions: Map<GestureAction, GestureEvent> = new Map();\n private _rafScheduled: boolean = false;\n private _rafId: number | null = null;\n\n // Wheel/scroll throttling\n private _wheelAccumulatorX: number = 0;\n private _wheelAccumulatorY: number = 0;\n private _wheelRafId: number | null = null;\n private _scrollRafId: number | null = null;\n\n // Pointer move tracking for early exit\n private _lastPointerX: number = 0;\n private _lastPointerY: number = 0;\n\n // Tracks which directions fired during the current gesture (for Complete events)\n private _activatedDirections: Set<string> = new Set();\n\n // Smooth progress interpolation\n private _targetProgress: number = 0;\n private _currentProgress: number = 0;\n private _interpolationRafId: number | null = null;\n\n // Sequence control for each mode (playNext/playPrevious)\n private _siblings: Timeline[] | null = null;\n private _index: number = 0;\n // Shared state object for tracking last played index across siblings\n // All siblings in the same group share the same object reference\n private static _sequenceStates: WeakMap<Timeline[], { lastIndex: number }> = new WeakMap();\n\n // Bound event handlers (for cleanup)\n private _boundHandlers: {\n pointerDown?: (e: PointerEvent) => void;\n pointerMove?: (e: PointerEvent) => void;\n pointerUp?: (e: PointerEvent) => void;\n touchStart?: (e: TouchEvent) => void;\n touchMove?: (e: TouchEvent) => void;\n touchEnd?: (e: TouchEvent) => void;\n wheel?: (e: WheelEvent) => void;\n scroll?: () => void;\n } = {};\n\n // Hover frozen-rect state — bounds are snapshotted at rest so that\n // translateY animations don't cause infinite mouseenter/mouseleave jitter.\n private _frozenHoverRect: DOMRect | null = null;\n private _gestureHoverActive: boolean = false;\n private _boundHoverMoveHandler: ((e: MouseEvent) => void) | null = null;\n\n constructor(timeline: Timeline, config: GestureConfig) {\n super(timeline, config);\n\n // Cache config values for hot path performance\n this._tolerance = config.tolerance ?? 1;\n this._dragMinimum = config.dragMinimum ?? 10;\n this._dragMinimumSquared = this._dragMinimum * this._dragMinimum;\n this._wheelSpeed = config.wheelSpeed ?? 1;\n this._scrollSpeed = config.scrollSpeed ?? 1;\n this._stopDelay = config.stopDelay ?? 150;\n this._preventDefault = config.preventDefault ?? false;\n this._lockAxis = config.lockAxis ?? false;\n this._animationStep = config.animationStep ?? 0.1;\n this._smooth = Math.max(0, Math.min(1, config.smooth ?? 0));\n\n // Set up sequence control for each mode\n const typedConfig = config as GestureConfigTyped;\n if (typedConfig._siblings && typedConfig._index !== undefined) {\n this._siblings = typedConfig._siblings;\n this._index = typedConfig._index;\n\n // Initialize shared sequence state if not exists\n if (!GestureTrigger._sequenceStates.has(this._siblings)) {\n GestureTrigger._sequenceStates.set(this._siblings, { lastIndex: 0 });\n }\n }\n }\n\n /**\n * Resolve target and attach gesture listeners once DOM is ready.\n */\n protected _onEnable(): void {\n // Resolve target (falls back to window if not configured or not found)\n const resolved = this._resolveElement(this._config.target, window);\n this._target = resolved ?? window;\n\n // Attach listeners based on enabled types\n const types = this._config.types;\n\n if (types.includes('pointer')) {\n this._addPointerListeners();\n }\n\n if (types.includes('touch')) {\n this._addTouchListeners();\n }\n\n if (types.includes('wheel')) {\n this._addWheelListener();\n }\n\n if (types.includes('scroll')) {\n this._addScrollListener();\n }\n\n // Always add hover listeners if Hover or HoverEnd events exist\n if (this._config.events.Hover || this._config.events.HoverEnd) {\n this._addHoverListeners();\n }\n }\n\n /**\n * Cancel all pending timers/RAF and remove all event listeners.\n */\n protected _onDisable(): void {\n // Clear stop timeout\n if (this._stopTimeout) {\n clearTimeout(this._stopTimeout);\n this._stopTimeout = null;\n }\n\n // Cancel any pending RAF\n if (this._rafId !== null) {\n cancelAnimationFrame(this._rafId);\n this._rafId = null;\n }\n if (this._wheelRafId !== null) {\n cancelAnimationFrame(this._wheelRafId);\n this._wheelRafId = null;\n }\n if (this._scrollRafId !== null) {\n cancelAnimationFrame(this._scrollRafId);\n this._scrollRafId = null;\n }\n if (this._interpolationRafId !== null) {\n cancelAnimationFrame(this._interpolationRafId);\n this._interpolationRafId = null;\n }\n this._rafScheduled = false;\n this._pendingActions.clear();\n\n // Remove all listeners\n const target = this._target;\n\n if (this._boundHandlers.pointerDown) {\n target.removeEventListener('pointerdown', this._boundHandlers.pointerDown as EventListener);\n window.removeEventListener('pointermove', this._boundHandlers.pointerMove as EventListener);\n window.removeEventListener('pointerup', this._boundHandlers.pointerUp as EventListener);\n }\n\n if (this._boundHandlers.touchStart) {\n target.removeEventListener('touchstart', this._boundHandlers.touchStart as EventListener);\n window.removeEventListener('touchmove', this._boundHandlers.touchMove as EventListener);\n window.removeEventListener('touchend', this._boundHandlers.touchEnd as EventListener);\n }\n\n if (this._boundHandlers.wheel) {\n target.removeEventListener('wheel', this._boundHandlers.wheel as EventListener);\n }\n\n if (this._boundHandlers.scroll) {\n const scrollTarget = this._target === window ? window : this._target;\n scrollTarget.removeEventListener('scroll', this._boundHandlers.scroll);\n }\n\n // Remove hover frozen-rect mousemove handler\n if (this._boundHoverMoveHandler) {\n window.removeEventListener('mousemove', this._boundHoverMoveHandler);\n this._boundHoverMoveHandler = null;\n }\n this._frozenHoverRect = null;\n this._gestureHoverActive = false;\n\n this._boundHandlers = {};\n }\n\n // ============================================\n // Pointer Events\n // ============================================\n\n private _addPointerListeners(): void {\n this._boundHandlers.pointerDown = this._handlePointerDown.bind(this);\n this._boundHandlers.pointerMove = this._handlePointerMove.bind(this);\n this._boundHandlers.pointerUp = this._handlePointerUp.bind(this);\n\n // Use passive listeners when preventDefault is not needed\n const passiveOption = { passive: !this._preventDefault };\n\n this._target.addEventListener(\n 'pointerdown',\n this._boundHandlers.pointerDown as EventListener,\n passiveOption\n );\n // Move and up on window to catch events outside target\n window.addEventListener(\n 'pointermove',\n this._boundHandlers.pointerMove as EventListener,\n passiveOption\n );\n window.addEventListener('pointerup', this._boundHandlers.pointerUp as EventListener);\n }\n\n private _handlePointerDown(e: PointerEvent): void {\n // Skip touch-originated pointer events if touch listeners are also enabled\n // to prevent double-firing on touch devices\n if (e.pointerType === 'touch' && this._config.types.includes('touch')) {\n return;\n }\n\n if (this._preventDefault) {\n e.preventDefault();\n }\n\n this._handleInputStart(e.clientX, e.clientY);\n }\n\n private _handlePointerMove(e: PointerEvent): void {\n // Skip touch-originated pointer events if touch listeners are also enabled\n if (e.pointerType === 'touch' && this._config.types.includes('touch')) {\n return;\n }\n\n if (this._handleInputMove(e.clientX, e.clientY)) {\n if (this._preventDefault) {\n e.preventDefault();\n }\n }\n }\n\n private _handlePointerUp(e: PointerEvent): void {\n // Skip touch-originated pointer events if touch listeners are also enabled\n if (e.pointerType === 'touch' && this._config.types.includes('touch')) {\n return;\n }\n\n this._handleInputEnd();\n }\n\n // ============================================\n // Touch Events\n // ============================================\n\n private _addTouchListeners(): void {\n this._boundHandlers.touchStart = this._handleTouchStart.bind(this);\n this._boundHandlers.touchMove = this._handleTouchMove.bind(this);\n this._boundHandlers.touchEnd = this._handleTouchEnd.bind(this);\n\n const passiveOption = { passive: !this._preventDefault };\n\n this._target.addEventListener(\n 'touchstart',\n this._boundHandlers.touchStart as EventListener,\n passiveOption\n );\n window.addEventListener(\n 'touchmove',\n this._boundHandlers.touchMove as EventListener,\n passiveOption\n );\n window.addEventListener('touchend', this._boundHandlers.touchEnd as EventListener);\n }\n\n private _handleTouchStart(e: TouchEvent): void {\n if (this._preventDefault) {\n e.preventDefault();\n }\n\n const touch = e.touches[0];\n if (!touch) return;\n\n this._handleInputStart(touch.clientX, touch.clientY);\n }\n\n private _handleTouchMove(e: TouchEvent): void {\n const touch = e.touches[0];\n if (!touch) return;\n\n if (this._handleInputMove(touch.clientX, touch.clientY)) {\n if (this._preventDefault) {\n e.preventDefault();\n }\n }\n }\n\n private _handleTouchEnd(_e: TouchEvent): void {\n this._handleInputEnd();\n }\n\n // ============================================\n // Shared Input Handlers (pointer/touch core logic)\n // ============================================\n\n /**\n * Shared start logic for pointer and touch input.\n */\n private _handleInputStart(x: number, y: number): void {\n this._fireEvent('PressInit');\n\n this._startX = x;\n this._startY = y;\n this._lastPointerX = x;\n this._lastPointerY = y;\n this._deltaX = 0;\n this._deltaY = 0;\n this._isPressed = true;\n this._isDragging = false;\n this._lockedAxis = null;\n\n this._fireEvent('Press');\n }\n\n /**\n * Shared move logic for pointer and touch input.\n * Returns true if movement was processed (caller should preventDefault if needed).\n */\n private _handleInputMove(x: number, y: number): boolean {\n if (!this._isPressed) return false;\n\n // Early exit for minimal movement (< 1px) to reduce processing\n const movementX = x - this._lastPointerX;\n const movementY = y - this._lastPointerY;\n if (Math.abs(movementX) < 1 && Math.abs(movementY) < 1) {\n return false;\n }\n this._lastPointerX = x;\n this._lastPointerY = y;\n\n const newDeltaX = x - this._startX;\n const newDeltaY = y - this._startY;\n\n this._updateDeltas(newDeltaX, newDeltaY);\n this._checkDragStart();\n this._checkDirectionAndFire(false);\n this._checkToggle(newDeltaX, newDeltaY);\n this._scheduleStopCheck();\n\n if (this._isDragging) {\n this._fireEvent('Drag');\n }\n\n return true;\n }\n\n /**\n * Shared end logic for pointer and touch input.\n */\n private _handleInputEnd(): void {\n if (!this._isPressed) return;\n\n if (this._isDragging) {\n this._fireEvent('DragEnd');\n }\n\n this._fireEvent('Release');\n this._fireCompleteCallbacks();\n this._fireEvent('Stop');\n\n this._resetGestureState();\n }\n\n // ============================================\n // Wheel Events (with RAF throttling)\n // ============================================\n\n private _addWheelListener(): void {\n this._boundHandlers.wheel = this._handleWheel.bind(this);\n this._target.addEventListener('wheel', this._boundHandlers.wheel as EventListener, {\n passive: !this._preventDefault,\n });\n }\n\n private _handleWheel(e: WheelEvent): void {\n if (this._preventDefault) {\n e.preventDefault();\n }\n\n // Accumulate wheel deltas for RAF processing\n this._wheelAccumulatorX += e.deltaX * this._wheelSpeed;\n this._wheelAccumulatorY += e.deltaY * this._wheelSpeed;\n\n // Throttle processing to RAF\n if (this._wheelRafId !== null) return;\n\n this._wheelRafId = requestAnimationFrame(() => {\n this._wheelRafId = null;\n\n // Use accumulated deltas\n this._deltaX = this._wheelAccumulatorX;\n this._deltaY = this._wheelAccumulatorY;\n this._wheelAccumulatorX = 0;\n this._wheelAccumulatorY = 0;\n\n this._checkDirectionAndFire(true);\n this._checkToggle(this._deltaX, this._deltaY);\n this._scheduleStopCheck();\n\n // Reset deltas after processing\n this._deltaX = 0;\n this._deltaY = 0;\n });\n }\n\n // ============================================\n // Scroll Events (with RAF throttling)\n // ============================================\n\n private _addScrollListener(): void {\n this._boundHandlers.scroll = this._handleScroll.bind(this);\n\n // Initialize scroll position\n if (this._target === window) {\n this._lastScrollX = window.scrollX;\n this._lastScrollY = window.scrollY;\n } else {\n const element = this._target as Element;\n this._lastScrollX = element.scrollLeft;\n this._lastScrollY = element.scrollTop;\n }\n\n this._target.addEventListener('scroll', this._boundHandlers.scroll, { passive: true });\n }\n\n private _handleScroll(): void {\n // Throttle processing to RAF\n if (this._scrollRafId !== null) return;\n\n this._scrollRafId = requestAnimationFrame(() => {\n this._scrollRafId = null;\n this._processScroll();\n });\n }\n\n private _processScroll(): void {\n let currentScrollX: number;\n let currentScrollY: number;\n\n if (this._target === window) {\n currentScrollX = window.scrollX;\n currentScrollY = window.scrollY;\n } else {\n const element = this._target as Element;\n currentScrollX = element.scrollLeft;\n currentScrollY = element.scrollTop;\n }\n\n const deltaX = (currentScrollX - this._lastScrollX) * this._scrollSpeed;\n const deltaY = (currentScrollY - this._lastScrollY) * this._scrollSpeed;\n\n this._lastScrollX = currentScrollX;\n this._lastScrollY = currentScrollY;\n\n // Use scroll deltas directly (instant like wheel)\n this._deltaX = deltaX;\n this._deltaY = deltaY;\n\n this._checkDirectionAndFire(true);\n this._checkToggle(deltaX, deltaY);\n this._scheduleStopCheck();\n\n // Reset deltas\n this._deltaX = 0;\n this._deltaY = 0;\n }\n\n // ============================================\n // Hover Events\n // ============================================\n\n private _addHoverListeners(): void {\n // Hover only makes sense on elements, not window\n if (this._target === window) return;\n\n const element = this._target as Element;\n\n // Snapshot element bounds at rest position using getLayoutRect.\n // This prevents jitter when animations move the element away from the cursor.\n this._frozenHoverRect = getLayoutRect(element);\n\n this._boundHoverMoveHandler = (e: MouseEvent) => {\n if (!this._frozenHoverRect) return;\n const rect = this._frozenHoverRect;\n const isOver =\n e.clientX >= rect.left && e.clientX <= rect.right &&\n e.clientY >= rect.top && e.clientY <= rect.bottom;\n\n if (isOver && !this._gestureHoverActive) {\n this._gestureHoverActive = true;\n this._fireEvent('Hover');\n } else if (!isOver && this._gestureHoverActive) {\n this._gestureHoverActive = false;\n this._fireEvent('HoverEnd');\n }\n };\n\n window.addEventListener('mousemove', this._boundHoverMoveHandler);\n }\n\n /**\n * Re-snapshot frozen hover rect after layout changes (e.g. window resize).\n * Called by TriggerManager on resize.\n */\n refresh(): void {\n if (this._target === window || !this._frozenHoverRect) return;\n this._frozenHoverRect = getLayoutRect(this._target as Element);\n }\n\n // ============================================\n // Direction Detection\n // ============================================\n\n private _updateDeltas(newDeltaX: number, newDeltaY: number): void {\n // Apply axis locking\n if (this._lockAxis && this._lockedAxis) {\n if (this._lockedAxis === 'x') {\n this._deltaX = newDeltaX;\n this._deltaY = 0;\n } else {\n this._deltaX = 0;\n this._deltaY = newDeltaY;\n }\n } else {\n this._deltaX = newDeltaX;\n this._deltaY = newDeltaY;\n }\n\n // Determine axis lock if enabled and not yet locked\n if (this._lockAxis && !this._lockedAxis) {\n const tolerance = this._tolerance;\n if (Math.abs(this._deltaX) > tolerance || Math.abs(this._deltaY) > tolerance) {\n this._lockedAxis = Math.abs(this._deltaX) > Math.abs(this._deltaY) ? 'x' : 'y';\n }\n }\n }\n\n /**\n * Check and fire directional callbacks.\n * @param instant - If true (wheel/scroll), skips axis-lock checks and direction tracking.\n * If false (pointer/touch), respects axis locking and tracks activated directions.\n */\n private _checkDirectionAndFire(instant: boolean): void {\n const tolerance = this._tolerance;\n\n // Check Y axis (up/down)\n // For continuous gestures (pointer/touch), respect axis locking\n if (instant || !this._lockedAxis || this._lockedAxis === 'y') {\n if (this._deltaY < -tolerance) {\n this._fireEvent('Up');\n if (!instant) this._activatedDirections.add('Up');\n }\n if (this._deltaY > tolerance) {\n this._fireEvent('Down');\n if (!instant) this._activatedDirections.add('Down');\n }\n }\n\n // Check X axis (left/right)\n if (instant || !this._lockedAxis || this._lockedAxis === 'x') {\n if (this._deltaX < -tolerance) {\n this._fireEvent('Left');\n if (!instant) this._activatedDirections.add('Left');\n }\n if (this._deltaX > tolerance) {\n this._fireEvent('Right');\n if (!instant) this._activatedDirections.add('Right');\n }\n }\n\n // onChange fires on any movement past tolerance\n if (Math.abs(this._deltaX) > tolerance || Math.abs(this._deltaY) > tolerance) {\n this._fireEvent('Change');\n\n if (Math.abs(this._deltaX) > tolerance) {\n this._fireEvent('ChangeX');\n }\n if (Math.abs(this._deltaY) > tolerance) {\n this._fireEvent('ChangeY');\n }\n }\n }\n\n /**\n * Check for direction toggle (reversal)\n */\n private _checkToggle(deltaX: number, deltaY: number): void {\n const tolerance = this._tolerance;\n\n // Only check ToggleX when X movement exceeds tolerance\n if (Math.abs(deltaX) >= tolerance) {\n const currentDirX = deltaX > 0 ? 1 : -1;\n if (this._lastDirectionX !== null && currentDirX !== this._lastDirectionX) {\n this._fireEvent('ToggleX');\n }\n this._lastDirectionX = currentDirX;\n }\n\n // Only check ToggleY when Y movement exceeds tolerance\n if (Math.abs(deltaY) >= tolerance) {\n const currentDirY = deltaY > 0 ? 1 : -1;\n if (this._lastDirectionY !== null && currentDirY !== this._lastDirectionY) {\n this._fireEvent('ToggleY');\n }\n this._lastDirectionY = currentDirY;\n }\n }\n\n private _checkDragStart(): void {\n if (this._isDragging) return;\n\n // Use squared distance comparison to avoid sqrt (faster)\n const distanceSquared = this._deltaX * this._deltaX + this._deltaY * this._deltaY;\n\n if (distanceSquared > this._dragMinimumSquared) {\n this._isDragging = true;\n }\n }\n\n // ============================================\n // Stop Detection\n // ============================================\n\n private _scheduleStopCheck(): void {\n this._isMoving = true;\n this._lastActivityTime = performance.now();\n\n // Only create a new timeout if one isn't already running\n // This avoids constant setTimeout/clearTimeout churn on high-frequency events\n if (this._stopTimeout) {\n return;\n }\n\n const checkStop = () => {\n const elapsed = performance.now() - this._lastActivityTime;\n if (elapsed >= this._stopDelay) {\n // Enough time has passed since last activity\n this._stopTimeout = null;\n if (this._isMoving) {\n this._isMoving = false;\n // Reset direction tracking for next gesture session\n this._lastDirectionX = null;\n this._lastDirectionY = null;\n // Only fire onStop for wheel/scroll (continuous pointer gestures end with pointerup)\n if (!this._isPressed) {\n this._fireEvent('Stop');\n }\n }\n } else {\n // Still active, schedule another check for remaining time\n this._stopTimeout = setTimeout(checkStop, this._stopDelay - elapsed);\n }\n };\n\n this._stopTimeout = setTimeout(checkStop, this._stopDelay);\n }\n\n // ============================================\n // Action Execution (with RAF batching)\n // ============================================\n\n private _fireEvent(event: GestureEvent): void {\n const action = this._config.events[event];\n if (action) {\n this._pendingActions.set(action, event);\n this._scheduleRAF();\n }\n }\n\n private _scheduleRAF(): void {\n if (this._rafScheduled) return;\n this._rafScheduled = true;\n\n this._rafId = requestAnimationFrame(() => {\n this._rafScheduled = false;\n this._rafId = null;\n\n // Execute all pending actions\n for (const [action, event] of this._pendingActions) {\n this._executeAction(action, event);\n }\n this._pendingActions.clear();\n });\n }\n\n private _getStepForEvent(event?: string): number {\n if (typeof this._animationStep === 'number') {\n return this._animationStep;\n }\n if (event && this._animationStep[event] !== undefined) {\n return this._animationStep[event]!;\n }\n const values = Object.values(this._animationStep);\n return values.length > 0 ? values[0]! : 0.1;\n }\n\n private _executeAction(action: GestureAction, event?: GestureEvent): void {\n switch (action) {\n // Common actions delegated to the shared executeTimelineAction utility\n case 'play':\n case 'pause':\n case 'reverse':\n case 'restart':\n case 'reset': // executeTimelineAction order: pause() then progress(0) — canonical\n case 'complete':\n executeTimelineAction(action as TimelineAction, this._timeline);\n break;\n\n // Gesture-specific actions handled locally\n case 'toggle':\n if (this._timeline.isActive()) {\n this._timeline.pause();\n } else {\n this._timeline.play();\n }\n break;\n case 'kill':\n this._timeline.kill();\n break;\n case 'playReverse':\n this._timeline.reverse();\n break;\n case 'progressUp':\n this._interpolateProgress(this._getStepForEvent(event));\n break;\n case 'progressDown':\n this._interpolateProgress(-this._getStepForEvent(event));\n break;\n case 'playNext':\n this._playSequenceItem(1);\n break;\n case 'playPrevious':\n this._playSequenceItem(-1);\n break;\n }\n }\n\n // ============================================\n // Sequence Control (playNext/playPrevious)\n // ============================================\n\n /**\n * Play next or previous item in sequence (for each mode)\n * @param direction 1 for next, -1 for previous\n */\n private _playSequenceItem(direction: 1 | -1): void {\n // Only works in each mode with siblings\n if (!this._siblings || this._siblings.length === 0) {\n return;\n }\n\n const state = GestureTrigger._sequenceStates.get(this._siblings);\n if (!state) return;\n\n const count = this._siblings.length;\n const currentIndex = state.lastIndex;\n\n // Calculate next index with wrapping\n const nextIndex = (currentIndex + direction + count) % count;\n\n // Reverse/reset the current item\n const currentTimeline = this._siblings[currentIndex];\n if (currentTimeline && (currentTimeline.progress() as number) > 0) {\n currentTimeline.reverse();\n }\n\n // Play the next item\n const nextTimeline = this._siblings[nextIndex];\n if (nextTimeline) {\n nextTimeline.restart();\n }\n\n // Update shared state\n state.lastIndex = nextIndex;\n }\n\n // ============================================\n // Smooth Progress Interpolation\n // ============================================\n\n /**\n * Smoothly interpolate progress by the given delta.\n * Uses lerp for continuous scrolling (responsive) with natural ease-out when stopping.\n */\n private _interpolateProgress(delta: number): void {\n const timelineProgress = this._timeline.progress() as number;\n\n // Sync from timeline if changed externally (drift detection)\n // This handles cases where timeline.progress() was set by external code\n const drift = Math.abs(timelineProgress - this._currentProgress);\n if (drift > 0.01) {\n this._currentProgress = timelineProgress;\n this._targetProgress = timelineProgress;\n }\n\n // Also sync on first call (when loop not running)\n if (this._interpolationRafId === null) {\n this._currentProgress = timelineProgress;\n this._targetProgress = timelineProgress;\n }\n\n // Update target progress (clamped)\n this._targetProgress = Math.max(0, Math.min(1, this._targetProgress + delta));\n\n // If smooth is 0, apply instantly\n if (this._smooth === 0) {\n this._currentProgress = this._targetProgress;\n this._timeline.progress(this._currentProgress);\n return;\n }\n\n // Start interpolation loop if not running\n if (this._interpolationRafId === null) {\n this._startInterpolationLoop();\n }\n }\n\n private _startInterpolationLoop(): void {\n if (this._interpolationRafId !== null) return;\n\n // Lerp factor: higher = snappier response\n // smooth 0 = instant, smooth 0.5 = 0.15, smooth 1 = 0.08\n const lerpFactor = 0.08 + (1 - this._smooth) * 0.12;\n\n const loop = () => {\n const diff = this._targetProgress - this._currentProgress;\n\n // Stop when close enough\n if (Math.abs(diff) < 0.0001) {\n this._currentProgress = this._targetProgress;\n this._timeline.progress(this._currentProgress);\n this._interpolationRafId = null;\n return;\n }\n\n // Lerp toward target - naturally smooth during continuous input,\n // naturally eases out when input stops\n this._currentProgress += diff * lerpFactor;\n this._timeline.progress(this._currentProgress);\n\n // Continue loop\n this._interpolationRafId = requestAnimationFrame(loop);\n };\n\n this._interpolationRafId = requestAnimationFrame(loop);\n }\n\n // ============================================\n // Complete Callbacks (direction gesture end)\n // ============================================\n\n /**\n * Fire directional Complete callbacks for any directions that were activated\n * during this gesture, then clear the tracking set.\n */\n private _fireCompleteCallbacks(): void {\n if (this._activatedDirections.has('Up')) this._fireEvent('UpComplete');\n if (this._activatedDirections.has('Down')) this._fireEvent('DownComplete');\n if (this._activatedDirections.has('Left')) this._fireEvent('LeftComplete');\n if (this._activatedDirections.has('Right')) this._fireEvent('RightComplete');\n this._activatedDirections.clear();\n }\n\n // ============================================\n // State Management\n // ============================================\n\n private _resetGestureState(): void {\n this._isPressed = false;\n this._isDragging = false;\n this._deltaX = 0;\n this._deltaY = 0;\n this._lockedAxis = null;\n this._lastDirectionX = null;\n this._lastDirectionY = null;\n this._activatedDirections.clear();\n }\n}\n\n// Register with TriggerManager\nTriggerManager.registerTrigger('gesture', GestureTrigger);\n",
|
|
25
|
+
"/**\n * CSSRenderer - Render CSS properties to DOM elements\n *\n * Handles:\n * - Transform properties (x, y, rotate, scale, etc.)\n * - Standard CSS properties (opacity, backgroundColor, etc.)\n * - Unit handling (px, %, deg, etc.)\n * - Auto-batching: queues during animation tick, writes immediately otherwise\n *\n * The render system automatically detects context:\n * - Inside Ticker tick → queue for batch flush at end of frame (performance)\n * - Outside tick → write immediately to DOM (responsiveness for seek/progress/etc.)\n */\n\nimport { setTransformValue, buildTransformString } from './TransformCache';\nimport { queueTransform, queueStyle } from './RenderBatch';\nimport { isTransformProp, isCSSVariable, camelToKebab } from '../utils/PropertyParser';\nimport { isInTick } from '../core/Ticker';\nimport { SDKRegistry } from '../registries/SDKRegistry';\nimport type { ParsedFilterFunction } from '../utils/FilterParser';\n\n// Cached reference for hot-path performance\nlet _filter: typeof SDKRegistry.filter = null;\n\n/**\n * Set a CSS property value on an element\n * Automatically batches during animation tick, writes immediately otherwise\n */\nexport function setCSSProperty(\n element: Element,\n property: string,\n value: number,\n unit: string\n): void {\n const htmlElement = element as HTMLElement;\n\n // Check if element has style property (for test environments)\n if (!htmlElement.style) {\n return;\n }\n\n // Check if we're inside a tick - determines batch vs immediate write\n const inTick = isInTick();\n\n if (isTransformProp(property)) {\n // Update transform cache with value and unit (sets dirty flag)\n setTransformValue(element, property, value, unit);\n\n if (inTick) {\n // Inside tick: queue for batch flush at end of frame\n queueTransform(element);\n } else {\n // Outside tick: write immediately for responsive feedback\n htmlElement.style.transform = buildTransformString(element);\n }\n } else {\n // Regular CSS property or CSS variable\n const cssValue = unit ? `${value}${unit}` : `${value}`;\n\n if (inTick) {\n // Inside tick: queue for batch flush\n queueStyle(element, property, cssValue);\n } else {\n // Outside tick: write immediately\n try {\n if (isCSSVariable(property)) {\n // CSS variables use setProperty with raw name\n htmlElement.style.setProperty(property, cssValue);\n } else if (property in htmlElement.style) {\n // Dynamic property access requires index signature cast\n (htmlElement.style as CSSStyleDeclaration & Record<string, string>)[property] = cssValue;\n } else {\n htmlElement.style.setProperty(camelToKebab(property), cssValue);\n }\n } catch {\n // Silently fail for invalid properties\n }\n }\n }\n}\n\n/**\n * Set a CSS color property value on an element\n * Takes RGBA values directly for performance (avoids string formatting in hot path)\n */\nexport function setCSSColorProperty(\n element: Element,\n property: string,\n r: number,\n g: number,\n b: number,\n a: number\n): void {\n const htmlElement = element as HTMLElement;\n\n if (!htmlElement.style) {\n return;\n }\n\n // Format as rgba() - use rgb() if fully opaque for cleaner output\n const cssValue = a === 1\n ? `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`\n : `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${a})`;\n\n const inTick = isInTick();\n\n if (inTick) {\n queueStyle(element, property, cssValue);\n } else {\n try {\n if (isCSSVariable(property)) {\n // CSS variables use setProperty with raw name\n htmlElement.style.setProperty(property, cssValue);\n } else if (property in htmlElement.style) {\n (htmlElement.style as CSSStyleDeclaration & Record<string, string>)[property] = cssValue;\n } else {\n htmlElement.style.setProperty(camelToKebab(property), cssValue);\n }\n } catch {\n // Silently fail for invalid properties\n }\n }\n}\n\n/**\n * Get computed value of a CSS property\n */\nexport function getCSSProperty(element: Element, property: string): string {\n const computed = window.getComputedStyle(element);\n const cssProp = camelToKebab(property);\n return computed.getPropertyValue(cssProp);\n}\n\n/**\n * Set a CSS filter property value on an element\n * Interpolates between start and end filter arrays at given progress\n */\nexport function setCSSFilterProperty(\n element: Element,\n startFilters: ParsedFilterFunction[],\n endFilters: ParsedFilterFunction[],\n progress: number\n): void {\n const htmlElement = element as HTMLElement;\n\n if (!htmlElement.style) {\n return;\n }\n\n // Interpolate filter values using SDKRegistry (cached for performance)\n if (_filter || (_filter = SDKRegistry.filter)) {\n const interpolated = _filter.interpolateFilters(startFilters, endFilters, progress);\n const cssValue = _filter.filterToString(interpolated);\n\n const inTick = isInTick();\n\n if (inTick) {\n queueStyle(element, 'filter', cssValue);\n } else {\n htmlElement.style.filter = cssValue;\n }\n }\n}\n",
|
|
26
|
+
"/**\n * PathRenderer - Encapsulates motion path rendering logic\n * \n * Extracted from PropTween to improve code organization.\n * PropTween still holds the data (for pooling), but rendering is delegated here.\n */\n\nimport { setTransformValue, buildTransformString } from './TransformCache';\nimport { queueTransform } from './RenderBatch';\nimport { isInTick } from '../core/Ticker';\nimport type { PathPoint } from '../utils/PathParser';\n\ninterface PathRenderState {\n pathLUT: PathPoint[];\n alignOffset: { x: number; y: number };\n pathOffset: { x: number; y: number };\n pathRotate: boolean;\n}\n\n/**\n * Render path position at given progress\n */\nexport function renderPath(\n target: Element,\n progress: number,\n state: PathRenderState\n): void {\n const { pathLUT, alignOffset, pathOffset, pathRotate } = state;\n\n if (!pathLUT || pathLUT.length === 0) return;\n\n // Get point from LUT with linear interpolation between samples\n const lutIndex = progress * (pathLUT.length - 1);\n const lowIndex = Math.floor(lutIndex);\n const highIndex = Math.min(lowIndex + 1, pathLUT.length - 1);\n const t = lutIndex - lowIndex;\n\n const p1 = pathLUT[lowIndex];\n const p2 = pathLUT[highIndex];\n\n // Linearly interpolate between the two nearest samples\n const pathX = p1.x + (p2.x - p1.x) * t;\n const pathY = p1.y + (p2.y - p1.y) * t;\n\n // Final position = pathOffset + pathPosition - alignOffset\n // pathOffset positions the path relative to align element\n // alignOffset adjusts for element's origin point\n const x = (pathOffset?.x ?? 0) + pathX - (alignOffset?.x ?? 0);\n const y = (pathOffset?.y ?? 0) + pathY - (alignOffset?.y ?? 0);\n\n // Set transform values\n setTransformValue(target, 'x', x, 'px');\n setTransformValue(target, 'y', y, 'px');\n\n if (pathRotate) {\n const angle = interpolateAngle(p1.angle, p2.angle, t);\n setTransformValue(target, 'rotate', angle, 'deg');\n }\n\n // Build and apply transform string\n if (isInTick()) {\n queueTransform(target);\n } else {\n (target as HTMLElement).style.transform = buildTransformString(target);\n }\n}\n\n/**\n * Interpolate angle with proper wraparound handling\n * Always takes the shorter rotation path (max 180° difference)\n */\nfunction interpolateAngle(angle1: number, angle2: number, t: number): number {\n let angleDiff = angle2 - angle1;\n\n // Normalize to shortest rotation path (±180°)\n if (angleDiff > 180) angleDiff -= 360;\n if (angleDiff < -180) angleDiff += 360;\n\n return angle1 + angleDiff * t;\n}\n",
|
|
27
|
+
"/**\n * PropTween - Individual property interpolation unit\n *\n * Linked list node for animating a single property.\n * Pooled for performance.\n * Supports scalar values, color (RGBA), and filter interpolation.\n */\n\nimport { setCSSProperty, setCSSColorProperty, setCSSFilterProperty } from '../render/CSSRenderer';\nimport { clearTransformCache } from '../render/TransformCache';\nimport type { AnimationTarget } from '../types';\nimport type { ParsedFilterFunction } from '../utils/FilterParser';\nimport type { ParsedDrawSVG } from '../utils/DrawSVGParser';\nimport { SDKRegistry } from '../registries/SDKRegistry';\nimport { getPointAtProgress, type PathPoint } from '../utils/PathParser';\nimport { renderPath } from '../render/PathRenderer';\n\n// Cached reference for hot-path performance\nlet _drawSVG: typeof SDKRegistry.drawSVG = null;\n\n// Minimum samples for path LUT (ensures smooth animation)\nconst MIN_PATH_SAMPLES = 100;\n// Maximum samples to prevent memory issues with very long paths\nconst MAX_PATH_SAMPLES = 500;\n// Sample density: approximately 1 sample per 2 pixels of path length\nconst PATH_SAMPLE_DENSITY = 0.5;\n\n// LUT array pool for memory efficiency\nconst lutPool: PathPoint[][] = [];\nconst MAX_POOL_SIZE = 50;\n\nfunction acquireLUT(size: number): PathPoint[] {\n // Try to find a pooled array of similar size\n for (let i = 0; i < lutPool.length; i++) {\n if (lutPool[i].length >= size) {\n const lut = lutPool.splice(i, 1)[0];\n // IMPORTANT: Always set length to requested size to avoid reading stale data\n lut.length = size;\n return lut;\n }\n }\n return new Array(size);\n}\n\nfunction releaseLUT(lut: PathPoint[]): void {\n if (lutPool.length < MAX_POOL_SIZE) {\n lutPool.push(lut);\n }\n}\n\ntype PropTweenValueType = 'scalar' | 'color' | 'filter' | 'drawSVG' | 'path';\n\nexport class PropTween {\n target: AnimationTarget | null = null;\n property: string = '';\n startValue: number = 0;\n endValue: number = 0;\n change: number = 0;\n unit: string = '';\n next: PropTween | null = null;\n\n // ─────────────────────────────────────────────────────────────\n // Type discriminator - determines which property group is active\n // ─────────────────────────────────────────────────────────────\n valueType: PropTweenValueType = 'scalar';\n\n // ─────────────────────────────────────────────────────────────\n // COLOR properties (valueType === 'color')\n // ─────────────────────────────────────────────────────────────\n startColor: Float32Array | null = null;\n endColor: Float32Array | null = null;\n changeColor: Float32Array = new Float32Array(4);\n\n // ─────────────────────────────────────────────────────────────\n // FILTER properties (valueType === 'filter')\n // ─────────────────────────────────────────────────────────────\n startFilters: ParsedFilterFunction[] | null = null;\n endFilters: ParsedFilterFunction[] | null = null;\n\n // ─────────────────────────────────────────────────────────────\n // DRAWSVG properties (valueType === 'drawSVG')\n // ─────────────────────────────────────────────────────────────\n startDraw: ParsedDrawSVG | null = null;\n endDraw: ParsedDrawSVG | null = null;\n pathLength: number = 0; // SVG stroke length for dasharray calc\n\n // ─────────────────────────────────────────────────────────────\n // MOTION PATH properties (valueType === 'path')\n // Pre-samples path into LUT for O(1) rendering per frame\n // ─────────────────────────────────────────────────────────────\n pathData: string | null = null; // Raw SVG path d attribute\n motionPathLength: number = 0; // Total path length in px\n startProgress: number = 0; // Start position on path (0-1)\n endProgress: number = 0; // End position on path (0-1)\n pathRotate: boolean = false; // Auto-rotate along tangent\n alignOffset: { x: number; y: number } | null = null; // Element origin offset\n pathOffset: { x: number; y: number } | null = null; // Align element offset\n pathLUT: PathPoint[] | null = null; // Pre-sampled lookup table\n\n // Cached target type check (avoids instanceof in hot path)\n private _isElement: boolean = false;\n\n /**\n * Initialize the PropTween with scalar values\n */\n init(\n target: AnimationTarget,\n property: string,\n startValue: number,\n endValue: number,\n unit: string = ''\n ): this {\n this.target = target;\n this.property = property;\n this.startValue = startValue;\n this.endValue = endValue;\n this.change = endValue - startValue;\n this.unit = unit;\n this.valueType = 'scalar';\n this._isElement = target instanceof Element;\n return this;\n }\n\n /**\n * Initialize the PropTween with color values\n */\n initColor(\n target: AnimationTarget,\n property: string,\n startColor: Float32Array,\n endColor: Float32Array\n ): this {\n this.target = target;\n this.property = property;\n this.valueType = 'color';\n this.startColor = startColor;\n this.endColor = endColor;\n this.changeColor[0] = endColor[0] - startColor[0];\n this.changeColor[1] = endColor[1] - startColor[1];\n this.changeColor[2] = endColor[2] - startColor[2];\n this.changeColor[3] = endColor[3] - startColor[3];\n this._isElement = target instanceof Element;\n return this;\n }\n\n /**\n * Initialize the PropTween with filter values\n */\n initFilter(\n target: AnimationTarget,\n property: string,\n startFilters: ParsedFilterFunction[],\n endFilters: ParsedFilterFunction[]\n ): this {\n this.target = target;\n this.property = property;\n this.valueType = 'filter';\n this.startFilters = startFilters;\n this.endFilters = endFilters;\n this._isElement = target instanceof Element;\n return this;\n }\n\n /**\n * Initialize the PropTween with drawSVG values\n */\n initDrawSVG(\n target: AnimationTarget,\n property: string,\n startDraw: ParsedDrawSVG,\n endDraw: ParsedDrawSVG,\n pathLength: number\n ): this {\n this.target = target;\n this.property = property;\n this.valueType = 'drawSVG';\n this.startDraw = startDraw;\n this.endDraw = endDraw;\n this.pathLength = pathLength;\n this._isElement = target instanceof Element;\n return this;\n }\n\n /**\n * Initialize the PropTween with path (motion path) values\n * Pre-samples the path into a lookup table for O(1) rendering\n */\n initPath(\n target: AnimationTarget,\n property: string,\n pathData: string,\n pathLength: number,\n startProgress: number,\n endProgress: number,\n rotate: boolean,\n alignOffset: { x: number; y: number },\n pathOffset: { x: number; y: number }\n ): this {\n // Clear transform cache AND inline style to ensure clean state for this animation\n // This prevents stale rotation values from previous path animations being\n // read back during hydration when the cache is recreated\n if (target instanceof Element) {\n clearTransformCache(target, true);\n }\n\n this.target = target;\n this.property = property;\n this.valueType = 'path';\n this.pathData = pathData;\n this.motionPathLength = pathLength;\n this.startProgress = startProgress;\n this.endProgress = endProgress;\n this.pathRotate = rotate;\n this.alignOffset = alignOffset;\n this.pathOffset = pathOffset;\n this._isElement = target instanceof Element;\n\n // Pre-sample path into lookup table for O(1) rendering\n // Calculate number of samples based on path length and animation range\n const progressRange = Math.abs(endProgress - startProgress);\n const effectiveLength = pathLength * progressRange;\n const sampleCount = Math.min(\n MAX_PATH_SAMPLES,\n Math.max(MIN_PATH_SAMPLES, Math.ceil(effectiveLength * PATH_SAMPLE_DENSITY))\n );\n\n // Acquire LUT from pool\n this.pathLUT = acquireLUT(sampleCount + 1);\n for (let i = 0; i <= sampleCount; i++) {\n const t = i / sampleCount;\n const pathProgress = startProgress + (endProgress - startProgress) * t;\n this.pathLUT[i] = getPointAtProgress(pathData, pathProgress, rotate);\n }\n\n return this;\n }\n\n /**\n * Render the property at given progress (0-1)\n */\n render(progress: number): void {\n if (!this.target) return;\n\n if (this.valueType === 'color' && this.startColor) {\n // Interpolate RGBA channels\n const r = this.startColor[0] + this.changeColor[0] * progress;\n const g = this.startColor[1] + this.changeColor[1] * progress;\n const b = this.startColor[2] + this.changeColor[2] * progress;\n const a = this.startColor[3] + this.changeColor[3] * progress;\n\n if (this._isElement) {\n setCSSColorProperty(this.target as Element, this.property, r, g, b, a);\n }\n } else if (this.valueType === 'filter' && this.startFilters && this.endFilters) {\n // Interpolate filter functions\n if (this._isElement) {\n setCSSFilterProperty(this.target as Element, this.startFilters, this.endFilters, progress);\n }\n } else if (this.valueType === 'drawSVG' && this.startDraw && this.endDraw) {\n // Interpolate drawSVG start and end positions\n if (this._isElement && this.pathLength > 0) {\n const currentStart = this.startDraw.start + (this.endDraw.start - this.startDraw.start) * progress;\n const currentEnd = this.startDraw.end + (this.endDraw.end - this.startDraw.end) * progress;\n if (_drawSVG || (_drawSVG = SDKRegistry.drawSVG)) {\n _drawSVG.applyDrawSVG(this.target as Element, currentStart, currentEnd, this.pathLength);\n }\n }\n } else if (this.valueType === 'path' && this.pathLUT && this.pathLUT.length > 0) {\n // Delegate to PathRenderer for cleaner code organization\n if (this._isElement) {\n renderPath(this.target as Element, progress, {\n pathLUT: this.pathLUT,\n alignOffset: this.alignOffset ?? { x: 0, y: 0 },\n pathOffset: this.pathOffset ?? { x: 0, y: 0 },\n pathRotate: this.pathRotate,\n });\n }\n } else {\n // Scalar interpolation\n const value = this.startValue + this.change * progress;\n\n if (this._isElement) {\n setCSSProperty(this.target as Element, this.property, value, this.unit);\n } else {\n // Plain object target (for testing)\n (this.target as Record<string, number>)[this.property] = value;\n }\n }\n }\n\n /**\n * Reset for object pooling\n */\n reset(): void {\n this.target = null;\n this.property = '';\n this.startValue = 0;\n this.endValue = 0;\n this.change = 0;\n this.unit = '';\n this.next = null;\n this.valueType = 'scalar';\n this.startColor = null;\n this.endColor = null;\n this.startFilters = null;\n this.endFilters = null;\n this.startDraw = null;\n this.endDraw = null;\n this.pathLength = 0;\n this.pathData = null;\n this.motionPathLength = 0;\n this.startProgress = 0;\n this.endProgress = 0;\n this.pathRotate = false;\n this.alignOffset = null;\n this.pathOffset = null;\n // Release LUT back to pool\n if (this.pathLUT) {\n releaseLUT(this.pathLUT);\n }\n this.pathLUT = null;\n this._isElement = false;\n }\n}\n",
|
|
28
|
+
"/**\n * ObjectPool<T> - Generic object pool for reusable instances\n *\n * Reuses objects that implement a `reset()` method to minimize\n * garbage collection overhead.\n */\n\n/** Constraint: pooled objects must be resettable */\nexport interface Poolable {\n reset(): void;\n}\n\nexport class ObjectPool<T extends Poolable> {\n private pool: T[] = [];\n private totalCreated: number = 0;\n private readonly factory: () => T;\n private readonly maxSize: number | undefined;\n\n constructor(factory: () => T, maxSize?: number) {\n this.factory = factory;\n this.maxSize = maxSize;\n }\n\n /**\n * Acquire an object from the pool or create a new one\n */\n acquire(): T {\n if (this.pool.length > 0) {\n return this.pool.pop()!;\n }\n this.totalCreated++;\n return this.factory();\n }\n\n /**\n * Release an object back to the pool\n */\n release(obj: T): void {\n obj.reset();\n if (this.maxSize === undefined || this.pool.length < this.maxSize) {\n this.pool.push(obj);\n }\n // else: pool is at capacity — let GC claim it\n }\n\n /**\n * Get count of pooled objects\n */\n getPoolSize(): number {\n return this.pool.length;\n }\n\n /**\n * Get total objects created (for stats)\n */\n getTotalCreated(): number {\n return this.totalCreated;\n }\n\n /**\n * Clear the pool (for testing/debugging)\n */\n clear(): void {\n this.pool = [];\n }\n}\n",
|
|
29
|
+
"/**\n * PropTweenPool - Singleton object pool for PropTween instances\n *\n * Reuses PropTween objects to minimize garbage collection overhead\n */\n\nimport { PropTween } from '../core/PropTween';\nimport { ObjectPool } from './ObjectPool';\n\nconst pool = new ObjectPool<PropTween>(() => new PropTween(), 500);\n\nexport const PropTweenPool = {\n acquire: () => pool.acquire(),\n release: (propTween: PropTween) => pool.release(propTween),\n getPoolSize: () => pool.getPoolSize(),\n getTotalCreated: () => pool.getTotalCreated(),\n clear: () => pool.clear(),\n};\n\nexport type PropTweenPool = typeof PropTweenPool;\n",
|
|
30
|
+
"/**\n * AnimationBuilder - Config-based animation creator\n * \n * Creates animations from configuration objects.\n * Used internally by Motion() function.\n */\n\nimport type { AnimationVars, AnimationTarget, TargetInput, StaggerVars, RepeatConfig, SplitType, AnimationConfig, FitConfig } from '../types';\nimport type { InternalAnimationConfig } from './Animation';\n\n/**\n * Internal builder configuration for cloning (not exported from SDK)\n */\ninterface BuilderConfig {\n fromVars?: AnimationVars;\n toVars?: AnimationVars;\n duration: number;\n delay: number;\n ease: string;\n axis?: 'x' | 'y';\n fit?: FitConfig;\n split?: SplitType;\n mask?: boolean;\n stagger?: number | StaggerVars;\n repeat?: number;\n repeatDelay?: number;\n yoyo?: boolean;\n onStart?: () => void;\n onUpdate?: (progress: number) => void;\n onComplete?: () => void;\n onRepeat?: (repeatCount: number) => void;\n onReverseComplete?: () => void;\n}\nimport { resolveTargets } from '../utils/TargetResolver';\nimport { Animation } from './Animation';\nimport { PropTweenPool } from '../memory/PropTweenPool';\nimport { clearTransformCache } from '../render/TransformCache';\nimport { SDKRegistry } from '../registries/SDKRegistry';\nimport { isColorProp, isFilterProp } from '../utils/PropertyParser';\n\n/**\n * Default \"natural\" values for animatable properties.\n * Used when from has properties that to doesn't specify.\n */\nconst PROPERTY_DEFAULTS: Record<string, number> = {\n x: 0, y: 0, z: 0,\n rotate: 0, rotateX: 0, rotateY: 0, rotateZ: 0,\n scale: 1, scaleX: 1, scaleY: 1, scaleZ: 1,\n skewX: 0, skewY: 0,\n opacity: 1,\n};\n\nexport class AnimationBuilder {\n /**\n * Callback to create animations via Engine (set by Engine to avoid circular dependency)\n * Engine→Timeline→AnimationBuilder→Engine cycle is broken by this callback.\n */\n private static _createAnimationCallback: ((\n targets: AnimationTarget[],\n vars: AnimationVars,\n duration: number,\n delay: number,\n ease: string,\n isFrom: boolean,\n fromVars?: AnimationVars,\n config?: InternalAnimationConfig\n ) => Animation | Animation[]) | null = null;\n\n static setCreateAnimationCallback(callback: typeof AnimationBuilder._createAnimationCallback): void {\n AnimationBuilder._createAnimationCallback = callback;\n }\n\n private _targets: AnimationTarget[];\n private _originalTargets: AnimationTarget[];\n private _fromVars?: AnimationVars;\n private _toVars?: AnimationVars;\n private _duration: number = 0.5;\n private _delay: number = 0;\n private _ease: string = 'power1.out';\n private _animation: Animation | null = null;\n private _animations: Animation[] = [];\n private _built: boolean = false;\n // Configuration\n private _splitType?: SplitType;\n private _mask?: boolean;\n private _repeat?: number;\n private _repeatDelay?: number;\n private _yoyo?: boolean;\n private _stagger?: number | StaggerVars;\n private _axis?: 'x' | 'y';\n private _fitConfig?: FitConfig;\n // Lifecycle callbacks\n private _onStart?: () => void;\n private _onUpdate?: (progress: number) => void;\n private _onComplete?: () => void;\n private _onRepeat?: (repeatCount: number) => void;\n private _onReverseComplete?: () => void;\n\n // When true, Engine skips the immediate render(0) for FROM animations.\n // Set by Timeline._addBuilder so it can capture initial values first.\n private _skipInitialFromRender = false;\n\n // For pooling/rebuild\n private _plainObjectInitialValues: Map<AnimationTarget, Record<string, number>> = new Map();\n private _expectedAnimationIds: number[] = [];\n private _propsToAnimate: string[] = [];\n\n /**\n * Create an animation builder\n * @param targets - Elements or objects to animate\n * @param config - Animation configuration\n */\n constructor(targets: TargetInput, config?: AnimationConfig) {\n this._targets = resolveTargets(targets);\n this._originalTargets = this._targets;\n\n if (this._targets.length === 0 && typeof targets === 'string') {\n console.warn(`[Motion] Selector \"${targets}\" matched no elements. Animation will not run.`);\n }\n\n if (config) {\n this._applyAnimationConfig(config);\n }\n }\n\n /**\n * Apply animation config\n */\n private _applyAnimationConfig(config: AnimationConfig): void {\n if (config.from) this._fromVars = config.from;\n if (config.to) this._toVars = config.to;\n\n if (config.duration !== undefined) {\n if (typeof config.duration !== 'number' || !isFinite(config.duration) || config.duration < 0) {\n console.warn(`[Motion] Invalid duration: ${config.duration}. Must be a finite number >= 0. Using default.`);\n } else {\n this._duration = config.duration;\n }\n }\n\n if (config.delay !== undefined) {\n if (typeof config.delay !== 'number' || isNaN(config.delay)) {\n console.warn(`[Motion] Invalid delay: ${config.delay}. Must be a number. Using default 0.`);\n } else if (!isFinite(config.delay)) {\n console.warn(`[Motion] Invalid delay: ${config.delay}. Infinity is not a valid delay. Using default 0.`);\n } else {\n this._delay = config.delay;\n }\n }\n\n if (config.ease) this._ease = config.ease;\n if (config.split) this._splitType = config.split;\n if (config.mask) this._mask = config.mask;\n if (config.stagger !== undefined) this._stagger = config.stagger;\n if (config.axis) this._axis = config.axis;\n if (config.fit) this._fitConfig = config.fit;\n\n if (config.repeat) {\n if (typeof config.repeat === 'number') {\n this._repeat = config.repeat;\n } else {\n this._repeat = config.repeat.times;\n this._repeatDelay = config.repeat.delay;\n this._yoyo = config.repeat.yoyo;\n }\n }\n\n if (config.onStart) this._onStart = config.onStart;\n if (config.onUpdate) this._onUpdate = config.onUpdate;\n if (config.onComplete) this._onComplete = config.onComplete;\n if (config.onRepeat) this._onRepeat = config.onRepeat;\n if (config.onReverseComplete) this._onReverseComplete = config.onReverseComplete;\n }\n\n /**\n * Capture initial values from plain object targets\n */\n private _capturePlainObjectValues(props: string[]): void {\n for (const target of this._targets) {\n if (target instanceof Element) continue;\n const obj = target as Record<string, number>;\n const values: Record<string, number> = {};\n for (const prop of props) {\n if (prop in obj && typeof obj[prop] === 'number') {\n values[prop] = obj[prop];\n }\n }\n if (Object.keys(values).length > 0) {\n this._plainObjectInitialValues.set(target, values);\n }\n }\n }\n\n /**\n * Restore plain object targets to initial values\n */\n private _restorePlainObjectValues(): void {\n for (const [target, values] of this._plainObjectInitialValues) {\n const obj = target as Record<string, number>;\n for (const [prop, value] of Object.entries(values)) {\n obj[prop] = value;\n }\n }\n }\n\n private _ensureBuilt(): void {\n if (!this._built) this._build();\n }\n\n /**\n * Check if animations have been invalidated (pooled or reused)\n */\n private _isInvalidated(): boolean {\n if (this._animations.length === 0) return false;\n return this._animations.some((anim, i) => {\n const currentId = anim.getId();\n const expectedId = this._expectedAnimationIds[i];\n return currentId === 0 || currentId !== expectedId;\n });\n }\n\n /**\n * Rebuild if animations were pooled\n */\n rebuildIfPooled(): void {\n if (this._isInvalidated()) {\n this._restorePlainObjectValues();\n for (const target of this._targets) {\n if (target instanceof Element) {\n clearTransformCache(target);\n SDKRegistry.styleReset?.clearAnimationStylesForProps?.(target, this._propsToAnimate);\n }\n }\n this._resetBuildState();\n this._ensureBuilt();\n }\n }\n\n private _build(): void {\n if (this._built) return;\n if (this._targets.length === 0) return;\n if (!this._fromVars && !this._toVars && !this._fitConfig) return;\n\n if (this._fitConfig) {\n if (!SDKRegistry.fit) {\n console.warn('[Motion] FitResolver not loaded. Did you import the SDK?');\n return;\n }\n this._built = true;\n\n // Issue #10: Guard against uninitialized Engine before calling the callback\n if (!AnimationBuilder._createAnimationCallback) {\n throw new Error('AnimationBuilder: Engine not initialized. Call Engine.init() first.');\n }\n\n const internalConfig: InternalAnimationConfig = {};\n if (this._repeat !== undefined) internalConfig.repeat = this._repeat;\n if (this._repeatDelay !== undefined) internalConfig.repeatDelay = this._repeatDelay;\n if (this._yoyo) internalConfig.yoyo = this._yoyo;\n if (this._onStart) internalConfig.onStart = this._onStart;\n if (this._onUpdate) internalConfig.onUpdate = this._onUpdate;\n if (this._onComplete) internalConfig.onComplete = this._onComplete;\n if (this._onRepeat) internalConfig.onRepeat = this._onRepeat;\n if (this._onReverseComplete) internalConfig.onReverseComplete = this._onReverseComplete;\n // Issue #9: Forward stagger into Fit internalConfig so staggered Fit works\n if (this._stagger !== undefined) internalConfig.stagger = this._stagger;\n\n // Issue #8: Create one Animation per Element target so that\n // registerPendingSetup (a single-slot setter) is called exactly once\n // per Animation — avoids overwriting geometry captured for earlier targets (Fit).\n const animations: Animation[] = [];\n for (const target of this._targets) {\n if (!(target instanceof Element)) continue;\n const result = AnimationBuilder._createAnimationCallback(\n [target],\n {},\n this._duration,\n this._delay,\n this._ease,\n false,\n undefined,\n internalConfig\n );\n if (Array.isArray(result)) {\n animations.push(...result);\n } else {\n animations.push(result);\n }\n }\n\n const fitCfg = this._fitConfig;\n for (const anim of animations) {\n for (const target of anim.getTargets()) {\n if (target instanceof Element) {\n SDKRegistry.fit!.registerPendingSetup(anim, target, fitCfg, PropTweenPool);\n }\n }\n }\n\n this._animation = animations[0] ?? null;\n this._animations = animations;\n this._expectedAnimationIds = animations.map((a) => a.getId());\n return;\n }\n\n this._built = true;\n\n let vars: AnimationVars = {};\n let isFrom = false;\n let fromVarsToPass: AnimationVars | undefined;\n\n if (this._fromVars && this._toVars) {\n vars = { ...this._toVars };\n fromVarsToPass = { ...this._fromVars };\n isFrom = true;\n\n // Merge missing properties from 'from' into 'to' with defaults\n for (const key of Object.keys(fromVarsToPass)) {\n if (!(key in vars)) {\n if (key in PROPERTY_DEFAULTS) {\n (vars as Record<string, number>)[key] = PROPERTY_DEFAULTS[key];\n } else if (isColorProp(key)) {\n (vars as Record<string, string>)[key] = '';\n } else if (isFilterProp(key)) {\n (vars as Record<string, string>)[key] = '';\n }\n }\n }\n } else if (this._fromVars) {\n vars = { ...this._fromVars };\n isFrom = true;\n } else if (this._toVars) {\n vars = { ...this._toVars };\n }\n\n // Handle text splitting (only if TextSplitter is loaded)\n if (this._splitType && SDKRegistry.textSplitter?.split) {\n const splitOptions = this._mask ? { mask: true } : undefined;\n const splitElements: Element[] = [];\n for (const target of this._originalTargets) {\n if (target instanceof Element) {\n const elements = SDKRegistry.textSplitter?.split(target, this._splitType, splitOptions);\n splitElements.push(...elements);\n }\n }\n if (splitElements.length > 0) {\n this._targets = splitElements;\n }\n }\n\n // Get properties to animate\n this._propsToAnimate = Object.keys(vars);\n if (fromVarsToPass) {\n for (const key of Object.keys(fromVarsToPass)) {\n if (!this._propsToAnimate.includes(key)) {\n this._propsToAnimate.push(key);\n }\n }\n }\n\n // Capture initial values for plain objects\n if (this._plainObjectInitialValues.size === 0) {\n this._capturePlainObjectValues(this._propsToAnimate);\n }\n\n // For FROM animations, clear cache and styles\n if (isFrom) {\n for (const target of this._targets) {\n if (target instanceof Element) {\n clearTransformCache(target);\n SDKRegistry.styleReset?.clearAnimationStylesForProps?.(target, this._propsToAnimate);\n }\n }\n }\n\n // Register animated properties for reset (only if StyleReset is loaded)\n if (SDKRegistry.styleReset?.registerAnimatedProps) {\n for (const target of this._targets) {\n if (target instanceof Element) {\n SDKRegistry.styleReset?.registerAnimatedProps(target, this._propsToAnimate);\n }\n }\n }\n\n // Build internal config for Engine\n const internalConfig = {\n repeat: this._repeat,\n repeatDelay: this._repeatDelay,\n yoyo: this._yoyo,\n stagger: this._stagger,\n onStart: this._onStart,\n onUpdate: this._onUpdate,\n onComplete: this._onComplete,\n onRepeat: this._onRepeat,\n onReverseComplete: this._onReverseComplete,\n _skipInitialRender: this._skipInitialFromRender || undefined,\n };\n\n if (!AnimationBuilder._createAnimationCallback) {\n throw new Error('AnimationBuilder: Engine not initialized. Call AnimationBuilder.setCreateAnimationCallback first.');\n }\n const result = AnimationBuilder._createAnimationCallback(\n this._targets,\n vars,\n this._duration,\n this._delay,\n this._ease,\n isFrom,\n fromVarsToPass,\n internalConfig\n );\n\n if (Array.isArray(result)) {\n this._animations = result;\n this._animation = result[0] || null;\n } else {\n this._animation = result;\n this._animations = [result];\n }\n\n this._expectedAnimationIds = this._animations.map(a => a.getId());\n }\n\n getAnimation(): Animation | null {\n this._ensureBuilt();\n return this._animation;\n }\n\n getAnimations(): Animation[] {\n this._ensureBuilt();\n return this._animations;\n }\n\n getTargets(): AnimationTarget[] {\n return this._targets;\n }\n\n /**\n * Get the original (pre-split) targets. When text splitting is used,\n * _targets holds the split fragments; _originalTargets holds the source elements.\n */\n getOriginalTargets(): AnimationTarget[] {\n return this._originalTargets;\n }\n\n /**\n * Whether this builder uses text splitting\n */\n hasSplit(): boolean {\n return !!this._splitType;\n }\n\n private _resetBuildState(): void {\n this._built = false;\n this._animations = [];\n this._animation = null;\n this._expectedAnimationIds = [];\n }\n\n /**\n * Get builder configuration for cloning (used by Timeline's each mode)\n */\n getConfig(): BuilderConfig {\n return {\n fromVars: this._fromVars ? { ...this._fromVars } : undefined,\n toVars: this._toVars ? { ...this._toVars } : undefined,\n duration: this._duration,\n delay: this._delay,\n ease: this._ease,\n axis: this._axis,\n fit: this._fitConfig ? { ...this._fitConfig } : undefined,\n split: this._splitType,\n mask: this._mask,\n stagger: this._stagger,\n repeat: this._repeat,\n repeatDelay: this._repeatDelay,\n yoyo: this._yoyo,\n onStart: this._onStart,\n onUpdate: this._onUpdate,\n onComplete: this._onComplete,\n onRepeat: this._onRepeat,\n onReverseComplete: this._onReverseComplete,\n };\n }\n\n /**\n * Set axis for mouse movement binding\n */\n setAxis(axis: 'x' | 'y'): void {\n this._axis = axis;\n }\n\n /**\n * Get axis binding\n */\n getAxis(): 'x' | 'y' | undefined {\n return this._axis;\n }\n\n /**\n * Tell Engine to skip the immediate FROM render (animation.render(0)).\n * Used by Timeline so it can capture initial values BEFORE FROM is applied.\n */\n setSkipInitialFromRender(skip: boolean): void {\n this._skipInitialFromRender = skip;\n }\n}\n",
|
|
31
|
+
"/**\n * PositionParser - Parse timeline position parameters\n *\n * Supports:\n * - Number: absolute time (e.g., 2)\n * - \"+=0.5\": relative to current end (0.5s after)\n * - \"-=0.5\": relative to current end (0.5s before, overlap)\n * - \"<\": start of previous animation\n * - \">\": end of previous animation\n * - \"<0.5\" or \"<+0.5\": start of previous + offset\n * - \"<-0.5\": start of previous - offset\n * - \">0.5\" or \">+0.5\": end of previous + offset\n * - \">-0.5\": end of previous - offset\n */\n\nexport class PositionParser {\n /**\n * Parse position parameter and return absolute time\n * @param position Position parameter (string or number)\n * @param currentEnd Current end time of timeline\n * @param previousStart Start time of previous child\n * @param previousEnd End time of previous child\n */\n static parse(\n position: string | number | undefined,\n currentEnd: number,\n previousStart: number,\n previousEnd: number\n ): number {\n if (position === undefined) {\n return currentEnd;\n }\n\n if (typeof position === 'number') {\n return position;\n }\n\n const str = position.trim();\n\n // \"<\": start of previous animation\n if (str === '<') {\n return previousStart;\n }\n\n // \">\": end of previous animation\n if (str === '>') {\n return previousEnd;\n }\n\n // \"<offset\": start of previous + offset\n if (str.startsWith('<')) {\n const offset = parseFloat(str.substring(1));\n if (!isNaN(offset)) {\n return previousStart + offset;\n }\n }\n\n // \">offset\": end of previous + offset\n if (str.startsWith('>')) {\n const offset = parseFloat(str.substring(1));\n if (!isNaN(offset)) {\n return previousEnd + offset;\n }\n }\n\n // \"+=\" relative to current end\n if (str.startsWith('+=')) {\n const offset = parseFloat(str.substring(2));\n return currentEnd + offset;\n }\n\n // \"-=\" relative to current end\n if (str.startsWith('-=')) {\n const offset = parseFloat(str.substring(2));\n return currentEnd - offset;\n }\n\n console.warn(`Invalid position parameter: \"${str}\", using current end`);\n return currentEnd;\n }\n}\n",
|
|
32
|
+
"/**\n * Timeline for sequencing animations\n *\n * Features:\n * - Named timeline registry (create/retrieve by name)\n * - Position parameter support (absolute, +=, -=, <, >)\n * - Nested timelines\n * - Callback scheduling\n * - Control methods (play, pause, reverse, seek, etc.)\n * - Trigger system integration\n */\n\nimport type {\n HoverConfig,\n ClickConfig,\n ScrollConfig,\n MouseMoveConfig,\n GestureConfig,\n GestureConfigInternal,\n CursorConfig,\n PageExitConfig,\n TargetInput,\n AnimationConfig,\n} from '../types';\nimport { Animation } from './Animation';\nimport { AnimationBuilder } from './AnimationBuilder';\nimport { PositionParser } from '../utils/PositionParser';\nimport { SDKRegistry } from '../registries/SDKRegistry';\nimport { isElement } from '../utils/TargetResolver';\nimport { getCurrentValue, getOriginalCSSValueWithUnit, isTransformProp, isColorProp, isFilterProp, isPathProp } from '../utils/PropertyParser';\nimport { setTransformValue, buildTransformString, clearTransformCache } from '../render/TransformCache';\n\ninterface TimelineChild {\n type: 'animation' | 'timeline' | 'callback';\n child: Animation | Timeline | ((...args: unknown[]) => void);\n builder?: AnimationBuilder; // Store builder for each-mode cloning\n startTime: number;\n duration: number;\n params?: unknown[];\n}\n\nexport class Timeline {\n /** Monotonic counter for generating unique each-mode instance names */\n private static _eachCounter = 0;\n\n /**\n * Static callback for registering named timelines with Engine.\n * Set by Engine.getInstance() to break circular dependency.\n * @internal\n */\n private static _registerWithEngine: ((name: string, timeline: Timeline) => void) | null = null;\n\n /**\n * Set the engine registration callback. Called once by Engine during initialization.\n * @internal\n */\n static setEngineRegisterCallback(callback: (name: string, timeline: Timeline) => void): void {\n Timeline._registerWithEngine = callback;\n }\n\n private _name?: string;\n private _children: TimelineChild[] = [];\n private _duration: number = 0;\n private _time: number = 0;\n private _progress: number = 0;\n private _timeScale: number = 1;\n private _isActive: boolean = false;\n private _isReversed: boolean = false;\n private _killed: boolean = false;\n\n // Callbacks\n private _onStart?: () => void;\n private _onUpdate?: (progress: number, time: number) => void;\n private _onComplete?: () => void;\n private _startFired: boolean = false;\n\n /**\n * Reference to attached trigger (for cleanup in kill()).\n * @internal\n */\n _trigger?: { disable(): void };\n\n /** @internal */\n private _unregisterCallback?: () => void;\n\n /**\n * Set unregister callback for cleanup. Called by Engine after registration.\n * @internal\n */\n setUnregisterCallback(callback: () => void): void {\n this._unregisterCallback = callback;\n }\n\n // Track previous child for position parsing\n private _previousStart: number = 0;\n private _previousEnd: number = 0;\n\n // Store cloned timelines for each-mode (independent per-element triggers)\n private _eachInstances?: Timeline[];\n private _eachDuration?: number; // Preserves pre-each duration for forwarding\n\n // Initial property values per element (captured once at build time, restored on seek to 0)\n // For regular CSS: stores value/unit OR a flag to just remove inline style\n // For transforms: we just track that the element was animated (will clear cache on restore)\n private _initialValues: Map<Element, Map<string, { \n removeInline: boolean; // If true, just remove inline style (lets stylesheet auto/calc/clamp take over)\n value?: number; \n unit?: string;\n }>> = new Map();\n\n // Track original inline transform values for restoration\n private _initialTransforms: Map<Element, string | null> = new Map();\n\n // Track elements that have transform animations (for cache cleanup)\n private _transformElements: Set<Element> = new Set();\n\n // Original inline transition values per animated element. Populated at build time.\n // During active playback, transitions are overridden with 'none' to prevent CSS\n // transitions from interfering with frame-by-frame SDK rendering.\n // Disabled on timeline start, restored on completion and kill.\n private _savedTransitions: Map<HTMLElement, string> = new Map();\n private _transitionsDisabled: boolean = false;\n\n // Original will-change values per animated element with transform properties.\n // Set to 'transform' at build time for compositor layer promotion (avoids\n // layout thrashing), restored on kill/clear.\n private _savedWillChange: Map<HTMLElement, string> = new Map();\n\n constructor(name?: string) {\n this._name = name;\n\n // Register named timeline with Engine via callback (avoids circular dependency)\n if (name && Timeline._registerWithEngine) {\n Timeline._registerWithEngine(name, this);\n }\n }\n\n /**\n * Get timeline duration\n */\n duration(): number {\n if (this._eachDuration !== undefined) {\n return this._eachDuration;\n }\n return this._duration;\n }\n\n /**\n * Clear timeline state for rebuild (used for re-triggerable animations)\n * Kills existing animations, clears children, resets state\n */\n clear(): this {\n // Kill existing animations\n for (const child of this._children) {\n if (child.type === 'animation') {\n (child.child as Animation).kill();\n } else if (child.type === 'timeline') {\n (child.child as Timeline).kill();\n }\n }\n\n // Reset state\n this._children = [];\n this._duration = 0;\n this._time = 0;\n this._progress = 0;\n this._isActive = false;\n this._isReversed = false;\n this._startFired = false;\n this._previousStart = 0;\n this._previousEnd = 0;\n this._initialValues.clear();\n this._initialTransforms.clear();\n this._transformElements.clear();\n this._clearTransitionData();\n\n return this;\n }\n\n /**\n * Capture initial values for an animation's properties (called once per property per element)\n */\n private _captureInitialValues(animation: Animation): void {\n const targets = animation.getTargets();\n // Access PropTweens to get property names\n let propTween = animation.getFirstPropTween();\n\n while (propTween) {\n const property = propTween.property;\n\n for (const target of targets) {\n if (!(target instanceof Element)) continue;\n\n // Get or create property map for this element\n let propMap = this._initialValues.get(target);\n if (!propMap) {\n propMap = new Map();\n this._initialValues.set(target, propMap);\n }\n\n // Only capture if not already recorded (first animation for this property wins)\n if (!propMap.has(property)) {\n const htmlEl = target as HTMLElement;\n \n if (isTransformProp(property) || isPathProp(property)) {\n // Transform properties AND path property: track that this element has transforms\n // Path animations modify x, y, and optionally rotate transforms\n // We'll clear the entire transform cache on restore\n this._transformElements.add(target);\n if (!this._initialTransforms.has(target)) {\n const inlineTransform = htmlEl.style.transform;\n this._initialTransforms.set(target, inlineTransform || null);\n }\n // Still add to propMap so we know to restore this element\n propMap.set(property, { removeInline: true });\n } else if (isColorProp(property) || isFilterProp(property)) {\n // Color and filter properties: always remove inline style on restore\n // These are strings (not numeric) - just let stylesheet take over\n propMap.set(property, { removeInline: true });\n } else {\n // Regular CSS properties (numeric values with units)\n const kebabProp = property.replace(/([A-Z])/g, '-$1').toLowerCase();\n const hasInlineStyle = htmlEl.style.getPropertyValue(kebabProp) !== '';\n \n if (hasInlineStyle) {\n // Inline style exists - capture its value/unit so we can restore it\n const initial = getOriginalCSSValueWithUnit(target, property);\n propMap.set(property, { removeInline: false, value: initial.value, unit: initial.unit });\n } else {\n // No inline style - just flag to remove inline on restore (lets stylesheet auto/calc/clamp take over)\n propMap.set(property, { removeInline: true });\n }\n }\n }\n }\n\n propTween = propTween.next;\n }\n }\n\n /**\n * Restore all elements to their initial property values (used when seeking to position 0)\n */\n private _restoreInitialValues(): void {\n // First, handle transform elements - clear cache and remove inline transform\n for (const element of this._transformElements) {\n const htmlEl = element as HTMLElement;\n // Clear the transform cache completely (resets to defaults: scale=1, others=0)\n clearTransformCache(element);\n const originalTransform = this._initialTransforms.get(element);\n if (originalTransform) {\n htmlEl.style.transform = originalTransform;\n } else {\n htmlEl.style.removeProperty('transform');\n }\n }\n\n // Then handle regular CSS properties\n for (const [element, propMap] of this._initialValues) {\n const htmlEl = element as HTMLElement;\n\n for (const [property, initial] of propMap) {\n // Skip transform properties - already handled above\n if (isTransformProp(property)) continue;\n\n const kebabProp = property.replace(/([A-Z])/g, '-$1').toLowerCase();\n\n if (initial.removeInline) {\n // No original inline style - just remove the inline style we added\n // This lets the stylesheet value (auto, calc, clamp, etc.) take over\n htmlEl.style.removeProperty(kebabProp);\n } else {\n // Regular CSS property - restore with original value and unit\n const cssValue = `${initial.value}${initial.unit}`;\n htmlEl.style.setProperty(kebabProp, cssValue);\n }\n }\n }\n }\n\n\n /**\n * Disable CSS transitions on all animated elements.\n * Called when the timeline starts playing to prevent CSS transitions\n * from fighting frame-by-frame SDK rendering.\n */\n private _disableTransitions(): void {\n if (this._transitionsDisabled) return;\n for (const element of this._savedTransitions.keys()) {\n element.style.transition = 'none';\n }\n this._transitionsDisabled = true;\n }\n\n /**\n * Restore original CSS transition values on all animated elements.\n * Called on timeline completion and kill to re-enable CSS hover/focus\n * transitions after the SDK animation is done.\n */\n private _restoreTransitions(): void {\n if (!this._transitionsDisabled) return;\n for (const [element, original] of this._savedTransitions) {\n element.style.transition = original;\n }\n this._transitionsDisabled = false;\n }\n\n /**\n * Clear all saved transition data (for kill/clear).\n */\n private _clearTransitionData(): void {\n this._restoreTransitions();\n this._savedTransitions.clear();\n this._transitionsDisabled = false;\n this._restoreWillChange();\n }\n\n /**\n * Restore original will-change values on elements promoted for transforms.\n */\n private _restoreWillChange(): void {\n for (const [element, original] of this._savedWillChange) {\n if (original) {\n element.style.willChange = original;\n } else {\n element.style.removeProperty('will-change');\n }\n }\n this._savedWillChange.clear();\n }\n\n /**\n * Internal: Add animation entry (used by Motion function)\n * @internal\n */\n _addEntry(target: TargetInput, config: AnimationConfig, position?: string | number): this {\n const startTime = PositionParser.parse(\n position,\n this._duration,\n this._previousStart,\n this._previousEnd\n );\n\n const builder = new AnimationBuilder(target, config);\n return this._addBuilder(builder, startTime);\n }\n\n /**\n * Internal method to add an AnimationBuilder directly\n */\n private _addBuilder(builder: AnimationBuilder, position: number): this {\n // Tell builder to skip Engine's immediate FROM render (animation.render(0)).\n // Timeline captures initial CSS values first via _captureInitialValues,\n // THEN renders the FROM state via renderAtTime(0) below.\n // Without this, Engine renders FROM values before capture, so kill()\n // would restore elements to FROM values instead of their true initial state.\n builder.setSkipInitialFromRender(true);\n\n const animations = builder.getAnimations();\n if (animations.length === 0) return this;\n\n let maxEndTime = position;\n for (const animation of animations) {\n const animDelay = animation.getDelay();\n const animStartTime = position + animDelay;\n animation.clearDelay();\n const animDuration = animation.totalDuration();\n\n const timelineChild: TimelineChild = {\n type: 'animation',\n child: animation,\n builder,\n startTime: animStartTime,\n duration: animDuration,\n };\n\n this._children.push(timelineChild);\n animation.setTimelineChild(true);\n animation.pause();\n this._captureInitialValues(animation);\n\n // Save original CSS transition values for later override during playback.\n // CSS transitions (e.g. `transition: transform 0.25s`) fight the SDK's\n // frame-by-frame rendering when both control the same property.\n // Values are saved here at build time; actual disable happens on timeline start.\n for (const target of animation.getTargets()) {\n if (target instanceof HTMLElement && !this._savedTransitions.has(target)) {\n this._savedTransitions.set(target, target.style.transition);\n }\n }\n\n // Promote elements with transform animations to their own compositor layer.\n // This prevents layout thrashing: without will-change, writing to\n // element.style.transform marks styles dirty, and any subsequent layout\n // read (e.g. scrollTo, getBoundingClientRect) forces a synchronous layout.\n // With will-change: transform, the browser composites the element on a\n // separate layer, so transform writes don't invalidate main-thread layout.\n // Saved/restored alongside transitions so cleanup is automatic.\n for (const target of animation.getTargets()) {\n if (target instanceof HTMLElement && this._transformElements.has(target) && !this._savedWillChange.has(target)) {\n this._savedWillChange.set(target, target.style.willChange);\n target.style.willChange = 'transform';\n }\n }\n\n const hasExplicitFrom = !!builder.getConfig().fromVars;\n if (animStartTime > 0 && !hasExplicitFrom) {\n animation.setLazyStartCapture(true);\n }\n\n // Immediately render from state for explicit-from animations to prevent\n // FOUC (Flash of Unstyled Content). Without this, elements remain at their\n // natural CSS state for one frame before the engine's rAF loop applies\n // the from values. Rendering synchronously ensures elements are hidden\n // (e.g. opacity: 0, y: '100%' behind a mask) before the browser paints.\n // Safe: animation is paused (_isActive=false) so no callbacks fire.\n if (hasExplicitFrom) {\n animation.renderAtTime(0);\n }\n\n // For split+mask animations, the from state targets split fragments (not\n // the original parent element). If the parent has a CSS pre-hide\n // (e.g. opacity: 0), it would block the fragments from ever becoming\n // visible — children cannot be more opaque than their parent. Override\n // the parent's opacity since the mask (overflow:hidden + y offset)\n // already handles visibility of the split fragments.\n if (builder.hasSplit()) {\n for (const target of builder.getOriginalTargets()) {\n if (target instanceof Element) {\n (target as HTMLElement).style.opacity = '1';\n }\n }\n }\n\n const endTime = animStartTime + animDuration;\n if (endTime > maxEndTime) {\n maxEndTime = endTime;\n }\n }\n\n this._previousStart = position;\n this._previousEnd = maxEndTime;\n\n if (maxEndTime > this._duration) {\n this._duration = maxEndTime;\n }\n\n return this;\n }\n\n /**\n * Call function at position\n */\n call(callback: (...args: unknown[]) => void, params?: unknown[], position?: string | number): this {\n const startTime = PositionParser.parse(\n position,\n this._duration,\n this._previousStart,\n this._previousEnd\n );\n\n const timelineChild: TimelineChild = {\n type: 'callback',\n child: callback,\n startTime,\n duration: 0,\n params,\n };\n\n this._children.push(timelineChild);\n\n // Update previous tracking\n this._previousStart = startTime;\n this._previousEnd = startTime;\n\n // Update timeline duration if callback is beyond current end\n if (startTime > this._duration) {\n this._duration = startTime;\n }\n\n return this;\n }\n\n /**\n * Get the first animation's target element for trigger inference\n * Note: Returns only DOM Elements, not plain objects\n */\n private _getFirstAnimationTarget(): Element | undefined {\n const firstAnimation = this.getFirstChildAnimation();\n if (firstAnimation) {\n const targets = firstAnimation.getTargets();\n // Find the first DOM element (skip plain objects)\n for (const target of targets) {\n if (isElement(target)) {\n return target;\n }\n }\n }\n return undefined;\n }\n\n /**\n * Get all target elements from the first animation (for each mode)\n * Note: Returns only DOM Elements, not plain objects\n *\n * When the builder uses text splitting (split: 'words' etc.), the animation's\n * targets are the split fragments — but for each-mode we need the ORIGINAL\n * pre-split elements so each gets its own ScrollTrigger. The cloned builder\n * will re-split inside that element independently.\n */\n private _getAllAnimationTargets(): Element[] {\n for (const child of this._children) {\n if (child.type === 'animation' && child.builder) {\n const targets = child.builder.hasSplit()\n ? child.builder.getOriginalTargets()\n : child.builder.getTargets();\n return targets.filter((target): target is Element => isElement(target));\n }\n }\n return [];\n }\n\n /**\n * Create a timeline instance for a single element (for each mode)\n * This clones the timeline structure but only targets the specific element.\n */\n private _createInstanceForElement(element: Element, index: number): Timeline {\n // Create a named timeline so it gets registered with Engine for ticking\n // Use parent name (if any) + unique suffix to avoid collisions\n const baseName = this._name || '__each';\n const instanceName = `${baseName}__each_${index}_${Timeline._eachCounter++}`;\n const instance = new Timeline(instanceName);\n\n const processedBuilders = new Set<AnimationBuilder>();\n\n // Clone each child animation for this specific element\n for (const child of this._children) {\n if (child.type === 'animation' && child.builder) {\n // Skip if we already processed this builder (stagger creates multiple children from one builder)\n if (processedBuilders.has(child.builder)) continue;\n processedBuilders.add(child.builder);\n\n const builderConfig = child.builder.getConfig();\n\n // Build repeat config if any repeat properties are set\n const repeatConfig = builderConfig.repeat != null\n ? { times: builderConfig.repeat, delay: builderConfig.repeatDelay, yoyo: builderConfig.yoyo }\n : undefined;\n\n // When the original builder uses split, pass split + stagger to the\n // clone so each element re-splits its own text and staggers internally.\n // Without split, stagger is omitted — each element animates independently.\n const hasSplit = child.builder.hasSplit();\n\n const newBuilder = new AnimationBuilder(element, {\n from: builderConfig.fromVars,\n to: builderConfig.toVars,\n duration: builderConfig.duration,\n delay: builderConfig.delay,\n ease: builderConfig.ease,\n repeat: repeatConfig,\n ...(hasSplit && builderConfig.split ? { split: builderConfig.split } : {}),\n ...(hasSplit && builderConfig.mask ? { mask: builderConfig.mask } : {}),\n ...(hasSplit && builderConfig.stagger !== undefined ? { stagger: builderConfig.stagger } : {}),\n onStart: builderConfig.onStart,\n onUpdate: builderConfig.onUpdate,\n onComplete: builderConfig.onComplete,\n onRepeat: builderConfig.onRepeat,\n onReverseComplete: builderConfig.onReverseComplete,\n });\n if (builderConfig.axis) {\n newBuilder.setAxis(builderConfig.axis);\n }\n\n // Add to instance at same position\n instance._addBuilder(newBuilder, child.startTime);\n } else if (child.type === 'callback') {\n // Clone callbacks too\n instance.call(\n child.child as (...args: unknown[]) => void,\n child.params,\n child.startTime\n );\n }\n // Note: nested timelines not supported in each mode for now\n }\n\n return instance;\n }\n\n /**\n * Create a timeline instance for multiple elements (for each mode with explicit container target).\n * This is a sibling of _createInstanceForElement — the only difference is that it accepts an\n * Element[] and passes the array directly to AnimationBuilder instead of a single element.\n */\n private _createInstanceForElements(elements: Element[], index: number): Timeline {\n const baseName = this._name || '__each';\n const instanceName = `${baseName}__each_${index}_${Timeline._eachCounter++}`;\n const instance = new Timeline(instanceName);\n\n const processedBuilders = new Set<AnimationBuilder>();\n\n for (const child of this._children) {\n if (child.type === 'animation' && child.builder) {\n if (processedBuilders.has(child.builder)) continue;\n processedBuilders.add(child.builder);\n\n const builderConfig = child.builder.getConfig();\n\n const repeatConfig = builderConfig.repeat != null\n ? { times: builderConfig.repeat, delay: builderConfig.repeatDelay, yoyo: builderConfig.yoyo }\n : undefined;\n\n const hasSplit = child.builder.hasSplit();\n\n const newBuilder = new AnimationBuilder(elements, {\n from: builderConfig.fromVars,\n to: builderConfig.toVars,\n duration: builderConfig.duration,\n delay: builderConfig.delay,\n ease: builderConfig.ease,\n repeat: repeatConfig,\n ...(hasSplit && builderConfig.split ? { split: builderConfig.split } : {}),\n ...(hasSplit && builderConfig.mask ? { mask: builderConfig.mask } : {}),\n ...(hasSplit && builderConfig.stagger !== undefined ? { stagger: builderConfig.stagger } : {}),\n onStart: builderConfig.onStart,\n onUpdate: builderConfig.onUpdate,\n onComplete: builderConfig.onComplete,\n onRepeat: builderConfig.onRepeat,\n onReverseComplete: builderConfig.onReverseComplete,\n });\n if (builderConfig.axis) {\n newBuilder.setAxis(builderConfig.axis);\n }\n\n instance._addBuilder(newBuilder, child.startTime);\n } else if (child.type === 'callback') {\n instance.call(\n child.child as (...args: unknown[]) => void,\n child.params,\n child.startTime\n );\n }\n // Note: nested timelines not supported in each mode for now\n }\n\n return instance;\n }\n\n /**\n * Resolve a CSS selector string or Element into an array of DOM elements.\n */\n private _resolveTargetElements(selector: string | Element): Element[] {\n if (typeof selector !== 'string') return [selector as Element];\n if (typeof document !== 'undefined') {\n try { return Array.from(document.querySelectorAll(selector)); }\n catch (e) { console.warn(`[Motion] Invalid selector \"${selector}\":`, e); }\n }\n return [];\n }\n\n /**\n * Generic helper for each-mode setup: resolve targets, create instances, register triggers.\n * The setupTrigger callback receives each (instance, element) pair for trigger-specific registration.\n */\n private _setupEachModeGeneric(\n targets: Element[],\n setupTrigger: (instance: Timeline, target: Element) => void\n ): void {\n if (targets.length === 0) return;\n\n this._eachInstances = [];\n\n // If the original builder used text splitting, the build already mutated the\n // DOM for ALL targets. Revert those splits now so each per-element clone can\n // split its own target independently from clean (original) HTML.\n const needsRevert = this._children.some(\n c => c.type === 'animation' && c.builder?.hasSplit()\n );\n if (needsRevert) {\n for (const el of targets) {\n SDKRegistry.textSplitter?.revert?.(el);\n }\n }\n\n // Clear transition data from the initial build.\n // Each per-element instance will independently save + manage transitions\n // for its own target during its own playback lifecycle.\n this._clearTransitionData();\n\n for (let i = 0; i < targets.length; i++) {\n const element = targets[i];\n const instance = this._createInstanceForElement(element, i);\n\n setupTrigger(instance, element);\n\n this._eachInstances.push(instance);\n }\n\n // Save pre-each duration for forwarding (control methods use this after children are cleared)\n this._eachDuration = this._duration;\n\n // CRITICAL: Clear the original timeline's animations so they don't compete\n // with the cloned per-element timelines. The original AnimationBuilder already\n // built PropTweens for ALL elements and rendered them (e.g., at opacity: 0).\n // If we leave them alive, they keep overwriting elements back to their FROM\n // state, causing only the first element to appear to animate. The original\n // timeline becomes just a container that holds _eachInstances references;\n // it should have NO active animations of its own.\n for (const child of this._children) {\n if (child.type === 'animation') {\n const animation = child.child as Animation;\n // Release from timeline ownership so it can be cleaned up\n animation.setTimelineChild(false);\n animation.kill();\n } else if (child.type === 'timeline') {\n (child.child as Timeline).kill();\n }\n }\n this._children = [];\n this._duration = 0;\n this._initialValues.clear();\n this._initialTransforms.clear();\n this._transformElements.clear();\n\n // Reset parent state after each-mode setup\n this._isActive = false;\n this._time = 0;\n this._progress = 0;\n this._isReversed = false;\n this._startFired = false;\n }\n\n /**\n * Set up each-mode: create independent timeline instances for each target element\n */\n private _setupEachMode(\n triggerType: 'hover' | 'click' | 'scroll',\n config: HoverConfig | ClickConfig | ScrollConfig\n ): void {\n const triggerManager = SDKRegistry.triggerManager?.getInstance?.();\n if (!triggerManager) return; // TriggerManager not loaded\n\n // Strip 'each' from config to prevent recursion in per-element instances\n const { each: _, ...cleanConfig } = config as ScrollConfig & { each?: boolean };\n\n // When an explicit container target is provided, scope animation targets to each container\n if (config.target) {\n const containers = this._resolveTargetElements(config.target as string | Element);\n if (containers.length === 0) return;\n\n const allAnimTargets = this._getAllAnimationTargets();\n\n // Revert text splits on animation targets so each container-scoped clone can re-split\n const needsRevert = this._children.some(c => c.type === 'animation' && c.builder?.hasSplit());\n if (needsRevert) {\n for (const el of allAnimTargets) { SDKRegistry.textSplitter?.revert?.(el); }\n }\n\n this._clearTransitionData();\n this._eachInstances = [];\n\n for (let i = 0; i < containers.length; i++) {\n const container = containers[i];\n const scopedTargets = allAnimTargets.filter(t => container.contains(t));\n if (scopedTargets.length === 0) continue;\n\n const instance = this._createInstanceForElements(scopedTargets, i);\n\n // Register trigger on the CONTAINER, not the animation targets\n if (triggerType === 'scroll') {\n triggerManager.registerScroll(instance, { ...cleanConfig, target: container } as ScrollConfig);\n } else if (triggerType === 'hover') {\n triggerManager.registerHover(instance, { ...cleanConfig, target: container as Element } as HoverConfig);\n } else {\n triggerManager.registerClick(instance, { ...cleanConfig, target: container as Element } as ClickConfig);\n }\n\n this._eachInstances.push(instance);\n }\n\n // Save pre-each duration for forwarding (control methods use this after children are cleared)\n this._eachDuration = this._duration;\n\n // CRITICAL: Clear the original timeline's animations so they don't compete\n // with the cloned per-container timelines.\n for (const child of this._children) {\n if (child.type === 'animation') {\n const animation = child.child as Animation;\n animation.setTimelineChild(false);\n animation.kill();\n } else if (child.type === 'timeline') {\n (child.child as Timeline).kill();\n }\n }\n this._children = [];\n this._duration = 0;\n this._initialValues.clear();\n this._initialTransforms.clear();\n this._transformElements.clear();\n\n // Reset parent state after each-mode setup\n this._isActive = false;\n this._time = 0;\n this._progress = 0;\n this._isReversed = false;\n this._startFired = false;\n return;\n }\n\n const targets = this._getAllAnimationTargets();\n\n this._setupEachModeGeneric(targets, (instance, element) => {\n if (triggerType === 'scroll') {\n triggerManager.registerScroll(instance, { ...cleanConfig, target: element as Element } as ScrollConfig);\n } else if (triggerType === 'hover') {\n triggerManager.registerHover(instance, { ...cleanConfig, target: element as Element } as HoverConfig);\n } else {\n triggerManager.registerClick(instance, { ...cleanConfig, target: element as Element } as ClickConfig);\n }\n });\n }\n\n /**\n * Trigger timeline on hover (mouseenter plays, mouseleave configurable)\n */\n onHover(config?: HoverConfig): this {\n if (config?.each) {\n this._setupEachMode('hover', config);\n return this;\n }\n\n const target = config?.target ?? this._getFirstAnimationTarget();\n SDKRegistry.triggerManager?.getInstance?.().registerHover(this, {\n target,\n onLeave: config?.onLeave,\n leaveDelay: config?.leaveDelay,\n });\n return this;\n }\n\n /**\n * Trigger timeline on click\n */\n onClick(config?: ClickConfig): this {\n if (config?.each) {\n this._setupEachMode('click', config);\n return this;\n }\n\n const target = config?.target ?? this._getFirstAnimationTarget();\n SDKRegistry.triggerManager?.getInstance?.().registerClick(this, {\n target,\n secondTarget: config?.secondTarget,\n toggle: config?.toggle,\n preventDefault: config?.preventDefault,\n });\n return this;\n }\n\n /**\n * Trigger timeline on scroll\n */\n onScroll(config?: ScrollConfig): this {\n if (config?.each) {\n this._setupEachMode('scroll', config);\n return this;\n }\n\n const target = config?.target ?? this._getFirstAnimationTarget();\n SDKRegistry.triggerManager?.getInstance?.().registerScroll(this, { ...config, target });\n return this;\n }\n\n /**\n * Trigger timeline on mouse movement\n * Drives animation progress based on mouse position (axis mode) or distance from center (distance mode)\n */\n onMouseMove(config?: MouseMoveConfig): this {\n // Handle each mode for mouse move (only with target)\n if (config?.each && config.target) {\n this._setupMouseMoveEachMode(config);\n return this;\n }\n\n SDKRegistry.triggerManager?.getInstance?.().registerMouseMove(this, {\n type: config?.type ?? 'distance',\n target: config?.target,\n smooth: config?.smooth,\n startProgress: config?.startProgress,\n leaveProgress: config?.leaveProgress,\n });\n return this;\n }\n\n /**\n * Set up each-mode for mouse movement: create independent timeline instances for each target element\n */\n private _setupMouseMoveEachMode(config: MouseMoveConfig): void {\n if (!config.target) return;\n\n // Resolve target elements\n let targets: Element[] = [];\n if (typeof config.target === 'string') {\n if (typeof document !== 'undefined') {\n targets = Array.from(document.querySelectorAll(config.target));\n }\n } else {\n targets = [config.target as Element];\n }\n\n const triggerManager = SDKRegistry.triggerManager?.getInstance?.();\n if (!triggerManager) return; // TriggerManager not loaded\n\n this._setupEachModeGeneric(targets, (instance, element) => {\n triggerManager.registerMouseMove(instance, {\n type: config.type ?? 'distance',\n target: element,\n smooth: config.smooth,\n startProgress: config.startProgress,\n leaveProgress: config.leaveProgress,\n });\n });\n }\n\n /**\n * Trigger timeline on page load\n */\n onPageLoad(): this {\n SDKRegistry.triggerManager?.getInstance?.().registerPageLoad(this, {});\n return this;\n }\n\n /**\n * Trigger timeline when user navigates away from the page (clicks a link).\n * Intercepts the click, plays the exit animation, then navigates to the target URL.\n */\n onPageExit(config?: PageExitConfig): this {\n SDKRegistry.triggerManager?.getInstance?.().registerPageExit(this, config || {});\n return this;\n }\n\n /**\n * Trigger timeline based on user gestures (pointer, touch, wheel, scroll)\n * Maps directional callbacks to timeline actions\n */\n onGesture(config: GestureConfig): this {\n // Handle each mode for gesture\n if (config.each) {\n this._setupGestureEachMode(config);\n return this;\n }\n\n SDKRegistry.triggerManager?.getInstance?.().registerGesture(this, config);\n return this;\n }\n\n /**\n * Set up each-mode for gesture: create independent instances for each target element\n */\n private _setupGestureEachMode(config: GestureConfig): void {\n // Resolve target elements\n let targets: Element[] = [];\n\n if (config.target) {\n if (typeof config.target === 'string') {\n if (typeof document !== 'undefined') {\n targets = Array.from(document.querySelectorAll(config.target));\n }\n } else {\n targets = [config.target as Element];\n }\n } else {\n // If no target specified, use animation targets\n targets = this._getAllAnimationTargets();\n }\n\n // First pass: create all instances (using generic helper with a no-op trigger setup)\n this._setupEachModeGeneric(targets, () => {});\n\n if (!this._eachInstances || this._eachInstances.length === 0) return;\n\n // Second pass: register triggers with sibling info for playNext/playPrevious\n const triggerManager = SDKRegistry.triggerManager?.getInstance?.();\n if (!triggerManager) return; // TriggerManager not loaded\n\n for (let i = 0; i < targets.length; i++) {\n const element = targets[i];\n const instance = this._eachInstances[i];\n\n triggerManager.registerGesture(instance, {\n ...config,\n target: element,\n each: false, // Prevent recursion\n _siblings: this._eachInstances, // Pass all siblings for sequence control\n _index: i, // Current index in sequence\n } as GestureConfigInternal);\n }\n }\n\n /**\n * Attach cursor behavior to this timeline's target element\n * Creates a custom cursor that follows mouse with smooth tracking and state-based animations\n */\n onCursor(config: CursorConfig): this {\n SDKRegistry.triggerManager?.getInstance?.().registerCursor(this, config);\n return this;\n }\n\n /**\n * Update a callback child: fire when time crosses its position\n */\n private _updateCallbackChild(child: TimelineChild, prevTime: number, deltaTime: number): void {\n const childStartTime = child.startTime;\n\n // Execute callback when time crosses its position (from either direction)\n // or when seeking directly to the callback position\n const crossedForward = prevTime < childStartTime && this._time >= childStartTime;\n const crossedBackward = prevTime > childStartTime && this._time <= childStartTime;\n const atPosition = Math.abs(this._time - childStartTime) < 0.001; // Within 1ms\n\n if (crossedForward || crossedBackward || (atPosition && deltaTime === 0)) {\n const callback = child.child as (...args: unknown[]) => void;\n const params = child.params || [];\n callback(...params);\n }\n }\n\n /**\n * Update an animation child: seek/activate/deactivate based on parent time\n */\n private _updateAnimationChild(child: TimelineChild, deltaTime: number): void {\n const animation = child.child as Animation;\n const timeScale = Math.abs(animation.getTimeScale()) || 1;\n const scaledDuration = animation.totalDuration() / timeScale;\n\n // Calculate local time within the animation\n const localTime = this._time - child.startTime;\n const scaledLocalTime = localTime * timeScale;\n\n if (localTime >= 0 && localTime <= scaledDuration) {\n // Animation should be active\n if (!animation.isActive()) {\n // Capture start values from current element state (for chained animations)\n animation.captureStartValues();\n animation.play();\n }\n\n // Seek animation to local time\n animation.renderAtTime(scaledLocalTime);\n } else if (localTime < 0) {\n // Animation hasn't started yet\n // Only render at start state if it has explicit FROM values (not lazy capture)\n // Lazy-capture animations should NEVER render before they start,\n // even after values have been captured, to avoid overwriting earlier animations\n if (!animation.isLazyCapture()) {\n animation.renderAtTime(0);\n }\n if (animation.isActive()) {\n animation.pause();\n // Fire onReverseComplete when an active animation exits the start boundary\n // during reverse playback. Also resets _startFired/_completeFired for next play.\n animation.fireReverseComplete();\n }\n } else if (animation.isActive()) {\n // Animation past its end - pause at final state\n animation.renderAtTime(animation.totalDuration());\n animation.pause();\n } else if (deltaTime === 0) {\n // Seeking past animation end - render at final state\n animation.renderAtTime(animation.totalDuration());\n }\n }\n\n /**\n * Update a nested timeline child: seek/activate/deactivate based on parent time\n */\n private _updateTimelineChild(child: TimelineChild, deltaTime: number): void {\n const timeline = child.child as Timeline;\n const actualDuration = timeline.duration();\n\n // Calculate local time within the timeline\n const localTime = this._time - child.startTime;\n\n if (localTime >= 0 && localTime <= actualDuration) {\n // Timeline should be active\n if (!timeline.isActive()) {\n timeline.play();\n }\n\n // Seek timeline to local time\n timeline.time(localTime);\n } else if (timeline.isActive()) {\n // Timeline should not be active - pause it at boundary\n if (localTime < 0) {\n timeline.time(0);\n timeline.pause();\n } else {\n timeline.time(actualDuration);\n timeline.pause();\n }\n } else if (deltaTime === 0) {\n // Only render boundary state when SEEKING (not during normal playback)\n if (localTime < 0) {\n // Timeline hasn't started yet - don't render during seek\n } else {\n timeline.time(actualDuration);\n }\n }\n // During normal playback, don't render inactive timelines outside their range\n }\n\n /**\n * Update timeline by delta time\n */\n update(deltaTime: number): void {\n // In each-mode, forward updates to instances\n if (this._eachInstances) {\n for (const inst of this._eachInstances) inst.update(deltaTime);\n return;\n }\n\n // Store previous time before updating\n const prevTime = this._time;\n\n if (!this._isActive && deltaTime === 0) {\n // This is a seek operation, still need to update children\n } else if (!this._isActive) {\n return;\n }\n\n // Apply time scale\n const scaledDelta = deltaTime * this._timeScale;\n\n // Update time\n this._time += this._isReversed ? -scaledDelta : scaledDelta;\n\n // Clamp time\n if (this._time < 0) {\n this._time = 0;\n } else if (this._time > this._duration) {\n this._time = this._duration;\n }\n\n // Update progress\n this._progress = this._duration > 0 ? this._time / this._duration : 0;\n\n // Fire onStart callback on first update\n if (!this._startFired && this._isActive && this._progress > 0) {\n this._startFired = true;\n // Disable CSS transitions during active playback to prevent interference\n this._disableTransitions();\n this._onStart?.();\n }\n\n // Fire onUpdate callback\n if (this._isActive) {\n this._onUpdate?.(this._progress, this._time);\n }\n\n // Update children - dispatch to type-specific helpers\n for (const child of this._children) {\n if (child.type === 'callback') {\n this._updateCallbackChild(child, prevTime, deltaTime);\n } else if (child.type === 'animation') {\n this._updateAnimationChild(child, deltaTime);\n } else if (child.type === 'timeline') {\n this._updateTimelineChild(child, deltaTime);\n }\n }\n\n // Check if complete\n if (!this._isReversed && this._time >= this._duration) {\n this._isActive = false;\n this._startFired = false; // Reset so next play/reverse fires onStart\n // Restore CSS transitions now that animation is done.\n // If the timeline replays (hover, restart), _disableTransitions re-fires on next onStart.\n this._restoreTransitions();\n this._onComplete?.();\n } else if (this._isReversed && this._time <= 0) {\n this._isActive = false;\n this._startFired = false; // Reset so next play/reverse fires onStart\n this._restoreTransitions();\n this._onComplete?.();\n }\n }\n\n // Control methods\n\n /**\n * Play timeline from position.\n * If already playing forward, continues from current position.\n */\n play(from?: number): this {\n if (this._killed) {\n console.warn('[Motion] Cannot play a killed timeline. Create a new one with Motion(name).');\n return this;\n }\n\n if (this._eachInstances) {\n for (const inst of this._eachInstances) inst.play(from);\n return this;\n }\n\n if (from !== undefined) {\n this._time = from;\n this._progress = this._duration > 0 ? this._time / this._duration : 0;\n }\n\n this._isReversed = false;\n this._isActive = true;\n\n return this;\n }\n\n /**\n * Pause timeline at time\n */\n pause(atTime?: number): this {\n if (this._killed) {\n console.warn('[Motion] Cannot pause a killed timeline. Create a new one with Motion(name).');\n return this;\n }\n\n if (this._eachInstances) {\n for (const inst of this._eachInstances) inst.pause(atTime);\n return this;\n }\n\n if (atTime !== undefined) {\n this._time = atTime;\n this._progress = this._duration > 0 ? this._time / this._duration : 0;\n }\n\n this._isActive = false;\n\n // Pause all children\n for (const child of this._children) {\n if (child.type !== 'callback') {\n const animation = child.child as Animation | Timeline;\n if ('pause' in animation) {\n animation.pause();\n }\n }\n }\n\n return this;\n }\n\n /**\n * Reverse timeline from position.\n * If already reversing, continues from current position.\n */\n reverse(from?: number): this {\n if (this._killed) {\n console.warn('[Motion] Cannot reverse a killed timeline. Create a new one with Motion(name).');\n return this;\n }\n\n if (this._eachInstances) {\n for (const inst of this._eachInstances) inst.reverse(from);\n return this;\n }\n\n if (from !== undefined) {\n this._time = from;\n this._progress = this._duration > 0 ? this._time / this._duration : 0;\n }\n\n this._isReversed = true;\n this._isActive = true;\n\n return this;\n }\n\n /**\n * Restart timeline\n */\n restart(): this {\n if (this._killed) {\n console.warn('[Motion] Cannot restart a killed timeline. Create a new one with Motion(name).');\n return this;\n }\n\n if (this._eachInstances) {\n for (const inst of this._eachInstances) inst.restart();\n return this;\n }\n\n this._time = 0;\n this._progress = 0;\n this._isActive = true;\n this._isReversed = false;\n this._startFired = false;\n\n // Reset callback flags for clean replay\n for (const child of this._children) {\n if (child.type === 'animation') {\n (child.child as Animation).resetForReplay();\n }\n }\n\n // Restore all properties to initial values, then render position-0 animations\n this._restoreInitialValues();\n this._renderPositionZeroAnimations();\n\n return this;\n }\n\n /**\n * Render only position-0 (non-lazy) animations at their start state\n */\n private _renderPositionZeroAnimations(): void {\n for (const child of this._children) {\n if (child.type === 'animation') {\n const animation = child.child as Animation;\n // Only render non-lazy animations (they have explicit FROM or are at position 0)\n if (!animation.isLazyCapture()) {\n animation.renderAtTime(0);\n }\n if (animation.isActive()) {\n animation.pause();\n }\n } else if (child.type === 'timeline') {\n (child.child as Timeline).progress(0);\n }\n }\n }\n\n /**\n * Seek to position\n */\n seek(position: number): this {\n if (this._killed) {\n console.warn('[Motion] Cannot seek a killed timeline. Create a new one with Motion(name).');\n return this;\n }\n\n if (this._eachInstances) {\n for (const inst of this._eachInstances) inst.seek(position);\n return this;\n }\n\n this._time = Math.max(0, Math.min(position, this._duration));\n this._progress = this._duration > 0 ? this._time / this._duration : 0;\n\n // Update all children to match seek position\n this.update(0);\n\n return this;\n }\n\n /**\n * Set progress for animations bound to a specific axis (for mouse movement trigger)\n * Only affects animations that have .axis() set to the specified axis.\n * @param axis - 'x' or 'y'\n * @param value - Progress value (0-1)\n */\n setAxisProgress(axis: 'x' | 'y', value: number): this {\n const clampedValue = Math.max(0, Math.min(1, value));\n\n for (const child of this._children) {\n if (child.type === 'animation' && child.builder) {\n const builderAxis = child.builder.getConfig().axis;\n if (builderAxis === axis) {\n const animation = child.child as Animation;\n const duration = animation.totalDuration();\n\n // Ensure animation is initialized (capture start values for TO-only animations)\n if (!animation.isActive()) {\n animation.captureStartValues();\n animation.play();\n }\n\n // Set animation time based on progress\n animation.renderAtTime(clampedValue * duration);\n }\n }\n }\n\n return this;\n }\n\n /**\n * Set callback for when timeline starts playing\n */\n onStart(callback: () => void): this {\n this._onStart = callback;\n return this;\n }\n\n /**\n * Set callback for progress updates (called every frame while active)\n * @param callback - Receives (progress: 0-1, time: seconds)\n */\n onUpdate(callback: (progress: number, time: number) => void): this {\n this._onUpdate = callback;\n return this;\n }\n\n /**\n * Set callback for when timeline completes\n */\n onComplete(callback: () => void): this {\n this._onComplete = callback;\n return this;\n }\n\n /**\n * Get or set progress (0-1)\n */\n progress(): number;\n progress(value: number): this;\n progress(value?: number): number | this {\n if (value === undefined) {\n // Getter: aggregate from each instances if in each-mode\n if (this._eachInstances && this._eachInstances.length > 0) {\n let maxProgress = 0;\n for (const inst of this._eachInstances) {\n const p = inst.progress();\n if (p > maxProgress) maxProgress = p;\n }\n return maxProgress;\n }\n return this._progress;\n }\n\n // Setter\n if (this._eachInstances) {\n for (const inst of this._eachInstances) inst.progress(value);\n return this;\n }\n\n const clampedValue = Math.max(0, Math.min(1, value));\n\n // Reset callback flags when seeking to beginning\n if (clampedValue === 0) {\n for (const child of this._children) {\n if (child.type === 'animation') {\n (child.child as Animation).resetForReplay();\n }\n }\n }\n\n this._time = clampedValue * this._duration;\n this._progress = clampedValue;\n\n // When seeking to position 0, restore initial values then render position-0 animations\n if (clampedValue === 0) {\n this._restoreInitialValues();\n this._renderPositionZeroAnimations();\n } else {\n this.update(0);\n }\n\n // Fire onUpdate callback when progress is explicitly set (e.g., scroll scrubbing)\n // This ensures playhead sync even when timeline isn't \"active\"\n this._onUpdate?.(this._progress, this._time);\n\n return this;\n }\n\n /**\n * Get or set time in seconds\n */\n time(): number;\n time(value: number): this;\n time(value?: number): number | this {\n if (value === undefined) {\n if (this._eachInstances && this._eachInstances.length > 0) {\n let maxTime = 0;\n for (const inst of this._eachInstances) {\n const t = inst.time();\n if (t > maxTime) maxTime = t;\n }\n return maxTime;\n }\n return this._time;\n }\n\n if (this._eachInstances) {\n for (const inst of this._eachInstances) inst.time(value);\n return this;\n }\n\n this._time = Math.max(0, Math.min(value, this._duration));\n this._progress = this._duration > 0 ? this._time / this._duration : 0;\n\n // Update all children\n this.update(0);\n\n return this;\n }\n\n /**\n * Get or set time scale (speed multiplier)\n */\n timeScale(): number;\n timeScale(value: number): this;\n timeScale(value?: number): number | this {\n if (value === undefined) {\n return this._timeScale;\n }\n\n this._timeScale = value;\n\n return this;\n }\n\n /**\n * Kill timeline and all children, reset all state\n * @param clearProps - If true (default), restore elements to their initial CSS values\n */\n kill(clearProps: boolean = true): void {\n this._killed = true;\n this._isActive = false;\n\n // Clean up attached triggers (including page load)\n SDKRegistry.triggerManager?.getInstance?.().unregister(this);\n\n // Clean up each-mode instances\n if (this._eachInstances) {\n for (const instance of this._eachInstances) {\n instance.kill(clearProps);\n }\n this._eachInstances = undefined;\n }\n\n // Restore CSS transitions and clear saved data before restoring initial values\n this._clearTransitionData();\n\n // Restore initial CSS values BEFORE killing animations\n // This resets elements to their state before the timeline was created\n // Check both _initialValues (CSS props) AND _transformElements (transform props like rotation from path)\n if (clearProps && (this._initialValues.size > 0 || this._transformElements.size > 0)) {\n this._restoreInitialValues();\n }\n\n // Release and kill all child animations (returns them to pool)\n for (const child of this._children) {\n if (child.type === 'animation') {\n const animation = child.child as Animation;\n // Release from timeline ownership so it can be pooled\n animation.setTimelineChild(false);\n animation.kill();\n } else if (child.type === 'timeline') {\n (child.child as Timeline).kill(clearProps);\n }\n }\n\n // Unregister from Engine if this is a named timeline\n if (this._unregisterCallback) {\n this._unregisterCallback();\n this._unregisterCallback = undefined;\n }\n\n // Reset all state so timeline can be reused cleanly\n this._children = [];\n this._initialValues.clear();\n this._initialTransforms.clear();\n this._transformElements.clear();\n this._savedTransitions.clear();\n this._savedWillChange.clear();\n this._transitionsDisabled = false;\n this._duration = 0;\n this._time = 0;\n this._progress = 0;\n this._previousStart = 0;\n this._previousEnd = 0;\n this._isReversed = false;\n this._startFired = false;\n }\n\n /**\n * Check if timeline is currently active\n */\n isActive(): boolean {\n if (this._eachInstances) {\n return this._eachInstances.some(inst => inst.isActive());\n }\n return this._isActive;\n }\n\n /**\n * Get timeline name\n */\n getName(): string | undefined {\n return this._name;\n }\n\n /**\n * Get first child animation (for trigger target resolution).\n * @internal\n */\n getFirstChildAnimation(): Animation | null {\n const firstChild = this._children[0];\n if (firstChild?.type === 'animation') {\n return firstChild.child as Animation;\n }\n return null;\n }\n}\n\n\n",
|
|
33
|
+
"/**\n * QuickSetter - High-performance property setter for per-frame updates\n *\n * Bypasses the full animation pipeline for maximum speed.\n * Used by CursorTrigger for smooth position tracking.\n */\n\nimport { setTransformValue, buildTransformString } from '../render/TransformCache';\nimport { isTransformProp } from './PropertyParser';\n\nexport type QuickSetterFn = (value: number) => void;\n\n/**\n * Create a high-performance property setter for per-frame updates.\n * Returns a function that sets the value directly without animation overhead.\n *\n * @param target - The element to animate\n * @param property - CSS property name (camelCase)\n * @param unit - Unit to append (default: 'px' for transforms, '' for others)\n */\nexport function createQuickSetter(\n target: Element,\n property: string,\n unit?: string\n): QuickSetterFn {\n const htmlEl = target as HTMLElement;\n\n // Transform properties use the cache system\n if (isTransformProp(property)) {\n const resolvedUnit = unit ?? (property.startsWith('scale') ? '' : 'px');\n return (value: number) => {\n setTransformValue(target, property, value, resolvedUnit);\n htmlEl.style.transform = buildTransformString(target);\n };\n }\n\n // CSS custom properties (variables)\n if (property.startsWith('--')) {\n const resolvedUnit = unit ?? '';\n return (value: number) => {\n htmlEl.style.setProperty(property, `${value}${resolvedUnit}`);\n };\n }\n\n // Regular CSS properties\n const kebabProp = property.replace(/([A-Z])/g, '-$1').toLowerCase();\n const resolvedUnit = unit ?? '';\n\n return (value: number) => {\n htmlEl.style.setProperty(kebabProp, `${value}${resolvedUnit}`);\n };\n}\n\n",
|
|
34
|
+
"/**\n * CursorTrigger - Custom cursor controller\n *\n * Features:\n * - Smooth position tracking with configurable lag\n * - State-based animations (default, hover, click)\n * - Velocity-based squeeze effect\n * - Text and media cursor variants\n */\n\nimport type { CursorConfig, CursorStateVars, CursorSqueezeConfig } from '../types';\nimport { Timeline } from '../core/Timeline';\nimport { Ticker } from '../core/Ticker';\nimport { createQuickSetter, type QuickSetterFn } from '../utils/QuickSetter';\nimport { camelToKebab } from '../utils/PropertyParser';\nimport { setTransformValue, buildTransformString } from '../render/TransformCache';\nimport { TriggerManager } from './TriggerManager';\nimport { BaseTrigger } from './BaseTrigger';\nimport { getLayoutRect } from '../utils/getLayoutRect';\n\ntype CursorState = 'default' | 'hover' | 'click';\n\n/**\n * Validate that a media URL is safe to load (http/https or relative path).\n * Blocks file://, data:, javascript:, and internal network URLs.\n */\nfunction isSafeMediaUrl(src: string): boolean {\n // SSR guard: if location is unavailable, only allow relative paths (no protocol)\n if (typeof location === 'undefined') {\n return !src.includes(':');\n }\n try {\n const url = new URL(src, location.href);\n return url.protocol === 'https:' || url.protocol === 'http:';\n } catch {\n // Relative paths without a protocol are safe\n return !src.includes(':');\n }\n}\n\ninterface ParsedStateConfig {\n properties: Record<string, string | number>;\n duration: number;\n ease: string;\n targets?: string[];\n enabled: boolean;\n}\n\nexport class CursorTrigger extends BaseTrigger<CursorConfig> {\n private _element: HTMLElement | null = null;\n private _innerElement: HTMLElement | null = null;\n\n // Position tracking\n private _mousePos = { x: -100, y: -100 };\n private _cursorPos = { x: -100, y: -100 };\n private _xSetter: QuickSetterFn | null = null;\n private _ySetter: QuickSetterFn | null = null;\n\n // Squeeze effect\n private _squeezeSetter: QuickSetterFn | null = null;\n private _squeezeConfig: CursorSqueezeConfig = { min: 0.55, max: 1.2, multiplier: 150 };\n\n // State management\n private _state: CursorState = 'default';\n private _stateTimelines: Map<CursorState, Timeline> = new Map();\n private _parsedStates: Map<CursorState, ParsedStateConfig> = new Map();\n\n // Special cursor elements\n private _textElement: HTMLElement | null = null;\n private _mediaElement: HTMLImageElement | HTMLVideoElement | null = null;\n\n // Event listeners\n private _tickerCallback?: (dt: number) => void;\n private _boundMouseMove?: (e: MouseEvent) => void;\n private _boundMouseDown?: () => void;\n private _boundMouseUp?: () => void;\n\n // Hover frozen-rect state for hover targets.\n // Bounds are snapshotted at rest so that animations on target elements don't\n // cause infinite enter/leave jitter if those elements are also animated.\n private _hoverFrozenRects: Map<Element, DOMRect> = new Map();\n private _isHoveringCursorTarget: boolean = false;\n private _boundHoverTargetMoveHandler: ((e: MouseEvent) => void) | null = null;\n\n // Legacy map kept only for _removeEventListeners() compatibility (always empty now).\n private _hoverListeners: Map<Element, { enter: () => void; leave: () => void }> = new Map();\n\n // Text/media attribute listeners\n private _attributeListeners: Map<Element, { enter: (e: Event) => void; leave: () => void }> = new Map();\n\n constructor(timeline: Timeline, config: CursorConfig) {\n super(timeline, config);\n this._config = {\n type: 'basic',\n smooth: 0.25,\n hideNative: false,\n ...config,\n };\n\n // Parse squeeze config\n if (config.squeeze === true) {\n this._squeezeConfig = { min: 0.55, max: 1.2, multiplier: 150 };\n } else if (typeof config.squeeze === 'object') {\n this._squeezeConfig = {\n min: config.squeeze.min ?? 0.55,\n max: config.squeeze.max ?? 1.2,\n multiplier: config.squeeze.multiplier ?? 150,\n };\n }\n\n // Parse state configs\n this._parsedStates.set('default', this._parseStateConfig(config.default));\n if (config.hover) {\n this._parsedStates.set('hover', this._parseStateConfig(config.hover));\n }\n if (config.click) {\n this._parsedStates.set('click', this._parseStateConfig(config.click));\n }\n }\n\n private _parseStateConfig(config: CursorStateVars): ParsedStateConfig {\n const { targets, duration, ease, enabled, ...properties } = config;\n return {\n properties: properties as Record<string, string | number>,\n duration: duration ?? 0.15,\n ease: ease ?? 'power3.inOut',\n targets,\n enabled: enabled ?? true,\n };\n }\n\n /**\n * Set up the cursor: resolve element, create setters, attach listeners, start ticker.\n *\n * Architecture (mirrors original GSAP cursor):\n * - Container (_element): position:fixed, zero-size, moved via x/y QuickSetters\n * - Inner element (_innerElement): centered via translate(-50%, -50%),\n * receives all visual CSS properties and state transitions\n */\n protected _onEnable(): void {\n // Get target element from timeline (this is the container)\n this._element = this._getTargetElement();\n if (!this._element) return;\n\n // Create inner element for visual styling, centered on the container origin.\n // Centering uses TransformCache (not inline CSS) so it composes correctly\n // with transform animations (e.g. scale) on hover/click states.\n this._innerElement = document.createElement('div');\n this._innerElement.setAttribute('data-cursor-inner', '');\n this._innerElement.style.cssText = 'pointer-events:none;position:absolute;top:0;left:0;';\n this._element.appendChild(this._innerElement);\n\n // Set centering via TransformCache so it composes with transform animations\n setTransformValue(this._innerElement, 'x', -50, '%');\n setTransformValue(this._innerElement, 'y', -50, '%');\n\n // Create quick setters for position on the container\n this._xSetter = createQuickSetter(this._element, 'x', 'px');\n this._ySetter = createQuickSetter(this._element, 'y', 'px');\n\n // Create squeeze setter if enabled (on inner element for visual effect)\n if (this._config.squeeze) {\n this._squeezeSetter = createQuickSetter(this._element, 'scale');\n }\n\n // Set initial position (off-screen)\n this._cursorPos = { x: -100, y: -100 };\n this._mousePos = { x: -100, y: -100 };\n this._xSetter(-100);\n this._ySetter(-100);\n\n // Apply default properties to the inner element\n // (may include transform shorthand which gets expanded via TransformCache)\n this._applyProperties(this._parsedStates.get('default')!.properties);\n\n // Ensure centering transform (and any default transforms) are flushed to DOM\n this._innerElement.style.transform = buildTransformString(this._innerElement);\n\n // Hide native cursor\n if (this._config.hideNative) {\n document.body.style.cursor = 'none';\n }\n\n // Setup special cursor types\n if (this._config.type === 'text') {\n this._setupTextCursor();\n } else if (this._config.type === 'media') {\n this._setupMediaCursor();\n }\n\n // Create state transition timelines\n this._createStateTimelines();\n\n // Add event listeners\n this._setupEventListeners();\n\n // Start ticker for position updates\n this._tickerCallback = (dt) => this._tick(dt);\n Ticker.getInstance().add(this._tickerCallback);\n }\n\n /**\n * Tear down the cursor: remove ticker, restore native cursor, remove listeners.\n */\n protected _onDisable(): void {\n // Remove ticker\n if (this._tickerCallback) {\n Ticker.getInstance().remove(this._tickerCallback);\n this._tickerCallback = undefined;\n }\n\n // Restore native cursor\n if (this._config.hideNative) {\n document.body.style.cursor = '';\n }\n\n // Remove event listeners\n this._removeEventListeners();\n\n // Kill state timelines\n this._stateTimelines.forEach(tl => tl.kill());\n this._stateTimelines.clear();\n\n // Remove inner element from DOM\n if (this._innerElement?.parentNode) {\n this._innerElement.remove();\n }\n this._innerElement = null;\n\n // Remove container element from DOM as safety net.\n // The caller (cursor connector) also calls element.remove(), but doing it\n // here ensures cleanup even if the connector's destroy path doesn't run.\n if (this._element?.parentNode) {\n this._element.remove();\n }\n this._element = null;\n }\n\n private _getTargetElement(): HTMLElement | null {\n // Try to resolve from config using base helper\n const resolved = this._resolveElement(this._config.target);\n if (resolved instanceof HTMLElement) return resolved;\n\n // Fallback: try to get from timeline's first animation\n const firstAnim = this._timeline.getFirstChildAnimation();\n if (firstAnim) {\n const targets = firstAnim.getTargets();\n if (targets[0] instanceof HTMLElement) {\n return targets[0];\n }\n }\n return null;\n }\n\n private _applyProperties(properties: Record<string, string | number>): void {\n if (!this._innerElement) return;\n\n for (const [key, value] of Object.entries(properties)) {\n if (key === 'transform') {\n // Expand CSS transform shorthand (e.g. \"scale(2)\") into individual\n // SDK transform properties and apply via TransformCache so they\n // compose with the centering translate(-50%, -50%).\n const expanded = CursorTrigger._expandTransformShorthand(String(value));\n for (const [prop, val] of Object.entries(expanded)) {\n setTransformValue(this._innerElement!, prop, val);\n }\n // Flush is handled by _onEnable after this call\n } else {\n this._innerElement.style.setProperty(camelToKebab(key), String(value));\n }\n }\n }\n\n private _createStateTimelines(): void {\n // State transitions target the inner element (visual styling)\n if (!this._innerElement) return;\n\n // Create hover timeline\n const hoverConfig = this._parsedStates.get('hover');\n if (hoverConfig && hoverConfig.enabled) {\n const hoverTl = new Timeline();\n hoverTl._addEntry(this._innerElement, {\n to: CursorTrigger._expandStateProperties(hoverConfig.properties),\n duration: hoverConfig.duration,\n ease: hoverConfig.ease,\n });\n this._stateTimelines.set('hover', hoverTl);\n }\n\n // Create click timeline\n const clickConfig = this._parsedStates.get('click');\n if (clickConfig && clickConfig.enabled) {\n const clickTl = new Timeline();\n clickTl._addEntry(this._innerElement, {\n to: CursorTrigger._expandStateProperties(clickConfig.properties),\n duration: clickConfig.duration,\n ease: clickConfig.ease,\n });\n this._stateTimelines.set('click', clickTl);\n }\n }\n\n /**\n * Expand CSS transform shorthand in state properties into individual\n * SDK transform properties that the animation system can interpolate.\n * E.g., {transform: 'scale(2)', width: '80px'} → {scale: 2, width: '80px'}\n */\n private static _expandStateProperties(properties: Record<string, string | number>): Record<string, string | number> {\n const result: Record<string, string | number> = {};\n for (const [key, value] of Object.entries(properties)) {\n if (key === 'transform') {\n const expanded = CursorTrigger._expandTransformShorthand(String(value));\n Object.assign(result, expanded);\n } else {\n result[key] = value;\n }\n }\n return result;\n }\n\n /**\n * Parse CSS transform shorthand string into individual SDK transform properties.\n * Handles: scale, scaleX/Y, rotate, rotateX/Y/Z, skewX/Y\n * E.g., \"scale(2)\" → {scale: 2}\n * \"scale(2, 3)\" → {scaleX: 2, scaleY: 3}\n * \"scale(2) rotate(45deg)\" → {scale: 2, rotate: 45}\n */\n private static _expandTransformShorthand(transformStr: string): Record<string, number> {\n const result: Record<string, number> = {};\n const regex = /([a-zA-Z0-9]+)\\(([^)]+)\\)/g;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(transformStr)) !== null) {\n const fn = match[1];\n const args = match[2].split(',').map(s => parseFloat(s.trim()));\n\n switch (fn) {\n case 'scale': {\n const x = args[0] ?? 1;\n const y = args.length > 1 ? args[1] : x;\n if (x === y) {\n result.scale = x;\n } else {\n result.scaleX = x;\n result.scaleY = y;\n }\n break;\n }\n case 'scaleX':\n result.scaleX = args[0] ?? 1;\n break;\n case 'scaleY':\n result.scaleY = args[0] ?? 1;\n break;\n case 'scaleZ':\n result.scaleZ = args[0] ?? 1;\n break;\n case 'rotate':\n result.rotate = args[0] ?? 0;\n break;\n case 'rotateX':\n result.rotateX = args[0] ?? 0;\n break;\n case 'rotateY':\n result.rotateY = args[0] ?? 0;\n break;\n case 'rotateZ':\n result.rotateZ = args[0] ?? 0;\n break;\n case 'skewX':\n result.skewX = args[0] ?? 0;\n break;\n case 'skewY':\n result.skewY = args[0] ?? 0;\n break;\n }\n }\n\n return result;\n }\n\n private _setupEventListeners(): void {\n // Mouse move (always needed for position tracking)\n this._boundMouseMove = (e) => {\n this._mousePos.x = e.clientX;\n this._mousePos.y = e.clientY;\n };\n document.addEventListener('mousemove', this._boundMouseMove);\n\n // Mouse down/up for click state\n const clickConfig = this._parsedStates.get('click');\n if (clickConfig?.enabled) {\n this._boundMouseDown = () => this._setState('click');\n this._boundMouseUp = () => this._setState(this._state === 'click' ? 'default' : this._state);\n document.addEventListener('mousedown', this._boundMouseDown);\n document.addEventListener('mouseup', this._boundMouseUp);\n }\n\n // Hover targets\n const hoverConfig = this._parsedStates.get('hover');\n if (hoverConfig?.enabled && hoverConfig.targets && hoverConfig.targets.length > 0) {\n this._setupHoverTargets(hoverConfig.targets);\n }\n }\n\n private _setupHoverTargets(targets: string[]): void {\n // Collect all hover target elements and snapshot their bounds at rest.\n // Using frozen rects prevents jitter when target elements are also animated\n // (e.g. a button that scales on hover would cause live bounds to shift,\n // triggering spurious enter/leave events if we used native mouseenter/mouseleave).\n for (const selector of targets) {\n let elements: NodeListOf<Element>;\n try {\n elements = document.querySelectorAll(selector);\n } catch (e) {\n console.warn(`[Motion] Invalid hover target selector \"${selector}\":`, e);\n continue;\n }\n for (const el of elements) {\n this._hoverFrozenRects.set(el, getLayoutRect(el as HTMLElement));\n }\n }\n\n if (this._hoverFrozenRects.size === 0) return;\n\n // A single window mousemove listener replaces per-element mouseenter/mouseleave.\n this._boundHoverTargetMoveHandler = (e: MouseEvent) => {\n const cx = e.clientX;\n const cy = e.clientY;\n\n let isOver = false;\n for (const rect of this._hoverFrozenRects.values()) {\n if (cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom) {\n isOver = true;\n break;\n }\n }\n\n if (isOver && !this._isHoveringCursorTarget) {\n this._isHoveringCursorTarget = true;\n this._setState('hover');\n } else if (!isOver && this._isHoveringCursorTarget) {\n this._isHoveringCursorTarget = false;\n if (this._state === 'hover') {\n this._setState('default');\n }\n }\n };\n\n window.addEventListener('mousemove', this._boundHoverTargetMoveHandler);\n }\n\n private _setupTextCursor(): void {\n if (!this._innerElement) return;\n\n // Style inner element for text cursor: grid centering + overflow visible\n // so text is visible even when the inner element is 0x0\n // (matches old GSAP TextCursor: display:grid, placeItems:center)\n this._innerElement.style.display = 'grid';\n this._innerElement.style.placeItems = 'center';\n this._innerElement.style.overflow = 'visible';\n\n // Create text element inside the inner cursor element\n this._textElement = document.createElement('div');\n this._textElement.setAttribute('data-cursor-text', '');\n // Prevent text wrapping inside zero-width container, start invisible\n this._textElement.style.whiteSpace = 'nowrap';\n this._textElement.style.opacity = '0';\n this._innerElement.appendChild(this._textElement);\n\n // Get transition duration from hover config for opacity animation\n const hoverConfig = this._parsedStates.get('hover');\n const transitionDuration = hoverConfig?.duration ?? 0.15;\n this._textElement.style.transition = `opacity ${transitionDuration}s ease-in-out`;\n\n // Apply text styles if provided\n if (this._config.text) {\n for (const [key, value] of Object.entries(this._config.text)) {\n this._textElement.style.setProperty(camelToKebab(key), String(value));\n }\n }\n\n // Setup attribute-based text triggers\n // Support both mp-cursor-text and mp-cursor-tooltip attributes\n const textElements = document.querySelectorAll('[mp-cursor-text], [mp-cursor-tooltip]');\n for (const el of textElements) {\n const enter = (e: Event) => {\n const target = e.target as Element;\n const text = target.getAttribute('mp-cursor-text')\n ?? target.getAttribute('mp-cursor-tooltip')\n ?? '';\n if (this._textElement) {\n this._textElement.textContent = text;\n this._textElement.style.opacity = '1';\n }\n this._setState('hover');\n };\n const leave = () => {\n if (this._textElement) {\n this._textElement.style.opacity = '0';\n }\n if (this._state === 'hover') {\n this._setState('default');\n }\n };\n\n el.addEventListener('mouseenter', enter);\n el.addEventListener('mouseleave', leave);\n this._attributeListeners.set(el, { enter, leave });\n }\n }\n\n private _setupMediaCursor(): void {\n if (!this._innerElement) return;\n\n // Create image element inside the inner cursor element\n const img = document.createElement('img');\n img.setAttribute('data-cursor-media', '');\n img.style.display = 'none';\n this._innerElement.appendChild(img);\n\n const video = document.createElement('video');\n video.setAttribute('data-cursor-media', '');\n video.autoplay = true;\n video.muted = true;\n video.loop = true;\n video.style.display = 'none';\n this._innerElement.appendChild(video);\n\n // Apply media styles if provided\n if (this._config.media) {\n for (const [key, value] of Object.entries(this._config.media)) {\n img.style.setProperty(camelToKebab(key), String(value));\n video.style.setProperty(camelToKebab(key), String(value));\n }\n }\n\n // Setup attribute-based media triggers\n const mediaElements = document.querySelectorAll('[mp-cursor-media]');\n for (const el of mediaElements) {\n const enter = (e: Event) => {\n const src = (e.target as Element).getAttribute('mp-cursor-media') || '';\n if (!src || !isSafeMediaUrl(src)) {\n if (src) console.warn(`[Motion] Blocked unsafe media URL: \"${src}\". Only http/https URLs and relative paths are allowed.`);\n return;\n }\n const isVideo = src.endsWith('.mp4') || src.endsWith('.webm');\n\n if (isVideo) {\n video.src = src;\n video.style.display = 'block';\n img.style.display = 'none';\n video.play().catch(() => {});\n this._mediaElement = video;\n } else {\n img.src = src;\n img.style.display = 'block';\n video.style.display = 'none';\n this._mediaElement = img;\n }\n this._setState('hover');\n };\n const leave = () => {\n img.style.display = 'none';\n video.style.display = 'none';\n video.pause();\n if (this._state === 'hover') {\n this._setState('default');\n }\n };\n\n el.addEventListener('mouseenter', enter);\n el.addEventListener('mouseleave', leave);\n this._attributeListeners.set(el, { enter, leave });\n }\n }\n\n private _removeEventListeners(): void {\n if (this._boundMouseMove) {\n document.removeEventListener('mousemove', this._boundMouseMove);\n }\n if (this._boundMouseDown) {\n document.removeEventListener('mousedown', this._boundMouseDown);\n }\n if (this._boundMouseUp) {\n document.removeEventListener('mouseup', this._boundMouseUp);\n }\n\n // Remove hover frozen-rect mousemove handler\n if (this._boundHoverTargetMoveHandler) {\n window.removeEventListener('mousemove', this._boundHoverTargetMoveHandler);\n this._boundHoverTargetMoveHandler = null;\n }\n this._hoverFrozenRects.clear();\n this._isHoveringCursorTarget = false;\n\n // _hoverListeners is kept empty now (frozen-rect approach replaced native listeners).\n this._hoverListeners.clear();\n\n // Remove attribute listeners\n this._attributeListeners.forEach((listeners, el) => {\n el.removeEventListener('mouseenter', listeners.enter);\n el.removeEventListener('mouseleave', listeners.leave);\n });\n this._attributeListeners.clear();\n }\n\n private _setState(newState: CursorState): void {\n if (this._state === newState) return;\n\n const prevState = this._state;\n this._state = newState;\n\n // Get the timeline for the new state\n const newTimeline = this._stateTimelines.get(newState);\n const prevTimeline = this._stateTimelines.get(prevState);\n\n if (newState === 'default') {\n // Return to default - reverse the previous state timeline\n if (prevTimeline) {\n prevTimeline.reverse();\n }\n } else {\n // Enter new state\n // If coming from a different non-default state, reverse the old one\n if (prevState !== 'default' && prevTimeline) {\n prevTimeline.reverse();\n }\n\n if (newTimeline) {\n // Use play() instead of restart() to resume from current position.\n // This prevents a visual jump when hovering between adjacent elements:\n // mouseleave on A starts reversing, mouseenter on B resumes forward\n // from wherever the timeline currently is — matching GSAP's overwrite behavior.\n newTimeline.play();\n }\n }\n }\n\n private _tick(deltaTime: number): void {\n if (!this._xSetter || !this._ySetter) return;\n\n // Smooth interpolation matching original GSAP cursor behavior:\n // smooth 0 = most delay/smoothing, smooth 1 = instant tracking\n // Uses power curve (2.5) for natural feel, frame-rate independent\n const smooth = this._config.smooth ?? 0.25;\n const rescaled = Math.pow(smooth, 2.5) || 0.000001;\n const lerpFactor = 1 - Math.pow(1 - rescaled, deltaTime * 60);\n\n // Update cursor position\n this._cursorPos.x += (this._mousePos.x - this._cursorPos.x) * lerpFactor;\n this._cursorPos.y += (this._mousePos.y - this._cursorPos.y) * lerpFactor;\n\n // Apply position\n this._xSetter(this._cursorPos.x);\n this._ySetter(this._cursorPos.y);\n\n // Apply squeeze effect if enabled\n if (this._squeezeSetter && this._config.squeeze) {\n const scale = this._calculateSqueeze();\n this._squeezeSetter(scale);\n }\n\n // Tick state transition timelines (they are unnamed, so not registered\n // with Engine and must be updated manually)\n for (const tl of this._stateTimelines.values()) {\n if (tl.isActive()) {\n tl.update(deltaTime);\n }\n }\n }\n\n private _calculateSqueeze(): number {\n const dx = this._cursorPos.x - this._mousePos.x;\n const dy = this._cursorPos.y - this._mousePos.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n const { min, max, multiplier } = this._squeezeConfig;\n const scale = 1 - (Math.exp(distance / multiplier!) - 1) / 10;\n\n if (Number.isNaN(scale)) {\n return 1;\n }\n\n return Math.max(min!, Math.min(max!, scale));\n }\n\n /**\n * Get the cursor element\n */\n getElement(): HTMLElement | null {\n return this._element;\n }\n\n /**\n * Get current state\n */\n getState(): CursorState {\n return this._state;\n }\n}\n\n// Register with TriggerManager\nTriggerManager.registerTrigger('cursor', CursorTrigger);\n",
|
|
35
|
+
"/**\n * PageExitTrigger\n *\n * Triggers timeline playback when a user clicks a link that would navigate away\n * from the current page. Intercepts the click, plays the exit animation, then\n * navigates to the target URL after the timeline completes.\n *\n * Supports three link selection modes:\n * - 'all': intercept all navigation links (default)\n * - 'include': only intercept links matching specific selectors\n * - 'exclude': intercept all links except those matching specific selectors\n *\n * Supports skipping certain href patterns:\n * - 'anchor': skip # links (same-page anchors)\n * - 'javascript': skip javascript: hrefs\n * - 'mailto': skip mailto: and tel: hrefs\n *\n * Works on all websites — no server-side dependencies (PHP, Node, etc.).\n */\n\nimport type { Timeline } from '../core/Timeline';\nimport { TriggerManager } from './TriggerManager';\nimport { BaseTrigger } from './BaseTrigger';\n\n/** Configuration for PageExitTrigger */\nexport interface PageExitTriggerConfig {\n /** Link selection mode: 'all' (default), 'include', or 'exclude' */\n mode?: 'all' | 'include' | 'exclude';\n /** CSS selector(s) for links — required for 'include'/'exclude' modes */\n selectors?: string;\n /** Href patterns to skip (never intercept) */\n skipHref?: ('anchor' | 'javascript' | 'mailto')[];\n}\n\nexport class PageExitTrigger extends BaseTrigger<PageExitTriggerConfig> {\n private _links: HTMLAnchorElement[] = [];\n private _listeners: Map<EventTarget, Map<string, EventListener>> = new Map();\n private _isNavigating: boolean = false;\n private _pendingUrl: string | null = null;\n private _onCompleteHandler: (() => void) | null = null;\n private _pollTimerId: ReturnType<typeof setTimeout> | null = null;\n\n constructor(timeline: Timeline, config: PageExitTriggerConfig) {\n super(timeline, config);\n }\n\n /**\n * Set up click listeners on qualifying links once DOM is ready.\n */\n protected _onEnable(): void {\n // Skip inside builder iframe\n if (this._isInsideBuilder()) return;\n\n this._resolveLinks();\n this._addClickListeners();\n }\n\n /**\n * Remove all click listeners and cancel pending navigation.\n */\n protected _onDisable(): void {\n this._cleanupListenerMap(this._listeners);\n this._links = [];\n this._isNavigating = false;\n this._pendingUrl = null;\n\n // Cancel pending poll timer\n if (this._pollTimerId !== null) {\n clearTimeout(this._pollTimerId);\n this._pollTimerId = null;\n }\n\n // Remove onComplete callback from timeline\n if (this._onCompleteHandler) {\n this._onCompleteHandler = null;\n }\n }\n\n /**\n * Detect if we're running inside the Motion.page builder iframe.\n * Exit animations should not intercept clicks in the builder.\n */\n private _isInsideBuilder(): boolean {\n try {\n if (typeof window !== 'undefined' && (window as any).top?.mp_iframe) {\n return true;\n }\n } catch {\n // Cross-origin access to top — not in builder\n }\n return false;\n }\n\n /**\n * Resolve which <a> elements should be intercepted based on config mode.\n */\n private _resolveLinks(): void {\n if (typeof document === 'undefined') return;\n\n const mode = this._config.mode || 'all';\n\n let candidates: HTMLAnchorElement[];\n\n if (mode === 'include' && this._config.selectors && this._config.selectors.trim() !== '') {\n // Only links matching the selectors\n try {\n candidates = Array.from(\n document.querySelectorAll<HTMLAnchorElement>(this._config.selectors)\n ).filter(el => el.tagName === 'A');\n\n // If selectors don't specifically target <a> tags, also find <a> descendants\n const containers = document.querySelectorAll(this._config.selectors);\n containers.forEach(container => {\n if (container.tagName !== 'A') {\n const nested = container.querySelectorAll<HTMLAnchorElement>('a');\n candidates.push(...Array.from(nested));\n }\n });\n } catch (e) {\n console.warn(`[Motion] Invalid selector \"${this._config.selectors}\":`, e);\n candidates = [];\n }\n } else if (mode === 'include') {\n // Include mode with missing or empty selectors — no links to intercept\n candidates = [];\n } else if (mode === 'exclude' && this._config.selectors && this._config.selectors.trim() !== '') {\n // All links except those matching selectors\n const allLinks = Array.from(document.querySelectorAll<HTMLAnchorElement>('a'));\n let excluded: Set<Element>;\n try {\n excluded = new Set(document.querySelectorAll(this._config.selectors));\n } catch (e) {\n console.warn(`[Motion] Invalid selector \"${this._config.selectors}\":`, e);\n excluded = new Set();\n }\n candidates = allLinks.filter(link => !excluded.has(link));\n } else {\n // 'all' mode or 'exclude' without selectors — all links on the page\n candidates = Array.from(document.querySelectorAll<HTMLAnchorElement>('a'));\n }\n\n // Deduplicate\n this._links = [...new Set(candidates)];\n\n // Filter out links whose href should be skipped\n this._links = this._links.filter(link => this._shouldIntercept(link));\n }\n\n /**\n * Determine if a link should be intercepted based on its href and skipHref config.\n * Skips links that don't actually navigate away.\n */\n private _shouldIntercept(link: HTMLAnchorElement): boolean {\n const href = link.getAttribute('href');\n\n // No href at all — skip\n if (!href || href === '') return false;\n\n const skipHref = this._config.skipHref || [];\n\n // Anchor links (same-page navigation)\n if (href.startsWith('#')) {\n return !skipHref.includes('anchor');\n }\n\n // javascript: protocol\n if (href.toLowerCase().startsWith('javascript:')) {\n return !skipHref.includes('javascript');\n }\n\n // mailto: and tel: protocols\n if (href.toLowerCase().startsWith('mailto:') || href.toLowerCase().startsWith('tel:')) {\n return !skipHref.includes('mailto');\n }\n\n return true;\n }\n\n /**\n * Attach click listeners to all resolved links.\n */\n private _addClickListeners(): void {\n this._links.forEach(link => {\n const elementListeners = new Map<string, EventListener>();\n\n const clickListener = (e: Event) => {\n // Don't intercept if already navigating (animation in progress)\n if (this._isNavigating) return;\n\n // Don't intercept if modifier keys are held (open in new tab, etc.)\n const mouseEvent = e as MouseEvent;\n if (mouseEvent.ctrlKey || mouseEvent.metaKey || mouseEvent.shiftKey || mouseEvent.altKey) {\n return;\n }\n\n // Don't intercept if link has target=\"_blank\" or similar\n const target = link.getAttribute('target');\n if (target && target !== '_self') return;\n\n // Get the navigation URL\n const href = link.href; // Use .href (resolved absolute URL), not getAttribute('href')\n if (!href) return;\n\n // Prevent default navigation\n e.preventDefault();\n e.stopPropagation();\n\n // Store the target URL and start exit animation\n this._isNavigating = true;\n this._pendingUrl = href;\n\n // Set up completion handler to navigate after animation finishes\n this._onCompleteHandler = () => {\n this._navigate();\n };\n\n // Play the timeline from start\n this._timeline.restart();\n\n // Listen for timeline completion\n // We use a polling approach since Timeline doesn't expose onComplete externally\n this._waitForCompletion();\n };\n\n link.addEventListener('click', clickListener);\n elementListeners.set('click', clickListener);\n this._listeners.set(link, elementListeners);\n });\n }\n\n /**\n * Wait for the timeline to complete, then navigate.\n * Uses requestAnimationFrame polling to check timeline progress.\n */\n private _waitForCompletion(): void {\n const timeline = this._timeline;\n const checkInterval = 16; // ~60fps check\n\n const check = () => {\n this._pollTimerId = null;\n\n // Bail if disabled or no longer navigating\n if (!this._enabled || !this._isNavigating || !this._pendingUrl) return;\n\n // Check if timeline has completed (progress >= 1 or not active)\n const progress = timeline.progress();\n const isActive = timeline.isActive();\n\n if (progress >= 1 || (!isActive && progress > 0)) {\n // Timeline finished — navigate\n this._navigate();\n } else {\n // Keep checking\n this._pollTimerId = setTimeout(check, checkInterval);\n }\n };\n\n // Start checking after a short delay to let the timeline begin\n this._pollTimerId = setTimeout(check, checkInterval);\n }\n\n /**\n * Navigate to the pending URL.\n */\n private _navigate(): void {\n if (!this._pendingUrl) return;\n\n const url = this._pendingUrl;\n this._pendingUrl = null;\n this._isNavigating = false;\n\n if (typeof window !== 'undefined') {\n window.location.href = url;\n }\n }\n}\n\n// Register with TriggerManager\nTriggerManager.registerTrigger('pageExit', PageExitTrigger);\n",
|
|
36
|
+
"/**\n * ColorParser - Parse any color format to RGBA\n *\n * Supports:\n * - Hex: #ff0000, #f00, #ff0000ff, #f00f\n * - RGB/RGBA: rgb(255, 0, 0), rgba(255, 0, 0, 0.5)\n * - HSL/HSLA: hsl(0, 100%, 50%), hsla(0, 100%, 50%, 0.5)\n * - Named colors: red, coral, transparent\n * - CSS variables: var(--color)\n */\n\n// Cached canvas context for browser-native color parsing\nlet canvasCtx: CanvasRenderingContext2D | null = null;\n\n/**\n * Get or create cached canvas context\n */\nfunction getCanvasContext(): CanvasRenderingContext2D | null {\n if (!canvasCtx && typeof document !== 'undefined') {\n const canvas = document.createElement('canvas');\n canvas.width = 1;\n canvas.height = 1;\n canvasCtx = canvas.getContext('2d');\n }\n return canvasCtx;\n}\n\n/**\n * Parse hex color to RGBA\n * Supports: #rgb, #rrggbb, #rgba, #rrggbbaa\n */\nfunction parseHex(hex: string): Float32Array | null {\n const h = hex.replace('#', '');\n let r: number, g: number, b: number, a = 1;\n\n if (h.length === 3) {\n // #rgb\n r = parseInt(h[0] + h[0], 16);\n g = parseInt(h[1] + h[1], 16);\n b = parseInt(h[2] + h[2], 16);\n } else if (h.length === 4) {\n // #rgba\n r = parseInt(h[0] + h[0], 16);\n g = parseInt(h[1] + h[1], 16);\n b = parseInt(h[2] + h[2], 16);\n a = parseInt(h[3] + h[3], 16) / 255;\n } else if (h.length === 6) {\n // #rrggbb\n r = parseInt(h.slice(0, 2), 16);\n g = parseInt(h.slice(2, 4), 16);\n b = parseInt(h.slice(4, 6), 16);\n } else if (h.length === 8) {\n // #rrggbbaa\n r = parseInt(h.slice(0, 2), 16);\n g = parseInt(h.slice(2, 4), 16);\n b = parseInt(h.slice(4, 6), 16);\n a = parseInt(h.slice(6, 8), 16) / 255;\n } else {\n return null;\n }\n\n if (isNaN(r) || isNaN(g) || isNaN(b) || isNaN(a)) {\n return null;\n }\n\n return new Float32Array([r, g, b, a]);\n}\n\n/**\n * Parse rgb() or rgba() color string\n */\nfunction parseRgb(color: string): Float32Array | null {\n const match = color.match(/rgba?\\s*\\(\\s*([\\d.]+)\\s*[,\\s]\\s*([\\d.]+)\\s*[,\\s]\\s*([\\d.]+)\\s*(?:[,\\/]\\s*([\\d.]+))?\\s*\\)/i);\n if (!match) return null;\n\n const r = parseFloat(match[1]);\n const g = parseFloat(match[2]);\n const b = parseFloat(match[3]);\n const a = match[4] !== undefined ? parseFloat(match[4]) : 1;\n\n if (isNaN(r) || isNaN(g) || isNaN(b) || isNaN(a)) {\n return null;\n }\n\n return new Float32Array([r, g, b, a]);\n}\n\n/**\n * Convert HSL to RGB\n * h: 0-360, s: 0-100, l: 0-100\n */\nfunction hslToRgb(h: number, s: number, l: number): [number, number, number] {\n h = h / 360;\n s = s / 100;\n l = l / 100;\n\n let r: number, g: number, b: number;\n\n if (s === 0) {\n r = g = b = l;\n } else {\n const hue2rgb = (p: number, q: number, t: number): number => {\n if (t < 0) t += 1;\n if (t > 1) t -= 1;\n if (t < 1 / 6) return p + (q - p) * 6 * t;\n if (t < 1 / 2) return q;\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\n return p;\n };\n\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n const p = 2 * l - q;\n r = hue2rgb(p, q, h + 1 / 3);\n g = hue2rgb(p, q, h);\n b = hue2rgb(p, q, h - 1 / 3);\n }\n\n return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];\n}\n\n/**\n * Parse hsl() or hsla() color string\n */\nfunction parseHsl(color: string): Float32Array | null {\n const match = color.match(/hsla?\\s*\\(\\s*([\\d.]+)\\s*[,\\s]\\s*([\\d.]+)%\\s*[,\\s]\\s*([\\d.]+)%\\s*(?:[,\\/]\\s*([\\d.]+))?\\s*\\)/i);\n if (!match) return null;\n\n const h = parseFloat(match[1]);\n const s = parseFloat(match[2]);\n const l = parseFloat(match[3]);\n const a = match[4] !== undefined ? parseFloat(match[4]) : 1;\n\n if (isNaN(h) || isNaN(s) || isNaN(l) || isNaN(a)) {\n return null;\n }\n\n const [r, g, b] = hslToRgb(h, s, l);\n return new Float32Array([r, g, b, a]);\n}\n\n/**\n * Parse CSS variable by resolving its computed value\n */\nfunction parseCssVariable(color: string, element?: Element): Float32Array | null {\n if (!element || typeof window === 'undefined') return null;\n\n const match = color.match(/var\\s*\\(\\s*(--[\\w-]+)\\s*\\)/i);\n if (!match) return null;\n\n const varName = match[1];\n const computed = window.getComputedStyle(element).getPropertyValue(varName).trim();\n\n if (!computed) return null;\n\n // Recursively parse the resolved value\n return parseColor(computed, element);\n}\n\n/**\n * Parse color using browser's canvas context\n * Handles named colors and validates any color string\n */\nfunction parseWithCanvas(color: string): Float32Array | null {\n const ctx = getCanvasContext();\n if (!ctx) return null;\n\n // Reset to known state\n ctx.fillStyle = '#000000';\n ctx.fillStyle = color;\n\n const result = ctx.fillStyle;\n\n // If result is still black and input wasn't black, invalid color\n if (result === '#000000' && color.toLowerCase() !== 'black' && color !== '#000000' && color !== '#000') {\n return null;\n }\n\n // Canvas returns hex, parse it\n return parseHex(result);\n}\n\n/**\n * Parse any color format to RGBA Float32Array\n * Returns [R, G, B, A] where RGB is 0-255 and A is 0-1\n */\nexport function parseColor(color: string, element?: Element): Float32Array | null {\n if (!color || typeof color !== 'string') return null;\n\n const trimmed = color.trim().toLowerCase();\n\n // Handle transparent\n if (trimmed === 'transparent') {\n return new Float32Array([0, 0, 0, 0]);\n }\n\n // Try hex\n if (trimmed.startsWith('#')) {\n return parseHex(trimmed);\n }\n\n // Try rgb/rgba\n if (trimmed.startsWith('rgb')) {\n return parseRgb(trimmed);\n }\n\n // Try hsl/hsla\n if (trimmed.startsWith('hsl')) {\n return parseHsl(trimmed);\n }\n\n // Try CSS variable\n if (trimmed.startsWith('var(')) {\n return parseCssVariable(trimmed, element);\n }\n\n // Fallback to canvas for named colors\n return parseWithCanvas(trimmed);\n}\n\n/**\n * Convert RGBA Float32Array to CSS rgba() string\n */\nexport function rgbaToString(rgba: Float32Array): string {\n const r = Math.round(rgba[0]);\n const g = Math.round(rgba[1]);\n const b = Math.round(rgba[2]);\n const a = rgba[3];\n\n // Use rgb() if fully opaque for cleaner output\n if (a === 1) {\n return `rgb(${r}, ${g}, ${b})`;\n }\n\n return `rgba(${r}, ${g}, ${b}, ${a})`;\n}\n\n/**\n * Get current color value from element's computed style\n */\nexport function getCurrentColor(element: Element, property: string): Float32Array {\n if (typeof window === 'undefined') {\n return new Float32Array([0, 0, 0, 1]);\n }\n\n const computed = window.getComputedStyle(element);\n const kebabProp = property.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);\n const value = computed.getPropertyValue(kebabProp).trim();\n\n if (!value || value === 'none') {\n // Default to transparent for unset colors\n return new Float32Array([0, 0, 0, 0]);\n }\n\n const parsed = parseColor(value, element);\n return parsed || new Float32Array([0, 0, 0, 1]);\n}\n\n// Register with SDKRegistry for conditional bundling\nimport { SDKRegistry } from '../registries/SDKRegistry';\nSDKRegistry.color = { parseColor, getCurrentColor };\n",
|
|
37
|
+
"/**\n * FilterParser - Parse CSS filter strings for animation\n *\n * Supports:\n * - blur(px)\n * - brightness(ratio)\n * - contrast(ratio)\n * - saturate(ratio)\n * - grayscale(ratio or %)\n * - sepia(ratio or %)\n * - hue-rotate(deg)\n * - invert(ratio or %)\n * - opacity(ratio or %)\n */\n\n/**\n * Parsed filter function with name, value, and unit\n */\nexport interface ParsedFilterFunction {\n name: string;\n value: number;\n unit: string;\n}\n\n/**\n * Default values for filter functions (when not specified)\n */\nconst FILTER_DEFAULTS: Record<string, { value: number; unit: string }> = {\n blur: { value: 0, unit: 'px' },\n brightness: { value: 1, unit: '' },\n contrast: { value: 1, unit: '' },\n saturate: { value: 1, unit: '' },\n grayscale: { value: 0, unit: '' },\n sepia: { value: 0, unit: '' },\n 'hue-rotate': { value: 0, unit: 'deg' },\n invert: { value: 0, unit: '' },\n opacity: { value: 1, unit: '' },\n};\n\n/**\n * Parse a single filter function like \"blur(10px)\" or \"brightness(1.5)\"\n */\nfunction parseFilterFunction(fn: string): ParsedFilterFunction | null {\n const match = fn.trim().match(/^([\\w-]+)\\(\\s*([\\d.]+)(px|%|deg)?\\s*\\)$/);\n if (!match) return null;\n\n const name = match[1].toLowerCase();\n let value = parseFloat(match[2]);\n let unit = match[3] || '';\n\n // Normalize percentage values to ratio (0-1) for consistency\n if (unit === '%') {\n value = value / 100;\n unit = '';\n }\n\n return { name, value, unit };\n}\n\n/**\n * Parse a complete filter string into an array of filter functions\n * Example: \"blur(10px) brightness(1.5)\" -> [{name: 'blur', value: 10, unit: 'px'}, ...]\n */\nexport function parseFilter(filter: string): ParsedFilterFunction[] | null {\n if (filter === null || filter === undefined || typeof filter !== 'string') return null;\n\n const trimmed = filter.trim().toLowerCase();\n\n // Handle 'none' or empty\n if (trimmed === 'none' || trimmed === '') {\n return [];\n }\n\n // Split by closing parenthesis followed by optional space\n // This handles \"blur(10px) brightness(1.5)\" correctly\n const functions: ParsedFilterFunction[] = [];\n const regex = /([\\w-]+)\\([^)]+\\)/g;\n let match;\n\n while ((match = regex.exec(trimmed)) !== null) {\n const parsed = parseFilterFunction(match[0]);\n if (parsed) {\n functions.push(parsed);\n }\n }\n\n return functions.length > 0 ? functions : null;\n}\n\n/**\n * Convert parsed filter functions back to CSS filter string\n */\nexport function filterToString(filters: ParsedFilterFunction[]): string {\n if (!filters || filters.length === 0) {\n return 'none';\n }\n\n return filters.map(f => {\n // Format value based on filter type\n let valueStr: string;\n if (f.name === 'blur') {\n valueStr = `${f.value}${f.unit || 'px'}`;\n } else if (f.name === 'hue-rotate') {\n valueStr = `${f.value}${f.unit || 'deg'}`;\n } else {\n // For ratio-based filters, output as number\n valueStr = `${f.value}`;\n }\n return `${f.name}(${valueStr})`;\n }).join(' ');\n}\n\n/**\n * Get current filter value from element's computed style\n */\nexport function getCurrentFilter(element: Element): ParsedFilterFunction[] {\n if (typeof window === 'undefined') {\n return [];\n }\n\n const computed = window.getComputedStyle(element);\n const filter = computed.filter || computed.getPropertyValue('filter');\n\n if (!filter || filter === 'none') {\n return [];\n }\n\n return parseFilter(filter) || [];\n}\n\n/**\n * Get default value for a filter function\n */\nfunction getFilterDefault(name: string): ParsedFilterFunction {\n const def = FILTER_DEFAULTS[name];\n if (def) {\n return { name, value: def.value, unit: def.unit };\n }\n // Unknown filter, default to 0\n return { name, value: 0, unit: '' };\n}\n\n/**\n * Merge two filter arrays, ensuring all functions from both are present\n * Missing functions get their default values\n */\nexport function mergeFilterArrays(\n start: ParsedFilterFunction[],\n end: ParsedFilterFunction[]\n): { start: ParsedFilterFunction[]; end: ParsedFilterFunction[] } {\n // Collect all unique filter names\n const allNames = new Set<string>();\n for (const f of start) allNames.add(f.name);\n for (const f of end) allNames.add(f.name);\n\n // Build merged arrays with same order and length\n const mergedStart: ParsedFilterFunction[] = [];\n const mergedEnd: ParsedFilterFunction[] = [];\n\n for (const name of allNames) {\n const startFn = start.find(f => f.name === name);\n const endFn = end.find(f => f.name === name);\n const defaultFn = getFilterDefault(name);\n\n mergedStart.push(startFn || defaultFn);\n mergedEnd.push(endFn || defaultFn);\n }\n\n return { start: mergedStart, end: mergedEnd };\n}\n\n// Pre-allocated array for interpolateFilters to avoid per-frame allocation\nlet filterResultBuffer: ParsedFilterFunction[] = [];\n\n/**\n * Interpolate between two filter arrays at given progress.\n * Reuses a module-level buffer to avoid allocating a new array every frame.\n * NOTE: The returned array is reused on the next call — consume it immediately.\n */\nexport function interpolateFilters(\n start: ParsedFilterFunction[],\n end: ParsedFilterFunction[],\n progress: number\n): ParsedFilterFunction[] {\n const len = start.length;\n\n // Grow the buffer if needed (shrinking is unnecessary — extra slots are harmless)\n if (filterResultBuffer.length < len) {\n for (let i = filterResultBuffer.length; i < len; i++) {\n filterResultBuffer.push({ name: '', value: 0, unit: '' });\n }\n }\n\n for (let i = 0; i < len; i++) {\n const s = start[i];\n const e = end[i];\n const slot = filterResultBuffer[i];\n\n slot.name = s.name;\n slot.value = s.value + (e.value - s.value) * progress;\n slot.unit = s.unit || e.unit;\n }\n\n // Set length to trim any extra slots from previous larger calls\n filterResultBuffer.length = len;\n\n return filterResultBuffer;\n}\n\n// Register with SDKRegistry for conditional bundling\nimport { SDKRegistry } from '../registries/SDKRegistry';\nSDKRegistry.filter = { parseFilter, getCurrentFilter, mergeFilterArrays, interpolateFilters, filterToString };\n",
|
|
38
|
+
"/**\n * DrawSVGParser - Parse and animate SVG stroke drawing\n *\n * Handles:\n * - Percentage values: \"0% 100%\", \"20% 80%\"\n * - Pixel values: \"100px 500px\"\n * - Object format: { start: 0, end: 100 }\n * - Single values: \"50%\" (draws from 0 to 50%)\n *\n * Uses stroke-dasharray and stroke-dashoffset for performant rendering.\n */\n\n/**\n * Parsed DrawSVG values (normalized to 0-1 range)\n */\nexport interface ParsedDrawSVG {\n start: number; // 0-1 normalized start position\n end: number; // 0-1 normalized end position\n}\n\n/**\n * DrawSVG input value types\n */\ntype DrawSVGValue = string | { start?: number; end?: number };\n\n// Cache for SVG path lengths (expensive to calculate)\nconst pathLengthCache = new WeakMap<Element, number>();\n\n/**\n * Check if an element supports getTotalLength (SVG geometry elements)\n */\nfunction hasTotalLength(element: Element): element is SVGGeometryElement {\n return 'getTotalLength' in element && typeof (element as SVGGeometryElement).getTotalLength === 'function';\n}\n\n/**\n * Get the total length of an SVG element's stroke path.\n * Cached per element for performance.\n */\nexport function getPathLength(element: Element): number {\n // Check cache first\n const cached = pathLengthCache.get(element);\n if (cached !== undefined) {\n return cached;\n }\n\n // Calculate length if element supports it\n if (hasTotalLength(element)) {\n const length = element.getTotalLength();\n pathLengthCache.set(element, length);\n return length;\n }\n\n // Fallback for elements without getTotalLength\n return 0;\n}\n\n/**\n * Clear cached path length for an element.\n * Call when SVG path data changes.\n */\nexport function clearPathLengthCache(element: Element): void {\n pathLengthCache.delete(element);\n}\n\n/**\n * Parse a DrawSVG value string or object into normalized 0-1 values.\n *\n * Supported formats:\n * - \"0% 100%\" - percentage range\n * - \"100px 500px\" - pixel range (requires element for length)\n * - \"50%\" - single percentage (0 to value)\n * - \"50\" - single number treated as percentage\n * - { start: 0, end: 100 } - object with percentages\n */\nexport function parseDrawSVG(value: DrawSVGValue, element?: Element): ParsedDrawSVG | null {\n if (value === null || value === undefined) {\n return null;\n }\n\n // Handle object format { start: number, end: number }\n if (typeof value === 'object') {\n const start = value.start ?? 0;\n const end = value.end ?? 100;\n return {\n start: start / 100,\n end: end / 100,\n };\n }\n\n // Handle string format\n if (typeof value === 'string') {\n const trimmed = value.trim();\n\n // Handle \"true\" keyword (full draw)\n if (trimmed === 'true') {\n return { start: 0, end: 1 };\n }\n\n // Handle \"false\" or empty (no draw)\n if (trimmed === 'false' || trimmed === '' || trimmed === 'none') {\n return { start: 0, end: 0 };\n }\n\n // Try to parse as two values: \"start end\"\n // Matches: \"0% 100%\", \"0 100\", \"100px 500px\", \"0% 100px\"\n const twoValueMatch = trimmed.match(/^([-\\d.]+)(px|%)?\\s+([-\\d.]+)(px|%)?$/);\n\n if (twoValueMatch) {\n const startVal = parseFloat(twoValueMatch[1]);\n const startUnit = twoValueMatch[2] || '%';\n const endVal = parseFloat(twoValueMatch[3]);\n const endUnit = twoValueMatch[4] || '%';\n\n const length = element ? getPathLength(element) : 0;\n\n const start = normalizeValue(startVal, startUnit, length);\n const end = normalizeValue(endVal, endUnit, length);\n\n return { start, end };\n }\n\n // Try to parse as single value: \"50%\" or \"50\"\n const singleValueMatch = trimmed.match(/^([-\\d.]+)(px|%)?$/);\n\n if (singleValueMatch) {\n const val = parseFloat(singleValueMatch[1]);\n const unit = singleValueMatch[2] || '%';\n const length = element ? getPathLength(element) : 0;\n\n const end = normalizeValue(val, unit, length);\n\n return { start: 0, end };\n }\n }\n\n return null;\n}\n\n/**\n * Normalize a value to 0-1 range based on unit\n */\nfunction normalizeValue(value: number, unit: string, pathLength: number): number {\n if (unit === 'px' && pathLength > 0) {\n // Pixel value - convert to ratio of total length\n return value / pathLength;\n }\n // Percentage or unitless - treat as percentage\n return value / 100;\n}\n\n/**\n * Get the current DrawSVG state from an element's computed style.\n * Parses existing stroke-dasharray and stroke-dashoffset.\n */\nexport function getCurrentDrawSVG(element: Element): ParsedDrawSVG {\n const length = getPathLength(element);\n\n if (length === 0) {\n return { start: 0, end: 1 };\n }\n\n try {\n if (typeof window !== 'undefined' && window.getComputedStyle) {\n const computed = window.getComputedStyle(element);\n const dashArray = computed.strokeDasharray;\n const dashOffset = computed.strokeDashoffset;\n\n // If no dash array set, stroke is fully visible\n if (!dashArray || dashArray === 'none') {\n return { start: 0, end: 1 };\n }\n\n // Parse dash array (format: \"drawLength totalLength\" or just \"drawLength\")\n const dashParts = dashArray.split(/[\\s,]+/).map(parseFloat).filter(n => !isNaN(n));\n const offset = parseFloat(dashOffset) || 0;\n\n if (dashParts.length >= 1) {\n const drawLength = dashParts[0];\n // start = offset / length, end = (offset + drawLength) / length\n const start = offset / length;\n const end = (offset + drawLength) / length;\n return { start, end };\n }\n }\n } catch {\n // Fail gracefully in test environments\n }\n\n return { start: 0, end: 1 };\n}\n\n/**\n * Apply DrawSVG values to an element's style.\n * Sets stroke-dasharray and stroke-dashoffset for the draw effect.\n *\n * @param element - Target SVG element\n * @param start - Start position (0-1)\n * @param end - End position (0-1)\n * @param length - Pre-calculated path length\n */\nexport function applyDrawSVG(\n element: Element,\n start: number,\n end: number,\n length: number\n): void {\n if (!(element instanceof SVGElement) || length === 0) {\n return;\n }\n\n const htmlEl = element as unknown as { style: CSSStyleDeclaration };\n\n // Calculate the visible portion length\n const drawLength = Math.abs(end - start) * length;\n\n // Calculate offset (where the visible portion starts)\n const dashOffset = Math.min(start, end) * length;\n\n // Apply stroke-dasharray: [visible-length] [total-length]\n // This creates a single dash of the visible portion followed by a gap\n htmlEl.style.strokeDasharray = `${drawLength} ${length}`;\n\n // Apply stroke-dashoffset to position the dash\n // Negative offset moves the dash forward (start of visible portion)\n htmlEl.style.strokeDashoffset = `${-dashOffset}`;\n}\n\n// Register with SDKRegistry for conditional bundling\nimport { SDKRegistry } from '../registries/SDKRegistry';\nSDKRegistry.drawSVG = { parseDrawSVG, getCurrentDrawSVG, getPathLength, applyDrawSVG };\n",
|
|
39
|
+
"/**\n * TextSplitter - Split text into animatable elements\n *\n * Splits text content into chars, words, or lines wrapped in spans.\n * Supports nested splitting (e.g., 'chars,words' = words containing char spans).\n *\n * Preserves existing element structure — inline elements like\n * `<span class=\"accent\">word</span>` keep their wrapper and styling.\n * Only text nodes are replaced with split spans.\n *\n * Usage:\n * const elements = TextSplitter.split(element, 'chars');\n * const elements = TextSplitter.split(element, 'words');\n * const elements = TextSplitter.split(element, 'chars,words');\n *\n * Revert:\n * TextSplitter.revert(element);\n */\n\nimport type { SplitType } from '../types';\nimport { SDKRegistry } from '../registries/SDKRegistry';\nexport type { SplitType };\n\nexport interface SplitOptions {\n /** Wrap each split element in a parent with overflow:hidden for clip-reveal effects */\n mask?: boolean;\n}\n\ninterface SplitResult {\n chars: HTMLElement[];\n words: HTMLElement[];\n lines: HTMLElement[];\n elements: HTMLElement[]; // The innermost elements (what gets animated)\n}\n\n// Store original HTML for revert\nconst originalContentMap = new WeakMap<Element, string>();\n\n// Store split results for access\nconst splitResultMap = new WeakMap<Element, SplitResult>();\n\nexport class TextSplitter {\n /**\n * Split text content into animatable elements\n */\n static split(element: Element, type: SplitType, options?: SplitOptions): HTMLElement[] {\n if (!(element instanceof HTMLElement)) {\n return [];\n }\n\n // Guard against re-splitting: if already split, restore original content first.\n // Without this, calling split() multiple times nests word/char spans inside each other.\n const existingOriginal = originalContentMap.get(element);\n if (existingOriginal !== undefined) {\n element.innerHTML = existingOriginal;\n element.removeAttribute('data-split');\n splitResultMap.delete(element);\n } else {\n // First split — store original content for revert\n originalContentMap.set(element, element.innerHTML);\n }\n\n const result: SplitResult = {\n chars: [],\n words: [],\n lines: [],\n elements: [],\n };\n\n // Parse split type\n const types = type.split(',').map(t => t.trim()) as ('chars' | 'words' | 'lines')[];\n const needsChars = types.includes('chars');\n const needsWords = types.includes('words');\n const needsLines = types.includes('lines');\n\n if (needsLines) {\n // Lines require layout measurement after an initial word split.\n // Use DOM-walking for the initial word pass, then measure and wrap.\n this._splitWithLinesDom(element, needsWords, needsChars, result);\n } else if (needsWords && needsChars) {\n this._splitWordsWithCharsDom(element, result);\n } else if (needsWords) {\n this._splitWordsDom(element, result);\n } else if (needsChars) {\n this._splitCharsDom(element, result);\n }\n\n // Determine innermost elements (what gets animated with stagger)\n if (needsChars) {\n result.elements = result.chars;\n } else if (needsWords) {\n result.elements = result.words;\n } else if (needsLines) {\n result.elements = result.lines;\n }\n\n // Apply mask wrappers (overflow:hidden parents for clip-reveal effects)\n if (options?.mask && result.elements.length > 0) {\n this._applyMaskWrappers(result);\n }\n\n // Store result\n splitResultMap.set(element, result);\n\n // Mark element as split\n element.setAttribute('data-split', type);\n\n return result.elements;\n }\n\n /* ── DOM-walking split methods ─────────────────────────────────────────── */\n\n /**\n * Split text nodes into word spans, preserving existing element wrappers.\n * Recurses into child elements so `<span class=\"accent\">word</span>` keeps\n * its wrapper and the text inside is split within it.\n */\n private static _splitWordsDom(parent: HTMLElement, result: SplitResult): void {\n const children = Array.from(parent.childNodes);\n\n for (const child of children) {\n if (child.nodeType === 3 /* TEXT_NODE */) {\n const text = child.textContent || '';\n if (!text) continue;\n\n const parts = text.split(/(\\s+)/);\n const fragment = document.createDocumentFragment();\n let hasContent = false;\n\n for (const part of parts) {\n if (/^\\s+$/.test(part)) {\n // Preserve whitespace between words\n fragment.appendChild(document.createTextNode(part.replace(/\\n/g, ' ')));\n hasContent = true;\n } else if (part) {\n const span = document.createElement('span');\n span.setAttribute('data-split-word', '');\n span.setAttribute('data-word-index', String(result.words.length));\n span.style.display = 'inline-block';\n span.textContent = part;\n result.words.push(span);\n fragment.appendChild(span);\n hasContent = true;\n }\n }\n\n if (hasContent) {\n parent.replaceChild(fragment, child);\n }\n } else if (child.nodeType === 1 /* ELEMENT_NODE */) {\n const el = child as HTMLElement;\n if (el.tagName === 'BR') continue; // preserve <br> as-is\n // Recurse — preserves wrapper element with all its classes/styles\n this._splitWordsDom(el, result);\n }\n }\n }\n\n /**\n * Split text nodes into character spans, preserving existing element wrappers.\n */\n private static _splitCharsDom(parent: HTMLElement, result: SplitResult): void {\n const children = Array.from(parent.childNodes);\n\n for (const child of children) {\n if (child.nodeType === 3 /* TEXT_NODE */) {\n const text = child.textContent || '';\n if (!text) continue;\n\n const fragment = document.createDocumentFragment();\n\n for (const char of text) {\n if (char === ' ' || char === '\\n' || char === '\\t') {\n fragment.appendChild(document.createTextNode(char === '\\n' ? ' ' : char));\n } else {\n const span = document.createElement('span');\n span.setAttribute('data-split-char', '');\n span.setAttribute('data-char-index', String(result.chars.length));\n span.style.display = 'inline-block';\n span.textContent = char;\n result.chars.push(span);\n fragment.appendChild(span);\n }\n }\n\n parent.replaceChild(fragment, child);\n } else if (child.nodeType === 1 /* ELEMENT_NODE */) {\n const el = child as HTMLElement;\n if (el.tagName === 'BR') continue;\n this._splitCharsDom(el, result);\n }\n }\n }\n\n /**\n * Split text nodes into word spans containing character spans.\n * Preserves existing element wrappers.\n */\n private static _splitWordsWithCharsDom(parent: HTMLElement, result: SplitResult): void {\n const children = Array.from(parent.childNodes);\n\n for (const child of children) {\n if (child.nodeType === 3 /* TEXT_NODE */) {\n const text = child.textContent || '';\n if (!text) continue;\n\n const parts = text.split(/(\\s+)/);\n const fragment = document.createDocumentFragment();\n\n for (const part of parts) {\n if (/^\\s+$/.test(part)) {\n fragment.appendChild(document.createTextNode(part.replace(/\\n/g, ' ')));\n } else if (part) {\n const wordSpan = document.createElement('span');\n wordSpan.setAttribute('data-split-word', '');\n wordSpan.setAttribute('data-word-index', String(result.words.length));\n wordSpan.style.display = 'inline-block';\n wordSpan.style.whiteSpace = 'nowrap';\n\n for (const char of part) {\n const charSpan = document.createElement('span');\n charSpan.setAttribute('data-split-char', '');\n charSpan.setAttribute('data-char-index', String(result.chars.length));\n charSpan.style.display = 'inline-block';\n charSpan.textContent = char;\n result.chars.push(charSpan);\n wordSpan.appendChild(charSpan);\n }\n\n result.words.push(wordSpan);\n fragment.appendChild(wordSpan);\n }\n }\n\n parent.replaceChild(fragment, child);\n } else if (child.nodeType === 1 /* ELEMENT_NODE */) {\n const el = child as HTMLElement;\n if (el.tagName === 'BR') continue;\n this._splitWordsWithCharsDom(el, result);\n }\n }\n }\n\n /**\n * Split with line detection (requires layout measurement).\n *\n * Strategy:\n * 1. DOM-walk to split into word spans (preserving element wrappers)\n * 2. Measure each word span's vertical position to group into lines\n * 3. Wrap each line group in a line span\n */\n private static _splitWithLinesDom(\n element: HTMLElement,\n needsWords: boolean,\n needsChars: boolean,\n result: SplitResult\n ): void {\n // Step 1: split into words (DOM-walking, preserves element structure)\n const wordResult: SplitResult = { chars: [], words: [], lines: [], elements: [] };\n this._splitWordsDom(element, wordResult);\n\n // Step 2: measure word positions and group into lines\n const lines: HTMLElement[][] = [];\n let currentLine: HTMLElement[] = [];\n let currentTop = -Infinity;\n\n for (const wordSpan of wordResult.words) {\n const rect = wordSpan.getBoundingClientRect();\n if (rect.top > currentTop + 2) {\n // New line (2px tolerance for rounding)\n if (currentLine.length > 0) {\n lines.push(currentLine);\n }\n currentLine = [wordSpan];\n currentTop = rect.top;\n } else {\n currentLine.push(wordSpan);\n }\n }\n if (currentLine.length > 0) {\n lines.push(currentLine);\n }\n\n // Step 3: rebuild with line wrappers, preserving inline element wrappers.\n // For each word, we capture its ancestor chain between it and the root element\n // so wrapper elements like <span class=\"text-primary\"> are serialized into the\n // rebuilt HTML. Wrappers that span multiple lines are cloned into each line.\n\n // Build a map of wrapper chains for each word span\n const wrapperChainMap = new Map<HTMLElement, HTMLElement[]>();\n for (const word of wordResult.words) {\n wrapperChainMap.set(word, this._getWrapperChain(word, element));\n }\n\n let html = '';\n let lineIndex = 0;\n let wordIndex = 0;\n let charIndex = 0;\n\n for (const lineWords of lines) {\n let lineHtml = '';\n let openWrappers: HTMLElement[] = [];\n\n for (let i = 0; i < lineWords.length; i++) {\n const wordSpan = lineWords[i];\n const wordText = wordSpan.textContent || '';\n const wrapperChain = wrapperChainMap.get(wordSpan) || [];\n\n // Find where current and new wrapper chains diverge\n let commonDepth = 0;\n while (\n commonDepth < openWrappers.length &&\n commonDepth < wrapperChain.length &&\n openWrappers[commonDepth] === wrapperChain[commonDepth]\n ) {\n commonDepth++;\n }\n\n // Close wrappers that are no longer active (deepest first)\n for (let j = openWrappers.length - 1; j >= commonDepth; j--) {\n lineHtml += `</${openWrappers[j].tagName.toLowerCase()}>`;\n }\n\n // Space between words (except before first word in line)\n if (i > 0) {\n lineHtml += ' ';\n }\n\n // Open new wrappers\n for (let j = commonDepth; j < wrapperChain.length; j++) {\n lineHtml += this._openTag(wrapperChain[j]);\n }\n\n // Serialize the word content\n if (needsChars) {\n let wordHtml = '';\n for (const char of wordText) {\n wordHtml += `<span data-split-char data-char-index=\"${charIndex}\" style=\"display:inline-block\">${this._escapeHtml(char)}</span>`;\n charIndex++;\n }\n if (needsWords) {\n lineHtml += `<span data-split-word data-word-index=\"${wordIndex}\" style=\"display:inline-block;white-space:nowrap\">${wordHtml}</span>`;\n } else {\n lineHtml += wordHtml;\n }\n } else if (needsWords) {\n lineHtml += `<span data-split-word data-word-index=\"${wordIndex}\" style=\"display:inline-block\">${this._escapeHtml(wordText)}</span>`;\n } else {\n lineHtml += this._escapeHtml(wordText);\n }\n\n wordIndex++;\n openWrappers = wrapperChain;\n }\n\n // Close any remaining open wrappers at end of line\n for (let j = openWrappers.length - 1; j >= 0; j--) {\n lineHtml += `</${openWrappers[j].tagName.toLowerCase()}>`;\n }\n\n html += `<span data-split-line data-line-index=\"${lineIndex}\" style=\"display:block\">${lineHtml}</span>`;\n lineIndex++;\n }\n\n element.innerHTML = html;\n\n // Collect results from the rebuilt DOM\n if (needsChars) {\n result.chars = Array.from(element.querySelectorAll('[data-split-char]')) as HTMLElement[];\n }\n if (needsWords) {\n result.words = Array.from(element.querySelectorAll('[data-split-word]')) as HTMLElement[];\n }\n result.lines = Array.from(element.querySelectorAll('[data-split-line]')) as HTMLElement[];\n }\n\n /* ── Mask wrappers ────────────────────────────────────────────────────── */\n\n /**\n * Wrap each split element in a parent with overflow:hidden for clip-reveal.\n * The wrapper clips content so animating y:'100%' creates a reveal effect.\n */\n private static _applyMaskWrappers(result: SplitResult): void {\n // Determine which level gets masks — the animatable elements\n const elements = result.elements;\n\n for (const el of elements) {\n const isLine = el.hasAttribute('data-split-line');\n const isWord = el.hasAttribute('data-split-word');\n\n // Create mask wrapper\n const mask = document.createElement('span');\n mask.setAttribute('data-split-mask', '');\n mask.style.overflow = 'hidden';\n mask.style.display = isLine ? 'block' : 'inline-block';\n\n // For inline elements, ensure vertical overflow is clipped properly\n if (!isLine) {\n mask.style.verticalAlign = isWord ? 'bottom' : 'bottom';\n }\n\n // Insert mask wrapper in the element's position, then move element inside\n el.parentNode!.insertBefore(mask, el);\n mask.appendChild(el);\n }\n }\n\n /* ── Revert / query ───────────────────────────────────────────────────── */\n\n /**\n * Revert element to original content\n */\n static revert(element: Element): boolean {\n const original = originalContentMap.get(element);\n if (original !== undefined && element instanceof HTMLElement) {\n element.innerHTML = original;\n element.removeAttribute('data-split');\n originalContentMap.delete(element);\n splitResultMap.delete(element);\n return true;\n }\n return false;\n }\n\n /**\n * Check if element has been split\n */\n static isSplit(element: Element): boolean {\n return element.hasAttribute('data-split');\n }\n\n /**\n * Get split result for an element\n */\n static getResult(element: Element): SplitResult | undefined {\n return splitResultMap.get(element);\n }\n\n /* ── Utilities ─────────────────────────────────────────────────────────── */\n\n /**\n * Get the chain of wrapper elements between a word span and the root element.\n * Returns outermost-first order (root's direct child → … → word's parent).\n */\n private static _getWrapperChain(word: HTMLElement, root: HTMLElement): HTMLElement[] {\n const chain: HTMLElement[] = [];\n let el = word.parentElement;\n while (el && el !== root) {\n chain.unshift(el);\n el = el.parentElement;\n }\n return chain;\n }\n\n /**\n * Serialize an element's opening tag, preserving all attributes (class, style, data-*, etc).\n */\n private static _openTag(el: HTMLElement): string {\n const tag = el.tagName.toLowerCase();\n let attrs = '';\n for (const attr of Array.from(el.attributes)) {\n attrs += ` ${attr.name}=\"${this._escapeHtml(attr.value)}\"`;\n }\n return `<${tag}${attrs}>`;\n }\n\n /**\n * Escape HTML special characters\n */\n private static _escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n }\n}\n\n// Self-register with the registry\nSDKRegistry.textSplitter = { split: (element, type, options) => TextSplitter.split(element, type, options), revert: TextSplitter.revert, isSplit: TextSplitter.isSplit };\n",
|
|
40
|
+
"/**\n * StaggerResolver - Calculates stagger delays for multiple targets\n */\n\nimport type { StaggerVars, AnimationTarget } from '../types';\nimport { SDKRegistry } from '../registries/SDKRegistry';\n\nexport class StaggerResolver {\n /**\n * Calculate stagger delays for an array of targets\n * Works with both DOM elements and plain objects\n */\n static resolve(\n targets: AnimationTarget[],\n stagger: number | StaggerVars\n ): number[] {\n if (targets.length === 0) {\n return [];\n }\n\n if (targets.length === 1) {\n return [0];\n }\n\n // Simple numeric stagger\n if (typeof stagger === 'number') {\n return targets.map((_, index) => index * stagger);\n }\n\n // Complex stagger configuration\n return this.resolveComplexStagger(targets, stagger);\n }\n\n /**\n * Resolve complex stagger configuration with from, grid, etc.\n */\n private static resolveComplexStagger(\n targets: AnimationTarget[],\n config: StaggerVars\n ): number[] {\n const count = targets.length;\n\n // Single target has no stagger delay\n if (count === 1) {\n return [0];\n }\n\n // Calculate base delay per element\n let delayPerElement: number;\n if (config.each !== undefined) {\n delayPerElement = config.each;\n } else if (config.amount !== undefined) {\n // Total time distributed across all elements (count > 1 guaranteed here)\n delayPerElement = config.amount / (count - 1);\n } else {\n delayPerElement = 0.1; // Default\n }\n\n // Get ordering based on 'from' parameter\n const ordering = this.getOrdering(targets, config);\n\n // Calculate delays based on ordering\n const delays: number[] = new Array(count);\n for (let i = 0; i < count; i++) {\n delays[ordering[i].originalIndex] = ordering[i].order * delayPerElement;\n }\n\n return delays;\n }\n\n /**\n * Get target ordering based on 'from' parameter\n */\n private static getOrdering(\n targets: AnimationTarget[],\n config: StaggerVars\n ): Array<{ originalIndex: number; order: number }> {\n const count = targets.length;\n const from = config.from ?? 'start';\n\n // Handle grid stagger\n if (config.grid) {\n return this.getGridOrdering(targets, config);\n }\n\n // Handle 1D ordering\n switch (from) {\n case 'start':\n return targets.map((_, i) => ({ originalIndex: i, order: i }));\n\n case 'end':\n return targets.map((_, i) => ({\n originalIndex: i,\n order: count - 1 - i,\n }));\n\n case 'center': {\n const center = Math.floor(count / 2);\n return targets.map((_, i) => {\n const distanceFromCenter = Math.abs(i - center);\n return { originalIndex: i, order: distanceFromCenter };\n });\n }\n\n case 'edges': {\n // Edges animate first, center animates last\n return targets.map((_, i) => {\n const distanceFromEdge = Math.min(i, count - 1 - i);\n return {\n originalIndex: i,\n order: distanceFromEdge,\n };\n });\n }\n\n case 'random': {\n // Create random ordering\n const ordering = targets.map((_, i) => ({\n originalIndex: i,\n order: Math.random(),\n }));\n // Sort by random order\n ordering.sort((a, b) => a.order - b.order);\n // Reassign sequential order values\n ordering.forEach((item, index) => {\n item.order = index;\n });\n return ordering;\n }\n\n default:\n // Numeric index\n if (typeof from === 'number') {\n const fromIndex = Math.max(0, Math.min(count - 1, from));\n return targets.map((_, i) => {\n const distance = Math.abs(i - fromIndex);\n return { originalIndex: i, order: distance };\n });\n }\n // Default to start\n return targets.map((_, i) => ({ originalIndex: i, order: i }));\n }\n }\n\n /**\n * Get ordering for grid-based stagger\n */\n private static getGridOrdering(\n targets: AnimationTarget[],\n config: StaggerVars\n ): Array<{ originalIndex: number; order: number }> {\n let cols: number, rows: number;\n\n if (config.grid === 'auto') {\n // Auto-detect grid from element positions (DOM only)\n const gridDimensions = this.detectGrid(targets);\n cols = gridDimensions.cols;\n rows = gridDimensions.rows;\n } else {\n // Use specified grid dimensions\n [cols, rows] = config.grid as [number, number];\n }\n\n const from = config.from ?? 'start';\n const axis = config.axis ?? 'x';\n\n // Calculate grid position for each target\n const gridPositions = targets.map((_, index) => {\n const col = index % cols;\n const row = Math.floor(index / cols);\n return { originalIndex: index, col, row };\n });\n\n // Handle random separately - needs to shuffle all grid positions\n if (from === 'random') {\n const ordering = gridPositions.map((pos) => ({\n originalIndex: pos.originalIndex,\n order: Math.random(),\n }));\n ordering.sort((a, b) => a.order - b.order);\n ordering.forEach((item, index) => {\n item.order = index;\n });\n return ordering;\n }\n\n // Calculate order based on from position and axis\n return gridPositions.map((pos) => {\n let order: number;\n\n if (typeof from === 'number') {\n // From specific index in grid\n const fromCol = from % cols;\n const fromRow = Math.floor(from / cols);\n const distance =\n axis === 'x'\n ? Math.abs(pos.col - fromCol) + Math.abs(pos.row - fromRow) * 0.5\n : Math.abs(pos.row - fromRow) + Math.abs(pos.col - fromCol) * 0.5;\n order = distance;\n } else if (from === 'center') {\n const centerCol = (cols - 1) / 2;\n const centerRow = (rows - 1) / 2;\n const distance =\n Math.abs(pos.col - centerCol) + Math.abs(pos.row - centerRow);\n order = distance;\n } else if (from === 'edges') {\n const distanceFromEdgeCol = Math.min(pos.col, cols - 1 - pos.col);\n const distanceFromEdgeRow = Math.min(pos.row, rows - 1 - pos.row);\n const distanceFromEdge = Math.min(\n distanceFromEdgeCol,\n distanceFromEdgeRow\n );\n order = distanceFromEdge;\n } else if (from === 'end') {\n // From bottom-right, proceed based on axis\n order = axis === 'x'\n ? (rows - 1 - pos.row) * cols + (cols - 1 - pos.col)\n : (cols - 1 - pos.col) * rows + (rows - 1 - pos.row);\n } else {\n // Default (start): from top-left, proceed based on axis\n order = axis === 'x' ? pos.row * cols + pos.col : pos.col * rows + pos.row;\n }\n\n return { originalIndex: pos.originalIndex, order };\n });\n }\n\n /**\n * Auto-detect grid dimensions from element positions\n * Only works with DOM elements. For plain objects, falls back to linear arrangement.\n */\n private static detectGrid(targets: AnimationTarget[]): { cols: number; rows: number } {\n if (targets.length === 0) {\n return { cols: 0, rows: 0 };\n }\n\n // Filter to only DOM elements that have getBoundingClientRect\n const elements = targets.filter((t): t is Element => t instanceof Element);\n\n // If no DOM elements, fall back to single row\n if (elements.length === 0) {\n return { cols: targets.length, rows: 1 };\n }\n\n // Get all unique Y positions (rows)\n const yPositions = new Map<number, number>();\n elements.forEach((el) => {\n const rect = el.getBoundingClientRect();\n const y = Math.round(rect.top);\n yPositions.set(y, (yPositions.get(y) || 0) + 1);\n });\n\n const rows = yPositions.size;\n const cols = Math.ceil(targets.length / rows);\n\n return { cols, rows };\n }\n}\n\n// Self-register with the registry\nSDKRegistry.stagger = { resolve: (targets, stagger) => StaggerResolver.resolve(targets, stagger) };\n",
|
|
41
|
+
"/**\n * FitResolver — SDK-native Fit animation geometry resolution\n *\n * Captures source and target element bounding rects at play time,\n * computes position and scale deltas, and creates PropTweens that\n * animate the source element toward the target's geometry.\n *\n * Non-absolute mode (default): animates CSS transforms (x, y, scaleX, scaleY)\n * Resize mode: animates CSS transforms (x, y) + width/height properties (no content distortion)\n * Absolute mode: animates CSS left/top/width/height properties\n */\n\nimport type { FitConfig } from '../types';\nimport type { Animation } from '../core/Animation';\nimport { PropTweenPool } from '../memory/PropTweenPool';\nimport { SDKRegistry } from '../registries/SDKRegistry';\nimport { clearTransformCache } from '../render/TransformCache';\n\n// ─────────────────────────────────────────────\n// Geometry types\n// ─────────────────────────────────────────────\n\ninterface FitGeometry {\n dx: number;\n dy: number;\n sx: number;\n sy: number;\n sourceWidth: number;\n sourceHeight: number;\n targetWidth: number;\n targetHeight: number;\n /** Only present when absolute === true */\n sourceLeft?: number;\n /** Only present when absolute === true */\n sourceTop?: number;\n /** Only present when absolute === true */\n targetLeft?: number;\n /** Only present when absolute === true */\n targetTop?: number;\n}\n\n// ─────────────────────────────────────────────\n// Geometry capture\n// ─────────────────────────────────────────────\n\nfunction captureGeometry(\n source: Element,\n target: string,\n absolute: boolean\n): FitGeometry | null {\n // Issue #4: Guard against SVG and other non-HTML elements.\n // offsetLeft/offsetTop are unsafe on SVG elements; bail out early.\n if (!(source instanceof HTMLElement)) {\n console.warn('[Motion Fit] Fit animations only support HTML elements. SVG elements are not supported.');\n return null;\n }\n\n const targetEl = document.querySelector(target);\n if (!targetEl) {\n console.warn(`[Motion Fit] Target element not found: \"${target}\". Animation skipped.`);\n return null;\n }\n\n // Issue #4: Same HTMLElement guard for the target.\n if (!(targetEl instanceof HTMLElement)) {\n console.warn('[Motion Fit] Fit animations only support HTML elements. SVG elements are not supported.');\n return null;\n }\n\n if (targetEl === source) {\n console.warn('[Motion Fit] Target element is the same as source element. Animation skipped.');\n return null;\n }\n\n // Always needed: bounding rects for delta and scale computation.\n const sourceRect = source.getBoundingClientRect();\n const targetRect = targetEl.getBoundingClientRect();\n\n const sourceW = sourceRect.width === 0 ? 1 : sourceRect.width;\n const sourceH = sourceRect.height === 0 ? 1 : sourceRect.height;\n\n // Use center-to-center deltas because CSS transform-origin defaults to 50% 50%.\n // Scale expands from center, so translating between centers ensures the final\n // geometry lands exactly on the target.\n const sourceCenterX = sourceRect.left + sourceW / 2;\n const sourceCenterY = sourceRect.top + sourceH / 2;\n const targetCenterX = targetRect.left + targetRect.width / 2;\n const targetCenterY = targetRect.top + targetRect.height / 2;\n const dx = targetCenterX - sourceCenterX;\n const dy = targetCenterY - sourceCenterY;\n const sx = targetRect.width / sourceW;\n const sy = targetRect.height / sourceH;\n\n if (!absolute) {\n // Issue #2: Transform mode only needs dx/dy/sx/sy — skip all offset-parent DOM reads.\n return {\n dx,\n dy,\n sx,\n sy,\n sourceWidth: sourceRect.width,\n sourceHeight: sourceRect.height,\n targetWidth: targetRect.width,\n targetHeight: targetRect.height,\n };\n }\n\n // Issue #2: Absolute mode only — compute offset-parent-relative positions for left/top tweens.\n // After the instanceof HTMLElement guard above, no casts are needed for source or targetEl.\n const offsetParent = source.offsetParent ?? document.documentElement;\n const parentRect = offsetParent.getBoundingClientRect();\n const parentScrollLeft = (offsetParent as HTMLElement).scrollLeft ?? 0;\n const parentScrollTop = (offsetParent as HTMLElement).scrollTop ?? 0;\n\n return {\n dx,\n dy,\n sx,\n sy,\n sourceWidth: sourceRect.width,\n sourceHeight: sourceRect.height,\n targetWidth: targetRect.width,\n targetHeight: targetRect.height,\n sourceLeft: source.offsetLeft,\n sourceTop: source.offsetTop,\n targetLeft: targetRect.left - parentRect.left + parentScrollLeft,\n targetTop: targetRect.top - parentRect.top + parentScrollTop,\n };\n}\n\n// ─────────────────────────────────────────────\n// PropTween creation\n// ─────────────────────────────────────────────\n\nconst DELTA_EPSILON = 0.01;\nconst SCALE_EPSILON = 0.001;\n\nfunction createPropTweens(\n animation: Animation,\n source: Element,\n fitConfig: FitConfig,\n pool: typeof PropTweenPool\n): void {\n // Destructure absolute/scale/resize before calling captureGeometry so flags can be forwarded.\n const { absolute = false, scale = true, resize = false } = fitConfig;\n\n // Clear stale inline styles and transform cache left over from previous Fit cycles.\n // This ensures getBoundingClientRect() returns the element's natural CSS-computed\n // geometry rather than the inline values written by the last animation.\n if (source instanceof HTMLElement) {\n clearTransformCache(source, true); // clears WeakMap entry + inline transform\n if (resize) {\n source.style.width = '';\n source.style.height = '';\n }\n if (absolute) {\n source.style.width = '';\n source.style.height = '';\n source.style.left = '';\n source.style.top = '';\n }\n }\n\n const geometry = captureGeometry(source, fitConfig.target, absolute);\n if (!geometry) return;\n\n if (!absolute) {\n if (resize) {\n // Resize mode: animate width/height (no content distortion, but triggers layout reflow).\n // width/height grow from the top-left corner, so the translation delta must be adjusted\n // from center-to-center (dx/dy) to top-left-to-top-left:\n // dxTopLeft = dxCenter - (targetWidth - sourceWidth) / 2\n const dxTopLeft = geometry.dx - (geometry.targetWidth - geometry.sourceWidth) / 2;\n const dyTopLeft = geometry.dy - (geometry.targetHeight - geometry.sourceHeight) / 2;\n\n if (Math.abs(dxTopLeft) > DELTA_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'x', 0, dxTopLeft, 'px');\n animation.addPropTween(pt);\n }\n if (Math.abs(dyTopLeft) > DELTA_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'y', 0, dyTopLeft, 'px');\n animation.addPropTween(pt);\n }\n if (Math.abs(geometry.targetWidth - geometry.sourceWidth) > DELTA_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'width', geometry.sourceWidth, geometry.targetWidth, 'px');\n animation.addPropTween(pt);\n }\n if (Math.abs(geometry.targetHeight - geometry.sourceHeight) > DELTA_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'height', geometry.sourceHeight, geometry.targetHeight, 'px');\n animation.addPropTween(pt);\n }\n } else {\n // Transform mode: translate + optional scale (scaleX/scaleY).\n if (Math.abs(geometry.dx) > DELTA_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'x', 0, geometry.dx, 'px');\n animation.addPropTween(pt);\n }\n if (Math.abs(geometry.dy) > DELTA_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'y', 0, geometry.dy, 'px');\n animation.addPropTween(pt);\n }\n if (scale) {\n if (Math.abs(geometry.sx - 1) > SCALE_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'scaleX', 1, geometry.sx, '');\n animation.addPropTween(pt);\n }\n if (Math.abs(geometry.sy - 1) > SCALE_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'scaleY', 1, geometry.sy, '');\n animation.addPropTween(pt);\n }\n }\n }\n } else {\n const el = source as HTMLElement;\n\n // KNOWN LIMITATION (v1): Absolute mode permanently sets position: absolute on the element.\n // Future versions should store the original position value and revert it on animation complete.\n // See fit-sdk-v1.md spec for details.\n const currentPosition = window.getComputedStyle(el).position;\n if (currentPosition === 'static') {\n el.style.position = 'absolute';\n }\n\n // sourceLeft/sourceTop/targetLeft/targetTop are always defined when absolute === true\n // because captureGeometry only populates them in the absolute branch.\n const sourceLeft = geometry.sourceLeft!;\n const sourceTop = geometry.sourceTop!;\n const targetLeft = geometry.targetLeft!;\n const targetTop = geometry.targetTop!;\n\n if (Math.abs(targetLeft - sourceLeft) > DELTA_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'left', sourceLeft, targetLeft, 'px');\n animation.addPropTween(pt);\n }\n if (Math.abs(targetTop - sourceTop) > DELTA_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'top', sourceTop, targetTop, 'px');\n animation.addPropTween(pt);\n }\n if (scale) {\n if (Math.abs(geometry.targetWidth - geometry.sourceWidth) > DELTA_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'width', geometry.sourceWidth, geometry.targetWidth, 'px');\n animation.addPropTween(pt);\n }\n if (Math.abs(geometry.targetHeight - geometry.sourceHeight) > DELTA_EPSILON) {\n const pt = pool.acquire();\n pt.init(source, 'height', geometry.sourceHeight, geometry.targetHeight, 'px');\n animation.addPropTween(pt);\n }\n }\n }\n}\n\n// ─────────────────────────────────────────────\n// Public API\n// ─────────────────────────────────────────────\n\nfunction registerPendingSetup(\n animation: Animation,\n element: Element,\n fitConfig: FitConfig,\n // pool is a protocol parameter required by the SDKRegistry.fit interface but is unused here.\n // The actual pool instance is provided at call time via the closure argument `p` in\n // setPendingFitSetup, not at registration time.\n pool: typeof PropTweenPool\n): void {\n animation.setPendingFitSetup((p) => createPropTweens(animation, element, fitConfig, p));\n animation.setLazyStartCapture(true);\n}\n\n// ─────────────────────────────────────────────\n// Self-registration\n// ─────────────────────────────────────────────\n\nSDKRegistry.fit = { registerPendingSetup };\n",
|
|
42
|
+
"/**\n * Easing Functions\n *\n * All easings are based on Robert Penner's easing equations\n * and normalized to accept/return values in the 0-1 range\n */\n\nimport type { EasingFunction } from '../types';\n\n// Linear\nexport const linear = (t: number): number => t;\nexport const none = linear;\n\n// Power easings (Quad, Cubic, Quart, Quint)\nexport const power1 = {\n in: (t: number): number => t * t,\n out: (t: number): number => t * (2 - t),\n inOut: (t: number): number => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\n};\n\nexport const power2 = {\n in: (t: number): number => t * t * t,\n out: (t: number): number => --t * t * t + 1,\n inOut: (t: number): number => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1),\n};\n\nexport const power3 = {\n in: (t: number): number => t * t * t * t,\n out: (t: number): number => 1 - --t * t * t * t,\n inOut: (t: number): number => (t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t),\n};\n\nexport const power4 = {\n in: (t: number): number => t * t * t * t * t,\n out: (t: number): number => 1 + --t * t * t * t * t,\n inOut: (t: number): number => (t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t),\n};\n\n// Sine\nexport const sine = {\n in: (t: number): number => 1 - Math.cos((t * Math.PI) / 2),\n out: (t: number): number => Math.sin((t * Math.PI) / 2),\n inOut: (t: number): number => -(Math.cos(Math.PI * t) - 1) / 2,\n};\n\n// Exponential\nexport const expo = {\n in: (t: number): number => (t === 0 ? 0 : Math.pow(2, 10 * (t - 1))),\n out: (t: number): number => (t === 1 ? 1 : 1 - Math.pow(2, -10 * t)),\n inOut: (t: number): number => {\n if (t === 0) return 0;\n if (t === 1) return 1;\n if ((t *= 2) < 1) return 0.5 * Math.pow(2, 10 * (t - 1));\n return 0.5 * (2 - Math.pow(2, -10 * (t - 1)));\n },\n};\n\n// Circular\nexport const circ = {\n in: (t: number): number => 1 - Math.sqrt(1 - t * t),\n out: (t: number): number => Math.sqrt(1 - --t * t),\n inOut: (t: number): number => {\n if ((t *= 2) < 1) return -0.5 * (Math.sqrt(1 - t * t) - 1);\n return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);\n },\n};\n\n// Back (overshoots then returns)\nconst backS = 1.70158;\nexport const back = {\n in: (t: number): number => t * t * ((backS + 1) * t - backS),\n out: (t: number): number => --t * t * ((backS + 1) * t + backS) + 1,\n inOut: (t: number): number => {\n const s = backS * 1.525;\n if ((t *= 2) < 1) return 0.5 * (t * t * ((s + 1) * t - s));\n return 0.5 * ((t -= 2) * t * ((s + 1) * t + s) + 2);\n },\n};\n\n// Elastic (spring-like oscillation)\nexport const elastic = {\n in: (t: number): number => {\n if (t === 0) return 0;\n if (t === 1) return 1;\n const p = 0.3;\n const s = p / 4;\n return -(Math.pow(2, 10 * (t -= 1)) * Math.sin(((t - s) * (2 * Math.PI)) / p));\n },\n out: (t: number): number => {\n if (t === 0) return 0;\n if (t === 1) return 1;\n const p = 0.3;\n const s = p / 4;\n return Math.pow(2, -10 * t) * Math.sin(((t - s) * (2 * Math.PI)) / p) + 1;\n },\n inOut: (t: number): number => {\n if (t === 0) return 0;\n if (t === 1) return 1;\n const p = 0.3 * 1.5;\n const s = p / 4;\n if ((t *= 2) < 1) {\n return -0.5 * (Math.pow(2, 10 * (t -= 1)) * Math.sin(((t - s) * (2 * Math.PI)) / p));\n }\n return Math.pow(2, -10 * (t -= 1)) * Math.sin(((t - s) * (2 * Math.PI)) / p) * 0.5 + 1;\n },\n};\n\n// Bounce (bouncing ball effect)\nconst bounceOut = (t: number): number => {\n if (t < 1 / 2.75) {\n return 7.5625 * t * t;\n } else if (t < 2 / 2.75) {\n return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;\n } else if (t < 2.5 / 2.75) {\n return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;\n } else {\n return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;\n }\n};\n\nexport const bounce = {\n in: (t: number): number => 1 - bounceOut(1 - t),\n out: bounceOut,\n inOut: (t: number): number => {\n if (t < 0.5) return bounce.in(t * 2) * 0.5;\n return bounce.out(t * 2 - 1) * 0.5 + 0.5;\n },\n};\n\n// Easing lookup map\nconst easingMap: Record<string, EasingFunction> = {\n // Linear\n linear,\n none,\n\n // Power1\n 'power1.in': power1.in,\n 'power1.out': power1.out,\n 'power1.inout': power1.inOut,\n\n // Power2\n 'power2.in': power2.in,\n 'power2.out': power2.out,\n 'power2.inout': power2.inOut,\n\n // Power3\n 'power3.in': power3.in,\n 'power3.out': power3.out,\n 'power3.inout': power3.inOut,\n\n // Power4\n 'power4.in': power4.in,\n 'power4.out': power4.out,\n 'power4.inout': power4.inOut,\n\n // Sine\n 'sine.in': sine.in,\n 'sine.out': sine.out,\n 'sine.inout': sine.inOut,\n\n // Expo\n 'expo.in': expo.in,\n 'expo.out': expo.out,\n 'expo.inout': expo.inOut,\n\n // Circ\n 'circ.in': circ.in,\n 'circ.out': circ.out,\n 'circ.inout': circ.inOut,\n\n // Back\n 'back.in': back.in,\n 'back.out': back.out,\n 'back.inout': back.inOut,\n\n // Elastic\n 'elastic.in': elastic.in,\n 'elastic.out': elastic.out,\n 'elastic.inout': elastic.inOut,\n\n // Bounce\n 'bounce.in': bounce.in,\n 'bounce.out': bounce.out,\n 'bounce.inout': bounce.inOut,\n};\n\n/**\n * Get an easing function by string name\n * @param name Easing name (case insensitive)\n * @returns Easing function, or power1.out if not found\n */\nexport function getEasing(name: string): EasingFunction {\n const key = name.toLowerCase();\n if (!easingMap[key]) {\n console.warn(\n `[Motion] Unknown easing \"${name}\". Falling back to \"power1.out\". ` +\n `Valid easings: linear, power1-4.in/out/inOut, sine, expo, circ, back, elastic, bounce`\n );\n }\n return easingMap[key] || power1.out;\n}\n",
|
|
43
|
+
"/**\n * Animation - Individual animation unit\n *\n * Manages interpolation of properties over time with easing.\n * Pooled for performance.\n */\n\nimport type { AnimationTarget, StaggerVars } from '../types';\n\n/**\n * Internal animation configuration (not exported from SDK)\n */\nexport interface InternalAnimationConfig {\n repeat?: number;\n repeatDelay?: number;\n yoyo?: boolean;\n stagger?: number | StaggerVars;\n onStart?: () => void;\n onUpdate?: (progress: number) => void;\n onComplete?: () => void;\n onRepeat?: (repeatCount: number) => void;\n onReverseComplete?: () => void;\n /**\n * When true, Engine skips the immediate render(0) for FROM animations.\n * Used by Timeline._addBuilder so it can capture initial values BEFORE\n * FROM values are applied to the DOM, then render FROM itself afterward.\n */\n _skipInitialRender?: boolean;\n}\nimport type { Timeline } from './Timeline';\nimport { PropTween } from './PropTween';\nimport { PropTweenPool } from '../memory/PropTweenPool';\nimport { getEasing } from '../easing';\nimport { getCurrentValue, isColorProp, isFilterProp } from '../utils/PropertyParser';\nimport { SDKRegistry } from '../registries/SDKRegistry';\n\nexport class Animation {\n // Animation state\n private _id: number = 0;\n private _targets: AnimationTarget[] = [];\n private _duration: number = 0.5;\n private _delay: number = 0;\n private _time: number = 0;\n private _progress: number = 0;\n private _timeScale: number = 1;\n private _isActive: boolean = false;\n private _isReversed: boolean = false;\n\n // Property tweens (linked list head + tail for O(1) append)\n private _propTweens: PropTween | null = null;\n private _lastPropTween: PropTween | null = null;\n\n // Animation config\n private _easingFn: ((t: number) => number) | null = null;\n private _repeat: number = 0;\n private _repeatDelay: number = 0;\n private _yoyo: boolean = false;\n private _currentRepeat: number = 0;\n\n // Callbacks (properly typed)\n private _onStart?: () => void;\n private _onUpdate?: (progress: number) => void;\n private _onComplete?: () => void;\n private _onRepeat?: (repeatCount: number) => void;\n private _onReverseComplete?: () => void;\n private _startFired: boolean = false;\n private _completeFired: boolean = false;\n\n // Flag to indicate this animation is controlled by a Timeline\n // When true, Engine should NOT update this animation directly\n // and should NOT auto-pool this animation on completion\n private _timelineChild: boolean = false;\n\n // Flag for lazy start value capture (for timeline chaining)\n private _needsStartCapture: boolean = false;\n private _startCaptured: boolean = false;\n\n // Fit animation support: setup function that captures geometry and creates PropTweens at play time\n private _fitSetupFn: ((pool: typeof PropTweenPool) => void) | null = null;\n private _pendingFitSetup: ((pool: typeof PropTweenPool) => void) | null = null;\n\n // Callback for unregistering from Engine (avoids circular dependency)\n private _unregisterCallback: (() => void) | null = null;\n\n /**\n * Initialize the animation\n */\n init(\n id: number,\n targets: AnimationTarget[],\n duration: number,\n delay: number,\n ease: string,\n config?: InternalAnimationConfig\n ): this {\n this._id = id;\n this._targets = targets;\n this._duration = duration;\n this._delay = delay;\n this._easingFn = getEasing(ease);\n this._time = 0;\n this._progress = 0;\n this._timeScale = 1;\n this._isActive = false;\n this._isReversed = false;\n this._startFired = false;\n this._completeFired = false;\n this._currentRepeat = 0;\n\n // Apply config\n this._repeat = config?.repeat ?? 0;\n this._repeatDelay = config?.repeatDelay ?? 0;\n this._yoyo = config?.yoyo ?? false;\n this._onStart = config?.onStart;\n this._onUpdate = config?.onUpdate;\n this._onComplete = config?.onComplete;\n this._onRepeat = config?.onRepeat;\n this._onReverseComplete = config?.onReverseComplete;\n\n return this;\n }\n\n /**\n * Update animation by delta time\n */\n update(deltaTime: number): void {\n if (!this._isActive) return;\n\n // Apply time scale\n const scaledDelta = deltaTime * this._timeScale;\n\n // Update time\n this._time += this._isReversed ? -scaledDelta : scaledDelta;\n\n // Handle delays for forward playback\n if (!this._isReversed && this._time < 0) {\n this._time = 0;\n return;\n }\n\n // Calculate progress\n const activeTime = Math.max(0, this._time - this._delay);\n this._progress = this._duration > 0 ? Math.min(1, activeTime / this._duration) : 1;\n\n // Fire onStart callback on first update\n if (!this._startFired && this._progress > 0) {\n this._startFired = true;\n this._onStart?.();\n }\n\n // Render at current progress\n this.render(this._progress);\n\n // Fire onUpdate callback with current progress\n this._onUpdate?.(this._progress);\n\n // Check if complete\n if (this._isReversed) {\n // When reversing, check if we've reached the beginning\n if (this._time <= 0) {\n this.handleComplete();\n }\n } else {\n // When playing forward, check if we've reached the end\n if (this._progress >= 1) {\n this.handleComplete();\n }\n }\n }\n\n /**\n * Render animation at specific progress (0-1)\n */\n render(progress: number): void {\n // Apply easing\n const easedProgress = this.applyEasing(progress);\n\n // Render all property tweens\n let propTween = this._propTweens;\n while (propTween) {\n propTween.render(easedProgress);\n propTween = propTween.next;\n }\n }\n\n /**\n * Render animation at an absolute time position (seconds)\n * Handles repeat, repeatDelay, and yoyo for timeline-driven scrubbing.\n * When driven by a Timeline (_isActive = true), also fires lifecycle callbacks.\n */\n renderAtTime(totalTime: number): void {\n const clampedTime = Math.max(0, totalTime);\n const duration = this._duration;\n const delay = this._delay;\n // Save repeat count before any update so we can detect cycle advances\n const prevRepeat = this._currentRepeat;\n\n if (duration === 0) {\n const progress = clampedTime >= delay ? 1 : 0;\n this._time = clampedTime;\n this._progress = progress;\n this.render(progress);\n } else {\n const activeTime = Math.max(0, clampedTime - delay);\n\n if (this._repeat === 0) {\n const progress = Math.min(1, activeTime / duration);\n this._time = Math.min(duration, activeTime);\n this._progress = progress;\n this.render(progress);\n } else {\n const repeatDelay = this._repeatDelay;\n const cycleDuration = duration + repeatDelay;\n const totalActiveDuration = this._repeat === -1\n ? Infinity\n : duration * (this._repeat + 1) + repeatDelay * this._repeat;\n\n const cappedActiveTime = this._repeat === -1\n ? activeTime\n : Math.min(activeTime, totalActiveDuration);\n\n const cycleIndex = cycleDuration > 0\n ? Math.floor(cappedActiveTime / cycleDuration)\n : 0;\n const cycleTime = cappedActiveTime - cycleIndex * cycleDuration;\n const inRepeatDelay = cycleTime > duration;\n\n let cycleProgress = duration > 0 ? Math.min(1, cycleTime / duration) : 1;\n const isYoyoCycle = this._yoyo && cycleIndex % 2 === 1;\n\n if (inRepeatDelay) {\n cycleProgress = isYoyoCycle ? 0 : 1;\n } else if (isYoyoCycle) {\n cycleProgress = 1 - cycleProgress;\n }\n\n this._currentRepeat = this._repeat === -1 ? cycleIndex : Math.min(cycleIndex, this._repeat);\n this._isReversed = isYoyoCycle;\n this._progress = cycleProgress;\n this._time = duration * cycleProgress;\n this.render(cycleProgress);\n }\n }\n\n // Fire lifecycle callbacks when driven by Timeline.\n // The Timeline sets _isActive = true via play() before the first active renderAtTime call\n // and _isActive = false via pause() when exiting the active window.\n // This guard ensures callbacks never fire during scrubbing (seek/progress ops).\n if (this._isActive) {\n // onStart — fires once on first active render\n if (!this._startFired) {\n this._startFired = true;\n try { this._onStart?.(); } catch (e) { console.error('[Motion] onStart callback error:', e); }\n }\n\n // onUpdate — fires every active frame\n try { this._onUpdate?.(this._progress); } catch (e) { console.error('[Motion] onUpdate callback error:', e); }\n\n // onRepeat — fires when the repeat cycle advances\n if (this._currentRepeat > prevRepeat) {\n try { this._onRepeat?.(this._currentRepeat); } catch (e) { console.error('[Motion] onRepeat callback error:', e); }\n }\n\n // onComplete — fires once when reaching total duration (not for infinite repeats)\n if (!this._completeFired && this._repeat !== -1) {\n if (clampedTime >= this.totalDuration()) {\n this._completeFired = true;\n try { this._onComplete?.(); } catch (e) { console.error('[Motion] onComplete callback error:', e); }\n }\n }\n }\n }\n\n /**\n * Apply easing function to progress\n */\n private applyEasing(progress: number): number {\n // Guard against null (fallback to linear if called before init)\n if (!this._easingFn) return progress;\n return this._easingFn(progress);\n }\n\n /**\n * Handle animation completion\n */\n private handleComplete(): void {\n // Check if we should repeat\n if (this._repeat === -1 || this._currentRepeat < this._repeat) {\n this._currentRepeat++;\n\n // Fire onRepeat callback with current repeat count\n this._onRepeat?.(this._currentRepeat);\n\n // Reset time for repeat\n if (this._yoyo) {\n this._isReversed = !this._isReversed;\n this._time = this._isReversed ? this._duration + this._delay : 0;\n } else {\n this._time = 0;\n this._startFired = false;\n }\n\n // Apply repeat delay\n this._time -= this._repeatDelay;\n\n return;\n }\n\n // Fire completion callback\n if (this._isReversed) {\n this._onReverseComplete?.();\n } else {\n this._onComplete?.();\n }\n\n // Animation is complete\n this._isActive = false;\n }\n\n /**\n * Play the animation\n * - If already playing forward, does nothing\n * - If paused, resumes from current position\n */\n play(from?: number): this {\n if (from !== undefined) {\n this.seek(from);\n this._isActive = true;\n this._isReversed = false;\n return this;\n }\n\n // If already playing forward, do nothing\n if (this._isActive && !this._isReversed) {\n return this;\n }\n\n this._isActive = true;\n this._isReversed = false;\n return this;\n }\n\n /**\n * Pause the animation\n */\n pause(atTime?: number): this {\n if (atTime !== undefined) {\n this._time = atTime;\n const activeTime = Math.max(0, this._time - this._delay);\n this._progress = this._duration > 0 ? Math.min(1, activeTime / this._duration) : 1;\n this.render(this._progress);\n }\n this._isActive = false;\n return this;\n }\n\n /**\n * Reverse the animation\n */\n reverse(from?: number): this {\n if (from !== undefined) {\n this.seek(from);\n }\n this._isActive = true;\n this._isReversed = true;\n return this;\n }\n\n /**\n * Restart the animation\n */\n restart(includeDelay: boolean = true): this {\n this._time = includeDelay ? 0 : this._delay;\n this._progress = 0;\n this._isActive = true;\n this._isReversed = false;\n this._startFired = false;\n this._currentRepeat = 0;\n this.render(0);\n return this;\n }\n\n /**\n * Seek to specific position\n */\n seek(position: number): this {\n this._time = position;\n const activeTime = Math.max(0, this._time - this._delay);\n this._progress = this._duration > 0 ? Math.min(1, activeTime / this._duration) : 1;\n this.render(this._progress);\n return this;\n }\n\n /**\n * Get/set progress (0-1)\n */\n progress(): number;\n progress(value: number): this;\n progress(value?: number): number | this {\n if (value === undefined) {\n return this._progress;\n }\n this._progress = Math.max(0, Math.min(1, value));\n // When setting progress to 0, set time to 0 so stagger delays work properly\n // Otherwise, calculate time from progress (positioned after delay)\n if (this._progress === 0) {\n this._time = 0;\n } else {\n this._time = this._progress * this._duration + this._delay;\n }\n this.render(this._progress);\n return this;\n }\n\n /**\n * Get/set time in seconds\n */\n time(): number;\n time(value: number): this;\n time(value?: number): number | this {\n if (value === undefined) {\n return this._time;\n }\n this._time = value;\n const activeTime = Math.max(0, this._time - this._delay);\n this._progress = this._duration > 0 ? Math.min(1, activeTime / this._duration) : 1;\n this.render(this._progress);\n return this;\n }\n\n /**\n * Get/set timeScale multiplier\n */\n timeScale(): number;\n timeScale(value: number): this;\n timeScale(value?: number): number | this {\n if (value === undefined) {\n return this._timeScale;\n }\n this._timeScale = value;\n return this;\n }\n\n /**\n * Get timeScale multiplier (typed getter)\n */\n getTimeScale(): number {\n return this._timeScale;\n }\n\n /**\n * Kill the animation\n */\n kill(): void {\n this._isActive = false;\n this._unregisterCallback?.();\n }\n\n /**\n * Check if animation is active\n */\n isActive(): boolean {\n return this._isActive;\n }\n\n /**\n * Check if this animation is a child of a Timeline\n * When true, Engine won't update or auto-pool this animation\n */\n isTimelineChild(): boolean {\n return this._timelineChild;\n }\n\n /**\n * Set whether this animation is a child of a Timeline\n * Timeline children are controlled by their parent timeline and\n * won't be auto-pooled until the timeline is killed\n */\n setTimelineChild(isChild: boolean): void {\n this._timelineChild = isChild;\n }\n\n /**\n * Enable lazy start value capture (for timeline chaining)\n * When enabled, start values are captured on first render instead of at creation\n */\n setLazyStartCapture(enabled: boolean): void {\n this._needsStartCapture = enabled;\n this._startCaptured = false;\n }\n\n /**\n * Register a Fit setup function to execute at play time.\n * Stores both a persistent copy (_fitSetupFn) for replay and an active\n * copy (_pendingFitSetup) that is consumed by captureStartValues().\n */\n setPendingFitSetup(fn: (pool: typeof PropTweenPool) => void): void {\n this._fitSetupFn = fn;\n this._pendingFitSetup = fn;\n }\n\n /**\n * Reset for timeline replay\n * Resets callback state for clean replay but KEEPS captured start values\n * so TO-only animations replay from their original start position.\n * Fit animations re-enable their setup so geometry is re-captured on the\n * next play (picking up any intervening DOM changes).\n */\n resetForReplay(): void {\n // Re-enable Fit setup so geometry is re-captured on the next play\n if (this._fitSetupFn) {\n this._pendingFitSetup = this._fitSetupFn;\n this._startCaptured = false;\n }\n // Don't reset _startCaptured for normal animations - we want to KEEP the\n // originally captured start values so the animation replays from the same\n // starting point\n this._startFired = false;\n this._completeFired = false;\n }\n\n /**\n * Check if this animation needs lazy start capture but hasn't captured yet\n * Used by Timeline to trigger capture when animation starts\n */\n needsStartCapture(): boolean {\n return this._needsStartCapture && !this._startCaptured;\n }\n\n /**\n * Check if this animation uses lazy start capture (TO-only animation)\n * These animations should NEVER render before their start time,\n * even after values have been captured, to avoid overwriting earlier animations\n */\n isLazyCapture(): boolean {\n return this._needsStartCapture;\n }\n\n /**\n * Fire the onReverseComplete callback and reset start/complete flags.\n * Called by Timeline when an active animation exits the start boundary (going in reverse).\n * Resetting flags allows onStart and onComplete to fire correctly on the next forward play.\n */\n fireReverseComplete(): void {\n try { this._onReverseComplete?.(); } catch (e) { console.error('[Motion] onReverseComplete callback error:', e); }\n // Reset flags so callbacks fire correctly on next forward play\n this._startFired = false;\n this._completeFired = false;\n }\n\n /**\n * Set callback for unregistering from Engine (called on kill)\n * This avoids circular dependency with Engine\n */\n setUnregisterCallback(callback: () => void): void {\n this._unregisterCallback = callback;\n }\n\n /**\n * Capture current element values as start values for all PropTweens\n * This enables proper chaining in timelines\n */\n captureStartValues(): void {\n if (!this._needsStartCapture || this._startCaptured) return;\n\n // Fit handling — executes after the guard passes.\n // Releases any placeholder PropTweens created at build time, then\n // invokes the setup function which captures source and target element\n // geometry and builds PropTweens from the source→target delta.\n if (this._pendingFitSetup) {\n let propTween = this._propTweens;\n while (propTween) {\n const next = propTween.next;\n PropTweenPool.release(propTween);\n propTween = next;\n }\n this._propTweens = null;\n this._lastPropTween = null;\n\n this._pendingFitSetup(PropTweenPool);\n this._pendingFitSetup = null;\n this._startCaptured = true;\n return;\n }\n\n let propTween = this._propTweens;\n while (propTween) {\n // Only capture start values for DOM elements (not plain objects)\n if (propTween.target instanceof Element) {\n const element = propTween.target;\n const property = propTween.property;\n\n if (propTween.valueType === 'color' && isColorProp(property)) {\n // Capture current color value\n const currentColor = SDKRegistry.color?.getCurrentColor(element, property);\n if (currentColor && propTween.endColor) {\n propTween.startColor = currentColor;\n // Recalculate change for each channel\n propTween.changeColor = new Float32Array([\n propTween.endColor[0] - currentColor[0],\n propTween.endColor[1] - currentColor[1],\n propTween.endColor[2] - currentColor[2],\n propTween.endColor[3] - currentColor[3]\n ]);\n }\n } else if (propTween.valueType === 'filter' && isFilterProp(property)) {\n // Capture current filter value\n const currentFilters = SDKRegistry.filter?.getCurrentFilter(element);\n if (currentFilters && propTween.endFilters) {\n // Merge to ensure consistent array structure\n const merged = SDKRegistry.filter!.mergeFilterArrays(currentFilters, propTween.endFilters);\n propTween.startFilters = merged.start;\n propTween.endFilters = merged.end;\n }\n } else {\n // Scalar value - get current numeric value\n const currentValue = getCurrentValue(element, property);\n propTween.startValue = currentValue;\n propTween.change = propTween.endValue - propTween.startValue;\n }\n }\n propTween = propTween.next;\n }\n\n this._startCaptured = true;\n }\n\n /**\n * Get animation ID\n */\n getId(): number {\n return this._id;\n }\n\n /**\n * Get animation targets\n */\n getTargets(): AnimationTarget[] {\n return this._targets;\n }\n\n /**\n * Get duration\n */\n getDuration(): number {\n return this._duration;\n }\n\n /**\n * Get delay (used by Timeline for stagger positioning)\n */\n getDelay(): number {\n return this._delay;\n }\n\n /**\n * Clear delay (used by Timeline after extracting delay for positioning)\n */\n clearDelay(): void {\n this._delay = 0;\n }\n\n /**\n * Get the head of the PropTween linked list.\n * @internal - Use only within sdk package for property inspection (e.g. captureStartValues).\n */\n getFirstPropTween(): PropTween | null {\n return this._propTweens;\n }\n\n /**\n * Add a property tween to the linked list\n */\n addPropTween(propTween: PropTween): void {\n if (!this._lastPropTween) {\n this._propTweens = propTween;\n } else {\n this._lastPropTween.next = propTween;\n }\n this._lastPropTween = propTween;\n }\n\n /**\n * Get animation duration (including delay and repeats)\n * Returns Infinity for infinitely repeating animations (_repeat === -1).\n */\n totalDuration(): number {\n if (this._repeat === -1) return Infinity;\n const baseDuration = this._duration;\n const total = baseDuration * (this._repeat + 1) + this._repeatDelay * this._repeat;\n return this._delay + total;\n }\n\n /**\n * Reset for object pooling\n */\n reset(): void {\n this._id = 0;\n this._targets = [];\n this._duration = 0.5;\n this._delay = 0;\n this._time = 0;\n this._progress = 0;\n this._timeScale = 1;\n this._isActive = false;\n this._isReversed = false;\n this._easingFn = null;\n this._repeat = 0;\n this._repeatDelay = 0;\n this._yoyo = false;\n this._currentRepeat = 0;\n this._onStart = undefined;\n this._onUpdate = undefined;\n this._onComplete = undefined;\n this._onRepeat = undefined;\n this._onReverseComplete = undefined;\n this._startFired = false;\n this._completeFired = false;\n this._timelineChild = false;\n this._needsStartCapture = false;\n this._startCaptured = false;\n this._fitSetupFn = null;\n this._pendingFitSetup = null;\n this._unregisterCallback = null;\n\n // Release all PropTweens back to pool\n const pool = PropTweenPool;\n let propTween = this._propTweens;\n while (propTween) {\n const next = propTween.next;\n pool.release(propTween);\n propTween = next;\n }\n this._propTweens = null;\n this._lastPropTween = null;\n }\n}\n",
|
|
44
|
+
"/**\n * AnimationPool - Singleton object pool for Animation instances\n *\n * Reuses Animation objects to minimize garbage collection overhead\n */\n\nimport { Animation } from '../core/Animation';\nimport { ObjectPool } from './ObjectPool';\n\nconst pool = new ObjectPool<Animation>(() => new Animation(), 100);\n\nexport const AnimationPool = {\n acquire: () => pool.acquire(),\n release: (animation: Animation) => pool.release(animation),\n getPoolSize: () => pool.getPoolSize(),\n getTotalCreated: () => pool.getTotalCreated(),\n clear: () => pool.clear(),\n};\n\nexport type AnimationPool = typeof AnimationPool;\n",
|
|
45
|
+
"/**\n * Engine Singleton - Central animation registry and coordinator\n *\n * Manages:\n * - Global animation registry\n * - Named timeline registry\n * - Object pool coordination\n * - Ticker integration\n * - Render queue routing\n */\n\nimport { Ticker } from './Ticker';\nimport { Animation } from './Animation';\nimport { Timeline } from './Timeline';\nimport { AnimationBuilder } from './AnimationBuilder';\nimport { AnimationPool } from '../memory/AnimationPool';\nimport { PropTweenPool } from '../memory/PropTweenPool';\nimport { parseProperty, type PropertyValue } from '../utils/PropertyParser';\nimport { setTransformValue, buildTransformString } from '../render/TransformCache';\nimport { SDKRegistry } from '../registries/SDKRegistry';\nimport { cleanupPathParser } from '../utils/PathParser';\nimport type { AnimationVars, StaggerVars, AnimationTarget } from '../types';\nimport type { InternalAnimationConfig } from './Animation';\n\nexport class Engine {\n private static instance: Engine;\n\n // Animation registry - array for fast iteration, map for O(1) ID lookup\n private animations: Animation[] = [];\n private animationIdToIndex: Map<number, number> = new Map();\n private nextAnimationId: number = 1;\n private totalCreated: number = 0;\n\n // Pre-allocated snapshot buffer for safe mid-iteration mutation handling\n private _animationSnapshot: Animation[] = [];\n\n // Timeline registry (named timelines)\n private timelines: Map<string, Timeline> = new Map();\n\n // Ticker reference\n private ticker: Ticker;\n\n // Pools\n private animationPool: AnimationPool;\n private propTweenPool: PropTweenPool;\n\n private constructor() {\n this.ticker = Ticker.getInstance();\n this.animationPool = AnimationPool;\n this.propTweenPool = PropTweenPool;\n\n // Register ticker callback\n this.ticker.add(this.update, 0);\n }\n\n /**\n * Get the singleton Engine instance\n */\n static getInstance(): Engine {\n if (!Engine.instance) {\n Engine.instance = new Engine();\n // Set up Timeline registration callback (breaks circular dependency)\n Timeline.setEngineRegisterCallback((name: string, timeline: Timeline) => {\n const engine = Engine.instance!;\n if (!engine.hasTimeline(name)) {\n engine.registerTimeline(name, timeline);\n }\n });\n // Set up AnimationBuilder callback to break circular dependency\n AnimationBuilder.setCreateAnimationCallback(\n (targets, vars, duration, delay, ease, isFrom, fromVars, config) => {\n return Engine.instance!.createAnimation(targets, vars, duration, delay, ease, isFrom, fromVars, config);\n }\n );\n }\n return Engine.instance;\n }\n\n /**\n * Main update loop called by Ticker\n */\n private update = (deltaTime: number): void => {\n // Snapshot the live array before iteration so that onComplete callbacks\n // which call kill() on other animations (triggering swap-and-pop on\n // this.animations) cannot corrupt the current loop.\n const snapshot = this._animationSnapshot;\n const liveLen = this.animations.length;\n snapshot.length = liveLen;\n for (let i = 0; i < liveLen; i++) {\n snapshot[i] = this.animations[i];\n }\n\n const toRemove: number[] = [];\n\n for (let i = 0, len = snapshot.length; i < len; i++) {\n const animation = snapshot[i];\n\n // Skip animations that are children of a Timeline (timeline controls them)\n if (animation.isTimelineChild()) continue;\n\n if (animation.isActive()) {\n animation.update(deltaTime);\n\n // Check if animation completed and should be pooled\n // Timeline children are NOT auto-pooled - they persist until timeline.kill()\n if (!animation.isActive() && !animation.isTimelineChild()) {\n toRemove.push(animation.getId());\n }\n }\n }\n\n // Remove completed animations (return to pool for performance)\n for (const id of toRemove) {\n this.unregisterAnimation(id);\n }\n\n // Update all active timelines\n for (const timeline of this.timelines.values()) {\n if (timeline.isActive()) {\n timeline.update(deltaTime);\n }\n }\n };\n\n /**\n * Create a new animation (called by AnimationBuilder)\n * Returns either a single Animation or an array of Animations (for stagger)\n */\n createAnimation(\n targets: AnimationTarget[],\n vars: AnimationVars,\n duration: number,\n delay: number,\n ease: string,\n isFrom: boolean,\n fromVars?: AnimationVars,\n config?: InternalAnimationConfig\n ): Animation | Animation[] {\n // Check if stagger is specified (from config, not vars)\n const staggerConfig = config?.stagger;\n\n if (staggerConfig && targets.length > 1) {\n // Create staggered animations\n return this.createStaggeredAnimations(\n targets,\n vars,\n duration,\n delay,\n ease,\n isFrom,\n fromVars,\n staggerConfig,\n config\n );\n }\n\n // Create single animation for all targets\n // Acquire animation from pool\n const animation = this.animationPool.acquire();\n\n // Generate ID and register\n const id = this.registerAnimation(animation);\n\n // Initialize animation with config\n animation.init(id, targets, duration, delay, ease, config);\n\n // Parse properties and create PropTweens for each target\n this.setupPropertyTweens(animation, targets, vars, isFrom, fromVars);\n\n // If duration is 0, immediately render at final values (instant set)\n if (duration === 0) {\n animation.render(1);\n } else if (isFrom && !config?._skipInitialRender) {\n // If this is a \"from\" animation (and not instant), immediately render starting values.\n // Timeline children skip this — Timeline captures initial values first, then renders FROM.\n animation.render(0);\n }\n\n return animation;\n }\n\n /**\n * Create staggered animations - one animation per target\n */\n private createStaggeredAnimations(\n targets: AnimationTarget[],\n vars: AnimationVars,\n duration: number,\n baseDelay: number,\n ease: string,\n isFrom: boolean,\n fromVars: AnimationVars | undefined,\n staggerConfig: number | StaggerVars,\n config?: InternalAnimationConfig\n ): Animation[] {\n // Calculate stagger delays (gracefully handle if StaggerResolver not loaded)\n if (!SDKRegistry.stagger) {\n // StaggerResolver not loaded - create animations without stagger\n const animation = this.animationPool.acquire();\n const id = this.registerAnimation(animation);\n animation.init(id, targets, duration, baseDelay, ease, config);\n this.setupPropertyTweens(animation, targets, vars, isFrom, fromVars);\n if (duration === 0) {\n animation.render(1);\n } else if (isFrom && !config?._skipInitialRender) {\n animation.render(0);\n }\n return [animation];\n }\n const staggerDelays = SDKRegistry.stagger.resolve(targets, staggerConfig);\n\n // Create one animation per target\n const animations: Animation[] = [];\n\n for (let i = 0; i < targets.length; i++) {\n const target = targets[i];\n const staggerDelay = staggerDelays[i];\n\n // Acquire animation from pool\n const animation = this.animationPool.acquire();\n\n // Generate ID and register\n const id = this.registerAnimation(animation);\n\n // Initialize animation with stagger delay added to base delay\n animation.init(id, [target], duration, baseDelay + staggerDelay, ease, config);\n\n // Parse properties and create PropTweens for this single target\n this.setupPropertyTweens(animation, [target], vars, isFrom, fromVars);\n\n // If duration is 0, immediately render at final values (instant set)\n if (duration === 0) {\n animation.render(1);\n } else if (isFrom && !config?._skipInitialRender) {\n // Timeline children skip this — Timeline captures initial values first, then renders FROM.\n animation.render(0);\n }\n\n animations.push(animation);\n }\n\n return animations;\n }\n\n /**\n * Set up property tweens for an animation\n */\n private setupPropertyTweens(\n animation: Animation,\n targets: AnimationTarget[],\n vars: AnimationVars,\n isFrom: boolean,\n fromVars?: AnimationVars\n ): void {\n // Collect all properties from both vars (to) and fromVars (from)\n // This ensures properties only in 'from' are also animated\n const toKeys = Object.keys(vars);\n const fromKeys = fromVars ? Object.keys(fromVars) : [];\n const propsToAnimate = [...new Set([...toKeys, ...fromKeys])];\n\n // For each target element\n // Cast vars to allow dynamic property access (we know keys are valid from Object.keys)\n const varsRecord = vars as Record<string, PropertyValue>;\n const fromVarsRecord = fromVars as Record<string, PropertyValue> | undefined;\n\n // Check if perspective should be animated (both from and to have explicit values)\n // or set as immediate context (only one side specified)\n const hasPerspectiveInTo = 'perspective' in varsRecord;\n const hasPerspectiveInFrom = fromVarsRecord && 'perspective' in fromVarsRecord;\n const animatePerspective = hasPerspectiveInTo && hasPerspectiveInFrom;\n\n for (const target of targets) {\n // Handle perspective as immediate 3D context when NOT animating it\n // (i.e., when only one side is specified, not both from and to)\n // This prevents interpolating from 0 which causes extreme visual distortion\n if (!animatePerspective && (hasPerspectiveInTo || hasPerspectiveInFrom) && target instanceof Element) {\n // Use 'to' value if available, otherwise 'from' value\n const rawValue = hasPerspectiveInTo ? varsRecord.perspective : fromVarsRecord!.perspective;\n const perspectiveValue = typeof rawValue === 'number'\n ? rawValue\n : parseFloat(String(rawValue)) || 0;\n\n if (perspectiveValue > 0) {\n setTransformValue(target, 'perspective', perspectiveValue, 'px');\n // Apply immediately to element\n (target as HTMLElement).style.transform = buildTransformString(target);\n }\n }\n\n // For each property\n for (const prop of propsToAnimate) {\n // Skip perspective if it's being set as immediate context (not animated)\n if (prop === 'perspective' && !animatePerspective) continue;\n\n const targetValue = varsRecord[prop];\n\n // Parse the property\n const parsed = parseProperty(\n target,\n prop,\n targetValue,\n fromVarsRecord ? fromVarsRecord[prop] : undefined\n );\n\n // Create PropTween from pool\n const propTween = this.propTweenPool.acquire();\n const swapForFrom = isFrom && !fromVars;\n\n switch (parsed.type) {\n case 'color': {\n const startColor = swapForFrom ? parsed.endColor : parsed.startColor;\n const endColor = swapForFrom ? parsed.startColor : parsed.endColor;\n propTween.initColor(target, prop, startColor, endColor);\n break;\n }\n case 'filter': {\n const startFilters = swapForFrom ? parsed.endFilters : parsed.startFilters;\n const endFilters = swapForFrom ? parsed.startFilters : parsed.endFilters;\n propTween.initFilter(target, prop, startFilters, endFilters);\n break;\n }\n case 'scalar': {\n const startValue = swapForFrom ? parsed.endValue : parsed.startValue;\n const endValue = swapForFrom ? parsed.startValue : parsed.endValue;\n propTween.init(target, prop, startValue, endValue, parsed.unit);\n break;\n }\n case 'drawSVG': {\n const startDraw = swapForFrom ? parsed.endDraw : parsed.startDraw;\n const endDraw = swapForFrom ? parsed.startDraw : parsed.endDraw;\n propTween.initDrawSVG(target, prop, startDraw, endDraw, parsed.length);\n break;\n }\n case 'path': {\n const startProgress = swapForFrom ? parsed.endProgress : parsed.startProgress;\n const endProgress = swapForFrom ? parsed.startProgress : parsed.endProgress;\n propTween.initPath(\n target, prop, parsed.pathData, parsed.pathLength,\n startProgress, endProgress, parsed.rotate, parsed.alignOffset, parsed.pathOffset\n );\n break;\n }\n }\n\n // Add to animation's linked list\n animation.addPropTween(propTween);\n }\n }\n }\n\n /**\n * Register an animation with the engine\n */\n registerAnimation(animation: Animation): number {\n const id = this.nextAnimationId++;\n const index = this.animations.length;\n\n this.animations.push(animation);\n this.animationIdToIndex.set(id, index);\n this.totalCreated++;\n\n // Provide unregister callback to avoid circular dependency\n animation.setUnregisterCallback(() => this.unregisterAnimation(id));\n\n return id;\n }\n\n /**\n * Unregister an animation from the engine\n * Uses swap-and-pop for O(1) removal\n */\n unregisterAnimation(id: number): void {\n const index = this.animationIdToIndex.get(id);\n if (index === undefined) return;\n\n const animation = this.animations[index];\n const lastIndex = this.animations.length - 1;\n\n // Swap with last element if not already last\n if (index !== lastIndex) {\n const lastAnimation = this.animations[lastIndex];\n this.animations[index] = lastAnimation;\n // Update index of moved animation\n this.animationIdToIndex.set(lastAnimation.getId(), index);\n }\n\n // Pop the last element\n this.animations.pop();\n this.animationIdToIndex.delete(id);\n\n // Return animation to pool\n this.animationPool.release(animation);\n }\n\n /**\n * Get an animation by ID\n */\n getAnimation(id: number): Animation | undefined {\n const index = this.animationIdToIndex.get(id);\n return index !== undefined ? this.animations[index] : undefined;\n }\n\n /**\n * Kill all animations\n */\n killAll(): void {\n // Copy array since kill() triggers unregister callbacks that modify it\n const animationsToKill = [...this.animations];\n for (let i = 0, len = animationsToKill.length; i < len; i++) {\n animationsToKill[i].kill();\n }\n // Ensure clean state\n this.animations.length = 0;\n this.animationIdToIndex.clear();\n }\n\n /**\n * Kill all animations targeting specific elements\n * Used by Motion.reset() to stop animations before clearing styles\n */\n killAnimationsForTargets(targets: Element[]): void {\n if (targets.length === 0) return;\n\n const targetSet = new Set(targets);\n const animationsToKill: Animation[] = [];\n\n // Find animations that target any of the specified elements\n for (let i = 0, len = this.animations.length; i < len; i++) {\n const animation = this.animations[i];\n const animTargets = animation.getTargets();\n\n for (const target of animTargets) {\n if (target instanceof Element && targetSet.has(target)) {\n animationsToKill.push(animation);\n break; // Found a match, no need to check other targets\n }\n }\n }\n\n // Kill matching animations\n for (const animation of animationsToKill) {\n animation.kill();\n }\n }\n\n /**\n * Get active animation count\n */\n getActiveCount(): number {\n let count = 0;\n for (let i = 0, len = this.animations.length; i < len; i++) {\n if (this.animations[i].isActive()) {\n count++;\n }\n }\n return count;\n }\n\n /**\n * Get total animations created\n */\n getTotalCreated(): number {\n return this.totalCreated;\n }\n\n /**\n * Get or create a named timeline\n */\n getTimeline(name: string): Timeline {\n let timeline = this.timelines.get(name);\n if (!timeline) {\n timeline = new Timeline(name);\n // Constructor may have already registered via callback, but ensure it's set\n if (!this.timelines.has(name)) {\n this.timelines.set(name, timeline);\n timeline.setUnregisterCallback(() => this.removeTimeline(name));\n }\n }\n return timeline;\n }\n\n /**\n * Check if timeline exists\n */\n hasTimeline(name: string): boolean {\n return this.timelines.has(name);\n }\n\n /**\n * Register a timeline\n */\n registerTimeline(name: string, timeline: Timeline): void {\n this.timelines.set(name, timeline);\n // Set cleanup callback so Timeline can unregister without importing Engine\n timeline.setUnregisterCallback(() => this.removeTimeline(name));\n }\n\n /**\n * Get timeline count\n */\n getTimelineCount(): number {\n return this.timelines.size;\n }\n\n /**\n * Get all timeline names\n */\n getTimelineNames(): string[] {\n return Array.from(this.timelines.keys());\n }\n\n /**\n * Remove a timeline from registry (called after kill)\n */\n removeTimeline(name: string): void {\n this.timelines.delete(name);\n }\n\n /**\n * Kill all timelines and their triggers\n */\n killAllTimelines(): void {\n for (const timeline of this.timelines.values()) {\n timeline.kill();\n }\n this.timelines.clear();\n }\n\n /**\n * Kill everything - all animations, all timelines, and cleanup resources\n */\n killEverything(): void {\n this.killAllTimelines();\n this.killAll();\n // Cleanup path parser resources (hidden SVG element, caches)\n cleanupPathParser();\n }\n\n /**\n * Get animation pool\n */\n getAnimationPool(): AnimationPool {\n return this.animationPool;\n }\n\n /**\n * Get PropTween pool\n */\n getPropTweenPool(): PropTweenPool {\n return this.propTweenPool;\n }\n\n /**\n * Get pool statistics\n */\n getPoolStats(): { animations: number; propTweens: number } {\n return {\n animations: this.animationPool.getPoolSize(),\n propTweens: this.propTweenPool.getPoolSize(),\n };\n }\n\n /**\n * Clear all pools (for debugging)\n */\n clearPools(): void {\n this.animationPool.clear();\n this.propTweenPool.clear();\n }\n\n /**\n * Get memory estimate in bytes\n */\n getMemoryEstimate(): number {\n // Rough estimate: each animation ~500 bytes, each PropTween ~100 bytes\n const activeAnimations = this.animations.length;\n const pooledAnimations = this.animationPool.getPoolSize();\n const pooledPropTweens = this.propTweenPool.getPoolSize();\n\n return (activeAnimations + pooledAnimations) * 500 + pooledPropTweens * 100;\n }\n\n /**\n * Get ticker reference\n */\n getTicker(): Ticker {\n return this.ticker;\n }\n}\n",
|
|
46
|
+
"/**\n * Motion.utils — GSAP-compatible utility functions\n *\n * Drop-in replacements for common gsap.utils.* helpers.\n * Enables migration from GSAP custom code to the Motion SDK.\n *\n * @example Migration from GSAP\n * // Before: gsap.utils.toArray(\".section\").length\n * // After: Motion.utils.toArray(\".section\").length\n */\n\n/**\n * Convert a selector, NodeList, or value into a flat Array of Elements.\n * Drop-in replacement for gsap.utils.toArray().\n *\n * @param target - CSS selector string, NodeList, HTMLCollection, Element, or array\n * @param scope - Optional parent element to scope selector queries\n * @returns Flat array of matched elements\n *\n * @example\n * Motion.utils.toArray(\".h-scroll-section\") // all matching elements\n * Motion.utils.toArray(\".item\", containerEl) // scoped to container\n * Motion.utils.toArray(document.querySelectorAll(\"p\")) // NodeList → Array\n */\nexport function toArray<T extends Element = Element>(\n target: string | NodeList | HTMLCollection | Element | T[] | ArrayLike<T>,\n scope?: Element | Document\n): T[] {\n if (typeof target === 'string') {\n const root = scope || (typeof document !== 'undefined' ? document : null);\n if (!root) return [];\n try {\n return Array.from(root.querySelectorAll(target)) as T[];\n } catch {\n return [];\n }\n }\n\n if (typeof Element !== 'undefined' && target instanceof Element) {\n return [target as unknown as T];\n }\n\n if (\n (typeof NodeList !== 'undefined' && target instanceof NodeList) ||\n (typeof HTMLCollection !== 'undefined' && target instanceof HTMLCollection)\n ) {\n return Array.from(target) as T[];\n }\n\n if (Array.isArray(target)) {\n return target;\n }\n\n // ArrayLike (has .length)\n if (target && typeof target === 'object' && 'length' in target) {\n return Array.from(target) as T[];\n }\n\n return [];\n}\n\n/**\n * Clamp a value between min and max.\n * Drop-in replacement for gsap.utils.clamp().\n *\n * Can be called with 3 args or curried with 2:\n * - `clamp(0, 100, 150)` → `100`\n * - `const c = clamp(0, 1); c(1.5)` → `1`\n *\n * @param min - Minimum allowed value\n * @param max - Maximum allowed value\n * @param value - Value to clamp (optional — returns curried function if omitted)\n */\nexport function clamp(min: number, max: number, value: number): number;\nexport function clamp(min: number, max: number): (value: number) => number;\nexport function clamp(min: number, max: number, value?: number): number | ((value: number) => number) {\n if (value === undefined) {\n return (v: number) => Math.min(max, Math.max(min, v));\n }\n return Math.min(max, Math.max(min, value));\n}\n\n/**\n * Generate a random number between min and max (inclusive).\n * Drop-in replacement for gsap.utils.random().\n *\n * @param min - Minimum value\n * @param max - Maximum value\n * @param snapIncrement - Optional increment to snap to (e.g., 5 → multiples of 5)\n * @returns Random number in the given range\n *\n * @example\n * Motion.utils.random(0, 100) // 0–100 (float)\n * Motion.utils.random(0, 100, 10) // 0, 10, 20, ... 100\n * Motion.utils.random(-1, 1) // -1 to 1\n */\nexport function random(min: number, max: number, snapIncrement?: number): number {\n const raw = min + Math.random() * (max - min);\n if (snapIncrement && snapIncrement > 0) {\n return Math.round(raw / snapIncrement) * snapIncrement;\n }\n return raw;\n}\n\n/**\n * Snap a number to the nearest increment.\n * Drop-in replacement for gsap.utils.snap().\n *\n * @param snapTo - Increment value or array of values to snap to\n * @param value - Value to snap (optional — returns curried function if omitted)\n *\n * @example\n * Motion.utils.snap(5, 13) // 15\n * Motion.utils.snap([0, 25, 50, 100], 30) // 25\n * const s = Motion.utils.snap(10); s(27) // 30\n */\nexport function snap(snapTo: number | number[], value: number): number;\nexport function snap(snapTo: number | number[]): (value: number) => number;\nexport function snap(snapTo: number | number[], value?: number): number | ((value: number) => number) {\n const snapFn = (v: number): number => {\n if (typeof snapTo === 'number') {\n if (snapTo <= 0) return v;\n return Math.round(v / snapTo) * snapTo;\n }\n\n // Array: find nearest value\n const sorted = snapTo;\n let closest = sorted[0];\n let minDist = Math.abs(v - closest);\n for (let i = 1; i < sorted.length; i++) {\n const dist = Math.abs(v - sorted[i]);\n if (dist < minDist) {\n minDist = dist;\n closest = sorted[i];\n }\n }\n return closest;\n };\n\n if (value === undefined) {\n return snapFn;\n }\n return snapFn(value);\n}\n\n/**\n * Linear interpolation between two values.\n * Drop-in replacement for gsap.utils.interpolate() (simple 2-value case).\n *\n * @param start - Start value\n * @param end - End value\n * @param progress - Interpolation factor (0–1)\n * @returns Interpolated value\n *\n * @example\n * Motion.utils.interpolate(0, 100, 0.5) // 50\n */\nexport function interpolate(start: number, end: number, progress: number): number {\n return start + (end - start) * progress;\n}\n\n/**\n * Map a value from one range to another.\n * Drop-in replacement for gsap.utils.mapRange().\n *\n * @param inMin - Input range minimum\n * @param inMax - Input range maximum\n * @param outMin - Output range minimum\n * @param outMax - Output range maximum\n * @param value - Value to map (optional — returns curried function if omitted)\n *\n * @example\n * Motion.utils.mapRange(0, 100, 0, 1, 50) // 0.5\n * const map = Motion.utils.mapRange(0, 1, -100, 100); map(0.75) // 50\n */\nexport function mapRange(inMin: number, inMax: number, outMin: number, outMax: number, value: number): number;\nexport function mapRange(inMin: number, inMax: number, outMin: number, outMax: number): (value: number) => number;\nexport function mapRange(\n inMin: number, inMax: number, outMin: number, outMax: number, value?: number\n): number | ((value: number) => number) {\n const mapFn = (v: number): number => {\n const ratio = (v - inMin) / (inMax - inMin);\n return outMin + ratio * (outMax - outMin);\n };\n\n if (value === undefined) {\n return mapFn;\n }\n return mapFn(value);\n}\n\n/**\n * Normalize a value from a range to 0–1.\n * Drop-in replacement for gsap.utils.normalize().\n *\n * @param min - Range minimum\n * @param max - Range maximum\n * @param value - Value to normalize (optional — returns curried function if omitted)\n *\n * @example\n * Motion.utils.normalize(0, 100, 25) // 0.25\n */\nexport function normalize(min: number, max: number, value: number): number;\nexport function normalize(min: number, max: number): (value: number) => number;\nexport function normalize(min: number, max: number, value?: number): number | ((value: number) => number) {\n const normFn = (v: number): number => (v - min) / (max - min);\n\n if (value === undefined) {\n return normFn;\n }\n return normFn(value);\n}\n\n/**\n * Wrap a value within a range (modular arithmetic).\n * Drop-in replacement for gsap.utils.wrap().\n *\n * @param min - Range minimum\n * @param max - Range maximum\n * @param value - Value to wrap (optional — returns curried function if omitted)\n *\n * @example\n * Motion.utils.wrap(0, 100, 150) // 50\n * Motion.utils.wrap(0, 360, -90) // 270\n */\nexport function wrap(min: number, max: number, value: number): number;\nexport function wrap(min: number, max: number): (value: number) => number;\nexport function wrap(min: number, max: number, value?: number): number | ((value: number) => number) {\n const range = max - min;\n const wrapFn = (v: number): number => {\n const r = ((v - min) % range + range) % range;\n return r + min;\n };\n\n if (value === undefined) {\n return wrapFn;\n }\n return wrapFn(value);\n}\n\n/**\n * The complete utils namespace, attached to Motion.utils\n */\nexport const MotionUtils = {\n toArray,\n clamp,\n random,\n snap,\n interpolate,\n mapRange,\n normalize,\n wrap,\n} as const;\n",
|
|
47
|
+
"/**\n * Motion API - Unified entry point for animations\n *\n * All animations are named timelines. Config-based API for consistency.\n *\n * @example Single animation\n * Motion('fadeIn', '.box', {\n * from: { opacity: 0 },\n * to: { opacity: 1 },\n * duration: 0.5\n * }).play()\n *\n * @example Multi-animation timeline\n * Motion('hero', [\n * { target: '.title', from: { y: 50 }, to: { y: 0 }, duration: 1 },\n * { target: '.subtitle', from: { opacity: 0 }, to: { opacity: 1 }, position: 0.5 }\n * ]).play()\n *\n * @example With trigger\n * Motion('scroll', '.box', { from: { x: -100 }, to: { x: 0 } })\n * .onScroll({ scrub: true })\n *\n * @example Replay\n * Motion('fadeIn').play()\n */\n\nimport type { TargetInput, AnimationConfig, AnimationEntry, AnimationVars } from '../types';\nimport { Timeline } from './Timeline';\nimport { Engine } from './Engine';\nimport { resolveTargets } from '../utils/TargetResolver';\nimport { clearTransformCache } from '../render/TransformCache';\nimport { SDKRegistry } from '../registries/SDKRegistry';\nimport { MotionUtils } from '../utils/MotionUtils';\n\n/**\n * Motion function overloads\n */\nfunction MotionFunction(name: string): Timeline;\nfunction MotionFunction(name: string, target: TargetInput, config: AnimationConfig): Timeline;\nfunction MotionFunction(name: string, animations: AnimationEntry[]): Timeline;\nfunction MotionFunction(\n name: string,\n targetOrAnimations?: TargetInput | AnimationEntry[],\n config?: AnimationConfig\n): Timeline {\n const engine = Engine.getInstance();\n const timeline = engine.getTimeline(name);\n\n // Warn when NEW entries are being added to an existing timeline that already has animations.\n // Calling Motion('name') with no second argument to retrieve a timeline should NOT warn.\n if (targetOrAnimations !== undefined && timeline.duration() > 0) {\n console.warn(\n `[Motion] Timeline \"${name}\" already has animations. ` +\n `New entries will be appended. Call Motion(\"${name}\").kill() first to rebuild from scratch.`\n );\n }\n\n // Check if second arg is an array of AnimationEntry objects\n // AnimationEntry has 'target' property, TargetInput arrays don't have objects with 'target'\n if (\n Array.isArray(targetOrAnimations) &&\n targetOrAnimations.length > 0 &&\n typeof targetOrAnimations[0] === 'object' &&\n targetOrAnimations[0] !== null &&\n 'target' in targetOrAnimations[0]\n ) {\n // Multi-animation: Motion('name', [{target, from, to}, ...])\n for (const entry of targetOrAnimations as AnimationEntry[]) {\n timeline._addEntry(entry.target, entry, entry.position);\n }\n } else if (targetOrAnimations !== undefined && config !== undefined) {\n // Single animation: Motion('name', '.target', {from, to})\n timeline._addEntry(targetOrAnimations as TargetInput, config);\n }\n // else: Just retrieve timeline: Motion('name')\n\n return timeline;\n}\n\n/**\n * Reset element(s) to their original state\n * Kills any active animations, clears transform cache and inline styles.\n */\nMotionFunction.reset = function(targets: TargetInput): void {\n Engine.getInstance(); // Ensure engine is initialized before any target resolution\n const resolved = resolveTargets(targets);\n\n const elementTargets: Element[] = [];\n for (const target of resolved) {\n if (target instanceof Element) {\n elementTargets.push(target);\n }\n }\n\n if (elementTargets.length > 0) {\n Engine.getInstance().killAnimationsForTargets(elementTargets);\n }\n\n for (const target of resolved) {\n if (!(target instanceof Element)) continue;\n\n if (SDKRegistry.textSplitter?.isSplit?.(target)) {\n SDKRegistry.textSplitter?.revert?.(target);\n }\n\n clearTransformCache(target);\n SDKRegistry.styleReset?.clearAnimationStylesAndUnregister?.(target);\n SDKRegistry.styleReset?.clearPinStylesAndUnregister?.(target);\n }\n};\n\n/**\n * Immediately set CSS properties on target(s) without animation.\n * Equivalent to GSAP's gsap.set() — useful for initializing element state.\n *\n * Values are applied synchronously via a zero-duration animation.\n * The internal timeline is discarded immediately after rendering (clearProps=false\n * so the transform cache remains in sync with the applied inline styles).\n *\n * @example\n * Motion.set('.box', { opacity: 0, x: -100 })\n */\nMotionFunction.set = function(target: TargetInput, vars: AnimationVars): void {\n Engine.getInstance(); // Ensure engine is initialized (sets AnimationBuilder callback)\n const tl = new Timeline();\n tl._addEntry(target, { to: vars, duration: 0 });\n // kill(false): skip prop restoration so applied values persist on the DOM\n // and the transform cache stays consistent with the new inline styles.\n tl.kill(false);\n};\n\n/**\n * Get an existing timeline by name (returns undefined if not found)\n */\nMotionFunction.get = function(name: string): Timeline | undefined {\n const engine = Engine.getInstance();\n return engine.hasTimeline(name) ? engine.getTimeline(name) : undefined;\n};\n\n/**\n * Check if a timeline exists\n */\nMotionFunction.has = function(name: string): boolean {\n return Engine.getInstance().hasTimeline(name);\n};\n\n/**\n * Get names of all registered timelines\n */\nMotionFunction.getNames = function(): string[] {\n return Engine.getInstance().getTimelineNames();\n};\n\n/**\n * Kill a timeline by name\n */\nMotionFunction.kill = function(name: string): void {\n const engine = Engine.getInstance();\n if (engine.hasTimeline(name)) {\n engine.getTimeline(name).kill();\n }\n};\n\n/**\n * Kill all timelines and animations\n */\nMotionFunction.killAll = function(): void {\n Engine.getInstance().killEverything();\n};\n\n/**\n * Refresh all scroll triggers\n */\nMotionFunction.refreshScrollTriggers = function(): void {\n SDKRegistry.triggerManager?.getInstance?.()?.refreshScrollTriggers();\n};\n\n/**\n * Clean up ScrollTrigger DOM artifacts\n */\nMotionFunction.cleanup = function(): void {\n if (typeof document === 'undefined') return;\n document.querySelectorAll('[data-scrolltrigger-spacer]').forEach(el => el.remove());\n document.querySelectorAll('[data-scrolltrigger-markers]').forEach(el => el.remove());\n};\n\n/**\n * Utility functions — drop-in replacements for gsap.utils.*\n *\n * @example Migration from GSAP\n * // Before: snap: 1 / (gsap.utils.toArray(\".section\").length - 1)\n * // After: snap: 1 / (Motion.utils.toArray(\".section\").length - 1)\n */\nMotionFunction.utils = MotionUtils;\n\nexport const Motion = MotionFunction;\n"
|
|
48
|
+
],
|
|
49
|
+
"mappings": "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",
|
|
50
|
+
"debugId": "C7E35D5C2172A20964756E2164756E21",
|
|
51
|
+
"names": []
|
|
52
|
+
}
|