UNPKG

@humanspeak/svelte-motion

Version:

Framer Motion for Svelte 5. Declarative motion.<tag> components with AnimatePresence exit animations, gestures (hover, tap, drag, focus, in-view), variants, FLIP layout animations, shared-layout transitions, spring physics, and scroll-linked motion values

102 lines (101 loc) 3.7 kB
import { getAnimatePresenceContext, getPresenceChildContext } from './presence'; /** * Returns whether the calling component is currently present in its parent * `<PresenceChild>`. While the wrapper holds the component for an exit, this * flips to `false` so the consumer can branch (render different markup, run * a custom exit animation, etc.). * * Outside of a `<PresenceChild>` always returns `true`. * * Reactivity note: the boolean tracks the wrapper's state and updates in * Svelte 5 reactive contexts (`$derived`, `$effect`, template). For non- * reactive snapshots, prefer `usePresence()` which exposes the same state * alongside the `safeToRemove` callback. * * @returns `true` while present, `false` while exiting. * @see https://motion.dev/docs/react-use-is-present * * @example * ```svelte * <script lang="ts"> * import { useIsPresent } from '@humanspeak/svelte-motion' * const isPresent = $derived(useIsPresent()) * </script> * <div class:exiting={!isPresent}>{isPresent ? 'live' : 'goodbye'}</div> * ``` */ export const useIsPresent = () => { const context = getPresenceChildContext(); return context ? context.isPresent : true; }; /** * Returns `[isPresent, safeToRemove]`. `isPresent` reflects the wrapper's * presence state; `safeToRemove` is the callback to invoke once a custom exit * animation finishes. Calling it triggers the actual unmount and decrements * the parent `<AnimatePresence>` exit-completion count. * * Outside of a `<PresenceChild>` returns `[true, null]` — the consumer is * effectively always present and there is nothing to safely remove. * * `safeToRemove` is idempotent and versioned: a stale callback from a * canceled exit cycle (re-entry before the consumer signaled completion) is * a no-op. * * @returns `[true, null]` while present (or outside any `PresenceChild`), * `[false, () => void]` while the wrapper holds the component for exit. * @see https://motion.dev/docs/react-use-presence * * @example * ```svelte * <script lang="ts"> * import { usePresence } from '@humanspeak/svelte-motion' * * let node: HTMLElement | undefined = $state() * const presence = $derived(usePresence()) * * $effect(() => { * const [isPresent, safeToRemove] = presence * if (isPresent || !node) return * const onEnd = () => safeToRemove() * node.addEventListener('transitionend', onEnd, { once: true }) * node.classList.add('exiting') * return () => node?.removeEventListener('transitionend', onEnd) * }) * </script> * * <div bind:this={node}>…</div> * ``` */ export const usePresence = () => { const context = getPresenceChildContext(); if (!context) return [true, null]; return context.isPresent ? [true, null] : [false, context.safeToRemove]; }; /** * Returns the nearest `<AnimatePresence custom={...}>` data. * * This mirrors Motion's `usePresenceData()` hook. It is useful for children * that need the latest parent presence data while rendering enter/exit * keyframes, for example directional slideshow controls. * * Outside of `<AnimatePresence>`, this returns `undefined`. * * @typeParam T Expected custom data type. * @returns The current AnimatePresence custom data, or `undefined` outside a * presence boundary. * @see https://motion.dev/docs/react-use-presence-data * * @example * ```svelte * <script lang="ts"> * import { usePresenceData } from '@humanspeak/svelte-motion' * * const direction = $derived(usePresenceData<1 | -1>() ?? 1) * </script> * ``` */ export const usePresenceData = () => { const context = getAnimatePresenceContext(); return context ? context.custom : undefined; };