react-native-morph-card 0.1.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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/android/build.gradle +59 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/java/com/melivalesca/morphcard/MorphCardModule.kt +120 -0
  6. package/android/src/main/java/com/melivalesca/morphcard/MorphCardPackage.kt +42 -0
  7. package/android/src/main/java/com/melivalesca/morphcard/MorphCardSourceManager.kt +40 -0
  8. package/android/src/main/java/com/melivalesca/morphcard/MorphCardSourceView.kt +755 -0
  9. package/android/src/main/java/com/melivalesca/morphcard/MorphCardTargetManager.kt +48 -0
  10. package/android/src/main/java/com/melivalesca/morphcard/MorphCardTargetView.kt +159 -0
  11. package/android/src/main/java/com/melivalesca/morphcard/MorphCardViewRegistry.kt +24 -0
  12. package/android/src/main/jni/CMakeLists.txt +62 -0
  13. package/common/cpp/react/renderer/components/morphcard/RNCMorphCardState.h +30 -0
  14. package/ios/Fabric/RNCMorphCardSourceComponentView.h +25 -0
  15. package/ios/Fabric/RNCMorphCardSourceComponentView.mm +582 -0
  16. package/ios/Fabric/RNCMorphCardTargetComponentView.h +20 -0
  17. package/ios/Fabric/RNCMorphCardTargetComponentView.mm +99 -0
  18. package/ios/RNCMorphCardModule.h +14 -0
  19. package/ios/RNCMorphCardModule.mm +126 -0
  20. package/ios/RNCMorphCardSource.h +23 -0
  21. package/ios/RNCMorphCardSource.m +144 -0
  22. package/ios/RNCMorphCardSourceManager.h +5 -0
  23. package/ios/RNCMorphCardSourceManager.m +17 -0
  24. package/ios/RNCMorphCardTarget.h +19 -0
  25. package/ios/RNCMorphCardTarget.m +27 -0
  26. package/ios/RNCMorphCardTargetManager.h +5 -0
  27. package/ios/RNCMorphCardTargetManager.m +16 -0
  28. package/ios/RNCMorphCardViewRegistry.h +35 -0
  29. package/ios/RNCMorphCardViewRegistry.m +40 -0
  30. package/lib/commonjs/MorphCard.types.js +6 -0
  31. package/lib/commonjs/MorphCard.types.js.map +1 -0
  32. package/lib/commonjs/MorphCardSource.js +95 -0
  33. package/lib/commonjs/MorphCardSource.js.map +1 -0
  34. package/lib/commonjs/MorphCardTarget.js +83 -0
  35. package/lib/commonjs/MorphCardTarget.js.map +1 -0
  36. package/lib/commonjs/index.js +45 -0
  37. package/lib/commonjs/index.js.map +1 -0
  38. package/lib/commonjs/package.json +1 -0
  39. package/lib/commonjs/specs/NativeMorphCardModule.js +9 -0
  40. package/lib/commonjs/specs/NativeMorphCardModule.js.map +1 -0
  41. package/lib/commonjs/specs/NativeMorphCardSource.js +10 -0
  42. package/lib/commonjs/specs/NativeMorphCardSource.js.map +1 -0
  43. package/lib/commonjs/specs/NativeMorphCardTarget.js +10 -0
  44. package/lib/commonjs/specs/NativeMorphCardTarget.js.map +1 -0
  45. package/lib/commonjs/useMorphTarget.js +28 -0
  46. package/lib/commonjs/useMorphTarget.js.map +1 -0
  47. package/lib/module/MorphCard.types.js +4 -0
  48. package/lib/module/MorphCard.types.js.map +1 -0
  49. package/lib/module/MorphCardSource.js +85 -0
  50. package/lib/module/MorphCardSource.js.map +1 -0
  51. package/lib/module/MorphCardTarget.js +76 -0
  52. package/lib/module/MorphCardTarget.js.map +1 -0
  53. package/lib/module/index.js +6 -0
  54. package/lib/module/index.js.map +1 -0
  55. package/lib/module/package.json +1 -0
  56. package/lib/module/specs/NativeMorphCardModule.js +5 -0
  57. package/lib/module/specs/NativeMorphCardModule.js.map +1 -0
  58. package/lib/module/specs/NativeMorphCardSource.js +5 -0
  59. package/lib/module/specs/NativeMorphCardSource.js.map +1 -0
  60. package/lib/module/specs/NativeMorphCardTarget.js +5 -0
  61. package/lib/module/specs/NativeMorphCardTarget.js.map +1 -0
  62. package/lib/module/useMorphTarget.js +22 -0
  63. package/lib/module/useMorphTarget.js.map +1 -0
  64. package/lib/typescript/src/MorphCard.types.d.ts +29 -0
  65. package/lib/typescript/src/MorphCard.types.d.ts.map +1 -0
  66. package/lib/typescript/src/MorphCardSource.d.ts +35 -0
  67. package/lib/typescript/src/MorphCardSource.d.ts.map +1 -0
  68. package/lib/typescript/src/MorphCardTarget.d.ts +20 -0
  69. package/lib/typescript/src/MorphCardTarget.d.ts.map +1 -0
  70. package/lib/typescript/src/index.d.ts +6 -0
  71. package/lib/typescript/src/index.d.ts.map +1 -0
  72. package/lib/typescript/src/specs/NativeMorphCardModule.d.ts +14 -0
  73. package/lib/typescript/src/specs/NativeMorphCardModule.d.ts.map +1 -0
  74. package/lib/typescript/src/specs/NativeMorphCardSource.d.ts +13 -0
  75. package/lib/typescript/src/specs/NativeMorphCardSource.d.ts.map +1 -0
  76. package/lib/typescript/src/specs/NativeMorphCardTarget.d.ts +25 -0
  77. package/lib/typescript/src/specs/NativeMorphCardTarget.d.ts.map +1 -0
  78. package/lib/typescript/src/useMorphTarget.d.ts +16 -0
  79. package/lib/typescript/src/useMorphTarget.d.ts.map +1 -0
  80. package/package.json +101 -0
  81. package/react-native-morph-card.podspec +41 -0
  82. package/react-native.config.js +13 -0
  83. package/src/MorphCard.types.ts +29 -0
  84. package/src/MorphCardSource.tsx +105 -0
  85. package/src/MorphCardTarget.tsx +127 -0
  86. package/src/index.tsx +10 -0
  87. package/src/specs/NativeMorphCardModule.ts +21 -0
  88. package/src/specs/NativeMorphCardSource.ts +20 -0
  89. package/src/specs/NativeMorphCardTarget.ts +38 -0
  90. package/src/useMorphTarget.ts +21 -0
@@ -0,0 +1,14 @@
1
+ #ifdef RCT_NEW_ARCH_ENABLED
2
+ #import <morphcard/morphcard.h>
3
+ #else
4
+ #import <React/RCTBridgeModule.h>
5
+ #endif
6
+
7
+ @interface RNCMorphCardModule : NSObject
8
+ #ifdef RCT_NEW_ARCH_ENABLED
9
+ <NativeMorphCardModuleSpec>
10
+ #else
11
+ <RCTBridgeModule>
12
+ #endif
13
+
14
+ @end
@@ -0,0 +1,126 @@
1
+ #import "RNCMorphCardModule.h"
2
+ #import "RNCMorphCardViewRegistry.h"
3
+
4
+ #ifdef RCT_NEW_ARCH_ENABLED
5
+ #import "RNCMorphCardSourceComponentView.h"
6
+ #endif
7
+
8
+ @implementation RNCMorphCardModule
9
+
10
+ RCT_EXPORT_MODULE()
11
+
12
+ // No-op on iOS — Android uses this to create the overlay before navigation
13
+ RCT_EXPORT_METHOD(prepareExpand : (double)sourceTag) {
14
+ }
15
+
16
+ RCT_EXPORT_METHOD(setTargetConfig
17
+ : (double)sourceTag targetWidth
18
+ : (double)targetWidth targetHeight
19
+ : (double)targetHeight targetBorderRadius
20
+ : (double)targetBorderRadius contentOffsetY
21
+ : (double)contentOffsetY contentCentered
22
+ : (BOOL)contentCentered) {
23
+ dispatch_async(dispatch_get_main_queue(), ^{
24
+ UIView *sourceView =
25
+ [[RNCMorphCardViewRegistry shared] viewForTag:(NSInteger)sourceTag];
26
+ #ifdef RCT_NEW_ARCH_ENABLED
27
+ if ([sourceView
28
+ isKindOfClass:[RNCMorphCardSourceComponentView class]]) {
29
+ RNCMorphCardSourceComponentView *src =
30
+ (RNCMorphCardSourceComponentView *)sourceView;
31
+ src.pendingTargetWidth = targetWidth;
32
+ src.pendingTargetHeight = targetHeight;
33
+ src.pendingTargetBorderRadius = targetBorderRadius;
34
+ src.pendingContentOffsetY = contentOffsetY;
35
+ src.pendingContentCentered = contentCentered;
36
+ }
37
+ #endif
38
+ });
39
+ }
40
+
41
+ RCT_EXPORT_METHOD(expand
42
+ : (double)sourceTag targetTag
43
+ : (double)targetTag resolve
44
+ : (RCTPromiseResolveBlock)resolve reject
45
+ : (RCTPromiseRejectBlock)reject) {
46
+ dispatch_after(
47
+ dispatch_time(DISPATCH_TIME_NOW, (int64_t)(150 * NSEC_PER_MSEC)),
48
+ dispatch_get_main_queue(), ^{
49
+ UIView *sourceView =
50
+ [[RNCMorphCardViewRegistry shared]
51
+ viewForTag:(NSInteger)sourceTag];
52
+
53
+ UIView *targetView =
54
+ [[RNCMorphCardViewRegistry shared]
55
+ viewForTag:(NSInteger)targetTag];
56
+
57
+ #ifdef RCT_NEW_ARCH_ENABLED
58
+ if ([sourceView
59
+ isKindOfClass:[RNCMorphCardSourceComponentView class]]) {
60
+ [(RNCMorphCardSourceComponentView *)sourceView
61
+ expandToTarget:targetView
62
+ resolve:resolve];
63
+ } else {
64
+ NSLog(@"[MorphCard] expand: source %ld is not a "
65
+ @"RNCMorphCardSourceComponentView",
66
+ (long)sourceTag);
67
+ resolve(@(NO));
68
+ }
69
+ #else
70
+ resolve(@(NO));
71
+ #endif
72
+ });
73
+ }
74
+
75
+ RCT_EXPORT_METHOD(getSourceSize
76
+ : (double)sourceTag resolve
77
+ : (RCTPromiseResolveBlock)resolve reject
78
+ : (RCTPromiseRejectBlock)reject) {
79
+ dispatch_async(dispatch_get_main_queue(), ^{
80
+ UIView *sourceView =
81
+ [[RNCMorphCardViewRegistry shared] viewForTag:(NSInteger)sourceTag];
82
+ if (sourceView) {
83
+ resolve(@{
84
+ @"width" : @(sourceView.bounds.size.width),
85
+ @"height" : @(sourceView.bounds.size.height)
86
+ });
87
+ } else {
88
+ resolve(@{@"width" : @(0), @"height" : @(0)});
89
+ }
90
+ });
91
+ }
92
+
93
+ RCT_EXPORT_METHOD(collapse
94
+ : (double)sourceTag resolve
95
+ : (RCTPromiseResolveBlock)resolve reject
96
+ : (RCTPromiseRejectBlock)reject) {
97
+ dispatch_async(dispatch_get_main_queue(), ^{
98
+ UIView *sourceView =
99
+ [[RNCMorphCardViewRegistry shared] viewForTag:(NSInteger)sourceTag];
100
+
101
+ #ifdef RCT_NEW_ARCH_ENABLED
102
+ if ([sourceView
103
+ isKindOfClass:[RNCMorphCardSourceComponentView class]]) {
104
+ [(RNCMorphCardSourceComponentView *)sourceView
105
+ collapseWithResolve:resolve];
106
+ } else {
107
+ NSLog(@"[MorphCard] collapse: source %ld is not a "
108
+ @"RNCMorphCardSourceComponentView",
109
+ (long)sourceTag);
110
+ resolve(@(NO));
111
+ }
112
+ #else
113
+ resolve(@(NO));
114
+ #endif
115
+ });
116
+ }
117
+
118
+ #ifdef RCT_NEW_ARCH_ENABLED
119
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
120
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
121
+ return std::make_shared<facebook::react::NativeMorphCardModuleSpecJSI>(
122
+ params);
123
+ }
124
+ #endif
125
+
126
+ @end
@@ -0,0 +1,23 @@
1
+ #import <React/RCTView.h>
2
+
3
+ @interface RNCMorphCardSource : RCTView
4
+
5
+ /// Event fired when the morph animation begins.
6
+ @property (nonatomic, copy) RCTDirectEventBlock onMorphStart;
7
+
8
+ /// Event fired when the morph animation completes (expand finished).
9
+ @property (nonatomic, copy) RCTDirectEventBlock onMorphComplete;
10
+
11
+ /// Event fired when the dismiss animation completes.
12
+ @property (nonatomic, copy) RCTDirectEventBlock onDismissComplete;
13
+
14
+ /// Duration of the morph animation in milliseconds. Defaults to 500.
15
+ @property (nonatomic, assign) CGFloat duration;
16
+
17
+ /// Programmatically trigger the expand.
18
+ - (void)expand;
19
+
20
+ /// Programmatically trigger the dismiss.
21
+ - (void)collapse;
22
+
23
+ @end
@@ -0,0 +1,144 @@
1
+ #import "RNCMorphCardSource.h"
2
+ #import "RNCMorphCardViewRegistry.h"
3
+
4
+ // ──────────────────────────────────────────────────────
5
+ // HOW THE EXPAND/COLLAPSE WORKS:
6
+ //
7
+ // The card is a normal RCTView in the view hierarchy. When expand
8
+ // is called, we:
9
+ //
10
+ // 1. Save the card's original frame and superview position
11
+ // 2. Move the card to the key window (so it's above everything)
12
+ // 3. Animate the card's frame from its original position to fullscreen
13
+ // 4. The card's children (both the "collapsed" card content and the
14
+ // "expanded" detail content) are inside it — JS controls which
15
+ // is visible via state
16
+ //
17
+ // On collapse, we reverse: animate back to original frame, then
18
+ // reparent back into the original superview.
19
+ //
20
+ // This means the ACTUAL VIEW expands — not a snapshot, not an overlay.
21
+ // The user's content (images, text, etc.) is live throughout.
22
+ // ──────────────────────────────────────────────────────
23
+
24
+ @implementation RNCMorphCardSource {
25
+ // Saved state for collapsing back.
26
+ CGRect _originalFrame;
27
+ UIView *_originalSuperview;
28
+ NSInteger _originalIndex;
29
+ CGFloat _originalCornerRadius;
30
+ BOOL _isExpanded;
31
+ }
32
+
33
+ - (instancetype)init {
34
+ if (self = [super init]) {
35
+ _duration = 500.0;
36
+ }
37
+ return self;
38
+ }
39
+
40
+ - (void)didMoveToWindow {
41
+ [super didMoveToWindow];
42
+ if (self.window) {
43
+ [[RNCMorphCardViewRegistry shared] registerView:self withTag:self.tag];
44
+ } else {
45
+ [[RNCMorphCardViewRegistry shared] unregisterViewWithTag:self.tag];
46
+ }
47
+ }
48
+
49
+ - (void)expand {
50
+ if (_isExpanded) return;
51
+ _isExpanded = YES;
52
+
53
+ // ── Save original position ──
54
+ // We need to remember where this view was so we can put it back.
55
+ _originalSuperview = self.superview;
56
+ _originalIndex = [_originalSuperview.subviews indexOfObject:self];
57
+ _originalCornerRadius = self.layer.cornerRadius;
58
+
59
+ // Get current position in window coordinates.
60
+ _originalFrame = [self convertRect:self.bounds toView:nil];
61
+
62
+ // ── Reparent to window ──
63
+ // Move the card to the key window so it sits above everything
64
+ // (navigation bars, tab bars, other views).
65
+ UIWindow *window = self.window;
66
+ if (!window) return;
67
+
68
+ // Set the frame in window coordinates BEFORE reparenting.
69
+ // After removeFromSuperview, the view's frame is relative to the
70
+ // new parent (window), so we use the window-relative frame.
71
+ [self removeFromSuperview];
72
+ self.frame = _originalFrame;
73
+ [window addSubview:self];
74
+
75
+ // Fire event so JS can swap to expanded content.
76
+ if (self.onMorphStart) {
77
+ self.onMorphStart(@{});
78
+ }
79
+
80
+ // ── Animate to fullscreen ──
81
+ NSTimeInterval durationSeconds = _duration / 1000.0;
82
+ CGRect fullscreen = window.bounds;
83
+
84
+ UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc]
85
+ initWithDuration:durationSeconds
86
+ dampingRatio:0.85
87
+ animations:^{
88
+ self.frame = fullscreen;
89
+ self.layer.cornerRadius = 0;
90
+ }];
91
+
92
+ [animator addCompletion:^(UIViewAnimatingPosition finalPosition) {
93
+ if (self.onMorphComplete) {
94
+ self.onMorphComplete(@{});
95
+ }
96
+ }];
97
+
98
+ [animator startAnimation];
99
+ }
100
+
101
+ - (void)collapse {
102
+ if (!_isExpanded) return;
103
+
104
+ NSTimeInterval durationSeconds = _duration / 1000.0;
105
+
106
+ UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc]
107
+ initWithDuration:durationSeconds
108
+ dampingRatio:0.85
109
+ animations:^{
110
+ self.frame = self->_originalFrame;
111
+ self.layer.cornerRadius = self->_originalCornerRadius;
112
+ }];
113
+
114
+ [animator addCompletion:^(UIViewAnimatingPosition finalPosition) {
115
+ self->_isExpanded = NO;
116
+
117
+ // ── Reparent back to original superview ──
118
+ // Put the card back where it was in the view hierarchy.
119
+ [self removeFromSuperview];
120
+ if (self->_originalSuperview) {
121
+ NSInteger insertIndex =
122
+ MIN(self->_originalIndex,
123
+ (NSInteger)self->_originalSuperview.subviews.count);
124
+ [self->_originalSuperview insertSubview:self atIndex:insertIndex];
125
+ }
126
+
127
+ // Restore the original frame relative to the parent.
128
+ // Since we're back in the original superview, we need the
129
+ // local frame, not the window frame. Convert back.
130
+ if (self->_originalSuperview) {
131
+ self.frame = [self->_originalSuperview
132
+ convertRect:self->_originalFrame
133
+ fromView:nil];
134
+ }
135
+
136
+ if (self.onDismissComplete) {
137
+ self.onDismissComplete(@{});
138
+ }
139
+ }];
140
+
141
+ [animator startAnimation];
142
+ }
143
+
144
+ @end
@@ -0,0 +1,5 @@
1
+ #import <React/RCTViewManager.h>
2
+
3
+ @interface RNCMorphCardSourceManager : RCTViewManager
4
+
5
+ @end
@@ -0,0 +1,17 @@
1
+ #import "RNCMorphCardSourceManager.h"
2
+ #import "RNCMorphCardSource.h"
3
+
4
+ @implementation RNCMorphCardSourceManager
5
+
6
+ RCT_EXPORT_MODULE()
7
+
8
+ - (UIView *)view {
9
+ return [[RNCMorphCardSource alloc] init];
10
+ }
11
+
12
+ RCT_EXPORT_VIEW_PROPERTY(onMorphStart, RCTDirectEventBlock)
13
+ RCT_EXPORT_VIEW_PROPERTY(onMorphComplete, RCTDirectEventBlock)
14
+ RCT_EXPORT_VIEW_PROPERTY(onDismissComplete, RCTDirectEventBlock)
15
+ RCT_EXPORT_VIEW_PROPERTY(duration, CGFloat)
16
+
17
+ @end
@@ -0,0 +1,19 @@
1
+ #import <React/RCTView.h>
2
+
3
+ @interface RNCMorphCardTarget : RCTView
4
+
5
+ /// Duration of the morph animation in milliseconds. Defaults to 350.
6
+ @property (nonatomic, assign) CGFloat duration;
7
+
8
+ /// The react tag of the source card this target morphs from.
9
+ /// Used by the module to look up the source view and snapshot it.
10
+ @property (nonatomic, assign) NSInteger sourceTag;
11
+
12
+ /// Event fired when the morph animation completes.
13
+ @property (nonatomic, copy) RCTDirectEventBlock onMorphComplete;
14
+
15
+ /// Returns this view's frame in window coordinates.
16
+ /// The animation needs this to know where the snapshot should land.
17
+ - (CGRect)frameInWindow;
18
+
19
+ @end
@@ -0,0 +1,27 @@
1
+ #import "RNCMorphCardTarget.h"
2
+ #import "RNCMorphCardViewRegistry.h"
3
+
4
+ @implementation RNCMorphCardTarget
5
+
6
+ - (instancetype)init {
7
+ if (self = [super init]) {
8
+ _duration = 350.0;
9
+ }
10
+ return self;
11
+ }
12
+
13
+ - (void)didMoveToWindow {
14
+ [super didMoveToWindow];
15
+ if (self.window) {
16
+ [[RNCMorphCardViewRegistry shared] registerView:self
17
+ withTag:self.tag];
18
+ } else {
19
+ [[RNCMorphCardViewRegistry shared] unregisterViewWithTag:self.tag];
20
+ }
21
+ }
22
+
23
+ - (CGRect)frameInWindow {
24
+ return [self convertRect:self.bounds toView:nil];
25
+ }
26
+
27
+ @end
@@ -0,0 +1,5 @@
1
+ #import <React/RCTViewManager.h>
2
+
3
+ @interface RNCMorphCardTargetManager : RCTViewManager
4
+
5
+ @end
@@ -0,0 +1,16 @@
1
+ #import "RNCMorphCardTargetManager.h"
2
+ #import "RNCMorphCardTarget.h"
3
+
4
+ @implementation RNCMorphCardTargetManager
5
+
6
+ RCT_EXPORT_MODULE()
7
+
8
+ - (UIView *)view {
9
+ return [[RNCMorphCardTarget alloc] init];
10
+ }
11
+
12
+ RCT_EXPORT_VIEW_PROPERTY(duration, CGFloat)
13
+ RCT_EXPORT_VIEW_PROPERTY(sourceTag, NSInteger)
14
+ RCT_EXPORT_VIEW_PROPERTY(onMorphComplete, RCTDirectEventBlock)
15
+
16
+ @end
@@ -0,0 +1,35 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ // ──────────────────────────────────────────────────────
4
+ // VIEW REGISTRY
5
+ //
6
+ // On Paper (old arch), we could use self.bridge.uiManager to
7
+ // look up any view by its React tag. On Fabric (new arch),
8
+ // there's no bridge — TurboModules are standalone objects.
9
+ //
10
+ // Our solution: a simple singleton registry. When a MorphCardSource
11
+ // or MorphCardTarget mounts, it registers itself here with its
12
+ // React tag. When the module needs to find a view, it looks it up
13
+ // in this registry instead of going through the bridge.
14
+ //
15
+ // NSMapTable with weak values means: if the view gets deallocated
16
+ // (e.g., the screen is removed), the entry automatically becomes nil.
17
+ // No manual cleanup needed — no risk of retain cycles or stale refs.
18
+ // ──────────────────────────────────────────────────────
19
+
20
+ @interface RNCMorphCardViewRegistry : NSObject
21
+
22
+ + (instancetype)shared;
23
+
24
+ /// Register a view with its React tag. Uses weak references
25
+ /// so views are not retained by the registry.
26
+ - (void)registerView:(UIView *)view withTag:(NSInteger)tag;
27
+
28
+ /// Remove a view from the registry (called on unmount).
29
+ - (void)unregisterViewWithTag:(NSInteger)tag;
30
+
31
+ /// Look up a view by its React tag. Returns nil if the view
32
+ /// has been deallocated or was never registered.
33
+ - (UIView *)viewForTag:(NSInteger)tag;
34
+
35
+ @end
@@ -0,0 +1,40 @@
1
+ #import "RNCMorphCardViewRegistry.h"
2
+
3
+ @implementation RNCMorphCardViewRegistry {
4
+ // NSMapTable lets us use NSNumber keys with weak object values.
5
+ // "weak" means: if the UIView is deallocated, the entry automatically
6
+ // becomes nil — no manual cleanup needed.
7
+ NSMapTable<NSNumber *, UIView *> *_views;
8
+ }
9
+
10
+ + (instancetype)shared {
11
+ static RNCMorphCardViewRegistry *instance;
12
+ static dispatch_once_t onceToken;
13
+ dispatch_once(&onceToken, ^{
14
+ instance = [[RNCMorphCardViewRegistry alloc] init];
15
+ });
16
+ return instance;
17
+ }
18
+
19
+ - (instancetype)init {
20
+ if (self = [super init]) {
21
+ // strongToWeakObjects: keys are strong (NSNumber stays alive),
22
+ // values are weak (UIView can be deallocated freely).
23
+ _views = [NSMapTable strongToWeakObjectsMapTable];
24
+ }
25
+ return self;
26
+ }
27
+
28
+ - (void)registerView:(UIView *)view withTag:(NSInteger)tag {
29
+ [_views setObject:view forKey:@(tag)];
30
+ }
31
+
32
+ - (void)unregisterViewWithTag:(NSInteger)tag {
33
+ [_views removeObjectForKey:@(tag)];
34
+ }
35
+
36
+ - (UIView *)viewForTag:(NSInteger)tag {
37
+ return [_views objectForKey:@(tag)];
38
+ }
39
+
40
+ @end
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ //# sourceMappingURL=MorphCard.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["MorphCard.types.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.MorphCardSource = void 0;
7
+ exports.getViewTag = getViewTag;
8
+ exports.morphCollapse = morphCollapse;
9
+ exports.morphExpand = morphExpand;
10
+ var React = _interopRequireWildcard(require("react"));
11
+ var _reactNative = require("react-native");
12
+ var _NativeMorphCardModule = _interopRequireDefault(require("./specs/NativeMorphCardModule"));
13
+ var _NativeMorphCardSource = _interopRequireDefault(require("./specs/NativeMorphCardSource"));
14
+ var _jsxRuntime = require("react/jsx-runtime");
15
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
17
+ const NativeSourceView = _NativeMorphCardSource.default ?? _reactNative.View;
18
+ const MorphCardSource = ({
19
+ children,
20
+ duration = 300,
21
+ width,
22
+ height,
23
+ borderRadius,
24
+ backgroundColor,
25
+ scaleMode,
26
+ onPress,
27
+ ref
28
+ }) => {
29
+ const nativeRef = React.useRef(null);
30
+ React.useImperativeHandle(ref, () => nativeRef.current);
31
+ const style = {};
32
+ if (width != null) style.width = width;
33
+ if (height != null) style.height = height;
34
+ if (borderRadius != null) {
35
+ style.borderRadius = borderRadius;
36
+ style.overflow = 'hidden';
37
+ }
38
+ if (backgroundColor != null) style.backgroundColor = backgroundColor;
39
+ const handlePress = React.useCallback(() => {
40
+ if (!onPress) return;
41
+ const tag = (0, _reactNative.findNodeHandle)(nativeRef.current);
42
+ if (tag != null) {
43
+ // Create overlay immediately BEFORE navigation to prevent target screen flash
44
+ _NativeMorphCardModule.default.prepareExpand(tag);
45
+ onPress(tag);
46
+ }
47
+ }, [onPress]);
48
+ const content = /*#__PURE__*/(0, _jsxRuntime.jsx)(NativeSourceView, {
49
+ ref: nativeRef,
50
+ duration: duration,
51
+ scaleMode: scaleMode,
52
+ cardBorderRadius: borderRadius,
53
+ style: style,
54
+ children: children
55
+ });
56
+ if (onPress) {
57
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
58
+ onPress: handlePress,
59
+ children: content
60
+ });
61
+ }
62
+ return content;
63
+ };
64
+
65
+ /**
66
+ * Get the native view tag from a ref. Useful for passing sourceTag
67
+ * to the detail screen via navigation params.
68
+ */
69
+ exports.MorphCardSource = MorphCardSource;
70
+ function getViewTag(viewRef) {
71
+ return (0, _reactNative.findNodeHandle)(viewRef.current);
72
+ }
73
+
74
+ /**
75
+ * Expand: background grows from card bounds to fullscreen while
76
+ * card snapshot moves to targetRef's position. Content fades in at the end.
77
+ *
78
+ * Call this AFTER navigating to the detail screen (so the target is mounted).
79
+ */
80
+ async function morphExpand(sourceRef, targetRef) {
81
+ const sourceTag = (0, _reactNative.findNodeHandle)(sourceRef.current);
82
+ const targetTag = (0, _reactNative.findNodeHandle)(targetRef.current);
83
+ if (!sourceTag || !targetTag) return false;
84
+ return _NativeMorphCardModule.default.expand(sourceTag, targetTag);
85
+ }
86
+
87
+ /**
88
+ * Collapse: content fades out, background shrinks from fullscreen back
89
+ * to card bounds while card snapshot moves from target back to card position.
90
+ * Uses the target stored from the last expand call.
91
+ */
92
+ async function morphCollapse(sourceTag) {
93
+ return _NativeMorphCardModule.default.collapse(sourceTag);
94
+ }
95
+ //# sourceMappingURL=MorphCardSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["React","_interopRequireWildcard","require","_reactNative","_NativeMorphCardModule","_interopRequireDefault","_NativeMorphCardSource","_jsxRuntime","e","__esModule","default","t","WeakMap","r","n","o","i","f","__proto__","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","NativeSourceView","NativeSourceViewSpec","View","MorphCardSource","children","duration","width","height","borderRadius","backgroundColor","scaleMode","onPress","ref","nativeRef","useRef","useImperativeHandle","current","style","overflow","handlePress","useCallback","tag","findNodeHandle","NativeMorphCardModule","prepareExpand","content","jsx","cardBorderRadius","Pressable","exports","getViewTag","viewRef","morphExpand","sourceRef","targetRef","sourceTag","targetTag","expand","morphCollapse","collapse"],"sourceRoot":"../../src","sources":["MorphCardSource.tsx"],"mappings":"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","ignoreList":[]}