@gfazioli/mantine-text-animate
Version:
The TextAnimate component allows you to animate text with various effects.
241 lines (237 loc) • 6.08 kB
JavaScript
'use client';
;
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