UNPKG

@gfazioli/mantine-text-animate

Version:

The TextAnimate component allows you to animate text with various effects.

241 lines (237 loc) 6.08 kB
'use client'; 'use strict'; var React = require('react'); function lcs(a, b) { const m = a.length; const n = b.length; const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); for (let i2 = 1; i2 <= m; i2++) { for (let j2 = 1; j2 <= n; j2++) { if (a[i2 - 1] === b[j2 - 1]) { dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1; } else { dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]); } } } let result = ""; let i = m; let j = n; while (i > 0 && j > 0) { if (a[i - 1] === b[j - 1]) { result = a[i - 1] + result; i--; j--; } else if (dp[i - 1][j] > dp[i][j - 1]) { i--; } else { j--; } } return result; } function buildCharacters(prevValue, nextValue, counterRef) { const common = lcs(prevValue, nextValue); const characters = []; let ci = 0; let pi = 0; let ni = 0; const lcsOldPositions = []; let tempPi = 0; let tempCi = 0; while (tempCi < common.length && tempPi < prevValue.length) { if (prevValue[tempPi] === common[tempCi]) { lcsOldPositions.push(tempPi); tempCi++; } tempPi++; } const lcsNewPositions = []; let tempNi = 0; tempCi = 0; while (tempCi < common.length && tempNi < nextValue.length) { if (nextValue[tempNi] === common[tempCi]) { lcsNewPositions.push(tempNi); tempCi++; } tempNi++; } for (let k = 0; k < common.length; k++) { const fromX = lcsOldPositions[k]; const toX = lcsNewPositions[k]; const state = fromX === toX ? "static" : "moving"; characters.push({ char: common[k], key: `${common[k]}-lcs-${k}-${counterRef.current++}`, state, fromX, toX }); } ci = 0; pi = 0; while (pi < prevValue.length) { if (ci < common.length && prevValue[pi] === common[ci] && pi === lcsOldPositions[ci]) { ci++; } else { characters.push({ char: prevValue[pi], key: `${prevValue[pi]}-exit-${pi}-${counterRef.current++}`, state: "exiting", fromX: pi, toX: pi }); } pi++; } ci = 0; ni = 0; while (ni < nextValue.length) { if (ci < common.length && nextValue[ni] === common[ci] && ni === lcsNewPositions[ci]) { ci++; } else { characters.push({ char: nextValue[ni], key: `${nextValue[ni]}-enter-${ni}-${counterRef.current++}`, state: "entering", fromX: ni, toX: ni }); } ni++; } return characters; } function useMorphing({ value, animate = true, speed = 1, onCompleted }) { const [characters, setCharacters] = React.useState([]); const [isAnimating, setIsAnimating] = React.useState(false); const prevValueRef = React.useRef(""); const counterRef = React.useRef(0); const timeoutRef = React.useRef(null); const mountedRef = React.useRef(false); const cleanup = React.useCallback(() => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null; } }, []); const completeTransition = React.useCallback(() => { prevValueRef.current = value; setCharacters( (prev) => prev.filter((c) => c.state !== "exiting").map((c) => ({ ...c, state: "static", fromX: c.toX })) ); setIsAnimating(false); onCompleted?.(); }, [value, onCompleted]); const startTransition = React.useCallback(() => { if (typeof window === "undefined") { return; } const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (prefersReducedMotion) { const finalChars = value.split("").map((char, i) => ({ char, key: `${char}-static-${i}-${counterRef.current++}`, state: "static", fromX: i, toX: i })); setCharacters(finalChars); prevValueRef.current = value; onCompleted?.(); return; } cleanup(); const chars = buildCharacters(prevValueRef.current, value, counterRef); setCharacters(chars); setIsAnimating(true); const duration = 1e3 / (speed || 1); timeoutRef.current = setTimeout(() => { if (mountedRef.current) { completeTransition(); } }, duration); }, [value, speed, cleanup, completeTransition, onCompleted]); const start = React.useCallback(() => { startTransition(); }, [startTransition]); const stop = React.useCallback(() => { cleanup(); setIsAnimating(false); }, [cleanup]); const reset = React.useCallback(() => { cleanup(); setIsAnimating(false); prevValueRef.current = ""; const chars = value.split("").map((char, i) => ({ char, key: `${char}-static-${i}-${counterRef.current++}`, state: "static", fromX: i, toX: i })); setCharacters(chars); prevValueRef.current = value; }, [cleanup, value]); React.useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; }; }, []); React.useEffect(() => { if (!mountedRef.current) { const chars = value.split("").map((char, i) => ({ char, key: `${char}-static-${i}-${counterRef.current++}`, state: "static", fromX: i, toX: i })); setCharacters(chars); prevValueRef.current = value; mountedRef.current = true; return; } if (value === prevValueRef.current) { return; } if (animate) { startTransition(); } else { const chars = value.split("").map((char, i) => ({ char, key: `${char}-static-${i}-${counterRef.current++}`, state: "static", fromX: i, toX: i })); setCharacters(chars); prevValueRef.current = value; } }, [value, animate, startTransition]); React.useEffect(() => { return () => { cleanup(); }; }, [cleanup]); return { characters, width: value.length, start, stop, reset, isAnimating }; } exports.useMorphing = useMorphing; //# sourceMappingURL=use-morphing.cjs.map