react-native-blur-vibe 0.1.1 → 0.1.3

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.
@@ -63,6 +63,13 @@ android {
63
63
  }
64
64
  }
65
65
 
66
+ repositories {
67
+ google()
68
+ mavenCentral()
69
+ maven { url 'https://jitpack.io' }
70
+ }
71
+
66
72
  dependencies {
67
73
  implementation "com.facebook.react:react-android"
74
+ implementation 'com.qmdeve.blurview:core:1.1.4'
68
75
  }
@@ -6,7 +6,9 @@ import com.facebook.react.bridge.ReactApplicationContext
6
6
  import com.facebook.react.uimanager.ViewManager
7
7
 
8
8
  class BlurVibePackage : ReactPackage {
9
- override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> = emptyList()
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
10
+ emptyList()
11
+
10
12
  override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
11
13
  listOf(BlurVibeViewManager())
12
14
  }
@@ -1,174 +1,240 @@
1
1
  package com.blurvibe
2
2
 
3
- import android.annotation.SuppressLint
4
3
  import android.content.Context
5
- import android.graphics.Bitmap
6
- import android.graphics.Canvas
7
4
  import android.graphics.Color
8
- import android.graphics.RenderEffect
9
- import android.graphics.Shader
10
- import android.os.Build
11
- import android.renderscript.Allocation
12
- import android.renderscript.Element
13
- import android.renderscript.RenderScript
14
- import android.renderscript.ScriptIntrinsicBlur
5
+ import android.graphics.Outline
6
+ import android.util.TypedValue
15
7
  import android.view.View
16
8
  import android.view.ViewGroup
17
- import android.widget.FrameLayout
9
+ import android.view.ViewOutlineProvider
10
+ import android.view.ViewTreeObserver
11
+ import androidx.core.graphics.toColorInt
12
+ import com.qmdeve.blurview.base.BaseBlurViewGroup
13
+ import com.qmdeve.blurview.widget.BlurViewGroup
18
14
 
19
15
  /**
20
- * BlurVibeView — Android implementation
16
+ * BlurVibeView — Android backdrop blur implementation
21
17
  *
22
- * API 31+ : RenderEffect (hardware accelerated)
23
- * API 24-30: RenderScript (built-in SDK, no extra dep)
18
+ * Extends QmBlurView's BlurViewGroup a high-performance blur library
19
+ * that correctly implements CSS backdrop-filter: blur() semantics:
20
+ * - Blurs content BEHIND the view, not the view itself
21
+ * - Hardware accelerated via native blur algorithms
22
+ * - Handles scroll, animation, zIndex, absolute positioning correctly
23
+ * - Never causes draw loops or bitmap capture on the JS thread
24
24
  *
25
- * Color props (overlayColor, reducedTransparencyFallbackColor) are
26
- * received as hex strings from JS and parsed manually here.
27
- * This gives full control over alpha channel handling.
25
+ * Uses reflection to redirect the blur capture root from the activity
26
+ * decor view to the nearest ReactRootView or react-native-screens Screen,
27
+ * preventing full-screen blur and navigation transition artifacts.
28
28
  *
29
- * Supports: "#RGB" "#RRGGBB" "#RRGGBBAA" "transparent"
29
+ * Credit: approach adapted from sbaiahmed1/react-native-blur
30
30
  */
31
- @SuppressLint("NewApi")
32
- class BlurVibeView(context: Context) : FrameLayout(context) {
33
-
34
- private val overlayView = View(context)
35
- private var blurAmountValue: Float = 10f
36
- private var overlayColorValue: Int = Color.TRANSPARENT
37
- private var fallbackColorValue: Int = Color.parseColor("#F2F2F2")
38
- private var blurRadiusDownscale: Int = 4
31
+ class BlurVibeView(context: Context) : BlurViewGroup(context, null) {
32
+
33
+ private var currentBlurRadius = DEFAULT_BLUR_RADIUS
34
+ private var currentOverlayColor = Color.TRANSPARENT
35
+ private var currentCornerRadius = 0f
36
+ private var isBlurInitialized = false
37
+
38
+ companion object {
39
+ private const val DEFAULT_BLUR_RADIUS = 10f
40
+ private const val MIN_BLUR_AMOUNT = 0f
41
+ private const val MAX_BLUR_AMOUNT = 100f
42
+ private const val MAX_BLUR_RADIUS = 100f
43
+
44
+ // Maps 0–100 blurAmount to 0–25 QmBlurView radius range
45
+ private fun mapBlurAmountToRadius(amount: Float): Float {
46
+ val clamped = amount.coerceIn(MIN_BLUR_AMOUNT, MAX_BLUR_AMOUNT)
47
+ return (clamped / MAX_BLUR_AMOUNT) * MAX_BLUR_RADIUS
48
+ }
49
+ }
39
50
 
40
51
  init {
41
- setWillNotDraw(false)
42
- overlayView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
43
- overlayView.isClickable = false
44
- overlayView.isFocusable = false
45
- addView(overlayView)
46
- applyBlur()
52
+ super.setBackgroundColor(currentOverlayColor)
53
+ clipChildren = true
54
+ clipToOutline = true
55
+ blurRounds = 5
56
+ super.setDownsampleFactor(6.0f)
47
57
  }
48
58
 
49
- // MARK: - Public setters (called by ViewManager)
50
-
51
- fun setBlurAmount(amount: Float) {
52
- blurAmountValue = amount.coerceIn(0f, 100f)
53
- applyBlur()
59
+ override fun onAttachedToWindow() {
60
+ super.onAttachedToWindow()
61
+ if (isBlurInitialized) return
62
+ swapBlurRootToOptimalAncestor()
63
+ initializeBlur()
54
64
  }
55
65
 
56
- fun setOverlayColor(colorString: String) {
57
- overlayColorValue = parseHexColor(colorString) ?: Color.TRANSPARENT
58
- updateOverlay()
66
+ override fun onDetachedFromWindow() {
67
+ super.onDetachedFromWindow()
68
+ isBlurInitialized = false
59
69
  }
60
70
 
61
- fun setReducedTransparencyFallbackColor(colorString: String) {
62
- fallbackColorValue = parseHexColor(colorString) ?: Color.parseColor("#F2F2F2")
63
- }
71
+ /**
72
+ * Uses reflection to redirect QmBlurView's internal blur capture root
73
+ * from the activity decor view to the nearest Screen or ReactRootView.
74
+ * This prevents the full-screen blur issue when BlurVibeView is used
75
+ * inside a ScrollView or with absolute positioning and zIndex.
76
+ */
77
+ private fun swapBlurRootToOptimalAncestor() {
78
+ val newRoot = findNearestScreenAncestor() ?: findNearestReactRootView() ?: return
64
79
 
65
- fun setBlurRadius(radius: Int) {
66
- blurRadiusDownscale = radius.coerceIn(1, 8)
67
- applyBlur()
68
- }
80
+ try {
81
+ val blurViewGroupClass = BlurViewGroup::class.java
82
+ val baseField = blurViewGroupClass.getDeclaredField("mBaseBlurViewGroup")
83
+ baseField.isAccessible = true
84
+ val baseBlurViewGroup = baseField.get(this) ?: return
85
+
86
+ val baseClass = BaseBlurViewGroup::class.java
69
87
 
70
- // MARK: - Blur
88
+ val decorViewField = baseClass.getDeclaredField("mDecorView")
89
+ decorViewField.isAccessible = true
90
+ val oldDecorView = decorViewField.get(baseBlurViewGroup) as? View
91
+
92
+ val preDrawListenerField = baseClass.getDeclaredField("preDrawListener")
93
+ preDrawListenerField.isAccessible = true
94
+ val preDrawListener = preDrawListenerField.get(baseBlurViewGroup)
95
+ as? ViewTreeObserver.OnPreDrawListener
96
+
97
+ if (oldDecorView != null && preDrawListener != null) {
98
+ // Remove listener from old root
99
+ oldDecorView.viewTreeObserver.removeOnPreDrawListener(preDrawListener)
100
+
101
+ // Set new root
102
+ decorViewField.set(baseBlurViewGroup, newRoot)
103
+
104
+ // Add listener to new root
105
+ newRoot.viewTreeObserver.addOnPreDrawListener(preDrawListener)
106
+
107
+ // Update mDifferentRoot flag
108
+ val differentRootField = baseClass.getDeclaredField("mDifferentRoot")
109
+ differentRootField.isAccessible = true
110
+ differentRootField.setBoolean(baseBlurViewGroup, newRoot.rootView != this.rootView)
111
+
112
+ // Force redraw
113
+ val forceRedrawField = baseClass.getDeclaredField("mForceRedraw")
114
+ forceRedrawField.isAccessible = true
115
+ forceRedrawField.setBoolean(baseBlurViewGroup, true)
116
+ }
117
+ } catch (e: Exception) {
118
+ // Reflection failed — QmBlurView internals changed
119
+ // Fall back gracefully to default decor view blur root
120
+ }
121
+ }
71
122
 
72
- private fun applyBlur() {
73
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
74
- applyRenderEffect()
75
- } else {
76
- post { renderScriptBlur() }
123
+ private fun findNearestScreenAncestor(): ViewGroup? {
124
+ var current = parent
125
+ while (current != null) {
126
+ if (current.javaClass.name == "com.swmansion.rnscreens.Screen") {
127
+ return current as? ViewGroup
128
+ }
129
+ current = current.parent
77
130
  }
78
- updateOverlay()
131
+ return null
79
132
  }
80
133
 
81
- private fun applyRenderEffect() {
82
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
83
- val sigma = (blurAmountValue * 0.5f).coerceAtLeast(0.01f)
84
- setRenderEffect(
85
- RenderEffect.createBlurEffect(sigma, sigma, Shader.TileMode.MIRROR)
86
- )
134
+ private fun findNearestReactRootView(): ViewGroup? {
135
+ var current = parent
136
+ while (current != null) {
137
+ if (current.javaClass.name == "com.facebook.react.ReactRootView") {
138
+ return current as? ViewGroup
139
+ }
140
+ current = current.parent
87
141
  }
142
+ return null
88
143
  }
89
144
 
90
- @Suppress("DEPRECATION")
91
- private fun renderScriptBlur() {
92
- val parentView = parent as? ViewGroup ?: return
93
- if (width <= 0 || height <= 0) return
145
+ private fun initializeBlur() {
146
+ if (isBlurInitialized) return
94
147
  try {
95
- val scaledW = (width / blurRadiusDownscale).coerceAtLeast(1)
96
- val scaledH = (height / blurRadiusDownscale).coerceAtLeast(1)
97
- val bitmap = Bitmap.createBitmap(scaledW, scaledH, Bitmap.Config.ARGB_8888)
98
- val canvas = Canvas(bitmap)
99
- canvas.scale(1f / blurRadiusDownscale, 1f / blurRadiusDownscale)
100
- canvas.translate(-left.toFloat(), -top.toFloat())
101
- parentView.draw(canvas)
102
-
103
- val rs = RenderScript.create(context)
104
- val input = Allocation.createFromBitmap(rs, bitmap)
105
- val output = Allocation.createTyped(rs, input.type)
106
- val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
107
- val sigma = (blurAmountValue / 100f * 25f).coerceIn(1f, 25f)
108
- script.setRadius(sigma)
109
- script.setInput(input)
110
- script.forEach(output)
111
- output.copyTo(bitmap)
112
- rs.destroy()
113
-
114
- background = android.graphics.drawable.BitmapDrawable(resources, bitmap)
148
+ super.setBlurRadius(currentBlurRadius)
149
+ super.setOverlayColor(currentOverlayColor)
150
+ updateCornerRadius()
151
+ isBlurInitialized = true
115
152
  } catch (e: Exception) {
116
- setBackgroundColor(fallbackColorValue)
153
+ // Ignore — view may not be fully attached yet
117
154
  }
118
155
  }
119
156
 
120
- // MARK: - Overlay
157
+ // MARK: - Public setters
121
158
 
122
- private fun updateOverlay() {
123
- overlayView.setBackgroundColor(overlayColorValue)
124
- bringChildToFront(overlayView)
125
- for (i in 0 until childCount) {
126
- val child = getChildAt(i)
127
- if (child !== overlayView) bringChildToFront(child)
128
- }
159
+ fun setBlurAmount(amount: Float) {
160
+ currentBlurRadius = mapBlurAmountToRadius(amount)
161
+ try { super.setBlurRadius(currentBlurRadius) } catch (e: Exception) {}
162
+ }
163
+
164
+ fun setOverlayColor(colorString: String?) {
165
+ currentOverlayColor = parseHexColor(colorString ?: "transparent") ?: Color.TRANSPARENT
166
+ try {
167
+ super.setBackgroundColor(currentOverlayColor)
168
+ super.setOverlayColor(currentOverlayColor)
169
+ } catch (e: Exception) {}
170
+ }
171
+
172
+ fun setReducedTransparencyFallbackColor(colorString: String?) {
173
+ // Stored for future use — QmBlurView handles accessibility fallback internally
174
+ }
175
+
176
+ fun setBlurRadius(radius: Int) {
177
+ // blurRadius is the Android downscale factor — map to QmBlurView's downsample factor
178
+ val downsample = radius.coerceIn(1, 8).toFloat()
179
+ try { super.setDownsampleFactor(downsample) } catch (e: Exception) {}
129
180
  }
130
181
 
131
- override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
132
- super.onLayout(changed, l, t, r, b)
133
- if (changed && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
134
- post { renderScriptBlur() }
182
+ fun setBorderRadius(radius: Float) {
183
+ currentCornerRadius = radius
184
+ updateCornerRadius()
185
+ }
186
+
187
+ private fun updateCornerRadius() {
188
+ val radiusPx = TypedValue.applyDimension(
189
+ TypedValue.COMPLEX_UNIT_DIP,
190
+ currentCornerRadius,
191
+ context.resources.displayMetrics
192
+ )
193
+ outlineProvider = object : ViewOutlineProvider() {
194
+ override fun getOutline(view: View, outline: Outline) {
195
+ outline.setRoundRect(0, 0, view.width, view.height, radiusPx)
196
+ }
135
197
  }
198
+ clipToOutline = true
199
+ try { super.setCornerRadius(radiusPx) } catch (e: Exception) {}
200
+ }
201
+
202
+ // React Native handles layout — prevent superclass from interfering
203
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
204
+ // No-op: layout handled by React Native's Yoga engine
136
205
  }
137
206
 
138
207
  // MARK: - Color parser
139
208
  // Supports: "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
140
- // Returns null if unparseable (caller uses fallback)
141
209
  private fun parseHexColor(colorString: String): Int? {
142
210
  val s = colorString.trim()
143
211
  if (s.equals("transparent", ignoreCase = true)) return Color.TRANSPARENT
144
- if (!s.startsWith("#")) return null
145
-
212
+ if (!s.startsWith("#")) {
213
+ return try { s.toColorInt() } catch (e: Exception) { null }
214
+ }
146
215
  val hex = s.removePrefix("#")
147
216
  return try {
148
217
  when (hex.length) {
149
- 3 -> { // #RGB → #RRGGBB
150
- val r = hex[0].toString().repeat(2).toInt(16)
151
- val g = hex[1].toString().repeat(2).toInt(16)
152
- val b = hex[2].toString().repeat(2).toInt(16)
153
- Color.argb(255, r, g, b)
154
- }
155
- 6 -> { // #RRGGBB
156
- val r = hex.substring(0, 2).toInt(16)
157
- val g = hex.substring(2, 4).toInt(16)
158
- val b = hex.substring(4, 6).toInt(16)
159
- Color.argb(255, r, g, b)
160
- }
161
- 8 -> { // #RRGGBBAA — note: AA is alpha, last two digits
162
- val r = hex.substring(0, 2).toInt(16)
163
- val g = hex.substring(2, 4).toInt(16)
164
- val b = hex.substring(4, 6).toInt(16)
165
- val a = hex.substring(6, 8).toInt(16)
166
- Color.argb(a, r, g, b)
167
- }
218
+ 3 -> Color.argb(
219
+ 255,
220
+ hex[0].toString().repeat(2).toInt(16),
221
+ hex[1].toString().repeat(2).toInt(16),
222
+ hex[2].toString().repeat(2).toInt(16)
223
+ )
224
+ 6 -> Color.argb(
225
+ 255,
226
+ hex.substring(0, 2).toInt(16),
227
+ hex.substring(2, 4).toInt(16),
228
+ hex.substring(4, 6).toInt(16)
229
+ )
230
+ 8 -> Color.argb(
231
+ hex.substring(6, 8).toInt(16), // AA is last in #RRGGBBAA
232
+ hex.substring(0, 2).toInt(16),
233
+ hex.substring(2, 4).toInt(16),
234
+ hex.substring(4, 6).toInt(16)
235
+ )
168
236
  else -> null
169
237
  }
170
- } catch (e: NumberFormatException) {
171
- null
172
- }
238
+ } catch (e: NumberFormatException) { null }
173
239
  }
174
240
  }
@@ -1,44 +1,50 @@
1
1
  package com.blurvibe
2
2
 
3
- import com.facebook.react.uimanager.SimpleViewManager
4
3
  import com.facebook.react.uimanager.ThemedReactContext
4
+ import com.facebook.react.uimanager.ViewGroupManager
5
5
  import com.facebook.react.uimanager.annotations.ReactProp
6
6
 
7
- class BlurVibeViewManager : SimpleViewManager<BlurVibeView>() {
7
+ /**
8
+ * BlurVibeViewManager
9
+ *
10
+ * ViewGroupManager — BlurVibeView (which extends BlurViewGroup/FrameLayout)
11
+ * hosts React children, so we must use ViewGroupManager, not SimpleViewManager.
12
+ */
13
+ class BlurVibeViewManager : ViewGroupManager<BlurVibeView>() {
8
14
 
9
15
  override fun getName() = "BlurVibeView"
10
16
 
11
17
  override fun createViewInstance(context: ThemedReactContext) = BlurVibeView(context)
12
18
 
13
- // Float — matches TS codegen Float type ✅
14
19
  @ReactProp(name = "blurAmount", defaultFloat = 10f)
15
20
  fun setBlurAmount(view: BlurVibeView, amount: Float) {
16
21
  view.setBlurAmount(amount)
17
22
  }
18
23
 
19
- // String — matches TS codegen string type ✅ (no-op on Android)
20
24
  @ReactProp(name = "blurType")
21
25
  fun setBlurType(view: BlurVibeView, type: String?) {
22
- // No-op on Android — blurType is iOS UIBlurEffectStyle only
26
+ // No-op on Android — blurType maps to iOS UIBlurEffectStyle only
23
27
  }
24
28
 
25
- // String — matches TS codegen string type ✅
26
- // We parse hex manually in BlurVibeView for full alpha control
27
- // Do NOT use Int with customType="Color" — RN reorders alpha bytes unexpectedly
28
29
  @ReactProp(name = "overlayColor")
29
30
  fun setOverlayColor(view: BlurVibeView, color: String?) {
30
- view.setOverlayColor(color ?: "transparent")
31
+ view.setOverlayColor(color)
31
32
  }
32
33
 
33
- // String — matches TS codegen string type ✅
34
34
  @ReactProp(name = "reducedTransparencyFallbackColor")
35
35
  fun setReducedTransparencyFallbackColor(view: BlurVibeView, color: String?) {
36
- view.setReducedTransparencyFallbackColor(color ?: "#F2F2F2")
36
+ view.setReducedTransparencyFallbackColor(color)
37
37
  }
38
38
 
39
- // Int32 — matches TS codegen Int32 type ✅
40
39
  @ReactProp(name = "blurRadius", defaultInt = 4)
41
40
  fun setBlurRadius(view: BlurVibeView, radius: Int) {
42
41
  view.setBlurRadius(radius)
43
42
  }
43
+
44
+ override fun onDropViewInstance(view: BlurVibeView) {
45
+ super.onDropViewInstance(view)
46
+ }
47
+
48
+ // React Native's Yoga handles child layout — return false
49
+ override fun needsCustomLayoutForChildren(): Boolean = false
44
50
  }
@@ -1,92 +1,106 @@
1
+ // BlurVibeView.swift
2
+ // UIKit wrapper that hosts the SwiftUI blur view via UIHostingController.
3
+ // Approach mirrors sbaiahmed1/react-native-blur's AdvancedBlurView.
4
+
5
+ import SwiftUI
1
6
  import UIKit
2
7
 
3
8
  @objc(BlurVibeView)
4
9
  class BlurVibeView: UIView {
5
10
 
6
- // MARK: - Private Views
7
- private var blurEffectView: UIVisualEffectView?
8
- private let overlayView = UIView()
11
+ // MARK: - Private
9
12
 
10
- // MARK: - Props
13
+ private var hostingController: UIHostingController<BlurVibeSwiftUIView>?
11
14
 
12
- /// Blur intensity 0–100. Maps to UIBlurEffect intensity via animator.
13
- @objc var blurAmount: NSNumber = 10 { didSet { updateBlur() } }
15
+ // MARK: - Props
14
16
 
15
- /// iOS blur style maps to UIBlurEffectStyle
16
- @objc var blurType: NSString = "light" { didSet { updateBlur() } }
17
+ @objc var blurAmount: NSNumber = 10 { didSet { updateView() } }
18
+ @objc var blurType: NSString = "light" { didSet { updateView() } }
17
19
 
18
- /// Overlay color on top of blur. Works on iOS AND Android.
19
- /// Alpha controls blur visibility like CSS backdrop-filter + background-color.
20
- /// Supports: "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
21
- @objc var overlayColor: NSString = "transparent" { didSet { updateOverlay() } }
20
+ /// Hex overlay color on top of blur works on iOS AND Android.
21
+ /// "#00000000" = transparent (pure blur), "#00000080" = tinted blur
22
+ @objc var overlayColor: NSString = "transparent" { didSet { updateView() } }
22
23
 
23
- /// Fallback color when Reduce Transparency is enabled.
24
- @objc var reducedTransparencyFallbackColor: NSString = "#F2F2F2" { didSet { updateBlur() } }
24
+ @objc var reducedTransparencyFallbackColor: NSString = "#F2F2F2" { didSet { updateView() } }
25
25
 
26
- /// Android-only downscale factor. Accepted on iOS to avoid prop warning — no-op.
26
+ /// Android-only downscale factor accepted here as no-op to avoid prop warning
27
27
  @objc var blurRadius: NSNumber = 4
28
28
 
29
29
  // MARK: - Init
30
- override init(frame: CGRect) { super.init(frame: frame); commonInit() }
31
- required init?(coder: NSCoder) { super.init(coder: coder); commonInit() }
32
30
 
33
- private func commonInit() {
31
+ override init(frame: CGRect) {
32
+ super.init(frame: frame)
33
+ backgroundColor = .clear
34
+ }
35
+
36
+ required init?(coder: NSCoder) {
37
+ super.init(coder: coder)
34
38
  backgroundColor = .clear
35
- clipsToBounds = true
36
- overlayView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
37
- overlayView.isUserInteractionEnabled = false
38
- updateBlur()
39
39
  }
40
40
 
41
41
  // MARK: - Layout
42
+
42
43
  override func layoutSubviews() {
43
44
  super.layoutSubviews()
44
- blurEffectView?.frame = bounds
45
- overlayView.frame = bounds
46
- bringSubviewToFront(overlayView)
47
- for subview in subviews where subview !== blurEffectView && subview !== overlayView {
48
- bringSubviewToFront(subview)
45
+ // Defer hosting controller setup until we have a valid frame
46
+ // Prevents issues with initial render in complex layouts (e.g. FlashList)
47
+ if hostingController == nil && bounds.width > 0 && bounds.height > 0 {
48
+ setupHostingController()
49
+ } else {
50
+ hostingController?.view.frame = bounds
49
51
  }
50
52
  }
51
53
 
52
- // MARK: - Blur
53
- private func updateBlur() {
54
- if UIAccessibility.isReduceTransparencyEnabled {
55
- blurEffectView?.removeFromSuperview()
56
- blurEffectView = nil
57
- backgroundColor = parseColor(reducedTransparencyFallbackColor as String)
58
- ?? UIColor(white: 0.95, alpha: 1)
59
- return
54
+ // MARK: - Hosting Controller
55
+
56
+ private func setupHostingController() {
57
+ // Remove existing hosting controller cleanly
58
+ if let old = hostingController {
59
+ old.view.removeFromSuperview()
60
+ old.removeFromParent()
60
61
  }
61
- backgroundColor = .clear
62
- let effect = UIBlurEffect(style: blurEffectStyle(for: blurType as String))
63
- if let existing = blurEffectView {
64
- existing.effect = effect
62
+ hostingController = nil
63
+
64
+ let swiftUIView = makeSwiftUIView()
65
+ let hosting = UIHostingController(rootView: swiftUIView)
66
+ hosting.view.backgroundColor = .clear
67
+ hosting.view.frame = bounds
68
+ hosting.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
69
+
70
+ // Insert at index 0 — stays behind React children
71
+ if !subviews.isEmpty {
72
+ insertSubview(hosting.view, at: 0)
65
73
  } else {
66
- let newBlurView = UIVisualEffectView(effect: effect)
67
- newBlurView.frame = bounds
68
- newBlurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
69
- blurEffectView = newBlurView
70
- insertSubview(newBlurView, at: 0)
71
- }
72
- if overlayView.superview == nil {
73
- insertSubview(overlayView, aboveSubview: blurEffectView!)
74
+ addSubview(hosting.view)
74
75
  }
75
- updateOverlay()
76
+
77
+ hostingController = hosting
76
78
  }
77
79
 
78
- private func updateOverlay() {
79
- let colorString = overlayColor as String
80
- if colorString.lowercased() == "transparent" {
81
- overlayView.backgroundColor = .clear
82
- } else {
83
- overlayView.backgroundColor = parseColor(colorString) ?? .clear
80
+ private func updateView() {
81
+ if let hosting = hostingController {
82
+ // Update root view without recreating the controller — avoids jank
83
+ hosting.rootView = makeSwiftUIView()
84
+ } else if bounds.width > 0 && bounds.height > 0 {
85
+ setupHostingController()
84
86
  }
85
87
  }
86
88
 
89
+ private func makeSwiftUIView() -> BlurVibeSwiftUIView {
90
+ return BlurVibeSwiftUIView(
91
+ blurAmount: Double(truncating: blurAmount),
92
+ blurStyle: blurStyleFromString(blurType as String),
93
+ overlayColor: parseColor(overlayColor as String) ?? .clear,
94
+ reducedTransparencyFallbackColor: parseColor(reducedTransparencyFallbackColor as String)
95
+ ?? UIColor(white: 0.95, alpha: 1)
96
+ )
97
+ }
98
+
87
99
  // MARK: - Blur Style Map
88
- private func blurEffectStyle(for type: String) -> UIBlurEffect.Style {
100
+
101
+ private func blurStyleFromString(_ type: String) -> UIBlurEffect.Style {
89
102
  switch type {
103
+ case "xlight": return .extraLight
90
104
  case "dark": return .dark
91
105
  case "extraLight": return .extraLight
92
106
  case "regular": return .regular
@@ -111,34 +125,61 @@ class BlurVibeView: UIView {
111
125
  }
112
126
 
113
127
  // MARK: - Color Parser
114
- // Supports: "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
128
+ // Supports: "transparent", named colors, "#RGB", "#RRGGBB", "#RRGGBBAA"
129
+
115
130
  private func parseColor(_ colorString: String) -> UIColor? {
131
+ let s = colorString.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
132
+
133
+ // Named colors
134
+ let namedColors: [String: UIColor] = [
135
+ "transparent": .clear, "clear": .clear,
136
+ "white": .white, "black": .black,
137
+ "red": .red, "green": .green, "blue": .blue,
138
+ "gray": .gray, "grey": .gray,
139
+ ]
140
+ if let named = namedColors[s] { return named }
141
+
142
+ // Hex colors
116
143
  var hex = colorString.trimmingCharacters(in: .whitespacesAndNewlines)
117
144
  guard hex.hasPrefix("#") else { return nil }
118
145
  hex.removeFirst()
146
+
147
+ // Validate hex chars
148
+ let validHex = CharacterSet(charactersIn: "0123456789ABCDEFabcdef")
149
+ guard hex.unicodeScalars.allSatisfy({ validHex.contains($0) }) else { return nil }
150
+
119
151
  var rgbValue: UInt64 = 0
120
152
  Scanner(string: hex).scanHexInt64(&rgbValue)
153
+
121
154
  switch hex.count {
122
- case 3: // #RGB
123
- return UIColor(
124
- red: CGFloat((rgbValue & 0xF00) >> 8) / 15,
125
- green: CGFloat((rgbValue & 0x0F0) >> 4) / 15,
126
- blue: CGFloat( rgbValue & 0x00F ) / 15,
127
- alpha: 1)
155
+ case 3: // #RGB → expand
156
+ let r = (rgbValue & 0xF00) >> 8; let g = (rgbValue & 0x0F0) >> 4; let b = rgbValue & 0x00F
157
+ return UIColor(red: CGFloat(r | (r << 4)) / 255, green: CGFloat(g | (g << 4)) / 255,
158
+ blue: CGFloat(b | (b << 4)) / 255, alpha: 1)
159
+ case 4: // #RGBA expand
160
+ let r = (rgbValue & 0xF000) >> 12; let g = (rgbValue & 0x0F00) >> 8
161
+ let b = (rgbValue & 0x00F0) >> 4; let a = rgbValue & 0x000F
162
+ return UIColor(red: CGFloat(r | (r << 4)) / 255, green: CGFloat(g | (g << 4)) / 255,
163
+ blue: CGFloat(b | (b << 4)) / 255, alpha: CGFloat(a | (a << 4)) / 255)
128
164
  case 6: // #RRGGBB
129
- return UIColor(
130
- red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255,
131
- green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255,
132
- blue: CGFloat( rgbValue & 0x0000FF ) / 255,
133
- alpha: 1)
165
+ return UIColor(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255,
166
+ green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255,
167
+ blue: CGFloat(rgbValue & 0x0000FF) / 255, alpha: 1)
134
168
  case 8: // #RRGGBBAA
135
- return UIColor(
136
- red: CGFloat((rgbValue & 0xFF000000) >> 24) / 255,
137
- green: CGFloat((rgbValue & 0x00FF0000) >> 16) / 255,
138
- blue: CGFloat((rgbValue & 0x0000FF00) >> 8) / 255,
139
- alpha: CGFloat( rgbValue & 0x000000FF ) / 255)
169
+ return UIColor(red: CGFloat((rgbValue & 0xFF000000) >> 24) / 255,
170
+ green: CGFloat((rgbValue & 0x00FF0000) >> 16) / 255,
171
+ blue: CGFloat((rgbValue & 0x0000FF00) >> 8) / 255,
172
+ alpha: CGFloat(rgbValue & 0x000000FF) / 255)
140
173
  default:
141
174
  return nil
142
175
  }
143
176
  }
177
+
178
+ // MARK: - Cleanup
179
+
180
+ deinit {
181
+ hostingController?.view.removeFromSuperview()
182
+ hostingController?.removeFromParent()
183
+ hostingController = nil
184
+ }
144
185
  }
@@ -3,8 +3,17 @@
3
3
 
4
4
  RCT_EXTERN_MODULE(BlurVibeViewManager, RCTViewManager)
5
5
 
6
+ // Float → NSNumber matches TS Float
6
7
  RCT_EXPORT_VIEW_PROPERTY(blurAmount, NSNumber)
8
+
9
+ // String → NSString matches TS string
7
10
  RCT_EXPORT_VIEW_PROPERTY(blurType, NSString)
11
+
12
+ // String → NSString matches TS string
8
13
  RCT_EXPORT_VIEW_PROPERTY(overlayColor, NSString)
14
+
15
+ // String → NSString matches TS string
9
16
  RCT_EXPORT_VIEW_PROPERTY(reducedTransparencyFallbackColor, NSString)
17
+
18
+ // Int32 → NSNumber matches TS Int32 (no-op in Swift)
10
19
  RCT_EXPORT_VIEW_PROPERTY(blurRadius, NSNumber)
@@ -1,5 +1,11 @@
1
1
  import Foundation
2
2
 
3
+ /**
4
+ * BlurVibeViewManager
5
+ *
6
+ * RCTViewManager subclass — registers BlurVibeView with React Native.
7
+ * requiresMainQueueSetup = true because we create UIKit views.
8
+ */
3
9
  @objc(BlurVibeViewManager)
4
10
  class BlurVibeViewManager: RCTViewManager {
5
11
 
@@ -10,8 +16,4 @@ class BlurVibeViewManager: RCTViewManager {
10
16
  override static func requiresMainQueueSetup() -> Bool {
11
17
  return true
12
18
  }
13
-
14
- @objc override func constantsToExport() -> [AnyHashable: Any]! {
15
- return [:]
16
- }
17
19
  }
@@ -0,0 +1,85 @@
1
+ // BlurEffectView.swift
2
+ // UIVisualEffectView with UIViewPropertyAnimator for custom blur intensity.
3
+ // This is the ONLY correct way to achieve custom blur radius on iOS.
4
+ // UIBlurEffect itself ignores any custom intensity — the animator interpolates it.
5
+
6
+ import SwiftUI
7
+ import UIKit
8
+
9
+ // MARK: - UIKit blur with custom intensity
10
+
11
+ class BlurEffectView: UIVisualEffectView {
12
+ private var animator: UIViewPropertyAnimator?
13
+ private var blurStyle: UIBlurEffect.Style = .systemMaterial
14
+ private var intensity: Double = 1.0
15
+
16
+ override init(effect: UIVisualEffect?) {
17
+ super.init(effect: effect)
18
+ setupBlur()
19
+ }
20
+
21
+ required init?(coder: NSCoder) {
22
+ super.init(coder: coder)
23
+ setupBlur()
24
+ }
25
+
26
+ func updateBlur(style: UIBlurEffect.Style, intensity: Double) {
27
+ // Skip expensive animator recreation when nothing changed
28
+ guard style != self.blurStyle || intensity != self.intensity else { return }
29
+ self.blurStyle = style
30
+ self.intensity = intensity
31
+ setupBlur()
32
+ }
33
+
34
+ override func didMoveToWindow() {
35
+ super.didMoveToWindow()
36
+ guard window != nil else { return }
37
+ // UIKit resumes paused CAAnimations when view re-joins a window.
38
+ // Re-pause and re-lock the fraction to prevent blur drifting to full intensity.
39
+ animator?.pauseAnimation()
40
+ animator?.fractionComplete = intensity
41
+ }
42
+
43
+ private func setupBlur() {
44
+ if let existing = animator, existing.state == .active {
45
+ existing.stopAnimation(true)
46
+ }
47
+ animator = nil
48
+ effect = nil
49
+
50
+ let newAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear)
51
+ newAnimator.addAnimations { [weak self] in
52
+ self?.effect = UIBlurEffect(style: self?.blurStyle ?? .systemMaterial)
53
+ }
54
+ // pausesOnCompletion: keeps animator .active even at fraction 1.0
55
+ // so didMoveToWindow can always safely call pauseAnimation()
56
+ newAnimator.pausesOnCompletion = true
57
+ newAnimator.startAnimation()
58
+ newAnimator.pauseAnimation()
59
+ newAnimator.fractionComplete = intensity
60
+ animator = newAnimator
61
+ }
62
+
63
+ deinit {
64
+ if let animator = animator, animator.state == .active {
65
+ animator.stopAnimation(true)
66
+ }
67
+ }
68
+ }
69
+
70
+ // MARK: - SwiftUI wrapper for BlurEffectView
71
+
72
+ struct BlurVibeEffect: UIViewRepresentable {
73
+ var style: UIBlurEffect.Style = .systemMaterial
74
+ var intensity: Double = 1.0
75
+
76
+ func makeUIView(context: Context) -> BlurEffectView {
77
+ let view = BlurEffectView(effect: nil)
78
+ view.updateBlur(style: style, intensity: intensity)
79
+ return view
80
+ }
81
+
82
+ func updateUIView(_ uiView: BlurEffectView, context: Context) {
83
+ uiView.updateBlur(style: style, intensity: intensity)
84
+ }
85
+ }
@@ -0,0 +1,46 @@
1
+ // BlurVibeSwiftUIView.swift
2
+ // SwiftUI view that composes blur effect + overlay color.
3
+
4
+ import SwiftUI
5
+ import UIKit
6
+
7
+ struct BlurVibeSwiftUIView: View {
8
+ let blurAmount: Double
9
+ let blurStyle: UIBlurEffect.Style
10
+ let overlayColor: UIColor
11
+ let reducedTransparencyFallbackColor: UIColor
12
+
13
+ private let isReducedTransparencyEnabled = UIAccessibility.isReduceTransparencyEnabled
14
+
15
+ // Map 0–100 blurAmount to 0.0–1.0 animator fraction
16
+ private var blurIntensity: Double {
17
+ (blurAmount / 100.0).clamped(to: 0.0...1.0)
18
+ }
19
+
20
+ var body: some View {
21
+ if isReducedTransparencyEnabled {
22
+ // Accessibility: Reduce Transparency is ON — show solid fallback color
23
+ Rectangle()
24
+ .fill(Color(reducedTransparencyFallbackColor))
25
+ } else {
26
+ ZStack {
27
+ // Layer 1: backdrop blur (what's behind this view gets blurred)
28
+ BlurVibeEffect(style: blurStyle, intensity: blurIntensity)
29
+
30
+ // Layer 2: overlay color with alpha on top of blur
31
+ // This is our overlayColor prop — same as CSS background-color with alpha
32
+ // "#00000000" = transparent = pure blur shows through
33
+ // "#00000080" = 50% black tint over blur
34
+ // "#000000FF" = fully opaque = blur hidden
35
+ Rectangle()
36
+ .fill(Color(overlayColor))
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ extension Comparable {
43
+ func clamped(to range: ClosedRange<Self>) -> Self {
44
+ min(max(self, range.lowerBound), range.upperBound)
45
+ }
46
+ }
@@ -9,13 +9,15 @@ Pod::Spec.new do |s|
9
9
  s.homepage = package["homepage"]
10
10
  s.license = package["license"]
11
11
  s.authors = package["author"]
12
- s.platforms = { :ios => "13.0" }
12
+ s.platforms = { :ios => "14.0" }
13
13
  s.source = { :git => "https://github.com/I-am-Pritam-20/react-native-blur-vibe.git", :tag => "#{s.version}" }
14
+
15
+ # Include all Swift and ObjC files in ios/ and ios/Views/
14
16
  s.source_files = "ios/**/*.{h,m,mm,swift}"
17
+
15
18
  s.requires_arc = true
16
19
 
17
20
  s.dependency "React-Core"
18
21
 
19
- # New Architecture (Fabric) support
20
22
  install_modules_dependencies(s)
21
23
  end
@@ -1,14 +1,37 @@
1
- // @ts-ignore - codegenNativeComponent is available at runtime in RN 0.71+
1
+ // @ts-ignore - internal RN path, exists at runtime
2
2
  import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
3
3
  import type { HostComponent, ViewProps } from 'react-native';
4
- // @ts-ignore - CodegenTypes available at runtime
4
+ // @ts-ignore - internal RN path, exists at runtime
5
5
  import type { Float, Int32 } from 'react-native/Libraries/Types/CodegenTypes';
6
6
 
7
+ /**
8
+ * NativeComponent codegen spec for BlurVibeView.
9
+ *
10
+ * Type mapping (JS → Native):
11
+ * Float → NSNumber (iOS) / Float (Android)
12
+ * string → NSString (iOS) / String (Android)
13
+ * Int32 → NSNumber (iOS) / Int (Android)
14
+ *
15
+ * Color props (overlayColor, reducedTransparencyFallbackColor) use
16
+ * plain `string` — NOT the RN `ColorValue` type — because we parse
17
+ * hex manually on both platforms for full alpha channel control.
18
+ * Using ColorValue would trigger RN's color normalization which
19
+ * reorders alpha bytes and breaks #RRGGBBAA format.
20
+ */
7
21
  export interface NativeBlurVibeViewProps extends ViewProps {
22
+ // 0–100 blur intensity
8
23
  blurAmount?: Float;
24
+
25
+ // iOS UIBlurEffectStyle name — no-op on Android
9
26
  blurType?: string;
27
+
28
+ // Hex color string with alpha — "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
10
29
  overlayColor?: string;
30
+
31
+ // Fallback when blur unavailable (Reduce Transparency / old API)
11
32
  reducedTransparencyFallbackColor?: string;
33
+
34
+ // Android downscale factor 1–8 — no-op on iOS
12
35
  blurRadius?: Int32;
13
36
  }
14
37
 
@@ -1,14 +1,37 @@
1
- // @ts-ignore - codegenNativeComponent is available at runtime in RN 0.71+
1
+ // @ts-ignore - internal RN path, exists at runtime
2
2
  import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
3
3
  import type { HostComponent, ViewProps } from 'react-native';
4
- // @ts-ignore - CodegenTypes available at runtime
4
+ // @ts-ignore - internal RN path, exists at runtime
5
5
  import type { Float, Int32 } from 'react-native/Libraries/Types/CodegenTypes';
6
6
 
7
+ /**
8
+ * NativeComponent codegen spec for BlurVibeView.
9
+ *
10
+ * Type mapping (JS → Native):
11
+ * Float → NSNumber (iOS) / Float (Android)
12
+ * string → NSString (iOS) / String (Android)
13
+ * Int32 → NSNumber (iOS) / Int (Android)
14
+ *
15
+ * Color props (overlayColor, reducedTransparencyFallbackColor) use
16
+ * plain `string` — NOT the RN `ColorValue` type — because we parse
17
+ * hex manually on both platforms for full alpha channel control.
18
+ * Using ColorValue would trigger RN's color normalization which
19
+ * reorders alpha bytes and breaks #RRGGBBAA format.
20
+ */
7
21
  export interface NativeBlurVibeViewProps extends ViewProps {
22
+ // 0–100 blur intensity
8
23
  blurAmount?: Float;
24
+
25
+ // iOS UIBlurEffectStyle name — no-op on Android
9
26
  blurType?: string;
27
+
28
+ // Hex color string with alpha — "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
10
29
  overlayColor?: string;
30
+
31
+ // Fallback when blur unavailable (Reduce Transparency / old API)
11
32
  reducedTransparencyFallbackColor?: string;
33
+
34
+ // Android downscale factor 1–8 — no-op on iOS
12
35
  blurRadius?: Int32;
13
36
  }
14
37
 
@@ -1,5 +1,19 @@
1
1
  import type { HostComponent, ViewProps } from 'react-native';
2
2
  import type { Float, Int32 } from 'react-native/Libraries/Types/CodegenTypes';
3
+ /**
4
+ * NativeComponent codegen spec for BlurVibeView.
5
+ *
6
+ * Type mapping (JS → Native):
7
+ * Float → NSNumber (iOS) / Float (Android)
8
+ * string → NSString (iOS) / String (Android)
9
+ * Int32 → NSNumber (iOS) / Int (Android)
10
+ *
11
+ * Color props (overlayColor, reducedTransparencyFallbackColor) use
12
+ * plain `string` — NOT the RN `ColorValue` type — because we parse
13
+ * hex manually on both platforms for full alpha channel control.
14
+ * Using ColorValue would trigger RN's color normalization which
15
+ * reorders alpha bytes and breaks #RRGGBBAA format.
16
+ */
3
17
  export interface NativeBlurVibeViewProps extends ViewProps {
4
18
  blurAmount?: Float;
5
19
  blurType?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"BlurVibeViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../../src/BlurVibeViewNativeComponent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAC;AAE9E,MAAM,WAAW,uBAAwB,SAAQ,SAAS;IACxD,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB;wBAII,aAAa,CAAC,uBAAuB,CAAC;AAF3C,wBAE4C"}
1
+ {"version":3,"file":"BlurVibeViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../../src/BlurVibeViewNativeComponent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAC;AAE9E;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,uBAAwB,SAAQ,SAAS;IAExD,UAAU,CAAC,EAAE,KAAK,CAAC;IAGnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAG1C,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB;wBAII,aAAa,CAAC,uBAAuB,CAAC;AAF3C,wBAE4C"}
@@ -1,5 +1,19 @@
1
1
  import type { HostComponent, ViewProps } from 'react-native';
2
2
  import type { Float, Int32 } from 'react-native/Libraries/Types/CodegenTypes';
3
+ /**
4
+ * NativeComponent codegen spec for BlurVibeView.
5
+ *
6
+ * Type mapping (JS → Native):
7
+ * Float → NSNumber (iOS) / Float (Android)
8
+ * string → NSString (iOS) / String (Android)
9
+ * Int32 → NSNumber (iOS) / Int (Android)
10
+ *
11
+ * Color props (overlayColor, reducedTransparencyFallbackColor) use
12
+ * plain `string` — NOT the RN `ColorValue` type — because we parse
13
+ * hex manually on both platforms for full alpha channel control.
14
+ * Using ColorValue would trigger RN's color normalization which
15
+ * reorders alpha bytes and breaks #RRGGBBAA format.
16
+ */
3
17
  export interface NativeBlurVibeViewProps extends ViewProps {
4
18
  blurAmount?: Float;
5
19
  blurType?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"BlurVibeViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../../src/BlurVibeViewNativeComponent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAC;AAE9E,MAAM,WAAW,uBAAwB,SAAQ,SAAS;IACxD,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB;wBAII,aAAa,CAAC,uBAAuB,CAAC;AAF3C,wBAE4C"}
1
+ {"version":3,"file":"BlurVibeViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../../src/BlurVibeViewNativeComponent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAC;AAE9E;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,uBAAwB,SAAQ,SAAS;IAExD,UAAU,CAAC,EAAE,KAAK,CAAC;IAGnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAG1C,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB;wBAII,aAAa,CAAC,uBAAuB,CAAC;AAF3C,wBAE4C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-blur-vibe",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "React Native package implementing Blur View in iOS and Android",
5
5
  "main": "./lib/commonjs/index.js",
6
6
  "module": "./lib/module/index.js",
@@ -1,14 +1,37 @@
1
- // @ts-ignore - codegenNativeComponent is available at runtime in RN 0.71+
1
+ // @ts-ignore - internal RN path, exists at runtime
2
2
  import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
3
3
  import type { HostComponent, ViewProps } from 'react-native';
4
- // @ts-ignore - CodegenTypes available at runtime
4
+ // @ts-ignore - internal RN path, exists at runtime
5
5
  import type { Float, Int32 } from 'react-native/Libraries/Types/CodegenTypes';
6
6
 
7
+ /**
8
+ * NativeComponent codegen spec for BlurVibeView.
9
+ *
10
+ * Type mapping (JS → Native):
11
+ * Float → NSNumber (iOS) / Float (Android)
12
+ * string → NSString (iOS) / String (Android)
13
+ * Int32 → NSNumber (iOS) / Int (Android)
14
+ *
15
+ * Color props (overlayColor, reducedTransparencyFallbackColor) use
16
+ * plain `string` — NOT the RN `ColorValue` type — because we parse
17
+ * hex manually on both platforms for full alpha channel control.
18
+ * Using ColorValue would trigger RN's color normalization which
19
+ * reorders alpha bytes and breaks #RRGGBBAA format.
20
+ */
7
21
  export interface NativeBlurVibeViewProps extends ViewProps {
22
+ // 0–100 blur intensity
8
23
  blurAmount?: Float;
24
+
25
+ // iOS UIBlurEffectStyle name — no-op on Android
9
26
  blurType?: string;
27
+
28
+ // Hex color string with alpha — "transparent", "#RGB", "#RRGGBB", "#RRGGBBAA"
10
29
  overlayColor?: string;
30
+
31
+ // Fallback when blur unavailable (Reduce Transparency / old API)
11
32
  reducedTransparencyFallbackColor?: string;
33
+
34
+ // Android downscale factor 1–8 — no-op on iOS
12
35
  blurRadius?: Int32;
13
36
  }
14
37