Skip to content

Commit 20eaa2a

Browse files
committed
feat: refactor transition handling to use category-based keys for improved clarity and flexibility
1 parent 7146763 commit 20eaa2a

7 files changed

Lines changed: 186 additions & 225 deletions

File tree

android/src/main/java/com/ease/EaseView.kt

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class EaseView(context: Context) : ReactViewGroup(context) {
5555
return
5656
}
5757
val configs = mutableMapOf<String, TransitionConfig>()
58-
val keys = listOf("defaultConfig", "opacity", "translateX", "translateY", "scaleX", "scaleY", "rotate", "rotateX", "rotateY", "borderRadius", "backgroundColor")
58+
val keys = listOf("defaultConfig", "transform", "opacity", "borderRadius", "backgroundColor")
5959
for (key in keys) {
6060
if (map.hasKey(key)) {
6161
val configMap = map.getMap(key) ?: continue
@@ -85,15 +85,31 @@ class EaseView(context: Context) : ReactViewGroup(context) {
8585
transitionConfigs = configs
8686
}
8787

88+
/** Map property name to category key, then fall back to defaultConfig. */
8889
fun getTransitionConfig(name: String): TransitionConfig {
89-
return transitionConfigs[name]
90-
?: transitionConfigs["defaultConfig"]
90+
val categoryKey = when (name) {
91+
"opacity" -> "opacity"
92+
"translateX", "translateY", "scaleX", "scaleY",
93+
"rotate", "rotateX", "rotateY" -> "transform"
94+
"borderRadius" -> "borderRadius"
95+
"backgroundColor" -> "backgroundColor"
96+
else -> null
97+
}
98+
if (categoryKey != null) {
99+
transitionConfigs[categoryKey]?.let { return it }
100+
}
101+
return transitionConfigs["defaultConfig"]
91102
?: TransitionConfig("timing", 300, floatArrayOf(0.42f, 0f, 0.58f, 1.0f), 15.0f, 120.0f, 1.0f, "none", 0L)
92103
}
93104

94105
private fun allTransitionsNone(): Boolean {
95-
val keys = listOf("opacity", "translateX", "translateY", "scaleX", "scaleY", "rotate", "rotateX", "rotateY", "borderRadius", "backgroundColor")
96-
return keys.all { getTransitionConfig(it).type == "none" }
106+
val defaultConfig = transitionConfigs["defaultConfig"]
107+
if (defaultConfig == null || defaultConfig.type != "none") return false
108+
val categories = listOf("transform", "opacity", "borderRadius", "backgroundColor")
109+
return categories.all { key ->
110+
val config = transitionConfigs[key]
111+
config == null || config.type == "none"
112+
}
97113
}
98114

99115
companion object {

example/src/demos/PerPropertyDemo.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export function PerPropertyDemo() {
1010
return (
1111
<Section title="Per-Property Transitions">
1212
<Text style={styles.hint}>
13-
Opacity fades slowly (1.5s timing), translateX slides with a bouncy
14-
spring — each property animates independently
13+
Opacity fades slowly (1.5s timing), transforms slide with a bouncy
14+
spring — each category animates independently
1515
</Text>
1616
<EaseView
1717
animate={{
@@ -20,7 +20,7 @@ export function PerPropertyDemo() {
2020
}}
2121
transition={{
2222
opacity: { type: 'timing', duration: 1500, easing: 'easeInOut' },
23-
translateX: { type: 'spring', damping: 8, stiffness: 120, mass: 1 },
23+
transform: { type: 'spring', damping: 8, stiffness: 120, mass: 1 },
2424
}}
2525
style={styles.box}
2626
/>

ios/EaseView.mm

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -97,32 +97,32 @@ static EaseTransitionConfig transitionConfigFromStruct(const T &src) {
9797
return config;
9898
}
9999

100+
// Check if a category config was explicitly set (non-empty type means JS sent
101+
// it)
102+
template <typename T> static bool hasConfig(const T &cfg) {
103+
return !cfg.type.empty();
104+
}
105+
100106
static EaseTransitionConfig
101107
transitionConfigForProperty(const std::string &name,
102108
const EaseViewProps &props) {
103-
if (name == "opacity") {
104-
return transitionConfigFromStruct(props.transitions.opacity);
105-
} else if (name == "translateX") {
106-
return transitionConfigFromStruct(props.transitions.translateX);
107-
} else if (name == "translateY") {
108-
return transitionConfigFromStruct(props.transitions.translateY);
109-
} else if (name == "scaleX") {
110-
return transitionConfigFromStruct(props.transitions.scaleX);
111-
} else if (name == "scaleY") {
112-
return transitionConfigFromStruct(props.transitions.scaleY);
113-
} else if (name == "rotate") {
114-
return transitionConfigFromStruct(props.transitions.rotate);
115-
} else if (name == "rotateX") {
116-
return transitionConfigFromStruct(props.transitions.rotateX);
117-
} else if (name == "rotateY") {
118-
return transitionConfigFromStruct(props.transitions.rotateY);
119-
} else if (name == "borderRadius") {
120-
return transitionConfigFromStruct(props.transitions.borderRadius);
121-
} else if (name == "backgroundColor") {
122-
return transitionConfigFromStruct(props.transitions.backgroundColor);
109+
const auto &t = props.transitions;
110+
111+
// Map property name to category, check if category override exists
112+
if (name == "opacity" && hasConfig(t.opacity)) {
113+
return transitionConfigFromStruct(t.opacity);
114+
} else if ((name == "translateX" || name == "translateY" ||
115+
name == "scaleX" || name == "scaleY" || name == "rotate" ||
116+
name == "rotateX" || name == "rotateY") &&
117+
hasConfig(t.transform)) {
118+
return transitionConfigFromStruct(t.transform);
119+
} else if (name == "borderRadius" && hasConfig(t.borderRadius)) {
120+
return transitionConfigFromStruct(t.borderRadius);
121+
} else if (name == "backgroundColor" && hasConfig(t.backgroundColor)) {
122+
return transitionConfigFromStruct(t.backgroundColor);
123123
}
124124
// Fallback to defaultConfig
125-
return transitionConfigFromStruct(props.transitions.defaultConfig);
125+
return transitionConfigFromStruct(t.defaultConfig);
126126
}
127127

128128
// Find lowest property name with a set mask bit among transform properties
@@ -487,16 +487,15 @@ - (void)updateProps:(const Props::Shared &)props
487487
RCTUIColorFromSharedColor(newViewProps.animateBackgroundColor)
488488
.CGColor;
489489
}
490-
} else if (newViewProps.transitions.opacity.type == "none" &&
491-
newViewProps.transitions.translateX.type == "none" &&
492-
newViewProps.transitions.translateY.type == "none" &&
493-
newViewProps.transitions.scaleX.type == "none" &&
494-
newViewProps.transitions.scaleY.type == "none" &&
495-
newViewProps.transitions.rotate.type == "none" &&
496-
newViewProps.transitions.rotateX.type == "none" &&
497-
newViewProps.transitions.rotateY.type == "none" &&
498-
newViewProps.transitions.borderRadius.type == "none" &&
499-
newViewProps.transitions.backgroundColor.type == "none") {
490+
} else if (newViewProps.transitions.defaultConfig.type == "none" &&
491+
(!hasConfig(newViewProps.transitions.transform) ||
492+
newViewProps.transitions.transform.type == "none") &&
493+
(!hasConfig(newViewProps.transitions.opacity) ||
494+
newViewProps.transitions.opacity.type == "none") &&
495+
(!hasConfig(newViewProps.transitions.borderRadius) ||
496+
newViewProps.transitions.borderRadius.type == "none") &&
497+
(!hasConfig(newViewProps.transitions.backgroundColor) ||
498+
newViewProps.transitions.backgroundColor.type == "none")) {
500499
// All transitions are 'none' — set values immediately
501500
[self.layer removeAllAnimations];
502501
if (mask & kMaskOpacity)

src/EaseView.tsx

Lines changed: 35 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -59,33 +59,6 @@ const EASING_PRESETS: Record<string, CubicBezier> = {
5959
easeInOut: [0.42, 0, 0.58, 1],
6060
};
6161

62-
/** Transition struct field names (matches NativeTransitions keys). */
63-
const TRANSITION_KEYS = [
64-
'opacity',
65-
'translateX',
66-
'translateY',
67-
'scaleX',
68-
'scaleY',
69-
'rotate',
70-
'rotateX',
71-
'rotateY',
72-
'borderRadius',
73-
'backgroundColor',
74-
] as const;
75-
76-
type TransitionKey = (typeof TRANSITION_KEYS)[number];
77-
78-
/** Transform property keys (indices 1-7) get spring defaults; others get timing. */
79-
const TRANSFORM_KEYS = new Set<TransitionKey>([
80-
'translateX',
81-
'translateY',
82-
'scaleX',
83-
'scaleY',
84-
'rotate',
85-
'rotateX',
86-
'rotateY',
87-
]);
88-
8962
/** Returns true if the transition is a SingleTransition (has a `type` field). */
9063
function isSingleTransition(t: Transition): t is SingleTransition {
9164
return 'type' in t;
@@ -106,20 +79,14 @@ type NativeTransitionConfig = {
10679
/** Full transitions struct passed to native. */
10780
type NativeTransitions = {
10881
defaultConfig: NativeTransitionConfig;
109-
opacity: NativeTransitionConfig;
110-
translateX: NativeTransitionConfig;
111-
translateY: NativeTransitionConfig;
112-
scaleX: NativeTransitionConfig;
113-
scaleY: NativeTransitionConfig;
114-
rotate: NativeTransitionConfig;
115-
rotateX: NativeTransitionConfig;
116-
rotateY: NativeTransitionConfig;
117-
borderRadius: NativeTransitionConfig;
118-
backgroundColor: NativeTransitionConfig;
82+
transform?: NativeTransitionConfig;
83+
opacity?: NativeTransitionConfig;
84+
borderRadius?: NativeTransitionConfig;
85+
backgroundColor?: NativeTransitionConfig;
11986
};
12087

121-
/** Library defaults: spring for transforms, timing 300ms easeInOut for others. */
122-
const TIMING_DEFAULT_CONFIG: NativeTransitionConfig = {
88+
/** Default config: timing 300ms easeInOut. */
89+
const DEFAULT_CONFIG: NativeTransitionConfig = {
12390
type: 'timing',
12491
duration: 300,
12592
easingBezier: [0.42, 0, 0.58, 1],
@@ -130,6 +97,7 @@ const TIMING_DEFAULT_CONFIG: NativeTransitionConfig = {
13097
delay: 0,
13198
};
13299

100+
/** Default config for transform properties: spring. */
133101
const SPRING_DEFAULT_CONFIG: NativeTransitionConfig = {
134102
type: 'spring',
135103
duration: 300,
@@ -141,12 +109,6 @@ const SPRING_DEFAULT_CONFIG: NativeTransitionConfig = {
141109
delay: 0,
142110
};
143111

144-
function getLibraryDefault(key: TransitionKey): NativeTransitionConfig {
145-
return TRANSFORM_KEYS.has(key)
146-
? SPRING_DEFAULT_CONFIG
147-
: TIMING_DEFAULT_CONFIG;
148-
}
149-
150112
/** Resolve a SingleTransition into a native config object. */
151113
function resolveSingleConfig(config: SingleTransition): NativeTransitionConfig {
152114
const type = config.type as string;
@@ -198,63 +160,47 @@ function resolveSingleConfig(config: SingleTransition): NativeTransitionConfig {
198160
};
199161
}
200162

201-
/** Resolve the transition prop into a fully-populated NativeTransitions struct. */
163+
/** Category keys that map to optional NativeTransitions fields. */
164+
const CATEGORY_KEYS = [
165+
'transform',
166+
'opacity',
167+
'borderRadius',
168+
'backgroundColor',
169+
] as const;
170+
171+
/** Resolve the transition prop into a NativeTransitions struct. */
202172
function resolveTransitions(transition?: Transition): NativeTransitions {
203-
// Single transition: apply the same config to all 11 slots
173+
// Single transition: set as defaultConfig, no category overrides needed
204174
if (transition != null && isSingleTransition(transition)) {
205-
const config = resolveSingleConfig(transition);
206-
const result = { defaultConfig: config } as Record<
207-
string,
208-
NativeTransitionConfig
209-
>;
210-
for (const key of TRANSITION_KEYS) {
211-
result[key] = config;
212-
}
213-
return result as unknown as NativeTransitions;
175+
return { defaultConfig: resolveSingleConfig(transition) };
214176
}
215177

216-
// No transition: use library defaults per category
178+
// No transition: timing default + spring for transforms
217179
if (transition == null) {
218-
const result = { defaultConfig: TIMING_DEFAULT_CONFIG } as Record<
219-
string,
220-
NativeTransitionConfig
221-
>;
222-
for (const key of TRANSITION_KEYS) {
223-
result[key] = getLibraryDefault(key);
224-
}
225-
return result as unknown as NativeTransitions;
180+
return { defaultConfig: DEFAULT_CONFIG, transform: SPRING_DEFAULT_CONFIG };
226181
}
227182

228-
// TransitionMap: resolve per property
229-
const transitionMap = transition;
230-
const defaultSingle = transitionMap.default;
231-
const defaultNative = defaultSingle
232-
? resolveSingleConfig(defaultSingle)
233-
: undefined;
183+
// TransitionMap: resolve defaultConfig + only specified category keys
184+
const defaultConfig = transition.default
185+
? resolveSingleConfig(transition.default)
186+
: DEFAULT_CONFIG;
234187

235-
const result = {
236-
defaultConfig: defaultNative ?? TIMING_DEFAULT_CONFIG,
237-
} as Record<string, NativeTransitionConfig>;
188+
const result: NativeTransitions = { defaultConfig };
238189

239-
for (const key of TRANSITION_KEYS) {
240-
const specific = transitionMap[key as keyof typeof transitionMap] as
241-
| SingleTransition
242-
| undefined;
190+
for (const key of CATEGORY_KEYS) {
191+
const specific = transition[key];
243192
if (specific != null) {
244-
result[key] = resolveSingleConfig(specific);
245-
} else if (
246-
(key === 'scaleX' || key === 'scaleY') &&
247-
transitionMap.scale != null
248-
) {
249-
result[key] = resolveSingleConfig(transitionMap.scale!);
250-
} else if (defaultNative) {
251-
result[key] = defaultNative;
252-
} else {
253-
result[key] = getLibraryDefault(key);
193+
(result as Record<string, NativeTransitionConfig>)[key] =
194+
resolveSingleConfig(specific);
254195
}
255196
}
256197

257-
return result as unknown as NativeTransitions;
198+
// Preserve spring default for transforms when not explicitly set
199+
if (result.transform == null && transition.default == null) {
200+
result.transform = SPRING_DEFAULT_CONFIG;
201+
}
202+
203+
return result;
258204
}
259205

260206
export type EaseViewProps = ViewProps & {

src/EaseViewNativeComponent.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,10 @@ type NativeTransitionConfig = Readonly<{
2222

2323
type NativeTransitions = Readonly<{
2424
defaultConfig: NativeTransitionConfig;
25-
opacity: NativeTransitionConfig;
26-
translateX: NativeTransitionConfig;
27-
translateY: NativeTransitionConfig;
28-
scaleX: NativeTransitionConfig;
29-
scaleY: NativeTransitionConfig;
30-
rotate: NativeTransitionConfig;
31-
rotateX: NativeTransitionConfig;
32-
rotateY: NativeTransitionConfig;
33-
borderRadius: NativeTransitionConfig;
34-
backgroundColor: NativeTransitionConfig;
25+
transform?: NativeTransitionConfig;
26+
opacity?: NativeTransitionConfig;
27+
borderRadius?: NativeTransitionConfig;
28+
backgroundColor?: NativeTransitionConfig;
3529
}>;
3630

3731
export interface NativeProps extends ViewProps {

0 commit comments

Comments
 (0)