All files / src/render/utils lifecycles.ts

100% Statements 22/22
100% Branches 6/6
100% Functions 9/9
100% Lines 18/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171    36x       36x                                                                                                                                                                                                                                                                           36x 7700x 700x 700x 3652x   751x 8261x 8261x     8261x     8261x 134x           700x 7700x 7700x 914x       700x    
import { MotionProps } from "../../motion/types"
import { Axis, Box, Delta } from "../../projection/geometry/types"
import { SubscriptionManager } from "../../utils/subscription-manager"
import { ResolvedValues } from "../types"
import { AnimationDefinition } from "./animation"
 
const names = [
    "LayoutMeasure",
    "BeforeLayoutMeasure",
    "LayoutUpdate",
    "ViewportBoxUpdate",
    "Update",
    "Render",
    "AnimationComplete",
    "LayoutAnimationComplete",
    "AnimationStart",
    "SetAxisTarget",
    "Unmount",
]
 
export type LayoutMeasureListener = (layout: Box, prevLayout?: Box) => void
export type BeforeLayoutMeasureListener = (layout: Axis) => void
export type LayoutUpdateListener = (
    layout: Axis,
    prevLayout: Axis
    // config?: SharedLayoutAnimationConfig
) => void
export type UpdateListener = (latest: ResolvedValues) => void
export type AnimationStartListener = (definition: AnimationDefinition) => void
export type AnimationCompleteListener = (
    definition: AnimationDefinition
) => void
export type LayoutAnimationCompleteListener = () => void
export type SetAxisTargetListener = () => void
export type RenderListener = () => void
export type OnViewportBoxUpdate = (box: Axis, delta: Delta) => void
 
export interface LayoutLifecycles {
    onBeforeLayoutMeasure?(box: Box): void
 
    onLayoutMeasure?(box: Box, prevBox: Box): void
 
    /**
     * @internal
     */
    onLayoutAnimationComplete?(): void
}
 
export interface AnimationLifecycles {
    /**
     * Callback with latest motion values, fired max once per frame.
     *
     * ```jsx
     * function onUpdate(latest) {
     *   console.log(latest.x, latest.opacity)
     * }
     *
     * <motion.div animate={{ x: 100, opacity: 0 }} onUpdate={onUpdate} />
     * ```
     */
    onUpdate?(latest: ResolvedValues): void
 
    /**
     * Callback when animation defined in `animate` begins.
     *
     * The provided callback will be called with the triggering animation definition.
     * If this is a variant, it'll be the variant name, and if a target object
     * then it'll be the target object.
     *
     * This way, it's possible to figure out which animation has started.
     *
     * ```jsx
     * function onStart() {
     *   console.log("Animation started")
     * }
     *
     * <motion.div animate={{ x: 100 }} onAnimationStart={onStart} />
     * ```
     */
    onAnimationStart?(definition: AnimationDefinition): void
 
    /**
     * Callback when animation defined in `animate` is complete.
     *
     * The provided callback will be called with the triggering animation definition.
     * If this is a variant, it'll be the variant name, and if a target object
     * then it'll be the target object.
     *
     * This way, it's possible to figure out which animation has completed.
     *
     * ```jsx
     * function onComplete() {
     *   console.log("Animation completed")
     * }
     *
     * <motion.div
     *   animate={{ x: 100 }}
     *   onAnimationComplete={definition => {
     *     console.log('Completed animating', definition)
     *   }}
     * />
     * ```
     */
    onAnimationComplete?(definition: AnimationDefinition): void
 
    /**
     * @internal
     */
    onUnmount?(): void
}
 
export type VisualElementLifecycles = LayoutLifecycles & AnimationLifecycles
 
export interface LifecycleManager {
    onLayoutMeasure: (callback: LayoutMeasureListener) => () => void
    notifyLayoutMeasure: LayoutMeasureListener
    onBeforeLayoutMeasure: (callback: BeforeLayoutMeasureListener) => () => void
    notifyBeforeLayoutMeasure: BeforeLayoutMeasureListener
    onLayoutUpdate: (callback: LayoutUpdateListener) => () => void
    notifyLayoutUpdate: LayoutUpdateListener
    onUpdate: (callback: UpdateListener) => () => void
    notifyUpdate: UpdateListener
    onAnimationStart: (callback: AnimationStartListener) => () => void
    notifyAnimationStart: AnimationStartListener
    onAnimationComplete: (callback: AnimationCompleteListener) => () => void
    notifyAnimationComplete: AnimationCompleteListener
    onLayoutAnimationComplete: (
        callback: LayoutAnimationCompleteListener
    ) => () => void
    notifyLayoutAnimationComplete: LayoutAnimationCompleteListener
    onSetAxisTarget: (callback: SetAxisTargetListener) => () => void
    notifySetAxisTarget: SetAxisTargetListener
    onRender: (callback: RenderListener) => () => void
    notifyRender: RenderListener
    onUnmount: (callback: () => void) => () => void
    notifyUnmount: () => void
    clearAllListeners: () => void
    updatePropListeners: (props: MotionProps) => void
}
 
export function createLifecycles() {
    const managers = names.map(() => new SubscriptionManager())
    const propSubscriptions: { [key: string]: () => {} } = {}
    const lifecycles: Partial<LifecycleManager> = {
        clearAllListeners: () => managers.forEach((manager) => manager.clear()),
        updatePropListeners: (props) => {
            names.forEach((name) => {
                const on = "on" + name
                const propListener = props[on]
 
                // Unsubscribe existing subscription
                propSubscriptions[name]?.()
 
                // Add new subscription
                if (propListener) {
                    propSubscriptions[name] = lifecycles[on](propListener)
                }
            })
        },
    }
 
    managers.forEach((manager, i) => {
        lifecycles["on" + names[i]] = (handler: any) => manager.add(handler)
        lifecycles["notify" + names[i]] = (...args: any) => {
            manager.notify(...args)
        }
    })
 
    return lifecycles as LifecycleManager
}