react-native-smooth-clip-view 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.
- package/LICENSE +20 -0
- package/README.md +170 -0
- package/SmoothClipView.podspec +24 -0
- package/android/build.gradle +63 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +93 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +190 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +104 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipViewPackage.kt +21 -0
- package/ios/SmoothClipGeometry.h +41 -0
- package/ios/SmoothClipView.h +9 -0
- package/ios/SmoothClipView.mm +224 -0
- package/lib/module/SmoothClipView.js +35 -0
- package/lib/module/SmoothClipView.js.map +1 -0
- package/lib/module/SmoothClipView.web.js +38 -0
- package/lib/module/SmoothClipView.web.js.map +1 -0
- package/lib/module/SmoothClipViewNativeComponent.ts +34 -0
- package/lib/module/geometry.js +41 -0
- package/lib/module/geometry.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/index.web.js +5 -0
- package/lib/module/index.web.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/webClipPath.js +25 -0
- package/lib/module/webClipPath.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/SmoothClipView.d.ts +12 -0
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipView.web.d.ts +12 -0
- package/lib/typescript/src/SmoothClipView.web.d.ts.map +1 -0
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +15 -0
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/geometry.d.ts +19 -0
- package/lib/typescript/src/geometry.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/index.web.d.ts +5 -0
- package/lib/typescript/src/index.web.d.ts.map +1 -0
- package/lib/typescript/src/webClipPath.d.ts +7 -0
- package/lib/typescript/src/webClipPath.d.ts.map +1 -0
- package/package.json +201 -0
- package/src/SmoothClipView.tsx +72 -0
- package/src/SmoothClipView.web.tsx +62 -0
- package/src/SmoothClipViewNativeComponent.ts +34 -0
- package/src/geometry.ts +83 -0
- package/src/index.ts +4 -0
- package/src/index.web.ts +4 -0
- package/src/webClipPath.ts +30 -0
package/package.json
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-smooth-clip-view",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Layout-free animated rounded clipping for React Native Fabric",
|
|
5
|
+
"main": "./lib/module/index.js",
|
|
6
|
+
"module": "./lib/module/index.js",
|
|
7
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
8
|
+
"react-native": "./src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
12
|
+
"react-native": "./src/index.ts",
|
|
13
|
+
"react-native-smooth-clip-view-source": "./src/index.ts",
|
|
14
|
+
"default": "./lib/module/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"lib",
|
|
21
|
+
"android",
|
|
22
|
+
"ios",
|
|
23
|
+
"SmoothClipView.podspec",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"!ios/build",
|
|
27
|
+
"!ios/tests",
|
|
28
|
+
"!android/build",
|
|
29
|
+
"!android/src/test",
|
|
30
|
+
"!**/__tests__",
|
|
31
|
+
"!**/__fixtures__",
|
|
32
|
+
"!**/__mocks__",
|
|
33
|
+
"!**/.*"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"example": "yarn workspace react-native-smooth-clip-view-example",
|
|
37
|
+
"clean": "del-cli lib",
|
|
38
|
+
"prepare": "bob build",
|
|
39
|
+
"typecheck": "tsc",
|
|
40
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
41
|
+
"test": "jest",
|
|
42
|
+
"test:android": "cd example/android && ./gradlew :react-native-smooth-clip-view:testDebugUnitTest",
|
|
43
|
+
"pack:check": "npm pack --dry-run --json",
|
|
44
|
+
"release": "release-it"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"react-native",
|
|
48
|
+
"reanimated",
|
|
49
|
+
"animation",
|
|
50
|
+
"clipping",
|
|
51
|
+
"fabric",
|
|
52
|
+
"ios",
|
|
53
|
+
"android",
|
|
54
|
+
"web"
|
|
55
|
+
],
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "git+https://github.com/kbrattli/react-native-smooth-clip-view.git"
|
|
59
|
+
},
|
|
60
|
+
"author": "kbrattli <kbrattli@live.no> (https://github.com/kbrattli)",
|
|
61
|
+
"license": "MIT",
|
|
62
|
+
"bugs": {
|
|
63
|
+
"url": "https://github.com/kbrattli/react-native-smooth-clip-view/issues"
|
|
64
|
+
},
|
|
65
|
+
"homepage": "https://github.com/kbrattli/react-native-smooth-clip-view#readme",
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public",
|
|
68
|
+
"registry": "https://registry.npmjs.org/"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@babel/core": "^7.29.7",
|
|
72
|
+
"@commitlint/config-conventional": "^21.0.2",
|
|
73
|
+
"@eslint/compat": "^2.1.0",
|
|
74
|
+
"@eslint/eslintrc": "^3.3.5",
|
|
75
|
+
"@eslint/js": "^10.0.1",
|
|
76
|
+
"@jest/globals": "^29.7.0",
|
|
77
|
+
"@react-native/babel-preset": "0.86.0",
|
|
78
|
+
"@react-native/eslint-config": "0.86.0",
|
|
79
|
+
"@react-native/jest-preset": "0.86.0",
|
|
80
|
+
"@react-native/metro-config": "0.86.0",
|
|
81
|
+
"@release-it/conventional-changelog": "^11.0.1",
|
|
82
|
+
"@types/react": "^19.2.0",
|
|
83
|
+
"commitlint": "^21.0.2",
|
|
84
|
+
"del-cli": "^7.0.0",
|
|
85
|
+
"eslint": "^9.39.4",
|
|
86
|
+
"eslint-config-prettier": "^10.1.8",
|
|
87
|
+
"eslint-plugin-ft-flow": "^3.0.11",
|
|
88
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
89
|
+
"hermes-eslint": "^0.37.0",
|
|
90
|
+
"jest": "^29.7.0",
|
|
91
|
+
"lefthook": "^2.1.9",
|
|
92
|
+
"prettier": "^3.8.3",
|
|
93
|
+
"react": "19.2.3",
|
|
94
|
+
"react-native": "0.86.0",
|
|
95
|
+
"react-native-builder-bob": "^0.43.0",
|
|
96
|
+
"react-native-reanimated": "4.5.0",
|
|
97
|
+
"react-native-worklets": "0.10.0",
|
|
98
|
+
"release-it": "^20.2.0",
|
|
99
|
+
"turbo": "^2.9.16",
|
|
100
|
+
"typescript": "^6.0.3"
|
|
101
|
+
},
|
|
102
|
+
"peerDependencies": {
|
|
103
|
+
"react": "*",
|
|
104
|
+
"react-native": ">=0.85.0",
|
|
105
|
+
"react-native-reanimated": ">=4.3.0"
|
|
106
|
+
},
|
|
107
|
+
"workspaces": [
|
|
108
|
+
"example"
|
|
109
|
+
],
|
|
110
|
+
"packageManager": "yarn@4.11.0",
|
|
111
|
+
"react-native-builder-bob": {
|
|
112
|
+
"source": "src",
|
|
113
|
+
"output": "lib",
|
|
114
|
+
"targets": [
|
|
115
|
+
[
|
|
116
|
+
"module",
|
|
117
|
+
{
|
|
118
|
+
"esm": true
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
[
|
|
122
|
+
"typescript",
|
|
123
|
+
{
|
|
124
|
+
"project": "tsconfig.build.json"
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
"codegenConfig": {
|
|
130
|
+
"name": "SmoothClipViewSpec",
|
|
131
|
+
"type": "components",
|
|
132
|
+
"jsSrcsDir": "src",
|
|
133
|
+
"android": {
|
|
134
|
+
"javaPackageName": "com.smoothclipview"
|
|
135
|
+
},
|
|
136
|
+
"ios": {
|
|
137
|
+
"components": {
|
|
138
|
+
"SmoothClipView": {
|
|
139
|
+
"className": "SmoothClipView"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"prettier": {
|
|
145
|
+
"quoteProps": "consistent",
|
|
146
|
+
"singleQuote": true,
|
|
147
|
+
"tabWidth": 2,
|
|
148
|
+
"trailingComma": "es5",
|
|
149
|
+
"useTabs": false
|
|
150
|
+
},
|
|
151
|
+
"jest": {
|
|
152
|
+
"preset": "@react-native/jest-preset",
|
|
153
|
+
"watchman": false,
|
|
154
|
+
"testEnvironmentOptions": {
|
|
155
|
+
"customExportConditions": [
|
|
156
|
+
"require",
|
|
157
|
+
"react-native",
|
|
158
|
+
"react-native-smooth-clip-view-source"
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
"modulePathIgnorePatterns": [
|
|
162
|
+
"<rootDir>/example/node_modules",
|
|
163
|
+
"<rootDir>/lib/"
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
"commitlint": {
|
|
167
|
+
"extends": [
|
|
168
|
+
"@commitlint/config-conventional"
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
"release-it": {
|
|
172
|
+
"git": {
|
|
173
|
+
"commitMessage": "chore: release ${version}",
|
|
174
|
+
"tagName": "v${version}"
|
|
175
|
+
},
|
|
176
|
+
"npm": {
|
|
177
|
+
"publish": true
|
|
178
|
+
},
|
|
179
|
+
"github": {
|
|
180
|
+
"release": true
|
|
181
|
+
},
|
|
182
|
+
"plugins": {
|
|
183
|
+
"@release-it/conventional-changelog": {
|
|
184
|
+
"preset": {
|
|
185
|
+
"name": "angular"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"create-react-native-library": {
|
|
191
|
+
"type": "fabric-view",
|
|
192
|
+
"languages": "kotlin-objc",
|
|
193
|
+
"tools": [
|
|
194
|
+
"eslint",
|
|
195
|
+
"jest",
|
|
196
|
+
"lefthook",
|
|
197
|
+
"release-it"
|
|
198
|
+
],
|
|
199
|
+
"version": "0.63.0"
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import { type ViewProps } from 'react-native';
|
|
4
|
+
import Animated, {
|
|
5
|
+
dispatchCommand,
|
|
6
|
+
useAnimatedReaction,
|
|
7
|
+
useAnimatedRef,
|
|
8
|
+
type SharedValue,
|
|
9
|
+
} from 'react-native-reanimated';
|
|
10
|
+
import NativeSmoothClipView, {
|
|
11
|
+
type NativeProps,
|
|
12
|
+
} from './SmoothClipViewNativeComponent';
|
|
13
|
+
import {
|
|
14
|
+
clipGeometryEquals,
|
|
15
|
+
isFiniteClipGeometry,
|
|
16
|
+
type ClipGeometry,
|
|
17
|
+
} from './geometry';
|
|
18
|
+
|
|
19
|
+
const AnimatedNativeSmoothClipView =
|
|
20
|
+
Animated.createAnimatedComponent(NativeSmoothClipView);
|
|
21
|
+
|
|
22
|
+
export type SmoothClipViewProps = ViewProps & {
|
|
23
|
+
initialClip: ClipGeometry;
|
|
24
|
+
animatedClip: SharedValue<ClipGeometry>;
|
|
25
|
+
children?: ReactNode;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function SmoothClipView({
|
|
29
|
+
initialClip,
|
|
30
|
+
animatedClip,
|
|
31
|
+
children,
|
|
32
|
+
...viewProps
|
|
33
|
+
}: SmoothClipViewProps) {
|
|
34
|
+
const nativeRef =
|
|
35
|
+
useAnimatedRef<React.ComponentRef<typeof NativeSmoothClipView>>();
|
|
36
|
+
|
|
37
|
+
useAnimatedReaction(
|
|
38
|
+
() => animatedClip.value,
|
|
39
|
+
(clip, previousClip) => {
|
|
40
|
+
if (
|
|
41
|
+
!isFiniteClipGeometry(clip) ||
|
|
42
|
+
clipGeometryEquals(previousClip, clip)
|
|
43
|
+
) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
dispatchCommand(nativeRef, 'setClipGeometry', [
|
|
48
|
+
clip.x,
|
|
49
|
+
clip.y,
|
|
50
|
+
clip.width,
|
|
51
|
+
clip.height,
|
|
52
|
+
clip.radius,
|
|
53
|
+
]);
|
|
54
|
+
},
|
|
55
|
+
[nativeRef, animatedClip]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const nativeProps: NativeProps = {
|
|
59
|
+
...viewProps,
|
|
60
|
+
initialClipX: initialClip.x,
|
|
61
|
+
initialClipY: initialClip.y,
|
|
62
|
+
initialClipWidth: initialClip.width,
|
|
63
|
+
initialClipHeight: initialClip.height,
|
|
64
|
+
initialClipRadius: initialClip.radius,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<AnimatedNativeSmoothClipView ref={nativeRef} {...nativeProps}>
|
|
69
|
+
{children}
|
|
70
|
+
</AnimatedNativeSmoothClipView>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type ViewProps, type ViewStyle } from 'react-native';
|
|
3
|
+
import Animated, {
|
|
4
|
+
useAnimatedReaction,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
useSharedValue,
|
|
7
|
+
type SharedValue,
|
|
8
|
+
} from 'react-native-reanimated';
|
|
9
|
+
import {
|
|
10
|
+
clipGeometryEquals,
|
|
11
|
+
isFiniteClipGeometry,
|
|
12
|
+
type ClipGeometry,
|
|
13
|
+
} from './geometry';
|
|
14
|
+
import { createWebClipPath } from './webClipPath';
|
|
15
|
+
|
|
16
|
+
export type SmoothClipViewProps = ViewProps & {
|
|
17
|
+
initialClip: ClipGeometry;
|
|
18
|
+
animatedClip: SharedValue<ClipGeometry>;
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Build-safe fallback. Web performance is intentionally outside the POC. */
|
|
23
|
+
export function SmoothClipView({
|
|
24
|
+
initialClip,
|
|
25
|
+
animatedClip,
|
|
26
|
+
children,
|
|
27
|
+
style,
|
|
28
|
+
...viewProps
|
|
29
|
+
}: SmoothClipViewProps) {
|
|
30
|
+
const acceptedClip = useSharedValue(initialClip);
|
|
31
|
+
const initialClipStyle = {
|
|
32
|
+
clipPath: createWebClipPath(initialClip),
|
|
33
|
+
overflow: 'hidden',
|
|
34
|
+
} as unknown as ViewStyle;
|
|
35
|
+
|
|
36
|
+
useAnimatedReaction(
|
|
37
|
+
() => animatedClip.value,
|
|
38
|
+
(clip, previousClip) => {
|
|
39
|
+
if (
|
|
40
|
+
isFiniteClipGeometry(clip) &&
|
|
41
|
+
!clipGeometryEquals(previousClip, clip)
|
|
42
|
+
) {
|
|
43
|
+
acceptedClip.value = clip;
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[animatedClip, acceptedClip]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const clipStyle = useAnimatedStyle(() => {
|
|
50
|
+
const clip = acceptedClip.value;
|
|
51
|
+
return {
|
|
52
|
+
clipPath: createWebClipPath(clip),
|
|
53
|
+
overflow: 'hidden',
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Animated.View {...viewProps} style={[style, initialClipStyle, clipStyle]}>
|
|
59
|
+
{children}
|
|
60
|
+
</Animated.View>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codegenNativeCommands,
|
|
3
|
+
codegenNativeComponent,
|
|
4
|
+
type CodegenTypes,
|
|
5
|
+
type HostComponent,
|
|
6
|
+
type ViewProps,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
|
|
9
|
+
export interface NativeProps extends ViewProps {
|
|
10
|
+
initialClipX: CodegenTypes.Double;
|
|
11
|
+
initialClipY: CodegenTypes.Double;
|
|
12
|
+
initialClipWidth: CodegenTypes.Double;
|
|
13
|
+
initialClipHeight: CodegenTypes.Double;
|
|
14
|
+
initialClipRadius: CodegenTypes.Double;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface NativeCommands {
|
|
18
|
+
setClipGeometry: (
|
|
19
|
+
viewRef: React.ElementRef<HostComponent<NativeProps>>,
|
|
20
|
+
x: CodegenTypes.Double,
|
|
21
|
+
y: CodegenTypes.Double,
|
|
22
|
+
width: CodegenTypes.Double,
|
|
23
|
+
height: CodegenTypes.Double,
|
|
24
|
+
radius: CodegenTypes.Double
|
|
25
|
+
) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
|
29
|
+
supportedCommands: ['setClipGeometry'],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export default codegenNativeComponent<NativeProps>(
|
|
33
|
+
'SmoothClipView'
|
|
34
|
+
) as HostComponent<NativeProps>;
|
package/src/geometry.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export type ClipGeometry = Readonly<{
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
radius: number;
|
|
7
|
+
}>;
|
|
8
|
+
|
|
9
|
+
export type ClipBounds = Readonly<{
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
export function isFiniteClipGeometry(geometry: ClipGeometry): boolean {
|
|
15
|
+
'worklet';
|
|
16
|
+
return (
|
|
17
|
+
Number.isFinite(geometry.x) &&
|
|
18
|
+
Number.isFinite(geometry.y) &&
|
|
19
|
+
Number.isFinite(geometry.width) &&
|
|
20
|
+
Number.isFinite(geometry.height) &&
|
|
21
|
+
Number.isFinite(geometry.radius)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function clipGeometryEquals(
|
|
26
|
+
first: ClipGeometry | null,
|
|
27
|
+
second: ClipGeometry
|
|
28
|
+
): boolean {
|
|
29
|
+
'worklet';
|
|
30
|
+
return (
|
|
31
|
+
first !== null &&
|
|
32
|
+
first.x === second.x &&
|
|
33
|
+
first.y === second.y &&
|
|
34
|
+
first.width === second.width &&
|
|
35
|
+
first.height === second.height &&
|
|
36
|
+
first.radius === second.radius
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Mirrors the native geometry contract for tests and non-native fallbacks.
|
|
42
|
+
* Native remains authoritative because its bounds are the actual rendered size.
|
|
43
|
+
*/
|
|
44
|
+
export function normalizeClipGeometry(
|
|
45
|
+
geometry: ClipGeometry,
|
|
46
|
+
bounds: ClipBounds
|
|
47
|
+
): ClipGeometry | null {
|
|
48
|
+
'worklet';
|
|
49
|
+
const values = [
|
|
50
|
+
geometry.x,
|
|
51
|
+
geometry.y,
|
|
52
|
+
geometry.width,
|
|
53
|
+
geometry.height,
|
|
54
|
+
geometry.radius,
|
|
55
|
+
bounds.width,
|
|
56
|
+
bounds.height,
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
if (!values.every(Number.isFinite)) return null;
|
|
60
|
+
|
|
61
|
+
const boundsWidth = Math.max(0, bounds.width);
|
|
62
|
+
const boundsHeight = Math.max(0, bounds.height);
|
|
63
|
+
const requestedWidth = Math.max(0, geometry.width);
|
|
64
|
+
const requestedHeight = Math.max(0, geometry.height);
|
|
65
|
+
|
|
66
|
+
const left = Math.min(boundsWidth, Math.max(0, geometry.x));
|
|
67
|
+
const top = Math.min(boundsHeight, Math.max(0, geometry.y));
|
|
68
|
+
const right = Math.min(boundsWidth, Math.max(0, geometry.x + requestedWidth));
|
|
69
|
+
const bottom = Math.min(
|
|
70
|
+
boundsHeight,
|
|
71
|
+
Math.max(0, geometry.y + requestedHeight)
|
|
72
|
+
);
|
|
73
|
+
const width = Math.max(0, right - left);
|
|
74
|
+
const height = Math.max(0, bottom - top);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
x: left,
|
|
78
|
+
y: top,
|
|
79
|
+
width,
|
|
80
|
+
height,
|
|
81
|
+
radius: Math.min(Math.max(0, geometry.radius), Math.min(width, height) / 2),
|
|
82
|
+
};
|
|
83
|
+
}
|
package/src/index.ts
ADDED
package/src/index.web.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ClipGeometry } from './geometry';
|
|
2
|
+
|
|
3
|
+
function px(value: number): string {
|
|
4
|
+
'worklet';
|
|
5
|
+
return `${value}px`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a fixed-host CSS clip. Percentage/right/bottom anchored descendants
|
|
10
|
+
* continue resolving against the host because this never changes its layout.
|
|
11
|
+
*/
|
|
12
|
+
export function createWebClipPath(geometry: ClipGeometry): string {
|
|
13
|
+
'worklet';
|
|
14
|
+
const width = Math.max(0, geometry.width);
|
|
15
|
+
const height = Math.max(0, geometry.height);
|
|
16
|
+
const left = Math.max(0, geometry.x);
|
|
17
|
+
const top = Math.max(0, geometry.y);
|
|
18
|
+
const rightEdge = Math.max(0, geometry.x + width);
|
|
19
|
+
const bottomEdge = Math.max(0, geometry.y + height);
|
|
20
|
+
const radius = Math.min(
|
|
21
|
+
Math.max(0, geometry.radius),
|
|
22
|
+
Math.min(width, height) / 2
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return `inset(${px(top)} max(0px, calc(100% - ${px(
|
|
26
|
+
rightEdge
|
|
27
|
+
)})) max(0px, calc(100% - ${px(bottomEdge)})) ${px(
|
|
28
|
+
left
|
|
29
|
+
)} round ${px(radius)})`;
|
|
30
|
+
}
|