UNPKG

@ssgoi/core

Version:

Core animation engine for SSGOI - Native app-like page transitions with spring physics

241 lines 8.36 kB
export type TransitionKey = string | symbol; export type SpringConfig = { stiffness?: number; damping?: number; }; /** * Schedule strategy for multi-spring animations * - overlap: All springs start immediately (parallel execution) * - wait: Each spring waits for previous to complete (sequential) * - chain: Springs start with offset delays */ export type ScheduleType = "overlap" | "wait" | "chain"; /** * Base configuration shared by both single and multi-spring transitions */ export type BaseTransitionConfig = { prepare?: (element: HTMLElement) => void; wait?: () => Promise<void>; onStart?: () => void; onEnd?: () => void; }; /** * Single spring configuration (existing - backward compatible) * Used for simple transitions with a single animation value */ export type SingleSpringConfig<TAnimationValue = number> = BaseTransitionConfig & { spring?: SpringConfig; from?: TAnimationValue; to?: TAnimationValue; tick?: (progress: TAnimationValue) => void; }; /** * Individual spring item in a multi-spring animation */ export type SpringItem<TAnimationValue = number> = { from?: TAnimationValue; to?: TAnimationValue; spring?: SpringConfig; /** * Frame callback - called on each animation frame with progress value * * **Performance Warning:** Use WRITE-ONLY operations for best performance. * Avoid reading layout properties inside tick to prevent layout thrashing. * * Layout-triggering properties (causes forced synchronous layout): * - element.offsetWidth, offsetHeight, offsetTop, offsetLeft * - element.clientWidth, clientHeight * - element.getBoundingClientRect() * - element.scrollWidth, scrollHeight, scrollTop, scrollLeft * - window.getComputedStyle(element) * * @example * // ❌ Bad: Layout read in tick (causes thrashing) * tick: (progress) => { * const height = element.offsetHeight // READ - forces layout! * element.style.height = height * progress * } * * @example * // ✅ Good: Read in prepare, write in tick * prepare: (element) => { * const height = element.offsetHeight // READ once before animation * element.dataset.height = height.toString() * }, * tick: (progress) => { * const height = parseFloat(element.dataset.height) * element.style.height = height * progress // WRITE only * } */ tick: (progress: TAnimationValue) => void; onStart?: () => void; onComplete?: () => void; /** * Delay offset in milliseconds before this spring starts * Only applies to 'chain' schedule mode */ offset?: number; }; /** * Multi-spring configuration (new feature) * Used for complex transitions with multiple coordinated animations */ export type MultiSpringConfig<TAnimationValue = number> = BaseTransitionConfig & { springs: SpringItem<TAnimationValue>[]; schedule?: ScheduleType; onProgress?: (completed: number, total: number) => void; }; /** * Transition configuration - supports both single and multi-spring animations * Uses Union Type for type safety and backward compatibility */ export type TransitionConfig<TAnimationValue = number> = SingleSpringConfig<TAnimationValue> | MultiSpringConfig<TAnimationValue>; /** * Type guard to check if config is single spring */ export declare function isSingleSpring<T>(config: TransitionConfig<T>): config is SingleSpringConfig<T>; /** * Type guard to check if config is multi-spring */ export declare function isMultiSpring<T>(config: TransitionConfig<T>): config is MultiSpringConfig<T>; export type GetTransitionConfig<TContext = undefined, TAnimationValue = number> = TContext extends undefined ? (node: HTMLElement) => TransitionConfig<TAnimationValue> | Promise<TransitionConfig<TAnimationValue>> : (node: HTMLElement, context: TContext) => TransitionConfig<TAnimationValue> | Promise<TransitionConfig<TAnimationValue>>; export type Transition<TContext = undefined, TAnimationValue = number> = { in?: GetTransitionConfig<TContext, TAnimationValue>; out?: GetTransitionConfig<TContext, TAnimationValue>; key?: TransitionKey; }; export type TransitionOptions<TContext = undefined, TAnimationValue = number> = Transition<TContext, TAnimationValue> & { key?: TransitionKey; ref?: object; }; export type TransitionCallback = (element: HTMLElement | null) => void | (() => void); /** * Context object passed to view transitions * Provides essential information for smooth page transitions */ export type SggoiTransitionContext = { /** * The scroll position difference between 'from' and 'to' pages * Used in hero and pinterest transitions to maintain natural scroll position * when transitioning between pages with different scroll states */ scrollOffset: { x: number; y: number; }; /** * The current page's scroll position * Provides the actual scroll coordinates for precise viewport calculations * OUT transition receives 'from' page scroll, IN transition receives 'to' page scroll */ scroll: { x: number; y: number; }; /** * The scrollable container element (document.documentElement or custom container) * Provides viewport dimensions for calculating transition animations * Lazy-evaluated to handle delayed initialization */ scrollingElement: HTMLElement; /** * The positioned parent element (containing block for absolute positioning) * Returns the nearest ancestor with position: relative/absolute/fixed/sticky * Falls back to document.body if no positioned ancestor exists * Lazy-evaluated to handle delayed initialization */ positionedParent: HTMLElement; }; export type SggoiTransition = Transition<SggoiTransitionContext>; export type SsgoiConfig = { transitions?: { from: string; to: string; transition: SggoiTransition; symmetric?: boolean; }[]; defaultTransition?: SggoiTransition; middleware?: (from: string, to: string) => { from: string; to: string; }; /** * Skip page transitions when iOS/Safari swipe-back gesture is detected * This prevents animation conflicts between SSGOI and Safari's native swipe animation * @default true */ skipOnIosSwipe?: boolean; }; export type SsgoiContext = (path: string) => Transition & { key: TransitionKey; }; /** * Normalized schedule entry for internal processing (discriminated union) * Used by AnimationScheduler to unify different schedule strategies * @internal */ export type NormalizedScheduleEntry = OffsetScheduleEntry | WaitScheduleEntry; /** * Offset-based schedule entry (overlap/chain modes) * - delay=0: immediate start (overlap) * - delay>0: delayed start with fixed timing (chain) * @internal */ export type OffsetScheduleEntry = { type: "offset"; id: string; delay: number; }; /** * Wait-based schedule entry (wait mode) * Dynamic dependency - starts after previous spring completes * @internal */ export type WaitScheduleEntry = { type: "wait"; id: string; }; /** * Animation state (discriminated union) * Different return types for single vs multi-spring animations * @internal */ export type AnimationState<T = number> = SingleAnimationState<T> | MultiAnimationState; /** * Single spring animation state * Supports both number and object animations * @internal */ export type SingleAnimationState<T = number> = { type: "single"; position: T; velocity: T extends number ? number : Record<string, number>; from: T; to: T; }; /** * Multi-spring animation state * Always uses number for progress tracking * @internal */ export type MultiAnimationState = { type: "multi"; completed: number; total: number; direction: "forward" | "backward"; }; /** * Common interface for animation controllers * Implemented by both Animator (single spring) and AnimationScheduler (multi-spring) * @internal */ export interface AnimationController<T = number> { forward(): void; backward(): void; stop(): void; reverse(options?: { offsetMode?: "immediate" | "mirror" | "reverse"; }): void; getCurrentState(): AnimationState<T>; } //# sourceMappingURL=types.d.ts.map