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/README.md +116 -0
- package/android/src/main/java/com/ease/EaseView.kt +261 -82
- package/android/src/main/java/com/ease/EaseViewManager.kt +5 -49
- package/ios/EaseView.mm +275 -79
- package/lib/module/EaseView.js +85 -28
- package/lib/module/EaseView.js.map +1 -1
- package/lib/module/EaseView.web.js +167 -26
- package/lib/module/EaseView.web.js.map +1 -1
- package/lib/module/EaseViewNativeComponent.ts +24 -16
- package/lib/typescript/src/EaseView.d.ts +2 -0
- package/lib/typescript/src/EaseView.d.ts.map +1 -1
- package/lib/typescript/src/EaseView.web.d.ts.map +1 -1
- package/lib/typescript/src/EaseViewNativeComponent.d.ts +20 -8
- package/lib/typescript/src/EaseViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +17 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/react-native-ease-refactor/SKILL.md +8 -2
- package/src/EaseView.tsx +116 -53
- package/src/EaseView.web.tsx +209 -42
- package/src/EaseViewNativeComponent.ts +24 -16
- package/src/index.tsx +2 -0
- package/src/types.ts +22 -2
|
@@ -3,6 +3,7 @@ package com.ease
|
|
|
3
3
|
import android.graphics.Color
|
|
4
4
|
import com.facebook.react.bridge.Arguments
|
|
5
5
|
import com.facebook.react.bridge.ReadableArray
|
|
6
|
+
import com.facebook.react.bridge.ReadableMap
|
|
6
7
|
import com.facebook.react.bridge.WritableMap
|
|
7
8
|
import com.facebook.react.module.annotations.ReactModule
|
|
8
9
|
import com.facebook.react.uimanager.PixelUtil
|
|
@@ -126,56 +127,11 @@ class EaseViewManager : ReactViewManager() {
|
|
|
126
127
|
view.initialAnimateBorderRadius = PixelUtil.toPixelFromDIP(value)
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
// ---
|
|
130
|
+
// --- Transitions config (single ReadableMap) ---
|
|
130
131
|
|
|
131
|
-
@ReactProp(name = "
|
|
132
|
-
fun
|
|
133
|
-
view.
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
@ReactProp(name = "transitionDuration", defaultInt = 300)
|
|
137
|
-
fun setTransitionDuration(view: EaseView, value: Int) {
|
|
138
|
-
view.transitionDuration = value
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
@ReactProp(name = "transitionEasingBezier")
|
|
142
|
-
fun setTransitionEasingBezier(view: EaseView, value: ReadableArray?) {
|
|
143
|
-
if (value != null && value.size() == 4) {
|
|
144
|
-
view.transitionEasingBezier = floatArrayOf(
|
|
145
|
-
value.getDouble(0).toFloat(),
|
|
146
|
-
value.getDouble(1).toFloat(),
|
|
147
|
-
value.getDouble(2).toFloat(),
|
|
148
|
-
value.getDouble(3).toFloat()
|
|
149
|
-
)
|
|
150
|
-
} else {
|
|
151
|
-
// Fallback: easeInOut
|
|
152
|
-
view.transitionEasingBezier = floatArrayOf(0.42f, 0f, 0.58f, 1.0f)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
@ReactProp(name = "transitionDamping", defaultFloat = 15f)
|
|
157
|
-
fun setTransitionDamping(view: EaseView, value: Float) {
|
|
158
|
-
view.transitionDamping = value
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
@ReactProp(name = "transitionStiffness", defaultFloat = 120f)
|
|
162
|
-
fun setTransitionStiffness(view: EaseView, value: Float) {
|
|
163
|
-
view.transitionStiffness = value
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
@ReactProp(name = "transitionMass", defaultFloat = 1f)
|
|
167
|
-
fun setTransitionMass(view: EaseView, value: Float) {
|
|
168
|
-
view.transitionMass = value
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
@ReactProp(name = "transitionLoop")
|
|
172
|
-
fun setTransitionLoop(view: EaseView, value: String?) {
|
|
173
|
-
view.transitionLoop = value ?: "none"
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
@ReactProp(name = "transitionDelay", defaultInt = 0)
|
|
177
|
-
fun setTransitionDelay(view: EaseView, value: Int) {
|
|
178
|
-
view.transitionDelay = value.toLong()
|
|
132
|
+
@ReactProp(name = "transitions")
|
|
133
|
+
fun setTransitions(view: EaseView, value: ReadableMap?) {
|
|
134
|
+
view.setTransitionsFromMap(value)
|
|
179
135
|
}
|
|
180
136
|
|
|
181
137
|
// --- Border radius ---
|
package/ios/EaseView.mm
CHANGED
|
@@ -58,6 +58,92 @@ static const int kMaskAnyTransform = kMaskTranslateX | kMaskTranslateY |
|
|
|
58
58
|
kMaskScaleX | kMaskScaleY | kMaskRotate |
|
|
59
59
|
kMaskRotateX | kMaskRotateY;
|
|
60
60
|
|
|
61
|
+
// Per-property transition config resolved from the transitions struct
|
|
62
|
+
struct EaseTransitionConfig {
|
|
63
|
+
std::string type;
|
|
64
|
+
int duration;
|
|
65
|
+
float bezier[4];
|
|
66
|
+
float damping;
|
|
67
|
+
float stiffness;
|
|
68
|
+
float mass;
|
|
69
|
+
std::string loop;
|
|
70
|
+
int delay;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Convert from a codegen-generated transition config struct to our local
|
|
74
|
+
// EaseTransitionConfig
|
|
75
|
+
template <typename T>
|
|
76
|
+
static EaseTransitionConfig transitionConfigFromStruct(const T &src) {
|
|
77
|
+
EaseTransitionConfig config;
|
|
78
|
+
config.type = src.type;
|
|
79
|
+
config.duration = src.duration;
|
|
80
|
+
const auto &b = src.easingBezier;
|
|
81
|
+
if (b.size() == 4) {
|
|
82
|
+
config.bezier[0] = b[0];
|
|
83
|
+
config.bezier[1] = b[1];
|
|
84
|
+
config.bezier[2] = b[2];
|
|
85
|
+
config.bezier[3] = b[3];
|
|
86
|
+
} else {
|
|
87
|
+
config.bezier[0] = 0.42f;
|
|
88
|
+
config.bezier[1] = 0.0f;
|
|
89
|
+
config.bezier[2] = 0.58f;
|
|
90
|
+
config.bezier[3] = 1.0f;
|
|
91
|
+
}
|
|
92
|
+
config.damping = src.damping;
|
|
93
|
+
config.stiffness = src.stiffness;
|
|
94
|
+
config.mass = src.mass;
|
|
95
|
+
config.loop = src.loop;
|
|
96
|
+
config.delay = src.delay;
|
|
97
|
+
return config;
|
|
98
|
+
}
|
|
99
|
+
|
|
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
|
+
|
|
106
|
+
static EaseTransitionConfig
|
|
107
|
+
transitionConfigForProperty(const std::string &name,
|
|
108
|
+
const EaseViewProps &props) {
|
|
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);
|
|
123
|
+
}
|
|
124
|
+
// Fallback to defaultConfig
|
|
125
|
+
return transitionConfigFromStruct(t.defaultConfig);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Find lowest property name with a set mask bit among transform properties
|
|
129
|
+
static std::string lowestTransformPropertyName(int mask) {
|
|
130
|
+
if (mask & kMaskTranslateX)
|
|
131
|
+
return "translateX";
|
|
132
|
+
if (mask & kMaskTranslateY)
|
|
133
|
+
return "translateY";
|
|
134
|
+
if (mask & kMaskScaleX)
|
|
135
|
+
return "scaleX";
|
|
136
|
+
if (mask & kMaskScaleY)
|
|
137
|
+
return "scaleY";
|
|
138
|
+
if (mask & kMaskRotate)
|
|
139
|
+
return "rotate";
|
|
140
|
+
if (mask & kMaskRotateX)
|
|
141
|
+
return "rotateX";
|
|
142
|
+
if (mask & kMaskRotateY)
|
|
143
|
+
return "rotateY";
|
|
144
|
+
return "translateX"; // fallback
|
|
145
|
+
}
|
|
146
|
+
|
|
61
147
|
@implementation EaseView {
|
|
62
148
|
BOOL _isFirstMount;
|
|
63
149
|
NSInteger _animationBatchId;
|
|
@@ -139,16 +225,16 @@ static const int kMaskAnyTransform = kMaskTranslateX | kMaskTranslateY |
|
|
|
139
225
|
- (CAAnimation *)createAnimationForKeyPath:(NSString *)keyPath
|
|
140
226
|
fromValue:(NSValue *)fromValue
|
|
141
227
|
toValue:(NSValue *)toValue
|
|
142
|
-
|
|
228
|
+
config:(EaseTransitionConfig)config
|
|
143
229
|
loop:(BOOL)loop {
|
|
144
|
-
if (
|
|
230
|
+
if (config.type == "spring") {
|
|
145
231
|
CASpringAnimation *spring =
|
|
146
232
|
[CASpringAnimation animationWithKeyPath:keyPath];
|
|
147
233
|
spring.fromValue = fromValue;
|
|
148
234
|
spring.toValue = toValue;
|
|
149
|
-
spring.damping =
|
|
150
|
-
spring.stiffness =
|
|
151
|
-
spring.mass =
|
|
235
|
+
spring.damping = config.damping;
|
|
236
|
+
spring.stiffness = config.stiffness;
|
|
237
|
+
spring.mass = config.mass;
|
|
152
238
|
spring.initialVelocity = 0;
|
|
153
239
|
spring.duration = spring.settlingDuration;
|
|
154
240
|
return spring;
|
|
@@ -156,23 +242,14 @@ static const int kMaskAnyTransform = kMaskTranslateX | kMaskTranslateY |
|
|
|
156
242
|
CABasicAnimation *timing = [CABasicAnimation animationWithKeyPath:keyPath];
|
|
157
243
|
timing.fromValue = fromValue;
|
|
158
244
|
timing.toValue = toValue;
|
|
159
|
-
timing.duration =
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
timing.timingFunction = [CAMediaTimingFunction
|
|
164
|
-
functionWithControlPoints:(float)b[0]:(float)b[1]:(float)b[2
|
|
165
|
-
]:(float)b[3]];
|
|
166
|
-
} else {
|
|
167
|
-
// Fallback: easeInOut
|
|
168
|
-
timing.timingFunction =
|
|
169
|
-
[CAMediaTimingFunction functionWithControlPoints:0.42:0.0:0.58:1.0];
|
|
170
|
-
}
|
|
171
|
-
}
|
|
245
|
+
timing.duration = config.duration / 1000.0;
|
|
246
|
+
timing.timingFunction = [CAMediaTimingFunction
|
|
247
|
+
functionWithControlPoints:config.bezier[0]:config.bezier[1
|
|
248
|
+
]:config.bezier[2]:config.bezier[3]];
|
|
172
249
|
if (loop) {
|
|
173
|
-
if (
|
|
250
|
+
if (config.loop == "repeat") {
|
|
174
251
|
timing.repeatCount = HUGE_VALF;
|
|
175
|
-
} else if (
|
|
252
|
+
} else if (config.loop == "reverse") {
|
|
176
253
|
timing.repeatCount = HUGE_VALF;
|
|
177
254
|
timing.autoreverses = YES;
|
|
178
255
|
}
|
|
@@ -185,18 +262,17 @@ static const int kMaskAnyTransform = kMaskTranslateX | kMaskTranslateY |
|
|
|
185
262
|
animationKey:(NSString *)animationKey
|
|
186
263
|
fromValue:(NSValue *)fromValue
|
|
187
264
|
toValue:(NSValue *)toValue
|
|
188
|
-
|
|
265
|
+
config:(EaseTransitionConfig)config
|
|
189
266
|
loop:(BOOL)loop {
|
|
190
267
|
_pendingAnimationCount++;
|
|
191
268
|
|
|
192
269
|
CAAnimation *animation = [self createAnimationForKeyPath:keyPath
|
|
193
270
|
fromValue:fromValue
|
|
194
271
|
toValue:toValue
|
|
195
|
-
|
|
272
|
+
config:config
|
|
196
273
|
loop:loop];
|
|
197
|
-
if (
|
|
198
|
-
animation.beginTime =
|
|
199
|
-
CACurrentMediaTime() + (props.transitionDelay / 1000.0);
|
|
274
|
+
if (config.delay > 0) {
|
|
275
|
+
animation.beginTime = CACurrentMediaTime() + (config.delay / 1000.0);
|
|
200
276
|
animation.fillMode = kCAFillModeBackwards;
|
|
201
277
|
}
|
|
202
278
|
[animation setValue:@(_animationBatchId) forKey:@"easeBatchId"];
|
|
@@ -229,6 +305,10 @@ static const int kMaskAnyTransform = kMaskTranslateX | kMaskTranslateY |
|
|
|
229
305
|
const auto &newViewProps =
|
|
230
306
|
*std::static_pointer_cast<const EaseViewProps>(props);
|
|
231
307
|
|
|
308
|
+
// oldProps can be null. Fall back to props so the diff is a no-op.
|
|
309
|
+
const auto &oldViewProps = *std::static_pointer_cast<const EaseViewProps>(
|
|
310
|
+
oldProps ? oldProps : props);
|
|
311
|
+
|
|
232
312
|
[super updateProps:props oldProps:oldProps];
|
|
233
313
|
|
|
234
314
|
[CATransaction begin];
|
|
@@ -303,40 +383,77 @@ static const int kMaskAnyTransform = kMaskTranslateX | kMaskTranslateY |
|
|
|
303
383
|
newViewProps.initialAnimateBackgroundColor)
|
|
304
384
|
.CGColor;
|
|
305
385
|
|
|
306
|
-
// Animate from initial to target
|
|
386
|
+
// Animate from initial to target (skip if config is 'none')
|
|
307
387
|
if (hasInitialOpacity) {
|
|
388
|
+
EaseTransitionConfig opacityConfig =
|
|
389
|
+
transitionConfigForProperty("opacity", newViewProps);
|
|
308
390
|
self.layer.opacity = newViewProps.animateOpacity;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
391
|
+
if (opacityConfig.type != "none") {
|
|
392
|
+
[self applyAnimationForKeyPath:@"opacity"
|
|
393
|
+
animationKey:kAnimKeyOpacity
|
|
394
|
+
fromValue:@(newViewProps.initialAnimateOpacity)
|
|
395
|
+
toValue:@(newViewProps.animateOpacity)
|
|
396
|
+
config:opacityConfig
|
|
397
|
+
loop:YES];
|
|
398
|
+
}
|
|
315
399
|
}
|
|
316
400
|
if (hasInitialTransform) {
|
|
401
|
+
// Build mask of which transform sub-properties actually changed
|
|
402
|
+
int changedInitTransform = 0;
|
|
403
|
+
if (newViewProps.initialAnimateTranslateX !=
|
|
404
|
+
newViewProps.animateTranslateX)
|
|
405
|
+
changedInitTransform |= kMaskTranslateX;
|
|
406
|
+
if (newViewProps.initialAnimateTranslateY !=
|
|
407
|
+
newViewProps.animateTranslateY)
|
|
408
|
+
changedInitTransform |= kMaskTranslateY;
|
|
409
|
+
if (newViewProps.initialAnimateScaleX != newViewProps.animateScaleX)
|
|
410
|
+
changedInitTransform |= kMaskScaleX;
|
|
411
|
+
if (newViewProps.initialAnimateScaleY != newViewProps.animateScaleY)
|
|
412
|
+
changedInitTransform |= kMaskScaleY;
|
|
413
|
+
if (newViewProps.initialAnimateRotate != newViewProps.animateRotate)
|
|
414
|
+
changedInitTransform |= kMaskRotate;
|
|
415
|
+
if (newViewProps.initialAnimateRotateX != newViewProps.animateRotateX)
|
|
416
|
+
changedInitTransform |= kMaskRotateX;
|
|
417
|
+
if (newViewProps.initialAnimateRotateY != newViewProps.animateRotateY)
|
|
418
|
+
changedInitTransform |= kMaskRotateY;
|
|
419
|
+
std::string transformName =
|
|
420
|
+
lowestTransformPropertyName(changedInitTransform);
|
|
421
|
+
EaseTransitionConfig transformConfig =
|
|
422
|
+
transitionConfigForProperty(transformName, newViewProps);
|
|
317
423
|
self.layer.transform = targetT;
|
|
318
|
-
|
|
424
|
+
if (transformConfig.type != "none") {
|
|
425
|
+
[self
|
|
426
|
+
applyAnimationForKeyPath:@"transform"
|
|
319
427
|
animationKey:kAnimKeyTransform
|
|
320
428
|
fromValue:[NSValue valueWithCATransform3D:initialT]
|
|
321
429
|
toValue:[NSValue valueWithCATransform3D:targetT]
|
|
322
|
-
|
|
430
|
+
config:transformConfig
|
|
323
431
|
loop:YES];
|
|
432
|
+
}
|
|
324
433
|
}
|
|
325
434
|
if (hasInitialBorderRadius) {
|
|
435
|
+
EaseTransitionConfig brConfig =
|
|
436
|
+
transitionConfigForProperty("borderRadius", newViewProps);
|
|
326
437
|
self.layer.cornerRadius = newViewProps.animateBorderRadius;
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
438
|
+
if (brConfig.type != "none") {
|
|
439
|
+
[self applyAnimationForKeyPath:@"cornerRadius"
|
|
440
|
+
animationKey:kAnimKeyCornerRadius
|
|
441
|
+
fromValue:@(newViewProps
|
|
442
|
+
.initialAnimateBorderRadius)
|
|
443
|
+
toValue:@(newViewProps.animateBorderRadius)
|
|
444
|
+
config:brConfig
|
|
445
|
+
loop:YES];
|
|
446
|
+
}
|
|
334
447
|
}
|
|
335
448
|
if (hasInitialBackgroundColor) {
|
|
449
|
+
EaseTransitionConfig bgConfig =
|
|
450
|
+
transitionConfigForProperty("backgroundColor", newViewProps);
|
|
336
451
|
self.layer.backgroundColor =
|
|
337
452
|
RCTUIColorFromSharedColor(newViewProps.animateBackgroundColor)
|
|
338
453
|
.CGColor;
|
|
339
|
-
|
|
454
|
+
if (bgConfig.type != "none") {
|
|
455
|
+
[self
|
|
456
|
+
applyAnimationForKeyPath:@"backgroundColor"
|
|
340
457
|
animationKey:kAnimKeyBackgroundColor
|
|
341
458
|
fromValue:(__bridge id)RCTUIColorFromSharedColor(
|
|
342
459
|
newViewProps
|
|
@@ -345,8 +462,19 @@ static const int kMaskAnyTransform = kMaskTranslateX | kMaskTranslateY |
|
|
|
345
462
|
toValue:(__bridge id)RCTUIColorFromSharedColor(
|
|
346
463
|
newViewProps.animateBackgroundColor)
|
|
347
464
|
.CGColor
|
|
348
|
-
|
|
465
|
+
config:bgConfig
|
|
349
466
|
loop:YES];
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// If all per-property configs were 'none', no animations were queued.
|
|
471
|
+
// Fire onTransitionEnd immediately to match the scalar 'none' contract.
|
|
472
|
+
if (_pendingAnimationCount == 0 && _eventEmitter) {
|
|
473
|
+
auto emitter =
|
|
474
|
+
std::static_pointer_cast<const EaseViewEventEmitter>(_eventEmitter);
|
|
475
|
+
emitter->onTransitionEnd(EaseViewEventEmitter::OnTransitionEnd{
|
|
476
|
+
.finished = true,
|
|
477
|
+
});
|
|
350
478
|
}
|
|
351
479
|
} else {
|
|
352
480
|
// No initial animation — set target values directly
|
|
@@ -363,8 +491,16 @@ static const int kMaskAnyTransform = kMaskTranslateX | kMaskTranslateY |
|
|
|
363
491
|
RCTUIColorFromSharedColor(newViewProps.animateBackgroundColor)
|
|
364
492
|
.CGColor;
|
|
365
493
|
}
|
|
366
|
-
} else if (newViewProps.
|
|
367
|
-
|
|
494
|
+
} else if (newViewProps.transitions.defaultConfig.type == "none" &&
|
|
495
|
+
(!hasConfig(newViewProps.transitions.transform) ||
|
|
496
|
+
newViewProps.transitions.transform.type == "none") &&
|
|
497
|
+
(!hasConfig(newViewProps.transitions.opacity) ||
|
|
498
|
+
newViewProps.transitions.opacity.type == "none") &&
|
|
499
|
+
(!hasConfig(newViewProps.transitions.borderRadius) ||
|
|
500
|
+
newViewProps.transitions.borderRadius.type == "none") &&
|
|
501
|
+
(!hasConfig(newViewProps.transitions.backgroundColor) ||
|
|
502
|
+
newViewProps.transitions.backgroundColor.type == "none")) {
|
|
503
|
+
// All transitions are 'none' — set values immediately
|
|
368
504
|
[self.layer removeAllAnimations];
|
|
369
505
|
if (mask & kMaskOpacity)
|
|
370
506
|
self.layer.opacity = newViewProps.animateOpacity;
|
|
@@ -387,19 +523,27 @@ static const int kMaskAnyTransform = kMaskTranslateX | kMaskTranslateY |
|
|
|
387
523
|
}
|
|
388
524
|
} else {
|
|
389
525
|
// Subsequent updates: animate changed properties
|
|
390
|
-
|
|
391
|
-
*std::static_pointer_cast<const EaseViewProps>(oldProps);
|
|
526
|
+
BOOL anyPropertyChanged = NO;
|
|
392
527
|
|
|
393
528
|
if ((mask & kMaskOpacity) &&
|
|
394
529
|
oldViewProps.animateOpacity != newViewProps.animateOpacity) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
530
|
+
anyPropertyChanged = YES;
|
|
531
|
+
EaseTransitionConfig opacityConfig =
|
|
532
|
+
transitionConfigForProperty("opacity", newViewProps);
|
|
533
|
+
if (opacityConfig.type == "none") {
|
|
534
|
+
self.layer.opacity = newViewProps.animateOpacity;
|
|
535
|
+
[self.layer removeAnimationForKey:kAnimKeyOpacity];
|
|
536
|
+
} else {
|
|
537
|
+
self.layer.opacity = newViewProps.animateOpacity;
|
|
538
|
+
[self
|
|
539
|
+
applyAnimationForKeyPath:@"opacity"
|
|
540
|
+
animationKey:kAnimKeyOpacity
|
|
541
|
+
fromValue:[self
|
|
542
|
+
presentationValueForKeyPath:@"opacity"]
|
|
543
|
+
toValue:@(newViewProps.animateOpacity)
|
|
544
|
+
config:opacityConfig
|
|
545
|
+
loop:NO];
|
|
546
|
+
}
|
|
403
547
|
}
|
|
404
548
|
|
|
405
549
|
// Check if ANY transform-related property changed
|
|
@@ -414,46 +558,98 @@ static const int kMaskAnyTransform = kMaskTranslateX | kMaskTranslateY |
|
|
|
414
558
|
oldViewProps.animateRotateY != newViewProps.animateRotateY;
|
|
415
559
|
|
|
416
560
|
if (anyTransformChanged) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
561
|
+
anyPropertyChanged = YES;
|
|
562
|
+
// Determine which transform sub-properties changed for config selection
|
|
563
|
+
int changedTransformMask = 0;
|
|
564
|
+
if (oldViewProps.animateTranslateX != newViewProps.animateTranslateX)
|
|
565
|
+
changedTransformMask |= kMaskTranslateX;
|
|
566
|
+
if (oldViewProps.animateTranslateY != newViewProps.animateTranslateY)
|
|
567
|
+
changedTransformMask |= kMaskTranslateY;
|
|
568
|
+
if (oldViewProps.animateScaleX != newViewProps.animateScaleX)
|
|
569
|
+
changedTransformMask |= kMaskScaleX;
|
|
570
|
+
if (oldViewProps.animateScaleY != newViewProps.animateScaleY)
|
|
571
|
+
changedTransformMask |= kMaskScaleY;
|
|
572
|
+
if (oldViewProps.animateRotate != newViewProps.animateRotate)
|
|
573
|
+
changedTransformMask |= kMaskRotate;
|
|
574
|
+
if (oldViewProps.animateRotateX != newViewProps.animateRotateX)
|
|
575
|
+
changedTransformMask |= kMaskRotateX;
|
|
576
|
+
if (oldViewProps.animateRotateY != newViewProps.animateRotateY)
|
|
577
|
+
changedTransformMask |= kMaskRotateY;
|
|
578
|
+
|
|
579
|
+
std::string transformName =
|
|
580
|
+
lowestTransformPropertyName(changedTransformMask);
|
|
581
|
+
EaseTransitionConfig transformConfig =
|
|
582
|
+
transitionConfigForProperty(transformName, newViewProps);
|
|
583
|
+
|
|
584
|
+
if (transformConfig.type == "none") {
|
|
585
|
+
self.layer.transform = [self targetTransformFromProps:newViewProps];
|
|
586
|
+
[self.layer removeAnimationForKey:kAnimKeyTransform];
|
|
587
|
+
} else {
|
|
588
|
+
CATransform3D fromT = [self presentationTransform];
|
|
589
|
+
CATransform3D toT = [self targetTransformFromProps:newViewProps];
|
|
590
|
+
self.layer.transform = toT;
|
|
591
|
+
[self applyAnimationForKeyPath:@"transform"
|
|
592
|
+
animationKey:kAnimKeyTransform
|
|
593
|
+
fromValue:[NSValue valueWithCATransform3D:fromT]
|
|
594
|
+
toValue:[NSValue valueWithCATransform3D:toT]
|
|
595
|
+
config:transformConfig
|
|
596
|
+
loop:NO];
|
|
597
|
+
}
|
|
426
598
|
}
|
|
427
599
|
}
|
|
428
600
|
|
|
429
601
|
if ((mask & kMaskBorderRadius) &&
|
|
430
602
|
oldViewProps.animateBorderRadius != newViewProps.animateBorderRadius) {
|
|
603
|
+
anyPropertyChanged = YES;
|
|
604
|
+
EaseTransitionConfig brConfig =
|
|
605
|
+
transitionConfigForProperty("borderRadius", newViewProps);
|
|
431
606
|
self.layer.cornerRadius = newViewProps.animateBorderRadius;
|
|
432
607
|
self.layer.masksToBounds = newViewProps.animateBorderRadius > 0;
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
608
|
+
if (brConfig.type == "none") {
|
|
609
|
+
[self.layer removeAnimationForKey:kAnimKeyCornerRadius];
|
|
610
|
+
} else {
|
|
611
|
+
[self applyAnimationForKeyPath:@"cornerRadius"
|
|
612
|
+
animationKey:kAnimKeyCornerRadius
|
|
613
|
+
fromValue:[self presentationValueForKeyPath:
|
|
614
|
+
@"cornerRadius"]
|
|
615
|
+
toValue:@(newViewProps.animateBorderRadius)
|
|
616
|
+
config:brConfig
|
|
617
|
+
loop:NO];
|
|
618
|
+
}
|
|
440
619
|
}
|
|
441
620
|
|
|
442
621
|
if ((mask & kMaskBackgroundColor) &&
|
|
443
622
|
oldViewProps.animateBackgroundColor !=
|
|
444
623
|
newViewProps.animateBackgroundColor) {
|
|
445
|
-
|
|
446
|
-
|
|
624
|
+
anyPropertyChanged = YES;
|
|
625
|
+
EaseTransitionConfig bgConfig =
|
|
626
|
+
transitionConfigForProperty("backgroundColor", newViewProps);
|
|
447
627
|
CGColorRef toColor =
|
|
448
628
|
RCTUIColorFromSharedColor(newViewProps.animateBackgroundColor)
|
|
449
629
|
.CGColor;
|
|
450
630
|
self.layer.backgroundColor = toColor;
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
631
|
+
if (bgConfig.type == "none") {
|
|
632
|
+
[self.layer removeAnimationForKey:kAnimKeyBackgroundColor];
|
|
633
|
+
} else {
|
|
634
|
+
CGColorRef fromColor = (__bridge CGColorRef)
|
|
635
|
+
[self presentationValueForKeyPath:@"backgroundColor"];
|
|
636
|
+
[self applyAnimationForKeyPath:@"backgroundColor"
|
|
637
|
+
animationKey:kAnimKeyBackgroundColor
|
|
638
|
+
fromValue:(__bridge id)fromColor
|
|
639
|
+
toValue:(__bridge id)toColor
|
|
640
|
+
config:bgConfig
|
|
641
|
+
loop:NO];
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// If all changed properties resolved to 'none', no animations were queued.
|
|
646
|
+
// Fire onTransitionEnd immediately.
|
|
647
|
+
if (anyPropertyChanged && _pendingAnimationCount == 0 && _eventEmitter) {
|
|
648
|
+
auto emitter =
|
|
649
|
+
std::static_pointer_cast<const EaseViewEventEmitter>(_eventEmitter);
|
|
650
|
+
emitter->onTransitionEnd(EaseViewEventEmitter::OnTransitionEnd{
|
|
651
|
+
.finished = true,
|
|
652
|
+
});
|
|
457
653
|
}
|
|
458
654
|
}
|
|
459
655
|
|
package/lib/module/EaseView.js
CHANGED
|
@@ -52,6 +52,88 @@ const EASING_PRESETS = {
|
|
|
52
52
|
easeOut: [0, 0, 0.58, 1],
|
|
53
53
|
easeInOut: [0.42, 0, 0.58, 1]
|
|
54
54
|
};
|
|
55
|
+
|
|
56
|
+
/** Returns true if the transition is a SingleTransition (has a `type` field). */
|
|
57
|
+
function isSingleTransition(t) {
|
|
58
|
+
return 'type' in t;
|
|
59
|
+
}
|
|
60
|
+
/** Default config: timing 300ms easeInOut. */
|
|
61
|
+
const DEFAULT_CONFIG = {
|
|
62
|
+
type: 'timing',
|
|
63
|
+
duration: 300,
|
|
64
|
+
easingBezier: [0.42, 0, 0.58, 1],
|
|
65
|
+
damping: 15,
|
|
66
|
+
stiffness: 120,
|
|
67
|
+
mass: 1,
|
|
68
|
+
loop: 'none',
|
|
69
|
+
delay: 0
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Resolve a SingleTransition into a native config object. */
|
|
73
|
+
function resolveSingleConfig(config) {
|
|
74
|
+
const type = config.type;
|
|
75
|
+
const duration = config.type === 'timing' ? config.duration ?? 300 : 300;
|
|
76
|
+
const rawEasing = config.type === 'timing' ? config.easing ?? 'easeInOut' : 'easeInOut';
|
|
77
|
+
if (__DEV__) {
|
|
78
|
+
if (Array.isArray(rawEasing)) {
|
|
79
|
+
if (rawEasing.length !== 4) {
|
|
80
|
+
console.warn('react-native-ease: Custom easing must be a [x1, y1, x2, y2] tuple (got length ' + rawEasing.length + ').');
|
|
81
|
+
}
|
|
82
|
+
if (rawEasing[0] < 0 || rawEasing[0] > 1 || rawEasing[2] < 0 || rawEasing[2] > 1) {
|
|
83
|
+
console.warn('react-native-ease: Easing x-values (x1, x2) must be between 0 and 1.');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const easingBezier = Array.isArray(rawEasing) ? rawEasing : EASING_PRESETS[rawEasing];
|
|
88
|
+
const damping = config.type === 'spring' ? config.damping ?? 15 : 15;
|
|
89
|
+
const stiffness = config.type === 'spring' ? config.stiffness ?? 120 : 120;
|
|
90
|
+
const mass = config.type === 'spring' ? config.mass ?? 1 : 1;
|
|
91
|
+
const loop = config.type === 'timing' ? config.loop ?? 'none' : 'none';
|
|
92
|
+
const delay = config.type === 'timing' || config.type === 'spring' ? config.delay ?? 0 : 0;
|
|
93
|
+
return {
|
|
94
|
+
type,
|
|
95
|
+
duration,
|
|
96
|
+
easingBezier,
|
|
97
|
+
damping,
|
|
98
|
+
stiffness,
|
|
99
|
+
mass,
|
|
100
|
+
loop,
|
|
101
|
+
delay
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Category keys that map to optional NativeTransitions fields. */
|
|
106
|
+
const CATEGORY_KEYS = ['transform', 'opacity', 'borderRadius', 'backgroundColor'];
|
|
107
|
+
|
|
108
|
+
/** Resolve the transition prop into a NativeTransitions struct. */
|
|
109
|
+
function resolveTransitions(transition) {
|
|
110
|
+
// No transition: timing default for all properties
|
|
111
|
+
if (transition == null) {
|
|
112
|
+
return {
|
|
113
|
+
defaultConfig: DEFAULT_CONFIG
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Single transition: set as defaultConfig only
|
|
118
|
+
if (isSingleTransition(transition)) {
|
|
119
|
+
return {
|
|
120
|
+
defaultConfig: resolveSingleConfig(transition)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// TransitionMap: resolve defaultConfig + only specified category keys
|
|
125
|
+
const defaultConfig = transition.default ? resolveSingleConfig(transition.default) : DEFAULT_CONFIG;
|
|
126
|
+
const result = {
|
|
127
|
+
defaultConfig
|
|
128
|
+
};
|
|
129
|
+
for (const key of CATEGORY_KEYS) {
|
|
130
|
+
const specific = transition[key];
|
|
131
|
+
if (specific != null) {
|
|
132
|
+
result[key] = resolveSingleConfig(specific);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
55
137
|
export function EaseView({
|
|
56
138
|
animate,
|
|
57
139
|
initialAnimate,
|
|
@@ -135,26 +217,8 @@ export function EaseView({
|
|
|
135
217
|
}
|
|
136
218
|
}
|
|
137
219
|
|
|
138
|
-
// Resolve transition config
|
|
139
|
-
const
|
|
140
|
-
const transitionDuration = transition?.type === 'timing' ? transition.duration ?? 300 : 300;
|
|
141
|
-
const rawEasing = transition?.type === 'timing' ? transition.easing ?? 'easeInOut' : 'easeInOut';
|
|
142
|
-
if (__DEV__) {
|
|
143
|
-
if (Array.isArray(rawEasing)) {
|
|
144
|
-
if (rawEasing.length !== 4) {
|
|
145
|
-
console.warn('react-native-ease: Custom easing must be a [x1, y1, x2, y2] tuple (got length ' + rawEasing.length + ').');
|
|
146
|
-
}
|
|
147
|
-
if (rawEasing[0] < 0 || rawEasing[0] > 1 || rawEasing[2] < 0 || rawEasing[2] > 1) {
|
|
148
|
-
console.warn('react-native-ease: Easing x-values (x1, x2) must be between 0 and 1.');
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
const bezier = Array.isArray(rawEasing) ? rawEasing : EASING_PRESETS[rawEasing];
|
|
153
|
-
const transitionDamping = transition?.type === 'spring' ? transition.damping ?? 15 : 15;
|
|
154
|
-
const transitionStiffness = transition?.type === 'spring' ? transition.stiffness ?? 120 : 120;
|
|
155
|
-
const transitionMass = transition?.type === 'spring' ? transition.mass ?? 1 : 1;
|
|
156
|
-
const transitionLoop = transition?.type === 'timing' ? transition.loop ?? 'none' : 'none';
|
|
157
|
-
const transitionDelay = transition?.type === 'timing' || transition?.type === 'spring' ? transition.delay ?? 0 : 0;
|
|
220
|
+
// Resolve transition config into a fully-populated struct
|
|
221
|
+
const transitions = resolveTransitions(transition);
|
|
158
222
|
const handleTransitionEnd = onTransitionEnd ? event => onTransitionEnd(event.nativeEvent) : undefined;
|
|
159
223
|
return /*#__PURE__*/_jsx(NativeEaseView, {
|
|
160
224
|
style: cleanStyle,
|
|
@@ -180,14 +244,7 @@ export function EaseView({
|
|
|
180
244
|
initialAnimateRotateY: resolvedInitial.rotateY,
|
|
181
245
|
initialAnimateBorderRadius: resolvedInitial.borderRadius,
|
|
182
246
|
initialAnimateBackgroundColor: initialBgColor,
|
|
183
|
-
|
|
184
|
-
transitionDuration: transitionDuration,
|
|
185
|
-
transitionEasingBezier: bezier,
|
|
186
|
-
transitionDamping: transitionDamping,
|
|
187
|
-
transitionStiffness: transitionStiffness,
|
|
188
|
-
transitionMass: transitionMass,
|
|
189
|
-
transitionLoop: transitionLoop,
|
|
190
|
-
transitionDelay: transitionDelay,
|
|
247
|
+
transitions: transitions,
|
|
191
248
|
useHardwareLayer: useHardwareLayer,
|
|
192
249
|
transformOriginX: transformOrigin?.x ?? 0.5,
|
|
193
250
|
transformOriginY: transformOrigin?.y ?? 0.5,
|