UNPKG

@ssgoi/core

Version:

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

473 lines 16.1 kB
import { Integrator } from './animator/integrator'; import { CreateNavigationDetector } from './ssgoi-transition/navigation-detector-strategy'; export type TransitionKey = string | symbol; /** * Double spring follower configuration * Allows customizing the follower spring independently */ export type DoubleSpringFollowerConfig = { stiffness: number; damping: number; }; export type SpringConfig = { stiffness: number; damping: number; /** * Enable double spring for ease-in-out effect (Chained/Lagged Spring) * * - true: same stiffness for both springs * - number (0-1): follower stiffness ratio (smaller = stronger ease-in) * - 1.0: same as true * - 0.5: follower has half stiffness → stronger ease-in * - 0.3: even stronger ease-in * - { stiffness, damping }: custom follower spring config * * Creates a two-spring system: * - Leader spring: tracks the target directly * - Follower spring: tracks the leader's position (this is the output) */ doubleSpring?: boolean | number | DoubleSpringFollowerConfig; /** * Position threshold for settling detection * When distance to target is below this, position is considered settled * @default 0.01 */ restDelta?: number; /** * Velocity threshold for settling detection * When speed is below this, velocity is considered settled * @default 0.01 */ restSpeed?: number; }; /** * Resistance type for inertia physics * - linear: resistance proportional to velocity (F = -r * v) * - quadratic: resistance proportional to velocity squared (F = -r * v²) */ export type ResistanceType = "linear" | "quadratic"; /** * Inertia configuration for ease-in effect * Simulates acceleration with resistance */ export type InertiaConfig = { /** Acceleration force (higher = faster acceleration) */ acceleration: number; /** Resistance coefficient (higher = more resistance) */ resistance: number; /** Resistance type (default: 'quadratic') */ resistanceType?: ResistanceType; /** * Minimum boundary value * When position goes below this, spring bounce is applied */ min?: number; /** * Maximum boundary value * When position goes above this, spring bounce is applied */ max?: number; /** * Spring stiffness for boundary bounce effect * Higher value = stiffer bounce * @default 500 */ bounceStiffness?: number; /** * Spring damping for boundary bounce effect * Higher value = faster settling * @default 10 */ bounceDamping?: number; /** * Position threshold for settling detection * When distance to target is below this, position is considered settled * @default 0.01 */ restDelta?: number; }; /** * Custom integrator factory function * Allows using custom physics implementations */ export type IntegratorFactory = () => Integrator; /** * Physics options for animations * Use with & to add physics options to any config type * * @example * type FadeOptions = { from?: number; to?: number } & PhysicsOptions; */ export type PhysicsOptions = { /** Spring physics (ease-out) */ spring?: SpringConfig; /** Inertia physics (ease-in) */ inertia?: InertiaConfig; /** Custom integrator factory */ integrator?: IntegratorFactory; }; /** * CSS style object type */ export type StyleObject = Record<string, number | string>; /** * Schedule strategy for multi-spring animations (user-facing) * - parallel: All springs start immediately (offset = 0) * - sequential: Each spring waits for previous to complete (offset = 1) * - stagger: Springs start at custom progress offsets (0-1) */ export type ScheduleType = "parallel" | "sequential" | "stagger"; /** * Base configuration shared by both single and multi-spring transitions */ export type BaseTransitionConfig = { prepare?: () => void; wait?: () => Promise<void>; onReady?: () => void; onStart?: () => void; onEnd?: () => void; }; /** * Single spring configuration * Used for simple transitions with a single animation value * * Supports two animation modes: * - tick: RAF-based animation with callback on each frame * - css: Web Animation API with CSS string generation */ export type SingleSpringConfig = BaseTransitionConfig & { /** * Physics configuration for animation * Choose one: spring (ease-out), inertia (ease-in), or custom integrator */ physics?: PhysicsOptions; tick?: (progress: number) => void; /** * Style generator for Web Animation API mode * * When provided, the animation uses Web Animation API instead of RAF. * Spring physics are pre-computed and converted to keyframes. * * @param progress - Current progress value (0 to 1) * @returns Style object for Web Animation API * * @example * ```ts * css: (progress) => ({ * opacity: progress, * transform: `translateY(${(1 - progress) * 20}px)`, * }) * ``` */ css?: (progress: number) => StyleObject; }; /** * Individual animation item in a multi-animation config * * Supports two animation modes (mutually exclusive): * - tick: RAF-based animation with callback on each frame * - css: Web Animation API with CSS string generation */ export type AnimationItem = { /** * Physics configuration for animation * Choose one: spring (ease-out), inertia (ease-in), or custom integrator */ physics?: PhysicsOptions; /** * Frame callback - called on each animation frame with progress value (RAF-based) * * **Performance Warning:** Use WRITE-ONLY operations for best performance. * Avoid reading layout properties inside tick to prevent layout thrashing. * * @example * tick: (progress) => { * element.style.opacity = String(progress) * } */ tick?: (progress: number) => void; /** * Style generator for Web Animation API mode * * When provided, the animation uses Web Animation API instead of RAF. * Spring physics are pre-computed and converted to keyframes. * * Two forms supported: * 1. Function only - uses parent element: `css: (progress) => ({ ... })` * 2. Object with element - specifies target element: `css: { element, style: (progress) => ({ ... }) }` * * @param progress - Current progress value (0 to 1) * @returns Style object for Web Animation API * * @example * // Form 1: Function (element from parent) * css: (progress) => ({ * opacity: progress, * transform: `translateY(${(1 - progress) * 20}px)`, * }) * * @example * // Form 2: Object with custom element * css: { * element: myElement, * style: (progress) => ({ * transform: `scale(${progress})`, * }), * } */ css?: ((progress: number) => StyleObject) | { element: HTMLElement; style: (progress: number) => StyleObject; }; onStart?: () => void; onComplete?: () => void; /** * Progress offset (0-1) before this spring starts * - 0: Start immediately (parallel behavior) * - 1: Start after previous spring completes (sequential behavior) * - 0.5: Start when previous spring is 50% complete * * Only applies to 'stagger' schedule mode. * For 'parallel' mode, all springs start at 0. * For 'sequential' mode, all springs use offset 1. */ offset?: number; }; /** * Multi-animation configuration * Used for complex transitions with multiple coordinated animations */ export type MultiAnimationConfig = BaseTransitionConfig & { items: AnimationItem[]; schedule?: ScheduleType; onProgress?: (completed: number, total: number) => void; }; /** * Transition configuration - supports both single and multi animations * Uses Union Type for type safety and backward compatibility */ export type TransitionConfig = SingleSpringConfig | MultiAnimationConfig; /** * Type guard to check if config is single animation */ export declare function isSingleAnimation(config: TransitionConfig): config is SingleSpringConfig; /** * Type guard to check if config is multi-animation */ export declare function isMultiAnimation(config: TransitionConfig): config is MultiAnimationConfig; /** * Type guard to check if config uses CSS mode (Web Animation API) */ export declare function isCssAnimation(config: SingleSpringConfig): config is SingleSpringConfig & { css: (progress: number) => string; }; /** * Type guard to check if config uses tick mode (RAF-based) */ export declare function isTickAnimation(config: SingleSpringConfig): config is SingleSpringConfig & { tick: (progress: number) => void; }; /** * Normalize TransitionConfig to MultiAnimationConfig * Converts SingleSpringConfig to MultiAnimationConfig with single animation item * Accepts both sync config and Promise<TransitionConfig> * @internal */ export declare function normalizeToMultiAnimation(config: TransitionConfig | Promise<TransitionConfig>): Promise<MultiAnimationConfig>; export type GetTransitionConfig<TContext = undefined> = TContext extends undefined ? (node: HTMLElement) => TransitionConfig | Promise<TransitionConfig> : (node: HTMLElement, context: TContext) => TransitionConfig | Promise<TransitionConfig>; export type Transition<TContext = undefined> = { in?: GetTransitionConfig<TContext>; out?: GetTransitionConfig<TContext>; key?: TransitionKey; }; /** * Scope behavior for transitions * - 'global': Always run animations (default) * - 'local': Skip animations when mounting/unmounting with parent TransitionScope */ export type TransitionScope = "global" | "local"; export type TransitionOptions<TContext = undefined> = Transition<TContext> & { key: TransitionKey; /** * Controls animation behavior relative to TransitionScope * - 'global' (default): Always run IN/OUT animations * - 'local': Skip animations when mounting/unmounting simultaneously with scope */ scope?: TransitionScope; }; export type TransitionCallback = (element: HTMLElement | null) => void | (() => void); /** * Ref callback type for 'auto' mode * Handles null and manages unmount automatically via MutationObserver */ export type RefCallback = (element: HTMLElement | null) => void; /** * Transition mode * - 'manual': Returns TransitionCallback with cleanup function (default) * - 'auto': Returns RefCallback with automatic unmount detection via MutationObserver */ export type TransitionMode = "manual" | "auto"; /** * 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; }; /** * Internal options for framework adapters * Not exposed to end users * @internal */ export type SsgoiInternalOptions = { /** * Whether OUT transition must arrive before IN transition * * - true (default): OUT must arrive first, IN completes the pair. * Best for frameworks with native destroy callbacks (Svelte, Vue). * * - false: OUT and IN can arrive in any order, both wait indefinitely. * Best for frameworks using MutationObserver for unmount detection (React). * * @default true */ outFirst?: boolean; /** * Custom navigation detector factory * If provided, overrides the default detector selection based on outFirst * Also returns SsgoiExtendedContext instead of SsgoiContext */ createNavigationDetector?: CreateNavigationDetector; }; export type SsgoiContext = (path: string) => Transition & { key: TransitionKey; }; /** * Extended context with additional utilities for frameworks that need * pre-transition visibility control (e.g., React with Next.js) */ export type SsgoiExtendedContext = { getTransition: SsgoiContext; /** * Check if a transition is configured for the given from/to paths * Useful for determining initial visibility before transition starts */ hasMatchingTransition: (from: string, to: string) => boolean; }; /** * Normalized schedule entry for internal processing * All schedule types are normalized to progress-based offsets (0-1) * * Progress offset determines when each spring starts relative to previous spring: * - 0: Start immediately (parallel) * - 1: Start after previous completes (sequential) * - 0.5: Start when previous is 50% complete * * @internal */ export type NormalizedScheduleEntry = { id: string; /** * Progress offset (0-1) relative to previous spring * - 0: Start immediately with previous spring * - 1: Start after previous spring completes * - 0-1: Start when previous spring reaches this progress */ offset: number; }; /** * Normalized animation item for internal use * - css is always in object form: { element, style } * - normalizedOffset is always present * @internal */ export type NormalizedAnimationItem = Omit<AnimationItem, "css" | "offset"> & { css?: { element: HTMLElement; style: (progress: number) => StyleObject; }; normalizedOffset: number; }; /** * Normalized multi-animation config for internal use * Schedule is always normalized to progress-based offsets * @internal */ export type NormalizedMultiAnimationConfig = Omit<MultiAnimationConfig, "items" | "schedule"> & { items: NormalizedAnimationItem[]; }; /** * Normalize MultiAnimationConfig schedule to progress-based offsets * * Converts user-facing schedule types to unified offset values: * - parallel: All items get offset 0 (start together) * - sequential: All items get offset 1 (wait for previous) * - stagger: Use each item's custom offset (0-1) * * Also normalizes css to object form: { element, style } * * @internal */ export declare function normalizeSchedule(config: MultiAnimationConfig, element?: HTMLElement): NormalizedMultiAnimationConfig; /** * Animation state * For MultiAnimator: returns first animator's state * @internal */ export type AnimationState = { position: number; velocity: number; from: number; to: number; }; //# sourceMappingURL=types.d.ts.map