@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
52 lines (51 loc) • 2.22 kB
JavaScript
import { attachFollow, isMotionValue, motionValue } from 'motion-dom';
import {} from 'svelte/store';
import { augmentMotionValue, isSvelteReadable, sampleSource } from './augmentMotionValue.svelte.js';
export function useFollowValue(source, options = {}) {
// SSR: return a static MotionValue with no animation. Reads return the
// best-effort initial value; .set / .jump become no-ops to avoid drifting
// away from the server-rendered snapshot.
if (typeof window === 'undefined') {
const initial = sampleSource(source);
const ssrValue = motionValue(initial);
ssrValue.set = () => undefined;
ssrValue.jump = () => undefined;
return augmentMotionValue(ssrValue);
}
// Resolve initial + follow source. Svelte readables get bridged into a
// motion-dom MotionValue so `attachFollow` can subscribe to their emits.
let followSource;
let cleanupReadableBridge;
let svelteBridge;
if (isMotionValue(source)) {
followSource = source;
}
else if (isSvelteReadable(source)) {
const initialFromReadable = sampleSource(source);
svelteBridge = motionValue(initialFromReadable);
cleanupReadableBridge = source.subscribe((v) => {
// Svelte readable contract emits synchronously on subscribe. Skip
// the initial emit (already seeded above) so attachFollow doesn't
// fire an animation on attach.
if (svelteBridge.get() === v)
return;
svelteBridge.set(v);
});
followSource = svelteBridge;
}
else {
followSource = source;
}
const initial = isMotionValue(followSource) ? followSource.get() : followSource;
const value = motionValue(initial);
// Default transition is `spring` — matches React framer-motion's
// `useFollowValue` default. Caller's `type` (if set) overrides.
const stopFollow = attachFollow(value, followSource, { type: 'spring', ...options });
const dispose = () => {
stopFollow?.();
cleanupReadableBridge?.();
svelteBridge?.destroy();
};
$effect(() => () => value.destroy());
return augmentMotionValue(value, dispose);
}