react-native-ease 0.3.0 → 0.4.0

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/src/EaseView.tsx CHANGED
@@ -1,8 +1,9 @@
1
1
  import { StyleSheet, type ViewProps, type ViewStyle } from 'react-native';
2
- import NativeEaseView from './EaseViewNativeComponent';
2
+ import NativeEaseView, { type NativeProps } from './EaseViewNativeComponent';
3
3
  import type {
4
4
  AnimateProps,
5
5
  CubicBezier,
6
+ SingleTransition,
6
7
  Transition,
7
8
  TransitionEndEvent,
8
9
  TransformOrigin,
@@ -58,6 +59,115 @@ const EASING_PRESETS: Record<string, CubicBezier> = {
58
59
  easeInOut: [0.42, 0, 0.58, 1],
59
60
  };
60
61
 
62
+ /** Returns true if the transition is a SingleTransition (has a `type` field). */
63
+ function isSingleTransition(t: Transition): t is SingleTransition {
64
+ return 'type' in t;
65
+ }
66
+
67
+ type NativeTransitions = NonNullable<NativeProps['transitions']>;
68
+ type NativeTransitionConfig = NativeTransitions['defaultConfig'];
69
+
70
+ /** Default config: timing 300ms easeInOut. */
71
+ const DEFAULT_CONFIG: NativeTransitionConfig = {
72
+ type: 'timing',
73
+ duration: 300,
74
+ easingBezier: [0.42, 0, 0.58, 1],
75
+ damping: 15,
76
+ stiffness: 120,
77
+ mass: 1,
78
+ loop: 'none',
79
+ delay: 0,
80
+ };
81
+
82
+ /** Resolve a SingleTransition into a native config object. */
83
+ function resolveSingleConfig(config: SingleTransition): NativeTransitionConfig {
84
+ const type = config.type as string;
85
+ const duration = config.type === 'timing' ? config.duration ?? 300 : 300;
86
+ const rawEasing =
87
+ config.type === 'timing' ? config.easing ?? 'easeInOut' : 'easeInOut';
88
+ if (__DEV__) {
89
+ if (Array.isArray(rawEasing)) {
90
+ if ((rawEasing as number[]).length !== 4) {
91
+ console.warn(
92
+ 'react-native-ease: Custom easing must be a [x1, y1, x2, y2] tuple (got length ' +
93
+ (rawEasing as number[]).length +
94
+ ').',
95
+ );
96
+ }
97
+ if (
98
+ rawEasing[0] < 0 ||
99
+ rawEasing[0] > 1 ||
100
+ rawEasing[2] < 0 ||
101
+ rawEasing[2] > 1
102
+ ) {
103
+ console.warn(
104
+ 'react-native-ease: Easing x-values (x1, x2) must be between 0 and 1.',
105
+ );
106
+ }
107
+ }
108
+ }
109
+ const easingBezier: number[] = Array.isArray(rawEasing)
110
+ ? rawEasing
111
+ : EASING_PRESETS[rawEasing]!;
112
+ const damping = config.type === 'spring' ? config.damping ?? 15 : 15;
113
+ const stiffness = config.type === 'spring' ? config.stiffness ?? 120 : 120;
114
+ const mass = config.type === 'spring' ? config.mass ?? 1 : 1;
115
+ const loop: string =
116
+ config.type === 'timing' ? config.loop ?? 'none' : 'none';
117
+ const delay =
118
+ config.type === 'timing' || config.type === 'spring'
119
+ ? config.delay ?? 0
120
+ : 0;
121
+ return {
122
+ type,
123
+ duration,
124
+ easingBezier,
125
+ damping,
126
+ stiffness,
127
+ mass,
128
+ loop,
129
+ delay,
130
+ };
131
+ }
132
+
133
+ /** Category keys that map to optional NativeTransitions fields. */
134
+ const CATEGORY_KEYS = [
135
+ 'transform',
136
+ 'opacity',
137
+ 'borderRadius',
138
+ 'backgroundColor',
139
+ ] as const;
140
+
141
+ /** Resolve the transition prop into a NativeTransitions struct. */
142
+ function resolveTransitions(transition?: Transition): NativeTransitions {
143
+ // No transition: timing default for all properties
144
+ if (transition == null) {
145
+ return { defaultConfig: DEFAULT_CONFIG };
146
+ }
147
+
148
+ // Single transition: set as defaultConfig only
149
+ if (isSingleTransition(transition)) {
150
+ return { defaultConfig: resolveSingleConfig(transition) };
151
+ }
152
+
153
+ // TransitionMap: resolve defaultConfig + only specified category keys
154
+ const defaultConfig = transition.default
155
+ ? resolveSingleConfig(transition.default)
156
+ : DEFAULT_CONFIG;
157
+
158
+ const result: NativeTransitions = { defaultConfig };
159
+
160
+ for (const key of CATEGORY_KEYS) {
161
+ const specific = transition[key];
162
+ if (specific != null) {
163
+ (result as Record<string, NativeTransitionConfig>)[key] =
164
+ resolveSingleConfig(specific);
165
+ }
166
+ }
167
+
168
+ return result;
169
+ }
170
+
61
171
  export type EaseViewProps = ViewProps & {
62
172
  /** Target values for animated properties. */
63
173
  animate?: AnimateProps;
@@ -86,6 +196,8 @@ export type EaseViewProps = ViewProps & {
86
196
  useHardwareLayer?: boolean;
87
197
  /** Pivot point for scale and rotation as 0–1 fractions. @default { x: 0.5, y: 0.5 } (center) */
88
198
  transformOrigin?: TransformOrigin;
199
+ /** NativeWind / Tailwind CSS class string. Requires NativeWind in your project. */
200
+ className?: string;
89
201
  };
90
202
 
91
203
  export function EaseView({
@@ -179,50 +291,8 @@ export function EaseView({
179
291
  }
180
292
  }
181
293
 
182
- // Resolve transition config
183
- const transitionType = transition?.type ?? 'timing';
184
- const transitionDuration =
185
- transition?.type === 'timing' ? transition.duration ?? 300 : 300;
186
- const rawEasing =
187
- transition?.type === 'timing'
188
- ? transition.easing ?? 'easeInOut'
189
- : 'easeInOut';
190
- if (__DEV__) {
191
- if (Array.isArray(rawEasing)) {
192
- if ((rawEasing as number[]).length !== 4) {
193
- console.warn(
194
- 'react-native-ease: Custom easing must be a [x1, y1, x2, y2] tuple (got length ' +
195
- (rawEasing as number[]).length +
196
- ').',
197
- );
198
- }
199
- if (
200
- rawEasing[0] < 0 ||
201
- rawEasing[0] > 1 ||
202
- rawEasing[2] < 0 ||
203
- rawEasing[2] > 1
204
- ) {
205
- console.warn(
206
- 'react-native-ease: Easing x-values (x1, x2) must be between 0 and 1.',
207
- );
208
- }
209
- }
210
- }
211
- const bezier: CubicBezier = Array.isArray(rawEasing)
212
- ? rawEasing
213
- : EASING_PRESETS[rawEasing]!;
214
- const transitionDamping =
215
- transition?.type === 'spring' ? transition.damping ?? 15 : 15;
216
- const transitionStiffness =
217
- transition?.type === 'spring' ? transition.stiffness ?? 120 : 120;
218
- const transitionMass =
219
- transition?.type === 'spring' ? transition.mass ?? 1 : 1;
220
- const transitionLoop =
221
- transition?.type === 'timing' ? transition.loop ?? 'none' : 'none';
222
- const transitionDelay =
223
- transition?.type === 'timing' || transition?.type === 'spring'
224
- ? transition.delay ?? 0
225
- : 0;
294
+ // Resolve transition config into a fully-populated struct
295
+ const transitions = resolveTransitions(transition);
226
296
 
227
297
  const handleTransitionEnd = onTransitionEnd
228
298
  ? (event: { nativeEvent: { finished: boolean } }) =>
@@ -254,14 +324,7 @@ export function EaseView({
254
324
  initialAnimateRotateY={resolvedInitial.rotateY}
255
325
  initialAnimateBorderRadius={resolvedInitial.borderRadius}
256
326
  initialAnimateBackgroundColor={initialBgColor}
257
- transitionType={transitionType}
258
- transitionDuration={transitionDuration}
259
- transitionEasingBezier={bezier}
260
- transitionDamping={transitionDamping}
261
- transitionStiffness={transitionStiffness}
262
- transitionMass={transitionMass}
263
- transitionLoop={transitionLoop}
264
- transitionDelay={transitionDelay}
327
+ transitions={transitions}
265
328
  useHardwareLayer={useHardwareLayer}
266
329
  transformOriginX={transformOrigin?.x ?? 0.5}
267
330
  transformOriginY={transformOrigin?.y ?? 0.5}
@@ -3,6 +3,7 @@ import { View, type ViewStyle, type StyleProp } from 'react-native';
3
3
  import type {
4
4
  AnimateProps,
5
5
  CubicBezier,
6
+ SingleTransition,
6
7
  Transition,
7
8
  TransitionEndEvent,
8
9
  TransformOrigin,
@@ -29,6 +30,80 @@ const EASING_PRESETS: Record<string, CubicBezier> = {
29
30
  easeInOut: [0.42, 0, 0.58, 1],
30
31
  };
31
32
 
33
+ // ---------------------------------------------------------------------------
34
+ // Spring simulation → CSS linear() easing
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /** Simulate a damped spring from 0 → 1 and return settling duration + sample points. */
38
+ function simulateSpring(
39
+ damping: number,
40
+ stiffness: number,
41
+ mass: number,
42
+ ): { durationMs: number; points: number[] } {
43
+ const dt = 1 / 120; // 120 Hz simulation
44
+ const maxTime = 10; // 10s safety cap
45
+ let x = 0;
46
+ let v = 0;
47
+ const samples: number[] = [0];
48
+ let step = 0;
49
+
50
+ while (step * dt < maxTime) {
51
+ const a = (-stiffness * (x - 1) - damping * v) / mass;
52
+ v += a * dt;
53
+ x += v * dt;
54
+ step++;
55
+ // Downsample to ~60 fps (every 2nd sample)
56
+ if (step % 2 === 0) {
57
+ samples.push(Math.round(x * 10000) / 10000);
58
+ }
59
+ // Settled?
60
+ if (Math.abs(x - 1) < 0.001 && Math.abs(v) < 0.001) break;
61
+ }
62
+
63
+ // Ensure last point is exactly 1
64
+ samples[samples.length - 1] = 1;
65
+
66
+ return {
67
+ durationMs: Math.round(step * dt * 1000),
68
+ points: samples,
69
+ };
70
+ }
71
+
72
+ /** Cache for computed spring easing strings (keyed by damping-stiffness-mass). */
73
+ const springCache = new Map<string, { duration: number; easing: string }>();
74
+
75
+ function getSpringEasing(
76
+ damping: number,
77
+ stiffness: number,
78
+ mass: number,
79
+ ): { duration: number; easing: string } {
80
+ const key = `${damping}-${stiffness}-${mass}`;
81
+ let cached = springCache.get(key);
82
+ if (cached) return cached;
83
+
84
+ const { durationMs, points } = simulateSpring(damping, stiffness, mass);
85
+ const easing = `linear(${points.join(', ')})`;
86
+ cached = { duration: durationMs, easing };
87
+ springCache.set(key, cached);
88
+ return cached;
89
+ }
90
+
91
+ /** Detect CSS linear() support (lazy, cached). */
92
+ let linearSupported: boolean | null = null;
93
+ function supportsLinearEasing(): boolean {
94
+ if (linearSupported != null) return linearSupported;
95
+ try {
96
+ const el = document.createElement('div');
97
+ el.style.transitionTimingFunction = 'linear(0, 1)';
98
+ linearSupported = el.style.transitionTimingFunction !== '';
99
+ } catch {
100
+ linearSupported = false;
101
+ }
102
+ return linearSupported;
103
+ }
104
+
105
+ const SPRING_FALLBACK_EASING = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)';
106
+
32
107
  export type EaseViewProps = {
33
108
  animate?: AnimateProps;
34
109
  initialAnimate?: AnimateProps;
@@ -81,10 +156,91 @@ function buildTransform(vals: ReturnType<typeof resolveAnimateValues>): string {
81
156
  return parts.length > 0 ? parts.join(' ') : 'none';
82
157
  }
83
158
 
84
- function resolveEasing(transition: Transition | undefined): string {
85
- if (!transition || transition.type !== 'timing') {
86
- return 'cubic-bezier(0.42, 0, 0.58, 1)';
159
+ /** Returns true if the transition is a SingleTransition (has a `type` field). */
160
+ function isSingleTransition(t: Transition): t is SingleTransition {
161
+ return 'type' in t;
162
+ }
163
+
164
+ /** Resolve a single config into CSS-ready duration/easing. */
165
+ function resolveConfigForCss(config: SingleTransition | undefined): {
166
+ duration: number;
167
+ easing: string;
168
+ type: string;
169
+ } {
170
+ if (!config || config.type === 'none') {
171
+ return { duration: 0, easing: 'linear', type: config?.type ?? 'timing' };
87
172
  }
173
+ return {
174
+ duration: resolveDuration(config),
175
+ easing: resolveEasing(config),
176
+ type: config.type,
177
+ };
178
+ }
179
+
180
+ /** CSS property names for each category. */
181
+ const CSS_PROP_MAP = {
182
+ opacity: 'opacity',
183
+ transform: 'transform',
184
+ borderRadius: 'border-radius',
185
+ backgroundColor: 'background-color',
186
+ } as const;
187
+
188
+ type CategoryKey = keyof typeof CSS_PROP_MAP;
189
+
190
+ /** Resolve transition prop into per-category CSS configs. */
191
+ function resolvePerCategoryConfigs(
192
+ transition: Transition | undefined,
193
+ ): Record<CategoryKey, { duration: number; easing: string; type: string }> {
194
+ if (!transition) {
195
+ const def = resolveConfigForCss(undefined);
196
+ return {
197
+ opacity: def,
198
+ transform: def,
199
+ borderRadius: def,
200
+ backgroundColor: def,
201
+ };
202
+ }
203
+ if (isSingleTransition(transition)) {
204
+ const def = resolveConfigForCss(transition);
205
+ return {
206
+ opacity: def,
207
+ transform: def,
208
+ borderRadius: def,
209
+ backgroundColor: def,
210
+ };
211
+ }
212
+ // TransitionMap
213
+ const defaultConfig = resolveConfigForCss(transition.default);
214
+ return {
215
+ opacity: transition.opacity
216
+ ? resolveConfigForCss(transition.opacity)
217
+ : defaultConfig,
218
+ transform: transition.transform
219
+ ? resolveConfigForCss(transition.transform)
220
+ : defaultConfig,
221
+ borderRadius: transition.borderRadius
222
+ ? resolveConfigForCss(transition.borderRadius)
223
+ : defaultConfig,
224
+ backgroundColor: transition.backgroundColor
225
+ ? resolveConfigForCss(transition.backgroundColor)
226
+ : defaultConfig,
227
+ };
228
+ }
229
+
230
+ function resolveEasing(transition: SingleTransition | undefined): string {
231
+ if (!transition || transition.type === 'none') {
232
+ return 'linear';
233
+ }
234
+ if (transition.type === 'spring') {
235
+ const d = transition.damping ?? 15;
236
+ const s = transition.stiffness ?? 120;
237
+ const m = transition.mass ?? 1;
238
+ if (supportsLinearEasing()) {
239
+ return getSpringEasing(d, s, m).easing;
240
+ }
241
+ return SPRING_FALLBACK_EASING;
242
+ }
243
+ // timing
88
244
  const easing = transition.easing ?? 'easeInOut';
89
245
  const bezier: CubicBezier = Array.isArray(easing)
90
246
  ? easing
@@ -92,24 +248,17 @@ function resolveEasing(transition: Transition | undefined): string {
92
248
  return `cubic-bezier(${bezier[0]}, ${bezier[1]}, ${bezier[2]}, ${bezier[3]})`;
93
249
  }
94
250
 
95
- function resolveDuration(transition: Transition | undefined): number {
251
+ function resolveDuration(transition: SingleTransition | undefined): number {
96
252
  if (!transition) return 300;
97
253
  if (transition.type === 'timing') return transition.duration ?? 300;
98
254
  if (transition.type === 'none') return 0;
99
- const damping = transition.damping ?? 15;
100
- const mass = transition.mass ?? 1;
101
- const tau = (2 * mass) / damping;
102
- return Math.round(tau * 4 * 1000);
255
+ // Spring: use simulation-derived duration (incorporates stiffness)
256
+ const d = transition.damping ?? 15;
257
+ const s = transition.stiffness ?? 120;
258
+ const m = transition.mass ?? 1;
259
+ return getSpringEasing(d, s, m).duration;
103
260
  }
104
261
 
105
- /** CSS transition properties that we animate. */
106
- const TRANSITION_PROPS = [
107
- 'opacity',
108
- 'transform',
109
- 'border-radius',
110
- 'background-color',
111
- ];
112
-
113
262
  /** Counter for unique keyframe names. */
114
263
  let keyframeCounter = 0;
115
264
 
@@ -146,21 +295,36 @@ export function EaseView({
146
295
  const displayValues =
147
296
  !mounted && hasInitial ? resolveAnimateValues(initialAnimate) : resolved;
148
297
 
149
- const duration = resolveDuration(transition);
150
- const easing = resolveEasing(transition);
298
+ const categoryConfigs = resolvePerCategoryConfigs(transition);
299
+
300
+ // For loop mode, use the default/single transition config
301
+ const singleTransition =
302
+ transition && isSingleTransition(transition)
303
+ ? transition
304
+ : transition && !isSingleTransition(transition)
305
+ ? transition.default
306
+ : undefined;
307
+ const loopMode =
308
+ singleTransition?.type === 'timing' ? singleTransition.loop : undefined;
309
+ const loopDuration = resolveDuration(singleTransition);
310
+ const loopEasing = resolveEasing(singleTransition);
151
311
 
152
312
  const originX = ((transformOrigin?.x ?? 0.5) * 100).toFixed(1);
153
313
  const originY = ((transformOrigin?.y ?? 0.5) * 100).toFixed(1);
154
314
 
155
- const transitionType = transition?.type ?? 'timing';
156
- const loopMode = transition?.type === 'timing' ? transition.loop : undefined;
157
-
158
315
  const transitionCss =
159
- transitionType === 'none' || (!mounted && hasInitial)
316
+ !mounted && hasInitial
160
317
  ? 'none'
161
- : TRANSITION_PROPS.map((prop) => `${prop} ${duration}ms ${easing}`).join(
162
- ', ',
163
- );
318
+ : (Object.keys(CSS_PROP_MAP) as CategoryKey[])
319
+ .filter((key) => {
320
+ const cfg = categoryConfigs[key];
321
+ return cfg.type !== 'none' && cfg.duration > 0;
322
+ })
323
+ .map((key) => {
324
+ const cfg = categoryConfigs[key];
325
+ return `${CSS_PROP_MAP[key]} ${cfg.duration}ms ${cfg.easing}`;
326
+ })
327
+ .join(', ') || 'none';
164
328
 
165
329
  // Apply CSS transition/animation properties imperatively (not in RN style spec).
166
330
  useEffect(() => {
@@ -168,14 +332,7 @@ export function EaseView({
168
332
  if (!el) return;
169
333
 
170
334
  if (!loopMode) {
171
- const springTransition =
172
- transitionType === 'spring'
173
- ? TRANSITION_PROPS.map(
174
- (prop) =>
175
- `${prop} ${duration}ms cubic-bezier(0.25, 0.46, 0.45, 0.94)`,
176
- ).join(', ')
177
- : null;
178
- el.style.transition = springTransition ?? transitionCss;
335
+ el.style.transition = transitionCss;
179
336
  }
180
337
  el.style.transformOrigin = `${originX}% ${originY}%`;
181
338
  });
@@ -257,27 +414,37 @@ export function EaseView({
257
414
 
258
415
  const direction = loopMode === 'reverse' ? 'alternate' : 'normal';
259
416
  el.style.transition = 'none';
260
- el.style.animation = `${name} ${duration}ms ${easing} infinite ${direction}`;
417
+ el.style.animation = `${name} ${loopDuration}ms ${loopEasing} infinite ${direction}`;
261
418
 
262
419
  return () => {
263
420
  styleEl.remove();
264
421
  el.style.animation = '';
265
422
  animationNameRef.current = null;
266
423
  };
267
- }, [loopMode, animate, initialAnimate, duration, easing, getElement]);
424
+ }, [loopMode, animate, initialAnimate, loopDuration, loopEasing, getElement]);
268
425
 
269
426
  // Build animated style using RN transform array format.
270
427
  // react-native-web converts these to CSS transform strings.
271
428
  const animatedStyle: ViewStyle = {
272
429
  opacity: displayValues.opacity,
273
430
  transform: [
274
- { translateX: displayValues.translateX },
275
- { translateY: displayValues.translateY },
276
- { scaleX: displayValues.scaleX },
277
- { scaleY: displayValues.scaleY },
278
- { rotate: `${displayValues.rotate}deg` },
279
- { rotateX: `${displayValues.rotateX}deg` },
280
- { rotateY: `${displayValues.rotateY}deg` },
431
+ ...(displayValues.translateX !== 0
432
+ ? [{ translateX: displayValues.translateX }]
433
+ : []),
434
+ ...(displayValues.translateY !== 0
435
+ ? [{ translateY: displayValues.translateY }]
436
+ : []),
437
+ ...(displayValues.scaleX !== 1 ? [{ scaleX: displayValues.scaleX }] : []),
438
+ ...(displayValues.scaleY !== 1 ? [{ scaleY: displayValues.scaleY }] : []),
439
+ ...(displayValues.rotate !== 0
440
+ ? [{ rotate: `${displayValues.rotate}deg` }]
441
+ : []),
442
+ ...(displayValues.rotateX !== 0
443
+ ? [{ rotateX: `${displayValues.rotateX}deg` }]
444
+ : []),
445
+ ...(displayValues.rotateY !== 0
446
+ ? [{ rotateY: `${displayValues.rotateY}deg` }]
447
+ : []),
281
448
  ],
282
449
  ...(displayValues.borderRadius > 0
283
450
  ? { borderRadius: displayValues.borderRadius }
@@ -6,6 +6,28 @@ import {
6
6
  type ColorValue,
7
7
  } from 'react-native';
8
8
 
9
+ type Float = CodegenTypes.Float;
10
+ type Int32 = CodegenTypes.Int32;
11
+
12
+ type NativeTransitionConfig = Readonly<{
13
+ type: string;
14
+ duration: Int32;
15
+ easingBezier: ReadonlyArray<Float>;
16
+ damping: Float;
17
+ stiffness: Float;
18
+ mass: Float;
19
+ loop: string;
20
+ delay: Int32;
21
+ }>;
22
+
23
+ type NativeTransitions = Readonly<{
24
+ defaultConfig: NativeTransitionConfig;
25
+ transform?: NativeTransitionConfig;
26
+ opacity?: NativeTransitionConfig;
27
+ borderRadius?: NativeTransitionConfig;
28
+ backgroundColor?: NativeTransitionConfig;
29
+ }>;
30
+
9
31
  export interface NativeProps extends ViewProps {
10
32
  // Bitmask of which properties are animated (0 = none, let style handle all)
11
33
  animatedProperties?: CodegenTypes.WithDefault<CodegenTypes.Int32, 0>;
@@ -37,22 +59,8 @@ export interface NativeProps extends ViewProps {
37
59
  >;
38
60
  initialAnimateBackgroundColor?: ColorValue;
39
61
 
40
- // Transition config
41
- transitionType?: CodegenTypes.WithDefault<
42
- 'timing' | 'spring' | 'none',
43
- 'timing'
44
- >;
45
- transitionDuration?: CodegenTypes.WithDefault<CodegenTypes.Int32, 300>;
46
- // Easing cubic bezier control points [x1, y1, x2, y2] (default: easeInOut)
47
- transitionEasingBezier?: ReadonlyArray<CodegenTypes.Float>;
48
- transitionDamping?: CodegenTypes.WithDefault<CodegenTypes.Float, 15.0>;
49
- transitionStiffness?: CodegenTypes.WithDefault<CodegenTypes.Float, 120.0>;
50
- transitionMass?: CodegenTypes.WithDefault<CodegenTypes.Float, 1.0>;
51
- transitionLoop?: CodegenTypes.WithDefault<
52
- 'none' | 'repeat' | 'reverse',
53
- 'none'
54
- >;
55
- transitionDelay?: CodegenTypes.WithDefault<CodegenTypes.Int32, 0>;
62
+ // Unified transition config — one struct with per-property configs
63
+ transitions?: NativeTransitions;
56
64
 
57
65
  // Transform origin (0–1 fractions, default center)
58
66
  transformOriginX?: CodegenTypes.WithDefault<CodegenTypes.Float, 0.5>;
package/src/index.tsx CHANGED
@@ -4,6 +4,8 @@ export type {
4
4
  AnimateProps,
5
5
  CubicBezier,
6
6
  Transition,
7
+ SingleTransition,
8
+ TransitionMap,
7
9
  TimingTransition,
8
10
  SpringTransition,
9
11
  NoneTransition,
package/src/types.ts CHANGED
@@ -42,8 +42,28 @@ export type NoneTransition = {
42
42
  type: 'none';
43
43
  };
44
44
 
45
- /** Animation transition configuration. */
46
- export type Transition = TimingTransition | SpringTransition | NoneTransition;
45
+ /** A single animation transition configuration. */
46
+ export type SingleTransition =
47
+ | TimingTransition
48
+ | SpringTransition
49
+ | NoneTransition;
50
+
51
+ /** Per-property transition map with category-based keys. */
52
+ export type TransitionMap = {
53
+ /** Fallback config for properties not explicitly listed. */
54
+ default?: SingleTransition;
55
+ /** Config for all transform properties (translateX/Y, scaleX/Y, rotate, rotateX/Y). */
56
+ transform?: SingleTransition;
57
+ /** Config for opacity. */
58
+ opacity?: SingleTransition;
59
+ /** Config for borderRadius. */
60
+ borderRadius?: SingleTransition;
61
+ /** Config for backgroundColor. */
62
+ backgroundColor?: SingleTransition;
63
+ };
64
+
65
+ /** Animation transition configuration — either a single config or a per-property map. */
66
+ export type Transition = SingleTransition | TransitionMap;
47
67
 
48
68
  /** Event fired when the animation ends. */
49
69
  export type TransitionEndEvent = {