react-native-gradient-mask 0.1.0-beta.1
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/.eslintrc.js +5 -0
- package/PUBLISHING.md +171 -0
- package/README.md +360 -0
- package/android/build.gradle +43 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/gradientmask/GradientMaskModule.kt +33 -0
- package/android/src/main/java/expo/modules/gradientmask/GradientMaskView.kt +198 -0
- package/build/AnimatedGradientMaskView.d.ts +36 -0
- package/build/AnimatedGradientMaskView.d.ts.map +1 -0
- package/build/AnimatedGradientMaskView.js +37 -0
- package/build/AnimatedGradientMaskView.js.map +1 -0
- package/build/AnimatedGradientMaskView.web.d.ts +17 -0
- package/build/AnimatedGradientMaskView.web.d.ts.map +1 -0
- package/build/AnimatedGradientMaskView.web.js +100 -0
- package/build/AnimatedGradientMaskView.web.js.map +1 -0
- package/build/GradientMask.types.d.ts +38 -0
- package/build/GradientMask.types.d.ts.map +1 -0
- package/build/GradientMask.types.js +2 -0
- package/build/GradientMask.types.js.map +1 -0
- package/build/GradientMaskModule.d.ts +3 -0
- package/build/GradientMaskModule.d.ts.map +1 -0
- package/build/GradientMaskModule.js +4 -0
- package/build/GradientMaskModule.js.map +1 -0
- package/build/GradientMaskModule.web.d.ts +3 -0
- package/build/GradientMaskModule.web.d.ts.map +1 -0
- package/build/GradientMaskModule.web.js +3 -0
- package/build/GradientMaskModule.web.js.map +1 -0
- package/build/GradientMaskView.d.ts +4 -0
- package/build/GradientMaskView.d.ts.map +1 -0
- package/build/GradientMaskView.js +7 -0
- package/build/GradientMaskView.js.map +1 -0
- package/build/GradientMaskView.web.d.ts +8 -0
- package/build/GradientMaskView.web.d.ts.map +1 -0
- package/build/GradientMaskView.web.js +99 -0
- package/build/GradientMaskView.web.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/GradientMask.podspec +29 -0
- package/ios/GradientMaskModule.swift +29 -0
- package/ios/GradientMaskView.swift +133 -0
- package/package.json +83 -0
- package/rn-gradient-mask-design.md +657 -0
- package/src/AnimatedGradientMaskView.tsx +60 -0
- package/src/AnimatedGradientMaskView.web.tsx +149 -0
- package/src/GradientMask.types.ts +43 -0
- package/src/GradientMaskModule.ts +4 -0
- package/src/GradientMaskModule.web.ts +2 -0
- package/src/GradientMaskView.tsx +11 -0
- package/src/GradientMaskView.web.tsx +129 -0
- package/src/index.ts +7 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GradientMaskView.web.js","sourceRoot":"","sources":["../src/GradientMaskView.web.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport { View, StyleSheet } from 'react-native';\n\nimport { GradientMaskViewProps } from './GradientMask.types';\n\n/**\n * 將 processColor 處理過的顏色值轉回 rgba 字串\n */\nfunction colorToRgba(color: number | null): string {\n if (color === null || color === undefined) {\n return 'rgba(0, 0, 0, 0)';\n }\n\n // processColor 在 web 上回傳的格式是 AARRGGBB (32-bit integer)\n const intValue = color >>> 0; // 確保是 unsigned\n const a = ((intValue >> 24) & 0xff) / 255;\n const r = (intValue >> 16) & 0xff;\n const g = (intValue >> 8) & 0xff;\n const b = intValue & 0xff;\n\n return `rgba(${r}, ${g}, ${b}, ${a})`;\n}\n\n/**\n * 根據 direction 取得 CSS linear-gradient 的方向\n */\nfunction getGradientDirection(direction: GradientMaskViewProps['direction']): string {\n switch (direction) {\n case 'top':\n return 'to bottom'; // 頂部透明 → 底部不透明\n case 'bottom':\n return 'to top'; // 底部透明 → 頂部不透明\n case 'left':\n return 'to right'; // 左側透明 → 右側不透明\n case 'right':\n return 'to left'; // 右側透明 → 左側不透明\n default:\n return 'to bottom';\n }\n}\n\n/**\n * 建立 CSS linear-gradient 字串\n */\nfunction buildGradientString(\n colors: (number | null)[],\n locations: number[],\n direction: GradientMaskViewProps['direction']\n): string {\n const gradientDirection = getGradientDirection(direction);\n\n const colorStops = colors.map((color, index) => {\n const rgba = colorToRgba(color);\n const location = locations[index] !== undefined ? locations[index] * 100 : (index / (colors.length - 1)) * 100;\n return `${rgba} ${location}%`;\n });\n\n return `linear-gradient(${gradientDirection}, ${colorStops.join(', ')})`;\n}\n\n/**\n * 根據 maskOpacity 調整顏色的 alpha 值\n * maskOpacity = 0 時,所有顏色變為完全不透明(無遮罩效果)\n * maskOpacity = 1 時,保持原本的 alpha 值\n */\nfunction adjustColorsForOpacity(\n colors: (number | null)[],\n maskOpacity: number\n): (number | null)[] {\n if (maskOpacity >= 1) return colors;\n if (maskOpacity <= 0) {\n // 全部變成不透明黑色,表示內容完全可見\n return colors.map(() => 0xff000000);\n }\n\n return colors.map((color) => {\n if (color === null || color === undefined) return color;\n const intValue = color >>> 0;\n const a = ((intValue >> 24) & 0xff) / 255;\n const r = (intValue >> 16) & 0xff;\n const g = (intValue >> 8) & 0xff;\n const b = intValue & 0xff;\n // 根據 maskOpacity 調整 alpha:當 maskOpacity = 0 時 alpha 趨近於 1(完全可見)\n const adjustedAlpha = a + (1 - a) * (1 - maskOpacity);\n return ((Math.round(adjustedAlpha * 255) << 24) | (r << 16) | (g << 8) | b) >>> 0;\n });\n}\n\n/**\n * GradientMaskView - Web 實作\n * 使用 CSS mask-image 搭配 linear-gradient 實現漸層遮罩效果\n */\nexport default function GradientMaskView(props: GradientMaskViewProps) {\n const {\n colors,\n locations,\n direction = 'top',\n maskOpacity = 1,\n style,\n children,\n } = props;\n\n const maskStyle = React.useMemo(() => {\n if (maskOpacity <= 0) {\n // 無遮罩效果\n return {};\n }\n\n const adjustedColors = adjustColorsForOpacity(colors, maskOpacity);\n const gradientString = buildGradientString(adjustedColors, locations, direction);\n\n return {\n WebkitMaskImage: gradientString,\n maskImage: gradientString,\n };\n }, [colors, locations, direction, maskOpacity]);\n\n return (\n <View style={[styles.container, style, maskStyle as any]}>\n {children}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n overflow: 'hidden',\n },\n});\n"]}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default } from './GradientMaskModule';
|
|
2
|
+
export { default as GradientMaskView } from './GradientMaskView';
|
|
3
|
+
export { default as AnimatedGradientMaskView } from './AnimatedGradientMaskView';
|
|
4
|
+
export type { AnimatedGradientMaskViewProps } from './AnimatedGradientMaskView';
|
|
5
|
+
export * from './GradientMask.types';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACjF,YAAY,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAChF,cAAc,sBAAsB,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Reexport the native module. On web, it will be resolved to GradientMaskModule.web.ts
|
|
2
|
+
// and on native platforms to GradientMaskModule.ts
|
|
3
|
+
export { default } from './GradientMaskModule';
|
|
4
|
+
export { default as GradientMaskView } from './GradientMaskView';
|
|
5
|
+
export { default as AnimatedGradientMaskView } from './AnimatedGradientMaskView';
|
|
6
|
+
export * from './GradientMask.types';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,mDAAmD;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEjF,cAAc,sBAAsB,CAAC","sourcesContent":["// Reexport the native module. On web, it will be resolved to GradientMaskModule.web.ts\n// and on native platforms to GradientMaskModule.ts\nexport { default } from './GradientMaskModule';\nexport { default as GradientMaskView } from './GradientMaskView';\nexport { default as AnimatedGradientMaskView } from './AnimatedGradientMaskView';\nexport type { AnimatedGradientMaskViewProps } from './AnimatedGradientMaskView';\nexport * from './GradientMask.types';\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'GradientMask'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.description = package['description']
|
|
10
|
+
s.license = package['license']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.homepage = package['homepage']
|
|
13
|
+
s.platforms = {
|
|
14
|
+
:ios => '15.1',
|
|
15
|
+
:tvos => '15.1'
|
|
16
|
+
}
|
|
17
|
+
s.swift_version = '5.9'
|
|
18
|
+
s.source = { git: 'https://github.com/CS6/s33' }
|
|
19
|
+
s.static_framework = true
|
|
20
|
+
|
|
21
|
+
s.dependency 'ExpoModulesCore'
|
|
22
|
+
|
|
23
|
+
# Swift/Objective-C compatibility
|
|
24
|
+
s.pod_target_xcconfig = {
|
|
25
|
+
'DEFINES_MODULE' => 'YES',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
public class GradientMaskModule: Module {
|
|
4
|
+
public func definition() -> ModuleDefinition {
|
|
5
|
+
Name("GradientMask")
|
|
6
|
+
|
|
7
|
+
View(GradientMaskView.self) {
|
|
8
|
+
// colors: List of processed colors (from processColor in JS)
|
|
9
|
+
Prop("colors") { (view: GradientMaskView, colors: [Int]?) in
|
|
10
|
+
view.setColors(colors)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// locations: Array of floats (0-1) for gradient stops
|
|
14
|
+
Prop("locations") { (view: GradientMaskView, locations: [Double]?) in
|
|
15
|
+
view.setLocations(locations)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// direction: "top" | "bottom" | "left" | "right"
|
|
19
|
+
Prop("direction") { (view: GradientMaskView, direction: String?) in
|
|
20
|
+
view.setDirection(direction ?? "top")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// maskOpacity: 0 = no mask effect, 1 = full gradient mask
|
|
24
|
+
Prop("maskOpacity") { (view: GradientMaskView, opacity: Double?) in
|
|
25
|
+
view.setMaskOpacity(opacity ?? 1.0)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
class GradientMaskView: ExpoView {
|
|
5
|
+
|
|
6
|
+
// MARK: - Properties
|
|
7
|
+
|
|
8
|
+
private let gradientMaskLayer = CAGradientLayer()
|
|
9
|
+
/// Used to overlay gradient when maskOpacity = 0, making content fully visible
|
|
10
|
+
private let solidMaskLayer = CALayer()
|
|
11
|
+
|
|
12
|
+
private var _colors: [CGColor] = [
|
|
13
|
+
UIColor.clear.cgColor,
|
|
14
|
+
UIColor.black.cgColor
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
private var _locations: [NSNumber] = [0, 1]
|
|
18
|
+
private var _direction: String = "top"
|
|
19
|
+
private var _maskOpacity: CGFloat = 1.0
|
|
20
|
+
|
|
21
|
+
// MARK: - Initialization
|
|
22
|
+
|
|
23
|
+
required init(appContext: AppContext? = nil) {
|
|
24
|
+
super.init(appContext: appContext)
|
|
25
|
+
setupView()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private func setupView() {
|
|
29
|
+
backgroundColor = .clear
|
|
30
|
+
clipsToBounds = true
|
|
31
|
+
|
|
32
|
+
// Initialize gradient mask layer
|
|
33
|
+
gradientMaskLayer.colors = _colors
|
|
34
|
+
gradientMaskLayer.locations = _locations
|
|
35
|
+
|
|
36
|
+
// Initialize solid mask layer (opaque black, used to overlay gradient)
|
|
37
|
+
solidMaskLayer.backgroundColor = UIColor.black.cgColor
|
|
38
|
+
// Initially solidMaskLayer is invisible (opacity = 1 - maskOpacity = 0)
|
|
39
|
+
// So mask effect shows full gradient
|
|
40
|
+
solidMaskLayer.opacity = 0.0
|
|
41
|
+
|
|
42
|
+
updateGradientDirection()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// MARK: - Props Setters
|
|
46
|
+
|
|
47
|
+
func setColors(_ colors: [Int]?) {
|
|
48
|
+
guard let colors = colors else { return }
|
|
49
|
+
_colors = colors.map { colorValue -> CGColor in
|
|
50
|
+
let intValue = UInt32(bitPattern: Int32(truncatingIfNeeded: colorValue))
|
|
51
|
+
return UIColor(
|
|
52
|
+
red: CGFloat((intValue >> 16) & 0xFF) / 255.0,
|
|
53
|
+
green: CGFloat((intValue >> 8) & 0xFF) / 255.0,
|
|
54
|
+
blue: CGFloat(intValue & 0xFF) / 255.0,
|
|
55
|
+
alpha: CGFloat((intValue >> 24) & 0xFF) / 255.0
|
|
56
|
+
).cgColor
|
|
57
|
+
}
|
|
58
|
+
updateGradientMask()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func setLocations(_ locations: [Double]?) {
|
|
62
|
+
guard let locations = locations else { return }
|
|
63
|
+
_locations = locations.map { NSNumber(value: $0) }
|
|
64
|
+
updateGradientMask()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func setDirection(_ direction: String) {
|
|
68
|
+
_direction = direction
|
|
69
|
+
updateGradientDirection()
|
|
70
|
+
updateGradientMask()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func setMaskOpacity(_ opacity: Double) {
|
|
74
|
+
_maskOpacity = CGFloat(opacity)
|
|
75
|
+
updateMaskOpacity()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// MARK: - Layout
|
|
79
|
+
|
|
80
|
+
override func layoutSubviews() {
|
|
81
|
+
super.layoutSubviews()
|
|
82
|
+
updateGradientMask()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// MARK: - Gradient Mask
|
|
86
|
+
|
|
87
|
+
private func updateGradientMask() {
|
|
88
|
+
gradientMaskLayer.frame = bounds
|
|
89
|
+
solidMaskLayer.frame = bounds
|
|
90
|
+
|
|
91
|
+
gradientMaskLayer.colors = _colors
|
|
92
|
+
gradientMaskLayer.locations = _locations
|
|
93
|
+
|
|
94
|
+
updateGradientDirection()
|
|
95
|
+
|
|
96
|
+
// Create composite mask: gradient + solid overlay
|
|
97
|
+
let compositeMask = CALayer()
|
|
98
|
+
compositeMask.frame = bounds
|
|
99
|
+
compositeMask.addSublayer(gradientMaskLayer)
|
|
100
|
+
compositeMask.addSublayer(solidMaskLayer)
|
|
101
|
+
|
|
102
|
+
layer.mask = compositeMask
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private func updateMaskOpacity() {
|
|
106
|
+
CATransaction.begin()
|
|
107
|
+
CATransaction.setDisableActions(true)
|
|
108
|
+
// maskOpacity = 0 → solidMaskLayer.opacity = 1 → content fully visible
|
|
109
|
+
// maskOpacity = 1 → solidMaskLayer.opacity = 0 → full gradient effect
|
|
110
|
+
solidMaskLayer.opacity = Float(1.0 - _maskOpacity)
|
|
111
|
+
CATransaction.commit()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private func updateGradientDirection() {
|
|
115
|
+
switch _direction {
|
|
116
|
+
case "top":
|
|
117
|
+
gradientMaskLayer.startPoint = CGPoint(x: 0.5, y: 0)
|
|
118
|
+
gradientMaskLayer.endPoint = CGPoint(x: 0.5, y: 1)
|
|
119
|
+
case "bottom":
|
|
120
|
+
gradientMaskLayer.startPoint = CGPoint(x: 0.5, y: 1)
|
|
121
|
+
gradientMaskLayer.endPoint = CGPoint(x: 0.5, y: 0)
|
|
122
|
+
case "left":
|
|
123
|
+
gradientMaskLayer.startPoint = CGPoint(x: 0, y: 0.5)
|
|
124
|
+
gradientMaskLayer.endPoint = CGPoint(x: 1, y: 0.5)
|
|
125
|
+
case "right":
|
|
126
|
+
gradientMaskLayer.startPoint = CGPoint(x: 1, y: 0.5)
|
|
127
|
+
gradientMaskLayer.endPoint = CGPoint(x: 0, y: 0.5)
|
|
128
|
+
default:
|
|
129
|
+
gradientMaskLayer.startPoint = CGPoint(x: 0.5, y: 0)
|
|
130
|
+
gradientMaskLayer.endPoint = CGPoint(x: 0.5, y: 1)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-gradient-mask",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"description": "A native gradient mask component for React Native with Reanimated animation support. Supports iOS, Android, and Web platforms.",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"module": "build/index.js",
|
|
7
|
+
"types": "build/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "expo-module build",
|
|
11
|
+
"clean": "expo-module clean",
|
|
12
|
+
"lint": "expo-module lint",
|
|
13
|
+
"test": "expo-module test",
|
|
14
|
+
"prepare": "expo-module prepare",
|
|
15
|
+
"prepublishOnly": "expo-module prepublishOnly",
|
|
16
|
+
"expo-module": "expo-module",
|
|
17
|
+
"open:ios": "xed example/ios",
|
|
18
|
+
"open:android": "open -a \"Android Studio\" example/android"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"react-native",
|
|
22
|
+
"expo",
|
|
23
|
+
"gradient",
|
|
24
|
+
"mask",
|
|
25
|
+
"fade",
|
|
26
|
+
"opacity",
|
|
27
|
+
"animation",
|
|
28
|
+
"reanimated",
|
|
29
|
+
"native",
|
|
30
|
+
"ios",
|
|
31
|
+
"android",
|
|
32
|
+
"web",
|
|
33
|
+
"gradient-mask",
|
|
34
|
+
"fade-effect",
|
|
35
|
+
"scroll-fade",
|
|
36
|
+
"list-mask"
|
|
37
|
+
],
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/CS6/react-native-gradient-mask.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/CS6/react-native-gradient-mask/issues"
|
|
44
|
+
},
|
|
45
|
+
"author": {
|
|
46
|
+
"name": "DaYuan Lin",
|
|
47
|
+
"email": "dayuan.code@gmail.com",
|
|
48
|
+
"url": "https://github.com/CS6"
|
|
49
|
+
},
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"homepage": "https://github.com/CS6/react-native-gradient-mask#readme",
|
|
52
|
+
"funding": {
|
|
53
|
+
"type": "github",
|
|
54
|
+
"url": "https://github.com/sponsors/CS6"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18.0.0"
|
|
58
|
+
},
|
|
59
|
+
"os": [
|
|
60
|
+
"darwin",
|
|
61
|
+
"linux",
|
|
62
|
+
"win32"
|
|
63
|
+
],
|
|
64
|
+
"dependencies": {},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/react": "~19.1.0",
|
|
67
|
+
"expo": "^54.0.27",
|
|
68
|
+
"expo-module-scripts": "^5.0.8",
|
|
69
|
+
"react-native": "0.81.5",
|
|
70
|
+
"react-native-reanimated": "^3.0.0"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"expo": "*",
|
|
74
|
+
"react": "*",
|
|
75
|
+
"react-native": "*",
|
|
76
|
+
"react-native-reanimated": ">=3.0.0"
|
|
77
|
+
},
|
|
78
|
+
"peerDependenciesMeta": {
|
|
79
|
+
"react-native-reanimated": {
|
|
80
|
+
"optional": true
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|