@idealyst/animate 1.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +72 -0
- package/src/index.native.ts +66 -0
- package/src/index.ts +64 -0
- package/src/types.ts +208 -0
- package/src/useAnimatedStyle.native.ts +168 -0
- package/src/useAnimatedStyle.ts +165 -0
- package/src/useAnimatedValue.native.ts +169 -0
- package/src/useAnimatedValue.ts +227 -0
- package/src/useGradientBorder.native.tsx +229 -0
- package/src/useGradientBorder.ts +180 -0
- package/src/usePresence.native.ts +157 -0
- package/src/usePresence.ts +151 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePresence - Web implementation
|
|
3
|
+
*
|
|
4
|
+
* Manages mount/unmount animations by keeping elements in the DOM
|
|
5
|
+
* during exit animations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
|
9
|
+
import { resolveDuration, resolveEasing } from '@idealyst/theme/animation';
|
|
10
|
+
import type { UsePresenceOptions, UsePresenceResult, AnimatableStyle } from './types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hook that manages presence animations for mount/unmount.
|
|
14
|
+
* The element stays rendered during exit animation.
|
|
15
|
+
*
|
|
16
|
+
* @param isVisible - Whether the element should be visible
|
|
17
|
+
* @param options - Animation configuration with enter/exit styles
|
|
18
|
+
* @returns Object with isPresent, style, and exit function
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* const { isPresent, style } = usePresence(isOpen, {
|
|
23
|
+
* enter: { opacity: 1, transform: [{ translateY: 0 }] },
|
|
24
|
+
* exit: { opacity: 0, transform: [{ translateY: -20 }] },
|
|
25
|
+
* duration: 'normal',
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* return isPresent && <div style={style}>Modal content</div>;
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function usePresence(isVisible: boolean, options: UsePresenceOptions): UsePresenceResult {
|
|
32
|
+
const { enter, exit, initial, duration = 'normal', easing = 'easeOut', delay = 0 } = options;
|
|
33
|
+
|
|
34
|
+
// Track whether the element should be in the DOM
|
|
35
|
+
const [isPresent, setIsPresent] = useState(isVisible);
|
|
36
|
+
// Track whether we're animating in or out
|
|
37
|
+
const [isEntering, setIsEntering] = useState(false);
|
|
38
|
+
// Track initial mount
|
|
39
|
+
const isInitialMount = useRef(true);
|
|
40
|
+
// Timeout ref for cleanup
|
|
41
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
42
|
+
|
|
43
|
+
const durationMs = resolveDuration(duration);
|
|
44
|
+
|
|
45
|
+
// Handle visibility changes
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
// Clear any pending timeout
|
|
48
|
+
if (timeoutRef.current) {
|
|
49
|
+
clearTimeout(timeoutRef.current);
|
|
50
|
+
timeoutRef.current = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (isVisible) {
|
|
54
|
+
// Entering: mount immediately, then animate in
|
|
55
|
+
setIsPresent(true);
|
|
56
|
+
// Use double RAF to ensure DOM is ready before animating
|
|
57
|
+
requestAnimationFrame(() => {
|
|
58
|
+
requestAnimationFrame(() => {
|
|
59
|
+
setIsEntering(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
} else if (!isInitialMount.current) {
|
|
63
|
+
// Exiting: animate out, then unmount
|
|
64
|
+
setIsEntering(false);
|
|
65
|
+
timeoutRef.current = setTimeout(() => {
|
|
66
|
+
setIsPresent(false);
|
|
67
|
+
}, durationMs + delay);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
isInitialMount.current = false;
|
|
71
|
+
|
|
72
|
+
return () => {
|
|
73
|
+
if (timeoutRef.current) {
|
|
74
|
+
clearTimeout(timeoutRef.current);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}, [isVisible, durationMs, delay]);
|
|
78
|
+
|
|
79
|
+
// Manual exit trigger
|
|
80
|
+
const triggerExit = useCallback(() => {
|
|
81
|
+
if (timeoutRef.current) {
|
|
82
|
+
clearTimeout(timeoutRef.current);
|
|
83
|
+
}
|
|
84
|
+
setIsEntering(false);
|
|
85
|
+
timeoutRef.current = setTimeout(() => {
|
|
86
|
+
setIsPresent(false);
|
|
87
|
+
}, durationMs + delay);
|
|
88
|
+
}, [durationMs, delay]);
|
|
89
|
+
|
|
90
|
+
// Convert transform array to CSS transform string
|
|
91
|
+
const transformToString = (transform: any[] | undefined): string | undefined => {
|
|
92
|
+
if (!transform) return undefined;
|
|
93
|
+
|
|
94
|
+
return transform
|
|
95
|
+
.map((t) => {
|
|
96
|
+
const [key, value] = Object.entries(t)[0];
|
|
97
|
+
switch (key) {
|
|
98
|
+
case 'translateX':
|
|
99
|
+
case 'translateY':
|
|
100
|
+
return `${key}(${typeof value === 'number' ? `${value}px` : value})`;
|
|
101
|
+
default:
|
|
102
|
+
return `${key}(${value})`;
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
.join(' ');
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Build the style object
|
|
109
|
+
const style = useMemo(() => {
|
|
110
|
+
const currentStyle = isEntering ? enter : (initial ?? exit);
|
|
111
|
+
const easingCss = resolveEasing(easing);
|
|
112
|
+
|
|
113
|
+
const result: Record<string, any> = {};
|
|
114
|
+
|
|
115
|
+
// Copy style properties
|
|
116
|
+
Object.entries(currentStyle).forEach(([key, value]) => {
|
|
117
|
+
if (key !== 'transform' && value !== undefined) {
|
|
118
|
+
result[key] = value;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Handle transform
|
|
123
|
+
const transformStr = transformToString(currentStyle.transform);
|
|
124
|
+
if (transformStr) {
|
|
125
|
+
result.transform = transformStr;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Add transition (always, since we want to animate both enter and exit)
|
|
129
|
+
const properties = Object.keys(currentStyle)
|
|
130
|
+
.filter((k) => k !== 'transform')
|
|
131
|
+
.map((k) => k.replace(/([A-Z])/g, '-$1').toLowerCase());
|
|
132
|
+
if (currentStyle.transform) {
|
|
133
|
+
properties.push('transform');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (properties.length > 0) {
|
|
137
|
+
const delayStr = delay > 0 ? ` ${delay}ms` : '';
|
|
138
|
+
result.transition = properties
|
|
139
|
+
.map((prop) => `${prop} ${durationMs}ms ${easingCss}${delayStr}`)
|
|
140
|
+
.join(', ');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return result as AnimatableStyle;
|
|
144
|
+
}, [isEntering, enter, exit, initial, durationMs, easing, delay]);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
isPresent,
|
|
148
|
+
style,
|
|
149
|
+
exit: triggerExit,
|
|
150
|
+
};
|
|
151
|
+
}
|