/** * Timeline for sequencing animations * * Features: * - Named timeline registry (create/retrieve by name) * - Position parameter support (absolute, +=, -=, <, >) * - Nested timelines * - Callback scheduling * - Control methods (play, pause, reverse, seek, etc.) * - Trigger system integration */ import type { HoverConfig, ClickConfig, ScrollConfig, MouseMoveConfig, GestureConfig, CursorConfig, PageExitConfig, TargetInput, AnimationConfig, TimelineConfig, RepeatConfig } from '../types'; import { Animation } from './Animation'; import { AnimationBuilder } from './AnimationBuilder'; export declare class Timeline { /** Monotonic counter for generating unique each-mode instance names */ private static _eachCounter; /** * Static callback for registering named timelines with Engine. * Set by Engine.getInstance() to break circular dependency. * @internal */ private static _registerWithEngine; private static _activateEngine; /** * Set the engine registration callback. Called once by Engine during initialization. * @internal */ static setEngineRegisterCallback(callback: (name: string, timeline: Timeline) => void): void; /** * Set the engine activation callback. Called once by Engine during initialization. * @internal */ static setEngineActivationCallback(callback: () => void): void; private _name?; private _children; private _initialRenderStartTimes; private _duration; private _time; private _progress; private _timeScale; private _isActive; private _isReversed; private _killed; private _atZeroState; private _repeat; private _repeatDelay; private _yoyo; private _currentRepeat; private _completeFired; private _inRepeatDelay; private _repeatDelayCounter; private _onStart?; private _onUpdate?; private _onComplete?; private _onRepeat?; private _startFired; /** * Reference to attached trigger (for cleanup in kill()). * @internal */ _trigger?: { disable(): void; }; /** @internal */ private _unregisterCallback?; /** * Set unregister callback for cleanup. Called by Engine after registration. * @internal */ setUnregisterCallback(callback: () => void): void; private _notifyActivated; private _previousStart; private _previousEnd; private _eachInstances?; private _eachDuration?; private _initialValues; private _initialTransforms; private _transformElements; private _savedTransitions; private _transitionsDisabled; private _splitParentOverrides; constructor(name?: string, config?: TimelineConfig); /** * Get timeline duration (single cycle) */ duration(): number; /** * Get total duration including all repeats. * Returns Infinity when repeat is -1 (infinite loop). */ totalDuration(): number; /** * Clear timeline state for rebuild (used for re-triggerable animations) * Kills existing animations, clears children, resets state */ clear(): this; /** * Capture initial values for an animation's properties (called once per property per element) */ private _captureInitialValues; /** * Restore all elements to their initial property values (used when seeking to position 0) */ private _restoreInitialValues; /** * Disable CSS transitions on all animated elements. * Called when the timeline starts playing to prevent CSS transitions * from fighting frame-by-frame SDK rendering. */ private _disableTransitions; /** * Restore original CSS transition values on all animated elements. * Called on timeline completion and kill to re-enable CSS hover/focus * transitions after the SDK animation is done. */ private _restoreTransitions; /** * Clear all saved transition data (for kill/clear). */ private _clearTransitionData; /** * Internal: Add animation entry (used by Motion function) * @internal */ _addEntry(target: TargetInput, config: AnimationConfig, position?: string | number): this; /** * Internal method to add an AnimationBuilder directly */ private _addBuilder; private _getInitialRenderStartTimes; private _renderAnimationInitialBoundary; private _renderInitialTimelineState; /** * Call function at position */ call(callback: (...args: unknown[]) => void, params?: unknown[], position?: string | number): this; /** * Get the first animation's target element for trigger inference. * When text splitting is active, returns the original pre-split container * element instead of the split char/word fragments, so the scroll trigger * is anchored to the full element rather than a tiny character span. * Note: Returns only DOM Elements, not plain objects */ private _getFirstAnimationTarget; /** * Get all target elements from the first animation (for each mode) * Note: Returns only DOM Elements, not plain objects * * When the builder uses text splitting (split: 'words' etc.), the animation's * targets are the split fragments — but for each-mode we need the ORIGINAL * pre-split elements so each gets its own ScrollTrigger. The cloned builder * will re-split inside that element independently. */ private _getAllAnimationTargets; /** * Create a timeline instance for a single element (for each mode) * This clones the timeline structure but only targets the specific element. */ private _createInstanceForElement; /** * Create a timeline instance for multiple elements (for each mode with explicit container target). * This is a sibling of _createInstanceForElement — the difference is that it accepts an * Element[] of scoped targets (animation targets belonging to one container). * * Per-builder filtering: each cloned builder ONLY animates the subset of `elements` * that the original builder targeted. Without this, a builder for `.subtitle` would * also animate `.eyebrow` and `.section-title` elements that happen to share the * same scoped container — producing visually broken animations. */ private _createInstanceForElements; /** * Find animation targets that "belong" to a container in each-mode. * * Strategy 1 (preferred): the container itself (when it's also an animation * target) and/or its descendants. Covers the most common Builder workflow * where the trigger target IS the animation target (e.g. * `.onScroll({ target: '.fade-up', each: true })` animating `.fade-up`), * as well as parent-child setups (e.g. `.section-header` containing * `.eyebrow`/`.subtitle`). * Strategy 2 (sibling fallback): anim targets sharing the container's parent * that aren't owned by another container. * * Returns [] when neither strategy yields targets — the caller skips this * container, dropping the orphaned anim targets entirely. This matches the * documented behaviour: when each:true + explicit target is set, only anim * targets associated with a container (self, descendant, or sibling) are * animated. */ private _findScopedTargetsForContainer; /** * Resolve a CSS selector string or Element into an array of DOM elements. */ private _resolveTargetElements; /** * Generic helper for each-mode setup: resolve targets, create instances, register triggers. * The setupTrigger callback receives each (instance, element) pair for trigger-specific registration. */ private _setupEachModeGeneric; /** * Set up each-mode: create independent timeline instances for each target element */ private _setupEachMode; /** * Trigger timeline on hover (mouseenter plays, mouseleave configurable) */ onHover(config?: HoverConfig): this; /** * Trigger timeline on click */ onClick(config?: ClickConfig): this; /** * Trigger timeline on scroll */ onScroll(config?: ScrollConfig): this; /** * Trigger timeline on mouse movement * Drives animation progress based on mouse position (axis mode) or distance from center (distance mode) */ onMouseMove(config?: MouseMoveConfig): this; /** * Set up each-mode for mouse movement: create independent timeline instances for each target element */ private _setupMouseMoveEachMode; /** * Trigger timeline on page load. * * @param config - Optional configuration * @param config.timing - When to play: 'before' (immediately), 'during' (DOMContentLoaded), 'after' (window load). Defaults to 'during'. * @param config.paused - When true, the timeline is built but NOT played automatically. * Start it manually with `Motion('timelineName').play()`. */ onPageLoad(config?: { timing?: 'before' | 'during' | 'after'; paused?: boolean; }): this; /** * Trigger timeline when user navigates away from the page (clicks a link). * Intercepts the click, plays the exit animation, then navigates to the target URL. */ onPageExit(config?: PageExitConfig): this; /** * Trigger timeline based on user gestures (pointer, touch, wheel, scroll) * Maps directional callbacks to timeline actions */ onGesture(config: GestureConfig): this; /** * Set up each-mode for gesture: create independent instances for each target element */ private _setupGestureEachMode; /** * Attach cursor behavior to this timeline's target element * Creates a custom cursor that follows mouse with smooth tracking and state-based animations */ onCursor(config: CursorConfig): this; /** * Update a callback child: fire when time crosses its position */ private _updateCallbackChild; /** * Update an animation child: seek/activate/deactivate based on parent time */ private _updateAnimationChild; /** * Update a nested timeline child: seek/activate/deactivate based on parent time */ private _updateTimelineChild; /** * Update timeline by delta time */ update(deltaTime: number): void; /** * Play timeline from position. * If already playing forward, continues from current position. * No-op if already completed forward (at the end) and no `from` is specified — * prevents spurious onStart/onComplete re-firing during continuous gesture events. */ play(from?: number): this; /** * Pause timeline at time */ pause(atTime?: number): this; /** * Reverse timeline from position. * If already reversing, continues from current position. * No-op if already completed reverse (at the start) and no `from` is specified — * prevents spurious onStart/onComplete re-firing during continuous gesture events. */ reverse(from?: number): this; /** * Restart timeline */ restart(): this; /** * Reset all children for the start of a new repeat cycle. * Restores elements to their captured initial values and re-renders * position-0 animations at their FROM state. */ private _resetChildrenForCycle; /** * Render initial start values without letting later timeline segments * overwrite earlier segments for the same target/property. */ private _renderPositionZeroAnimations; /** * Seek to position */ seek(position: number): this; /** * Set progress for animations bound to a specific axis (for mouse movement trigger) * Only affects animations that have .axis() set to the specified axis. * @param axis - 'x' or 'y' * @param value - Progress value (0-1) */ setAxisProgress(axis: 'x' | 'y', value: number): this; /** * Set callback for when timeline starts playing */ onStart(callback: () => void): this; /** * Set callback for progress updates (called every frame while active) * @param callback - Receives (progress: 0-1, time: seconds) */ onUpdate(callback: (progress: number, time: number) => void): this; /** * Set callback for when timeline completes */ onComplete(callback: () => void): this; /** * Set callback for each repeat cycle completion * @param callback - Receives the current repeat count (starts at 1) */ onRepeat(callback: (repeatCount: number) => void): this; /** * Configure timeline-level repeat behavior (fluent). * Use -1 for infinite repeat, or a RepeatConfig for advanced options. * @example * Motion('loop', '.box', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 0.5 }) * .withRepeat(-1) * .onPageLoad() * * Motion('bounce', [entries]) * .withRepeat({ times: 3, yoyo: true, delay: 0.2 }) * .onPageLoad() */ withRepeat(config: number | RepeatConfig): this; /** * Get or set progress (0-1) */ progress(): number; progress(value: number): this; /** * Get or set time in seconds */ time(): number; time(value: number): this; /** * Get or set time scale (speed multiplier) */ timeScale(): number; timeScale(value: number): this; /** * Kill timeline and all children, reset all state * @param clearProps - If true (default), restore elements to their initial CSS values * * Any text splits owned by this timeline's builders are reverted via * SDKRegistry.textSplitter.revert() (reference-counted — safe when * other timelines still share the split element). */ kill(clearProps?: boolean): void; /** * Check if timeline is currently active. */ isActive(): boolean; /** * Check if timeline is currently playing in reverse direction */ reversed(): boolean; /** * Get timeline name */ getName(): string | undefined; /** * Get first child animation (for trigger target resolution). * @internal */ getFirstChildAnimation(): Animation | null; /** * Propagate a motion-path geometry invalidation to every descendant animation, * so aligned path offsets re-resolve against post-layout geometry on the next * render. Called from ScrollTrigger.refresh() (window.load / resize). */ invalidatePathGeometry(): void; /** * Get first child builder (for split-aware trigger target resolution). * When text splitting is active, the builder holds original pre-split targets. * @internal */ getFirstChildBuilder(): AnimationBuilder | null; }